puma_worker_killer 0.0.1 → 0.0.2
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
- data/.gitignore +2 -1
- data/README.md +5 -0
- data/lib/puma_worker_killer.rb +1 -0
- data/lib/puma_worker_killer/puma_memory.rb +77 -0
- data/lib/puma_worker_killer/reaper.rb +10 -34
- data/lib/puma_worker_killer/version.rb +1 -1
- data/puma_worker_killer.gemspec +2 -2
- data/test/fixtures/app.ru +25 -0
- data/test/puma_worker_killer_test.rb +16 -2
- data/test/test_helper.rb +6 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52f6ead7a78bc887408c2a249b3aed913fe90129
|
4
|
+
data.tar.gz: 752ef55b46747f29aa84b92d84d0d72496fe315a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c12b64d3108e5c4921dba09eb832edbfc008edfeade7cae4ca77429aea1da1dfcbe7fe49f37ac3ac2b5048ebf947e555cc45b352ca6e0303acf8fa9ed38705c
|
7
|
+
data.tar.gz: 99ef751d60702d8ffd5d5eb83d8cb6fc22e5d4174d5fc3cba199bcec5b48dd46c3c80c7eb0682ab7708e484add4957d819656bb3d7742ceac30d59c3e92e14ea
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -58,7 +58,12 @@ You may want to tune the worker killer to run more or less often. You can adjust
|
|
58
58
|
PumaWorkerKiller.frequency = 20 # seconds
|
59
59
|
```
|
60
60
|
|
61
|
+
|
61
62
|
## License
|
62
63
|
|
63
64
|
MIT
|
64
65
|
|
66
|
+
|
67
|
+
## Feedback
|
68
|
+
|
69
|
+
Open up an issue or ping me on twitter [@schneems](http://twitter.com/schneems).
|
data/lib/puma_worker_killer.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
module PumaWorkerKiller
|
2
|
+
class PumaMemory
|
3
|
+
def initialize(master = nil)
|
4
|
+
@master = master || get_master
|
5
|
+
end
|
6
|
+
|
7
|
+
def master
|
8
|
+
@master
|
9
|
+
end
|
10
|
+
|
11
|
+
def size
|
12
|
+
workers.size
|
13
|
+
end
|
14
|
+
|
15
|
+
def term_largest_worker
|
16
|
+
largest_worker.term
|
17
|
+
# Process.wait(largest_worker.pid)
|
18
|
+
# rescue Errno::ECHILD
|
19
|
+
end
|
20
|
+
|
21
|
+
def running?
|
22
|
+
@master && workers.any?
|
23
|
+
end
|
24
|
+
|
25
|
+
def smallest_worker
|
26
|
+
smallest, _ = workers.to_a.first
|
27
|
+
smallest
|
28
|
+
end
|
29
|
+
|
30
|
+
def smallest_worker_memory
|
31
|
+
_, smallest_mem = workers.to_a.first
|
32
|
+
smallest_mem
|
33
|
+
end
|
34
|
+
|
35
|
+
def largest_worker
|
36
|
+
largest_worker, _ = workers.to_a.last
|
37
|
+
largest_worker
|
38
|
+
end
|
39
|
+
|
40
|
+
def largest_worker_memory
|
41
|
+
_, largest_memory_used = workers.to_a.last
|
42
|
+
largest_memory_used
|
43
|
+
end
|
44
|
+
|
45
|
+
# Will refresh @workers
|
46
|
+
def get_total(workers = set_workers)
|
47
|
+
master_memory = GetProcessMem.new(Process.pid).mb
|
48
|
+
worker_memory = workers.map {|_, mem| mem }.inject(&:+) || 0
|
49
|
+
worker_memory + master_memory
|
50
|
+
end
|
51
|
+
alias :get_total_memory :get_total
|
52
|
+
|
53
|
+
def workers
|
54
|
+
@workers || set_workers
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def get_master
|
60
|
+
ObjectSpace.each_object(Puma::Cluster).map { |obj| obj }.first if defined?(Puma::Cluster)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns sorted hash, keys are worker objects, values are memory used per worker
|
64
|
+
# sorted by memory ascending (smallest first, largest last)
|
65
|
+
def set_workers
|
66
|
+
workers = {}
|
67
|
+
@master.instance_variable_get("@workers").each do |worker|
|
68
|
+
workers[worker] = GetProcessMem.new(worker.pid).mb
|
69
|
+
end
|
70
|
+
if workers.any?
|
71
|
+
@workers = Hash[ workers.sort_by {|_, mem| mem } ]
|
72
|
+
else
|
73
|
+
{}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,46 +1,22 @@
|
|
1
1
|
module PumaWorkerKiller
|
2
2
|
class Reaper
|
3
|
-
def initialize(max_ram, master =
|
3
|
+
def initialize(max_ram, master = nil)
|
4
|
+
@cluster = PumaWorkerKiller::PumaMemory.new(master)
|
4
5
|
@max_ram = max_ram
|
5
|
-
@master = master
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def get_memory(pid)
|
13
|
-
GetProcessMem.new(pid).mb
|
14
|
-
end
|
15
|
-
|
16
|
-
def get_workers
|
17
|
-
workers = {}
|
18
|
-
@master.instance_variable_get("@workers").each { |worker| workers[worker] = get_memory(worker.pid) }
|
19
|
-
workers
|
20
|
-
end
|
21
|
-
|
22
|
-
def get_total_memory(workers = self.get_workers)
|
23
|
-
master_memory = get_memory(Process.pid)
|
24
|
-
worker_memory = workers.map {|_, mem| mem }.inject(&:+) || 0
|
25
|
-
worker_memory + master_memory
|
26
|
-
end
|
27
|
-
|
28
|
-
def wait(pid)
|
29
|
-
Process.wait(pid)
|
30
|
-
rescue Errno::ECHILD
|
8
|
+
# used for tes
|
9
|
+
def get_total_memory
|
10
|
+
@cluster.get_total_memory
|
31
11
|
end
|
32
12
|
|
33
13
|
def reap
|
34
|
-
return false unless @
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
biggest_worker, memory_used = workers.sort_by {|_, mem| mem }.last
|
39
|
-
biggest_worker.term
|
40
|
-
@master.log "PumaWorkerKiller: Out of memory. #{workers.count} workers consuming total: #{total_memory} mb out of max: #{@max_ram} mb. Sending TERM to #{biggest_worker.inspect} consuming #{memory_used} mb."
|
41
|
-
wait(biggest_worker.pid)
|
14
|
+
return false unless @cluster.running?
|
15
|
+
if (total = get_total_memory) > @max_ram
|
16
|
+
@cluster.master.log "PumaWorkerKiller: Out of memory. #{@cluster.workers.count} workers consuming total: #{total} mb out of max: #{@max_ram} mb. Sending TERM to #{@cluster.largest_worker.inspect} consuming #{@cluster.largest_worker_memory} mb."
|
17
|
+
@cluster.term_largest_worker
|
42
18
|
else
|
43
|
-
@master.log "PumaWorkerKiller: Consuming #{
|
19
|
+
@cluster.master.log "PumaWorkerKiller: Consuming #{total} mb with master and #{@cluster.workers.count} workers"
|
44
20
|
end
|
45
21
|
end
|
46
22
|
end
|
data/puma_worker_killer.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = PumaWorkerKiller::VERSION
|
9
9
|
gem.authors = ["Richard Schneeman"]
|
10
10
|
gem.email = ["richard.schneeman+rubygems@gmail.com"]
|
11
|
-
gem.description = %q{ }
|
12
|
-
gem.summary = %q{ }
|
11
|
+
gem.description = %q{ Kills pumas, the code kind }
|
12
|
+
gem.summary = %q{ If you have a memory leak in your web code puma_worker_killer can keep it in check. }
|
13
13
|
gem.homepage = "https://github.com/schneems/puma_worker_killer"
|
14
14
|
gem.license = "MIT"
|
15
15
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/server'
|
3
|
+
|
4
|
+
require 'puma_worker_killer'
|
5
|
+
|
6
|
+
PumaWorkerKiller.config do |config|
|
7
|
+
config.ram = Integer(ENV['PUMA_RAM']) if ENV['PUMA_RAM']
|
8
|
+
config.frequency = Integer(ENV['PUMA_FREQUENCY']) if ENV['PUMA_FREQUENCY']
|
9
|
+
end
|
10
|
+
PumaWorkerKiller.start
|
11
|
+
|
12
|
+
|
13
|
+
class HelloWorld
|
14
|
+
def response
|
15
|
+
[200, {}, ['Hello World']]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class HelloWorldApp
|
20
|
+
def self.call(env)
|
21
|
+
HelloWorld.new.response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
run HelloWorldApp
|
@@ -2,6 +2,18 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class PumaWorkerKillerTest < Test::Unit::TestCase
|
4
4
|
|
5
|
+
def test_starts
|
6
|
+
app_path = fixture_path.join("app.ru")
|
7
|
+
port = 0 # http://stackoverflow.com/questions/200484/how-do-you-find-a-free-tcp-server-port-using-ruby
|
8
|
+
puma_log = Pathname.new "puma.log"
|
9
|
+
`rm #{puma_log}; touch #{puma_log}`
|
10
|
+
pid = Process.spawn("PUMA_FREQUENCY=1 bundle exec puma #{app_path} -t 1:1 -w 5 --preload --debug -p #{port} > #{puma_log}")
|
11
|
+
sleep 5
|
12
|
+
assert_match "PumaWorkerKiller:", puma_log.read
|
13
|
+
ensure
|
14
|
+
Process.kill('TERM', pid) if pid
|
15
|
+
end
|
16
|
+
|
5
17
|
def test_worker_reaped
|
6
18
|
ram = 1 #mb
|
7
19
|
cluster = FakeCluster.new
|
@@ -25,18 +37,20 @@ class PumaWorkerKillerTest < Test::Unit::TestCase
|
|
25
37
|
end
|
26
38
|
|
27
39
|
def test_kills_memory_leak
|
28
|
-
ram = 75 #mb
|
40
|
+
ram = rand(75..100) #mb
|
29
41
|
cluster = FakeCluster.new
|
30
42
|
reaper = PumaWorkerKiller::Reaper.new(ram, cluster)
|
31
43
|
while reaper.get_total_memory < (ram * 0.80)
|
32
44
|
cluster.add_worker
|
45
|
+
sleep 0.01
|
33
46
|
end
|
34
47
|
|
35
48
|
reaper.reap
|
36
49
|
assert_equal 0, cluster.workers.select {|w| w.is_term? }.count
|
37
50
|
|
38
|
-
|
51
|
+
until reaper.get_total_memory > ram
|
39
52
|
cluster.add_worker
|
53
|
+
sleep 0.01
|
40
54
|
end
|
41
55
|
|
42
56
|
reaper.reap
|
data/test/test_helper.rb
CHANGED
@@ -3,6 +3,9 @@ Bundler.require
|
|
3
3
|
require 'puma_worker_killer'
|
4
4
|
require 'test/unit'
|
5
5
|
|
6
|
+
def fixture_path
|
7
|
+
Pathname.new(File.expand_path("../fixtures", __FILE__))
|
8
|
+
end
|
6
9
|
|
7
10
|
# Mock object stand in for Puma::Cluster
|
8
11
|
class FakeCluster
|
@@ -10,6 +13,9 @@ class FakeCluster
|
|
10
13
|
@workers = []
|
11
14
|
end
|
12
15
|
|
16
|
+
def wakeup!
|
17
|
+
end
|
18
|
+
|
13
19
|
class Worker
|
14
20
|
attr_accessor :pid
|
15
21
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma_worker_killer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Schneeman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: puma
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.1'
|
55
|
-
description: " "
|
55
|
+
description: " Kills pumas, the code kind "
|
56
56
|
email:
|
57
57
|
- richard.schneeman+rubygems@gmail.com
|
58
58
|
executables: []
|
@@ -66,9 +66,11 @@ files:
|
|
66
66
|
- Rakefile
|
67
67
|
- lib/puma_worker_killer.rb
|
68
68
|
- lib/puma_worker_killer/auto_reap.rb
|
69
|
+
- lib/puma_worker_killer/puma_memory.rb
|
69
70
|
- lib/puma_worker_killer/reaper.rb
|
70
71
|
- lib/puma_worker_killer/version.rb
|
71
72
|
- puma_worker_killer.gemspec
|
73
|
+
- test/fixtures/app.ru
|
72
74
|
- test/puma_worker_killer_test.rb
|
73
75
|
- test/test_helper.rb
|
74
76
|
homepage: https://github.com/schneems/puma_worker_killer
|
@@ -91,11 +93,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
93
|
version: '0'
|
92
94
|
requirements: []
|
93
95
|
rubyforge_project:
|
94
|
-
rubygems_version: 2.2.
|
96
|
+
rubygems_version: 2.2.2
|
95
97
|
signing_key:
|
96
98
|
specification_version: 4
|
97
|
-
summary:
|
99
|
+
summary: If you have a memory leak in your web code puma_worker_killer can keep it
|
100
|
+
in check.
|
98
101
|
test_files:
|
102
|
+
- test/fixtures/app.ru
|
99
103
|
- test/puma_worker_killer_test.rb
|
100
104
|
- test/test_helper.rb
|
101
|
-
has_rdoc:
|