passenger_reaper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +6 -0
- data/README.md +24 -0
- data/Rakefile +8 -0
- data/bin/passenger_reaper +4 -0
- data/lib/passenger_reaper.rb +45 -0
- data/lib/passenger_reaper/passenger_process.rb +129 -0
- data/lib/passenger_reaper/ps_etime.rb +19 -0
- data/lib/passenger_reaper/version.rb +3 -0
- data/passenger_reaper.gemspec +24 -0
- data/spec/passenger_reaper_spec.rb +40 -0
- data/spec/test_helper.rb +2 -0
- metadata +83 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Passenger Reaper
|
2
|
+
|
3
|
+
Gemification of https://gist.github.com/596401/db750535df61e679aad69e8d9c9750f8640a234f
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
#> gem install passenger_reaper
|
8
|
+
#> passenger_reaper
|
9
|
+
|
10
|
+
Error: please use the following syntax:
|
11
|
+
|
12
|
+
passenger_reaper <command> <options>
|
13
|
+
|
14
|
+
Commands:
|
15
|
+
|
16
|
+
status displays the number of total passenger workers and the number of active workers
|
17
|
+
active kills stale workers that passengers has in the pool
|
18
|
+
inactive kills workers that passenger no longer controls
|
19
|
+
debug shows the last log entry from each inactive worker
|
20
|
+
|
21
|
+
Options:
|
22
|
+
|
23
|
+
--noop don't actually kill processes but show which ones would have been killed
|
24
|
+
--hard send the KILL signal
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "passenger_reaper/version"
|
2
|
+
require "passenger_reaper/ps_etime"
|
3
|
+
require "passenger_reaper/passenger_process"
|
4
|
+
|
5
|
+
module PassengerReaper
|
6
|
+
MINIMUM_ETIME = 600 # 10 minutes
|
7
|
+
|
8
|
+
class Runner
|
9
|
+
def self.run(args)
|
10
|
+
if %W(active inactive debug status).include?(ARGV.first)
|
11
|
+
case ARGV.first
|
12
|
+
when 'active'
|
13
|
+
PassengerProcess.kill_stale_passengers
|
14
|
+
when 'inactive'
|
15
|
+
PassengerProcess.kill_inactive_passengers
|
16
|
+
when 'debug'
|
17
|
+
PassengerProcess.inactive_passengers_last_log_entry
|
18
|
+
when 'status'
|
19
|
+
puts "Total of passengers: #{PassengerProcess.all_passenger_pids.count}"
|
20
|
+
puts "Stale passengers: #{PassengerProcess.stale.count}"
|
21
|
+
end
|
22
|
+
else
|
23
|
+
help =<<-EOF
|
24
|
+
Error: please use the following syntax:
|
25
|
+
|
26
|
+
passenger_reaper <command> <options>
|
27
|
+
|
28
|
+
Commands:
|
29
|
+
|
30
|
+
status displays the number of total passenger workers and the number of active workers
|
31
|
+
active kills stale workers that passengers has in the pool
|
32
|
+
inactive kills workers that passenger no longer controls
|
33
|
+
debug shows the last log entry from each inactive worker
|
34
|
+
|
35
|
+
Options:
|
36
|
+
|
37
|
+
--noop don't actually kill processes but show which ones would have been killed
|
38
|
+
--hard send the KILL signal
|
39
|
+
EOF
|
40
|
+
puts help
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
|
3
|
+
module PassengerReaper
|
4
|
+
class PassengerProcess
|
5
|
+
attr_accessor :pid, :uptime
|
6
|
+
|
7
|
+
def initialize(passenger_status_line)
|
8
|
+
values = passenger_status_line.match(/PID:\s(\d*).*Uptime:\s*(.*)$/)
|
9
|
+
@pid = values[1].to_i
|
10
|
+
@uptime = values[2]
|
11
|
+
end
|
12
|
+
|
13
|
+
def uptime_in_seconds
|
14
|
+
uptime_values = uptime.split(/\s/).map { |u| u.to_i }
|
15
|
+
seconds = uptime_values[-1] || 0
|
16
|
+
minutes = uptime_values[-2] || 0
|
17
|
+
hours = uptime_values[-3] || 0
|
18
|
+
seconds + (minutes*60) + (hours*3600)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def self.passenger_status
|
23
|
+
@passenger_status ||= `passenger-status | grep PID`
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.active
|
27
|
+
passengers = []
|
28
|
+
passenger_status.each_line do |line|
|
29
|
+
passengers << PassengerProcess.new(line)
|
30
|
+
end
|
31
|
+
passengers
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.stale
|
35
|
+
stale_passengers = []
|
36
|
+
potentially_stale_processes = active.select { |p| p.uptime_in_seconds > 600 }
|
37
|
+
potentially_stale_processes.each do |process|
|
38
|
+
process_last_log_entry = last_log_entry(process.pid)
|
39
|
+
etime = (Time.now - parse_time_from_log_entry(process_last_log_entry)).to_i
|
40
|
+
if etime > 600
|
41
|
+
puts "Stale process last log entry: #{process_last_log_entry}" if debug?
|
42
|
+
stale_passengers << process
|
43
|
+
end
|
44
|
+
end
|
45
|
+
stale_passengers
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.last_log_entry(pid)
|
49
|
+
pwd = `pwd`.chomp
|
50
|
+
`grep 'rails\\[#{pid}\\]' #{pwd}/log/production.log | tail -n 1`.chomp
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.last_log_entry_time(pid)
|
54
|
+
log_entry = last_log_entry(pid)
|
55
|
+
log_entry_time = parse_time_from_log_entry(log_entry)
|
56
|
+
(Time.now - log_entry_time).to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.parse_time_from_log_entry(entry)
|
60
|
+
Chronic.parse(entry.match(/.*\s.*\s\d{1,2}:\d{1,2}:\d{1,2}/)[0])
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.active_passenger_pids
|
64
|
+
active.map{ |p| p.pid }
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.passenger_memory_stats
|
68
|
+
# 17630 287.0 MB 64.5 MB Rails: /var/www/apps/gldl/current
|
69
|
+
# 17761 285.9 MB 64.9 MB Rails: /var/www/apps/gldl/current
|
70
|
+
# 18242 293.1 MB 71.4 MB Rails: /var/www/apps/gldl/current
|
71
|
+
# 18255 285.9 MB 60.6 MB Rails: /var/www/apps/gldl/current
|
72
|
+
@passenger_memory_stats ||= `passenger-memory-stats | grep 'Rails:.*\/current'`
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.all_passenger_pids
|
76
|
+
passengers = []
|
77
|
+
passenger_memory_stats.each_line do |line|
|
78
|
+
matcher = line.match(/\s?(\d*)\s/)
|
79
|
+
passengers << matcher[1] if matcher
|
80
|
+
end
|
81
|
+
passengers
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.inactive_passenger_pids
|
85
|
+
inactive_passenger_pids = []
|
86
|
+
(all_passenger_pids - active_passenger_pids).each do |pid|
|
87
|
+
raw_etime = `ps -p #{pid} --no-headers -o etime`.chomp
|
88
|
+
etime = PsEtime.new(raw_etime)
|
89
|
+
inactive_passenger_pids << pid unless (etime.age_in_seconds < (MINIMUM_ETIME || 600))
|
90
|
+
end
|
91
|
+
inactive_passenger_pids
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.kill_inactive_passengers
|
95
|
+
inactive_passenger_pids.each do |pid|
|
96
|
+
kill(pid)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.kill_stale_passengers
|
101
|
+
stale.each do |p|
|
102
|
+
kill(p.pid)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.kill(pid)
|
107
|
+
signal = ARGV.include?('--hard') ? 9 : 15
|
108
|
+
command = "kill -#{signal} #{pid}"
|
109
|
+
puts command
|
110
|
+
`#{command}` unless noop?
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.inactive_passengers_last_log_entry
|
114
|
+
inactive_passenger_pids.each do |pid|
|
115
|
+
puts last_log_entry(pid)[0, 150]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def self.noop?
|
122
|
+
@noop ||= ARGV.include?('--noop')
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.debug?
|
126
|
+
@noop ||= ARGV.include?('--debug')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PassengerReaper
|
2
|
+
class PsEtime
|
3
|
+
attr_accessor :seconds, :minutes, :hours, :days
|
4
|
+
attr_reader :etime
|
5
|
+
|
6
|
+
def initialize(raw_etime)
|
7
|
+
@etime = raw_etime
|
8
|
+
split_etime = @etime.split(/\-|\:/).map {|a| a.to_i}
|
9
|
+
@seconds = split_etime[-1] || 0
|
10
|
+
@minutes = split_etime[-2] || 0
|
11
|
+
@hours = split_etime[-3] || 0
|
12
|
+
@days = split_etime[-4] || 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def age_in_seconds
|
16
|
+
@seconds + (@minutes*60) + (@hours*3600) + (@days*86400)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "passenger_reaper/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "passenger_reaper"
|
7
|
+
s.version = PassengerReaper::VERSION
|
8
|
+
s.authors = ["Josh Hull"]
|
9
|
+
s.email = ["joshbuddy@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Gemification of https://gist.github.com/596401/db750535df61e679aad69e8d9c9750f8640a234f}
|
12
|
+
s.description = %q{Gemification of https://gist.github.com/596401/db750535df61e679aad69e8d9c9750f8640a234f.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "passenger_reaper"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_runtime_dependency "chronic"
|
23
|
+
s.add_development_dependency "rspec", ">= 2.0.0"
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe PassengerReaper::PassengerProcess do
|
4
|
+
before do
|
5
|
+
@passenger_status =<<EOF
|
6
|
+
PID: 14548 Sessions: 0 Processed: 42 Uptime: 40s
|
7
|
+
PID: 14521 Sessions: 0 Processed: 29 Uptime: 1h 20m 43s
|
8
|
+
PID: 14525 Sessions: 0 Processed: 32 Uptime: 20m 5s
|
9
|
+
PID: 14007 Sessions: 0 Processed: 36 Uptime: 1m 47s
|
10
|
+
PID: 14830 Sessions: 1 Processed: 1 Uptime: 5s
|
11
|
+
EOF
|
12
|
+
PassengerReaper::PassengerProcess.stub!(:passenger_status).and_return(@passenger_status)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return active passenger processes" do
|
16
|
+
all_passenger_processes = PassengerReaper::PassengerProcess.active
|
17
|
+
all_passenger_processes.count.should eql(5)
|
18
|
+
passenger = all_passenger_processes.first
|
19
|
+
passenger.pid.should eql(14548)
|
20
|
+
passenger.uptime.should eql('40s')
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should parse the uptime into seconds" do
|
24
|
+
all_passenger_processes = PassengerReaper::PassengerProcess.active
|
25
|
+
all_passenger_processes[0].uptime_in_seconds.should eql(40)
|
26
|
+
all_passenger_processes[4].uptime_in_seconds.should eql(5)
|
27
|
+
all_passenger_processes[3].uptime_in_seconds.should eql(107)
|
28
|
+
all_passenger_processes[1].uptime_in_seconds.should eql(4843)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return the stale passengers" do
|
32
|
+
stub_time = Chronic.parse('Sep 26 17:40:34')
|
33
|
+
Time.stub!(:now).and_return(stub_time)
|
34
|
+
PassengerReaper::PassengerProcess.stub!(:last_log_entry).with(14521).and_return('Sep 26 16:40:34 web1 rails[14521]: Rendering promotions/index')
|
35
|
+
PassengerReaper::PassengerProcess.stub!(:last_log_entry).with(14525).and_return('Sep 26 17:39:34 web1 rails[14525]: Rendering promotions/index')
|
36
|
+
stale_passengers = PassengerReaper::PassengerProcess.stale
|
37
|
+
stale_passengers.count.should eql(1)
|
38
|
+
stale_passengers[0].pid.should eql(14521)
|
39
|
+
end
|
40
|
+
end
|
data/spec/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: passenger_reaper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Hull
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chronic
|
16
|
+
requirement: &70248854448020 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70248854448020
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70248854447440 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70248854447440
|
36
|
+
description: Gemification of https://gist.github.com/596401/db750535df61e679aad69e8d9c9750f8640a234f.
|
37
|
+
email:
|
38
|
+
- joshbuddy@gmail.com
|
39
|
+
executables:
|
40
|
+
- passenger_reaper
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- bin/passenger_reaper
|
49
|
+
- lib/passenger_reaper.rb
|
50
|
+
- lib/passenger_reaper/passenger_process.rb
|
51
|
+
- lib/passenger_reaper/ps_etime.rb
|
52
|
+
- lib/passenger_reaper/version.rb
|
53
|
+
- passenger_reaper.gemspec
|
54
|
+
- spec/passenger_reaper_spec.rb
|
55
|
+
- spec/test_helper.rb
|
56
|
+
homepage: ''
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: passenger_reaper
|
76
|
+
rubygems_version: 1.8.15
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Gemification of https://gist.github.com/596401/db750535df61e679aad69e8d9c9750f8640a234f
|
80
|
+
test_files:
|
81
|
+
- spec/passenger_reaper_spec.rb
|
82
|
+
- spec/test_helper.rb
|
83
|
+
has_rdoc:
|