flare 0.4.0 → 1.0.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.
Potentially problematic release.
This version of flare might be problematic. Click here for more details.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/flare.gemspec +66 -0
- data/lib/flare/active_record.rb +102 -0
- data/lib/flare/collection.rb +47 -0
- data/lib/flare/configuration.rb +64 -123
- data/lib/flare/index_builder.rb +24 -0
- data/lib/flare/session.rb +142 -0
- data/lib/flare/tasks.rb +18 -0
- data/lib/flare.rb +19 -537
- data/test/helper.rb +10 -0
- data/test/test_flare.rb +7 -0
- metadata +89 -242
- checksums.yaml +0 -7
- data/CHANGELOG.md +0 -3
- data/LICENSE.txt +0 -21
- data/README.md +0 -148
- data/app/controllers/flare/application_controller.rb +0 -30
- data/app/controllers/flare/jobs_controller.rb +0 -55
- data/app/controllers/flare/requests_controller.rb +0 -73
- data/app/controllers/flare/spans_controller.rb +0 -101
- data/app/helpers/flare/application_helper.rb +0 -168
- data/app/views/flare/jobs/index.html.erb +0 -69
- data/app/views/flare/jobs/show.html.erb +0 -323
- data/app/views/flare/requests/index.html.erb +0 -120
- data/app/views/flare/requests/show.html.erb +0 -498
- data/app/views/flare/spans/index.html.erb +0 -112
- data/app/views/flare/spans/show.html.erb +0 -184
- data/app/views/layouts/flare/application.html.erb +0 -126
- data/config/routes.rb +0 -20
- data/exe/flare +0 -9
- data/lib/flare/backoff_policy.rb +0 -73
- data/lib/flare/cli/doctor_command.rb +0 -129
- data/lib/flare/cli/output.rb +0 -45
- data/lib/flare/cli/setup_command.rb +0 -404
- data/lib/flare/cli/status_command.rb +0 -47
- data/lib/flare/cli.rb +0 -50
- data/lib/flare/client_headers.rb +0 -39
- data/lib/flare/deadline.rb +0 -27
- data/lib/flare/engine.rb +0 -45
- data/lib/flare/filtering_span_processor.rb +0 -429
- data/lib/flare/http_metrics_config.rb +0 -101
- data/lib/flare/http_transport.rb +0 -76
- data/lib/flare/lifecycle.rb +0 -39
- data/lib/flare/marker.rb +0 -106
- data/lib/flare/metric_counter.rb +0 -51
- data/lib/flare/metric_flusher.rb +0 -279
- data/lib/flare/metric_key.rb +0 -42
- data/lib/flare/metric_span_processor.rb +0 -470
- data/lib/flare/metric_storage.rb +0 -67
- data/lib/flare/metric_submitter.rb +0 -239
- data/lib/flare/recording_batch_span_processor.rb +0 -238
- data/lib/flare/rule_manager.rb +0 -141
- data/lib/flare/sampler.rb +0 -130
- data/lib/flare/source_location.rb +0 -113
- data/lib/flare/sqlite_exporter.rb +0 -334
- data/lib/flare/storage/sqlite.rb +0 -794
- data/lib/flare/storage.rb +0 -54
- data/lib/flare/trace_blob.rb +0 -116
- data/lib/flare/trace_exporter.rb +0 -182
- data/lib/flare/trace_health_reporter.rb +0 -74
- data/lib/flare/upload_url_pool.rb +0 -108
- data/lib/flare/version.rb +0 -5
- data/lib/flare/web_marker_subscriber.rb +0 -76
- data/public/flare-assets/flare.css +0 -1245
- data/public/flare-assets/images/flipper.png +0 -0
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "concurrent/atomic/atomic_fixnum"
|
|
4
|
-
require "logger"
|
|
5
|
-
require "opentelemetry/sdk"
|
|
6
|
-
|
|
7
|
-
require_relative "deadline"
|
|
8
|
-
|
|
9
|
-
module Flare
|
|
10
|
-
# BSP-shaped span processor whose filter is `sampled OR marked` instead
|
|
11
|
-
# of BSP's `sampled` (BSP early-returns on RECORD_ONLY spans -- our
|
|
12
|
-
# Path 2 spans have sampled=false so they'd be dropped). Forwards
|
|
13
|
-
# matching spans to a trace exporter on a background worker thread so
|
|
14
|
-
# the exporter never runs on the request/job thread (CAF-3).
|
|
15
|
-
#
|
|
16
|
-
# On every on_finish we also check marker.owner?(trace_id, span_id) and
|
|
17
|
-
# unmark when the owning rack span finishes (CAF-2). Cleanup runs even
|
|
18
|
-
# for spans we don't export.
|
|
19
|
-
class FilteringSpanProcessor
|
|
20
|
-
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
21
|
-
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
|
|
22
|
-
TIMEOUT = OpenTelemetry::SDK::Trace::Export::TIMEOUT
|
|
23
|
-
|
|
24
|
-
DEFAULT_MAX_QUEUE = 5_000
|
|
25
|
-
DEFAULT_FLUSH_INTERVAL = 5 # seconds
|
|
26
|
-
DEFAULT_EXPORT_TIMEOUT = 30 # seconds
|
|
27
|
-
DEFAULT_MARKED_TRACE_GRACE_PERIOD = 1.0 # seconds
|
|
28
|
-
|
|
29
|
-
attr_reader :dropped_count, :failed_export_count, :exception_count, :buffer_high_watermark, :max_queue
|
|
30
|
-
|
|
31
|
-
def initialize(exporter:, marker:,
|
|
32
|
-
max_queue: DEFAULT_MAX_QUEUE,
|
|
33
|
-
flush_interval: DEFAULT_FLUSH_INTERVAL,
|
|
34
|
-
export_timeout: DEFAULT_EXPORT_TIMEOUT,
|
|
35
|
-
marked_trace_grace_period: DEFAULT_MARKED_TRACE_GRACE_PERIOD,
|
|
36
|
-
logger: nil)
|
|
37
|
-
@exporter = exporter
|
|
38
|
-
@marker = marker
|
|
39
|
-
@max_queue = max_queue
|
|
40
|
-
@flush_interval = flush_interval
|
|
41
|
-
@export_timeout = export_timeout
|
|
42
|
-
@marked_trace_grace_period = marked_trace_grace_period.to_f
|
|
43
|
-
@logger = logger || Logger.new($stderr, level: Logger::WARN)
|
|
44
|
-
|
|
45
|
-
@pending_by_trace = {}
|
|
46
|
-
@trace_order = []
|
|
47
|
-
@pending_count = 0
|
|
48
|
-
@ready_queue = []
|
|
49
|
-
@delayed_ready_by_trace = {}
|
|
50
|
-
@mutex = Mutex.new
|
|
51
|
-
@cond = ConditionVariable.new
|
|
52
|
-
@stopped = false
|
|
53
|
-
@active_exports = 0
|
|
54
|
-
@export_completion_sequence = 0
|
|
55
|
-
@last_export_result = SUCCESS
|
|
56
|
-
@flush_owner = nil
|
|
57
|
-
@pid = $$
|
|
58
|
-
|
|
59
|
-
@dropped_count = Concurrent::AtomicFixnum.new(0)
|
|
60
|
-
@failed_export_count = Concurrent::AtomicFixnum.new(0)
|
|
61
|
-
@exception_count = Concurrent::AtomicFixnum.new(0)
|
|
62
|
-
@buffer_high_watermark = Concurrent::AtomicFixnum.new(0)
|
|
63
|
-
|
|
64
|
-
start_worker
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def on_start(_span, _parent_context); end
|
|
68
|
-
|
|
69
|
-
def on_finish(span)
|
|
70
|
-
detect_forking
|
|
71
|
-
|
|
72
|
-
ctx = span.context
|
|
73
|
-
sampled = ctx&.trace_flags&.sampled?
|
|
74
|
-
marked = ctx && @marker.marked?(ctx.trace_id)
|
|
75
|
-
owner_finished = marked && @marker.owner?(ctx.trace_id, ctx.span_id)
|
|
76
|
-
|
|
77
|
-
return unless sampled || marked
|
|
78
|
-
|
|
79
|
-
span_data = span.respond_to?(:to_span_data) ? span.to_span_data : span
|
|
80
|
-
enqueue(
|
|
81
|
-
span_data,
|
|
82
|
-
complete: owner_finished || sampled_completion_span?(span_data),
|
|
83
|
-
delay: owner_finished ? @marked_trace_grace_period : 0
|
|
84
|
-
)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def force_flush(timeout: nil)
|
|
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
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def shutdown(timeout: nil)
|
|
104
|
-
detect_forking
|
|
105
|
-
deadline = Deadline.new(timeout)
|
|
106
|
-
return TIMEOUT unless lock_before_deadline(deadline)
|
|
107
|
-
|
|
108
|
-
begin
|
|
109
|
-
@stopped = true
|
|
110
|
-
@cond.broadcast
|
|
111
|
-
ensure
|
|
112
|
-
@mutex.unlock
|
|
113
|
-
end
|
|
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
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def buffer_size
|
|
128
|
-
@mutex.synchronize { queued_span_count }
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def reset_buffer_high_watermark
|
|
132
|
-
@buffer_high_watermark.value = buffer_size
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
private
|
|
136
|
-
|
|
137
|
-
def enqueue(span_data, complete:, delay: 0)
|
|
138
|
-
@mutex.synchronize do
|
|
139
|
-
trace_id = span_data.trace_id
|
|
140
|
-
@trace_order << trace_id unless @pending_by_trace.key?(trace_id)
|
|
141
|
-
@pending_by_trace[trace_id] ||= []
|
|
142
|
-
@pending_by_trace[trace_id] << span_data
|
|
143
|
-
@pending_count += 1
|
|
144
|
-
evict_oldest_spans
|
|
145
|
-
|
|
146
|
-
if complete
|
|
147
|
-
delay.positive? ? delay_trace_ready(trace_id, delay) : mark_trace_ready(trace_id)
|
|
148
|
-
end
|
|
149
|
-
evict_oldest_spans
|
|
150
|
-
update_buffer_high_watermark
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def worker_loop
|
|
155
|
-
until stopped?
|
|
156
|
-
@mutex.synchronize do
|
|
157
|
-
timeout = next_wait_timeout
|
|
158
|
-
waiting_for_export = @flush_owner || @active_exports.positive?
|
|
159
|
-
@cond.wait(@mutex, timeout) if (@ready_queue.empty? || waiting_for_export) && !@stopped
|
|
160
|
-
end
|
|
161
|
-
drain_and_export
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def stopped?
|
|
166
|
-
@mutex.synchronize { @stopped }
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def drain_and_export
|
|
170
|
-
batch = nil
|
|
171
|
-
@mutex.synchronize do
|
|
172
|
-
promote_due_delayed_traces
|
|
173
|
-
return if @ready_queue.empty? || @flush_owner || @active_exports.positive?
|
|
174
|
-
|
|
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)
|
|
194
|
-
end
|
|
195
|
-
@flush_owner = Thread.current
|
|
196
|
-
|
|
197
|
-
while @active_exports.positive?
|
|
198
|
-
return false if deadline.expired?
|
|
199
|
-
|
|
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
|
|
230
|
-
batch = @ready_queue
|
|
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
|
|
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?
|
|
265
|
-
|
|
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)
|
|
274
|
-
@failed_export_count.increment if result != SUCCESS
|
|
275
|
-
result
|
|
276
|
-
rescue StandardError => e
|
|
277
|
-
@exception_count.increment
|
|
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
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
def mark_trace_ready(trace_id)
|
|
307
|
-
batch = @pending_by_trace.delete(trace_id)
|
|
308
|
-
return unless batch
|
|
309
|
-
|
|
310
|
-
@trace_order.delete(trace_id)
|
|
311
|
-
@delayed_ready_by_trace.delete(trace_id)
|
|
312
|
-
@pending_count -= batch.length
|
|
313
|
-
@ready_queue.concat(batch)
|
|
314
|
-
@cond.signal
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
def delay_trace_ready(trace_id, delay)
|
|
318
|
-
@delayed_ready_by_trace[trace_id] = monotonic_now + delay
|
|
319
|
-
@cond.signal
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
def promote_due_delayed_traces
|
|
323
|
-
now = monotonic_now
|
|
324
|
-
ready_trace_ids = @delayed_ready_by_trace.select { |_, ready_at| ready_at <= now }.keys
|
|
325
|
-
ready_trace_ids.each do |trace_id|
|
|
326
|
-
mark_trace_ready(trace_id)
|
|
327
|
-
@marker.unmark(trace_id)
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
def unmark_delayed_traces
|
|
332
|
-
@delayed_ready_by_trace.each_key { |trace_id| @marker.unmark(trace_id) }
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def next_wait_timeout
|
|
336
|
-
next_ready_at = @delayed_ready_by_trace.values.min
|
|
337
|
-
return @flush_interval unless next_ready_at
|
|
338
|
-
|
|
339
|
-
[next_ready_at - monotonic_now, 0].max
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def evict_oldest_spans
|
|
343
|
-
while queued_span_count > @max_queue
|
|
344
|
-
trace_id = @trace_order.first
|
|
345
|
-
unless trace_id
|
|
346
|
-
@ready_queue.shift
|
|
347
|
-
@dropped_count.increment
|
|
348
|
-
next
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
spans = @pending_by_trace[trace_id]
|
|
352
|
-
if spans.nil? || spans.empty?
|
|
353
|
-
@trace_order.shift
|
|
354
|
-
next
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
spans.shift
|
|
358
|
-
@pending_count -= 1
|
|
359
|
-
@dropped_count.increment
|
|
360
|
-
|
|
361
|
-
if spans.empty?
|
|
362
|
-
@pending_by_trace.delete(trace_id)
|
|
363
|
-
@trace_order.shift
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
def queued_span_count
|
|
369
|
-
@pending_count + @ready_queue.length
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
def update_buffer_high_watermark
|
|
373
|
-
current = queued_span_count
|
|
374
|
-
@buffer_high_watermark.update { |previous| current > previous ? current : previous }
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
def monotonic_now
|
|
378
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
def sampled_completion_span?(span_data)
|
|
382
|
-
root_span?(span_data) || entry_span?(span_data)
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
def root_span?(span_data)
|
|
386
|
-
parent_id = span_data.parent_span_id if span_data.respond_to?(:parent_span_id)
|
|
387
|
-
parent_id.nil? ||
|
|
388
|
-
(parent_id.respond_to?(:empty?) && parent_id.empty?) ||
|
|
389
|
-
parent_id == OpenTelemetry::Trace::INVALID_SPAN_ID
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
def entry_span?(span_data)
|
|
393
|
-
return false unless span_data.respond_to?(:kind)
|
|
394
|
-
|
|
395
|
-
span_data.kind == :server || span_data.kind == :consumer
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
def detect_forking
|
|
399
|
-
return if @pid == $$
|
|
400
|
-
|
|
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
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
def start_worker
|
|
423
|
-
return if @worker&.alive?
|
|
424
|
-
|
|
425
|
-
@worker = Thread.new { worker_loop }
|
|
426
|
-
@worker.name = "flare-filtering-span-processor"
|
|
427
|
-
end
|
|
428
|
-
end
|
|
429
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Flare
|
|
4
|
-
class HttpMetricsConfig
|
|
5
|
-
class HostConfig
|
|
6
|
-
attr_reader :rules
|
|
7
|
-
|
|
8
|
-
def initialize
|
|
9
|
-
@rules = []
|
|
10
|
-
@all = false
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def initialize_copy(source)
|
|
14
|
-
@rules = source.rules.dup
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def all?
|
|
18
|
-
@all
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Track all paths for this host (normalized via normalize_path)
|
|
22
|
-
def all
|
|
23
|
-
@all = true
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Track paths matching this regex (normalized via normalize_path)
|
|
27
|
-
def allow(pattern)
|
|
28
|
-
@rules << {pattern: pattern, replacement: nil}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Track paths matching this regex with a custom replacement string
|
|
32
|
-
def map(pattern, replacement)
|
|
33
|
-
@rules << {pattern: pattern, replacement: replacement}
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Returns the resolved path for a given raw path, or "*" if no match.
|
|
37
|
-
# If :all, returns nil to signal "use normalize_path".
|
|
38
|
-
# If a rule matches with a replacement, returns the replacement.
|
|
39
|
-
# If a rule matches without a replacement, returns nil to signal "use normalize_path".
|
|
40
|
-
# If no rules match, returns "*".
|
|
41
|
-
def resolve(path)
|
|
42
|
-
return nil if @all
|
|
43
|
-
|
|
44
|
-
@rules.each do |rule|
|
|
45
|
-
if rule[:pattern].match?(path)
|
|
46
|
-
return rule[:replacement] # nil means use normalize_path
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
"*"
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def initialize
|
|
55
|
-
@hosts = {}
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def initialize_copy(source)
|
|
59
|
-
@hosts = source.instance_variable_get(:@hosts).transform_values(&:dup)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def host(hostname, mode = nil, &block)
|
|
63
|
-
config = @hosts[hostname] ||= HostConfig.new
|
|
64
|
-
|
|
65
|
-
if mode == :all
|
|
66
|
-
config.all
|
|
67
|
-
elsif block
|
|
68
|
-
yield config
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Resolve a host+path to the target path for metrics.
|
|
73
|
-
# Returns "*" for unknown hosts or unmatched paths.
|
|
74
|
-
# Returns nil to signal "use normalize_path".
|
|
75
|
-
# Returns a string for custom replacements.
|
|
76
|
-
def resolve(hostname, path)
|
|
77
|
-
host_config = @hosts[hostname]
|
|
78
|
-
return "*" unless host_config
|
|
79
|
-
|
|
80
|
-
host_config.resolve(path)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
DEFAULT = new.tap do |config|
|
|
84
|
-
config.host "flare.am" do |h|
|
|
85
|
-
h.allow %r{/api/metrics}
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
config.host "www.flippercloud.io" do |h|
|
|
89
|
-
h.map %r{/adapter/features/[^/]+/(boolean|actors|groups|percentage_of_actors|percentage_of_time|expression|clear)}, "/adapter/features/:name/:gate"
|
|
90
|
-
h.map %r{/adapter/features/[^/]+}, "/adapter/features/:name"
|
|
91
|
-
h.map %r{/adapter/actors/[^/]+}, "/adapter/actors/:id"
|
|
92
|
-
h.allow %r{/adapter/features}
|
|
93
|
-
h.allow %r{/adapter/import}
|
|
94
|
-
h.allow %r{/adapter/telemetry/summary}
|
|
95
|
-
h.allow %r{/adapter/telemetry}
|
|
96
|
-
h.allow %r{/adapter/events}
|
|
97
|
-
h.allow %r{/adapter/audits}
|
|
98
|
-
end
|
|
99
|
-
end.freeze
|
|
100
|
-
end
|
|
101
|
-
end
|
data/lib/flare/http_transport.rb
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "net/http"
|
|
4
|
-
require "uri"
|
|
5
|
-
|
|
6
|
-
require_relative "deadline"
|
|
7
|
-
|
|
8
|
-
module Flare
|
|
9
|
-
# Tiny HTTP wrapper used by TraceExporter (and anything else that wants
|
|
10
|
-
# to PUT/POST without pulling in a heavy client). Designed for injection
|
|
11
|
-
# at the boundary so tests can swap in a recording fake; no other moving
|
|
12
|
-
# parts.
|
|
13
|
-
class HttpTransport
|
|
14
|
-
DeadlineExceeded = Class.new(StandardError)
|
|
15
|
-
DEFAULT_OPEN_TIMEOUT = 2
|
|
16
|
-
DEFAULT_READ_TIMEOUT = 5
|
|
17
|
-
DEFAULT_WRITE_TIMEOUT = 5
|
|
18
|
-
|
|
19
|
-
Response = Struct.new(:code, :body, :headers, keyword_init: true) do
|
|
20
|
-
def header(name)
|
|
21
|
-
return nil unless headers
|
|
22
|
-
headers[name] || headers[name.downcase] || headers[name.upcase]
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def initialize(open_timeout: DEFAULT_OPEN_TIMEOUT,
|
|
27
|
-
read_timeout: DEFAULT_READ_TIMEOUT,
|
|
28
|
-
write_timeout: DEFAULT_WRITE_TIMEOUT)
|
|
29
|
-
@open_timeout = open_timeout
|
|
30
|
-
@read_timeout = read_timeout
|
|
31
|
-
@write_timeout = write_timeout
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def get(url, headers = {}, timeout: nil)
|
|
35
|
-
request(url, nil, headers, Net::HTTP::Get, timeout: timeout)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def put(url, body, headers = {}, timeout: nil)
|
|
39
|
-
request(url, body, headers, Net::HTTP::Put, timeout: timeout)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def post(url, body, headers = {}, timeout: nil)
|
|
43
|
-
request(url, body, headers, Net::HTTP::Post, timeout: timeout)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def request(url, body, headers, klass, timeout: nil)
|
|
49
|
-
deadline = Deadline.new(timeout)
|
|
50
|
-
raise DeadlineExceeded if deadline.expired?
|
|
51
|
-
|
|
52
|
-
uri = URI(url)
|
|
53
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
54
|
-
http.use_ssl = uri.scheme == "https"
|
|
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=)
|
|
58
|
-
|
|
59
|
-
req = klass.new(uri.request_uri == "" ? "/" : uri.request_uri)
|
|
60
|
-
headers.each { |k, v| req[k] = v }
|
|
61
|
-
req.body = body if body
|
|
62
|
-
|
|
63
|
-
response = http.request(req)
|
|
64
|
-
raise DeadlineExceeded if deadline.expired?
|
|
65
|
-
|
|
66
|
-
hash = response.each_header.to_h
|
|
67
|
-
Response.new(code: response.code.to_s, body: response.body, headers: hash)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def effective_timeout(configured_timeout, remaining)
|
|
71
|
-
return configured_timeout unless remaining
|
|
72
|
-
|
|
73
|
-
[configured_timeout, remaining].min
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
data/lib/flare/lifecycle.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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
|