puma_worker_killer 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|