dead_bro 0.2.14 → 0.2.15
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/CHANGELOG.md +9 -0
- data/lib/dead_bro/gc_tracker.rb +47 -0
- data/lib/dead_bro/job_subscriber.rb +11 -0
- data/lib/dead_bro/sql_tracking_middleware.rb +4 -0
- data/lib/dead_bro/subscriber.rb +5 -0
- data/lib/dead_bro/version.rb +1 -1
- data/lib/dead_bro.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2251b88befeee4d2b7668bc504cc99d94cd154dd4539258545c1084e7a388709
|
|
4
|
+
data.tar.gz: 10141477e9697b91d95290ccfc695abb304e8dfbdfb526f95646269be03be0ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39ec9243a8792016f7cf9bf5f8d81856c121365b5cebef91ef7d318f642e7480a66b32a92b63ec944ee3aebb9e92ffdfc27c9e284e41da7daa081661e893b38a
|
|
7
|
+
data.tar.gz: 519f6df31fb8dc567a6974052e58787098021078514eaa2877a2f582651c5a5fec316f4da58a625a4b6d00eed7fae614fe5dd79f1da17ae2d4d7227ce8f7061b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.14] - 2026-05-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `DbConnectionSubscriber`: measures the time threads spend waiting to acquire a connection from the ActiveRecord connection pool and counts checkouts per request. Uses `prepend` on `ActiveRecord::ConnectionAdapters::ConnectionPool#checkout` so the overhead is minimal and the instrumentation is invisible to application code. `db_connection_wait_ms` and `db_connection_checkouts` are included in both request and job payloads.
|
|
7
|
+
- Queue duration tracking for web requests via `X-Request-Start` / `X-Queue-Start` headers. Parses both the Heroku microsecond format (`t=<µs>`) and the nginx seconds format (`t=<s.ms>`), and applies a 60 s clock-skew cap so a misconfigured proxy timestamp cannot produce a nonsensical value.
|
|
8
|
+
- Queue duration tracking for background jobs: time from `job.enqueued_at` to when `perform` begins is reported as `queue_duration_ms` in every job payload.
|
|
9
|
+
- `RedisSubscriber`: prepend-based instrumentation on `Redis::Client` that records every individual command, pipeline, and `MULTI`/`EXEC` block with command name, key, duration in ms, and database index. Falls back to an `ActiveSupport::Notifications` subscription for `redis.*` events emitted by other libraries. Capped at 1 000 events per request to bound memory growth.
|
|
10
|
+
- DB connection tracking wired into background jobs: `DbConnectionSubscriber` is started and stopped around job execution in both the normal completion path and the exception handler fallback path, with cleanup in `drain_job_tracking`.
|
|
11
|
+
|
|
3
12
|
## [0.1.0] - 2025-08-28
|
|
4
13
|
|
|
5
14
|
- Initial release
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeadBro
|
|
4
|
+
module GcTracker
|
|
5
|
+
THREAD_KEY = :dead_bro_gc_start
|
|
6
|
+
|
|
7
|
+
def self.start_request_tracking
|
|
8
|
+
Thread.current[THREAD_KEY] = snapshot
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.stop_request_tracking
|
|
12
|
+
before = Thread.current[THREAD_KEY]
|
|
13
|
+
return {} if before.nil? || before.empty?
|
|
14
|
+
diff(before, snapshot)
|
|
15
|
+
ensure
|
|
16
|
+
Thread.current[THREAD_KEY] = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.snapshot
|
|
20
|
+
return {} unless defined?(GC) && GC.respond_to?(:stat)
|
|
21
|
+
stat = GC.stat
|
|
22
|
+
{
|
|
23
|
+
minor_gc_count: stat[:minor_gc_count] || 0,
|
|
24
|
+
major_gc_count: stat[:major_gc_count] || 0,
|
|
25
|
+
total_allocated_objects: stat[:total_allocated_objects] || 0,
|
|
26
|
+
gc_time_ns: GC.respond_to?(:total_time) ? GC.total_time : nil
|
|
27
|
+
}
|
|
28
|
+
rescue
|
|
29
|
+
{}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.diff(before, after)
|
|
33
|
+
return {} if before.empty? || after.empty?
|
|
34
|
+
gc_time_ms = if before[:gc_time_ns] && after[:gc_time_ns]
|
|
35
|
+
((after[:gc_time_ns] - before[:gc_time_ns]) / 1_000_000.0).round(3)
|
|
36
|
+
end
|
|
37
|
+
{
|
|
38
|
+
minor_gc_runs: (after[:minor_gc_count] || 0) - (before[:minor_gc_count] || 0),
|
|
39
|
+
major_gc_runs: (after[:major_gc_count] || 0) - (before[:major_gc_count] || 0),
|
|
40
|
+
allocated_objects: (after[:total_allocated_objects] || 0) - (before[:total_allocated_objects] || 0),
|
|
41
|
+
gc_time_ms: gc_time_ms
|
|
42
|
+
}
|
|
43
|
+
rescue
|
|
44
|
+
{}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -12,6 +12,12 @@ module DeadBro
|
|
|
12
12
|
JOB_EXCEPTION_EVENT_NAME = "exception.active_job"
|
|
13
13
|
|
|
14
14
|
def self.subscribe!(client: Client.new)
|
|
15
|
+
# Snap GC state before the job runs so stop_request_tracking gets a valid diff
|
|
16
|
+
ActiveSupport::Notifications.subscribe("perform_start.active_job") do |_name, _started, _finished, _unique_id, _data|
|
|
17
|
+
DeadBro::GcTracker.start_request_tracking if defined?(DeadBro::GcTracker)
|
|
18
|
+
rescue
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
# Track job execution
|
|
16
22
|
ActiveSupport::Notifications.subscribe(JOB_EVENT_NAME) do |name, started, finished, _unique_id, data|
|
|
17
23
|
begin
|
|
@@ -62,6 +68,7 @@ module DeadBro
|
|
|
62
68
|
# Get SQL queries executed during this job
|
|
63
69
|
sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
|
|
64
70
|
db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
|
|
71
|
+
gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
|
|
65
72
|
|
|
66
73
|
# Stop memory tracking and get collected memory data
|
|
67
74
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
@@ -100,6 +107,7 @@ module DeadBro
|
|
|
100
107
|
queue_duration_ms: queue_duration_ms,
|
|
101
108
|
db_connection_wait_ms: db_connection_stats[:wait_ms],
|
|
102
109
|
db_connection_checkouts: db_connection_stats[:checkouts],
|
|
110
|
+
gc_pressure: gc_pressure,
|
|
103
111
|
status: "completed",
|
|
104
112
|
sql_queries: sql_queries,
|
|
105
113
|
rails_env: DeadBro.env,
|
|
@@ -153,6 +161,7 @@ module DeadBro
|
|
|
153
161
|
# Get SQL queries executed during this job
|
|
154
162
|
sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
|
|
155
163
|
db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
|
|
164
|
+
gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
|
|
156
165
|
|
|
157
166
|
# Stop memory tracking and get collected memory data
|
|
158
167
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
@@ -191,6 +200,7 @@ module DeadBro
|
|
|
191
200
|
queue_duration_ms: queue_duration_ms,
|
|
192
201
|
db_connection_wait_ms: db_connection_stats[:wait_ms],
|
|
193
202
|
db_connection_checkouts: db_connection_stats[:checkouts],
|
|
203
|
+
gc_pressure: gc_pressure,
|
|
194
204
|
status: "failed",
|
|
195
205
|
sql_queries: sql_queries,
|
|
196
206
|
exception_class: exception&.class&.name,
|
|
@@ -217,6 +227,7 @@ module DeadBro
|
|
|
217
227
|
def self.drain_job_tracking
|
|
218
228
|
DeadBro::SqlSubscriber.stop_request_tracking if defined?(DeadBro::SqlSubscriber)
|
|
219
229
|
DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
|
|
230
|
+
DeadBro::GcTracker.stop_request_tracking if defined?(DeadBro::GcTracker)
|
|
220
231
|
DeadBro::LightweightMemoryTracker.stop_request_tracking if defined?(DeadBro::LightweightMemoryTracker)
|
|
221
232
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
222
233
|
DeadBro::MemoryTrackingSubscriber.stop_request_tracking
|
|
@@ -61,6 +61,9 @@ module DeadBro
|
|
|
61
61
|
DeadBro::DbConnectionSubscriber.start_request_tracking
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Start GC pressure tracking — snapshot before any app code runs
|
|
65
|
+
DeadBro::GcTracker.start_request_tracking if defined?(DeadBro::GcTracker)
|
|
66
|
+
|
|
64
67
|
# Start outgoing HTTP accumulation for this request
|
|
65
68
|
Thread.current[:dead_bro_http_events] = []
|
|
66
69
|
|
|
@@ -97,6 +100,7 @@ module DeadBro
|
|
|
97
100
|
Thread.current[:dead_bro_http_events] = nil
|
|
98
101
|
Thread.current[:dead_bro_queue_duration_ms] = nil
|
|
99
102
|
DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
|
|
103
|
+
Thread.current[DeadBro::GcTracker::THREAD_KEY] = nil if defined?(DeadBro::GcTracker)
|
|
100
104
|
Thread.current[DeadBro::TRACKING_START_TIME_KEY] = nil
|
|
101
105
|
end
|
|
102
106
|
|
data/lib/dead_bro/subscriber.rb
CHANGED
|
@@ -65,6 +65,9 @@ module DeadBro
|
|
|
65
65
|
# Stop DB connection pool wait tracking
|
|
66
66
|
db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
|
|
67
67
|
|
|
68
|
+
# Stop GC pressure tracking — diff minor/major runs, objects allocated, GC time
|
|
69
|
+
gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
|
|
70
|
+
|
|
68
71
|
# Stop view rendering tracking and get collected view events
|
|
69
72
|
view_events = DeadBro::ViewRenderingSubscriber.stop_request_tracking
|
|
70
73
|
view_performance = DeadBro::ViewRenderingSubscriber.analyze_view_performance(view_events)
|
|
@@ -178,6 +181,7 @@ module DeadBro
|
|
|
178
181
|
queue_duration_ms: Thread.current[:dead_bro_queue_duration_ms],
|
|
179
182
|
db_connection_wait_ms: db_connection_stats[:wait_ms],
|
|
180
183
|
db_connection_checkouts: db_connection_stats[:checkouts],
|
|
184
|
+
gc_pressure: gc_pressure,
|
|
181
185
|
logs: DeadBro.logger.logs
|
|
182
186
|
}
|
|
183
187
|
client.post_metric(event_name: name, payload: payload)
|
|
@@ -199,6 +203,7 @@ module DeadBro
|
|
|
199
203
|
end
|
|
200
204
|
Thread.current[:dead_bro_http_events] = nil
|
|
201
205
|
DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
|
|
206
|
+
DeadBro::GcTracker.stop_request_tracking if defined?(DeadBro::GcTracker)
|
|
202
207
|
rescue
|
|
203
208
|
# Best effort — draining must never raise from the notifications callback.
|
|
204
209
|
end
|
data/lib/dead_bro/version.rb
CHANGED
data/lib/dead_bro.rb
CHANGED
|
@@ -18,6 +18,7 @@ module DeadBro
|
|
|
18
18
|
autoload :MemoryTrackingSubscriber, "dead_bro/memory_tracking_subscriber"
|
|
19
19
|
autoload :MemoryLeakDetector, "dead_bro/memory_leak_detector"
|
|
20
20
|
autoload :LightweightMemoryTracker, "dead_bro/lightweight_memory_tracker"
|
|
21
|
+
autoload :GcTracker, "dead_bro/gc_tracker"
|
|
21
22
|
autoload :MemoryHelpers, "dead_bro/memory_helpers"
|
|
22
23
|
autoload :JobSubscriber, "dead_bro/job_subscriber"
|
|
23
24
|
autoload :JobSqlTrackingMiddleware, "dead_bro/job_sql_tracking_middleware"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dead_bro
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emanuel Comsa
|
|
@@ -37,6 +37,7 @@ files:
|
|
|
37
37
|
- lib/dead_bro/dispatcher.rb
|
|
38
38
|
- lib/dead_bro/elasticsearch_subscriber.rb
|
|
39
39
|
- lib/dead_bro/error_middleware.rb
|
|
40
|
+
- lib/dead_bro/gc_tracker.rb
|
|
40
41
|
- lib/dead_bro/http_instrumentation.rb
|
|
41
42
|
- lib/dead_bro/job_sql_tracking_middleware.rb
|
|
42
43
|
- lib/dead_bro/job_subscriber.rb
|