dead_bro 0.2.13 → 0.2.14

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: 2c308968cd84cfa8df47974178b741ab2847809754e86dc49cbe716d07a7a0bb
4
- data.tar.gz: 6cdfb10f1ac9b2ecd9994bf6f35d8468022dc024e16e9ab31c1664fa91db7041
3
+ metadata.gz: 3f7618eda689dd41bbb5c71cdff40619e5857a71b31a182f234064d89197d79c
4
+ data.tar.gz: fbcac3f8c189834690a5a75e013f07cec20044f4e74285139aa22f66ae0cf65d
5
5
  SHA512:
6
- metadata.gz: 2a8f31655b9512739fa991ee94675c1f948c5ae2229dbb9de62a4e3c6ea0b5a8883ea880f986d911044e200d0c8ae6e696f6d8a58e5c338da6c8c3756c08e543
7
- data.tar.gz: ddf1ed72e8310eb1f42db17f5124b704c5214447706ed68c1f9bae9f47f669fbf56006733f430a064af0fc8965be161dbb85bcaca2aee6b6c14a68b4865216f9
6
+ metadata.gz: 68864135e9fd51e65293ee369f1002e6f37341627fd674d570781fa163dd5b2345a4e7421356f49bc2bc00c1b8f37946b7fe9d31ff0370a6781f892392c7ec5d
7
+ data.tar.gz: 5e72422ba6297af9ef9a521e675d832dd94a0f81783ae964be53ecdd16eaa96dd4bc55448baceb697ea1b0b4bfd024090009d43dd87a3893f197df607cc08fd4
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadBro
4
+ module DbConnectionSubscriber
5
+ WAIT_KEY = :dead_bro_db_connection_wait_ms
6
+ COUNT_KEY = :dead_bro_db_connection_checkouts
7
+
8
+ # Prepended onto ConnectionPool so every checkout is timed.
9
+ # Only accumulates when a request is being tracked (thread-local is a Numeric).
10
+ module CheckoutInstrumentation
11
+ def checkout(*args)
12
+ return super unless Thread.current[DbConnectionSubscriber::WAIT_KEY].is_a?(Numeric)
13
+
14
+ # Initialize conn before calling super so the rescue block can tell whether
15
+ # checkout succeeded before timing code raised (avoids double-checkout).
16
+ conn = nil
17
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
18
+ conn = super
19
+ Thread.current[DbConnectionSubscriber::WAIT_KEY] += (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000.0
20
+ Thread.current[DbConnectionSubscriber::COUNT_KEY] += 1
21
+ conn
22
+ rescue
23
+ conn || super
24
+ end
25
+ end
26
+
27
+ def self.install!
28
+ return unless defined?(ActiveRecord::ConnectionAdapters::ConnectionPool)
29
+ return if ActiveRecord::ConnectionAdapters::ConnectionPool.ancestors.include?(CheckoutInstrumentation)
30
+
31
+ ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(CheckoutInstrumentation)
32
+ rescue StandardError => e
33
+ warn "[DeadBro] DbConnectionSubscriber install failed: #{e.class}: #{e.message}"
34
+ end
35
+
36
+ def self.start_request_tracking
37
+ Thread.current[WAIT_KEY] = 0.0
38
+ Thread.current[COUNT_KEY] = 0
39
+ end
40
+
41
+ def self.stop_request_tracking
42
+ wait_ms = Thread.current[WAIT_KEY]
43
+ checkouts = Thread.current[COUNT_KEY]
44
+ Thread.current[WAIT_KEY] = nil
45
+ Thread.current[COUNT_KEY] = nil
46
+ { wait_ms: wait_ms&.round(2), checkouts: checkouts }
47
+ end
48
+ end
49
+ end
@@ -24,6 +24,11 @@ module DeadBro
24
24
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
25
25
  DeadBro::MemoryTrackingSubscriber.start_request_tracking
26
26
  end
27
+
28
+ # Start DB connection pool wait tracking
29
+ if defined?(DeadBro::DbConnectionSubscriber)
30
+ DeadBro::DbConnectionSubscriber.start_request_tracking
31
+ end
27
32
  end
28
33
  rescue
29
34
  # Never raise from instrumentation install
@@ -43,6 +43,7 @@ module DeadBro
43
43
  end
44
44
 
45
45
  duration_ms = ((finished - started) * 1000.0).round(2)
46
+ queue_duration_ms = job_queue_duration_ms(data[:job], started)
46
47
 
47
48
  # Ensure tracking was started (fallback if perform_start.active_job didn't fire)
48
49
  # This handles job backends that don't emit perform_start events
@@ -50,6 +51,7 @@ module DeadBro
50
51
  DeadBro.logger.clear
51
52
  Thread.current[DeadBro::TRACKING_START_TIME_KEY] = Time.now
52
53
  DeadBro::SqlSubscriber.start_request_tracking
54
+ DeadBro::DbConnectionSubscriber.start_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
53
55
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
54
56
  DeadBro::MemoryTrackingSubscriber.start_request_tracking
55
57
  else
@@ -59,6 +61,7 @@ module DeadBro
59
61
 
60
62
  # Get SQL queries executed during this job
61
63
  sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
64
+ db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
62
65
 
63
66
  # Stop memory tracking and get collected memory data
64
67
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
@@ -94,6 +97,9 @@ module DeadBro
94
97
  queue_name: data[:job].queue_name,
95
98
  arguments: safe_arguments(data[:job].arguments),
96
99
  duration_ms: duration_ms,
100
+ queue_duration_ms: queue_duration_ms,
101
+ db_connection_wait_ms: db_connection_stats[:wait_ms],
102
+ db_connection_checkouts: db_connection_stats[:checkouts],
97
103
  status: "completed",
98
104
  sql_queries: sql_queries,
99
105
  rails_env: DeadBro.env,
@@ -129,13 +135,14 @@ module DeadBro
129
135
 
130
136
  duration_ms = ((finished - started) * 1000.0).round(2)
131
137
  exception = data[:exception_object]
132
- data[:job].class.name
138
+ queue_duration_ms = job_queue_duration_ms(data[:job], started)
133
139
 
134
140
  # Ensure tracking was started (fallback if perform_start.active_job didn't fire)
135
141
  unless DeadBro::SqlSubscriber.tracking_active?
136
142
  DeadBro.logger.clear
137
143
  Thread.current[DeadBro::TRACKING_START_TIME_KEY] = Time.now
138
144
  DeadBro::SqlSubscriber.start_request_tracking
145
+ DeadBro::DbConnectionSubscriber.start_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
139
146
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
140
147
  DeadBro::MemoryTrackingSubscriber.start_request_tracking
141
148
  else
@@ -145,6 +152,7 @@ module DeadBro
145
152
 
146
153
  # Get SQL queries executed during this job
147
154
  sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
155
+ db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
148
156
 
149
157
  # Stop memory tracking and get collected memory data
150
158
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
@@ -180,6 +188,9 @@ module DeadBro
180
188
  queue_name: data[:job].queue_name,
181
189
  arguments: safe_arguments(data[:job].arguments),
182
190
  duration_ms: duration_ms,
191
+ queue_duration_ms: queue_duration_ms,
192
+ db_connection_wait_ms: db_connection_stats[:wait_ms],
193
+ db_connection_checkouts: db_connection_stats[:checkouts],
183
194
  status: "failed",
184
195
  sql_queries: sql_queries,
185
196
  exception_class: exception&.class&.name,
@@ -205,6 +216,7 @@ module DeadBro
205
216
  # build a payload (excluded job / sampled out). Matches Subscriber.drain_request_tracking.
206
217
  def self.drain_job_tracking
207
218
  DeadBro::SqlSubscriber.stop_request_tracking if defined?(DeadBro::SqlSubscriber)
219
+ DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
208
220
  DeadBro::LightweightMemoryTracker.stop_request_tracking if defined?(DeadBro::LightweightMemoryTracker)
209
221
  if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
210
222
  DeadBro::MemoryTrackingSubscriber.stop_request_tracking
@@ -215,6 +227,17 @@ module DeadBro
215
227
 
216
228
  private
217
229
 
230
+ def self.job_queue_duration_ms(job, perform_started)
231
+ enqueued_at = job.enqueued_at
232
+ return nil if enqueued_at.nil?
233
+
234
+ enqueued_time = enqueued_at.is_a?(Time) ? enqueued_at : Time.parse(enqueued_at.to_s)
235
+ diff_ms = ((perform_started - enqueued_time) * 1000.0).round(2)
236
+ diff_ms >= 0 ? diff_ms : nil
237
+ rescue
238
+ nil
239
+ end
240
+
218
241
  def self.safe_arguments(arguments)
219
242
  return [] unless arguments.is_a?(Array)
220
243
 
@@ -36,6 +36,10 @@ if defined?(Rails) && defined?(Rails::Railtie)
36
36
  require "dead_bro/elasticsearch_subscriber"
37
37
  DeadBro::ElasticsearchSubscriber.subscribe!
38
38
 
39
+ # Install DB connection pool wait tracking
40
+ require "dead_bro/db_connection_subscriber"
41
+ DeadBro::DbConnectionSubscriber.install!
42
+
39
43
  # Install view rendering tracking
40
44
  require "dead_bro/view_rendering_subscriber"
41
45
  DeadBro::ViewRenderingSubscriber.subscribe!(client: shared_client)
@@ -9,6 +9,15 @@ module DeadBro
9
9
  def call(env)
10
10
  return @app.call(env) if DeadBro.configuration.skip_tracking?
11
11
 
12
+ # Capture rack entry time before any setup so middleware overhead is accurately measured.
13
+ rack_entry = Time.now
14
+ Thread.current[DeadBro::TRACKING_START_TIME_KEY] = rack_entry
15
+
16
+ # Queue time: gap between when the upstream proxy accepted the connection and when a Rack
17
+ # worker picked it up. Heroku sets X-Request-Start as "t=<microseconds>"; nginx typically
18
+ # uses "t=<seconds.ms>". Both are parsed below.
19
+ Thread.current[:dead_bro_queue_duration_ms] = parse_queue_start(env, rack_entry)
20
+
12
21
  # Clear logs for this request
13
22
  DeadBro.logger.clear
14
23
 
@@ -47,12 +56,14 @@ module DeadBro
47
56
  DeadBro::ElasticsearchSubscriber.start_request_tracking
48
57
  end
49
58
 
59
+ # Start DB connection pool wait tracking
60
+ if defined?(DeadBro::DbConnectionSubscriber)
61
+ DeadBro::DbConnectionSubscriber.start_request_tracking
62
+ end
63
+
50
64
  # Start outgoing HTTP accumulation for this request
51
65
  Thread.current[:dead_bro_http_events] = []
52
66
 
53
- # Set tracking start time once for all subscribers (before starting any tracking)
54
- Thread.current[DeadBro::TRACKING_START_TIME_KEY] = Time.now
55
-
56
67
  @app.call(env)
57
68
  ensure
58
69
  # Clean up thread-local storage
@@ -81,10 +92,40 @@ module DeadBro
81
92
  Thread.current[:dead_bro_lightweight_memory] = nil
82
93
  end
83
94
 
84
- # Clean up HTTP events, ES events, and tracking start time
95
+ # Clean up HTTP events, ES events, DB connection tracking, and tracking start time
85
96
  Thread.current[:dead_bro_elasticsearch_events] = nil
86
97
  Thread.current[:dead_bro_http_events] = nil
98
+ Thread.current[:dead_bro_queue_duration_ms] = nil
99
+ DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
87
100
  Thread.current[DeadBro::TRACKING_START_TIME_KEY] = nil
88
101
  end
102
+
103
+ private
104
+
105
+ def parse_queue_start(env, rack_entry)
106
+ raw = env["HTTP_X_REQUEST_START"] || env["HTTP_X_QUEUE_START"]
107
+ return nil if raw.nil? || raw.empty?
108
+
109
+ # Strip "t=" prefix used by Heroku and nginx
110
+ raw = raw.sub(/\At=/, "")
111
+ num = raw.to_f
112
+ return nil if num <= 0
113
+
114
+ request_start =
115
+ if num > 1_000_000_000_000_000 # microseconds (Heroku)
116
+ Time.at(num / 1_000_000.0)
117
+ elsif num > 1_000_000_000_000 # milliseconds
118
+ Time.at(num / 1_000.0)
119
+ else # seconds (nginx)
120
+ Time.at(num)
121
+ end
122
+
123
+ # Guard against clocks being out of sync or wildly misconfigured proxy timestamps.
124
+ # Cap at 60 s — anything larger almost certainly means the header value is wrong.
125
+ diff_ms = ((rack_entry - request_start) * 1000.0).round(2)
126
+ diff_ms >= 0 && diff_ms <= 60_000 ? diff_ms : nil
127
+ rescue
128
+ nil
129
+ end
89
130
  end
90
131
  end
@@ -49,6 +49,11 @@ module DeadBro
49
49
  end
50
50
 
51
51
  duration_ms = ((finished - started) * 1000.0).round(2)
52
+
53
+ # Time spent in Rack middleware before ActionController took over (routing, session, auth, etc.)
54
+ rack_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
55
+ rack_duration_ms = rack_start ? ([((started - rack_start) * 1000.0), 0].max).round(2) : nil
56
+
52
57
  # Stop SQL tracking and get collected queries (this was started by the request)
53
58
  sql_queries = DeadBro::SqlSubscriber.stop_request_tracking
54
59
 
@@ -57,6 +62,9 @@ module DeadBro
57
62
  redis_events = defined?(DeadBro::RedisSubscriber) ? DeadBro::RedisSubscriber.stop_request_tracking : []
58
63
  elasticsearch_events = defined?(DeadBro::ElasticsearchSubscriber) ? DeadBro::ElasticsearchSubscriber.stop_request_tracking : []
59
64
 
65
+ # Stop DB connection pool wait tracking
66
+ db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
67
+
60
68
  # Stop view rendering tracking and get collected view events
61
69
  view_events = DeadBro::ViewRenderingSubscriber.stop_request_tracking
62
70
  view_performance = DeadBro::ViewRenderingSubscriber.analyze_view_performance(view_events)
@@ -166,6 +174,10 @@ module DeadBro
166
174
  view_performance: view_performance,
167
175
  memory_events: memory_events,
168
176
  memory_performance: memory_performance,
177
+ rack_duration_ms: rack_duration_ms,
178
+ queue_duration_ms: Thread.current[:dead_bro_queue_duration_ms],
179
+ db_connection_wait_ms: db_connection_stats[:wait_ms],
180
+ db_connection_checkouts: db_connection_stats[:checkouts],
169
181
  logs: DeadBro.logger.logs
170
182
  }
171
183
  client.post_metric(event_name: name, payload: payload)
@@ -186,6 +198,7 @@ module DeadBro
186
198
  DeadBro::MemoryTrackingSubscriber.stop_request_tracking
187
199
  end
188
200
  Thread.current[:dead_bro_http_events] = nil
201
+ DeadBro::DbConnectionSubscriber.stop_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
189
202
  rescue
190
203
  # Best effort — draining must never raise from the notifications callback.
191
204
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadBro
4
- VERSION = "0.2.13"
4
+ VERSION = "0.2.14"
5
5
  end
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.13
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuel Comsa
@@ -33,6 +33,7 @@ files:
33
33
  - lib/dead_bro/collectors/sample_store.rb
34
34
  - lib/dead_bro/collectors/system.rb
35
35
  - lib/dead_bro/configuration.rb
36
+ - lib/dead_bro/db_connection_subscriber.rb
36
37
  - lib/dead_bro/dispatcher.rb
37
38
  - lib/dead_bro/elasticsearch_subscriber.rb
38
39
  - lib/dead_bro/error_middleware.rb