memory-leak 0.1.0 → 0.2.0
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 +4 -4
- checksums.yaml.gz.sig +1 -4
- data/lib/memory/leak/cluster.rb +88 -0
- data/lib/memory/leak/{detector.rb → monitor.rb} +26 -16
- data/lib/memory/leak/version.rb +1 -1
- data/lib/memory/leak.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c289492ac6aa4dc49416b15d859f629fbf1f0cc234d0f1969c0f0fc9e362607
|
4
|
+
data.tar.gz: 23f0b4b50772e92d5a90d89510a9929eb0889c050b45cf864084061e11307fe8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 796f477f191c517e8e46cd2889ae04501fe81912c45190ecaa9ea2b0e61f70c621cc84668c3429a32fa221aecb8c64c42ea48d8b0a16c16a9d243f82a02fe4e4
|
7
|
+
data.tar.gz: 40ff9dbe71d8b6a063295a2ce622961d2fb85c2149c51a8ebcf682ef80e276e0edfa148154ea460cc03f82c06c7de1c63d9b5ff1b70f17a65b2f88c99cfa16c2
|
checksums.yaml.gz.sig
CHANGED
@@ -1,4 +1 @@
|
|
1
|
-
|
2
|
-
Q+�:��9�a�����#~x����W�����'�Ӧp��H4��V�#=��d,���$���ۨ�<hjN��<�aó����Gk�O��5�[4���{��
|
3
|
-
��p�(l�%5��X����l���z�>��iKv�L�uF{{b�L�+[���
|
4
|
-
�háD������Ǽ�,��4�D�1~��H�u8�=��V��������z������l �'���}��;�]�\��d�'(,� �]�%�\/5+��ڜ��� ����}塥H_��6����Ra0J������1e!�1*��&�PK���`��g^9��
|
1
|
+
K��W�mQ�����PF��0�0=EA�G%�P��i���Ӑ��@���:�*?b��wRok�{��|gv^+9,0�/�>s��{��0r]Is�����Y��TU���(�� �3�Wt�=)�2/#9sU���\��v,�T)��h��BQͽ��t���'ZJg�(!��Å>_X�NO�2V2q^6�57M����o�J��Y`�F5ꑍ�M��ܨ�3SB����*�Je���gJҺ{>�z�1;��!=��3�,^y�ǀ'ۭWV�,�$=���1Aq��Y��i����Mo��2o[W�Z��@�����VL�ЇDb?�Aq��+2�r�@a�`�u��m6�9mz��Rlf��43���?��+Zw�x*��j����`X�>��5�#�/�wf��
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require "console"
|
7
|
+
require_relative "monitor"
|
8
|
+
|
9
|
+
module Memory
|
10
|
+
module Leak
|
11
|
+
# Detects memory leaks in a cluster of processes.
|
12
|
+
#
|
13
|
+
# This class is used to manage a cluster of processes and detect memory leaks in each process. It can also apply a memory limit to the cluster, and terminate processes if the memory limit is exceeded.
|
14
|
+
class Cluster
|
15
|
+
# Create a new cluster.
|
16
|
+
#
|
17
|
+
# @parameter limit [Numeric | Nil] The (total) memory limit for the cluster.
|
18
|
+
def initialize(limit: nil)
|
19
|
+
@limit = limit
|
20
|
+
|
21
|
+
@pids = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# @attribute [Numeric | Nil] The memory limit for the cluster.
|
25
|
+
attr_accessor :limit
|
26
|
+
|
27
|
+
# @attribute [Hash(PID, Monitor)] The process IDs and monitors in the cluster.
|
28
|
+
attr :pids
|
29
|
+
|
30
|
+
# Add a new process ID to the cluster.
|
31
|
+
def add(pid, **options)
|
32
|
+
@pids[pid] = Monitor.new(pid, **options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Remove a process ID from the cluster.
|
36
|
+
def remove(pid)
|
37
|
+
@pids.delete(pid)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Apply the memory limit to the cluster. If the total memory usage exceeds the limit, yields each PID and monitor in order of maximum memory usage, so that they could be terminated and/or removed.
|
41
|
+
#
|
42
|
+
# @yields {|pid, monitor| ...} each process ID and monitor in order of maximum memory usage, return true if it was terminated to adjust memory usage.
|
43
|
+
def apply_limit!(limit = @limit)
|
44
|
+
total = @pids.values.map(&:current).sum
|
45
|
+
|
46
|
+
if total > limit
|
47
|
+
Console.warn(self, "Total memory usage exceeded limit.", total: total, limit: limit)
|
48
|
+
end
|
49
|
+
|
50
|
+
sorted = @pids.sort_by do |pid, monitor|
|
51
|
+
-monitor.current
|
52
|
+
end
|
53
|
+
|
54
|
+
sorted.each do |pid, monitor|
|
55
|
+
if total > limit
|
56
|
+
if yield pid, monitor, total
|
57
|
+
total -= monitor.current
|
58
|
+
end
|
59
|
+
else
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check all processes in the cluster for memory leaks.
|
66
|
+
#
|
67
|
+
# @yields {|pid, monitor| ...} each process ID and monitor that is leaking or exceeds the memory limit.
|
68
|
+
def check!(&block)
|
69
|
+
leaking = []
|
70
|
+
|
71
|
+
@pids.each do |pid, monitor|
|
72
|
+
monitor.sample!
|
73
|
+
|
74
|
+
if monitor.leaking?
|
75
|
+
Console.debug(self, "Memory Leak Detected!", pid: pid, monitor: monitor)
|
76
|
+
|
77
|
+
leaking << [pid, monitor]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
leaking.each(&block)
|
82
|
+
|
83
|
+
# Finally, apply any per-cluster memory limits:
|
84
|
+
apply_limit!(@limit, &block) if @limit
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -12,7 +12,7 @@ module Memory
|
|
12
12
|
# 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.
|
13
13
|
#
|
14
14
|
# 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.
|
15
|
-
class
|
15
|
+
class Monitor
|
16
16
|
# We only track heap size changes greater than this threshold (KB), across the DEFAULT_INTERVAL.
|
17
17
|
# True memory leaks will eventually hit this threshold, while small fluctuations will not.
|
18
18
|
DEFAULT_THRESHOLD = 1024*10
|
@@ -22,22 +22,27 @@ module Memory
|
|
22
22
|
# With a default interval of 10 seconds, this will track the last ~3 minutes of heap size increases.
|
23
23
|
DEFAULT_LIMIT = 20
|
24
24
|
|
25
|
-
# Create a new
|
25
|
+
# Create a new monitor.
|
26
26
|
#
|
27
27
|
# @parameter maximum [Numeric] The initial maximum heap size, from which we willl track increases, in KiB.
|
28
28
|
# @parameter threshold [Numeric] The threshold for heap size increases, in KiB.
|
29
29
|
# @parameter limit [Numeric] The limit for the number of heap size increases, before we assume a memory leak.
|
30
30
|
# @pid [Integer] The process ID to monitor.
|
31
|
-
def initialize(maximum: nil, threshold: DEFAULT_THRESHOLD, limit: DEFAULT_LIMIT
|
31
|
+
def initialize(pid = Process.pid, maximum: nil, threshold: DEFAULT_THRESHOLD, limit: DEFAULT_LIMIT)
|
32
|
+
@pid = pid
|
33
|
+
|
32
34
|
@maximum = maximum
|
33
35
|
@threshold = threshold
|
34
36
|
@limit = limit
|
35
|
-
@pid = pid
|
36
37
|
|
37
38
|
# The number of increasing heap size samples.
|
38
39
|
@count = 0
|
40
|
+
@current = nil
|
39
41
|
end
|
40
42
|
|
43
|
+
# @attribute [Integer] The process ID to monitor.
|
44
|
+
attr :pid
|
45
|
+
|
41
46
|
# @attribute [Numeric] The current maximum heap size.
|
42
47
|
attr :maximum
|
43
48
|
|
@@ -55,43 +60,48 @@ module Memory
|
|
55
60
|
# 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.
|
56
61
|
#
|
57
62
|
# @returns [Numeric] Memory usage size in KiB.
|
58
|
-
def memory_usage
|
59
|
-
IO.popen(["ps", "-o", "rss=", pid.to_s]) do |io|
|
63
|
+
private def memory_usage
|
64
|
+
IO.popen(["ps", "-o", "rss=", @pid.to_s]) do |io|
|
60
65
|
return Integer(io.readlines.last)
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
69
|
+
# @returns [Integer] The last sampled memory usage.
|
70
|
+
def current
|
71
|
+
@current ||= memory_usage
|
72
|
+
end
|
73
|
+
|
64
74
|
# Indicates whether a memory leak has been detected.
|
65
75
|
#
|
66
76
|
# If the number of increasing heap size samples is greater than or equal to the limit, a memory leak is assumed.
|
67
77
|
#
|
68
78
|
# @returns [Boolean] True if a memory leak has been detected.
|
69
|
-
def
|
79
|
+
def leaking?
|
70
80
|
@count >= @limit
|
71
81
|
end
|
72
82
|
|
73
83
|
# Capture a memory usage sample and yield if a memory leak is detected.
|
74
84
|
#
|
75
|
-
# @yields {|sample,
|
76
|
-
def
|
77
|
-
|
85
|
+
# @yields {|sample, monitor| ...} If a memory leak is detected.
|
86
|
+
def sample!
|
87
|
+
@current = memory_usage
|
78
88
|
|
79
89
|
if @maximum
|
80
|
-
delta =
|
81
|
-
Console.debug(self, "Heap size captured.",
|
90
|
+
delta = @current - @maximum
|
91
|
+
Console.debug(self, "Heap size captured.", current: @current, delta: delta, threshold: @threshold, maximum: @maximum)
|
82
92
|
|
83
93
|
if delta > @threshold
|
84
|
-
@maximum =
|
94
|
+
@maximum = @current
|
85
95
|
@count += 1
|
86
96
|
|
87
97
|
Console.debug(self, "Heap size increased.", maximum: @maximum, count: @count)
|
88
98
|
end
|
89
99
|
else
|
90
|
-
Console.debug(self, "Initial heap size captured.",
|
91
|
-
@maximum =
|
100
|
+
Console.debug(self, "Initial heap size captured.", current: @current)
|
101
|
+
@maximum = @current
|
92
102
|
end
|
93
103
|
|
94
|
-
return
|
104
|
+
return @current
|
95
105
|
end
|
96
106
|
end
|
97
107
|
end
|
data/lib/memory/leak/version.rb
CHANGED
data/lib/memory/leak.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memory-leak
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -43,7 +43,8 @@ extensions: []
|
|
43
43
|
extra_rdoc_files: []
|
44
44
|
files:
|
45
45
|
- lib/memory/leak.rb
|
46
|
-
- lib/memory/leak/
|
46
|
+
- lib/memory/leak/cluster.rb
|
47
|
+
- lib/memory/leak/monitor.rb
|
47
48
|
- lib/memory/leak/version.rb
|
48
49
|
- license.md
|
49
50
|
- readme.md
|
@@ -70,5 +71,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
71
|
requirements: []
|
71
72
|
rubygems_version: 3.6.2
|
72
73
|
specification_version: 4
|
73
|
-
summary: A memory leak
|
74
|
+
summary: A memory leak monitor.
|
74
75
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|