flare 0.3.1 → 0.4.1
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 +74 -0
- data/lib/flare/metric_flusher.rb +156 -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/rule_manager.rb +2 -2
- 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 +6 -17
- metadata +5 -2
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require "sqlite3"
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
|
+
require_relative "deadline"
|
|
7
|
+
|
|
6
8
|
module Flare
|
|
7
9
|
class SQLiteExporter
|
|
8
10
|
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
@@ -16,45 +18,61 @@ module Flare
|
|
|
16
18
|
@database_path = database_path
|
|
17
19
|
@mutex = Mutex.new
|
|
18
20
|
@setup = false
|
|
21
|
+
@pid = $$
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
# Maximum number of retry attempts when the database is busy.
|
|
22
25
|
# Mirrors ActiveRecord's retry strategy for SQLite.
|
|
23
26
|
MAX_RETRIES = 3
|
|
27
|
+
ExportDeadlineExceeded = Class.new(StandardError)
|
|
24
28
|
|
|
25
29
|
def export(span_datas, timeout: nil)
|
|
26
|
-
|
|
30
|
+
detect_forking
|
|
31
|
+
deadline = Deadline.new(timeout)
|
|
27
32
|
|
|
28
33
|
retries = 0
|
|
29
34
|
exported = 0
|
|
30
35
|
|
|
31
36
|
begin
|
|
32
|
-
@
|
|
37
|
+
setup_database(deadline) unless @setup
|
|
38
|
+
raise ExportDeadlineExceeded if deadline.expired?
|
|
39
|
+
raise ExportDeadlineExceeded unless lock_before_deadline(deadline)
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
apply_busy_timeout(deadline)
|
|
33
43
|
connection.transaction do
|
|
34
44
|
span_datas.each do |span_data|
|
|
45
|
+
raise ExportDeadlineExceeded if deadline.expired?
|
|
35
46
|
next if should_ignore_span?(span_data)
|
|
36
47
|
|
|
37
48
|
create_span(span_data)
|
|
38
49
|
exported += 1
|
|
39
50
|
end
|
|
40
51
|
end
|
|
52
|
+
ensure
|
|
53
|
+
@mutex.unlock
|
|
41
54
|
end
|
|
55
|
+
rescue ExportDeadlineExceeded
|
|
56
|
+
return TIMEOUT
|
|
42
57
|
rescue ::SQLite3::BusyException
|
|
43
58
|
retries += 1
|
|
44
59
|
if retries <= MAX_RETRIES
|
|
45
|
-
|
|
60
|
+
sleep_time = 0.1 * retries
|
|
61
|
+
return TIMEOUT if deadline.remaining && sleep_time >= deadline.remaining
|
|
62
|
+
|
|
63
|
+
sleep(sleep_time)
|
|
46
64
|
retry
|
|
47
65
|
end
|
|
48
66
|
warn "[Flare] SQLite export error: database is busy after #{MAX_RETRIES} retries"
|
|
49
67
|
return FAILURE
|
|
50
68
|
end
|
|
51
69
|
|
|
52
|
-
Flare.log "Exported #{exported} spans to SQLite" if exported > 0
|
|
70
|
+
Flare.log "Exported #{exported} spans to SQLite" if exported > 0 && Flare.respond_to?(:log)
|
|
53
71
|
|
|
54
72
|
# Periodically prune old data
|
|
55
|
-
maybe_prune
|
|
73
|
+
maybe_prune unless deadline.expired?
|
|
56
74
|
|
|
57
|
-
SUCCESS
|
|
75
|
+
deadline.expired? ? TIMEOUT : SUCCESS
|
|
58
76
|
rescue => e
|
|
59
77
|
warn "[Flare] SQLite export error: #{e.message}"
|
|
60
78
|
FAILURE
|
|
@@ -70,6 +88,31 @@ module Flare
|
|
|
70
88
|
|
|
71
89
|
private
|
|
72
90
|
|
|
91
|
+
def detect_forking
|
|
92
|
+
return if @pid == $$
|
|
93
|
+
|
|
94
|
+
@pid = $$
|
|
95
|
+
@mutex = Mutex.new
|
|
96
|
+
close_connection
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def lock_before_deadline(deadline)
|
|
100
|
+
return @mutex.lock unless deadline.remaining
|
|
101
|
+
|
|
102
|
+
until @mutex.try_lock
|
|
103
|
+
return false if deadline.expired?
|
|
104
|
+
|
|
105
|
+
sleep([deadline.remaining, 0.001].min)
|
|
106
|
+
end
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def apply_busy_timeout(deadline)
|
|
111
|
+
remaining = deadline.remaining
|
|
112
|
+
timeout_ms = remaining ? [(remaining * 1_000).floor, 1].max : 5_000
|
|
113
|
+
connection.busy_timeout = [timeout_ms, 5_000].min
|
|
114
|
+
end
|
|
115
|
+
|
|
73
116
|
def maybe_prune
|
|
74
117
|
return unless rand < PRUNE_PROBABILITY
|
|
75
118
|
|
|
@@ -162,14 +205,16 @@ module Flare
|
|
|
162
205
|
end
|
|
163
206
|
end
|
|
164
207
|
|
|
165
|
-
def setup_database
|
|
166
|
-
|
|
208
|
+
def setup_database(deadline)
|
|
209
|
+
raise ExportDeadlineExceeded unless lock_before_deadline(deadline)
|
|
210
|
+
|
|
211
|
+
begin
|
|
167
212
|
return if @setup
|
|
168
213
|
|
|
169
214
|
db = connection
|
|
170
|
-
configure_pragmas(db)
|
|
215
|
+
configure_pragmas(db, deadline)
|
|
171
216
|
|
|
172
|
-
db
|
|
217
|
+
execute_setup(db, deadline, <<~SQL)
|
|
173
218
|
CREATE TABLE IF NOT EXISTS flare_spans (
|
|
174
219
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
175
220
|
name TEXT NOT NULL,
|
|
@@ -187,23 +232,23 @@ module Flare
|
|
|
187
232
|
)
|
|
188
233
|
SQL
|
|
189
234
|
|
|
190
|
-
db
|
|
235
|
+
execute_setup(db, deadline, <<~SQL)
|
|
191
236
|
CREATE INDEX IF NOT EXISTS idx_spans_span_id ON flare_spans(span_id)
|
|
192
237
|
SQL
|
|
193
238
|
|
|
194
|
-
db
|
|
239
|
+
execute_setup(db, deadline, <<~SQL)
|
|
195
240
|
CREATE INDEX IF NOT EXISTS idx_spans_trace_id ON flare_spans(trace_id)
|
|
196
241
|
SQL
|
|
197
242
|
|
|
198
|
-
db
|
|
243
|
+
execute_setup(db, deadline, <<~SQL)
|
|
199
244
|
CREATE INDEX IF NOT EXISTS idx_spans_parent_span_id ON flare_spans(parent_span_id)
|
|
200
245
|
SQL
|
|
201
246
|
|
|
202
|
-
db
|
|
247
|
+
execute_setup(db, deadline, <<~SQL)
|
|
203
248
|
CREATE INDEX IF NOT EXISTS idx_spans_created_at ON flare_spans(created_at)
|
|
204
249
|
SQL
|
|
205
250
|
|
|
206
|
-
db
|
|
251
|
+
execute_setup(db, deadline, <<~SQL)
|
|
207
252
|
CREATE TABLE IF NOT EXISTS flare_events (
|
|
208
253
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
209
254
|
span_id INTEGER NOT NULL,
|
|
@@ -214,11 +259,11 @@ module Flare
|
|
|
214
259
|
)
|
|
215
260
|
SQL
|
|
216
261
|
|
|
217
|
-
db
|
|
262
|
+
execute_setup(db, deadline, <<~SQL)
|
|
218
263
|
CREATE INDEX IF NOT EXISTS idx_events_span_id ON flare_events(span_id)
|
|
219
264
|
SQL
|
|
220
265
|
|
|
221
|
-
db
|
|
266
|
+
execute_setup(db, deadline, <<~SQL)
|
|
222
267
|
CREATE TABLE IF NOT EXISTS flare_properties (
|
|
223
268
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
224
269
|
key TEXT NOT NULL,
|
|
@@ -231,27 +276,37 @@ module Flare
|
|
|
231
276
|
)
|
|
232
277
|
SQL
|
|
233
278
|
|
|
234
|
-
db
|
|
279
|
+
execute_setup(db, deadline, <<~SQL)
|
|
235
280
|
CREATE INDEX IF NOT EXISTS idx_properties_owner ON flare_properties(owner_type, owner_id)
|
|
236
281
|
SQL
|
|
237
282
|
|
|
238
|
-
db
|
|
283
|
+
execute_setup(db, deadline, <<~SQL)
|
|
239
284
|
CREATE INDEX IF NOT EXISTS idx_properties_key ON flare_properties(key)
|
|
240
285
|
SQL
|
|
241
286
|
|
|
242
287
|
close_connection # avoid inheriting connection across fork
|
|
243
288
|
@setup = true
|
|
289
|
+
ensure
|
|
290
|
+
@mutex.unlock
|
|
244
291
|
end
|
|
245
292
|
end
|
|
246
293
|
|
|
247
294
|
# Applies the same SQLite pragmas that ActiveRecord uses for good
|
|
248
295
|
# concurrency and performance with threaded/multi-process access.
|
|
249
|
-
def configure_pragmas(db)
|
|
250
|
-
db
|
|
251
|
-
db
|
|
252
|
-
db
|
|
253
|
-
db
|
|
254
|
-
db
|
|
296
|
+
def configure_pragmas(db, deadline)
|
|
297
|
+
execute_setup(db, deadline, "PRAGMA journal_mode=WAL")
|
|
298
|
+
execute_setup(db, deadline, "PRAGMA synchronous=NORMAL")
|
|
299
|
+
execute_setup(db, deadline, "PRAGMA mmap_size=134217728") # 128MB
|
|
300
|
+
execute_setup(db, deadline, "PRAGMA journal_size_limit=67108864") # 64MB
|
|
301
|
+
execute_setup(db, deadline, "PRAGMA cache_size=2000")
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def execute_setup(db, deadline, statement)
|
|
305
|
+
raise ExportDeadlineExceeded if deadline.expired?
|
|
306
|
+
|
|
307
|
+
apply_busy_timeout(deadline)
|
|
308
|
+
db.execute(statement)
|
|
309
|
+
raise ExportDeadlineExceeded if deadline.expired?
|
|
255
310
|
end
|
|
256
311
|
|
|
257
312
|
def connection_key
|
data/lib/flare/trace_exporter.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "client_headers"
|
|
|
12
12
|
require_relative "sampler"
|
|
13
13
|
require_relative "trace_blob"
|
|
14
14
|
require_relative "http_transport"
|
|
15
|
+
require_relative "deadline"
|
|
15
16
|
|
|
16
17
|
module Flare
|
|
17
18
|
# Custom OTel exporter. For each batch FilteringSpanProcessor hands over:
|
|
@@ -33,6 +34,7 @@ module Flare
|
|
|
33
34
|
class TraceExporter
|
|
34
35
|
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
35
36
|
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
|
|
37
|
+
TIMEOUT = OpenTelemetry::SDK::Trace::Export::TIMEOUT
|
|
36
38
|
|
|
37
39
|
PUT_HEADERS = {
|
|
38
40
|
"Content-Type" => "application/json",
|
|
@@ -58,16 +60,22 @@ module Flare
|
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
def export(spans, timeout: nil)
|
|
63
|
+
deadline = Deadline.new(timeout)
|
|
61
64
|
grouped = spans.group_by(&:trace_id)
|
|
62
65
|
return SUCCESS if grouped.empty?
|
|
63
66
|
|
|
64
67
|
overall = SUCCESS
|
|
65
68
|
grouped.each do |trace_id, group|
|
|
66
|
-
|
|
69
|
+
return TIMEOUT if deadline.expired?
|
|
70
|
+
|
|
71
|
+
result = ship(TraceBlob.build(trace_id: trace_id, spans: group), deadline: deadline)
|
|
72
|
+
return TIMEOUT if result == TIMEOUT || deadline.expired?
|
|
67
73
|
overall = FAILURE if result == FAILURE
|
|
68
74
|
end
|
|
69
75
|
overall
|
|
70
76
|
rescue StandardError => e
|
|
77
|
+
return TIMEOUT if deadline&.expired? || e.is_a?(HttpTransport::DeadlineExceeded)
|
|
78
|
+
|
|
71
79
|
@exception_count.increment
|
|
72
80
|
@logger.warn("[Flare::TraceExporter] export raised: #{e.class}: #{e.message}")
|
|
73
81
|
FAILURE
|
|
@@ -83,8 +91,9 @@ module Flare
|
|
|
83
91
|
|
|
84
92
|
private
|
|
85
93
|
|
|
86
|
-
def ship(blob, retried: false)
|
|
94
|
+
def ship(blob, deadline:, retried: false)
|
|
87
95
|
return FAILURE if blob.nil?
|
|
96
|
+
return TIMEOUT if deadline.expired?
|
|
88
97
|
|
|
89
98
|
entry = @pool.checkout
|
|
90
99
|
if entry.nil?
|
|
@@ -93,29 +102,55 @@ module Flare
|
|
|
93
102
|
end
|
|
94
103
|
|
|
95
104
|
body = gzip(JSON.generate(blob.to_h))
|
|
96
|
-
response =
|
|
105
|
+
response = transport_request(:put, entry[:put_url], body, PUT_HEADERS, timeout: deadline.remaining)
|
|
106
|
+
return TIMEOUT if deadline.expired?
|
|
97
107
|
|
|
98
108
|
case response.code
|
|
99
109
|
when "200", "204"
|
|
100
|
-
notify(entry[:key])
|
|
101
|
-
SUCCESS
|
|
110
|
+
notify(entry[:key], deadline: deadline)
|
|
102
111
|
when "403"
|
|
103
112
|
# Presigned URL probably expired; try once more with the next one.
|
|
104
|
-
retried ? record_put_failure(response) : ship(blob, retried: true)
|
|
113
|
+
retried ? record_put_failure(response) : ship(blob, deadline: deadline, retried: true)
|
|
105
114
|
else
|
|
106
115
|
record_put_failure(response)
|
|
107
116
|
end
|
|
108
117
|
end
|
|
109
118
|
|
|
110
|
-
def notify(key)
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
def notify(key, deadline:)
|
|
120
|
+
return TIMEOUT if deadline.expired?
|
|
121
|
+
|
|
122
|
+
response = transport_request(
|
|
123
|
+
:post,
|
|
124
|
+
@notify_url,
|
|
125
|
+
JSON.generate(key: key),
|
|
126
|
+
notify_headers,
|
|
127
|
+
timeout: deadline.remaining
|
|
128
|
+
)
|
|
129
|
+
return TIMEOUT if deadline.expired?
|
|
130
|
+
return SUCCESS if response.code == "202"
|
|
113
131
|
|
|
114
132
|
@notify_failure_count.increment
|
|
115
133
|
@logger.warn("[Flare::TraceExporter] notify failed: HTTP #{response.code}")
|
|
134
|
+
SUCCESS
|
|
116
135
|
rescue StandardError => e
|
|
136
|
+
return TIMEOUT if deadline.expired? || e.is_a?(HttpTransport::DeadlineExceeded)
|
|
137
|
+
|
|
117
138
|
@notify_failure_count.increment
|
|
118
139
|
@logger.warn("[Flare::TraceExporter] notify exception: #{e.class}: #{e.message}")
|
|
140
|
+
SUCCESS
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def transport_request(method, *args, timeout:)
|
|
144
|
+
parameters = @transport.method(method).parameters
|
|
145
|
+
accepts_timeout = parameters.any? do |type, name|
|
|
146
|
+
type == :keyrest || ([:key, :keyreq].include?(type) && name == :timeout)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if accepts_timeout
|
|
150
|
+
@transport.public_send(method, *args, timeout: timeout)
|
|
151
|
+
else
|
|
152
|
+
@transport.public_send(method, *args)
|
|
153
|
+
end
|
|
119
154
|
end
|
|
120
155
|
|
|
121
156
|
# Identifies the client on the Flare-API notify POST. The presigned R2
|
data/lib/flare/version.rb
CHANGED
data/lib/flare.rb
CHANGED
|
@@ -13,6 +13,8 @@ require_relative "flare/metric_span_processor"
|
|
|
13
13
|
require_relative "flare/metric_flusher"
|
|
14
14
|
require_relative "flare/backoff_policy"
|
|
15
15
|
require_relative "flare/metric_submitter"
|
|
16
|
+
require_relative "flare/recording_batch_span_processor"
|
|
17
|
+
require_relative "flare/lifecycle"
|
|
16
18
|
|
|
17
19
|
require_relative "flare/sampler"
|
|
18
20
|
require_relative "flare/marker"
|
|
@@ -88,7 +90,7 @@ module Flare
|
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
def span_processor
|
|
91
|
-
@span_processor ||=
|
|
93
|
+
@span_processor ||= RecordingBatchSpanProcessor.new(
|
|
92
94
|
exporter,
|
|
93
95
|
max_queue_size: 1000,
|
|
94
96
|
max_export_batch_size: 100,
|
|
@@ -228,20 +230,7 @@ module Flare
|
|
|
228
230
|
subscribe_to_notifications
|
|
229
231
|
end
|
|
230
232
|
|
|
231
|
-
|
|
232
|
-
log "Shutting down..."
|
|
233
|
-
if configuration.spans_enabled && @span_processor
|
|
234
|
-
span_processor.force_flush
|
|
235
|
-
span_processor.shutdown
|
|
236
|
-
log "Span processor flushed and stopped"
|
|
237
|
-
end
|
|
238
|
-
if @trace_span_processor
|
|
239
|
-
@trace_span_processor.force_flush
|
|
240
|
-
@trace_span_processor.shutdown
|
|
241
|
-
log "Trace span processor flushed and stopped"
|
|
242
|
-
end
|
|
243
|
-
log "Shutdown complete"
|
|
244
|
-
end
|
|
233
|
+
install_shutdown_hook
|
|
245
234
|
|
|
246
235
|
@otel_configured = true
|
|
247
236
|
end
|
|
@@ -270,7 +259,7 @@ module Flare
|
|
|
270
259
|
@rule_manager.start
|
|
271
260
|
log "Rule manager started (poll=#{configuration.tracing_poll_interval}s)"
|
|
272
261
|
|
|
273
|
-
|
|
262
|
+
install_shutdown_hook
|
|
274
263
|
end
|
|
275
264
|
|
|
276
265
|
def setup_tracing_components
|
|
@@ -355,7 +344,7 @@ module Flare
|
|
|
355
344
|
@metric_flusher.start
|
|
356
345
|
log "Metrics flusher started (interval=#{configuration.metrics_flush_interval}s)"
|
|
357
346
|
|
|
358
|
-
|
|
347
|
+
install_shutdown_hook
|
|
359
348
|
else
|
|
360
349
|
log "Metrics submission not configured (missing url or key)"
|
|
361
350
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flare
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Nunemaker
|
|
@@ -198,10 +198,12 @@ files:
|
|
|
198
198
|
- lib/flare/cli/status_command.rb
|
|
199
199
|
- lib/flare/client_headers.rb
|
|
200
200
|
- lib/flare/configuration.rb
|
|
201
|
+
- lib/flare/deadline.rb
|
|
201
202
|
- lib/flare/engine.rb
|
|
202
203
|
- lib/flare/filtering_span_processor.rb
|
|
203
204
|
- lib/flare/http_metrics_config.rb
|
|
204
205
|
- lib/flare/http_transport.rb
|
|
206
|
+
- lib/flare/lifecycle.rb
|
|
205
207
|
- lib/flare/marker.rb
|
|
206
208
|
- lib/flare/metric_counter.rb
|
|
207
209
|
- lib/flare/metric_flusher.rb
|
|
@@ -209,6 +211,7 @@ files:
|
|
|
209
211
|
- lib/flare/metric_span_processor.rb
|
|
210
212
|
- lib/flare/metric_storage.rb
|
|
211
213
|
- lib/flare/metric_submitter.rb
|
|
214
|
+
- lib/flare/recording_batch_span_processor.rb
|
|
212
215
|
- lib/flare/rule_manager.rb
|
|
213
216
|
- lib/flare/sampler.rb
|
|
214
217
|
- lib/flare/source_location.rb
|
|
@@ -245,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
245
248
|
- !ruby/object:Gem::Version
|
|
246
249
|
version: '0'
|
|
247
250
|
requirements: []
|
|
248
|
-
rubygems_version: 4.0.
|
|
251
|
+
rubygems_version: 4.0.18
|
|
249
252
|
specification_version: 4
|
|
250
253
|
summary: Light up what's slowing you down
|
|
251
254
|
test_files: []
|