dead_bro 0.2.29 → 0.2.30
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 +5 -0
- data/lib/dead_bro/configuration.rb +8 -3
- data/lib/dead_bro/job_sql_tracking_middleware.rb +2 -0
- data/lib/dead_bro/job_subscriber.rb +7 -0
- data/lib/dead_bro/sql_subscriber.rb +15 -0
- data/lib/dead_bro/sql_tracking_middleware.rb +3 -0
- data/lib/dead_bro/subscriber.rb +4 -0
- data/lib/dead_bro/version.rb +1 -1
- data/lib/dead_bro/watch_tracker.rb +189 -0
- data/lib/dead_bro.rb +25 -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: cccae4ac857f38e889e12c292943b25fad11f7e372d7f13e4eb838d13c6330ee
|
|
4
|
+
data.tar.gz: 4d459c4d049219747ed5c1b6f10e1417648282eca2e3e81c7eeef132e8a347bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d12c33c0902c95f318cd716d7c88c78925543b235729ee90525d0453b16ed46521e2b00cc1daa5131b673abb7ee7e74e0968f299d56da055fd36452c2e314b90
|
|
7
|
+
data.tar.gz: e04ca78806e4d9a07757c0fedc177c2e4b77b295437d415fab94546d84add717ceab84791e34991f4955197ab1d8036ad498d679b089e795e5356beb0662d286
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
### Added
|
|
4
4
|
- Monitor thread now sends a synchronous heartbeat on startup before the first collection tick. This ensures remote settings — including `monitor_enabled` — are applied from the very first reporting cycle, so Sidekiq workers and other non-web processes that have not yet sent any metrics still receive the correct configuration immediately on boot rather than waiting up to 60 seconds for the first scheduled tick.
|
|
5
5
|
|
|
6
|
+
## [0.2.30] - 2026-06-19
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **`DeadBro.watch(label, **tags) { ... }`**: opt-in scoped timing for arbitrary code during instrumented web requests and background jobs. Records elapsed time, `start_offset_ms`, nesting depth, SQL count/duration attributable to the block, optional tags, and exception metadata (then re-raises). Spans are sent as `watch_events` in the normal APM payload for the Request Trace waterfall. Disabled by default; enable locally via `DeadBro.configure { |c| c.watch_enabled = true }` or remotely via the `watch_enabled` setting. Guardrails: max depth 10, max 50 spans per request, label length 200 chars, max 5 tags per span with tag values truncated to 100 chars. **Nested SQL:** each span diffs the request-wide SQL counter at enter/exit; parent spans include SQL from nested child spans (inclusive totals — use inner spans for per-block SQL, do not sum parent + child counts).
|
|
10
|
+
|
|
6
11
|
## [0.2.25] - 2026-06-14
|
|
7
12
|
|
|
8
13
|
### Added
|
|
@@ -17,7 +17,7 @@ module DeadBro
|
|
|
17
17
|
|
|
18
18
|
# Remote-managed settings (overwritten by backend JSON `settings` on successful API responses)
|
|
19
19
|
attr_accessor :memory_tracking_enabled, :allocation_tracking_enabled, :allocation_sample_rate,
|
|
20
|
-
:sample_rate, :slow_query_threshold_ms,
|
|
20
|
+
:sample_rate, :slow_query_threshold_ms, :watch_enabled,
|
|
21
21
|
:monitor_enabled, :enable_db_stats, :enable_process_stats, :enable_system_stats,
|
|
22
22
|
:max_sql_queries_to_send, :max_logs_to_send
|
|
23
23
|
|
|
@@ -67,7 +67,7 @@ module DeadBro
|
|
|
67
67
|
REMOTE_SETTING_KEYS = %w[
|
|
68
68
|
enabled sample_rate memory_tracking_enabled allocation_tracking_enabled allocation_sample_rate
|
|
69
69
|
explain_analyze_enabled slow_query_threshold_ms max_sql_queries_to_send max_logs_to_send
|
|
70
|
-
excluded_controllers excluded_jobs exclusive_controllers exclusive_jobs
|
|
70
|
+
watch_enabled excluded_controllers excluded_jobs exclusive_controllers exclusive_jobs
|
|
71
71
|
monitor_enabled enable_db_stats enable_process_stats enable_system_stats
|
|
72
72
|
sample_rates_by_type
|
|
73
73
|
].freeze
|
|
@@ -102,6 +102,7 @@ module DeadBro
|
|
|
102
102
|
@slow_query_threshold_ms = 500
|
|
103
103
|
@max_sql_queries_to_send = 500
|
|
104
104
|
@max_logs_to_send = 100
|
|
105
|
+
@watch_enabled = false
|
|
105
106
|
self.excluded_controllers = []
|
|
106
107
|
self.excluded_jobs = []
|
|
107
108
|
self.exclusive_controllers = []
|
|
@@ -178,7 +179,7 @@ module DeadBro
|
|
|
178
179
|
# must never be able to switch it on — only off. The local opt-in
|
|
179
180
|
# (explain_analyze_enabled) stays untouched; see #explain_analyze_active?
|
|
180
181
|
@remote_explain_analyze_enabled = !!value
|
|
181
|
-
when "enabled", "memory_tracking_enabled", "allocation_tracking_enabled",
|
|
182
|
+
when "enabled", "memory_tracking_enabled", "allocation_tracking_enabled", "watch_enabled",
|
|
182
183
|
"monitor_enabled", "enable_db_stats", "enable_process_stats", "enable_system_stats"
|
|
183
184
|
send(:"#{k}=", !!value)
|
|
184
185
|
when "excluded_controllers", "excluded_jobs", "exclusive_controllers", "exclusive_jobs"
|
|
@@ -208,6 +209,10 @@ module DeadBro
|
|
|
208
209
|
Time.now.utc < t
|
|
209
210
|
end
|
|
210
211
|
|
|
212
|
+
def watch_enabled?
|
|
213
|
+
!!@watch_enabled
|
|
214
|
+
end
|
|
215
|
+
|
|
211
216
|
def resolve_deploy_id
|
|
212
217
|
explicit = @explicit_deploy_revision&.to_s&.strip
|
|
213
218
|
return explicit unless explicit.nil? || explicit.empty?
|
|
@@ -40,6 +40,8 @@ module DeadBro
|
|
|
40
40
|
if defined?(DeadBro::DbConnectionSubscriber)
|
|
41
41
|
DeadBro::DbConnectionSubscriber.start_request_tracking
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
DeadBro::WatchTracker.start_request_tracking if defined?(DeadBro::WatchTracker)
|
|
43
45
|
end
|
|
44
46
|
rescue
|
|
45
47
|
# Never raise from instrumentation install
|
|
@@ -61,6 +61,7 @@ module DeadBro
|
|
|
61
61
|
DeadBro::SqlSubscriber.start_request_tracking
|
|
62
62
|
start_job_dependency_tracking
|
|
63
63
|
DeadBro::DbConnectionSubscriber.start_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
|
|
64
|
+
DeadBro::WatchTracker.start_request_tracking if defined?(DeadBro::WatchTracker)
|
|
64
65
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
65
66
|
DeadBro::MemoryTrackingSubscriber.start_request_tracking
|
|
66
67
|
else
|
|
@@ -74,6 +75,7 @@ module DeadBro
|
|
|
74
75
|
db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
|
|
75
76
|
gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
|
|
76
77
|
ar_instantiation_count = defined?(DeadBro::ArObjectTracker) ? DeadBro::ArObjectTracker.stop_request_tracking : nil
|
|
78
|
+
watch_events = defined?(DeadBro::WatchTracker) ? DeadBro::WatchTracker.stop_request_tracking : []
|
|
77
79
|
|
|
78
80
|
# Stop memory tracking and get collected memory data
|
|
79
81
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
@@ -124,6 +126,7 @@ module DeadBro
|
|
|
124
126
|
gc_stats: gc_stats,
|
|
125
127
|
memory_events: memory_events,
|
|
126
128
|
memory_performance: memory_performance,
|
|
129
|
+
watch_events: watch_events,
|
|
127
130
|
logs: DeadBro.logger.logs
|
|
128
131
|
}.merge(dependency_events)
|
|
129
132
|
|
|
@@ -162,6 +165,7 @@ module DeadBro
|
|
|
162
165
|
DeadBro::SqlSubscriber.start_request_tracking
|
|
163
166
|
start_job_dependency_tracking
|
|
164
167
|
DeadBro::DbConnectionSubscriber.start_request_tracking if defined?(DeadBro::DbConnectionSubscriber)
|
|
168
|
+
DeadBro::WatchTracker.start_request_tracking if defined?(DeadBro::WatchTracker)
|
|
165
169
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
166
170
|
DeadBro::MemoryTrackingSubscriber.start_request_tracking
|
|
167
171
|
else
|
|
@@ -175,6 +179,7 @@ module DeadBro
|
|
|
175
179
|
db_connection_stats = defined?(DeadBro::DbConnectionSubscriber) ? DeadBro::DbConnectionSubscriber.stop_request_tracking : {}
|
|
176
180
|
gc_pressure = defined?(DeadBro::GcTracker) ? DeadBro::GcTracker.stop_request_tracking : {}
|
|
177
181
|
ar_instantiation_count = defined?(DeadBro::ArObjectTracker) ? DeadBro::ArObjectTracker.stop_request_tracking : nil
|
|
182
|
+
watch_events = defined?(DeadBro::WatchTracker) ? DeadBro::WatchTracker.stop_request_tracking : []
|
|
178
183
|
|
|
179
184
|
# Stop memory tracking and get collected memory data
|
|
180
185
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
@@ -228,6 +233,7 @@ module DeadBro
|
|
|
228
233
|
gc_stats: gc_stats,
|
|
229
234
|
memory_events: memory_events,
|
|
230
235
|
memory_performance: memory_performance,
|
|
236
|
+
watch_events: watch_events,
|
|
231
237
|
logs: DeadBro.logger.logs
|
|
232
238
|
}.merge(dependency_events)
|
|
233
239
|
|
|
@@ -255,6 +261,7 @@ module DeadBro
|
|
|
255
261
|
if DeadBro.configuration.allocation_tracking_enabled && defined?(DeadBro::MemoryTrackingSubscriber)
|
|
256
262
|
DeadBro::MemoryTrackingSubscriber.stop_request_tracking
|
|
257
263
|
end
|
|
264
|
+
DeadBro::WatchTracker.stop_request_tracking if defined?(DeadBro::WatchTracker)
|
|
258
265
|
rescue
|
|
259
266
|
# Best effort
|
|
260
267
|
end
|
|
@@ -50,6 +50,21 @@ module DeadBro
|
|
|
50
50
|
stack.last
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Sum of SQL counts and durations recorded so far in the active tracking
|
|
54
|
+
# context. Used by WatchTracker to attribute SQL to a DeadBro.watch block.
|
|
55
|
+
def self.current_sql_metrics
|
|
56
|
+
agg_stack = Thread.current[THREAD_LOCAL_AGGREGATES_KEY]
|
|
57
|
+
aggregates_h = (agg_stack.is_a?(Array) && agg_stack.any?) ? agg_stack.last : {}
|
|
58
|
+
return {count: 0, duration_ms: 0.0} unless aggregates_h.is_a?(Hash) && aggregates_h.any?
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
count: aggregates_h.values.sum { |a| (a[:count] || 0).to_i },
|
|
62
|
+
duration_ms: aggregates_h.values.sum { |a| (a[:total_duration_ms] || 0.0).to_f }.round(2)
|
|
63
|
+
}
|
|
64
|
+
rescue StandardError
|
|
65
|
+
{count: 0, duration_ms: 0.0}
|
|
66
|
+
end
|
|
67
|
+
|
|
53
68
|
# Check if we should continue tracking based on count and time limits
|
|
54
69
|
def self.should_continue_tracking?(current_queries_array, max_count)
|
|
55
70
|
return false unless current_queries_array.is_a?(Array)
|
|
@@ -82,6 +82,9 @@ module DeadBro
|
|
|
82
82
|
# Start CPU time tracking for this request (thread-local clock)
|
|
83
83
|
DeadBro::CpuTracker.start_request_tracking if defined?(DeadBro::CpuTracker)
|
|
84
84
|
|
|
85
|
+
# Start user-defined watch block tracking for this request
|
|
86
|
+
DeadBro::WatchTracker.start_request_tracking if defined?(DeadBro::WatchTracker)
|
|
87
|
+
|
|
85
88
|
# Start outgoing HTTP accumulation for this request
|
|
86
89
|
Thread.current[:dead_bro_http_events] = []
|
|
87
90
|
|
data/lib/dead_bro/subscriber.rb
CHANGED
|
@@ -80,6 +80,8 @@ module DeadBro
|
|
|
80
80
|
view_events = DeadBro::ViewRenderingSubscriber.stop_request_tracking
|
|
81
81
|
view_performance = DeadBro::ViewRenderingSubscriber.analyze_view_performance(view_events)
|
|
82
82
|
|
|
83
|
+
watch_events = defined?(DeadBro::WatchTracker) ? DeadBro::WatchTracker.stop_request_tracking : []
|
|
84
|
+
|
|
83
85
|
# Per-phase allocation attribution (under memory tracking) — which phase
|
|
84
86
|
# allocated the request's objects (sql / view / elasticsearch).
|
|
85
87
|
allocation_phases = if DeadBro.configuration.memory_tracking_enabled && defined?(DeadBro::MemoryPhaseTracker)
|
|
@@ -222,6 +224,7 @@ module DeadBro
|
|
|
222
224
|
gc_pressure: gc_pressure,
|
|
223
225
|
ar_instantiation_count: ar_instantiation_count,
|
|
224
226
|
cpu_time_ms: cpu_time_ms,
|
|
227
|
+
watch_events: watch_events,
|
|
225
228
|
logs: DeadBro.logger.logs
|
|
226
229
|
}
|
|
227
230
|
# force: true — the sampling decision (global or per-request-type) was
|
|
@@ -254,6 +257,7 @@ module DeadBro
|
|
|
254
257
|
DeadBro::GcTracker.stop_request_tracking if defined?(DeadBro::GcTracker)
|
|
255
258
|
DeadBro::ArObjectTracker.stop_request_tracking if defined?(DeadBro::ArObjectTracker)
|
|
256
259
|
DeadBro::CpuTracker.stop_request_tracking if defined?(DeadBro::CpuTracker)
|
|
260
|
+
DeadBro::WatchTracker.stop_request_tracking if defined?(DeadBro::WatchTracker)
|
|
257
261
|
rescue
|
|
258
262
|
# Best effort — draining must never raise from the notifications callback.
|
|
259
263
|
end
|
data/lib/dead_bro/version.rb
CHANGED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeadBro
|
|
4
|
+
# Records user-defined timed blocks (DeadBro.watch) during an instrumented
|
|
5
|
+
# request or background job. Spans include wall duration, nesting depth, and
|
|
6
|
+
# SQL attributable to the block (delta from SqlSubscriber aggregates).
|
|
7
|
+
#
|
|
8
|
+
# SQL attribution: each span diffs the global SQL counter at block entry vs
|
|
9
|
+
# exit. Nested blocks each get their own diff; a parent span's sql_count /
|
|
10
|
+
# sql_duration_ms therefore includes queries run inside nested child spans
|
|
11
|
+
# (inclusive totals, not exclusive). Compare inner spans when you need
|
|
12
|
+
# per-block SQL; do not sum parent and child counts.
|
|
13
|
+
#
|
|
14
|
+
# Unlike DeadBro.analyze, watch spans are sent to the DeadBro backend as part
|
|
15
|
+
# of the normal request/job payload when watch_enabled is on.
|
|
16
|
+
module WatchTracker
|
|
17
|
+
THREAD_LOCAL_EVENTS_KEY = :dead_bro_watch_events
|
|
18
|
+
THREAD_LOCAL_ACTIVE_STACK_KEY = :dead_bro_watch_active_stack
|
|
19
|
+
|
|
20
|
+
# Deepest depth index still recorded — depths 0..MAX_DEPTH are kept, anything
|
|
21
|
+
# nested below that yields untracked.
|
|
22
|
+
MAX_DEPTH = 10
|
|
23
|
+
MAX_SPANS_PER_REQUEST = 50
|
|
24
|
+
MAX_LABEL_LENGTH = 200
|
|
25
|
+
MAX_TAGS = 5
|
|
26
|
+
MAX_TAG_VALUE_LENGTH = 100
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def enabled?
|
|
31
|
+
DeadBro.configuration.watch_enabled?
|
|
32
|
+
rescue StandardError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def tracking_active?
|
|
37
|
+
Thread.current[THREAD_LOCAL_EVENTS_KEY].is_a?(Array)
|
|
38
|
+
rescue StandardError
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def start_request_tracking
|
|
43
|
+
Thread.current[THREAD_LOCAL_EVENTS_KEY] = []
|
|
44
|
+
Thread.current[THREAD_LOCAL_ACTIVE_STACK_KEY] = []
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def stop_request_tracking
|
|
48
|
+
events = Thread.current[THREAD_LOCAL_EVENTS_KEY]
|
|
49
|
+
Thread.current[THREAD_LOCAL_EVENTS_KEY] = nil
|
|
50
|
+
Thread.current[THREAD_LOCAL_ACTIVE_STACK_KEY] = nil
|
|
51
|
+
# compact drops reserved slots whose block never completed — can't happen
|
|
52
|
+
# for a lexical block, but keeps a nil out of the payload if it ever does.
|
|
53
|
+
events.is_a?(Array) ? events.compact : []
|
|
54
|
+
rescue StandardError
|
|
55
|
+
[]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def watch(label = nil, **tags)
|
|
59
|
+
return yield unless block_given?
|
|
60
|
+
return yield unless enabled?
|
|
61
|
+
return yield unless tracking_active?
|
|
62
|
+
|
|
63
|
+
events = Thread.current[THREAD_LOCAL_EVENTS_KEY]
|
|
64
|
+
active_stack = Thread.current[THREAD_LOCAL_ACTIVE_STACK_KEY]
|
|
65
|
+
return yield unless events.is_a?(Array) && active_stack.is_a?(Array)
|
|
66
|
+
|
|
67
|
+
depth = active_stack.length
|
|
68
|
+
return yield if depth > MAX_DEPTH
|
|
69
|
+
return yield if events.length >= MAX_SPANS_PER_REQUEST
|
|
70
|
+
|
|
71
|
+
sanitized_label = sanitize_label(label)
|
|
72
|
+
sanitized_tags = sanitize_tags(tags)
|
|
73
|
+
start_offset_ms = compute_start_offset_ms
|
|
74
|
+
sql_before = sql_metrics_snapshot
|
|
75
|
+
block_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
76
|
+
|
|
77
|
+
frame = {
|
|
78
|
+
label: sanitized_label,
|
|
79
|
+
depth: depth,
|
|
80
|
+
tags: sanitized_tags,
|
|
81
|
+
start_offset_ms: start_offset_ms,
|
|
82
|
+
sql_before: sql_before,
|
|
83
|
+
block_start: block_start,
|
|
84
|
+
error: false,
|
|
85
|
+
exception_class: nil
|
|
86
|
+
}
|
|
87
|
+
# Reserve the slot on entry so spans come out in start order. Blocks only
|
|
88
|
+
# complete inner-first, so appending on completion would emit a nested span
|
|
89
|
+
# ahead of its own parent; the placeholder keeps parents before children.
|
|
90
|
+
# It also makes MAX_SPANS_PER_REQUEST count blocks still in flight.
|
|
91
|
+
frame[:slot] = events.length
|
|
92
|
+
events << nil
|
|
93
|
+
active_stack << frame
|
|
94
|
+
|
|
95
|
+
begin
|
|
96
|
+
yield
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
frame[:error] = true
|
|
99
|
+
# Anonymous classes (Class.new(StandardError)) have a nil name — leave the
|
|
100
|
+
# field unset rather than shipping an empty string the dashboard would
|
|
101
|
+
# render as a blank exception.
|
|
102
|
+
class_name = e.class.name
|
|
103
|
+
frame[:exception_class] = class_name[0, 200] if class_name
|
|
104
|
+
raise
|
|
105
|
+
ensure
|
|
106
|
+
active_stack.pop if active_stack.last.equal?(frame)
|
|
107
|
+
append_completed_span(events, frame)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def sanitize_label(label)
|
|
112
|
+
text = label.to_s.strip
|
|
113
|
+
text = "block" if text.empty?
|
|
114
|
+
(text.length > MAX_LABEL_LENGTH) ? text[0, MAX_LABEL_LENGTH] + "..." : text
|
|
115
|
+
rescue StandardError
|
|
116
|
+
"block"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def sanitize_tags(tags)
|
|
120
|
+
return {} unless tags.is_a?(Hash) && tags.any?
|
|
121
|
+
|
|
122
|
+
tags.first(MAX_TAGS).each_with_object({}) do |(key, value), out|
|
|
123
|
+
k = key.to_s.strip[0, 50]
|
|
124
|
+
next if k.empty?
|
|
125
|
+
|
|
126
|
+
v = value.to_s.strip
|
|
127
|
+
v = v[0, MAX_TAG_VALUE_LENGTH] + "..." if v.length > MAX_TAG_VALUE_LENGTH
|
|
128
|
+
out[k] = v
|
|
129
|
+
end
|
|
130
|
+
rescue StandardError
|
|
131
|
+
{}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def compute_start_offset_ms
|
|
135
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
136
|
+
return 0.0 unless tracking_start
|
|
137
|
+
|
|
138
|
+
((Time.now - tracking_start) * 1000.0).round(2)
|
|
139
|
+
rescue StandardError
|
|
140
|
+
0.0
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def sql_metrics_snapshot
|
|
144
|
+
return {count: 0, duration_ms: 0.0} unless defined?(DeadBro::SqlSubscriber)
|
|
145
|
+
|
|
146
|
+
DeadBro::SqlSubscriber.current_sql_metrics
|
|
147
|
+
rescue StandardError
|
|
148
|
+
{count: 0, duration_ms: 0.0}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def append_completed_span(events, frame)
|
|
152
|
+
return unless events.is_a?(Array)
|
|
153
|
+
|
|
154
|
+
slot = frame[:slot]
|
|
155
|
+
return unless slot.is_a?(Integer) && slot < events.length
|
|
156
|
+
|
|
157
|
+
elapsed_ms = begin
|
|
158
|
+
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - frame[:block_start]) * 1000.0).round(2)
|
|
159
|
+
rescue StandardError
|
|
160
|
+
0.0
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
sql_after = sql_metrics_snapshot
|
|
164
|
+
sql_before = frame[:sql_before] || {count: 0, duration_ms: 0.0}
|
|
165
|
+
sql_count = [(sql_after[:count] || 0).to_i - (sql_before[:count] || 0).to_i, 0].max
|
|
166
|
+
sql_duration_ms = [
|
|
167
|
+
(sql_after[:duration_ms] || 0.0).to_f - (sql_before[:duration_ms] || 0.0).to_f,
|
|
168
|
+
0.0
|
|
169
|
+
].max.round(2)
|
|
170
|
+
|
|
171
|
+
span = {
|
|
172
|
+
label: frame[:label],
|
|
173
|
+
duration_ms: elapsed_ms,
|
|
174
|
+
start_offset_ms: frame[:start_offset_ms],
|
|
175
|
+
depth: frame[:depth],
|
|
176
|
+
sql_count: sql_count,
|
|
177
|
+
sql_duration_ms: sql_duration_ms,
|
|
178
|
+
error: frame[:error] == true
|
|
179
|
+
}
|
|
180
|
+
span[:exception_class] = frame[:exception_class] if frame[:exception_class]
|
|
181
|
+
tags = frame[:tags]
|
|
182
|
+
span[:tags] = tags if tags.is_a?(Hash) && tags.any?
|
|
183
|
+
|
|
184
|
+
events[slot] = span
|
|
185
|
+
rescue StandardError
|
|
186
|
+
# Never raise from instrumentation
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
data/lib/dead_bro.rb
CHANGED
|
@@ -29,6 +29,7 @@ module DeadBro
|
|
|
29
29
|
autoload :Monitor, "dead_bro/monitor"
|
|
30
30
|
autoload :MemoryDetails, "dead_bro/memory_details"
|
|
31
31
|
autoload :Logger, "dead_bro/logger"
|
|
32
|
+
autoload :WatchTracker, "dead_bro/watch_tracker"
|
|
32
33
|
begin
|
|
33
34
|
require "dead_bro/railtie"
|
|
34
35
|
rescue LoadError
|
|
@@ -458,4 +459,28 @@ module DeadBro
|
|
|
458
459
|
raise error if error
|
|
459
460
|
analysis_result
|
|
460
461
|
end
|
|
462
|
+
|
|
463
|
+
# Time a named block during an instrumented request or background job.
|
|
464
|
+
#
|
|
465
|
+
# When watch_enabled is on and tracking is active (web request or job),
|
|
466
|
+
# records elapsed time, optional tags, nested SQL count/duration, and errors
|
|
467
|
+
# as watch_events in the APM payload for the Request Trace waterfall.
|
|
468
|
+
#
|
|
469
|
+
# Nested watch blocks: each span's sql_count/sql_duration_ms is the SQL that
|
|
470
|
+
# ran while that block was active. Parent spans include SQL from nested child
|
|
471
|
+
# spans (inclusive). Inner spans show their own slice; do not add parent and
|
|
472
|
+
# child SQL counts together.
|
|
473
|
+
#
|
|
474
|
+
# When disabled or outside an instrumented context, yields with no overhead
|
|
475
|
+
# beyond a single config check.
|
|
476
|
+
#
|
|
477
|
+
# Usage:
|
|
478
|
+
# DeadBro.watch("load users") { User.where(active: true).load }
|
|
479
|
+
# DeadBro.watch("sync billing", customer_id: user.id) { sync!(user) }
|
|
480
|
+
#
|
|
481
|
+
# Compare to DeadBro.analyze — analyze is local-only console profiling;
|
|
482
|
+
# watch spans are sent to the DeadBro dashboard when enabled.
|
|
483
|
+
def self.watch(label = nil, **tags, &block)
|
|
484
|
+
WatchTracker.watch(label, **tags, &block)
|
|
485
|
+
end
|
|
461
486
|
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.
|
|
4
|
+
version: 0.2.30
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emanuel Comsa
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- lib/dead_bro/subscriber.rb
|
|
59
59
|
- lib/dead_bro/version.rb
|
|
60
60
|
- lib/dead_bro/view_rendering_subscriber.rb
|
|
61
|
+
- lib/dead_bro/watch_tracker.rb
|
|
61
62
|
- lib/generators/dead_bro/install/install_generator.rb
|
|
62
63
|
- lib/generators/dead_bro/install/templates/dead_bro.rb
|
|
63
64
|
homepage: https://www.deadbro.com
|