Skip to content

Commit dcf3745

Browse files
committed
Initial memory leak detection code.
1 parent 61007e4 commit dcf3745

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

examples/leaks/leaks.rb

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env ruby
2+
3+
require "process/metrics"
4+
require "async/clock"
5+
require "securerandom"
6+
7+
class RingBuffer
8+
def initialize(size)
9+
@size = 0
10+
11+
@buffer = Array.new(size)
12+
@index = 0
13+
end
14+
15+
attr :size
16+
17+
def full?
18+
@size == @buffer.size
19+
end
20+
21+
def <<(value)
22+
index = (@index + @size) % @buffer.size
23+
@buffer[index] = value
24+
25+
if @size < @buffer.size
26+
@size += 1
27+
end
28+
end
29+
30+
def each
31+
return enum_for(:each) unless block_given?
32+
33+
@size.times do |i|
34+
index = (@index + i) % @buffer.size
35+
yield @buffer[index]
36+
end
37+
end
38+
39+
include Enumerable
40+
end
41+
42+
class LeakDetector
43+
class Sample < Struct.new(:time, :memory)
44+
def self.capture
45+
new(Async::Clock.now, Process::Metrics::Memory.capture(Process.pid).proportional_size)
46+
end
47+
48+
def delta(sample)
49+
(memory - sample.memory) / (time - sample.time)
50+
end
51+
end
52+
53+
def initialize(threshold: 0.0, interval: 10.0, samples: 12)
54+
@threshold = threshold
55+
@interval = interval
56+
57+
@derivatives = RingBuffer.new(samples)
58+
@previous_sample = nil
59+
end
60+
61+
def run
62+
@thread = Thread.new do
63+
loop do
64+
capture_sample
65+
check_for_leaks!
66+
sleep @interval
67+
end
68+
end
69+
end
70+
71+
private
72+
73+
def capture_sample
74+
sample = Sample.capture
75+
76+
$stderr.puts "Sample: #{sample.memory.round(2)} KB"
77+
78+
if @previous_sample
79+
@derivatives << sample.delta(@previous_sample)
80+
end
81+
82+
@previous_sample = sample
83+
end
84+
85+
def check_for_leaks!
86+
return unless samples.full?
87+
88+
average_derivative = @derivatives.sum / @derivatives.size
89+
90+
if average_derivative > @threshold
91+
$stderr.puts "🚨 Memory leak detected!"
92+
puts "Average derivative: #{average_derivative.round(2)} KB/s"
93+
puts "Cumulative derivative: #{@cumulative_derivative.round(2)} KB"
94+
puts "Derivatives: #{@derivatives.map { |d| d.round(2) }.join(', ')}"
95+
end
96+
end
97+
end
98+
99+
# Initialize and run the leak detector
100+
leak_detector = LeakDetector.new
101+
leak_detector.run
102+
103+
leak_rate = 1024 * 1024 # 1 MB/s
104+
105+
loop do
106+
leaks = Array.new(10) do
107+
SecureRandom.random_bytes(leak_rate)
108+
sleep 0.1
109+
end
110+
end

0 commit comments

Comments
 (0)