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 +4 -4
- data/lib/dead_bro/db_connection_subscriber.rb +49 -0
- data/lib/dead_bro/job_sql_tracking_middleware.rb +5 -0
- data/lib/dead_bro/job_subscriber.rb +24 -1
- data/lib/dead_bro/railtie.rb +4 -0
- data/lib/dead_bro/sql_tracking_middleware.rb +45 -4
- data/lib/dead_bro/subscriber.rb +13 -0
- data/lib/dead_bro/version.rb +1 -1
- 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: 3f7618eda689dd41bbb5c71cdff40619e5857a71b31a182f234064d89197d79c
|
|
4
|
+
data.tar.gz: fbcac3f8c189834690a5a75e013f07cec20044f4e74285139aa22f66ae0cf65d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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]
|
|
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
|
|
data/lib/dead_bro/railtie.rb
CHANGED
|
@@ -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
|
data/lib/dead_bro/subscriber.rb
CHANGED
|
@@ -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
|
data/lib/dead_bro/version.rb
CHANGED
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.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
|