Module: DeadBro::MemoryHelpers

Defined in:
lib/dead_bro/memory_helpers.rb

Constant Summary collapse

RSS_CACHE_TTL_SECONDS =

Helper methods for memory tracking and leak detection

1.0

Class Method Summary collapse

Class Method Details

.analyze_memoryObject

Get current memory analysis



75
76
77
# File 'lib/dead_bro/memory_helpers.rb', line 75

def self.analyze_memory
  DeadBro::MemoryLeakDetector.get_memory_analysis
end

.check_for_leaksObject

Check for memory leaks



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/dead_bro/memory_helpers.rb', line 80

def self.check_for_leaks
  analysis = analyze_memory
  if analysis[:leak_alerts]&.any?
    puts "🚨 Memory leak detected!"
    analysis[:leak_alerts].each do |alert|
      puts "  - Growth: #{alert[:memory_growth_mb]}MB"
      puts "  - Rate: #{alert[:growth_rate_mb_per_second]}MB/sec"
      puts "  - Confidence: #{(alert[:confidence] * 100).round(1)}%"
      puts "  - Recent controllers: #{alert[:recent_controllers].join(", ")}"
    end
  else
    puts "✅ No memory leaks detected"
  end
  analysis
end

.clear_historyObject

Clear memory history (useful for testing)



137
138
139
# File 'lib/dead_bro/memory_helpers.rb', line 137

def self.clear_history
  DeadBro::MemoryLeakDetector.clear_history
end

.memory_summaryObject

Get memory usage summary



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/dead_bro/memory_helpers.rb', line 97

def self.memory_summary
  analysis = analyze_memory
  return "Insufficient data" if analysis[:status] == "insufficient_data"

  memory_stats = analysis[:memory_stats]
  puts "📊 Memory Summary:"
  puts "  - Current: #{memory_stats[:mean]}MB (avg)"
  puts "  - Range: #{memory_stats[:min]}MB - #{memory_stats[:max]}MB"
  puts "  - Volatility: #{memory_stats[:std_dev]}MB"
  puts "  - Samples: #{analysis[:sample_count]}"

  if analysis[:memory_trend][:slope] > 0
    puts "  - Trend: ↗️ Growing at #{analysis[:memory_trend][:slope].round(3)}MB/sec"
  elsif analysis[:memory_trend][:slope] < 0
    puts "  - Trend: ↘️ Shrinking at #{analysis[:memory_trend][:slope].abs.round(3)}MB/sec"
  else
    puts "  - Trend: ➡️ Stable"
  end

  analysis
end

.monitor_memory(label, &block) ⇒ Object

Monitor memory during a block execution



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/dead_bro/memory_helpers.rb', line 120

def self.monitor_memory(label, &block)
  snapshot("before_#{label}")
  result = yield
  snapshot("after_#{label}")

  # Get the difference
  analysis = analyze_memory
  if analysis[:memory_stats]
    puts "🔍 Memory monitoring for '#{label}':"
    puts "  - Memory change: #{analysis[:memory_stats][:max] - analysis[:memory_stats][:min]}MB"
    puts "  - Peak usage: #{analysis[:memory_stats][:max]}MB"
  end

  result
end

.read_rss_bytesObject



40
41
42
43
44
45
46
47
48
# File 'lib/dead_bro/memory_helpers.rb', line 40

def self.read_rss_bytes
  if File.readable?("/proc/self/status")
    read_rss_from_proc_status
  else
    read_rss_from_ps
  end
rescue
  0
end

.read_rss_from_proc_statusObject



50
51
52
53
54
55
56
57
58
59
# File 'lib/dead_bro/memory_helpers.rb', line 50

def self.read_rss_from_proc_status
  File.foreach("/proc/self/status") do |line|
    next unless line.start_with?("VmRSS:")
    kb = line.split[1].to_i
    return kb * 1024 if kb > 0
  end
  0
rescue
  0
end

.read_rss_from_psObject



61
62
63
64
65
66
67
# File 'lib/dead_bro/memory_helpers.rb', line 61

def self.read_rss_from_ps
  kb = `ps -o rss= -p #{Process.pid}`.to_i
  return 0 if kb <= 0
  kb * 1024
rescue
  0
end

.rss_bytesObject

Current process RSS in bytes. Uses /proc/self/status on Linux (cheap read) and falls back to ‘ps` elsewhere. Result is cached for 1s across threads so this is safe to call from every request without flooding the kernel.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/dead_bro/memory_helpers.rb', line 14

def self.rss_bytes
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  cached = @rss_cache
  if cached && (now - cached[1]) < RSS_CACHE_TTL_SECONDS
    return cached[0]
  end

  value = read_rss_bytes
  @rss_cache_mutex.synchronize do
    # Re-check inside the lock to avoid racing a newer reading.
    cached = @rss_cache
    if cached.nil? || (now - cached[1]) >= RSS_CACHE_TTL_SECONDS
      @rss_cache = [value, now]
    end
  end
  value
rescue
  0
end

.rss_mbObject



34
35
36
37
38
# File 'lib/dead_bro/memory_helpers.rb', line 34

def self.rss_mb
  (rss_bytes.to_f / (1024 * 1024)).round(2)
rescue
  0.0
end

.snapshot(label) ⇒ Object

Take a memory snapshot with a custom label



70
71
72
# File 'lib/dead_bro/memory_helpers.rb', line 70

def self.snapshot(label)
  DeadBro::MemoryTrackingSubscriber.take_memory_snapshot(label)
end

.top_allocatorsObject

Get top memory allocating classes



142
143
144
145
146
147
# File 'lib/dead_bro/memory_helpers.rb', line 142

def self.top_allocators
  # This would need to be called from within a request context
  # where memory_events are available
  puts "Top memory allocators:"
  puts "  (Call this from within a request to see allocation data)"
end