zombie_passenger_killer 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -2
- data/Gemfile.lock +6 -6
- data/Rakefile +15 -12
- data/Readme.md +3 -3
- data/bin/zombie_passenger_killer +2 -6
- data/lib/zombie_passenger_killer/reaper.rb +75 -0
- data/lib/zombie_passenger_killer/version.rb +3 -0
- data/lib/zombie_passenger_killer.rb +3 -70
- data/spec/zombie_passenger_killer_spec.rb +1 -1
- data/zombie_passenger_killer.gemspec +7 -36
- metadata +10 -9
- data/VERSION +0 -1
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
zombie_passenger_killer (0.2.0)
|
5
|
+
|
1
6
|
GEM
|
2
7
|
remote: http://rubygems.org/
|
3
8
|
specs:
|
4
9
|
diff-lcs (1.1.3)
|
5
|
-
git (1.2.5)
|
6
|
-
jeweler (1.6.4)
|
7
|
-
bundler (~> 1.0)
|
8
|
-
git (>= 1.2.5)
|
9
|
-
rake
|
10
10
|
rake (0.9.2)
|
11
11
|
rspec (2.6.0)
|
12
12
|
rspec-core (~> 2.6.0)
|
@@ -21,6 +21,6 @@ PLATFORMS
|
|
21
21
|
ruby
|
22
22
|
|
23
23
|
DEPENDENCIES
|
24
|
-
jeweler
|
25
24
|
rake
|
26
25
|
rspec (~> 2)
|
26
|
+
zombie_passenger_killer!
|
data/Rakefile
CHANGED
@@ -1,18 +1,21 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
1
3
|
task :default do
|
2
4
|
sh "rspec spec/"
|
3
5
|
end
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
rule /^version:bump:.*/ do |t|
|
8
|
+
sh "git status | grep 'nothing to commit'" # ensure we are not dirty
|
9
|
+
index = ['major', 'minor','patch'].index(t.name.split(':').last)
|
10
|
+
file = 'lib/zombie_passenger_killer/version.rb'
|
11
|
+
|
12
|
+
version_file = File.read(file)
|
13
|
+
old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
|
14
|
+
version_parts[index] = version_parts[index].to_i + 1
|
15
|
+
version_parts[2] = 0 if index < 2 # remove patch for minor
|
16
|
+
version_parts[1] = 0 if index < 1 # remove minor for major
|
17
|
+
new_version = version_parts * '.'
|
18
|
+
File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
|
14
19
|
|
15
|
-
|
16
|
-
rescue LoadError
|
17
|
-
puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
|
20
|
+
sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
|
18
21
|
end
|
data/Readme.md
CHANGED
@@ -43,14 +43,14 @@ Usage
|
|
43
43
|
|
44
44
|
### God script
|
45
45
|
|
46
|
-
TODO
|
47
|
-
|
46
|
+
# TODO
|
48
47
|
|
49
48
|
Author
|
50
49
|
======
|
51
50
|
|
52
51
|
###Contributors
|
53
|
-
- [
|
52
|
+
- [Roman Heinrich](https://github.com/mindreframer)
|
53
|
+
- [Kevin Mullin](https://github.com/kmullin)
|
54
54
|
|
55
55
|
[Michael Grosser](http://grosser.it)<br/>
|
56
56
|
michael@grosser.it<br/>
|
data/bin/zombie_passenger_killer
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'rubygems'
|
3
2
|
require 'optparse'
|
4
3
|
|
5
4
|
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
6
5
|
require 'zombie_passenger_killer'
|
7
6
|
|
8
|
-
unless system("which timeout > /dev/null")
|
9
|
-
warn "Please install timeout commandline tool e.g. via apt-get install timeout / apt-get install coreutils"
|
10
|
-
end
|
11
|
-
|
12
7
|
options = {}
|
13
8
|
OptionParser.new do |opts|
|
14
9
|
opts.banner = <<BANNER
|
@@ -32,8 +27,9 @@ end.parse!
|
|
32
27
|
$stdout.sync = true
|
33
28
|
puts "Started at #{Time.now}"
|
34
29
|
|
35
|
-
killer = ZombiePassengerKiller.new(options)
|
30
|
+
killer = ZombiePassengerKiller::Reaper.new(options)
|
36
31
|
|
32
|
+
$0 = File.basename(__FILE__)
|
37
33
|
loop do
|
38
34
|
killer.hunt_zombies
|
39
35
|
sleep options[:interval] || 10
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ZombiePassengerKiller
|
2
|
+
class Reaper
|
3
|
+
|
4
|
+
attr_accessor :out # overwriteable for tests
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@history = {}
|
8
|
+
@history_entries = options[:history] || 5
|
9
|
+
@max_high_cpu = options[:max]
|
10
|
+
@high_cpu = options[:cpu] || 70
|
11
|
+
@grace_time = options[:grace] || 5
|
12
|
+
@pattern = options[:pattern] || ' Rack: '
|
13
|
+
@strace_time = 5
|
14
|
+
@out = STDOUT
|
15
|
+
end
|
16
|
+
|
17
|
+
def store_current_cpu(processes)
|
18
|
+
keys_to_remove = @history.keys - processes.map{|x| x[:pid] }
|
19
|
+
keys_to_remove.each{|k| !@history.delete k }
|
20
|
+
|
21
|
+
processes.each do |process|
|
22
|
+
@history[process[:pid]] ||= []
|
23
|
+
@history[process[:pid]] << process[:cpu]
|
24
|
+
@history[process[:pid]] = @history[process[:pid]].last(@history_entries)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_strace(pid, time)
|
29
|
+
Process.getpgid(pid) rescue return 'No such process'
|
30
|
+
`( strace -p #{pid} 2>&1 ) & sleep #{time} ; kill $! 2>&1`
|
31
|
+
end
|
32
|
+
|
33
|
+
def hunt_zombies
|
34
|
+
active_pids_in_passenger_status = passenger_pids
|
35
|
+
active_processes_in_processlist = process_status
|
36
|
+
zombies = active_processes_in_processlist.map{|x| x[:pid] } - active_pids_in_passenger_status rescue Array.new
|
37
|
+
|
38
|
+
# kill processes with high CPU if user wants it
|
39
|
+
high_load = if @max_high_cpu
|
40
|
+
store_current_cpu active_processes_in_processlist
|
41
|
+
active_pids_in_passenger_status.select do |pid|
|
42
|
+
@history[pid].count{|x| x > @high_cpu } >= @max_high_cpu
|
43
|
+
end
|
44
|
+
else
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
|
48
|
+
(high_load + zombies).each do |pid|
|
49
|
+
kill_zombie pid
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# return array of pids reported from passenger-status command, nil if passenger-status doesn't run
|
54
|
+
def passenger_pids
|
55
|
+
pids = %x(passenger-status|grep PID).split("\n").map { |x| x.strip.match(/PID: \d*/).to_s.split[1].to_i }
|
56
|
+
pids if $?.exitstatus.zero?
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_status
|
60
|
+
%x(ps -eo pid,pcpu,args|grep -v grep|grep '#{@pattern}').split("\n").map do |line|
|
61
|
+
values = line.strip.split[0..1]
|
62
|
+
{:pid => values.first.to_i, :cpu => values.last.to_f}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def kill_zombie(pid)
|
67
|
+
@out.puts "Killing passenger process #{pid}"
|
68
|
+
@out.puts get_strace(pid, @strace_time)
|
69
|
+
Process.kill('TERM', pid) rescue nil
|
70
|
+
sleep @grace_time # wait for it to die
|
71
|
+
Process.kill('KILL', pid) rescue nil
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -1,71 +1,4 @@
|
|
1
|
-
|
2
|
-
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(options)
|
7
|
-
@history = {}
|
8
|
-
@history_entries = options[:history] || 5
|
9
|
-
@max_high_cpu = options[:max]
|
10
|
-
@high_cpu = options[:cpu] || 70
|
11
|
-
@grace_time = options[:grace] || 5
|
12
|
-
@pattern = options[:pattern] || ' Rack: '
|
13
|
-
@strace_time = 5
|
14
|
-
@out = STDOUT
|
15
|
-
end
|
16
|
-
|
17
|
-
def store_current_cpu(processes)
|
18
|
-
keys_to_remove = @history.keys - processes.map{|x| x[:pid] }
|
19
|
-
keys_to_remove.each{|k| !@history.delete k }
|
20
|
-
|
21
|
-
processes.each do |process|
|
22
|
-
@history[process[:pid]] ||= []
|
23
|
-
@history[process[:pid]] << process[:cpu]
|
24
|
-
@history[process[:pid]] = @history[process[:pid]].last(@history_entries)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def get_strace(pid, time)
|
29
|
-
Process.getpgid(pid) rescue return 'No such process'
|
30
|
-
`( strace -p #{pid} 2>&1 ) & sleep #{time} ; kill $! 2>&1`
|
31
|
-
end
|
32
|
-
|
33
|
-
def hunt_zombies
|
34
|
-
active_pids_in_passenger_status = passenger_pids
|
35
|
-
active_processes_in_processlist = process_status
|
36
|
-
zombies = active_processes_in_processlist.map{|x| x[:pid] } - active_pids_in_passenger_status
|
37
|
-
|
38
|
-
# kill processes with high CPU if user wants it
|
39
|
-
high_load = if @max_high_cpu
|
40
|
-
store_current_cpu active_processes_in_processlist
|
41
|
-
active_pids_in_passenger_status.select do |pid|
|
42
|
-
@history[pid].count{|x| x > @high_cpu } >= @max_high_cpu
|
43
|
-
end
|
44
|
-
else
|
45
|
-
[]
|
46
|
-
end
|
47
|
-
|
48
|
-
(high_load + zombies).each do |pid|
|
49
|
-
kill_zombie pid
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def passenger_pids
|
54
|
-
%x(passenger-status|grep PID).split("\n").map{|x| x.strip.match(/PID: \d*/).to_s.split[1]}.map(&:to_i)
|
55
|
-
end
|
56
|
-
|
57
|
-
def process_status
|
58
|
-
%x(ps -eo pid,pcpu,args|grep -v grep|grep '#{@pattern}').split("\n").map do |line|
|
59
|
-
values = line.strip.split[0..1]
|
60
|
-
{:pid => values.first.to_i, :cpu => values.last.to_f}
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def kill_zombie(pid)
|
65
|
-
@out.puts "Killing passenger process #{pid}"
|
66
|
-
@out.puts get_strace(pid, @strace_time)
|
67
|
-
Process.kill('TERM', pid) rescue nil
|
68
|
-
sleep @grace_time
|
69
|
-
Process.kill('KILL', pid) rescue nil
|
70
|
-
end
|
71
|
-
end
|
3
|
+
require 'zombie_passenger_killer/version'
|
4
|
+
require 'zombie_passenger_killer/reaper'
|
@@ -1,41 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
-
# -*- encoding: utf-8 -*-
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
require 'zombie_passenger_killer/version'
|
5
3
|
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.
|
8
|
-
s.version = "0.1.3"
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
Gem::Specification.new "zombie_passenger_killer", ZombiePassengerKiller::VERSION do |s|
|
5
|
+
s.summary = "Guaranteed zombie passengers death"
|
11
6
|
s.authors = ["Michael Grosser"]
|
12
|
-
s.date = "2012-01-05"
|
13
7
|
s.email = "michael@grosser.it"
|
14
|
-
s.executables = ["zombie_passenger_killer"]
|
15
|
-
s.files = [
|
16
|
-
"Gemfile",
|
17
|
-
"Gemfile.lock",
|
18
|
-
"Rakefile",
|
19
|
-
"Readme.md",
|
20
|
-
"VERSION",
|
21
|
-
"bin/zombie_passenger_killer",
|
22
|
-
"lib/zombie_passenger_killer.rb",
|
23
|
-
"spec/spec_helper.rb",
|
24
|
-
"spec/zombie_passenger_killer_spec.rb",
|
25
|
-
"zombie_passenger_killer.gemspec"
|
26
|
-
]
|
27
8
|
s.homepage = "http://github.com/grosser/zombie_passenger_killer"
|
28
|
-
s.
|
29
|
-
s.
|
30
|
-
s.
|
31
|
-
|
32
|
-
if s.respond_to? :specification_version then
|
33
|
-
s.specification_version = 3
|
34
|
-
|
35
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
36
|
-
else
|
37
|
-
end
|
38
|
-
else
|
39
|
-
end
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.executables = ["zombie_passenger_killer"]
|
11
|
+
s.license = "MIT"
|
40
12
|
end
|
41
|
-
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zombie_passenger_killer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Michael Grosser
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-02-09 00:00:00 Z
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description:
|
@@ -31,15 +31,16 @@ files:
|
|
31
31
|
- Gemfile.lock
|
32
32
|
- Rakefile
|
33
33
|
- Readme.md
|
34
|
-
- VERSION
|
35
34
|
- bin/zombie_passenger_killer
|
36
35
|
- lib/zombie_passenger_killer.rb
|
36
|
+
- lib/zombie_passenger_killer/reaper.rb
|
37
|
+
- lib/zombie_passenger_killer/version.rb
|
37
38
|
- spec/spec_helper.rb
|
38
39
|
- spec/zombie_passenger_killer_spec.rb
|
39
40
|
- zombie_passenger_killer.gemspec
|
40
41
|
homepage: http://github.com/grosser/zombie_passenger_killer
|
41
|
-
licenses:
|
42
|
-
|
42
|
+
licenses:
|
43
|
+
- MIT
|
43
44
|
post_install_message:
|
44
45
|
rdoc_options: []
|
45
46
|
|
@@ -66,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
67
|
requirements: []
|
67
68
|
|
68
69
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.8.
|
70
|
+
rubygems_version: 1.8.15
|
70
71
|
signing_key:
|
71
72
|
specification_version: 3
|
72
73
|
summary: Guaranteed zombie passengers death
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.1.3
|