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 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: