class Memory::Leak::Monitor
We should be careful not to filter historical data, as some memory leaks may only become apparent after a long period of time. Any kind of filtering may prevent us from detecting such a leak.
A memory leak is characterised by the memory usage of the application continuing to rise over time. We can detect this by sampling memory usage and comparing it to the previous sample. If the memory usage is higher than the previous sample, we can say that the application has allocated more memory. Eventually we expect to see this stabilize, but if it continues to rise, we can say that the application has a memory leak.
Detects memory leaks by tracking heap size increases.
def as_json(...)
def as_json(...) { process_id: @process_id, current: @current, maximum: @maximum, threshold: @threshold, limit: @limit, count: @count, } end
def current
def current @current ||= memory_usage end
def initialize(process_id = Process.pid, maximum: nil, threshold: DEFAULT_THRESHOLD, limit: DEFAULT_LIMIT)
@parameter limit [Numeric] The limit for the number of heap size increases, before we assume a memory leak.
@parameter threshold [Numeric] The threshold for heap size increases, in bytes.
@parameter maximum [Numeric] The initial maximum heap size, from which we willl track increases, in bytes.
Create a new monitor.
def initialize(process_id = Process.pid, maximum: nil, threshold: DEFAULT_THRESHOLD, limit: DEFAULT_LIMIT) @process_id = process_id @maximum = maximum @threshold = threshold @limit = limit # The number of increasing heap size samples. @count = 0 @current = nil end
def leaking?
If the number of increasing heap size samples is greater than or equal to the limit, a memory leak is assumed.
Indicates whether a memory leak has been detected.
def leaking? @count >= @limit end
def memory_usage
Even thought the absolute value of this number may not very useful, the relative change is useful for detecting memory leaks, and it works on most platforms.
The current resident set size (RSS) of the process.
def memory_usage n(["ps", "-o", "rss=", @process_id.to_s]) do |io| Integer(io.readlines.last) * 1024
def sample!
Capture a memory usage sample and yield if a memory leak is detected.
def sample! @current = memory_usage if @maximum delta = @current - @maximum Console.debug(self, "Heap size captured.", current: @current, delta: delta, threshold: @threshold, maximum: @maximum) if delta > @threshold @maximum = @current @count += 1 Console.debug(self, "Heap size increased.", maximum: @maximum, count: @count) end else Console.debug(self, "Initial heap size captured.", current: @current) @maximum = @current end return @current end
def to_json(...)
def to_json(...) as_json.to_json(...) end