bluepill 0.0.60 → 0.0.61
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +32 -5
- data/bluepill.gemspec +1 -1
- data/lib/bluepill/application.rb +17 -6
- data/lib/bluepill/condition_watch.rb +2 -1
- data/lib/bluepill/controller.rb +3 -2
- data/lib/bluepill/process.rb +34 -5
- data/lib/bluepill/process_conditions/always_true.rb +2 -2
- data/lib/bluepill/process_conditions/cpu_usage.rb +3 -3
- data/lib/bluepill/process_conditions/http.rb +1 -1
- data/lib/bluepill/process_conditions/mem_usage.rb +3 -3
- data/lib/bluepill/process_conditions/process_condition.rb +2 -2
- data/lib/bluepill/process_journal.rb +207 -0
- data/lib/bluepill/system.rb +31 -7
- data/lib/bluepill/version.rb +1 -1
- metadata +81 -28
data/README.md
CHANGED
@@ -149,6 +149,17 @@ You can also set an app-wide uid/gid:
|
|
149
149
|
end
|
150
150
|
```
|
151
151
|
|
152
|
+
To track resources of child processes, use :include_children:
|
153
|
+
```ruby
|
154
|
+
Bluepill.application("app_name") do |app|
|
155
|
+
app.process("process_name") do |process|
|
156
|
+
process.start_command = "/usr/bin/some_start_command"
|
157
|
+
process.pid_file = "/tmp/some_pid_file.pid"
|
158
|
+
process.checks :mem_usage, :every => 1.seconds, :below => 5.megabytes, :times => [3,5], :include_children => true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
152
163
|
To check for flapping:
|
153
164
|
|
154
165
|
```ruby
|
@@ -254,23 +265,39 @@ We recommend that you _not_ do that and instead use the config options to captur
|
|
254
265
|
The main benefit of using the config options is that Bluepill will be able to monitor the correct process instead of just watching the shell that spawned your actual server.
|
255
266
|
|
256
267
|
### CLI
|
257
|
-
|
268
|
+
|
269
|
+
#### Usage
|
270
|
+
|
271
|
+
bluepill [app_name] command [options]
|
272
|
+
|
273
|
+
For the "load" command, the _app_name_ is specified in the config file, and
|
274
|
+
must not be provided on the command line.
|
275
|
+
|
276
|
+
For all other commands, the _app_name_ is optional if there is only
|
277
|
+
one bluepill daemon running. Otherwise, the _app_name_ must be
|
278
|
+
provided, because the command will fail when there are multiple
|
279
|
+
bluepill daemons running. The example commands below leaves out the
|
280
|
+
_app_name_.
|
281
|
+
|
282
|
+
#### Commands
|
283
|
+
|
284
|
+
To start a bluepill daemon and load the config for an application:
|
258
285
|
|
259
286
|
sudo bluepill load /path/to/production.pill
|
260
287
|
|
261
|
-
To act on a process or group:
|
288
|
+
To act on a process or group for an application:
|
262
289
|
|
263
290
|
sudo bluepill <start|stop|restart|unmonitor> <process_or_group_name>
|
264
291
|
|
265
|
-
To view process statuses:
|
292
|
+
To view process statuses for an application:
|
266
293
|
|
267
294
|
sudo bluepill status
|
268
295
|
|
269
|
-
To view the log for a process or group:
|
296
|
+
To view the log for a process or group for an application:
|
270
297
|
|
271
298
|
sudo bluepill log <process_or_group_name>
|
272
299
|
|
273
|
-
To quit bluepill:
|
300
|
+
To quit the bluepill daemon for an application:
|
274
301
|
|
275
302
|
sudo bluepill quit
|
276
303
|
|
data/bluepill.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.summary = %q{A process monitor written in Ruby with stability and minimalism in mind.}
|
15
15
|
s.description = %q{Bluepill keeps your daemons up while taking up as little resources as possible. After all you probably want the resources of your server to be used by whatever daemons you are running rather than the thing that's supposed to make sure they are brought back up, should they die or misbehave.}
|
16
16
|
|
17
|
-
s.add_dependency 'daemons', ['~> 1.1.4'
|
17
|
+
s.add_dependency 'daemons', ['~> 1.1.4']
|
18
18
|
s.add_dependency 'state_machine', '~> 1.1.0'
|
19
19
|
s.add_dependency 'activesupport', '>= 3.0.0'
|
20
20
|
s.add_dependency 'i18n', '>= 0.5.0'
|
data/lib/bluepill/application.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require 'thread'
|
3
|
+
require 'bluepill/system'
|
4
|
+
require 'bluepill/process_journal'
|
3
5
|
|
4
6
|
module Bluepill
|
5
7
|
class Application
|
6
8
|
PROCESS_COMMANDS = [:start, :stop, :restart, :unmonitor, :status]
|
7
9
|
|
8
|
-
attr_accessor :name, :logger, :base_dir, :socket, :pid_file
|
10
|
+
attr_accessor :name, :logger, :base_dir, :socket, :pid_file, :kill_timeout
|
9
11
|
attr_accessor :groups, :work_queue
|
10
12
|
attr_accessor :pids_dir, :log_file
|
11
13
|
|
@@ -17,10 +19,11 @@ module Bluepill
|
|
17
19
|
self.base_dir = options[:base_dir] || '/var/run/bluepill'
|
18
20
|
self.pid_file = File.join(self.base_dir, 'pids', self.name + ".pid")
|
19
21
|
self.pids_dir = File.join(self.base_dir, 'pids', self.name)
|
22
|
+
self.kill_timeout = options[:kill_timeout] || 10
|
20
23
|
|
21
24
|
self.groups = {}
|
22
25
|
|
23
|
-
self.logger = Bluepill::Logger.new(:log_file => self.log_file, :stdout => foreground?).prefix_with(self.name)
|
26
|
+
self.logger = ProcessJournal.logger = Bluepill::Logger.new(:log_file => self.log_file, :stdout => foreground?).prefix_with(self.name)
|
24
27
|
|
25
28
|
self.setup_signal_traps
|
26
29
|
self.setup_pids_dir
|
@@ -115,6 +118,9 @@ module Bluepill
|
|
115
118
|
|
116
119
|
def start_server
|
117
120
|
self.kill_previous_bluepill
|
121
|
+
ProcessJournal.kill_all_from_all_journals
|
122
|
+
ProcessJournal.clear_all_atomic_fs_locks
|
123
|
+
::Process.setpgid(0, 0)
|
118
124
|
|
119
125
|
Daemonize.daemonize unless foreground?
|
120
126
|
|
@@ -141,17 +147,22 @@ module Bluepill
|
|
141
147
|
end
|
142
148
|
sleep 1
|
143
149
|
end
|
144
|
-
cleanup
|
145
150
|
end
|
146
151
|
|
147
152
|
def cleanup
|
148
|
-
|
149
|
-
|
153
|
+
ProcessJournal.kill_all_from_all_journals
|
154
|
+
ProcessJournal.clear_all_atomic_fs_locks
|
155
|
+
begin
|
156
|
+
System.delete_if_exists(self.socket.path) if self.socket
|
157
|
+
rescue IOError
|
158
|
+
end
|
159
|
+
System.delete_if_exists(self.pid_file)
|
150
160
|
end
|
151
161
|
|
152
162
|
def setup_signal_traps
|
153
163
|
terminator = Proc.new do
|
154
164
|
puts "Terminating..."
|
165
|
+
cleanup
|
155
166
|
@running = false
|
156
167
|
end
|
157
168
|
|
@@ -183,7 +194,7 @@ module Bluepill
|
|
183
194
|
$stderr.puts "#{e.class}: #{e.message}"
|
184
195
|
exit(4) unless e.is_a?(Errno::ESRCH)
|
185
196
|
else
|
186
|
-
|
197
|
+
kill_timeout.times do |i|
|
187
198
|
sleep 0.5
|
188
199
|
break unless System.pid_alive?(previous_pid)
|
189
200
|
end
|
@@ -15,6 +15,7 @@ module Bluepill
|
|
15
15
|
@every = options.delete(:every)
|
16
16
|
@times = options.delete(:times) || [1,1]
|
17
17
|
@times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
|
18
|
+
@include_children = options.delete(:include_children) || false
|
18
19
|
|
19
20
|
self.clear_history!
|
20
21
|
|
@@ -25,7 +26,7 @@ module Bluepill
|
|
25
26
|
if @last_ran_at.nil? || (@last_ran_at + @every) <= tick_number
|
26
27
|
@last_ran_at = tick_number
|
27
28
|
|
28
|
-
value = @process_condition.run(pid)
|
29
|
+
value = @process_condition.run(pid, @include_children)
|
29
30
|
@history << HistoryValue.new(@process_condition.format_value(value), @process_condition.check(value))
|
30
31
|
self.logger.info(self.to_s)
|
31
32
|
|
data/lib/bluepill/controller.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require 'fileutils'
|
3
|
+
require 'bluepill/system'
|
3
4
|
|
4
5
|
module Bluepill
|
5
6
|
class Controller
|
@@ -90,8 +91,8 @@ module Bluepill
|
|
90
91
|
if !pid || !System.pid_alive?(pid)
|
91
92
|
pid_file = File.join(self.pids_dir, "#{app}.pid")
|
92
93
|
sock_file = File.join(self.sockets_dir, "#{app}.sock")
|
93
|
-
|
94
|
-
|
94
|
+
System.delete_if_exists(pid_file)
|
95
|
+
System.delete_if_exists(sock_file)
|
95
96
|
end
|
96
97
|
end
|
97
98
|
end
|
data/lib/bluepill/process.rb
CHANGED
@@ -5,10 +5,13 @@ gem "state_machine"
|
|
5
5
|
|
6
6
|
require "state_machine"
|
7
7
|
require "daemons"
|
8
|
+
require "bluepill/system"
|
9
|
+
require "bluepill/process_journal"
|
8
10
|
|
9
11
|
module Bluepill
|
10
12
|
class Process
|
11
13
|
CONFIGURABLE_ATTRIBUTES = [
|
14
|
+
:pre_start_command,
|
12
15
|
:start_command,
|
13
16
|
:stop_command,
|
14
17
|
:restart_command,
|
@@ -263,11 +266,19 @@ module Bluepill
|
|
263
266
|
end
|
264
267
|
|
265
268
|
def start_process
|
269
|
+
ProcessJournal.kill_all_from_journal(name) # be sure nothing else is running from previous runs
|
270
|
+
pre_start_process
|
266
271
|
logger.warning "Executing start command: #{start_command}"
|
267
|
-
|
268
272
|
if self.daemonize?
|
269
|
-
System.daemonize(start_command, self.system_command_options)
|
270
|
-
|
273
|
+
daemon_id = System.daemonize(start_command, self.system_command_options)
|
274
|
+
if daemon_id > 0
|
275
|
+
ProcessJournal.append_pid_to_journal(name, daemon_id)
|
276
|
+
children.each {|child|
|
277
|
+
child_pid = child.actual_id
|
278
|
+
ProcessJournal.append_pid_to_journal(name, child_id)
|
279
|
+
} if self.monitor_children?
|
280
|
+
end
|
281
|
+
daemon_id
|
271
282
|
else
|
272
283
|
# This is a self-daemonizing process
|
273
284
|
with_timeout(start_grace_time) do
|
@@ -283,7 +294,23 @@ module Bluepill
|
|
283
294
|
self.skip_ticks_for(start_grace_time)
|
284
295
|
end
|
285
296
|
|
297
|
+
def pre_start_process
|
298
|
+
return unless pre_start_command
|
299
|
+
logger.warning "Executing pre start command: #{pre_start_command}"
|
300
|
+
result = System.execute_blocking(pre_start_command, self.system_command_options)
|
301
|
+
unless result[:exit_code].zero?
|
302
|
+
logger.warning "Pre start command execution returned non-zero exit code:"
|
303
|
+
logger.warning result.inspect
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
286
307
|
def stop_process
|
308
|
+
if monitor_children
|
309
|
+
System.get_children(self.actual_pid).each do |child_pid|
|
310
|
+
ProcessJournal.append_pid_to_journal(name, child_pid)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
287
314
|
if stop_command
|
288
315
|
cmd = self.prepare_command(stop_command)
|
289
316
|
logger.warning "Executing stop command: #{cmd}"
|
@@ -325,6 +352,7 @@ module Bluepill
|
|
325
352
|
logger.warning "Executing default stop command. Sending TERM signal to #{actual_pid}"
|
326
353
|
signal_process("TERM")
|
327
354
|
end
|
355
|
+
ProcessJournal.kill_all_from_journal(name) # finish cleanup
|
328
356
|
self.unlink_pid # TODO: we only write the pid file if we daemonize, should we only unlink it if we daemonize?
|
329
357
|
|
330
358
|
self.skip_ticks_for(stop_grace_time)
|
@@ -404,6 +432,7 @@ module Bluepill
|
|
404
432
|
end
|
405
433
|
|
406
434
|
def actual_pid=(pid)
|
435
|
+
ProcessJournal.append_pid_to_journal(name, pid) # be sure to always log the pid
|
407
436
|
@actual_pid = pid
|
408
437
|
end
|
409
438
|
|
@@ -412,8 +441,7 @@ module Bluepill
|
|
412
441
|
end
|
413
442
|
|
414
443
|
def unlink_pid
|
415
|
-
|
416
|
-
rescue Errno::ENOENT
|
444
|
+
System.delete_if_exists(pid_file)
|
417
445
|
end
|
418
446
|
|
419
447
|
# Internal State Methods
|
@@ -440,6 +468,7 @@ module Bluepill
|
|
440
468
|
|
441
469
|
# Construct a new process wrapper for each new found children
|
442
470
|
new_children_pids.each do |child_pid|
|
471
|
+
ProcessJournal.append_pid_to_journal(name, child_pid)
|
443
472
|
name = "<child(pid:#{child_pid})>"
|
444
473
|
logger = self.logger.prefix_with(name)
|
445
474
|
|
@@ -6,9 +6,9 @@ module Bluepill
|
|
6
6
|
@below = options[:below]
|
7
7
|
end
|
8
8
|
|
9
|
-
def run(pid)
|
9
|
+
def run(pid, include_children)
|
10
10
|
# third col in the ps axu output
|
11
|
-
System.cpu_usage(pid).to_f
|
11
|
+
System.cpu_usage(pid, include_children).to_f
|
12
12
|
end
|
13
13
|
|
14
14
|
def check(value)
|
@@ -16,4 +16,4 @@ module Bluepill
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
|
-
end
|
19
|
+
end
|
@@ -11,9 +11,9 @@ module Bluepill
|
|
11
11
|
@below = options[:below]
|
12
12
|
end
|
13
13
|
|
14
|
-
def run(pid)
|
14
|
+
def run(pid, include_children)
|
15
15
|
# rss is on the 5th col
|
16
|
-
System.memory_usage(pid).to_f
|
16
|
+
System.memory_usage(pid, include_children).to_f
|
17
17
|
end
|
18
18
|
|
19
19
|
def check(value)
|
@@ -29,4 +29,4 @@ module Bluepill
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
32
|
-
end
|
32
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'bluepill/system'
|
2
|
+
|
3
|
+
module Bluepill
|
4
|
+
module ProcessJournal
|
5
|
+
extend self
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :logger
|
9
|
+
|
10
|
+
def logger=(new_logger)
|
11
|
+
@logger ||= new_logger
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def skip_pid?(pid)
|
16
|
+
!pid.is_a?(Integer) || pid <= 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def skip_pgid?(pgid)
|
20
|
+
!pgid.is_a?(Integer) || pgid <= 1
|
21
|
+
end
|
22
|
+
|
23
|
+
# atomic operation on POSIX filesystems, since
|
24
|
+
# f.flock(File::LOCK_SH) is not available on all platforms
|
25
|
+
def acquire_atomic_fs_lock(name)
|
26
|
+
times = 0
|
27
|
+
name += '.lock'
|
28
|
+
Dir.mkdir name, 0700
|
29
|
+
logger.debug("Acquired lock #{name}")
|
30
|
+
yield
|
31
|
+
rescue Errno::EEXIST
|
32
|
+
times += 1
|
33
|
+
logger.debug("Waiting for lock #{name}")
|
34
|
+
sleep 1
|
35
|
+
unless times >= 10
|
36
|
+
retry
|
37
|
+
else
|
38
|
+
logger.info("Timeout waiting for lock #{name}")
|
39
|
+
raise "Timeout waiting for lock #{name}"
|
40
|
+
end
|
41
|
+
ensure
|
42
|
+
clear_atomic_fs_lock(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear_all_atomic_fs_locks
|
46
|
+
Dir['.*.lock'].each do |f|
|
47
|
+
System.delete_if_exists(f) if File.directory?(f)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def pid_journal_filename(journal_name)
|
52
|
+
".bluepill_pids_journal.#{journal_name}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def pgid_journal_filename(journal_name)
|
56
|
+
".bluepill_pgids_journal.#{journal_name}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def pid_journal(filename)
|
60
|
+
logger.debug("pid journal PWD=#{Dir.pwd}")
|
61
|
+
result = File.open(filename, 'r').readlines.map(&:to_i).reject {|pid| skip_pid?(pid)}
|
62
|
+
logger.debug("pid journal = #{result.join(' ')}")
|
63
|
+
result
|
64
|
+
rescue Errno::ENOENT
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
|
68
|
+
def pgid_journal(filename)
|
69
|
+
logger.debug("pgid journal PWD=#{Dir.pwd}")
|
70
|
+
result = File.open(filename, 'r').readlines.map(&:to_i).reject {|pgid| skip_pgid?(pgid)}
|
71
|
+
logger.debug("pgid journal = #{result.join(' ')}")
|
72
|
+
result
|
73
|
+
rescue Errno::ENOENT
|
74
|
+
[]
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear_atomic_fs_lock(name)
|
78
|
+
if File.directory?(name)
|
79
|
+
Dir.rmdir(name)
|
80
|
+
logger.debug("Cleared lock #{name}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def kill_all_from_all_journals
|
85
|
+
Dir[".bluepill_pids_journal.*"].map { |x|
|
86
|
+
x.sub(/^\.bluepill_pids_journal\./,"")
|
87
|
+
}.reject { |y|
|
88
|
+
y =~ /\.lock$/
|
89
|
+
}.each do |journal_name|
|
90
|
+
kill_all_from_journal(journal_name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def kill_all_from_journal(journal_name)
|
95
|
+
kill_all_pids_from_journal(journal_name)
|
96
|
+
kill_all_pgids_from_journal(journal_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def kill_all_pgids_from_journal(journal_name)
|
100
|
+
filename = pgid_journal_filename(journal_name)
|
101
|
+
j = pgid_journal(filename)
|
102
|
+
if j.length > 0
|
103
|
+
acquire_atomic_fs_lock(filename) do
|
104
|
+
j.each do |pgid|
|
105
|
+
begin
|
106
|
+
::Process.kill('TERM', -pgid)
|
107
|
+
logger.info("Termed old process group #{pgid}")
|
108
|
+
rescue Errno::ESRCH
|
109
|
+
logger.debug("Unable to term missing process group #{pgid}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if j.select { |pgid| System.pid_alive?(pgid) }.length > 1
|
114
|
+
sleep(1)
|
115
|
+
j.each do |pgid|
|
116
|
+
begin
|
117
|
+
::Process.kill('KILL', -pgid)
|
118
|
+
logger.info("Killed old process group #{pgid}")
|
119
|
+
rescue Errno::ESRCH
|
120
|
+
logger.debug("Unable to kill missing process group #{pgid}")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
System.delete_if_exists(filename) # reset journal
|
125
|
+
logger.debug('Journal cleanup completed')
|
126
|
+
end
|
127
|
+
else
|
128
|
+
logger.debug('No previous process journal - Skipping cleanup')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def kill_all_pids_from_journal(journal_name)
|
133
|
+
filename = pid_journal_filename(journal_name)
|
134
|
+
j = pid_journal(filename)
|
135
|
+
if j.length > 0
|
136
|
+
acquire_atomic_fs_lock(filename) do
|
137
|
+
j.each do |pid|
|
138
|
+
begin
|
139
|
+
::Process.kill('TERM', pid)
|
140
|
+
logger.info("Termed old process #{pid}")
|
141
|
+
rescue Errno::ESRCH
|
142
|
+
logger.debug("Unable to term missing process #{pid}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if j.select { |pid| System.pid_alive?(pid) }.length > 1
|
147
|
+
sleep(1)
|
148
|
+
j.each do |pid|
|
149
|
+
begin
|
150
|
+
::Process.kill('KILL', pid)
|
151
|
+
logger.info("Killed old process #{pid}")
|
152
|
+
rescue Errno::ESRCH
|
153
|
+
logger.debug("Unable to kill missing process #{pid}")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
System.delete_if_exists(filename) # reset journal
|
158
|
+
logger.debug('Journal cleanup completed')
|
159
|
+
end
|
160
|
+
else
|
161
|
+
logger.debug('No previous process journal - Skipping cleanup')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def append_pgid_to_journal(journal_name, pgid)
|
166
|
+
if skip_pgid?(pgid)
|
167
|
+
logger.debug("Skipping invalid pgid #{pgid} (class #{pgid.class})")
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
filename = pgid_journal_filename(journal_name)
|
172
|
+
acquire_atomic_fs_lock(filename) do
|
173
|
+
unless pgid_journal(filename).include?(pgid)
|
174
|
+
logger.debug("Saving pgid #{pgid} to process journal #{journal_name}")
|
175
|
+
File.open(filename, 'a+', 0600) { |f| f.puts(pgid) }
|
176
|
+
logger.info("Saved pgid #{pgid} to journal #{journal_name}")
|
177
|
+
logger.debug("Journal now = #{File.open(filename, 'r').read}")
|
178
|
+
else
|
179
|
+
logger.debug("Skipping duplicate pgid #{pgid} already in journal #{journal_name}")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def append_pid_to_journal(journal_name, pid)
|
185
|
+
begin
|
186
|
+
append_pgid_to_journal(journal_name, ::Process.getpgid(pid))
|
187
|
+
rescue Errno::ESRCH
|
188
|
+
end
|
189
|
+
if skip_pid?(pid)
|
190
|
+
logger.debug("Skipping invalid pid #{pid} (class #{pid.class})")
|
191
|
+
return
|
192
|
+
end
|
193
|
+
|
194
|
+
filename = pid_journal_filename(journal_name)
|
195
|
+
acquire_atomic_fs_lock(filename) do
|
196
|
+
unless pid_journal(filename).include?(pid)
|
197
|
+
logger.debug("Saving pid #{pid} to process journal #{journal_name}")
|
198
|
+
File.open(filename, 'a+', 0600) { |f| f.puts(pid) }
|
199
|
+
logger.info("Saved pid #{pid} to journal #{journal_name}")
|
200
|
+
logger.debug("Journal now = #{File.open(filename, 'r').read}")
|
201
|
+
else
|
202
|
+
logger.debug("Skipping duplicate pid #{pid} already in journal #{journal_name}")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/lib/bluepill/system.rb
CHANGED
@@ -21,17 +21,31 @@ module Bluepill
|
|
21
21
|
begin
|
22
22
|
::Process.kill(0, pid)
|
23
23
|
true
|
24
|
+
rescue Errno::EPERM # no permission, but it is definitely alive
|
25
|
+
true
|
24
26
|
rescue Errno::ESRCH
|
25
27
|
false
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
def cpu_usage(pid)
|
30
|
-
|
31
|
+
def cpu_usage(pid, include_children)
|
32
|
+
ps = ps_axu
|
33
|
+
return unless ps[pid]
|
34
|
+
cpu_used = ps[pid][IDX_MAP[:pcpu]].to_f
|
35
|
+
get_children(pid).each { |child_pid|
|
36
|
+
cpu_used += ps[child_pid][IDX_MAP[:pcpu]].to_f if ps[child_pid]
|
37
|
+
} if include_children
|
38
|
+
cpu_used
|
31
39
|
end
|
32
40
|
|
33
|
-
def memory_usage(pid)
|
34
|
-
|
41
|
+
def memory_usage(pid, include_children)
|
42
|
+
ps = ps_axu
|
43
|
+
return unless ps[pid]
|
44
|
+
mem_used = ps[pid][IDX_MAP[:rss]].to_f
|
45
|
+
get_children(pid).each { |child_pid|
|
46
|
+
mem_used += ps[child_pid][IDX_MAP[:rss]].to_f if ps[child_pid]
|
47
|
+
} if include_children
|
48
|
+
mem_used
|
35
49
|
end
|
36
50
|
|
37
51
|
def get_children(parent_pid)
|
@@ -39,7 +53,8 @@ module Bluepill
|
|
39
53
|
ps_axu.each_pair do |pid, chunks|
|
40
54
|
child_pids << chunks[IDX_MAP[:pid]].to_i if chunks[IDX_MAP[:ppid]].to_i == parent_pid.to_i
|
41
55
|
end
|
42
|
-
child_pids
|
56
|
+
grand_children = child_pids.map{|pid| get_children(pid)}.flatten
|
57
|
+
child_pids.concat grand_children
|
43
58
|
end
|
44
59
|
|
45
60
|
# Returns the pid of the child that executes the cmd
|
@@ -69,7 +84,7 @@ module Bluepill
|
|
69
84
|
|
70
85
|
to_daemonize = lambda do
|
71
86
|
# Setting end PWD env emulates bash behavior when dealing with symlinks
|
72
|
-
Dir.chdir(ENV["PWD"] = options[:working_dir])
|
87
|
+
Dir.chdir(ENV["PWD"] = options[:working_dir].to_s) if options[:working_dir]
|
73
88
|
options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
|
74
89
|
|
75
90
|
redirect_io(*options.values_at(:stdin, :stdout, :stderr))
|
@@ -89,6 +104,15 @@ module Bluepill
|
|
89
104
|
end
|
90
105
|
end
|
91
106
|
|
107
|
+
def delete_if_exists(filename)
|
108
|
+
tries = 0
|
109
|
+
File.unlink(filename) if filename && File.exists?(filename)
|
110
|
+
rescue IOError, Errno::ENOENT
|
111
|
+
rescue Errno::EACCES
|
112
|
+
retry if (tries += 1) < 3
|
113
|
+
$stderr.puts("Warning: permission denied trying to delete #{filename}")
|
114
|
+
end
|
115
|
+
|
92
116
|
# Returns the stdout, stderr and exit code of the cmd
|
93
117
|
def execute_blocking(cmd, options = {})
|
94
118
|
rd, wr = IO.pipe
|
@@ -115,7 +139,7 @@ module Bluepill
|
|
115
139
|
# grandchild
|
116
140
|
drop_privileges(options[:uid], options[:gid], options[:supplementary_groups])
|
117
141
|
|
118
|
-
Dir.chdir(ENV["PWD"] = options[:working_dir]) if options[:working_dir]
|
142
|
+
Dir.chdir(ENV["PWD"] = options[:working_dir].to_s) if options[:working_dir]
|
119
143
|
options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
|
120
144
|
|
121
145
|
# close unused fds so ancestors wont hang. This line is the only reason we are not
|
data/lib/bluepill/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bluepill
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.61
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,25 +11,27 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2013-03-18 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: daemons
|
18
|
-
requirement:
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 1.1.4
|
24
|
-
- - <=
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.1.6
|
27
24
|
type: :runtime
|
28
25
|
prerelease: false
|
29
|
-
version_requirements:
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.1.4
|
30
32
|
- !ruby/object:Gem::Dependency
|
31
33
|
name: state_machine
|
32
|
-
requirement:
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
33
35
|
none: false
|
34
36
|
requirements:
|
35
37
|
- - ~>
|
@@ -37,10 +39,15 @@ dependencies:
|
|
37
39
|
version: 1.1.0
|
38
40
|
type: :runtime
|
39
41
|
prerelease: false
|
40
|
-
version_requirements:
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.0
|
41
48
|
- !ruby/object:Gem::Dependency
|
42
49
|
name: activesupport
|
43
|
-
requirement:
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
44
51
|
none: false
|
45
52
|
requirements:
|
46
53
|
- - ! '>='
|
@@ -48,10 +55,15 @@ dependencies:
|
|
48
55
|
version: 3.0.0
|
49
56
|
type: :runtime
|
50
57
|
prerelease: false
|
51
|
-
version_requirements:
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 3.0.0
|
52
64
|
- !ruby/object:Gem::Dependency
|
53
65
|
name: i18n
|
54
|
-
requirement:
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
55
67
|
none: false
|
56
68
|
requirements:
|
57
69
|
- - ! '>='
|
@@ -59,10 +71,15 @@ dependencies:
|
|
59
71
|
version: 0.5.0
|
60
72
|
type: :runtime
|
61
73
|
prerelease: false
|
62
|
-
version_requirements:
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 0.5.0
|
63
80
|
- !ruby/object:Gem::Dependency
|
64
81
|
name: bundler
|
65
|
-
requirement:
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
66
83
|
none: false
|
67
84
|
requirements:
|
68
85
|
- - ! '>='
|
@@ -70,10 +87,15 @@ dependencies:
|
|
70
87
|
version: 1.0.10
|
71
88
|
type: :development
|
72
89
|
prerelease: false
|
73
|
-
version_requirements:
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 1.0.10
|
74
96
|
- !ruby/object:Gem::Dependency
|
75
97
|
name: rake
|
76
|
-
requirement:
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
77
99
|
none: false
|
78
100
|
requirements:
|
79
101
|
- - ! '!='
|
@@ -81,10 +103,15 @@ dependencies:
|
|
81
103
|
version: 0.9.0
|
82
104
|
type: :development
|
83
105
|
prerelease: false
|
84
|
-
version_requirements:
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '!='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 0.9.0
|
85
112
|
- !ruby/object:Gem::Dependency
|
86
113
|
name: rspec-core
|
87
|
-
requirement:
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
88
115
|
none: false
|
89
116
|
requirements:
|
90
117
|
- - ~>
|
@@ -92,10 +119,15 @@ dependencies:
|
|
92
119
|
version: '2.0'
|
93
120
|
type: :development
|
94
121
|
prerelease: false
|
95
|
-
version_requirements:
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ~>
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '2.0'
|
96
128
|
- !ruby/object:Gem::Dependency
|
97
129
|
name: rspec-expectations
|
98
|
-
requirement:
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
99
131
|
none: false
|
100
132
|
requirements:
|
101
133
|
- - ~>
|
@@ -103,10 +135,15 @@ dependencies:
|
|
103
135
|
version: '2.0'
|
104
136
|
type: :development
|
105
137
|
prerelease: false
|
106
|
-
version_requirements:
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ~>
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '2.0'
|
107
144
|
- !ruby/object:Gem::Dependency
|
108
145
|
name: rr
|
109
|
-
requirement:
|
146
|
+
requirement: !ruby/object:Gem::Requirement
|
110
147
|
none: false
|
111
148
|
requirements:
|
112
149
|
- - ~>
|
@@ -114,10 +151,15 @@ dependencies:
|
|
114
151
|
version: '1.0'
|
115
152
|
type: :development
|
116
153
|
prerelease: false
|
117
|
-
version_requirements:
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ~>
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '1.0'
|
118
160
|
- !ruby/object:Gem::Dependency
|
119
161
|
name: faker
|
120
|
-
requirement:
|
162
|
+
requirement: !ruby/object:Gem::Requirement
|
121
163
|
none: false
|
122
164
|
requirements:
|
123
165
|
- - ~>
|
@@ -125,10 +167,15 @@ dependencies:
|
|
125
167
|
version: '0.9'
|
126
168
|
type: :development
|
127
169
|
prerelease: false
|
128
|
-
version_requirements:
|
170
|
+
version_requirements: !ruby/object:Gem::Requirement
|
171
|
+
none: false
|
172
|
+
requirements:
|
173
|
+
- - ~>
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0.9'
|
129
176
|
- !ruby/object:Gem::Dependency
|
130
177
|
name: yard
|
131
|
-
requirement:
|
178
|
+
requirement: !ruby/object:Gem::Requirement
|
132
179
|
none: false
|
133
180
|
requirements:
|
134
181
|
- - ~>
|
@@ -136,7 +183,12 @@ dependencies:
|
|
136
183
|
version: '0.7'
|
137
184
|
type: :development
|
138
185
|
prerelease: false
|
139
|
-
version_requirements:
|
186
|
+
version_requirements: !ruby/object:Gem::Requirement
|
187
|
+
none: false
|
188
|
+
requirements:
|
189
|
+
- - ~>
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0.7'
|
140
192
|
description: Bluepill keeps your daemons up while taking up as little resources as
|
141
193
|
possible. After all you probably want the resources of your server to be used by
|
142
194
|
whatever daemons you are running rather than the thing that's supposed to make sure
|
@@ -186,6 +238,7 @@ files:
|
|
186
238
|
- lib/bluepill/process_conditions/http.rb
|
187
239
|
- lib/bluepill/process_conditions/mem_usage.rb
|
188
240
|
- lib/bluepill/process_conditions/process_condition.rb
|
241
|
+
- lib/bluepill/process_journal.rb
|
189
242
|
- lib/bluepill/process_statistics.rb
|
190
243
|
- lib/bluepill/socket.rb
|
191
244
|
- lib/bluepill/system.rb
|
@@ -218,7 +271,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
271
|
version: '0'
|
219
272
|
requirements: []
|
220
273
|
rubyforge_project:
|
221
|
-
rubygems_version: 1.8.
|
274
|
+
rubygems_version: 1.8.24
|
222
275
|
signing_key:
|
223
276
|
specification_version: 3
|
224
277
|
summary: A process monitor written in Ruby with stability and minimalism in mind.
|