Module: DeadBro::GcTracker
- Defined in:
- lib/dead_bro/gc_tracker.rb
Constant Summary collapse
- THREAD_KEY =
:dead_bro_gc_start
Class Method Summary collapse
- .diff(before, after) ⇒ Object
- .memory_tracking_enabled? ⇒ Boolean
- .snapshot ⇒ Object
- .start_request_tracking ⇒ Object
- .stop_request_tracking ⇒ Object
Class Method Details
.diff(before, after) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/dead_bro/gc_tracker.rb', line 47 def self.diff(before, after) return {} if before.empty? || after.empty? gc_time_ms = if before[:gc_time_ns] && after[:gc_time_ns] ((after[:gc_time_ns] - before[:gc_time_ns]) / 1_000_000.0).round(3) end result = { minor_gc_runs: (after[:minor_gc_count] || 0) - (before[:minor_gc_count] || 0), major_gc_runs: (after[:major_gc_count] || 0) - (before[:major_gc_count] || 0), allocated_objects: (after[:total_allocated_objects] || 0) - (before[:total_allocated_objects] || 0), gc_time_ms: gc_time_ms } # Present only when the enrichment was captured (memory tracking enabled). if after.key?(:heap_live_slots) || before.key?(:heap_live_slots) # Net change in live slots over the request. A small value alongside a # large allocated_objects means the memory was transient (reclaimed by # GC); a large value means objects were retained — the real leak signal. result[:heap_live_slots_growth] = (after[:heap_live_slots] || 0) - (before[:heap_live_slots] || 0) # Off-heap malloc pressure pending at request end (see snapshot). result[:malloc_increase_bytes] = after[:malloc_increase_bytes] || 0 result[:oldmalloc_increase_bytes] = after[:oldmalloc_increase_bytes] || 0 end result rescue {} end |
.memory_tracking_enabled? ⇒ Boolean
75 76 77 78 79 |
# File 'lib/dead_bro/gc_tracker.rb', line 75 def self.memory_tracking_enabled? DeadBro.configuration.memory_tracking_enabled rescue false end |
.snapshot ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/dead_bro/gc_tracker.rb', line 19 def self.snapshot return {} unless defined?(GC) && GC.respond_to?(:stat) stat = GC.stat base = { minor_gc_count: stat[:minor_gc_count] || 0, major_gc_count: stat[:major_gc_count] || 0, total_allocated_objects: stat[:total_allocated_objects] || 0, gc_time_ns: GC.respond_to?(:total_time) ? GC.total_time : nil } # Memory-tracking enrichment (a few extra GC.stat reads). Only the base # GC pressure fields above are truly always-on. if memory_tracking_enabled? # Live heap slots — net retained objects. Comparing this delta against # allocated_objects separates transient churn from genuine retention. base[:heap_live_slots] = stat[:heap_live_slots] || 0 # Bytes malloc'd outside the Ruby object heap (big strings/buffers, e.g. # parsed JSON response bodies). These are point-in-time gauges reset by # GC, so we report the request-end value rather than a diff. base[:malloc_increase_bytes] = stat[:malloc_increase_bytes] || 0 base[:oldmalloc_increase_bytes] = stat[:oldmalloc_increase_bytes] || 0 end base rescue {} end |
.start_request_tracking ⇒ Object
7 8 9 |
# File 'lib/dead_bro/gc_tracker.rb', line 7 def self.start_request_tracking Thread.current[THREAD_KEY] = snapshot end |
.stop_request_tracking ⇒ Object
11 12 13 14 15 16 17 |
# File 'lib/dead_bro/gc_tracker.rb', line 11 def self.stop_request_tracking before = Thread.current[THREAD_KEY] return {} if before.nil? || before.empty? diff(before, snapshot) ensure Thread.current[THREAD_KEY] = nil end |