bluepill 0.0.24 → 0.0.25
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -1
- data/VERSION +1 -1
- data/bin/bluepill +20 -24
- data/bluepill.gemspec +2 -2
- data/lib/bluepill/application.rb +122 -166
- data/lib/bluepill/condition_watch.rb +1 -1
- data/lib/bluepill/controller.rb +62 -6
- data/lib/bluepill/dsl.rb +33 -5
- data/lib/bluepill/group.rb +4 -1
- data/lib/bluepill/process.rb +1 -1
- data/lib/bluepill/socket.rb +17 -23
- data/lib/bluepill/system.rb +5 -4
- data/lib/bluepill/version.rb +1 -1
- data/lib/example.rb +18 -8
- metadata +2 -2
data/README.md
CHANGED
@@ -211,9 +211,10 @@ By default, bluepill uses syslog local6 facility as described in the installatio
|
|
211
211
|
|
212
212
|
Keep in mind that you still need to set up log rotation (described in the installation section) to keep the log file from growing huge.
|
213
213
|
|
214
|
-
##
|
214
|
+
## Links
|
215
215
|
Code: [http://github.com/arya/bluepill](http://github.com/arya/bluepill)
|
216
216
|
Bugs/Features: [http://github.com/arya/bluepill/issues](http://github.com/arya/bluepill/issues)
|
217
|
+
Mailing List: [http://groups.google.com/group/bluepill-rb](http://groups.google.com/group/bluepill-rb)
|
217
218
|
|
218
219
|
|
219
220
|
[gemcutter]: http://gemcutter.org
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.25
|
data/bin/bluepill
CHANGED
@@ -20,16 +20,16 @@ OptionParser.new do |opts|
|
|
20
20
|
opts.on('-l', "--logfile LOGFILE", "Path to logfile, defaults to #{options[:log_file]}") do |file|
|
21
21
|
options[:log_file] = file
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
opts.on('-c', "--base-dir DIR", "Directory to store bluepill socket and pid files, defaults to #{options[:base_dir]}") do |base_dir|
|
25
25
|
options[:base_dir] = base_dir
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
opts.on("-v", "--version") do
|
29
29
|
puts "bluepill, version #{Bluepill::VERSION}"
|
30
30
|
exit
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
help = lambda do
|
34
34
|
puts opts
|
35
35
|
puts
|
@@ -46,18 +46,18 @@ OptionParser.new do |opts|
|
|
46
46
|
puts "See http://github.com/arya/bluepill for README"
|
47
47
|
exit
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
opts.on_tail('-h','--help', 'Show this message', &help)
|
51
51
|
help.call if ARGV.empty?
|
52
52
|
end.parse!
|
53
53
|
|
54
54
|
APPLICATION_COMMANDS = %w(status start stop restart unmonitor quit log)
|
55
55
|
|
56
|
-
controller = Bluepill::Controller.new(options.slice(:base_dir))
|
56
|
+
controller = Bluepill::Controller.new(options.slice(:base_dir, :log_file))
|
57
57
|
|
58
58
|
if controller.running_applications.include?(ARGV.first)
|
59
59
|
# the first arg is the application name
|
60
|
-
options[:application] = ARGV.shift
|
60
|
+
options[:application] = ARGV.shift
|
61
61
|
elsif APPLICATION_COMMANDS.include?(ARGV.first)
|
62
62
|
if controller.running_applications.length == 1
|
63
63
|
# there is only one, let's just use that
|
@@ -79,26 +79,22 @@ end
|
|
79
79
|
|
80
80
|
options[:command] = ARGV.shift
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
if options[:command] == "load"
|
83
|
+
file = ARGV.shift
|
84
|
+
if File.exists?(file)
|
85
|
+
# Restart the ruby interpreter for the config file so that anything loaded here
|
86
|
+
# does not stay in memory for the daemon
|
87
|
+
require 'rbconfig'
|
88
|
+
ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
|
89
|
+
load_path = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
90
|
+
file_path = File.expand_path(file)
|
91
|
+
|
92
|
+
exec(ruby, "-I#{load_path}", '-rbluepill', file_path)
|
93
|
+
|
87
94
|
else
|
88
95
|
$stderr.puts "Can't find file: #{file}"
|
89
96
|
end
|
90
|
-
when "log"
|
91
|
-
requested_pattern = ARGV.shift
|
92
|
-
grep_pattern = controller.send_command_to_application(options[:application], :grep_pattern, requested_pattern)
|
93
|
-
|
94
|
-
tail = "tail -n 100 -f #{options[:log_file]} | grep -E '#{grep_pattern}'"
|
95
|
-
puts "Tailing log for #{requested_pattern}..."
|
96
|
-
Kernel.exec(tail)
|
97
|
-
|
98
|
-
when *APPLICATION_COMMANDS
|
99
|
-
target = ARGV.shift
|
100
|
-
puts controller.send_command_to_application(options[:application], options[:command], target)
|
101
|
-
|
102
97
|
else
|
103
|
-
|
98
|
+
target = ARGV.shift
|
99
|
+
controller.handle_command(options[:application], options[:command], target)
|
104
100
|
end
|
data/bluepill.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{bluepill}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.25"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Arya Asemanfar", "Gary Tsang", "Rohith Ravi"]
|
12
|
-
s.date = %q{2009-11-
|
12
|
+
s.date = %q{2009-11-30}
|
13
13
|
s.default_executable = %q{bluepill}
|
14
14
|
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.}
|
15
15
|
s.email = %q{entombedvirus@gmail.com}
|
data/lib/bluepill/application.rb
CHANGED
@@ -1,25 +1,30 @@
|
|
1
1
|
module Bluepill
|
2
2
|
class Application
|
3
|
+
PROCESS_COMMANDS = [:start, :stop, :restart, :unmonitor]
|
4
|
+
|
3
5
|
attr_accessor :name, :logger, :base_dir, :socket, :pid_file
|
4
|
-
attr_accessor :groups, :work_queue
|
6
|
+
attr_accessor :groups, :work_queue
|
7
|
+
attr_accessor :pids_dir, :log_file
|
5
8
|
|
6
9
|
def initialize(name, options = {})
|
7
10
|
self.name = name
|
8
|
-
self.base_dir = options[:base_dir] ||= '/var/bluepill'
|
9
|
-
self.socket_timeout = options[:socket_timeout] ||= 10
|
10
|
-
|
11
|
-
self.logger = Bluepill::Logger.new(options.slice(:log_file)).prefix_with(self.name)
|
12
|
-
|
13
|
-
self.groups = Hash.new
|
14
11
|
|
12
|
+
self.log_file = options[:log_file]
|
13
|
+
self.base_dir = options[:base_dir] || '/var/bluepill'
|
15
14
|
self.pid_file = File.join(self.base_dir, 'pids', self.name + ".pid")
|
15
|
+
self.pids_dir = File.join(self.base_dir, 'pids', self.name)
|
16
|
+
|
17
|
+
self.groups = {}
|
16
18
|
|
17
|
-
|
19
|
+
self.logger = Bluepill::Logger.new(:log_file => self.log_file).prefix_with(self.name)
|
20
|
+
|
21
|
+
self.setup_signal_traps
|
22
|
+
self.setup_pids_dir
|
18
23
|
end
|
19
24
|
|
20
25
|
def load
|
21
26
|
begin
|
22
|
-
start_server
|
27
|
+
self.start_server
|
23
28
|
rescue StandardError => e
|
24
29
|
$stderr.puts "Failed to start bluepill:"
|
25
30
|
$stderr.puts "%s `%s`" % [e.class.name, e.message]
|
@@ -29,132 +34,88 @@ module Bluepill
|
|
29
34
|
end
|
30
35
|
|
31
36
|
def status
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
|
39
|
-
|
40
|
-
if p.monitor_children?
|
41
|
-
depth += 2
|
42
|
-
p.children.each do |c|
|
43
|
-
buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
|
44
|
-
end
|
45
|
-
depth -= 2
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
37
|
+
buffer = []
|
38
|
+
depth = 0
|
39
|
+
|
40
|
+
if self.groups.has_key?(nil)
|
41
|
+
self.groups[nil].processes.each do |p|
|
42
|
+
buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
|
49
43
|
|
50
|
-
|
51
|
-
next if group_name.nil?
|
52
|
-
|
53
|
-
buffer << "\n#{group_name}"
|
54
|
-
|
55
|
-
group.processes.each do |p|
|
44
|
+
if p.monitor_children?
|
56
45
|
depth += 2
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
if p.monitor_children?
|
61
|
-
depth += 2
|
62
|
-
p.children.each do |c|
|
63
|
-
buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
|
64
|
-
end
|
65
|
-
depth -= 2
|
46
|
+
p.children.each do |c|
|
47
|
+
buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
|
66
48
|
end
|
67
|
-
|
68
49
|
depth -= 2
|
69
50
|
end
|
70
51
|
end
|
71
|
-
|
72
|
-
buffer.join("\n")
|
73
|
-
|
74
|
-
else
|
75
|
-
send_to_server('status')
|
76
52
|
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def stop(process_or_group_name)
|
80
|
-
send_to_process_or_group(:stop, process_or_group_name)
|
81
|
-
end
|
82
|
-
|
83
|
-
def start(process_or_group_name)
|
84
|
-
send_to_process_or_group(:start, process_or_group_name)
|
85
|
-
end
|
86
53
|
|
87
|
-
|
88
|
-
|
89
|
-
end
|
54
|
+
self.groups.each do |group_name, group|
|
55
|
+
next if group_name.nil?
|
90
56
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
group.send(method)
|
103
|
-
else
|
104
|
-
self.groups.values.each do |group|
|
105
|
-
group.send(method, process_or_group_name)
|
57
|
+
buffer << "\n#{group_name}"
|
58
|
+
|
59
|
+
group.processes.each do |p|
|
60
|
+
depth += 2
|
61
|
+
|
62
|
+
buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
|
63
|
+
|
64
|
+
if p.monitor_children?
|
65
|
+
depth += 2
|
66
|
+
p.children.each do |c|
|
67
|
+
buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
|
106
68
|
end
|
69
|
+
depth -= 2
|
107
70
|
end
|
71
|
+
|
72
|
+
depth -= 2
|
108
73
|
end
|
109
|
-
return "ok"
|
110
|
-
else
|
111
|
-
send_to_server("#{method}:#{process_or_group_name}")
|
112
74
|
end
|
75
|
+
|
76
|
+
buffer.join("\n")
|
113
77
|
end
|
114
78
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
79
|
+
PROCESS_COMMANDS.each do |command|
|
80
|
+
class_eval <<-END
|
81
|
+
def #{command}(group_name, process_name = nil)
|
82
|
+
puts "got #{command}"
|
83
|
+
self.send_to_process_or_group(:#{command}, group_name, process_name)
|
84
|
+
end
|
85
|
+
END
|
122
86
|
end
|
123
87
|
|
124
88
|
def add_process(process, group_name = nil)
|
89
|
+
group_name = group_name.to_s if group_name
|
90
|
+
|
125
91
|
self.groups[group_name] ||= Group.new(group_name, :logger => self.logger.prefix_with(group_name))
|
126
92
|
self.groups[group_name].add_process(process)
|
127
93
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
|
141
|
-
rescue Errno::ECONNREFUSED
|
142
|
-
abort("Connection Refused: Server is not running")
|
94
|
+
|
95
|
+
protected
|
96
|
+
def send_to_process_or_group(method, group_name, process_name)
|
97
|
+
if self.groups.key?(group_name)
|
98
|
+
self.groups[group_name].send(method, process_name)
|
99
|
+
elsif process_name.nil?
|
100
|
+
# they must be targeting just by process name
|
101
|
+
process_name = group_name
|
102
|
+
self.groups.values.collect do |group|
|
103
|
+
group.send(method, process_name)
|
104
|
+
end.flatten
|
105
|
+
else
|
106
|
+
[]
|
143
107
|
end
|
144
|
-
buffer
|
145
108
|
end
|
146
109
|
|
147
|
-
private
|
148
|
-
|
149
110
|
def start_listener
|
150
111
|
@listener_thread.kill if @listener_thread
|
151
|
-
@listener_thread = Thread.new
|
112
|
+
@listener_thread = Thread.new do
|
152
113
|
begin
|
153
114
|
loop do
|
154
|
-
client =
|
155
|
-
|
156
|
-
response =
|
157
|
-
client.write(response)
|
115
|
+
client = self.socket.accept
|
116
|
+
command, *args = client.readline.strip.split(":")
|
117
|
+
response = self.send(command, *args)
|
118
|
+
client.write(Marshal.dump(response))
|
158
119
|
client.close
|
159
120
|
end
|
160
121
|
rescue StandardError => e
|
@@ -164,93 +125,88 @@ module Bluepill
|
|
164
125
|
end
|
165
126
|
end
|
166
127
|
|
167
|
-
def start_worker
|
168
|
-
@worker_thread.kill if @worker_thread
|
169
|
-
@worker_thread = Thread.new(self) do |app|
|
170
|
-
loop do
|
171
|
-
begin
|
172
|
-
job = app.work_queue.pop
|
173
|
-
send_to_process_or_group(job[0], job[1], false)
|
174
|
-
rescue StandardError => e
|
175
|
-
logger.err("Error while trying to execute %s from work_queue" % job.inspect)
|
176
|
-
logger.err("%s: `%s`" % [e.class.name, e.message])
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
128
|
def start_server
|
183
|
-
|
184
|
-
previous_pid = File.read(self.pid_file).to_i
|
185
|
-
begin
|
186
|
-
::Process.kill(0, previous_pid)
|
187
|
-
puts "Killing previous bluepilld[#{previous_pid}]"
|
188
|
-
::Process.kill(2, previous_pid)
|
189
|
-
rescue Exception => e
|
190
|
-
exit unless e.is_a?(Errno::ESRCH)
|
191
|
-
else
|
192
|
-
5.times do |i|
|
193
|
-
sleep 0.5
|
194
|
-
break unless System.pid_alive?(previous_pid)
|
195
|
-
end
|
196
|
-
|
197
|
-
if System.pid_alive?(previous_pid)
|
198
|
-
$stderr.puts "Previous bluepilld[#{previous_pid}] didn't die"
|
199
|
-
exit(4)
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
129
|
+
self.kill_previous_bluepill
|
203
130
|
|
204
131
|
Daemonize.daemonize
|
132
|
+
|
205
133
|
self.logger.reopen
|
206
134
|
|
207
|
-
@server = true
|
208
135
|
$0 = "bluepilld: #{self.name}"
|
209
136
|
|
210
|
-
self.
|
211
|
-
|
212
|
-
self.socket = Bluepill::Socket.new(name, base_dir).server
|
213
|
-
File.open(self.pid_file, 'w') { |x| x.write(::Process.pid) }
|
137
|
+
self.groups.each {|_, group| group.boot }
|
138
|
+
|
214
139
|
|
215
|
-
self.
|
140
|
+
self.write_pid_file
|
141
|
+
self.socket = Bluepill::Socket.server(self.base_dir, self.name)
|
142
|
+
self.start_listener
|
216
143
|
|
217
|
-
|
218
|
-
start_listener
|
219
|
-
start_worker
|
220
|
-
run
|
144
|
+
self.run
|
221
145
|
end
|
222
146
|
|
223
147
|
def run
|
224
|
-
|
148
|
+
@running = true # set to false by signal trap
|
149
|
+
while @running
|
225
150
|
System.reset_data
|
226
|
-
|
227
|
-
self.groups.each do |_, group|
|
228
|
-
group.tick
|
229
|
-
end
|
151
|
+
self.groups.each { |_, group| group.tick }
|
230
152
|
sleep 1
|
231
153
|
end
|
154
|
+
cleanup
|
155
|
+
end
|
156
|
+
|
157
|
+
def cleanup
|
158
|
+
File.unlink(self.socket.path) if self.socket
|
159
|
+
File.unlink(self.pid_file) if File.exists?(self.pid_file)
|
232
160
|
end
|
233
161
|
|
234
162
|
def setup_signal_traps
|
235
163
|
terminator = lambda do
|
236
164
|
puts "Terminating..."
|
237
|
-
|
238
|
-
File.unlink(self.pid_file) if File.exists?(self.pid_file)
|
239
|
-
::Kernel.exit
|
165
|
+
@running = false
|
240
166
|
end
|
241
167
|
|
242
168
|
Signal.trap("TERM", &terminator)
|
243
169
|
Signal.trap("INT", &terminator)
|
244
170
|
|
245
171
|
Signal.trap("HUP") do
|
246
|
-
self.logger.reopen
|
172
|
+
self.logger.reopen if self.logger
|
247
173
|
end
|
248
174
|
end
|
249
|
-
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
|
175
|
+
|
176
|
+
def setup_pids_dir
|
177
|
+
FileUtils.mkdir_p(self.pids_dir) unless File.exists?(self.pids_dir)
|
178
|
+
# we need everybody to be able to write to the pids_dir as processes managed by
|
179
|
+
# bluepill will be writing to this dir after they've dropped privileges
|
180
|
+
FileUtils.chmod(0777, self.pids_dir)
|
181
|
+
end
|
182
|
+
|
183
|
+
def kill_previous_bluepill
|
184
|
+
if File.exists?(self.pid_file)
|
185
|
+
previous_pid = File.read(self.pid_file).to_i
|
186
|
+
begin
|
187
|
+
::Process.kill(0, previous_pid)
|
188
|
+
puts "Killing previous bluepilld[#{previous_pid}]"
|
189
|
+
::Process.kill(2, previous_pid)
|
190
|
+
rescue Exception => e
|
191
|
+
$stderr.puts "Encountered error trying to kill previous bluepill:"
|
192
|
+
$stderr.puts "#{e.class}: #{e.message}"
|
193
|
+
exit(4) unless e.is_a?(Errno::ESRCH)
|
194
|
+
else
|
195
|
+
10.times do |i|
|
196
|
+
sleep 0.5
|
197
|
+
break unless System.pid_alive?(previous_pid)
|
198
|
+
end
|
199
|
+
|
200
|
+
if System.pid_alive?(previous_pid)
|
201
|
+
$stderr.puts "Previous bluepilld[#{previous_pid}] didn't die"
|
202
|
+
exit(4)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def write_pid_file
|
209
|
+
File.open(self.pid_file, 'w') { |x| x.write(::Process.pid) }
|
254
210
|
end
|
255
211
|
end
|
256
212
|
end
|
@@ -9,7 +9,7 @@ module Bluepill
|
|
9
9
|
@logger = options.delete(:logger)
|
10
10
|
@fires = options.has_key?(:fires) ? Array(options.delete(:fires)) : [:restart]
|
11
11
|
@every = options.delete(:every)
|
12
|
-
@times = options
|
12
|
+
@times = options.delete(:times) || [1,1]
|
13
13
|
@times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
|
14
14
|
|
15
15
|
self.clear_history!
|
data/lib/bluepill/controller.rb
CHANGED
@@ -2,14 +2,14 @@ require 'fileutils'
|
|
2
2
|
|
3
3
|
module Bluepill
|
4
4
|
class Controller
|
5
|
-
attr_accessor :base_dir, :sockets_dir, :pids_dir
|
6
|
-
attr_accessor :applications
|
5
|
+
attr_accessor :base_dir, :log_file, :sockets_dir, :pids_dir
|
7
6
|
|
8
7
|
def initialize(options = {})
|
8
|
+
self.log_file = options[:log_file]
|
9
9
|
self.base_dir = options[:base_dir]
|
10
10
|
self.sockets_dir = File.join(base_dir, 'socks')
|
11
11
|
self.pids_dir = File.join(base_dir, 'pids')
|
12
|
-
|
12
|
+
|
13
13
|
setup_dir_structure
|
14
14
|
cleanup_bluepill_directory
|
15
15
|
end
|
@@ -18,11 +18,67 @@ module Bluepill
|
|
18
18
|
Dir[File.join(sockets_dir, "*.sock")].map{|x| File.basename(x, ".sock")}
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
21
|
+
def handle_command(application, command, *args)
|
22
|
+
case command.to_sym
|
23
|
+
when *Application::PROCESS_COMMANDS
|
24
|
+
# these need to be sent to the daemon and the results printed out
|
25
|
+
affected = self.send_to_daemon(application, command, *args)
|
26
|
+
if affected.empty?
|
27
|
+
puts "No processes effected"
|
28
|
+
else
|
29
|
+
puts "Sent #{command} to:"
|
30
|
+
affected.each do |process|
|
31
|
+
puts " #{process}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
when :status
|
35
|
+
puts self.send_to_daemon(application, :status, *args)
|
36
|
+
when :quit
|
37
|
+
pid = pid_for(application)
|
38
|
+
if System.pid_alive?(pid)
|
39
|
+
::Process.kill("TERM", pid)
|
40
|
+
puts "Killing bluepilld[#{pid}]"
|
41
|
+
else
|
42
|
+
puts "bluepilld[#{pid}] not running"
|
43
|
+
end
|
44
|
+
when :log
|
45
|
+
log_file_location = self.send_to_daemon(application, :log_file)
|
46
|
+
log_file_location = self.log_file if log_file_location.to_s.strip.empty?
|
47
|
+
|
48
|
+
requested_pattern = args.first
|
49
|
+
grep_pattern = self.grep_pattern(application, requested_pattern)
|
50
|
+
|
51
|
+
tail = "tail -n 100 -f #{log_file_location} | grep -E '#{grep_pattern}'"
|
52
|
+
puts "Tailing log for #{requested_pattern}..."
|
53
|
+
Kernel.exec(tail)
|
54
|
+
else
|
55
|
+
$stderr.puts "Unknown command `%s` (or application `%s` has not been loaded yet)" % [command, command]
|
56
|
+
end
|
24
57
|
end
|
25
58
|
|
59
|
+
def send_to_daemon(application, command, *args)
|
60
|
+
|
61
|
+
begin
|
62
|
+
Timeout::timeout(Socket::TIMEOUT) do
|
63
|
+
buffer = ""
|
64
|
+
socket = Socket.client(base_dir, application) # Something that should be interrupted if it takes too much time...
|
65
|
+
socket.puts(([command] + args).join(":"))
|
66
|
+
while line = socket.gets
|
67
|
+
buffer << line
|
68
|
+
end
|
69
|
+
Marshal.load(buffer)
|
70
|
+
end
|
71
|
+
rescue Timeout::Error
|
72
|
+
abort("Socket Timeout: Server may not be responding")
|
73
|
+
rescue Errno::ECONNREFUSED
|
74
|
+
abort("Connection Refused: Server is not running")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def grep_pattern(application, query = nil)
|
79
|
+
pattern = [application, query].compact.join(':')
|
80
|
+
['\[.*', Regexp.escape(pattern), '.*'].compact.join
|
81
|
+
end
|
26
82
|
private
|
27
83
|
|
28
84
|
def cleanup_bluepill_directory
|
data/lib/bluepill/dsl.rb
CHANGED
@@ -58,6 +58,7 @@ module Bluepill
|
|
58
58
|
process.add_watch(name, opts)
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
61
62
|
process
|
62
63
|
end
|
63
64
|
end
|
@@ -65,23 +66,38 @@ module Bluepill
|
|
65
66
|
app_proxy = Class.new do
|
66
67
|
@@app = app
|
67
68
|
@@process_proxy = process_proxy
|
68
|
-
@@
|
69
|
+
@@process_keys = Hash.new # because I don't want to require Set just for validations
|
70
|
+
@@pid_files = Hash.new
|
69
71
|
attr_accessor :working_dir, :uid, :gid
|
70
72
|
|
71
73
|
def validate_process(process, process_name)
|
72
|
-
|
73
|
-
|
74
|
+
# validate uniqueness of group:process
|
75
|
+
process_key = [process.attributes[:group], process_name].join(":")
|
76
|
+
if @@process_keys.key?(process_key)
|
77
|
+
$stderr.print "Config Error: You have two entries for the process name '#{process_name}'"
|
78
|
+
$stderr.print " in the group '#{process.attributes[:group]}'" if process.attributes.key?(:group)
|
79
|
+
$stderr.puts
|
74
80
|
exit(6)
|
75
81
|
else
|
76
|
-
@@
|
82
|
+
@@process_keys[process_key] = 0
|
77
83
|
end
|
78
84
|
|
79
|
-
|
85
|
+
# validate required attributes
|
86
|
+
[:start_command].each do |required_attr|
|
80
87
|
if !process.attributes.key?(required_attr)
|
81
88
|
$stderr.puts "Config Error: You must specify a #{required_attr} for '#{process_name}'"
|
82
89
|
exit(6)
|
83
90
|
end
|
84
91
|
end
|
92
|
+
|
93
|
+
# validate uniqueness of pid files
|
94
|
+
pid_key = process.pid_file.strip
|
95
|
+
if @@pid_files.key?(pid_key)
|
96
|
+
$stderr.puts "Config Error: You have two entries with the pid file: #{process.pid_file}"
|
97
|
+
exit(6)
|
98
|
+
else
|
99
|
+
@@pid_files[pid_key] = 0
|
100
|
+
end
|
85
101
|
end
|
86
102
|
|
87
103
|
def process(process_name, &process_block)
|
@@ -89,11 +105,15 @@ module Bluepill
|
|
89
105
|
process_block.call(process_proxy)
|
90
106
|
set_app_wide_attributes(process_proxy)
|
91
107
|
|
108
|
+
assign_default_pid_file(process_proxy, process_name)
|
109
|
+
|
92
110
|
validate_process(process_proxy, process_name)
|
93
111
|
|
94
112
|
group = process_proxy.attributes.delete(:group)
|
95
113
|
process = process_proxy.to_process(process_name)
|
96
114
|
|
115
|
+
|
116
|
+
|
97
117
|
@@app.add_process(process, group)
|
98
118
|
end
|
99
119
|
|
@@ -104,6 +124,14 @@ module Bluepill
|
|
104
124
|
end
|
105
125
|
end
|
106
126
|
end
|
127
|
+
|
128
|
+
def assign_default_pid_file(process_proxy, process_name)
|
129
|
+
unless process_proxy.attributes.key?(:pid_file)
|
130
|
+
group_name = process_proxy.attributes["group"]
|
131
|
+
default_pid_name = [group_name, process_name].compact.join('_').gsub(/[^A-Za-z0-9_\-]/, "_")
|
132
|
+
process_proxy.pid_file = File.join(@@app.pids_dir, default_pid_name + ".pid")
|
133
|
+
end
|
134
|
+
end
|
107
135
|
end
|
108
136
|
|
109
137
|
yield(app_proxy.new)
|
data/lib/bluepill/group.rb
CHANGED
@@ -21,15 +21,18 @@ module Bluepill
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# proxied events
|
24
|
-
[:start, :unmonitor, :stop, :restart, :boot
|
24
|
+
[:start, :unmonitor, :stop, :restart, :boot].each do |event|
|
25
25
|
class_eval <<-END
|
26
26
|
def #{event}(process_name = nil)
|
27
27
|
threads = []
|
28
|
+
affected = []
|
28
29
|
self.processes.each do |process|
|
29
30
|
next if process_name && process_name != process.name
|
31
|
+
affected << [self.name, process.name].join(":")
|
30
32
|
threads << Thread.new { process.handle_user_command("#{event}") }
|
31
33
|
end
|
32
34
|
threads.each { |t| t.join }
|
35
|
+
affected
|
33
36
|
end
|
34
37
|
END
|
35
38
|
end
|
data/lib/bluepill/process.rb
CHANGED
data/lib/bluepill/socket.rb
CHANGED
@@ -1,41 +1,35 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
module Bluepill
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(name, base_dir)
|
8
|
-
self.name = name
|
9
|
-
self.base_dir = base_dir
|
10
|
-
end
|
4
|
+
module Socket
|
5
|
+
TIMEOUT = 10
|
6
|
+
extend self
|
11
7
|
|
12
|
-
def client
|
13
|
-
|
8
|
+
def client(base_dir, name)
|
9
|
+
UNIXSocket.open(socket_path(base_dir, name))
|
14
10
|
end
|
15
11
|
|
16
|
-
def server
|
12
|
+
def server(base_dir, name)
|
13
|
+
socket_path = self.socket_path(base_dir, name)
|
17
14
|
begin
|
18
|
-
|
15
|
+
UNIXServer.open(socket_path)
|
19
16
|
rescue Errno::EADDRINUSE
|
20
17
|
# if sock file has been created. test to see if there is a server
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
begin
|
19
|
+
return UNIXSocket.open(socket_path)
|
20
|
+
rescue Errno::ECONNREFUSED
|
21
|
+
File.delete(socket_path)
|
22
|
+
return UNIXServer.open(socket_path)
|
25
23
|
else
|
26
|
-
|
24
|
+
logger.err("Server is already running!")
|
25
|
+
exit(7)
|
27
26
|
end
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
|
-
def
|
32
|
-
File.
|
30
|
+
def socket_path(base_dir, name)
|
31
|
+
File.join(base_dir, 'socks', name + ".sock")
|
33
32
|
end
|
34
|
-
|
35
|
-
def socket_name
|
36
|
-
@socket_name ||= File.join(base_dir, 'socks', name + ".sock")
|
37
|
-
end
|
38
|
-
|
39
33
|
end
|
40
34
|
end
|
41
35
|
|
data/lib/bluepill/system.rb
CHANGED
@@ -4,6 +4,7 @@ module Bluepill
|
|
4
4
|
# This class represents the system that bluepill is running on.. It's mainly used to memoize
|
5
5
|
# results of running ps auxx etc so that every watch in the every process will not result in a fork
|
6
6
|
module System
|
7
|
+
APPEND_MODE = "a"
|
7
8
|
extend self
|
8
9
|
|
9
10
|
# The position of each field in ps output
|
@@ -209,15 +210,15 @@ module Bluepill
|
|
209
210
|
end
|
210
211
|
|
211
212
|
def redirect_io(io_in, io_out, io_err)
|
212
|
-
$stdin.reopen(
|
213
|
+
$stdin.reopen(io_in) if io_in
|
213
214
|
|
214
215
|
if !io_out.nil? && !io_err.nil? && io_out == io_err
|
215
|
-
$stdout.reopen(io_out)
|
216
|
+
$stdout.reopen(io_out, APPEND_MODE)
|
216
217
|
$stderr.reopen($stdout)
|
217
218
|
|
218
219
|
else
|
219
|
-
$stdout.reopen(io_out) if io_out
|
220
|
-
$stderr.reopen(io_err) if io_err
|
220
|
+
$stdout.reopen(io_out, APPEND_MODE) if io_out
|
221
|
+
$stderr.reopen(io_err, APPEND_MODE) if io_err
|
221
222
|
end
|
222
223
|
end
|
223
224
|
end
|
data/lib/bluepill/version.rb
CHANGED
data/lib/example.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Bluepill
|
2
|
+
module ProcessConditions
|
3
|
+
class ProcMemUsage < MemUsage
|
4
|
+
def run(pid)
|
5
|
+
rand(1000)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
4
10
|
|
5
11
|
ROOT_DIR = "/tmp/bp"
|
6
12
|
|
7
13
|
# Watch with
|
8
14
|
# watch -n0.2 'ps axu | egrep "(CPU|forking|bluepill|sleep)" | grep -v grep | sort'
|
9
|
-
Bluepill.application(:sample_app) do |app|
|
15
|
+
Bluepill.application(:sample_app, :log_file => "/Users/arya/Desktop/bp.log") do |app|
|
10
16
|
0.times do |i|
|
11
17
|
app.process("process_#{i}") do |process|
|
12
18
|
process.pid_file = "#{ROOT_DIR}/pids/process_#{i}.pid"
|
@@ -61,7 +67,7 @@ Bluepill.application(:sample_app) do |app|
|
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
64
|
-
|
70
|
+
2.times do |i|
|
65
71
|
app.process("group_process_#{i}") do |process|
|
66
72
|
process.uid = "arya"
|
67
73
|
process.gid = "wheel"
|
@@ -71,10 +77,9 @@ Bluepill.application(:sample_app) do |app|
|
|
71
77
|
|
72
78
|
|
73
79
|
process.group = "grouped"
|
74
|
-
|
75
|
-
process.start_command = "sleep 10"
|
80
|
+
process.start_command = %Q{ruby -e '$stderr.puts("hello stderr");$stdout.puts("hello stdout"); $stdout.flush; $stderr.flush; sleep 10'}
|
76
81
|
process.daemonize = true
|
77
|
-
process.pid_file = "/tmp/p_#{process.group}
|
82
|
+
# process.pid_file = "/tmp/p_#{process.group}#{i}_.pid"
|
78
83
|
|
79
84
|
# process.checks :always_true, :every => 5
|
80
85
|
process.checks :cpu_usage,
|
@@ -87,6 +92,11 @@ Bluepill.application(:sample_app) do |app|
|
|
87
92
|
:below => 600.megabytes,
|
88
93
|
:times => [3, 5],
|
89
94
|
:fires => [:stop]
|
95
|
+
|
96
|
+
process.checks :proc_mem_usage,
|
97
|
+
:every => 3,
|
98
|
+
:below => 600.megabytes,
|
99
|
+
:times => [3, 5]
|
90
100
|
end
|
91
101
|
end
|
92
102
|
end
|
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.25
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arya Asemanfar
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2009-11-
|
14
|
+
date: 2009-11-30 00:00:00 -08:00
|
15
15
|
default_executable: bluepill
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|