dead_bro 0.2.4 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6e2fe55b896183a75b3590cb0939c3cc1326822eb6be81bbd147cebb9ebbee2
4
- data.tar.gz: fed15de1f8aa27c1fb6df413dc3cf8b1d6de9f09ec983f28c52d00afbccacd41
3
+ metadata.gz: 95efdf7e1aadb84f27739eb59612c8ad3bf0c6a5315d12497322140fdd0ca676
4
+ data.tar.gz: c9285dd8f024f10d974b04b76e98a79f365bb6e07ab1fa665a8340cc34bc69e7
5
5
  SHA512:
6
- metadata.gz: 278bcd7b6da8de36b01a8c41c5325af407c150b3094aff5b358d6609ecea052970be33cfcba901378ddbdfe9fbfdeeb1a228571b5786e1538b6252c116595fc2
7
- data.tar.gz: 27bc67cfa0f02510c4b52426a66151d4c1a828229dedc2468bfc899d589bed1d6e7e132c864f4e55c263162ead93be22ab91bf71e5ba3c95ed028fbf67c36cd6
6
+ metadata.gz: 7b12f73db0672c45f624b0a3916b65c95c8d0929e491ef173832b5f960cfb073b2cf0b337a2071908f5a4bbe768551e851747c56e5a90a704c76719e13449773
7
+ data.tar.gz: b99e770dae2b68d773eee279b02c78821244bf9c0322e91776301611d0204a0ebdd3c3b5e0c4bf62aec9e5a403bdb4c66ee35f29907fb7d5e68ae978eca33c4d
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadBro
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/dead_bro.rb CHANGED
@@ -84,4 +84,275 @@ module DeadBro
84
84
  # Shared constant for tracking start time (used by all subscribers)
85
85
  TRACKING_START_TIME_KEY = :dead_bro_tracking_start_time
86
86
  MAX_TRACKING_DURATION_SECONDS = 3600 # 1 hour
