zombie_passenger_killer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Zombies on a train](http://www.motifake.com/image/demotivational-poster/1002/zombies-on-a-train-zombies-oh-shi-demotivational-poster-1265174018.jpg)
|
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
|
+
|