bluepill 0.0.46 → 0.0.47
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 -1
- data/Gemfile +4 -0
- data/README.md +2 -2
- data/Rakefile +2 -55
- data/bin/bluepill +1 -1
- data/bin/sample_forking_server +53 -0
- data/bluepill.gemspec +21 -81
- data/{lib → examples}/example.rb +24 -24
- data/{lib → examples}/runit_example.rb +4 -3
- data/lib/bluepill.rb +6 -1
- data/lib/bluepill/application.rb +27 -26
- data/lib/bluepill/application/client.rb +2 -1
- data/lib/bluepill/application/server.rb +4 -3
- data/lib/bluepill/condition_watch.rb +25 -30
- data/lib/bluepill/controller.rb +13 -12
- data/lib/bluepill/dsl.rb +4 -151
- data/lib/bluepill/dsl/app_proxy.rb +25 -0
- data/lib/bluepill/dsl/process_factory.rb +87 -0
- data/lib/bluepill/dsl/process_proxy.rb +36 -0
- data/lib/bluepill/group.rb +9 -8
- data/lib/bluepill/logger.rb +10 -9
- data/lib/bluepill/process.rb +90 -97
- data/lib/bluepill/process_conditions.rb +2 -1
- data/lib/bluepill/process_conditions/always_true.rb +3 -2
- data/lib/bluepill/process_conditions/cpu_usage.rb +3 -2
- data/lib/bluepill/process_conditions/http.rb +1 -0
- data/lib/bluepill/process_conditions/mem_usage.rb +5 -4
- data/lib/bluepill/process_conditions/process_condition.rb +5 -4
- data/lib/bluepill/process_statistics.rb +7 -8
- data/lib/bluepill/socket.rb +6 -6
- data/lib/bluepill/system.rb +43 -42
- data/lib/bluepill/trigger.rb +10 -19
- data/lib/bluepill/triggers/flapping.rb +13 -14
- data/lib/bluepill/util/rotational_array.rb +14 -66
- data/lib/bluepill/version.rb +2 -1
- metadata +53 -46
- data/VERSION +0 -1
@@ -1,14 +1,15 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Bluepill
|
2
3
|
module ProcessConditions
|
3
4
|
class AlwaysTrue < ProcessCondition
|
4
5
|
def initialize(options = {})
|
5
6
|
@below = options[:below]
|
6
7
|
end
|
7
|
-
|
8
|
+
|
8
9
|
def run(pid)
|
9
10
|
1
|
10
11
|
end
|
11
|
-
|
12
|
+
|
12
13
|
def check(value)
|
13
14
|
true
|
14
15
|
end
|
@@ -1,15 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Bluepill
|
2
3
|
module ProcessConditions
|
3
4
|
class CpuUsage < ProcessCondition
|
4
5
|
def initialize(options = {})
|
5
6
|
@below = options[:below]
|
6
7
|
end
|
7
|
-
|
8
|
+
|
8
9
|
def run(pid)
|
9
10
|
# third col in the ps axu output
|
10
11
|
System.cpu_usage(pid).to_f
|
11
12
|
end
|
12
|
-
|
13
|
+
|
13
14
|
def check(value)
|
14
15
|
value < @below
|
15
16
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Bluepill
|
2
3
|
module ProcessConditions
|
3
4
|
class MemUsage < ProcessCondition
|
@@ -5,20 +6,20 @@ module Bluepill
|
|
5
6
|
FORMAT_STR = "%d%s"
|
6
7
|
MB_LABEL = "MB"
|
7
8
|
KB_LABEL = "KB"
|
8
|
-
|
9
|
+
|
9
10
|
def initialize(options = {})
|
10
11
|
@below = options[:below]
|
11
12
|
end
|
12
|
-
|
13
|
+
|
13
14
|
def run(pid)
|
14
15
|
# rss is on the 5th col
|
15
16
|
System.memory_usage(pid).to_f
|
16
17
|
end
|
17
|
-
|
18
|
+
|
18
19
|
def check(value)
|
19
20
|
value.kilobytes < @below
|
20
21
|
end
|
21
|
-
|
22
|
+
|
22
23
|
def format_value(value)
|
23
24
|
if value.kilobytes >= MB
|
24
25
|
FORMAT_STR % [(value / 1024).round, MB_LABEL]
|
@@ -1,18 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Bluepill
|
2
3
|
module ProcessConditions
|
3
|
-
class ProcessCondition
|
4
|
+
class ProcessCondition
|
4
5
|
def initialize(options = {})
|
5
6
|
@options = options
|
6
7
|
end
|
7
|
-
|
8
|
+
|
8
9
|
def run(pid)
|
9
10
|
raise "Implement in subclass!"
|
10
11
|
end
|
11
|
-
|
12
|
+
|
12
13
|
def check(value)
|
13
14
|
raise "Implement in subclass!"
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
def format_value(value)
|
17
18
|
value
|
18
19
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Bluepill
|
2
|
-
class ProcessStatistics
|
3
|
+
class ProcessStatistics
|
3
4
|
STRFTIME = "%m/%d/%Y %H:%I:%S"
|
5
|
+
|
4
6
|
# possibly persist this data.
|
5
7
|
def initialize
|
6
8
|
@events = Util::RotationalArray.new(10)
|
@@ -11,14 +13,11 @@ module Bluepill
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def to_s
|
14
|
-
str =
|
15
|
-
@events.each do |(event, reason, time)|
|
16
|
+
str = @events.reverse.collect do |(event, reason, time)|
|
16
17
|
str << " #{event} at #{time.strftime(STRFTIME)} - #{reason || "unspecified"}"
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
str.reverse.join("\n")
|
18
|
+
end.join("\n")
|
19
|
+
|
20
|
+
"event history:\n#{str}"
|
22
21
|
end
|
23
22
|
end
|
24
23
|
end
|
data/lib/bluepill/socket.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
require 'socket'
|
2
3
|
|
3
4
|
module Bluepill
|
4
5
|
module Socket
|
5
|
-
TIMEOUT =
|
6
|
+
TIMEOUT = 60 # Used for client commands
|
6
7
|
|
7
8
|
extend self
|
8
9
|
|
9
10
|
def client(base_dir, name, &b)
|
10
11
|
UNIXSocket.open(socket_path(base_dir, name), &b)
|
11
12
|
end
|
12
|
-
|
13
|
+
|
13
14
|
def client_command(base_dir, name, command)
|
14
15
|
client(base_dir, name) do |socket|
|
15
16
|
Timeout.timeout(TIMEOUT) do
|
@@ -20,7 +21,7 @@ module Bluepill
|
|
20
21
|
rescue EOFError, Timeout::Error
|
21
22
|
abort("Socket Timeout: Server may not be responding")
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
def server(base_dir, name)
|
25
26
|
socket_path = self.socket_path(base_dir, name)
|
26
27
|
begin
|
@@ -38,10 +39,9 @@ module Bluepill
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
41
|
-
|
42
|
+
|
42
43
|
def socket_path(base_dir, name)
|
43
|
-
File.join(base_dir, 'socks', name + ".sock")
|
44
|
+
File.join(base_dir, 'socks', name + ".sock")
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
47
|
-
|
data/lib/bluepill/system.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
require 'etc'
|
2
3
|
require "shellwords"
|
3
4
|
|
@@ -7,7 +8,7 @@ module Bluepill
|
|
7
8
|
module System
|
8
9
|
APPEND_MODE = "a"
|
9
10
|
extend self
|
10
|
-
|
11
|
+
|
11
12
|
# The position of each field in ps output
|
12
13
|
IDX_MAP = {
|
13
14
|
:pid => 0,
|
@@ -15,7 +16,7 @@ module Bluepill
|
|
15
16
|
:pcpu => 2,
|
16
17
|
:rss => 3
|
17
18
|
}
|
18
|
-
|
19
|
+
|
19
20
|
def pid_alive?(pid)
|
20
21
|
begin
|
21
22
|
::Process.kill(0, pid)
|
@@ -24,23 +25,23 @@ module Bluepill
|
|
24
25
|
false
|
25
26
|
end
|
26
27
|
end
|
27
|
-
|
28
|
+
|
28
29
|
def cpu_usage(pid)
|
29
30
|
ps_axu[pid] && ps_axu[pid][IDX_MAP[:pcpu]].to_f
|
30
31
|
end
|
31
|
-
|
32
|
+
|
32
33
|
def memory_usage(pid)
|
33
34
|
ps_axu[pid] && ps_axu[pid][IDX_MAP[:rss]].to_f
|
34
35
|
end
|
35
|
-
|
36
|
+
|
36
37
|
def get_children(parent_pid)
|
37
38
|
child_pids = Array.new
|
38
|
-
ps_axu.each_pair do |pid, chunks|
|
39
|
+
ps_axu.each_pair do |pid, chunks|
|
39
40
|
child_pids << chunks[IDX_MAP[:pid]].to_i if chunks[IDX_MAP[:ppid]].to_i == parent_pid.to_i
|
40
41
|
end
|
41
42
|
child_pids
|
42
43
|
end
|
43
|
-
|
44
|
+
|
44
45
|
# Returns the pid of the child that executes the cmd
|
45
46
|
def daemonize(cmd, options = {})
|
46
47
|
rd, wr = IO.pipe
|
@@ -48,76 +49,76 @@ module Bluepill
|
|
48
49
|
if child = Daemonize.safefork
|
49
50
|
# we do not wanna create zombies, so detach ourselves from the child exit status
|
50
51
|
::Process.detach(child)
|
51
|
-
|
52
|
+
|
52
53
|
# parent
|
53
54
|
wr.close
|
54
|
-
|
55
|
+
|
55
56
|
daemon_id = rd.read.to_i
|
56
57
|
rd.close
|
57
|
-
|
58
|
+
|
58
59
|
return daemon_id if daemon_id > 0
|
59
|
-
|
60
|
+
|
60
61
|
else
|
61
62
|
# child
|
62
63
|
rd.close
|
63
64
|
|
64
65
|
drop_privileges(options[:uid], options[:gid])
|
65
|
-
|
66
|
+
|
66
67
|
# if we cannot write the pid file as the provided user, err out
|
67
68
|
exit unless can_write_pid_file(options[:pid_file], options[:logger])
|
68
|
-
|
69
|
+
|
69
70
|
to_daemonize = lambda do
|
70
71
|
# Setting end PWD env emulates bash behavior when dealing with symlinks
|
71
72
|
Dir.chdir(ENV["PWD"] = options[:working_dir]) if options[:working_dir]
|
72
73
|
options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
|
73
|
-
|
74
|
+
|
74
75
|
redirect_io(*options.values_at(:stdin, :stdout, :stderr))
|
75
|
-
|
76
|
+
|
76
77
|
::Kernel.exec(*Shellwords.shellwords(cmd))
|
77
78
|
exit
|
78
79
|
end
|
79
80
|
|
80
81
|
daemon_id = Daemonize.call_as_daemon(to_daemonize, nil, cmd)
|
81
|
-
|
82
|
+
|
82
83
|
File.open(options[:pid_file], "w") {|f| f.write(daemon_id)}
|
83
|
-
|
84
|
-
wr.write daemon_id
|
84
|
+
|
85
|
+
wr.write daemon_id
|
85
86
|
wr.close
|
86
87
|
|
87
88
|
exit
|
88
89
|
end
|
89
90
|
end
|
90
|
-
|
91
|
+
|
91
92
|
# Returns the stdout, stderr and exit code of the cmd
|
92
93
|
def execute_blocking(cmd, options = {})
|
93
94
|
rd, wr = IO.pipe
|
94
|
-
|
95
|
+
|
95
96
|
if child = Daemonize.safefork
|
96
97
|
# parent
|
97
98
|
wr.close
|
98
|
-
|
99
|
+
|
99
100
|
cmd_status = rd.read
|
100
101
|
rd.close
|
101
|
-
|
102
|
+
|
102
103
|
::Process.waitpid(child)
|
103
|
-
|
104
|
+
|
104
105
|
return Marshal.load(cmd_status)
|
105
|
-
|
106
|
+
|
106
107
|
else
|
107
108
|
# child
|
108
109
|
rd.close
|
109
|
-
|
110
|
+
|
110
111
|
# create a child in which we can override the stdin, stdout and stderr
|
111
112
|
cmd_out_read, cmd_out_write = IO.pipe
|
112
113
|
cmd_err_read, cmd_err_write = IO.pipe
|
113
|
-
|
114
|
+
|
114
115
|
pid = fork {
|
115
116
|
# grandchild
|
116
117
|
drop_privileges(options[:uid], options[:gid])
|
117
|
-
|
118
|
+
|
118
119
|
Dir.chdir(ENV["PWD"] = options[:working_dir]) if options[:working_dir]
|
119
120
|
options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
|
120
|
-
|
121
|
+
|
121
122
|
# close unused fds so ancestors wont hang. This line is the only reason we are not
|
122
123
|
# using something like popen3. If this fd is not closed, the .read call on the parent
|
123
124
|
# will never return because "wr" would still be open in the "exec"-ed cmd
|
@@ -143,10 +144,10 @@ module Bluepill
|
|
143
144
|
# we do not use these ends of the pipes in the child
|
144
145
|
cmd_out_write.close
|
145
146
|
cmd_err_write.close
|
146
|
-
|
147
|
+
|
147
148
|
# wait for the cmd to finish executing and acknowledge it's death
|
148
149
|
::Process.waitpid(pid)
|
149
|
-
|
150
|
+
|
150
151
|
# collect stdout, stderr and exitcode
|
151
152
|
result = {
|
152
153
|
:stdout => cmd_out_read.read,
|
@@ -157,30 +158,30 @@ module Bluepill
|
|
157
158
|
# We're done with these ends of the pipes as well
|
158
159
|
cmd_out_read.close
|
159
160
|
cmd_err_read.close
|
160
|
-
|
161
|
+
|
161
162
|
# Time to tell the parent about what went down
|
162
163
|
wr.write Marshal.dump(result)
|
163
164
|
wr.close
|
164
165
|
|
165
|
-
exit
|
166
|
+
exit
|
166
167
|
end
|
167
168
|
end
|
168
|
-
|
169
|
+
|
169
170
|
def store
|
170
171
|
@store ||= Hash.new
|
171
172
|
end
|
172
|
-
|
173
|
+
|
173
174
|
def reset_data
|
174
175
|
store.clear unless store.empty?
|
175
176
|
end
|
176
|
-
|
177
|
+
|
177
178
|
def ps_axu
|
178
179
|
# TODO: need a mutex here
|
179
180
|
store[:ps_axu] ||= begin
|
180
181
|
# BSD style ps invocation
|
181
182
|
lines = `ps axo pid,ppid,pcpu,rss`.split("\n")
|
182
183
|
|
183
|
-
lines.inject(Hash.new) do |mem, line|
|
184
|
+
lines.inject(Hash.new) do |mem, line|
|
184
185
|
chunks = line.split(/\s+/)
|
185
186
|
chunks.delete_if {|c| c.strip.empty? }
|
186
187
|
pid = chunks[IDX_MAP[:pid]].strip.to_i
|
@@ -189,7 +190,7 @@ module Bluepill
|
|
189
190
|
end
|
190
191
|
end
|
191
192
|
end
|
192
|
-
|
193
|
+
|
193
194
|
# be sure to call this from a fork otherwise it will modify the attributes
|
194
195
|
# of the bluepill daemon
|
195
196
|
def drop_privileges(uid, gid)
|
@@ -202,25 +203,25 @@ module Bluepill
|
|
202
203
|
::Process::Sys.setuid(uid_num) if uid
|
203
204
|
end
|
204
205
|
end
|
205
|
-
|
206
|
+
|
206
207
|
def can_write_pid_file(pid_file, logger)
|
207
208
|
FileUtils.touch(pid_file)
|
208
209
|
File.unlink(pid_file)
|
209
210
|
return true
|
210
|
-
|
211
|
+
|
211
212
|
rescue Exception => e
|
212
213
|
logger.warning "%s - %s" % [e.class.name, e.message]
|
213
214
|
e.backtrace.each {|l| logger.warning l}
|
214
215
|
return false
|
215
216
|
end
|
216
|
-
|
217
|
+
|
217
218
|
def redirect_io(io_in, io_out, io_err)
|
218
219
|
$stdin.reopen(io_in) if io_in
|
219
|
-
|
220
|
+
|
220
221
|
if !io_out.nil? && !io_err.nil? && io_out == io_err
|
221
222
|
$stdout.reopen(io_out, APPEND_MODE)
|
222
223
|
$stderr.reopen($stdout)
|
223
|
-
|
224
|
+
|
224
225
|
else
|
225
226
|
$stdout.reopen(io_out, APPEND_MODE) if io_out
|
226
227
|
$stderr.reopen(io_err, APPEND_MODE) if io_err
|
data/lib/bluepill/trigger.rb
CHANGED
@@ -1,45 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Bluepill
|
2
3
|
class Trigger
|
3
4
|
@implementations = {}
|
4
5
|
def self.inherited(klass)
|
5
6
|
@implementations[klass.name.split('::').last.underscore.to_sym] = klass
|
6
7
|
end
|
7
|
-
|
8
|
+
|
8
9
|
def self.[](name)
|
9
10
|
@implementations[name]
|
10
11
|
end
|
11
12
|
|
12
13
|
attr_accessor :process, :logger, :mutex, :scheduled_events
|
13
|
-
|
14
|
+
|
14
15
|
def initialize(process, options = {})
|
15
16
|
self.process = process
|
16
17
|
self.logger = options[:logger]
|
17
18
|
self.mutex = Mutex.new
|
18
19
|
self.scheduled_events = []
|
19
20
|
end
|
20
|
-
|
21
|
+
|
21
22
|
def reset!
|
22
23
|
self.cancel_all_events
|
23
24
|
end
|
24
|
-
|
25
|
+
|
25
26
|
def notify(transition)
|
26
27
|
raise "Implement in subclass"
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
def dispatch!(event)
|
30
31
|
self.process.dispatch!(event, self.class.name.split("::").last)
|
31
32
|
end
|
32
|
-
|
33
|
-
def deep_copy
|
34
|
-
# TODO: This is a kludge. Ideally, process templates
|
35
|
-
# would be facotries, and not a template object.
|
36
|
-
mutex, @mutex = @mutex, nil
|
37
|
-
clone = Marshal.load(Marshal.dump(self))
|
38
|
-
clone.instance_variable_set("@mutex", Monitor.new)
|
39
|
-
@mutex = mutex
|
40
|
-
clone
|
41
|
-
end
|
42
|
-
|
33
|
+
|
43
34
|
def schedule_event(event, delay)
|
44
35
|
# TODO: maybe wrap this in a ScheduledEvent class with methods like cancel
|
45
36
|
thread = Thread.new(self) do |trigger|
|
@@ -54,16 +45,16 @@ module Bluepill
|
|
54
45
|
trigger.logger.err(e.backtrace.join("\n"))
|
55
46
|
end
|
56
47
|
end
|
57
|
-
|
48
|
+
|
58
49
|
self.scheduled_events.push([event, thread])
|
59
50
|
end
|
60
|
-
|
51
|
+
|
61
52
|
def cancel_all_events
|
62
53
|
self.logger.info "Canceling all scheduled events"
|
63
54
|
self.mutex.synchronize do
|
64
55
|
self.scheduled_events.each {|_, thread| thread.kill}
|
65
56
|
end
|
66
57
|
end
|
67
|
-
|
58
|
+
|
68
59
|
end
|
69
60
|
end
|