dead_bro 0.2.14 → 0.2.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f7618eda689dd41bbb5c71cdff40619e5857a71b31a182f234064d89197d79c
4
- data.tar.gz: fbcac3f8c189834690a5a75e013f07cec20044f4e74285139aa22f66ae0cf65d
3
+ metadata.gz: 0bfea7b6292743b5e6a0f7226c1bd8877ebfbe1131f95d5c1679f07377768ed2
4
+ data.tar.gz: '08ab07422afbd9d2d340e5195f47ceda9a184faa9a870e5464d6169dfc86ee6b'
5
5
  SHA512:
6
- metadata.gz: 68864135e9fd51e65293ee369f1002e6f37341627fd674d570781fa163dd5b2345a4e7421356f49bc2bc00c1b8f37946b7fe9d31ff0370a6781f892392c7ec5d
7
- data.tar.gz: 5e72422ba6297af9ef9a521e675d832dd94a0f81783ae964be53ecdd16eaa96dd4bc55448baceb697ea1b0b4bfd024090009d43dd87a3893f197df607cc08fd4
6
+ metadata.gz: 76822c848e35d76f6cb54d7953e9ad7bb68de09b706be6f7586c4c67a3e430074fcd33515394965aeeb1d41c74577295ce56942badb05d96fd390e1daefeb410
7
+ data.tar.gz: 2cd76061aa3f443723dfc3e934b1cd36e7e7576257f6637a4a1deab4e05c3154d309c9d4b59d084b46e89521774e47475a84fdc711db42d03b1dab496682d532
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.16] - 2026-05-24
4
+
5
+ ### Added
6
+ - `ArObjectTracker`: subscribes to Rails' built-in `instantiation.active_record` notification to count the total number of ActiveRecord model instances hydrated during each request or background job. The count is reported as `ar_instantiation_count` in every payload. Uses a thread-local counter with an idempotent `subscribe!` guard, matching the same start/stop lifecycle as `GcTracker`. No monkey-patching required — Rails emits this event natively with a `record_count` field that accumulates correctly across batch loads.
7
+
8
+ ## [0.2.14] - 2026-05-23
9
+
10
+ ### Added
11
+ - `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.
12
+ - 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.
13
+ - 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.
14
+ - `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.
15
+ - 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`.
16
+
3
17
  ## [0.1.0] - 2025-08-28
4
18
 
5
19
  - Initial release
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+
5
+ module DeadBro
6
+ module ArObjectTracker
7
+ THREAD_KEY = :dead_bro_ar_objects
8
+
9
+ def self.subscribe!
10
+ return if @subscribed
11
+ @subscribed = true
12
+ ActiveSupport::Notifications.subscribe("instantiation.active_record") do |_name, _started, _finished, _id, data|
13
+ count = Thread.current[THREAD_KEY]
14
+ next unless count
15
+ Thread.current[THREAD_KEY] = count + (data[:record_count] || 1).to_i
16
+ end
17
+ rescue StandardError
18
+ # Never raise from instrumentation install
19
+ end
20
+
21
+ def self.start_request_tracking
22
+ Thread.current[THREAD_KEY] = 0
23
+ end
24
+
25
+ def self.stop_request_tracking
26
+ Thread.current[THREAD_KEY]
27
+ ensure
28
+ Thread.current[THREAD_KEY] = nil
29
+ end
30
+ end
31
+ end
@@ -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,13 @@ 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
+ DeadBro::ArObjectTracker.start_request_tracking if defined?(DeadBro::ArObjectTracker)
19
+ rescue
20
+ end
21
+
15
22
  # Track job execution
16
23
  ActiveSupport::Notifications.subscribe(JOB_EVENT_NAME) do |name, started, finished, _unique_id, data|
17
24
  begin
@@ -62,6 +69,8 @@ module DeadBro
62
69
  # Get SQL queries executed during this job
63
70
  sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
64
71
  db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
72
+ gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
73
+ ar_instantiation_count = defined?(DeadBro::ArObjectTracker) ? DeadBro::ArObjectTracker.stop_request_tracking : nil
65
74
 
66
75
  # Stop memory tracking and get collected memory data
67
76
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
@@ -100,6 +109,8 @@ module DeadBro
100
109
  queue_duration_ms: queue_duration_ms,
101
110
  db_connection_wait_ms: db_connection_stats[:wait_ms],
102
111
  db_connection_checkouts: db_connection_stats[:checkouts],
112
+ gc_pressure: gc_pressure,
113
+ ar_instantiation_count: ar_instantiation_count,
103
114
  status: "completed",
104
115
  sql_queries: sql_queries,
105
116
  rails_env: DeadBro.env,
@@ -153,6 +164,8 @@ module DeadBro
153
164
  # Get SQL queries executed during this job
154
165
  sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
155
166
  db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
167
+ gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
168
+ ar_instantiation_count = defined?(DeadBro::ArObjectTracker) ? DeadBro::ArObjectTracker.stop_request_tracking : nil
156
169
 
157
170
  # Stop memory tracking and get collected memory data
158
171
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
@@ -191,6 +204,8 @@ module DeadBro
191
204
  queue_duration_ms: queue_duration_ms,
192
205
  db_connection_wait_ms: db_connection_stats[:wait_ms],
193
206
  db_connection_checkouts: db_connection_stats[:checkouts],
207
+ gc_pressure: gc_pressure,
208
+ ar_instantiation_count: ar_instantiation_count,
194
209
  status: "failed",
195
210
  sql_queries: sql_queries,
196
211
  exception_class: exception&.class&.name,
@@ -217,6 +232,8 @@ module DeadBro
217
232
  def self.drain_job_tracking
218
233
  DeadBro::SqlSubscriber.stop_request_tracking if defined?(DeadBro::SqlSubscriber)
219
234
  DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
235
+ DeadBro::GcTracker.stop_request_tracking if defined?(DeadBro::GcTracker)
236
+ DeadBro::ArObjectTracker.stop_request_tracking if defined?(DeadBro::ArObjectTracker)
220
237
  DeadBro::LightweightMemoryTracker.stop_request_tracking if defined?(DeadBro::LightweightMemoryTracker)
221
238
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
222
239
  DeadBro::MemoryTrackingSubscriber.stop_request_tracking
@@ -40,6 +40,10 @@ if defined?(Rails) && defined?(Rails::Railtie)
40
40
  require "dead_bro/db_connection_subscriber"
41
41
  DeadBro::DbConnectionSubscriber.install!
42
42
 
43
+ # Install ActiveRecord object instantiation tracking
44
+ require "dead_bro/ar_object_tracker"
45
+ DeadBro::ArObjectTracker.subscribe!
46
+
43
47
  # Install view rendering tracking
44
48
  require "dead_bro/view_rendering_subscriber"
45
49
  DeadBro::ViewRenderingSubscriber.subscribe!(client: shared_client)
@@ -61,6 +61,12 @@ 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
+
67
+ # Start AR object instantiation counting for this request
68
+ DeadBro::ArObjectTracker.start_request_tracking if defined?(DeadBro::ArObjectTracker)
69
+
64
70
  # Start outgoing HTTP accumulation for this request
65
71
  Thread.current[:dead_bro_http_events] = []
66
72
 
@@ -97,6 +103,9 @@ module DeadBro
97
103
  Thread.current[:dead_bro_http_events] = nil
98
104
  Thread.current[:dead_bro_queue_duration_ms] = nil
99
105
  DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
106
+ Thread.current[DeadBro::GcTracker::THREAD_KEY] = nil if defined?(DeadBro::GcTracker)
107
+ # Bypass stop_request_tracking intentionally — cleanup only, no return value needed here.
108
+ Thread.current[DeadBro::ArObjectTracker::THREAD_KEY] = nil if defined?(DeadBro::ArObjectTracker)
100
109
  Thread.current[DeadBro::TRACKING_START_TIME_KEY] = nil
101
110
  end
102
111
 
@@ -65,6 +65,12 @@ 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
+
71
+ # Stop AR object instantiation tracking
72
+ ar_instantiation_count = defined?(DeadBro::ArObjectTracker) ? DeadBro::ArObjectTracker.stop_request_tracking : nil
73
+
68
74
  # Stop view rendering tracking and get collected view events
69
75
  view_events = DeadBro::ViewRenderingSubscriber.stop_request_tracking
70
76
  view_performance = DeadBro::ViewRenderingSubscriber.analyze_view_performance(view_events)
@@ -178,6 +184,8 @@ module DeadBro
178
184
  queue_duration_ms: Thread.current[:dead_bro_queue_duration_ms],
179
185
  db_connection_wait_ms: db_connection_stats[:wait_ms],
180
186
  db_connection_checkouts: db_connection_stats[:checkouts],
187
+ gc_pressure: gc_pressure,
188
+ ar_instantiation_count: ar_instantiation_count,
181
189
  logs: DeadBro.logger.logs
182
190
  }
183
191
  client.post_metric(event_name: name, payload: payload)
@@ -199,6 +207,8 @@ module DeadBro
199
207
  end
200
208
  Thread.current[:dead_bro_http_events] = nil
201
209
  DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
210
+ DeadBro::GcTracker.stop_request_tracking if defined?(DeadBro::GcTracker)
211
+ DeadBro::ArObjectTracker.stop_request_tracking if defined?(DeadBro::ArObjectTracker)
202
212
  rescue
203
213
  # Best effort — draining must never raise from the notifications callback.
204
214
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadBro
4
- VERSION = "0.2.14"
4
+ VERSION = "0.2.16"
5
5
  end
data/lib/dead_bro.rb CHANGED
@@ -18,6 +18,8 @@ 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"
22
+ autoload :ArObjectTracker, "dead_bro/ar_object_tracker"
21
23
  autoload :MemoryHelpers, "dead_bro/memory_helpers"
22
24
  autoload :JobSubscriber, "dead_bro/job_subscriber"
23
25
  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.14
4
+ version: 0.2.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuel Comsa
@@ -21,6 +21,7 @@ files:
21
21
  - FEATURES.md
22
22
  - README.md
23
23
  - lib/dead_bro.rb
24
+ - lib/dead_bro/ar_object_tracker.rb
24
25
  - lib/dead_bro/cache_subscriber.rb
25
26
  - lib/dead_bro/circuit_breaker.rb
26
27
  - lib/dead_bro/client.rb
@@ -37,6 +38,7 @@ files:
37
38
  - lib/dead_bro/dispatcher.rb
38
39
  - lib/dead_bro/elasticsearch_subscriber.rb
39
40
  - lib/dead_bro/error_middleware.rb
41
+ - lib/dead_bro/gc_tracker.rb
40
42
  - lib/dead_bro/http_instrumentation.rb
41
43
  - lib/dead_bro/job_sql_tracking_middleware.rb
42
44
  - lib/dead_bro/job_subscriber.rb