zombie_passenger_killer 0.1.0
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.
- data/Gemfile +7 -0
- data/Gemfile.lock +26 -0
- data/Rakefile +18 -0
- data/Readme.md +42 -0
- data/VERSION +1 -0
- data/bin/zombie_passenger_killer +39 -0
- data/lib/zombie_passenger_killer.rb +67 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/zombie_passenger_killer_spec.rb +58 -0
- data/zombie_passenger_killer.gemspec +42 -0
- metadata +76 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
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
|
+
rake (0.9.2)
|
11
|
+
rspec (2.6.0)
|
12
|
+
rspec-core (~> 2.6.0)
|
13
|
+
rspec-expectations (~> 2.6.0)
|
14
|
+
rspec-mocks (~> 2.6.0)
|
15
|
+
rspec-core (2.6.4)
|
16
|
+
rspec-expectations (2.6.0)
|
17
|
+
diff-lcs (~> 1.1.2)
|
18
|
+
rspec-mocks (2.6.0)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
jeweler
|
25
|
+
rake
|
26
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
task :default do
|
2
|
+
sh "rspec spec/"
|
3
|
+
end
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = 'zombie_passenger_killer'
|
9
|
+
gem.summary = "Guaranteed zombie passengers death"
|
10
|
+
gem.email = "michael@grosser.it"
|
11
|
+
gem.homepage = "http://github.com/grosser/#{gem.name}"
|
12
|
+
gem.authors = ["Michael Grosser"]
|
13
|
+
end
|
14
|
+
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
|
18
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
Guaranteed zombie passengers death.
|
4
|
+
|
5
|
+
- no longer in passenger-status ?
|
6
|
+
- high CPU load over long period (Optional) ?
|
7
|
+
|
8
|
+
strace of killed zombies is printed, so debugging is easier.
|
9
|
+
|
10
|
+
(god/bluepill are not suited to monitor passenger apps because of ever-changing pids)
|
11
|
+
|
12
|
+
Add passenger-status to `/etc/sudoers` or run with sudo.
|
13
|
+
|
14
|
+
Install
|
15
|
+
=======
|
16
|
+
sudo gem install zombie_passenger_killer
|
17
|
+
|
18
|
+
Usage
|
19
|
+
=====
|
20
|
+
|
21
|
+
zombie_passenger_killer [options]
|
22
|
+
|
23
|
+
Options:
|
24
|
+
-m, --max [SIZE] Max high CPU entries in history before killing
|
25
|
+
--history [SIZE] History size
|
26
|
+
-c, --cpu [PERCENT] Mark as high CPU when above PERCENT
|
27
|
+
-g, --grace [SECONDS] Wait SECONDS before hard-killing (-9) a process
|
28
|
+
-i, --interval [SECONDS] Check every SECONDS
|
29
|
+
-p, --pattern [PATTERN] Find processes with this pattern
|
30
|
+
-h, --help Show this.
|
31
|
+
-v, --version Show Version
|
32
|
+
|
33
|
+
|
34
|
+
Author
|
35
|
+
======
|
36
|
+
|
37
|
+
###Contributors
|
38
|
+
- [mindreframer](https://github.com/mindreframer)
|
39
|
+
|
40
|
+
[Michael Grosser](http://grosser.it)<br/>
|
41
|
+
michael@grosser.it<br/>
|
42
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
6
|
+
require 'zombie_passenger_killer'
|
7
|
+
|
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
|
+
options = {}
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = <<BANNER
|
15
|
+
Guaranteed zombie passengers death
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
zombie_passenger_killer [options]
|
19
|
+
|
20
|
+
Options:
|
21
|
+
BANNER
|
22
|
+
opts.on("-m", "--max [SIZE]", Integer, "Max high CPU entries in history before killing") {|i| options[:max]=i }
|
23
|
+
opts.on("--history [SIZE]", Integer, "History size") {|i| options[:history]=i }
|
24
|
+
opts.on("-c", "--cpu [PERCENT]", Integer, "Mark as high CPU when above PERCENT") {|i| options[:cpu]=i }
|
25
|
+
opts.on("-g", "--grace [SECONDS]", Integer, "Wait SECONDS before hard-killing (-9) a process") {|i| options[:grace]=i }
|
26
|
+
opts.on("-i", "--interval [SECONDS]", Integer, "Check every SECONDS") {|i| options[:interval]=i }
|
27
|
+
opts.on("-p", "--pattern [PATTERN]", String, "Find processes with this pattern") {|i| options[:pattern]=i }
|
28
|
+
opts.on("-h", "--help","Show this.") { puts opts; exit }
|
29
|
+
opts.on('-v', '--version','Show Version'){ puts Smusher::VERSION; exit}
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
$stdout.sync = true
|
33
|
+
puts "Started at #{Time.now}"
|
34
|
+
|
35
|
+
killer = ZombiePassengerKiller.new(options)
|
36
|
+
|
37
|
+
loop do
|
38
|
+
killer.hunt_zombies
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class ZombiePassengerKiller
|
2
|
+
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
|
3
|
+
|
4
|
+
def initialize(options)
|
5
|
+
@history = {}
|
6
|
+
@history_entries = options[:history] || 5
|
7
|
+
@max_high_cpu = options[:max]
|
8
|
+
@high_cpu = options[:cpu] || 70
|
9
|
+
@grace_time = options[:grace] || 5
|
10
|
+
@interval = options[:interval] || 5
|
11
|
+
@pattern = options[:pattern] || ' Rack: '
|
12
|
+
end
|
13
|
+
|
14
|
+
def store_current_cpu(processes)
|
15
|
+
keys_to_remove = @history.keys - processes.map{|x| x[:pid] }
|
16
|
+
keys_to_remove.each{|k| !@history.delete k }
|
17
|
+
|
18
|
+
processes.each do |process|
|
19
|
+
@history[process[:pid]] ||= []
|
20
|
+
@history[process[:pid]] << process[:cpu]
|
21
|
+
@history[process[:pid]] = @history[process[:pid]].last(@history_entries)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_strace(pid, time)
|
26
|
+
%x(timeout #{time} strace -p #{pid} 2>&1) if system("which timeout > /dev/null")
|
27
|
+
end
|
28
|
+
|
29
|
+
def hunt_zombies
|
30
|
+
active_pids_in_passenger_status = passenger_pids
|
31
|
+
active_processes_in_processlist = process_status
|
32
|
+
zombies = active_processes_in_processlist.map{|x| x[:pid] } - active_pids_in_passenger_status
|
33
|
+
|
34
|
+
# kill processes with high CPU if user wants it
|
35
|
+
high_load = if @max_high_cpu
|
36
|
+
store_current_cpu active_processes_in_processlist
|
37
|
+
active_pids_in_passenger_status.select do |pid|
|
38
|
+
@history[pid].count{|x| x > @high_cpu } >= @max_high_cpu
|
39
|
+
end
|
40
|
+
else
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
44
|
+
(high_load + zombies).each do |pid|
|
45
|
+
kill_zombie pid
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def passenger_pids
|
50
|
+
%x(passenger-status|grep PID).split("\n").map{|x| x.strip.match(/PID: \d*/).to_s.split[1]}.map(&:to_i)
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_status
|
54
|
+
%x(ps -eo pid,pcpu,args|grep -v grep|grep '#{@pattern}').split("\n").map do |line|
|
55
|
+
values = line.strip.split[0..1]
|
56
|
+
{:pid => values.first.to_i, :cpu => values.last.to_f}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def kill_zombie(pid)
|
61
|
+
puts "Killing passenger process #{pid}"
|
62
|
+
puts get_strace(pid, 5)
|
63
|
+
puts %x(kill #{pid})
|
64
|
+
sleep @grace_time
|
65
|
+
%x(kill -9 #{pid})
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.expand_path('spec/spec_helper')
|
2
|
+
|
3
|
+
describe ZombiePassengerKiller do
|
4
|
+
let(:killer){
|
5
|
+
k = ZombiePassengerKiller.new(@options || {})
|
6
|
+
k.stub!(:passenger_pids).and_return([111])
|
7
|
+
k
|
8
|
+
}
|
9
|
+
|
10
|
+
it "has a VERSION" do
|
11
|
+
ZombiePassengerKiller::VERSION.should =~ /^\d+\.\d+\.\d+$/
|
12
|
+
end
|
13
|
+
|
14
|
+
it "does not kill anything by default" do
|
15
|
+
killer.should_not_receive(:kill_zombie)
|
16
|
+
killer.hunt_zombies
|
17
|
+
end
|
18
|
+
|
19
|
+
it "kill zombies" do
|
20
|
+
killer.stub!(:passenger_pids).and_return([123])
|
21
|
+
killer.stub!(:process_status).and_return([{:pid => 124, :cpu => 0}])
|
22
|
+
killer.should_receive(:kill_zombie).with(124)
|
23
|
+
killer.hunt_zombies
|
24
|
+
end
|
25
|
+
|
26
|
+
it "kills zombies with high cpu over max" do
|
27
|
+
@options = {:max => 1}
|
28
|
+
killer.stub!(:process_status).and_return([{:pid => 111, :cpu => 100}])
|
29
|
+
killer.should_receive(:kill_zombie).with(111)
|
30
|
+
killer.hunt_zombies
|
31
|
+
end
|
32
|
+
|
33
|
+
it "does not kills zombies with high cpu under max" do
|
34
|
+
@options = {:max => 2}
|
35
|
+
killer.stub!(:process_status).and_return([{:pid => 111, :cpu => 100}])
|
36
|
+
killer.should_not_receive(:kill_zombie).with(111)
|
37
|
+
killer.hunt_zombies
|
38
|
+
end
|
39
|
+
|
40
|
+
it "ignores high cpu levels in old history" do
|
41
|
+
@options = {:max => 2, :history => 2}
|
42
|
+
killer.should_not_receive(:kill_zombie).with(111)
|
43
|
+
killer.stub!(:process_status).and_return([{:pid => 111, :cpu => 100}])
|
44
|
+
killer.hunt_zombies
|
45
|
+
killer.stub!(:process_status).and_return([{:pid => 111, :cpu => 0}])
|
46
|
+
killer.hunt_zombies
|
47
|
+
killer.stub!(:process_status).and_return([{:pid => 111, :cpu => 100}])
|
48
|
+
killer.hunt_zombies
|
49
|
+
end
|
50
|
+
|
51
|
+
it "kills on high cpu levels in recent history" do
|
52
|
+
@options = {:max => 2, :history => 2}
|
53
|
+
killer.stub!(:process_status).and_return([{:pid => 111, :cpu => 100}])
|
54
|
+
killer.hunt_zombies
|
55
|
+
killer.should_receive(:kill_zombie).with(111)
|
56
|
+
killer.hunt_zombies
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{zombie_passenger_killer}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Grosser"]
|
12
|
+
s.date = %q{2011-09-08}
|
13
|
+
s.default_executable = %q{zombie_passenger_killer}
|
14
|
+
s.email = %q{michael@grosser.it}
|
15
|
+
s.executables = ["zombie_passenger_killer"]
|
16
|
+
s.files = [
|
17
|
+
"Gemfile",
|
18
|
+
"Gemfile.lock",
|
19
|
+
"Rakefile",
|
20
|
+
"Readme.md",
|
21
|
+
"VERSION",
|
22
|
+
"bin/zombie_passenger_killer",
|
23
|
+
"lib/zombie_passenger_killer.rb",
|
24
|
+
"spec/spec_helper.rb",
|
25
|
+
"spec/zombie_passenger_killer_spec.rb",
|
26
|
+
"zombie_passenger_killer.gemspec"
|
27
|
+
]
|
28
|
+
s.homepage = %q{http://github.com/grosser/zombie_passenger_killer}
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
s.rubygems_version = %q{1.6.2}
|
31
|
+
s.summary = %q{Guaranteed zombie passengers death}
|
32
|
+
|
33
|
+
if s.respond_to? :specification_version then
|
34
|
+
s.specification_version = 3
|
35
|
+
|
36
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
37
|
+
else
|
38
|
+
end
|
39
|
+
else
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zombie_passenger_killer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael Grosser
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-08 00:00:00 +02:00
|
19
|
+
default_executable: zombie_passenger_killer
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: michael@grosser.it
|
24
|
+
executables:
|
25
|
+
- zombie_passenger_killer
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- Gemfile
|
32
|
+
- Gemfile.lock
|
33
|
+
- Rakefile
|
34
|
+
- Readme.md
|
35
|
+
- VERSION
|
36
|
+
- bin/zombie_passenger_killer
|
37
|
+
- lib/zombie_passenger_killer.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
- spec/zombie_passenger_killer_spec.rb
|
40
|
+
- zombie_passenger_killer.gemspec
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/grosser/zombie_passenger_killer
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.6.2
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Guaranteed zombie passengers death
|
75
|
+
test_files: []
|
76
|
+
|