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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce372755dae3f6e9abb64192488e0709ecf7af12
4
- data.tar.gz: 8c51e18768753c3716487379f217a431f4ae6f1e
3
+ metadata.gz: 52f6ead7a78bc887408c2a249b3aed913fe90129
4
+ data.tar.gz: 752ef55b46747f29aa84b92d84d0d72496fe315a
5
5
  SHA512:
6
- metadata.gz: 535cc32a6414aae221b2842a85bc5205d7043cc37699a3b1d488af5922b89688aebacce10276e025a9af9c0190f98c19f3ed55521ffc49c14fafcd532ac9b12e
7
- data.tar.gz: 0c025d47e64ddfe8589ee3145178cb9205adcc1248d2b494631e15b1fd5186178662c716cec2f8a2a126ae86addc757fc598b8e6c415a8cb0bfa795d14a6f4b0
6
+ metadata.gz: 2c12b64d3108e5c4921dba09eb832edbfc008edfeade7cae4ca77429aea1da1dfcbe7fe49f37ac3ac2b5048ebf947e555cc45b352ca6e0303acf8fa9ed38705c
7
+ data.tar.gz: 99ef751d60702d8ffd5d5eb83d8cb6fc22e5d4174d5fc3cba199bcec5b48dd46c3c80c7eb0682ab7708e484add4957d819656bb3d7742ceac30d59c3e92e14ea
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  Gemfile.lock
2
- *.gem
2
+ *.gem
3
+ puma.log
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).
@@ -21,6 +21,7 @@ module PumaWorkerKiller
21
21
  end
22
22
  end
23
23
 
24
+ require 'puma_worker_killer/puma_memory'
24
25
  require 'puma_worker_killer/reaper'
25
26
  require 'puma_worker_killer/auto_reap'
26
27
  require 'puma_worker_killer/version'
@@ -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 = self.get_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
- def get_master
9
- ObjectSpace.each_object(Puma::Cluster).map { |obj| obj }.first
10
- end
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 @master
35
- workers = get_workers
36
- total_memory = get_total_memory(workers)
37
- if workers.any? && total_memory > @max_ram
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 #{total_memory} mb with master and #{workers.count} workers"
19
+ @cluster.master.log "PumaWorkerKiller: Consuming #{total} mb with master and #{@cluster.workers.count} workers"
44
20
  end
45
21
  end
46
22
  end
@@ -1,3 +1,3 @@
1
1
  module PumaWorkerKiller
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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
- while reaper.get_total_memory < ram
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.1
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-14 00:00:00.000000000 Z
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.0
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: