dead_bro 0.2.36 → 0.2.38
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 +23 -0
- data/README.md +14 -0
- data/lib/dead_bro/cache_subscriber.rb +12 -1
- data/lib/dead_bro/configuration.rb +24 -1
- data/lib/dead_bro/elasticsearch_subscriber.rb +4 -1
- data/lib/dead_bro/http_instrumentation.rb +48 -6
- data/lib/dead_bro/job_subscriber.rb +2 -0
- data/lib/dead_bro/middleware_probe.rb +85 -0
- data/lib/dead_bro/railtie.rb +16 -0
- data/lib/dead_bro/redis_subscriber.rb +10 -1
- data/lib/dead_bro/span_tracker.rb +650 -0
- data/lib/dead_bro/sql_subscriber.rb +8 -2
- data/lib/dead_bro/sql_tracking_middleware.rb +6 -0
- data/lib/dead_bro/subscriber.rb +39 -20
- data/lib/dead_bro/version.rb +1 -2
- data/lib/dead_bro/watch_tracker.rb +5 -0
- data/lib/dead_bro.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4258f98fde5b0e222593ebfadee095b6402576d94a33a2b57ac26c71f0b75d47
|
|
4
|
+
data.tar.gz: 4a268f4eb24b882bf2dddd36a45db694f664319d3fa9650c3587eb59dd37f2af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94f6cccf6594f0c50467b61a5d2bf53d6b73e7e72e6f042c2eae3cea2bad12e674e8a2f525bb7880f51f3b6cc679e3f3951dfba224b07e9701b48669060ffd51
|
|
7
|
+
data.tar.gz: eae0e565ae2d1b43f20df7c3fd14311c7543f29b1680f0495c1fa1b4ca86a30c880b20dc94ba9cc147288dff41fa39eb7aae360dafb76139b751dccc380d02e4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.38] - 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Nested Request Trace.** Each request now sends its span tree as `spans`: the Rack middleware chain, the router, the controller action, view renders (layouts, templates, partials and collections), `DeadBro.watch` blocks, the block a `Rails.cache.fetch` miss runs, and SQL queries, each with an `id` and the id of the span it ran inside (`parent_id`). Repeated calls on the same path share one span with a `count`: a partial rendered 50 times from one template is one span, and the query it runs each time is one span under it. A span's `total_ms` is its calls' summed time with the spans nested in it included, and `start_ms` / `end_ms` are its first call's start and its last call's end. Cache, Redis, HTTP, Elasticsearch and transaction events carry the `parent_id` of the span they ran in, the cache event of a `Rails.cache.fetch` block and `DeadBro.watch` events the `span_id` of their span, and `sql_queries` entries the `id` their SQL spans refer to. DeadBro shows the tree with each span's total time and self time (total minus the spans nested in it). Up to 500 spans are kept per request; `spans_dropped` counts the ones left out. Background jobs don't send `spans`. An action that uses `ActionController::Live` runs on a thread of its own; its spans are recorded there, without the middleware chain.
|
|
7
|
+
- **Middleware timing.** Each middleware of the app's Rails middleware stack and the router it ends in are timed as spans, so the trace shows the time each one spends before and after the app. The dashboard's middleware spans setting turns their recording off and on without a restart, and `config.middleware_spans_enabled = false` in an initializer leaves the middleware stack as Rails builds it, without the probes. Either way, the trace then starts at the controller action.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- A request's payload is sent once the outermost middleware has returned instead of when the controller action finishes, so that the middleware spans are complete. If that middleware never returns through DeadBro's probe, for example when a timeout outside the Rails middleware stack raises into the thread at that moment, the payload is sent when the thread's next request comes in, and the next request's spans start afresh.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **An HTTP request made through Faraday was recorded twice:** once by DeadBro's Faraday middleware and once by its `Net::HTTP` or Typhoeus hook underneath, so its time was counted twice. It is now recorded once, by the Faraday middleware, which also sends the request's start offset and exception class as the `Net::HTTP` hook does. Elasticsearch requests sent through Faraday are recorded once too. A request the Faraday middleware leaves out (one to localhost) is still left to the `Net::HTTP` or Typhoeus hook, as before.
|
|
14
|
+
|
|
15
|
+
## [0.2.37] - 2026-10-26
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **`sql_count` stopped at 100 on any page that ran more queries.** It was never a query count: the subscriber read `ActiveRecord::Base.connection.query_cache.size`, the number of distinct SELECTs in the query cache — an LRU capped at 100 entries since Rails 7.1. An N+1 page that ran 188 queries reported "SQL Count 100" and "100 calls/request". `sql_count` is now the sum of the per-statement aggregate counts the SQL subscriber already keeps (every query, not only the first `MAX_TRACKED_QUERIES`), with query-cache hits included the way Rails counts them in its `(N queries, M cached)` log line. The old lookup also leased a database connection on requests that ran no SQL; nothing in the subscriber touches `ActiveRecord::Base.connection` any more.
|
|
19
|
+
- Background jobs sent no `sql_count` at all, so the dashboard showed 0 database calls for every job; job payloads now carry the same count.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- `sql_cached_count` on request and job payloads: how many of `sql_count` were answered by the query cache.
|
|
23
|
+
|
|
24
|
+
## [0.2.36] - 2026-10-25
|
|
25
|
+
|
|
3
26
|
### Added
|
|
4
27
|
- **`gc_pressure` now says whether the window's GC had finished.** `gc_state_at_end` is the GC's phase when a request or job window closes — `"none"`, `"marking"` or `"sweeping"`, from `GC.latest_gc_info(:state)` — sent with the other memory-tracking fields. Ruby marks and sweeps in steps between allocations, so a GC counted in the window is often still running when it closes, and until it finishes `heap_live_slots_growth` still counts garbage it hasn't freed. The dashboard uses it so a sample measured mid-GC isn't flagged as a possible leak. It's a single-key read; no hash is built.
|
|
5
28
|
|
data/README.md
CHANGED
|
@@ -118,6 +118,20 @@ DeadBro automatically tracks view rendering performance for each request. This i
|
|
|
118
118
|
- **Slow view detection**: Identification of the slowest rendering views
|
|
119
119
|
- **Frequency analysis**: Most frequently rendered views
|
|
120
120
|
|
|
121
|
+
## Request Trace (span tree)
|
|
122
|
+
|
|
123
|
+
Each web request carries its span tree as `spans`, which DeadBro shows as a nested Request Trace with each span's total and self time:
|
|
124
|
+
|
|
125
|
+
- **Spans**: every middleware of the Rails middleware stack and the router it ends in, the controller action, view renders (layouts, templates, partials, collections), `DeadBro.watch` blocks, the block a `Rails.cache.fetch` miss runs, and SQL queries. Each has an `id`, the `parent_id` of the span it ran inside (`nil` at the top) and a `kind` (`middleware`, `endpoint`, `action`, `view`, `watch`, `cache`, `sql`).
|
|
126
|
+
- **Repeated calls**: calls with the same name under the same parent share one span with a `count`. A partial rendered 50 times from one template is one span; the query it runs each time is one span under it. `Rails.cache.fetch` blocks whose keys differ only in their digits share one span, named by the key with each run of digits as `?`.
|
|
127
|
+
- **Times**: `total_ms` is the calls' summed duration, nested spans included; `start_ms` and `end_ms` are the first call's start and the last call's end, as offsets from the request's tracking start like every `start_offset_ms` (middleware that runs before DeadBro's own has negative offsets).
|
|
128
|
+
- **Other events**: cache, Redis, HTTP, Elasticsearch and transaction events stay in their own lists and carry the `parent_id` of the span they ran in. The cache event of a `Rails.cache.fetch` block and `DeadBro.watch` events carry the `span_id` of their span, and SQL spans refer to their `sql_queries` entry by `ref`.
|
|
129
|
+
- **Limits**: up to 500 spans per request; `spans_dropped` counts the ones left out, whose time stays in their parent's self time.
|
|
130
|
+
|
|
131
|
+
Middleware is timed by a probe Rails places around each middleware when it builds the middleware stack. The middleware stack returns after the controller action has finished, so a request's payload is sent once the outermost middleware has returned. An action that uses `ActionController::Live` runs on a thread of its own, and its spans are recorded there, without the middleware chain. Background jobs don't send `spans`.
|
|
132
|
+
|
|
133
|
+
The dashboard's **Middleware spans** setting (`middleware_spans_enabled`, on by default) turns their recording off and on without a restart. While it's off, the probes stay in place but record nothing: the trace starts at the controller action, and a request's payload is sent when the action finishes. To leave the middleware stack as Rails builds it, without the probes, set `config.middleware_spans_enabled = false` in `config/initializers/dead_bro.rb` (default `true`). It's read when Rails builds the middleware stack, after the app's initializers have run, so a change takes effect when the app restarts; while it's `false`, the dashboard setting can't turn the spans on.
|
|
134
|
+
|
|
121
135
|
## Memory Tracking & Leak Detection
|
|
122
136
|
|
|
123
137
|
DeadBro automatically tracks memory usage and detects memory leaks with minimal performance impact. This includes:
|
|
@@ -69,7 +69,7 @@ module DeadBro
|
|
|
69
69
|
def self.build_event(name, data, duration_ms, start_offset_ms = nil)
|
|
70
70
|
return nil unless data.is_a?(Hash)
|
|
71
71
|
|
|
72
|
-
{
|
|
72
|
+
event = {
|
|
73
73
|
event: name,
|
|
74
74
|
duration_ms: duration_ms,
|
|
75
75
|
start_offset_ms: start_offset_ms,
|
|
@@ -80,6 +80,17 @@ module DeadBro
|
|
|
80
80
|
namespace: safe_namespace(data[:namespace]),
|
|
81
81
|
at: Time.now.utc.to_i
|
|
82
82
|
}
|
|
83
|
+
# A Rails.cache.fetch block is a span of the request's tree: its event
|
|
84
|
+
# names that span, which ran in the span the event's parent_id gives.
|
|
85
|
+
node = DeadBro::SpanTracker.cache_node(data)
|
|
86
|
+
if node
|
|
87
|
+
event[:span_id] = node[:id]
|
|
88
|
+
event[:parent_id] = node[:parent_id] if node[:parent_id]
|
|
89
|
+
else
|
|
90
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
91
|
+
event[:parent_id] = parent_id if parent_id
|
|
92
|
+
end
|
|
93
|
+
event
|
|
83
94
|
rescue
|
|
84
95
|
nil
|
|
85
96
|
end
|
|
@@ -19,6 +19,14 @@ module DeadBro
|
|
|
19
19
|
alias_method :explain_analyze_enabled, :explain_enabled
|
|
20
20
|
alias_method :explain_analyze_enabled=, :explain_enabled=
|
|
21
21
|
|
|
22
|
+
# Middleware spans. false in the app's initializer leaves the middleware
|
|
23
|
+
# stack as Rails builds it, without a MiddlewareProbe around each
|
|
24
|
+
# middleware and the router, so the span tree starts at the controller
|
|
25
|
+
# action; it's read when Rails builds the stack, after the initializers.
|
|
26
|
+
# With the probes in place, the dashboard's setting of the same name turns
|
|
27
|
+
# their recording on and off (see #middleware_spans_active?).
|
|
28
|
+
attr_accessor :middleware_spans_enabled
|
|
29
|
+
|
|
22
30
|
# Remote-managed settings (overwritten by backend JSON `settings` on successful API responses)
|
|
23
31
|
attr_accessor :memory_tracking_enabled, :allocation_tracking_enabled, :allocation_sample_rate,
|
|
24
32
|
:sample_rate, :slow_query_threshold_ms, :watch_enabled,
|
|
@@ -73,7 +81,7 @@ module DeadBro
|
|
|
73
81
|
explain_enabled slow_query_threshold_ms max_sql_queries_to_send max_logs_to_send
|
|
74
82
|
watch_enabled excluded_controllers excluded_jobs exclusive_controllers exclusive_jobs
|
|
75
83
|
monitor_enabled enable_db_stats enable_process_stats enable_system_stats
|
|
76
|
-
sample_rates_by_type report_routing_errors
|
|
84
|
+
sample_rates_by_type report_routing_errors middleware_spans_enabled
|
|
77
85
|
].freeze
|
|
78
86
|
|
|
79
87
|
def initialize
|
|
@@ -89,6 +97,10 @@ module DeadBro
|
|
|
89
97
|
@explicit_deploy_revision = nil
|
|
90
98
|
@disk_paths = ["/"]
|
|
91
99
|
@interfaces_ignore = %w[lo lo0 docker0]
|
|
100
|
+
@middleware_spans_enabled = true
|
|
101
|
+
# The dashboard's switch for recording middleware spans; on until a
|
|
102
|
+
# settings response says otherwise (older backends never send it).
|
|
103
|
+
@remote_middleware_spans_enabled = true
|
|
92
104
|
|
|
93
105
|
# Remote-managed defaults (used until backend sends real values)
|
|
94
106
|
@sample_rate = 100
|
|
@@ -193,6 +205,10 @@ module DeadBro
|
|
|
193
205
|
# must never be able to switch it on — only off. The local opt-in
|
|
194
206
|
# (explain_enabled) stays untouched; see #explain_active?
|
|
195
207
|
@remote_explain_enabled = !!value
|
|
208
|
+
when "middleware_spans_enabled"
|
|
209
|
+
# The probes are already in place (or not) by now; this only turns
|
|
210
|
+
# their recording on and off. The local value stays as it was.
|
|
211
|
+
@remote_middleware_spans_enabled = !!value
|
|
196
212
|
when "enabled", "memory_tracking_enabled", "allocation_tracking_enabled", "watch_enabled",
|
|
197
213
|
"monitor_enabled", "enable_db_stats", "enable_process_stats", "enable_system_stats",
|
|
198
214
|
"report_routing_errors"
|
|
@@ -212,6 +228,13 @@ module DeadBro
|
|
|
212
228
|
!!(@explain_enabled && @remote_explain_enabled)
|
|
213
229
|
end
|
|
214
230
|
|
|
231
|
+
# Middleware spans are recorded when the app hasn't turned them off in its
|
|
232
|
+
# initializer (which also leaves the stack without probes) and the
|
|
233
|
+
# dashboard hasn't either.
|
|
234
|
+
def middleware_spans_active?
|
|
235
|
+
!!(@middleware_spans_enabled && @remote_middleware_spans_enabled)
|
|
236
|
+
end
|
|
237
|
+
|
|
215
238
|
alias_method :explain_analyze_active?, :explain_active?
|
|
216
239
|
|
|
217
240
|
def heartbeat_due?
|
|
@@ -130,7 +130,7 @@ module DeadBro
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def build_event(method, path, status, duration_ms, start_offset_ms = nil)
|
|
133
|
-
{
|
|
133
|
+
event = {
|
|
134
134
|
method: method.to_s.upcase,
|
|
135
135
|
path: sanitize_path(path),
|
|
136
136
|
operation: extract_operation(method, path),
|
|
@@ -138,6 +138,9 @@ module DeadBro
|
|
|
138
138
|
duration_ms: duration_ms,
|
|
139
139
|
start_offset_ms: start_offset_ms
|
|
140
140
|
}
|
|
141
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
142
|
+
event[:parent_id] = parent_id if parent_id
|
|
143
|
+
event
|
|
141
144
|
end
|
|
142
145
|
end
|
|
143
146
|
end
|
|
@@ -8,6 +8,10 @@ module DeadBro
|
|
|
8
8
|
EVENT_NAME = "outgoing.http"
|
|
9
9
|
THREAD_LOCAL_KEY = :dead_bro_http_events
|
|
10
10
|
MAX_TRACKED_EVENTS = 1000
|
|
11
|
+
# Set while DeadBro's Faraday middleware runs the request: the adapter's
|
|
12
|
+
# own Net::HTTP or Typhoeus call is that request, recorded once by the
|
|
13
|
+
# middleware.
|
|
14
|
+
IN_FARADAY_KEY = :dead_bro_http_in_faraday
|
|
11
15
|
|
|
12
16
|
def self.install!(client: Client.new)
|
|
13
17
|
install_net_http!(client)
|
|
@@ -50,7 +54,11 @@ module DeadBro
|
|
|
50
54
|
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
51
55
|
start_offset_ms = tracking_start ? ((wall_start - tracking_start) * 1000.0).round(2) : nil
|
|
52
56
|
|
|
53
|
-
if
|
|
57
|
+
if Thread.current[DeadBro::HttpInstrumentation::IN_FARADAY_KEY]
|
|
58
|
+
# Faraday sent it through this adapter; DeadBro's Faraday
|
|
59
|
+
# middleware around the request records it.
|
|
60
|
+
nil
|
|
61
|
+
elsif is_es_host
|
|
54
62
|
# Route to elasticsearch subscriber instead of http_outgoing
|
|
55
63
|
if Thread.current[DeadBro::ElasticsearchSubscriber::THREAD_LOCAL_KEY]
|
|
56
64
|
path = (uri && uri.path) || req.path
|
|
@@ -75,6 +83,8 @@ module DeadBro
|
|
|
75
83
|
start_offset_ms: start_offset_ms,
|
|
76
84
|
exception: error && error.class.name
|
|
77
85
|
}
|
|
86
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
87
|
+
payload[:parent_id] = parent_id if parent_id
|
|
78
88
|
if Thread.current[THREAD_LOCAL_KEY] && DeadBro::HttpInstrumentation.should_continue_tracking?
|
|
79
89
|
Thread.current[THREAD_LOCAL_KEY] << payload
|
|
80
90
|
end
|
|
@@ -106,7 +116,9 @@ module DeadBro
|
|
|
106
116
|
(respond_to?(:base_url) ? base_url : nil)
|
|
107
117
|
end
|
|
108
118
|
|
|
109
|
-
|
|
119
|
+
# Inside DeadBro's Faraday middleware, which records the request.
|
|
120
|
+
skip_instrumentation = Thread.current[DeadBro::HttpInstrumentation::IN_FARADAY_KEY] ||
|
|
121
|
+
(req_url && (req_url.include?("localhost:3100/apm/v1/metrics") || req_url.include?("deadbro.aberatii.com/apm/v1/metrics")))
|
|
110
122
|
|
|
111
123
|
unless skip_instrumentation
|
|
112
124
|
payload = {
|
|
@@ -116,6 +128,8 @@ module DeadBro
|
|
|
116
128
|
status: response && response.code,
|
|
117
129
|
duration_ms: duration_ms
|
|
118
130
|
}
|
|
131
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
132
|
+
payload[:parent_id] = parent_id if parent_id
|
|
119
133
|
if Thread.current[THREAD_LOCAL_KEY] && DeadBro::HttpInstrumentation.should_continue_tracking?
|
|
120
134
|
Thread.current[THREAD_LOCAL_KEY] << payload
|
|
121
135
|
end
|
|
@@ -137,10 +151,21 @@ module DeadBro
|
|
|
137
151
|
middleware_klass = Class.new(::Faraday::Middleware) do
|
|
138
152
|
def call(env)
|
|
139
153
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
154
|
+
wall_start = Time.now
|
|
140
155
|
response = nil
|
|
156
|
+
error = nil
|
|
157
|
+
# A request this middleware leaves out (below) is left to the
|
|
158
|
+
# adapter's own Net::HTTP or Typhoeus hook, as before.
|
|
159
|
+
records = DeadBro::HttpInstrumentation.faraday_records?(env)
|
|
160
|
+
in_faraday = Thread.current[DeadBro::HttpInstrumentation::IN_FARADAY_KEY]
|
|
161
|
+
Thread.current[DeadBro::HttpInstrumentation::IN_FARADAY_KEY] = true if records
|
|
141
162
|
begin
|
|
142
163
|
response = @app.call(env)
|
|
164
|
+
rescue => e
|
|
165
|
+
error = e
|
|
166
|
+
raise
|
|
143
167
|
ensure
|
|
168
|
+
Thread.current[DeadBro::HttpInstrumentation::IN_FARADAY_KEY] = in_faraday
|
|
144
169
|
finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
145
170
|
duration_ms = ((finish_time - start_time) * 1000.0).round(2)
|
|
146
171
|
begin
|
|
@@ -150,7 +175,9 @@ module DeadBro
|
|
|
150
175
|
url_str = url.to_s
|
|
151
176
|
|
|
152
177
|
is_es_host = DeadBro::HttpInstrumentation.elasticsearch_host?(host, port)
|
|
153
|
-
|
|
178
|
+
|
|
179
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
180
|
+
start_offset_ms = tracking_start ? ((wall_start - tracking_start) * 1000.0).round(2) : nil
|
|
154
181
|
|
|
155
182
|
if is_es_host
|
|
156
183
|
if Thread.current[DeadBro::ElasticsearchSubscriber::THREAD_LOCAL_KEY]
|
|
@@ -158,10 +185,11 @@ module DeadBro
|
|
|
158
185
|
method: env.method.to_s.upcase,
|
|
159
186
|
path: url.path,
|
|
160
187
|
status: response&.status,
|
|
161
|
-
duration_ms: duration_ms
|
|
188
|
+
duration_ms: duration_ms,
|
|
189
|
+
start_offset_ms: start_offset_ms
|
|
162
190
|
)
|
|
163
191
|
end
|
|
164
|
-
elsif
|
|
192
|
+
elsif records
|
|
165
193
|
lib = DeadBro::HttpInstrumentation.typesense_host?(host, port) ? "typesense" : "faraday"
|
|
166
194
|
payload = {
|
|
167
195
|
library: lib,
|
|
@@ -170,8 +198,12 @@ module DeadBro
|
|
|
170
198
|
host: host,
|
|
171
199
|
path: url.path,
|
|
172
200
|
status: response&.status,
|
|
173
|
-
duration_ms: duration_ms
|
|
201
|
+
duration_ms: duration_ms,
|
|
202
|
+
start_offset_ms: start_offset_ms,
|
|
203
|
+
exception: error&.class&.name
|
|
174
204
|
}
|
|
205
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
206
|
+
payload[:parent_id] = parent_id if parent_id
|
|
175
207
|
key = DeadBro::HttpInstrumentation::THREAD_LOCAL_KEY
|
|
176
208
|
if Thread.current[key] && DeadBro::HttpInstrumentation.should_continue_tracking?
|
|
177
209
|
Thread.current[key] << payload
|
|
@@ -202,6 +234,16 @@ module DeadBro
|
|
|
202
234
|
rescue
|
|
203
235
|
end
|
|
204
236
|
|
|
237
|
+
# Whether DeadBro's Faraday middleware records the request: everything but
|
|
238
|
+
# localhost and DeadBro's own endpoint, Elasticsearch hosts always.
|
|
239
|
+
def self.faraday_records?(env)
|
|
240
|
+
url = env.url
|
|
241
|
+
url_str = url.to_s
|
|
242
|
+
elasticsearch_host?(url.host.to_s, url.port) || !(url_str.include?("localhost") || url_str.include?("aberatii.com"))
|
|
243
|
+
rescue *DeadBro::CONTAINED_ERRORS
|
|
244
|
+
false
|
|
245
|
+
end
|
|
246
|
+
|
|
205
247
|
def self.elasticsearch_host?(host, port)
|
|
206
248
|
return false if host.nil?
|
|
207
249
|
return true if port == 9200
|
|
@@ -141,6 +141,8 @@ module DeadBro
|
|
|
141
141
|
gc_pressure: gc_pressure,
|
|
142
142
|
ar_instantiation_count: ar_instantiation_count,
|
|
143
143
|
status: has_error ? "failed" : "completed",
|
|
144
|
+
sql_count: DeadBro::Subscriber.sql_count(sql_queries),
|
|
145
|
+
sql_cached_count: DeadBro::Subscriber.sql_cached_count(sql_queries),
|
|
144
146
|
sql_queries: sql_queries,
|
|
145
147
|
transaction_events: transaction_events,
|
|
146
148
|
rails_env: DeadBro.env,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeadBro
|
|
4
|
+
# Times one Rack middleware — or the router the stack ends in — as a span of
|
|
5
|
+
# the request's tree (see SpanTracker). Rails wraps every middleware in one
|
|
6
|
+
# when it builds the app's middleware stack; the probe reads a clock on the
|
|
7
|
+
# way in and out and allocates nothing.
|
|
8
|
+
class MiddlewareProbe
|
|
9
|
+
attr_reader :span_name, :span_kind
|
|
10
|
+
|
|
11
|
+
def initialize(app, name, kind)
|
|
12
|
+
@app = app
|
|
13
|
+
@span_name = name
|
|
14
|
+
@span_kind = kind
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(env)
|
|
18
|
+
frame = SpanTracker.rack_enter(self)
|
|
19
|
+
@app.call(env)
|
|
20
|
+
ensure
|
|
21
|
+
SpanTracker.rack_leave(frame) if frame
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Anything else asked of the middleware goes to it unchanged.
|
|
25
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
26
|
+
@app.respond_to?(method_name, include_private) || super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def method_missing(method_name, *args, &block)
|
|
30
|
+
if @app.respond_to?(method_name)
|
|
31
|
+
@app.public_send(method_name, *args, &block)
|
|
32
|
+
else
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
|
37
|
+
|
|
38
|
+
# ActionDispatch::MiddlewareStack::Middleware#build: one probe per middleware.
|
|
39
|
+
module MiddlewareBuild
|
|
40
|
+
def build(app)
|
|
41
|
+
return super unless MiddlewareProbe.enabled?
|
|
42
|
+
|
|
43
|
+
MiddlewareProbe.wrap(super, inspect, SpanTracker::MIDDLEWARE)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# ActionDispatch::MiddlewareStack#build: a probe around the endpoint (the
|
|
48
|
+
# app's routes), so routing and controller setup aren't counted as the
|
|
49
|
+
# innermost middleware's own time.
|
|
50
|
+
module StackBuild
|
|
51
|
+
def build(app = nil, &block)
|
|
52
|
+
endpoint = app || block
|
|
53
|
+
return super if endpoint.nil? || !MiddlewareProbe.enabled?
|
|
54
|
+
|
|
55
|
+
super(MiddlewareProbe.wrap(endpoint, endpoint.class.name, SpanTracker::ENDPOINT))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# config.middleware_spans_enabled, read when Rails builds the stack.
|
|
60
|
+
def self.enabled?
|
|
61
|
+
DeadBro.configuration.middleware_spans_enabled ? true : false
|
|
62
|
+
rescue
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.wrap(app, name, kind)
|
|
67
|
+
new(app, name.to_s.dup.freeze, kind)
|
|
68
|
+
rescue
|
|
69
|
+
app
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Must run before Rails builds the middleware stack (the
|
|
73
|
+
# build_middleware_stack initializer); a stack built earlier has no probes.
|
|
74
|
+
def self.install!
|
|
75
|
+
return false unless defined?(::ActionDispatch::MiddlewareStack)
|
|
76
|
+
|
|
77
|
+
stack = ::ActionDispatch::MiddlewareStack
|
|
78
|
+
stack.prepend(StackBuild) unless stack.ancestors.include?(StackBuild)
|
|
79
|
+
stack::Middleware.prepend(MiddlewareBuild) unless stack::Middleware.ancestors.include?(MiddlewareBuild)
|
|
80
|
+
true
|
|
81
|
+
rescue
|
|
82
|
+
false
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/dead_bro/railtie.rb
CHANGED
|
@@ -53,6 +53,11 @@ if defined?(Rails) && defined?(Rails::Railtie)
|
|
|
53
53
|
require "dead_bro/view_rendering_subscriber"
|
|
54
54
|
DeadBro::ViewRenderingSubscriber.subscribe!(client: shared_client)
|
|
55
55
|
|
|
56
|
+
# Install the span tree (controller action and view spans; the
|
|
57
|
+
# middleware spans come from the probes installed below)
|
|
58
|
+
require "dead_bro/span_tracker"
|
|
59
|
+
DeadBro::SpanTracker.subscribe!
|
|
60
|
+
|
|
56
61
|
# Install lightweight memory tracking (default)
|
|
57
62
|
require "dead_bro/lightweight_memory_tracker"
|
|
58
63
|
require "dead_bro/memory_leak_detector"
|
|
@@ -104,6 +109,17 @@ if defined?(Rails) && defined?(Rails::Railtie)
|
|
|
104
109
|
# Never raise in Railtie init
|
|
105
110
|
end
|
|
106
111
|
|
|
112
|
+
# Time every middleware and the router for the span tree. Railtie
|
|
113
|
+
# initializers run before the application's build_middleware_stack, so
|
|
114
|
+
# the probes are in place when Rails builds the stack.
|
|
115
|
+
initializer "dead_bro.middleware_probes" do
|
|
116
|
+
require "dead_bro/span_tracker"
|
|
117
|
+
require "dead_bro/middleware_probe"
|
|
118
|
+
DeadBro::MiddlewareProbe.install!
|
|
119
|
+
rescue
|
|
120
|
+
# Never raise in Railtie init
|
|
121
|
+
end
|
|
122
|
+
|
|
107
123
|
# Insert SQL tracking middleware
|
|
108
124
|
initializer "dead_bro.sql_tracking_middleware" do |app|
|
|
109
125
|
require "dead_bro/sql_tracking_middleware"
|
|
@@ -90,6 +90,8 @@ module DeadBro
|
|
|
90
90
|
db: RedisSubscriber.safe_db(@db),
|
|
91
91
|
error: error ? error.class.name : nil
|
|
92
92
|
}
|
|
93
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
94
|
+
event[:parent_id] = parent_id if parent_id
|
|
93
95
|
|
|
94
96
|
if Thread.current[RedisSubscriber::THREAD_LOCAL_KEY] && RedisSubscriber.should_continue_tracking?
|
|
95
97
|
Thread.current[RedisSubscriber::THREAD_LOCAL_KEY] << event
|
|
@@ -122,6 +124,8 @@ module DeadBro
|
|
|
122
124
|
start_offset_ms: start_offset_ms,
|
|
123
125
|
db: RedisSubscriber.safe_db(@db)
|
|
124
126
|
}
|
|
127
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
128
|
+
event[:parent_id] = parent_id if parent_id
|
|
125
129
|
|
|
126
130
|
if Thread.current[RedisSubscriber::THREAD_LOCAL_KEY] && RedisSubscriber.should_continue_tracking?
|
|
127
131
|
Thread.current[RedisSubscriber::THREAD_LOCAL_KEY] << event
|
|
@@ -154,6 +158,8 @@ module DeadBro
|
|
|
154
158
|
start_offset_ms: start_offset_ms,
|
|
155
159
|
db: RedisSubscriber.safe_db(@db)
|
|
156
160
|
}
|
|
161
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
162
|
+
event[:parent_id] = parent_id if parent_id
|
|
157
163
|
|
|
158
164
|
if Thread.current[RedisSubscriber::THREAD_LOCAL_KEY] && RedisSubscriber.should_continue_tracking?
|
|
159
165
|
Thread.current[RedisSubscriber::THREAD_LOCAL_KEY] << event
|
|
@@ -220,7 +226,7 @@ module DeadBro
|
|
|
220
226
|
|
|
221
227
|
def self.build_event(name, data, duration_ms, start_offset_ms = nil)
|
|
222
228
|
cmd = extract_command(data)
|
|
223
|
-
{
|
|
229
|
+
event = {
|
|
224
230
|
event: name.to_s,
|
|
225
231
|
command: cmd[:command],
|
|
226
232
|
key: cmd[:key],
|
|
@@ -229,6 +235,9 @@ module DeadBro
|
|
|
229
235
|
start_offset_ms: start_offset_ms,
|
|
230
236
|
db: safe_db(data[:db])
|
|
231
237
|
}
|
|
238
|
+
parent_id = DeadBro::SpanTracker.current_id
|
|
239
|
+
event[:parent_id] = parent_id if parent_id
|
|
240
|
+
event
|
|
232
241
|
rescue
|
|
233
242
|
nil
|
|
234
243
|
end
|