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 ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ group :dev do # not development <-> would add unneeded development dependencies in gemspec
4
+ gem 'rake'
5
+ gem 'rspec', '~>2'
6
+ gem 'jeweler'
7
+ end
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
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'zombie_passenger_killer'
@@ -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
+