flare 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/flare/deadline.rb +27 -0
- data/lib/flare/filtering_span_processor.rb +180 -30
- data/lib/flare/http_transport.rb +24 -10
- data/lib/flare/lifecycle.rb +39 -0
- data/lib/flare/metric_flusher.rb +154 -12
- data/lib/flare/metric_storage.rb +20 -0
- data/lib/flare/metric_submitter.rb +34 -11
- data/lib/flare/recording_batch_span_processor.rb +238 -0
- data/lib/flare/sqlite_exporter.rb +80 -25
- data/lib/flare/trace_exporter.rb +44 -9
- data/lib/flare/version.rb +1 -1
- data/lib/flare.rb +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cee6e0910ebcd04f073efc41638823f7ed4d7abf511ab71b68caead994997af1
|
|
4
|
+
data.tar.gz: f28d669e444b2abb8b6043990b8f1cfc5cbd39abc14d25942cf5f41e08a80cb2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06647d35a64ae38dafb6e946b181fd87a8b8dc8e7065c5b0d4af3cb47419bd5be5dc9e930f3aa322740e824279169d9b2ae867c2b3c14dfda22c1d01be25507d
|
|
7
|
+
data.tar.gz: 837f70a2ef44f4d0f8c415cc15c6455ab5250b63e7dd06580ad640492385d8d1e6842cda3f56b036d918bb38c7b55438a2d60546a6c41fd7ce77a429b2d1793a
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flare
|
|
4
|
+
# A small monotonic deadline shared by lifecycle operations. A nil timeout
|
|
5
|
+
# represents an unbounded operation.
|
|
6
|
+
class Deadline
|
|
7
|
+
def initialize(timeout)
|
|
8
|
+
@expires_at = monotonic_now + [timeout.to_f, 0].max unless timeout.nil?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def remaining
|
|
12
|
+
return nil unless @expires_at
|
|
13
|
+
|
|
14
|
+
[@expires_at - monotonic_now, 0].max
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def expired?
|
|
18
|
+
remaining == 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def monotonic_now
|
|
24
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -4,6 +4,8 @@ require "concurrent/atomic/atomic_fixnum"
|
|
|
4
4
|
require "logger"
|
|
5
5
|
require "opentelemetry/sdk"
|
|
6
6
|
|
|
7
|
+
require_relative "deadline"
|
|
8
|
+
|
|
7
9
|
module Flare
|
|
8
10
|
# BSP-shaped span processor whose filter is `sampled OR marked` instead
|
|
9
11
|
# of BSP's `sampled` (BSP early-returns on RECORD_ONLY spans -- our
|
|
@@ -17,6 +19,7 @@ module Flare
|
|
|
17
19
|
class FilteringSpanProcessor
|
|
18
20
|
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
19
21
|
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
|
|
22
|
+
TIMEOUT = OpenTelemetry::SDK::Trace::Export::TIMEOUT
|
|
20
23
|
|
|
21
24
|
DEFAULT_MAX_QUEUE = 5_000
|
|
22
25
|
DEFAULT_FLUSH_INTERVAL = 5 # seconds
|
|
@@ -47,6 +50,10 @@ module Flare
|
|
|
47
50
|
@mutex = Mutex.new
|
|
48
51
|
@cond = ConditionVariable.new
|
|
49
52
|
@stopped = false
|
|
53
|
+
@active_exports = 0
|
|
54
|
+
@export_completion_sequence = 0
|
|
55
|
+
@last_export_result = SUCCESS
|
|
56
|
+
@flush_owner = nil
|
|
50
57
|
@pid = $$
|
|
51
58
|
|
|
52
59
|
@dropped_count = Concurrent::AtomicFixnum.new(0)
|
|
@@ -78,19 +85,43 @@ module Flare
|
|
|
78
85
|
end
|
|
79
86
|
|
|
80
87
|
def force_flush(timeout: nil)
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
detect_forking
|
|
89
|
+
deadline = Deadline.new(timeout)
|
|
90
|
+
return TIMEOUT unless begin_flush(deadline)
|
|
91
|
+
prior_result = @flush_prior_result
|
|
92
|
+
|
|
93
|
+
batch = snapshot_for_flush(deadline)
|
|
94
|
+
return TIMEOUT unless batch
|
|
95
|
+
|
|
96
|
+
operation = start_flush_export(batch, deadline)
|
|
97
|
+
result = wait_for_flush_export(operation, deadline)
|
|
98
|
+
[prior_result, result].max
|
|
99
|
+
ensure
|
|
100
|
+
finish_flush if @flush_owner == Thread.current
|
|
83
101
|
end
|
|
84
102
|
|
|
85
103
|
def shutdown(timeout: nil)
|
|
86
|
-
|
|
104
|
+
detect_forking
|
|
105
|
+
deadline = Deadline.new(timeout)
|
|
106
|
+
return TIMEOUT unless lock_before_deadline(deadline)
|
|
107
|
+
|
|
108
|
+
begin
|
|
87
109
|
@stopped = true
|
|
88
110
|
@cond.broadcast
|
|
111
|
+
ensure
|
|
112
|
+
@mutex.unlock
|
|
89
113
|
end
|
|
90
|
-
@worker.join(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
114
|
+
@worker.join(deadline.remaining || 5)
|
|
115
|
+
return TIMEOUT if @worker.alive? || deadline.expired?
|
|
116
|
+
|
|
117
|
+
result = force_flush(timeout: deadline.remaining)
|
|
118
|
+
return result unless result == SUCCESS
|
|
119
|
+
return TIMEOUT if deadline.expired?
|
|
120
|
+
|
|
121
|
+
exporter_result = @exporter.shutdown(timeout: deadline.remaining) if @exporter.respond_to?(:shutdown)
|
|
122
|
+
return TIMEOUT if deadline.expired?
|
|
123
|
+
|
|
124
|
+
exporter_result || SUCCESS
|
|
94
125
|
end
|
|
95
126
|
|
|
96
127
|
def buffer_size
|
|
@@ -124,7 +155,8 @@ module Flare
|
|
|
124
155
|
until stopped?
|
|
125
156
|
@mutex.synchronize do
|
|
126
157
|
timeout = next_wait_timeout
|
|
127
|
-
@
|
|
158
|
+
waiting_for_export = @flush_owner || @active_exports.positive?
|
|
159
|
+
@cond.wait(@mutex, timeout) if (@ready_queue.empty? || waiting_for_export) && !@stopped
|
|
128
160
|
end
|
|
129
161
|
drain_and_export
|
|
130
162
|
end
|
|
@@ -134,30 +166,141 @@ module Flare
|
|
|
134
166
|
@mutex.synchronize { @stopped }
|
|
135
167
|
end
|
|
136
168
|
|
|
137
|
-
def drain_and_export
|
|
169
|
+
def drain_and_export
|
|
138
170
|
batch = nil
|
|
139
171
|
@mutex.synchronize do
|
|
140
172
|
promote_due_delayed_traces
|
|
173
|
+
return if @ready_queue.empty? || @flush_owner || @active_exports.positive?
|
|
141
174
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
175
|
+
batch = @ready_queue
|
|
176
|
+
@ready_queue = []
|
|
177
|
+
@active_exports += 1
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
result = export_batch(batch, timeout: @export_timeout)
|
|
181
|
+
ensure
|
|
182
|
+
export_finished(result || FAILURE) if batch
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def begin_flush(deadline)
|
|
186
|
+
return false unless lock_before_deadline(deadline)
|
|
187
|
+
|
|
188
|
+
begin
|
|
189
|
+
initial_sequence = @export_completion_sequence
|
|
190
|
+
while @flush_owner && @flush_owner != Thread.current
|
|
191
|
+
return false if deadline.expired?
|
|
192
|
+
|
|
193
|
+
@cond.wait(@mutex, deadline.remaining)
|
|
149
194
|
end
|
|
195
|
+
@flush_owner = Thread.current
|
|
196
|
+
|
|
197
|
+
while @active_exports.positive?
|
|
198
|
+
return false if deadline.expired?
|
|
150
199
|
|
|
151
|
-
|
|
200
|
+
@cond.wait(@mutex, deadline.remaining)
|
|
201
|
+
end
|
|
202
|
+
@flush_prior_result = if @export_completion_sequence > initial_sequence
|
|
203
|
+
@last_export_result
|
|
204
|
+
else
|
|
205
|
+
SUCCESS
|
|
206
|
+
end
|
|
207
|
+
ensure
|
|
208
|
+
@mutex.unlock
|
|
209
|
+
end
|
|
210
|
+
true
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def finish_flush
|
|
214
|
+
@mutex.synchronize do
|
|
215
|
+
@flush_owner = nil
|
|
216
|
+
@cond.broadcast
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def snapshot_for_flush(deadline)
|
|
221
|
+
return unless lock_before_deadline(deadline)
|
|
222
|
+
|
|
223
|
+
begin
|
|
224
|
+
@ready_queue.concat(@pending_by_trace.values.flatten)
|
|
225
|
+
@pending_by_trace.clear
|
|
226
|
+
@trace_order.clear
|
|
227
|
+
@pending_count = 0
|
|
228
|
+
unmark_delayed_traces
|
|
229
|
+
@delayed_ready_by_trace.clear
|
|
152
230
|
batch = @ready_queue
|
|
153
231
|
@ready_queue = []
|
|
232
|
+
batch
|
|
233
|
+
ensure
|
|
234
|
+
@mutex.unlock
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def start_flush_export(batch, deadline)
|
|
239
|
+
operation = { done: false, result: nil }
|
|
240
|
+
@mutex.synchronize { @active_exports += 1 }
|
|
241
|
+
Thread.new do
|
|
242
|
+
result = batch.empty? ? SUCCESS : export_batch(batch, timeout: deadline.remaining)
|
|
243
|
+
if result == SUCCESS && !deadline.expired? && @exporter.respond_to?(:force_flush)
|
|
244
|
+
result = @exporter.force_flush(timeout: deadline.remaining)
|
|
245
|
+
end
|
|
246
|
+
result = TIMEOUT if deadline.expired?
|
|
247
|
+
operation[:result] = result
|
|
248
|
+
rescue StandardError => e
|
|
249
|
+
@exception_count.increment
|
|
250
|
+
@logger.warn("[Flare::FilteringSpanProcessor] force flush failed: #{e.class}: #{e.message}")
|
|
251
|
+
operation[:result] = FAILURE
|
|
252
|
+
ensure
|
|
253
|
+
@mutex.synchronize do
|
|
254
|
+
operation[:done] = true
|
|
255
|
+
complete_export(operation[:result])
|
|
256
|
+
end
|
|
154
257
|
end
|
|
258
|
+
operation
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def wait_for_flush_export(operation, deadline)
|
|
262
|
+
@mutex.synchronize do
|
|
263
|
+
until operation[:done]
|
|
264
|
+
return TIMEOUT if deadline.expired?
|
|
155
265
|
|
|
156
|
-
|
|
266
|
+
@cond.wait(@mutex, deadline.remaining)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
operation[:result]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def export_batch(batch, timeout:)
|
|
273
|
+
result = @exporter.export(batch, timeout: timeout)
|
|
157
274
|
@failed_export_count.increment if result != SUCCESS
|
|
275
|
+
result
|
|
158
276
|
rescue StandardError => e
|
|
159
277
|
@exception_count.increment
|
|
160
278
|
@logger.warn("[Flare::FilteringSpanProcessor] export failed: #{e.class}: #{e.message}")
|
|
279
|
+
FAILURE
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def export_finished(result)
|
|
283
|
+
@mutex.synchronize do
|
|
284
|
+
complete_export(result)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def complete_export(result)
|
|
289
|
+
@active_exports -= 1
|
|
290
|
+
@export_completion_sequence += 1
|
|
291
|
+
@last_export_result = result || FAILURE
|
|
292
|
+
@cond.broadcast
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def lock_before_deadline(deadline)
|
|
296
|
+
return @mutex.lock unless deadline.remaining
|
|
297
|
+
|
|
298
|
+
until @mutex.try_lock
|
|
299
|
+
return false if deadline.expired?
|
|
300
|
+
|
|
301
|
+
sleep([deadline.remaining, 0.001].min)
|
|
302
|
+
end
|
|
303
|
+
true
|
|
161
304
|
end
|
|
162
305
|
|
|
163
306
|
def mark_trace_ready(trace_id)
|
|
@@ -255,18 +398,25 @@ module Flare
|
|
|
255
398
|
def detect_forking
|
|
256
399
|
return if @pid == $$
|
|
257
400
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
401
|
+
# The child only retains the forking thread. Replace synchronization
|
|
402
|
+
# objects so it cannot inherit locks or active-export bookkeeping owned
|
|
403
|
+
# by vanished threads.
|
|
404
|
+
@pid = $$
|
|
405
|
+
@mutex = Mutex.new
|
|
406
|
+
@cond = ConditionVariable.new
|
|
407
|
+
@pending_by_trace = {}
|
|
408
|
+
@trace_order = []
|
|
409
|
+
@ready_queue = []
|
|
410
|
+
@delayed_ready_by_trace = {}
|
|
411
|
+
@pending_count = 0
|
|
412
|
+
@active_exports = 0
|
|
413
|
+
@export_completion_sequence = 0
|
|
414
|
+
@last_export_result = SUCCESS
|
|
415
|
+
@flush_prior_result = SUCCESS
|
|
416
|
+
@flush_owner = nil
|
|
417
|
+
@stopped = false
|
|
418
|
+
@worker = nil
|
|
419
|
+
start_worker
|
|
270
420
|
end
|
|
271
421
|
|
|
272
422
|
def start_worker
|
data/lib/flare/http_transport.rb
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "uri"
|
|
5
5
|
|
|
6
|
+
require_relative "deadline"
|
|
7
|
+
|
|
6
8
|
module Flare
|
|
7
9
|
# Tiny HTTP wrapper used by TraceExporter (and anything else that wants
|
|
8
10
|
# to PUT/POST without pulling in a heavy client). Designed for injection
|
|
9
11
|
# at the boundary so tests can swap in a recording fake; no other moving
|
|
10
12
|
# parts.
|
|
11
13
|
class HttpTransport
|
|
14
|
+
DeadlineExceeded = Class.new(StandardError)
|
|
12
15
|
DEFAULT_OPEN_TIMEOUT = 2
|
|
13
16
|
DEFAULT_READ_TIMEOUT = 5
|
|
14
17
|
DEFAULT_WRITE_TIMEOUT = 5
|
|
@@ -28,35 +31,46 @@ module Flare
|
|
|
28
31
|
@write_timeout = write_timeout
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
def get(url, headers = {})
|
|
32
|
-
request(url, nil, headers, Net::HTTP::Get)
|
|
34
|
+
def get(url, headers = {}, timeout: nil)
|
|
35
|
+
request(url, nil, headers, Net::HTTP::Get, timeout: timeout)
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
def put(url, body, headers = {})
|
|
36
|
-
request(url, body, headers, Net::HTTP::Put)
|
|
38
|
+
def put(url, body, headers = {}, timeout: nil)
|
|
39
|
+
request(url, body, headers, Net::HTTP::Put, timeout: timeout)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
|
-
def post(url, body, headers = {})
|
|
40
|
-
request(url, body, headers, Net::HTTP::Post)
|
|
42
|
+
def post(url, body, headers = {}, timeout: nil)
|
|
43
|
+
request(url, body, headers, Net::HTTP::Post, timeout: timeout)
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
private
|
|
44
47
|
|
|
45
|
-
def request(url, body, headers, klass)
|
|
48
|
+
def request(url, body, headers, klass, timeout: nil)
|
|
49
|
+
deadline = Deadline.new(timeout)
|
|
50
|
+
raise DeadlineExceeded if deadline.expired?
|
|
51
|
+
|
|
46
52
|
uri = URI(url)
|
|
47
53
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
48
54
|
http.use_ssl = uri.scheme == "https"
|
|
49
|
-
http.open_timeout = @open_timeout
|
|
50
|
-
http.read_timeout = @read_timeout
|
|
51
|
-
http.write_timeout = @write_timeout if http.respond_to?(:write_timeout=)
|
|
55
|
+
http.open_timeout = effective_timeout(@open_timeout, deadline.remaining)
|
|
56
|
+
http.read_timeout = effective_timeout(@read_timeout, deadline.remaining)
|
|
57
|
+
http.write_timeout = effective_timeout(@write_timeout, deadline.remaining) if http.respond_to?(:write_timeout=)
|
|
52
58
|
|
|
53
59
|
req = klass.new(uri.request_uri == "" ? "/" : uri.request_uri)
|
|
54
60
|
headers.each { |k, v| req[k] = v }
|
|
55
61
|
req.body = body if body
|
|
56
62
|
|
|
57
63
|
response = http.request(req)
|
|
64
|
+
raise DeadlineExceeded if deadline.expired?
|
|
65
|
+
|
|
58
66
|
hash = response.each_header.to_h
|
|
59
67
|
Response.new(code: response.code.to_s, body: response.body, headers: hash)
|
|
60
68
|
end
|
|
69
|
+
|
|
70
|
+
def effective_timeout(configured_timeout, remaining)
|
|
71
|
+
return configured_timeout unless remaining
|
|
72
|
+
|
|
73
|
+
[configured_timeout, remaining].min
|
|
74
|
+
end
|
|
61
75
|
end
|
|
62
76
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "opentelemetry/sdk"
|
|
4
|
+
|
|
5
|
+
require_relative "deadline"
|
|
6
|
+
|
|
7
|
+
module Flare
|
|
8
|
+
module Lifecycle
|
|
9
|
+
# Flush all trace processors and the separate aggregated metric pipeline
|
|
10
|
+
# using one monotonic timeout budget. This is safe to call from lifecycle
|
|
11
|
+
# hooks for short-lived and fork-per-job workers.
|
|
12
|
+
def force_flush(timeout: nil)
|
|
13
|
+
deadline = Deadline.new(timeout)
|
|
14
|
+
results = []
|
|
15
|
+
|
|
16
|
+
results << tracer_provider_for_flush.force_flush(timeout: deadline.remaining)
|
|
17
|
+
return OpenTelemetry::SDK::Trace::Export::TIMEOUT if deadline.expired?
|
|
18
|
+
|
|
19
|
+
flusher = metric_flusher_for_flush
|
|
20
|
+
results << flusher.force_flush(timeout: deadline.remaining) if flusher
|
|
21
|
+
return OpenTelemetry::SDK::Trace::Export::TIMEOUT if deadline.expired?
|
|
22
|
+
|
|
23
|
+
results.max || OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
24
|
+
rescue => e
|
|
25
|
+
warn "[Flare] Telemetry flush error: #{e.message}"
|
|
26
|
+
OpenTelemetry::SDK::Trace::Export::FAILURE
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def tracer_provider_for_flush
|
|
30
|
+
OpenTelemetry.tracer_provider
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def metric_flusher_for_flush
|
|
34
|
+
@metric_flusher
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
extend Lifecycle
|
|
39
|
+
end
|
data/lib/flare/metric_flusher.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require "concurrent/timer_task"
|
|
4
4
|
require "concurrent/executor/fixed_thread_pool"
|
|
5
|
+
require "opentelemetry/sdk"
|
|
6
|
+
|
|
7
|
+
require_relative "deadline"
|
|
5
8
|
|
|
6
9
|
module Flare
|
|
7
10
|
# Background threads that periodically drain in-memory metrics and submit
|
|
@@ -10,6 +13,9 @@ module Flare
|
|
|
10
13
|
#
|
|
11
14
|
# Fork-safe: detects forked processes and restarts automatically.
|
|
12
15
|
class MetricFlusher
|
|
16
|
+
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
17
|
+
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
|
|
18
|
+
TIMEOUT = OpenTelemetry::SDK::Trace::Export::TIMEOUT
|
|
13
19
|
DEFAULT_INTERVAL = 60 # seconds
|
|
14
20
|
DEFAULT_SHUTDOWN_TIMEOUT = 5 # seconds
|
|
15
21
|
|
|
@@ -23,6 +29,7 @@ module Flare
|
|
|
23
29
|
@health_reporters = Array(health_reporters)
|
|
24
30
|
@pid = $$
|
|
25
31
|
@stopped = false
|
|
32
|
+
initialize_synchronization
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
def start
|
|
@@ -40,23 +47,25 @@ module Flare
|
|
|
40
47
|
}) { post_to_pool }
|
|
41
48
|
end
|
|
42
49
|
|
|
43
|
-
def stop
|
|
50
|
+
def stop(timeout: @shutdown_timeout)
|
|
44
51
|
return if @stopped
|
|
45
52
|
|
|
53
|
+
deadline = Deadline.new(timeout)
|
|
46
54
|
@stopped = true
|
|
47
55
|
|
|
48
56
|
log "Shutting down metrics flusher, draining remaining metrics..."
|
|
49
57
|
|
|
50
58
|
if @timer
|
|
51
59
|
@timer.shutdown
|
|
52
|
-
@timer.wait_for_termination(1)
|
|
60
|
+
@timer.wait_for_termination([deadline.remaining || 1, 1].min)
|
|
53
61
|
@timer.kill unless @timer.shutdown?
|
|
54
62
|
end
|
|
55
63
|
|
|
64
|
+
force_flush(timeout: deadline.remaining)
|
|
65
|
+
|
|
56
66
|
if @pool
|
|
57
|
-
post_to_pool # one last drain
|
|
58
67
|
@pool.shutdown
|
|
59
|
-
pool_terminated = @pool.wait_for_termination(@shutdown_timeout)
|
|
68
|
+
pool_terminated = @pool.wait_for_termination(deadline.remaining || @shutdown_timeout)
|
|
60
69
|
@pool.kill unless pool_terminated
|
|
61
70
|
end
|
|
62
71
|
|
|
@@ -70,14 +79,11 @@ module Flare
|
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
# Manually trigger a flush (useful for testing or forced flushes).
|
|
73
|
-
def flush_now
|
|
82
|
+
def flush_now(timeout: nil)
|
|
74
83
|
return 0 unless @storage && @submitter
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return 0 if drained.empty?
|
|
79
|
-
|
|
80
|
-
count, error = @submitter.submit(drained)
|
|
85
|
+
detect_forking
|
|
86
|
+
count, error, = flush_synchronously(Deadline.new(timeout))
|
|
81
87
|
if error
|
|
82
88
|
warn "[Flare] Metric submission error: #{error.message}"
|
|
83
89
|
end
|
|
@@ -87,6 +93,21 @@ module Flare
|
|
|
87
93
|
0
|
|
88
94
|
end
|
|
89
95
|
|
|
96
|
+
def force_flush(timeout: nil)
|
|
97
|
+
return SUCCESS unless @storage && @submitter
|
|
98
|
+
|
|
99
|
+
detect_forking
|
|
100
|
+
deadline = Deadline.new(timeout)
|
|
101
|
+
_count, error, timed_out = flush_synchronously(deadline)
|
|
102
|
+
return TIMEOUT if timed_out || deadline.expired?
|
|
103
|
+
return FAILURE if error
|
|
104
|
+
|
|
105
|
+
SUCCESS
|
|
106
|
+
rescue => e
|
|
107
|
+
warn "[Flare] Metric flush error: #{e.message}"
|
|
108
|
+
FAILURE
|
|
109
|
+
end
|
|
110
|
+
|
|
90
111
|
def running?
|
|
91
112
|
@timer&.running? || false
|
|
92
113
|
end
|
|
@@ -96,22 +117,46 @@ module Flare
|
|
|
96
117
|
# after_fork hooks.
|
|
97
118
|
def after_fork
|
|
98
119
|
@pid = $$
|
|
99
|
-
|
|
120
|
+
@storage.after_fork if @storage.respond_to?(:after_fork)
|
|
121
|
+
initialize_synchronization
|
|
122
|
+
@timer = nil
|
|
123
|
+
@pool = nil
|
|
124
|
+
start
|
|
100
125
|
end
|
|
101
126
|
|
|
102
127
|
private
|
|
103
128
|
|
|
129
|
+
def detect_forking
|
|
130
|
+
after_fork if @pid != $$
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def initialize_synchronization
|
|
134
|
+
@submission_mutex = Mutex.new
|
|
135
|
+
@submission_condition = ConditionVariable.new
|
|
136
|
+
@pending_submissions = 0
|
|
137
|
+
@flush_owner = nil
|
|
138
|
+
end
|
|
139
|
+
|
|
104
140
|
def post_to_pool
|
|
141
|
+
return unless reserve_background_submission
|
|
142
|
+
|
|
105
143
|
record_health_metrics
|
|
106
144
|
drained = @storage.drain
|
|
107
145
|
if drained.empty?
|
|
108
146
|
log "No metrics to flush"
|
|
147
|
+
background_submission_finished
|
|
109
148
|
return
|
|
110
149
|
end
|
|
111
150
|
|
|
112
151
|
log "Drained #{drained.size} metric keys for submission"
|
|
113
|
-
@pool.post
|
|
152
|
+
posted = @pool.post do
|
|
153
|
+
submit_to_cloud(drained)
|
|
154
|
+
ensure
|
|
155
|
+
background_submission_finished
|
|
156
|
+
end
|
|
157
|
+
background_submission_finished unless posted
|
|
114
158
|
rescue => e
|
|
159
|
+
background_submission_finished
|
|
115
160
|
warn "[Flare] Metric drain error: #{e.message}"
|
|
116
161
|
end
|
|
117
162
|
|
|
@@ -124,6 +169,103 @@ module Flare
|
|
|
124
169
|
warn "[Flare] Metric submission error: #{e.message}"
|
|
125
170
|
end
|
|
126
171
|
|
|
172
|
+
def reserve_background_submission
|
|
173
|
+
@submission_mutex.synchronize do
|
|
174
|
+
return false if @flush_owner || @pending_submissions.positive?
|
|
175
|
+
|
|
176
|
+
@pending_submissions += 1
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def background_submission_finished
|
|
182
|
+
@submission_mutex.synchronize do
|
|
183
|
+
@pending_submissions -= 1 if @pending_submissions.positive?
|
|
184
|
+
@submission_condition.broadcast
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def flush_synchronously(deadline)
|
|
189
|
+
return [0, nil, true] unless begin_synchronous_flush(deadline)
|
|
190
|
+
|
|
191
|
+
record_health_metrics
|
|
192
|
+
drained = @storage.drain
|
|
193
|
+
return [0, nil, false] if drained.empty?
|
|
194
|
+
|
|
195
|
+
submit_with_deadline(drained, deadline)
|
|
196
|
+
ensure
|
|
197
|
+
finish_synchronous_flush if @flush_owner == Thread.current
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def begin_synchronous_flush(deadline)
|
|
201
|
+
@submission_mutex.synchronize do
|
|
202
|
+
while @flush_owner && @flush_owner != Thread.current
|
|
203
|
+
return false if deadline.expired?
|
|
204
|
+
|
|
205
|
+
@submission_condition.wait(@submission_mutex, deadline.remaining)
|
|
206
|
+
end
|
|
207
|
+
@flush_owner = Thread.current
|
|
208
|
+
|
|
209
|
+
while @pending_submissions.positive?
|
|
210
|
+
return false if deadline.expired?
|
|
211
|
+
|
|
212
|
+
@submission_condition.wait(@submission_mutex, deadline.remaining)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
true
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def finish_synchronous_flush
|
|
219
|
+
@submission_mutex.synchronize do
|
|
220
|
+
@flush_owner = nil
|
|
221
|
+
@submission_condition.broadcast
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def submit_metrics(drained, timeout:)
|
|
226
|
+
parameters = @submitter.method(:submit).parameters
|
|
227
|
+
accepts_timeout = parameters.any? do |type, name|
|
|
228
|
+
type == :keyrest || ([:key, :keyreq].include?(type) && name == :timeout)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if accepts_timeout
|
|
232
|
+
@submitter.submit(drained, timeout: timeout)
|
|
233
|
+
else
|
|
234
|
+
@submitter.submit(drained)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def submit_with_deadline(drained, deadline)
|
|
239
|
+
operation = { done: false, count: 0, error: nil }
|
|
240
|
+
@submission_mutex.synchronize { @pending_submissions += 1 }
|
|
241
|
+
Thread.new do
|
|
242
|
+
operation[:count], operation[:error] = submit_metrics(drained, timeout: deadline.remaining)
|
|
243
|
+
rescue => e
|
|
244
|
+
operation[:error] = e
|
|
245
|
+
ensure
|
|
246
|
+
@submission_mutex.synchronize do
|
|
247
|
+
operation[:done] = true
|
|
248
|
+
@pending_submissions -= 1
|
|
249
|
+
@submission_condition.broadcast
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
@submission_mutex.synchronize do
|
|
254
|
+
until operation[:done]
|
|
255
|
+
return [0, nil, true] if deadline.expired?
|
|
256
|
+
|
|
257
|
+
@submission_condition.wait(@submission_mutex, deadline.remaining)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
timed_out = deadline.expired? || deadline_error?(operation[:error])
|
|
262
|
+
[operation[:count], operation[:error], timed_out]
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def deadline_error?(error)
|
|
266
|
+
defined?(MetricSubmitter::DeadlineExceeded) && error.is_a?(MetricSubmitter::DeadlineExceeded)
|
|
267
|
+
end
|
|
268
|
+
|
|
127
269
|
def record_health_metrics
|
|
128
270
|
@health_reporters.each { |reporter| reporter.record(@storage) }
|
|
129
271
|
rescue => e
|