dead_bro 0.2.8 → 0.2.10
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/README.md +105 -43
- data/lib/dead_bro/circuit_breaker.rb +58 -38
- data/lib/dead_bro/client.rb +112 -143
- data/lib/dead_bro/configuration.rb +76 -40
- data/lib/dead_bro/dispatcher.rb +130 -0
- data/lib/dead_bro/elasticsearch_subscriber.rb +141 -0
- data/lib/dead_bro/error_middleware.rb +1 -1
- data/lib/dead_bro/http_instrumentation.rb +108 -15
- data/lib/dead_bro/job_subscriber.rb +35 -12
- data/lib/dead_bro/lightweight_memory_tracker.rb +5 -7
- data/lib/dead_bro/logger.rb +30 -11
- data/lib/dead_bro/memory_details.rb +71 -0
- data/lib/dead_bro/memory_helpers.rb +62 -0
- data/lib/dead_bro/memory_leak_detector.rb +178 -158
- data/lib/dead_bro/memory_tracking_subscriber.rb +7 -31
- data/lib/dead_bro/monitor.rb +18 -5
- data/lib/dead_bro/railtie.rb +10 -6
- data/lib/dead_bro/sql_subscriber.rb +102 -70
- data/lib/dead_bro/sql_tracking_middleware.rb +7 -1
- data/lib/dead_bro/subscriber.rb +40 -15
- data/lib/dead_bro/version.rb +1 -1
- data/lib/dead_bro.rb +129 -113
- metadata +4 -1
data/lib/dead_bro.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "dead_bro/version"
|
|
|
5
5
|
module DeadBro
|
|
6
6
|
autoload :Configuration, "dead_bro/configuration"
|
|
7
7
|
autoload :Client, "dead_bro/client"
|
|
8
|
+
autoload :Dispatcher, "dead_bro/dispatcher"
|
|
8
9
|
autoload :CircuitBreaker, "dead_bro/circuit_breaker"
|
|
9
10
|
autoload :Collectors, "dead_bro/collectors"
|
|
10
11
|
autoload :Subscriber, "dead_bro/subscriber"
|
|
@@ -12,6 +13,7 @@ module DeadBro
|
|
|
12
13
|
autoload :SqlTrackingMiddleware, "dead_bro/sql_tracking_middleware"
|
|
13
14
|
autoload :CacheSubscriber, "dead_bro/cache_subscriber"
|
|
14
15
|
autoload :RedisSubscriber, "dead_bro/redis_subscriber"
|
|
16
|
+
autoload :ElasticsearchSubscriber, "dead_bro/elasticsearch_subscriber"
|
|
15
17
|
autoload :ViewRenderingSubscriber, "dead_bro/view_rendering_subscriber"
|
|
16
18
|
autoload :MemoryTrackingSubscriber, "dead_bro/memory_tracking_subscriber"
|
|
17
19
|
autoload :MemoryLeakDetector, "dead_bro/memory_leak_detector"
|
|
@@ -20,6 +22,7 @@ module DeadBro
|
|
|
20
22
|
autoload :JobSubscriber, "dead_bro/job_subscriber"
|
|
21
23
|
autoload :JobSqlTrackingMiddleware, "dead_bro/job_sql_tracking_middleware"
|
|
22
24
|
autoload :Monitor, "dead_bro/monitor"
|
|
25
|
+
autoload :MemoryDetails, "dead_bro/memory_details"
|
|
23
26
|
autoload :Logger, "dead_bro/logger"
|
|
24
27
|
begin
|
|
25
28
|
require "dead_bro/railtie"
|
|
@@ -28,6 +31,57 @@ module DeadBro
|
|
|
28
31
|
|
|
29
32
|
class Error < StandardError; end
|
|
30
33
|
|
|
34
|
+
# Returned by DeadBro.analyze. sql_queries is intentionally omitted from
|
|
35
|
+
# inspect/to_s/pretty_print to avoid bloating console output.
|
|
36
|
+
# Access it explicitly via .sql_queries.
|
|
37
|
+
class AnalysisResult
|
|
38
|
+
attr_reader :label, :total_time_ms, :sql_count, :sql_time_ms,
|
|
39
|
+
:sql_queries, :memory_before_mb, :memory_after_mb,
|
|
40
|
+
:memory_delta_mb, :memory_details, :verbose
|
|
41
|
+
|
|
42
|
+
def initialize(label:, total_time_ms:, sql_count:, sql_time_ms:, sql_queries:,
|
|
43
|
+
memory_before_mb:, memory_after_mb:, memory_delta_mb:,
|
|
44
|
+
memory_details:, verbose:)
|
|
45
|
+
@label = label
|
|
46
|
+
@total_time_ms = total_time_ms
|
|
47
|
+
@sql_count = sql_count
|
|
48
|
+
@sql_time_ms = sql_time_ms
|
|
49
|
+
@sql_queries = sql_queries
|
|
50
|
+
@memory_before_mb = memory_before_mb
|
|
51
|
+
@memory_after_mb = memory_after_mb
|
|
52
|
+
@memory_delta_mb = memory_delta_mb
|
|
53
|
+
@memory_details = memory_details
|
|
54
|
+
@verbose = verbose
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def inspect
|
|
58
|
+
"#<DeadBro::AnalysisResult label=#{label.inspect} total_time_ms=#{total_time_ms} " \
|
|
59
|
+
"sql_count=#{sql_count} sql_time_ms=#{sql_time_ms} " \
|
|
60
|
+
"memory_before_mb=#{memory_before_mb} memory_after_mb=#{memory_after_mb} " \
|
|
61
|
+
"memory_delta_mb=#{memory_delta_mb}>"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
alias_method :to_s, :inspect
|
|
65
|
+
|
|
66
|
+
def most_queries
|
|
67
|
+
sql_queries.max_by(5) { |q| q[:count] }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def longest_queries
|
|
71
|
+
sql_queries.max_by(5) { |q| q[:total_time_ms] }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def pretty_print(pp)
|
|
75
|
+
pp.text(inspect)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def [](key)
|
|
79
|
+
public_send(key)
|
|
80
|
+
rescue NoMethodError
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
31
85
|
def self.configure
|
|
32
86
|
yield configuration
|
|
33
87
|
end
|
|
@@ -100,7 +154,8 @@ module DeadBro
|
|
|
100
154
|
# - memory before/after and delta
|
|
101
155
|
# - when detailed memory tracking is enabled, GC and allocation stats
|
|
102
156
|
#
|
|
103
|
-
# The return value
|
|
157
|
+
# The return value is a DeadBro::AnalysisResult struct. sql_queries is omitted from
|
|
158
|
+
# inspect/to_s but accessible via .sql_queries. Other members:
|
|
104
159
|
# - :label
|
|
105
160
|
# - :total_time_ms
|
|
106
161
|
# - :sql_count
|
|
@@ -110,33 +165,39 @@ module DeadBro
|
|
|
110
165
|
# - :memory_after_mb
|
|
111
166
|
# - :memory_delta_mb
|
|
112
167
|
# - :memory_details (detailed GC/allocation stats when available)
|
|
113
|
-
def self.analyze(label = nil)
|
|
168
|
+
def self.analyze(label = nil, verbose: false)
|
|
114
169
|
raise ArgumentError, "DeadBro.analyze requires a block" unless block_given?
|
|
115
170
|
|
|
116
171
|
label ||= "block"
|
|
117
172
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
173
|
+
# Lower Rails log level to DEBUG and enable ActiveRecord verbose_query_logs
|
|
174
|
+
# so Rails' own SQL logging (including ↳ caller frames) is visible.
|
|
175
|
+
original_log_level = nil
|
|
176
|
+
original_verbose_query_logs = nil
|
|
177
|
+
if verbose
|
|
178
|
+
begin
|
|
179
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger.respond_to?(:level)
|
|
180
|
+
original_log_level = Rails.logger.level
|
|
181
|
+
Rails.logger.level = 0 # Logger::DEBUG
|
|
182
|
+
end
|
|
183
|
+
rescue
|
|
126
184
|
end
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
else
|
|
134
|
-
0.0
|
|
185
|
+
begin
|
|
186
|
+
if defined?(ActiveRecord) && ActiveRecord.respond_to?(:verbose_query_logs)
|
|
187
|
+
original_verbose_query_logs = ActiveRecord.verbose_query_logs
|
|
188
|
+
ActiveRecord.verbose_query_logs = true
|
|
189
|
+
end
|
|
190
|
+
rescue
|
|
135
191
|
end
|
|
136
|
-
rescue
|
|
137
|
-
memory_before_mb = 0.0
|
|
138
192
|
end
|
|
139
193
|
|
|
194
|
+
# Capture baseline memory stats — config-independent, analyze is debug-only.
|
|
195
|
+
gc_before = begin; GC.stat; rescue; {}; end
|
|
196
|
+
memory_before_mb = begin; DeadBro::MemoryHelpers.rss_mb; rescue; 0.0; end
|
|
197
|
+
object_counts_before = begin
|
|
198
|
+
defined?(ObjectSpace) && ObjectSpace.respond_to?(:count_objects) ? ObjectSpace.count_objects.dup : {}
|
|
199
|
+
rescue; {}; end
|
|
200
|
+
|
|
140
201
|
# Local SQL tracking just for this block.
|
|
141
202
|
# We subscribe directly to ActiveSupport::Notifications instead of relying
|
|
142
203
|
# on DeadBro's global SqlSubscriber tracking so we don't interfere with or
|
|
@@ -182,11 +243,7 @@ module DeadBro
|
|
|
182
243
|
"SQL"
|
|
183
244
|
end
|
|
184
245
|
|
|
185
|
-
local_sql_queries << {
|
|
186
|
-
duration_ms: duration_ms,
|
|
187
|
-
sql: normalized_sql,
|
|
188
|
-
query_type: query_type
|
|
189
|
-
}
|
|
246
|
+
local_sql_queries << {duration_ms: duration_ms, sql: normalized_sql, query_type: query_type}
|
|
190
247
|
end
|
|
191
248
|
end
|
|
192
249
|
rescue
|
|
@@ -202,6 +259,20 @@ module DeadBro
|
|
|
202
259
|
rescue => e
|
|
203
260
|
error = e
|
|
204
261
|
ensure
|
|
262
|
+
# Restore Rails log level before any output
|
|
263
|
+
begin
|
|
264
|
+
if verbose && original_log_level
|
|
265
|
+
Rails.logger.level = original_log_level
|
|
266
|
+
end
|
|
267
|
+
rescue
|
|
268
|
+
end
|
|
269
|
+
begin
|
|
270
|
+
if verbose && !original_verbose_query_logs.nil?
|
|
271
|
+
ActiveRecord.verbose_query_logs = original_verbose_query_logs
|
|
272
|
+
end
|
|
273
|
+
rescue
|
|
274
|
+
end
|
|
275
|
+
|
|
205
276
|
# Always unsubscribe our local SQL subscriber
|
|
206
277
|
begin
|
|
207
278
|
if sql_notification_subscription && defined?(ActiveSupport) && defined?(ActiveSupport::Notifications)
|
|
@@ -231,97 +302,41 @@ module DeadBro
|
|
|
231
302
|
entry[:type] ||= q[:query_type]
|
|
232
303
|
end
|
|
233
304
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
memory_after_mb = memory_before_mb
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if memory_tracking_started
|
|
241
|
-
begin
|
|
242
|
-
raw_events = DeadBro::MemoryTrackingSubscriber.stop_request_tracking || {}
|
|
243
|
-
rescue
|
|
244
|
-
raw_events = {}
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
begin
|
|
249
|
-
# Prefer values from detailed tracking when available
|
|
250
|
-
if raw_events[:memory_before]
|
|
251
|
-
memory_before_mb = raw_events[:memory_before]
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
memory_after_mb = if raw_events[:memory_after]
|
|
255
|
-
raw_events[:memory_after]
|
|
256
|
-
elsif defined?(DeadBro::MemoryTrackingSubscriber)
|
|
257
|
-
DeadBro::MemoryTrackingSubscriber.memory_usage_mb
|
|
258
|
-
else
|
|
259
|
-
memory_before_mb
|
|
260
|
-
end
|
|
261
|
-
rescue
|
|
262
|
-
memory_after_mb = memory_before_mb
|
|
263
|
-
end
|
|
305
|
+
# Capture post-block memory state — always, regardless of config.
|
|
306
|
+
gc_after = begin; GC.stat; rescue; {}; end
|
|
307
|
+
memory_after_mb = begin; DeadBro::MemoryHelpers.rss_mb; rescue; memory_before_mb; end
|
|
308
|
+
object_counts_after = begin
|
|
309
|
+
defined?(ObjectSpace) && ObjectSpace.respond_to?(:count_objects) ? ObjectSpace.count_objects.dup : {}
|
|
310
|
+
rescue; {}; end
|
|
264
311
|
|
|
265
312
|
memory_delta_mb = (memory_after_mb - memory_before_mb).round(2)
|
|
266
313
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
detailed_memory_summary = nil
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
sql_queries_segment = ""
|
|
284
|
-
unless top_query_signatures.empty?
|
|
285
|
-
formatted_queries = top_query_signatures.map do |sig, data|
|
|
286
|
-
type = data[:type] || "SQL"
|
|
287
|
-
count = data[:count]
|
|
288
|
-
total_ms = data[:total_time_ms].round(2)
|
|
289
|
-
"#{type} #{sig} (#{count}x, #{total_ms}ms)"
|
|
290
|
-
end
|
|
291
|
-
sql_queries_segment = ", sql_top_queries=[#{formatted_queries.join(" | ")}]"
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
base_summary = "Analysis for #{label} - total_time=#{total_time_ms}ms, " \
|
|
295
|
-
"sql_queries=#{sql_count}, sql_time=#{sql_time_ms}ms, " \
|
|
296
|
-
"memory_before=#{memory_before_mb.round(2)}MB, " \
|
|
297
|
-
"memory_after=#{memory_after_mb.round(2)}MB, " \
|
|
298
|
-
"memory_delta=#{memory_delta_mb}MB" \
|
|
299
|
-
"#{sql_queries_segment}"
|
|
300
|
-
|
|
301
|
-
summary =
|
|
302
|
-
if detailed_memory_summary
|
|
303
|
-
top_classes = (detailed_memory_summary[:top_allocating_classes] || []).map { |c|
|
|
304
|
-
"#{c[:class_name]}:#{c[:size_mb]}MB"
|
|
305
|
-
}.join(", ")
|
|
306
|
-
|
|
307
|
-
"#{base_summary}, " \
|
|
308
|
-
"memory_growth=#{detailed_memory_summary[:memory_growth_mb].round(2)}MB, " \
|
|
309
|
-
"gc_runs=+#{detailed_memory_summary[:gc_count_increase]}, " \
|
|
310
|
-
"heap_pages=+#{detailed_memory_summary[:heap_pages_increase]}, " \
|
|
311
|
-
"allocated=#{detailed_memory_summary[:total_allocated_size_mb].round(2)}MB, " \
|
|
312
|
-
"top_allocators=[#{top_classes}]"
|
|
314
|
+
# Large object scan — full ObjectSpace walk. analyze is debug-only, not hot path.
|
|
315
|
+
large_objects = begin
|
|
316
|
+
if defined?(ObjectSpace) && ObjectSpace.respond_to?(:each_object) && ObjectSpace.respond_to?(:memsize_of)
|
|
317
|
+
found = []
|
|
318
|
+
ObjectSpace.each_object do |obj|
|
|
319
|
+
size = begin; ObjectSpace.memsize_of(obj); rescue; 0; end
|
|
320
|
+
next unless size > 1_000_000
|
|
321
|
+
klass = begin; obj.class.name || "Unknown"; rescue; "Unknown"; end
|
|
322
|
+
found << {class_name: klass, size_mb: (size / 1_000_000.0).round(2)}
|
|
323
|
+
break if found.length >= 50
|
|
324
|
+
end
|
|
325
|
+
found.sort_by { |h| -h[:size_mb] }
|
|
313
326
|
else
|
|
314
|
-
|
|
327
|
+
[]
|
|
315
328
|
end
|
|
329
|
+
rescue; []; end
|
|
316
330
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
331
|
+
detailed_memory_summary = DeadBro::MemoryDetails.build(
|
|
332
|
+
gc_before: gc_before,
|
|
333
|
+
gc_after: gc_after,
|
|
334
|
+
memory_before_mb: memory_before_mb,
|
|
335
|
+
memory_after_mb: memory_after_mb,
|
|
336
|
+
object_counts_before: object_counts_before,
|
|
337
|
+
object_counts_after: object_counts_after,
|
|
338
|
+
large_objects: large_objects
|
|
339
|
+
)
|
|
325
340
|
|
|
326
341
|
# Build structured result hash to return to the caller
|
|
327
342
|
sql_queries_detail = query_signatures.map do |sig, data|
|
|
@@ -333,7 +348,7 @@ module DeadBro
|
|
|
333
348
|
}
|
|
334
349
|
end
|
|
335
350
|
|
|
336
|
-
analysis_result =
|
|
351
|
+
analysis_result = AnalysisResult.new(
|
|
337
352
|
label: label,
|
|
338
353
|
total_time_ms: total_time_ms,
|
|
339
354
|
sql_count: sql_count,
|
|
@@ -342,8 +357,9 @@ module DeadBro
|
|
|
342
357
|
memory_before_mb: memory_before_mb,
|
|
343
358
|
memory_after_mb: memory_after_mb,
|
|
344
359
|
memory_delta_mb: memory_delta_mb,
|
|
345
|
-
memory_details: detailed_memory_summary
|
|
346
|
-
|
|
360
|
+
memory_details: detailed_memory_summary,
|
|
361
|
+
verbose: verbose
|
|
362
|
+
)
|
|
347
363
|
end
|
|
348
364
|
|
|
349
365
|
raise error if error
|
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.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emanuel Comsa
|
|
@@ -33,12 +33,15 @@ 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/dispatcher.rb
|
|
37
|
+
- lib/dead_bro/elasticsearch_subscriber.rb
|
|
36
38
|
- lib/dead_bro/error_middleware.rb
|
|
37
39
|
- lib/dead_bro/http_instrumentation.rb
|
|
38
40
|
- lib/dead_bro/job_sql_tracking_middleware.rb
|
|
39
41
|
- lib/dead_bro/job_subscriber.rb
|
|
40
42
|
- lib/dead_bro/lightweight_memory_tracker.rb
|
|
41
43
|
- lib/dead_bro/logger.rb
|
|
44
|
+
- lib/dead_bro/memory_details.rb
|
|
42
45
|
- lib/dead_bro/memory_helpers.rb
|
|
43
46
|
- lib/dead_bro/memory_leak_detector.rb
|
|
44
47
|
- lib/dead_bro/memory_tracking_subscriber.rb
|