passenger_reaper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in passenger_reaper.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
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,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new do |spec|
6
+ spec.pattern = 'spec/**/*_spec.rb'
7
+ spec.rspec_opts = ['--backtrace']
8
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'passenger_reaper'
4
+ PassengerReaper::Runner.run(ARGV)
@@ -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,3 @@
1
+ module PassengerReaper
2
+ VERSION = "0.0.1"
3
+ 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
@@ -0,0 +1,2 @@
1
+ #require 'minitest/autorun'
2
+ require 'passenger_reaper'
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: