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.
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 of this method is a hash with the following keys:
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
- memory_tracking_started = false
119
- memory_before_mb = 0.0
120
-
121
- begin
122
- if defined?(DeadBro::MemoryTrackingSubscriber) &&
123
- !Thread.current[DeadBro::MemoryTrackingSubscriber::THREAD_LOCAL_KEY]
124
- DeadBro::MemoryTrackingSubscriber.start_request_tracking
125
- memory_tracking_started = true
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
- rescue
128
- end
129
-
130
- begin
131
- memory_before_mb = if defined?(DeadBro::MemoryTrackingSubscriber)
132
- DeadBro::MemoryTrackingSubscriber.memory_usage_mb
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
- top_query_signatures = query_signatures.sort_by { |_, data| -data[:count] }.first(3)
235
-
236
- memory_after_mb = memory_before_mb
237
- detailed_memory_summary = nil
238
-
239
- raw_events = {}
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
- if memory_tracking_started && !raw_events.empty?
268
- begin
269
- perf = DeadBro::MemoryTrackingSubscriber.analyze_memory_performance(raw_events) || {}
270
-
271
- detailed_memory_summary = {
272
- memory_growth_mb: (perf[:memory_growth_mb] || memory_delta_mb).to_f,
273
- gc_count_increase: perf.dig(:gc_efficiency, :gc_count_increase) || 0,
274
- heap_pages_increase: perf.dig(:gc_efficiency, :heap_pages_increase) || 0,
275
- total_allocated_size_mb: (perf[:total_allocated_size_mb] || 0.0).to_f,
276
- top_allocating_classes: (perf[:top_allocating_classes] || []).first(3)
277
- }
278
- rescue
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
- base_summary
327
+ []
315
328
  end
329
+ rescue; []; end
316
330
 
317
- begin
318
- DeadBro.logger.info(summary)
319
- rescue
320
- begin
321
- $stdout.puts("[DeadBro] #{summary}")
322
- rescue
323
- end
324
- end
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.8
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