passenger_reaper 0.0.1
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/.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:
|