87
+
88
+ # Analyze a block of code by tracking its runtime, SQL queries, and memory usage.
89
+ #
90
+ # Usage:
91
+ # DeadBro.analyze("load users") do
92
+ # User.where(active: true).to_a
93
+ # end
94
+ #
95
+ # This will print a summary to the console (or Rails logger) including:
96
+ # - total time the block took
97
+ # - number of SQL queries executed
98
+ # - total SQL time
99
+ # - breakdown of distinct SQL query patterns (count and total time)
100
+ # - memory before/after and delta
101
+ # - when detailed memory tracking is enabled, GC and allocation stats
102
+ #
103
+ # The return value of this method is a hash with the following keys:
104
+ # - :label
105
+ # - :total_time_ms
106
+ # - :sql_count
107
+ # - :sql_time_ms
108
+ # - :sql_queries (array of distinct query patterns with counts and timings)
109
+ # - :memory_before_mb
110
+ # - :memory_after_mb
111
+ # - :memory_delta_mb
112
+ # - :memory_details (detailed GC/allocation stats when available)
113
+ def self.analyze(label = nil)
114
+ raise ArgumentError, "DeadBro.analyze requires a block" unless block_given?
115
+
116
+ label ||= "block"
117
+
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
126
+ end
127
+ rescue
128
+ end
129
+
130
+ begin
131
+ if defined?(DeadBro::MemoryTrackingSubscriber)
132
+ memory_before_mb = DeadBro::MemoryTrackingSubscriber.memory_usage_mb
133
+ else
134
+ memory_before_mb = 0.0
135
+ end
136
+ rescue
137
+ memory_before_mb = 0.0
138
+ end
139
+
140
+ # Local SQL tracking just for this block.
141
+ # We subscribe directly to ActiveSupport::Notifications instead of relying
142
+ # on DeadBro's global SqlSubscriber tracking so we don't interfere with or
143
+ # depend on request/job instrumentation.
144
+ current_thread = Thread.current
145
+ local_sql_queries = []
146
+ sql_notification_subscription = nil
147
+
148
+ begin
149
+ if defined?(ActiveSupport) && defined?(ActiveSupport::Notifications)
150
+ # Ensure SqlSubscriber is loaded so SQL_EVENT_NAME is defined
151
+ DeadBro::SqlSubscriber
152
+ event_name = DeadBro::SqlSubscriber::SQL_EVENT_NAME
153
+
154
+ sql_notification_subscription =
155
+ ActiveSupport::Notifications.subscribe(event_name) do |_name, started, finished, _id, data|
156
+ # Only count queries executed on this thread and skip schema queries
157
+ next unless Thread.current == current_thread
158
+ next if data[:name] == "SCHEMA"
159
+
160
+ duration_ms = begin
161
+ ((finished - started) * 1000.0).round(2)
162
+ rescue
163
+ 0.0
164
+ end
165
+
166
+ raw_sql = data[:sql].to_s
167
+ # Normalize SQL so identical logical queries group together
168
+ normalized_sql = begin
169
+ sql = DeadBro::SqlSubscriber.sanitize_sql(raw_sql)
170
+ # Collapse whitespace
171
+ sql = sql.gsub(/\s+/, " ").strip
172
+ # Normalize numeric literals and quoted strings to '?'
173
+ sql = sql.gsub(/=\s*\d+(\.\d+)?/i, "= ?")
174
+ sql = sql.gsub(/=\s*'[^']*'/i, "= ?")
175
+ sql
176
+ rescue
177
+ raw_sql.to_s.strip
178
+ end
179
+
180
+ query_type = begin
181
+ raw_sql.strip.split.first.to_s.upcase
182
+ rescue
183
+ "SQL"
184
+ end
185
+
186
+ local_sql_queries << {
187
+ duration_ms: duration_ms,
188
+ sql: normalized_sql,
189
+ query_type: query_type
190
+ }
191
+ end
192
+ end
193
+ rescue
194
+ sql_notification_subscription = nil
195
+ end
196
+
197
+ block_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
198
+
199
+ error = nil
200
+ result = nil
201
+ analysis_result = nil
202
+
203
+ begin
204
+ result = yield
205
+ rescue => e
206
+ error = e
207
+ ensure
208
+ # Always unsubscribe our local SQL subscriber
209
+ begin
210
+ if sql_notification_subscription && defined?(ActiveSupport) && defined?(ActiveSupport::Notifications)
211
+ ActiveSupport::Notifications.unsubscribe(sql_notification_subscription)
212
+ end
213
+ rescue
214
+ end
215
+
216
+ total_time_ms = begin
217
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - block_start
218
+ (elapsed * 1000.0).round(2)
219
+ rescue
220
+ 0.0
221
+ end
222
+
223
+ # Aggregate SQL metrics from our local subscription
224
+ sql_count = local_sql_queries.length
225
+ sql_time_ms = local_sql_queries.sum { |q| (q[:duration_ms] || 0.0).to_f }.round(2)
226
+
227
+ # Group SQL queries by normalized pattern to show frequency and cost
228
+ query_signatures = Hash.new { |h, k| h[k] = { count: 0, total_time_ms: 0.0, type: nil } }
229
+ local_sql_queries.each do |q|
230
+ sig = (q[:sql] || "UNKNOWN").to_s
231
+ entry = query_signatures[sig]
232
+ entry[:count] += 1
233
+ entry[:total_time_ms] += (q[:duration_ms] || 0.0).to_f
234
+ entry[:type] ||= q[:query_type]
235
+ end
236
+
237
+ top_query_signatures = query_signatures.sort_by { |_, data| -data[:count] }.first(3)
238
+
239
+ memory_after_mb = memory_before_mb
240
+ memory_delta_mb = 0.0
241
+ detailed_memory_summary = nil
242
+
243
+ raw_events = {}
244
+ if memory_tracking_started
245
+ begin
246
+ raw_events = DeadBro::MemoryTrackingSubscriber.stop_request_tracking || {}
247
+ rescue
248
+ raw_events = {}
249
+ end
250
+ end
251
+
252
+ begin
253
+ # Prefer values from detailed tracking when available
254
+ if raw_events[:memory_before]
255
+ memory_before_mb = raw_events[:memory_before]
256
+ end
257
+
258
+ if raw_events[:memory_after]
259
+ memory_after_mb = raw_events[:memory_after]
260
+ else
261
+ if defined?(DeadBro::MemoryTrackingSubscriber)
262
+ memory_after_mb = DeadBro::MemoryTrackingSubscriber.memory_usage_mb
263
+ else
264
+ memory_after_mb = memory_before_mb
265
+ end
266
+ end
267
+ rescue
268
+ memory_after_mb = memory_before_mb
269
+ end
270
+
271
+ memory_delta_mb = (memory_after_mb - memory_before_mb).round(2)
272
+
273
+ if memory_tracking_started && !raw_events.empty?
274
+ begin
275
+ perf = DeadBro::MemoryTrackingSubscriber.analyze_memory_performance(raw_events) || {}
276
+
277
+ detailed_memory_summary = {
278
+ memory_growth_mb: (perf[:memory_growth_mb] || memory_delta_mb).to_f,
279
+ gc_count_increase: perf.dig(:gc_efficiency, :gc_count_increase) || 0,
280
+ heap_pages_increase: perf.dig(:gc_efficiency, :heap_pages_increase) || 0,
281
+ total_allocated_size_mb: (perf[:total_allocated_size_mb] || 0.0).to_f,
282
+ top_allocating_classes: (perf[:top_allocating_classes] || []).first(3)
283
+ }
284
+ rescue
285
+ detailed_memory_summary = nil
286
+ end
287
+ end
288
+
289
+ sql_queries_segment = ""
290
+ unless top_query_signatures.empty?
291
+ formatted_queries = top_query_signatures.map do |sig, data|
292
+ type = data[:type] || "SQL"
293
+ count = data[:count]
294
+ total_ms = data[:total_time_ms].round(2)
295
+ "#{type} #{sig} (#{count}x, #{total_ms}ms)"
296
+ end
297
+ sql_queries_segment = ", sql_top_queries=[#{formatted_queries.join(" | ")}]"
298
+ end
299
+
300
+ base_summary = "Analysis for #{label} - total_time=#{total_time_ms}ms, " \
301
+ "sql_queries=#{sql_count}, sql_time=#{sql_time_ms}ms, " \
302
+ "memory_before=#{memory_before_mb.round(2)}MB, " \
303
+ "memory_after=#{memory_after_mb.round(2)}MB, " \
304
+ "memory_delta=#{memory_delta_mb}MB" \
305
+ "#{sql_queries_segment}"
306
+
307
+ summary =
308
+ if detailed_memory_summary
309
+ top_classes = (detailed_memory_summary[:top_allocating_classes] || []).map { |c|
310
+ "#{c[:class_name]}:#{c[:size_mb]}MB"
311
+ }.join(", ")
312
+
313
+ "#{base_summary}, " \
314
+ "memory_growth=#{detailed_memory_summary[:memory_growth_mb].round(2)}MB, " \
315
+ "gc_runs=+#{detailed_memory_summary[:gc_count_increase]}, " \
316
+ "heap_pages=+#{detailed_memory_summary[:heap_pages_increase]}, " \
317
+ "allocated=#{detailed_memory_summary[:total_allocated_size_mb].round(2)}MB, " \
318
+ "top_allocators=[#{top_classes}]"
319
+ else
320
+ base_summary
321
+ end
322
+
323
+ begin
324
+ DeadBro.logger.info(summary)
325
+ rescue
326
+ begin
327
+ $stdout.puts("[DeadBro] #{summary}")
328
+ rescue
329
+ end
330
+ end
331
+
332
+ # Build structured result hash to return to the caller
333
+ sql_queries_detail = query_signatures.map do |sig, data|
334
+ {
335
+ sql: sig,
336
+ query_type: data[:type] || "SQL",
337
+ count: data[:count],
338
+ total_time_ms: data[:total_time_ms].round(2)
339
+ }
340
+ end
341
+
342
+ analysis_result = {
343
+ label: label,
344
+ total_time_ms: total_time_ms,
345
+ sql_count: sql_count,
346
+ sql_time_ms: sql_time_ms,
347
+ sql_queries: sql_queries_detail,
348
+ memory_before_mb: memory_before_mb,
349
+ memory_after_mb: memory_after_mb,
350
+ memory_delta_mb: memory_delta_mb,
351
+ memory_details: detailed_memory_summary
352
+ }
353
+ end
354
+
355
+ raise error if error
356
+ analysis_result
357
+ end
87
358
  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
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuel Comsa