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 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: