strobemonkey-god 0.7.13
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/History.txt +289 -0
- data/Manifest.txt +114 -0
- data/README.txt +59 -0
- data/Rakefile +35 -0
- data/bin/god +128 -0
- data/examples/events.god +84 -0
- data/examples/gravatar.god +54 -0
- data/examples/single.god +66 -0
- data/ext/god/extconf.rb +55 -0
- data/ext/god/kqueue_handler.c +123 -0
- data/ext/god/netlink_handler.c +167 -0
- data/init/god +42 -0
- data/lib/god/behavior.rb +52 -0
- data/lib/god/behaviors/clean_pid_file.rb +21 -0
- data/lib/god/behaviors/clean_unix_socket.rb +21 -0
- data/lib/god/behaviors/notify_when_flapping.rb +51 -0
- data/lib/god/cli/command.rb +229 -0
- data/lib/god/cli/run.rb +176 -0
- data/lib/god/cli/version.rb +23 -0
- data/lib/god/condition.rb +96 -0
- data/lib/god/conditions/always.rb +23 -0
- data/lib/god/conditions/complex.rb +86 -0
- data/lib/god/conditions/cpu_usage.rb +80 -0
- data/lib/god/conditions/degrading_lambda.rb +52 -0
- data/lib/god/conditions/disk_usage.rb +27 -0
- data/lib/god/conditions/file_mtime.rb +28 -0
- data/lib/god/conditions/flapping.rb +128 -0
- data/lib/god/conditions/http_response_code.rb +168 -0
- data/lib/god/conditions/lambda.rb +25 -0
- data/lib/god/conditions/memory_usage.rb +82 -0
- data/lib/god/conditions/process_exits.rb +72 -0
- data/lib/god/conditions/process_running.rb +74 -0
- data/lib/god/conditions/tries.rb +44 -0
- data/lib/god/configurable.rb +57 -0
- data/lib/god/contact.rb +106 -0
- data/lib/god/contacts/campfire.rb +82 -0
- data/lib/god/contacts/email.rb +95 -0
- data/lib/god/contacts/jabber.rb +65 -0
- data/lib/god/contacts/twitter.rb +39 -0
- data/lib/god/contacts/webhook.rb +47 -0
- data/lib/god/dependency_graph.rb +41 -0
- data/lib/god/diagnostics.rb +37 -0
- data/lib/god/driver.rb +206 -0
- data/lib/god/errors.rb +24 -0
- data/lib/god/event_handler.rb +111 -0
- data/lib/god/event_handlers/dummy_handler.rb +13 -0
- data/lib/god/event_handlers/kqueue_handler.rb +17 -0
- data/lib/god/event_handlers/netlink_handler.rb +13 -0
- data/lib/god/logger.rb +120 -0
- data/lib/god/metric.rb +59 -0
- data/lib/god/process.rb +341 -0
- data/lib/god/registry.rb +32 -0
- data/lib/god/simple_logger.rb +53 -0
- data/lib/god/socket.rb +96 -0
- data/lib/god/sugar.rb +47 -0
- data/lib/god/system/portable_poller.rb +42 -0
- data/lib/god/system/process.rb +42 -0
- data/lib/god/system/slash_proc_poller.rb +92 -0
- data/lib/god/task.rb +491 -0
- data/lib/god/timeline.rb +25 -0
- data/lib/god/trigger.rb +43 -0
- data/lib/god/watch.rb +183 -0
- data/lib/god.rb +667 -0
- data/test/configs/child_events/child_events.god +44 -0
- data/test/configs/child_events/simple_server.rb +3 -0
- data/test/configs/child_polls/child_polls.god +37 -0
- data/test/configs/child_polls/simple_server.rb +12 -0
- data/test/configs/complex/complex.god +59 -0
- data/test/configs/complex/simple_server.rb +3 -0
- data/test/configs/contact/contact.god +84 -0
- data/test/configs/contact/simple_server.rb +3 -0
- data/test/configs/daemon_events/daemon_events.god +37 -0
- data/test/configs/daemon_events/simple_server.rb +8 -0
- data/test/configs/daemon_events/simple_server_stop.rb +11 -0
- data/test/configs/daemon_polls/daemon_polls.god +17 -0
- data/test/configs/daemon_polls/simple_server.rb +6 -0
- data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
- data/test/configs/degrading_lambda/tcp_server.rb +15 -0
- data/test/configs/matias/matias.god +50 -0
- data/test/configs/real.rb +59 -0
- data/test/configs/running_load/running_load.god +16 -0
- data/test/configs/stress/simple_server.rb +3 -0
- data/test/configs/stress/stress.god +15 -0
- data/test/configs/task/logs/.placeholder +0 -0
- data/test/configs/task/task.god +26 -0
- data/test/configs/test.rb +61 -0
- data/test/helper.rb +151 -0
- data/test/suite.rb +6 -0
- data/test/test_behavior.rb +21 -0
- data/test/test_campfire.rb +41 -0
- data/test/test_condition.rb +50 -0
- data/test/test_conditions_disk_usage.rb +56 -0
- data/test/test_conditions_http_response_code.rb +109 -0
- data/test/test_conditions_process_running.rb +44 -0
- data/test/test_conditions_tries.rb +67 -0
- data/test/test_contact.rb +109 -0
- data/test/test_dependency_graph.rb +62 -0
- data/test/test_driver.rb +11 -0
- data/test/test_email.rb +45 -0
- data/test/test_event_handler.rb +80 -0
- data/test/test_god.rb +598 -0
- data/test/test_handlers_kqueue_handler.rb +16 -0
- data/test/test_logger.rb +63 -0
- data/test/test_metric.rb +72 -0
- data/test/test_process.rb +246 -0
- data/test/test_registry.rb +15 -0
- data/test/test_socket.rb +42 -0
- data/test/test_sugar.rb +42 -0
- data/test/test_system_portable_poller.rb +17 -0
- data/test/test_system_process.rb +30 -0
- data/test/test_task.rb +262 -0
- data/test/test_timeline.rb +37 -0
- data/test/test_trigger.rb +59 -0
- data/test/test_watch.rb +279 -0
- metadata +193 -0
data/lib/god/process.rb
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
module God
|
|
2
|
+
class Process
|
|
3
|
+
WRITES_PID = [:start, :restart]
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :uid, :gid, :log, :log_cmd, :start, :stop, :restart, :unix_socket, :chroot, :env
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
self.log = '/dev/null'
|
|
9
|
+
|
|
10
|
+
@pid_file = nil
|
|
11
|
+
@tracking_pid = true
|
|
12
|
+
@user_log = false
|
|
13
|
+
@pid = nil
|
|
14
|
+
@unix_socket = nil
|
|
15
|
+
@log_cmd = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def alive?
|
|
19
|
+
if self.pid
|
|
20
|
+
System::Process.new(self.pid).exists?
|
|
21
|
+
else
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def file_writable?(file)
|
|
27
|
+
pid = fork do
|
|
28
|
+
uid_num = Etc.getpwnam(self.uid).uid if self.uid
|
|
29
|
+
gid_num = Etc.getgrnam(self.gid).gid if self.gid
|
|
30
|
+
|
|
31
|
+
::Dir.chroot(self.chroot) if self.chroot
|
|
32
|
+
::Process.groups = [gid_num] if self.gid
|
|
33
|
+
::Process::Sys.setgid(gid_num) if self.gid
|
|
34
|
+
::Process::Sys.setuid(uid_num) if self.uid
|
|
35
|
+
|
|
36
|
+
File.writable?(file_in_chroot(file)) ? exit(0) : exit(1)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
wpid, status = ::Process.waitpid2(pid)
|
|
40
|
+
status.exitstatus == 0 ? true : false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def valid?
|
|
44
|
+
# determine if we're tracking pid or not
|
|
45
|
+
self.pid_file
|
|
46
|
+
|
|
47
|
+
valid = true
|
|
48
|
+
|
|
49
|
+
# a start command must be specified
|
|
50
|
+
if self.start.nil?
|
|
51
|
+
valid = false
|
|
52
|
+
applog(self, :error, "No start command was specified")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# self-daemonizing processes must specify a stop command
|
|
56
|
+
if !@tracking_pid && self.stop.nil?
|
|
57
|
+
valid = false
|
|
58
|
+
applog(self, :error, "No stop command was specified")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# uid must exist if specified
|
|
62
|
+
if self.uid
|
|
63
|
+
begin
|
|
64
|
+
Etc.getpwnam(self.uid)
|
|
65
|
+
rescue ArgumentError
|
|
66
|
+
valid = false
|
|
67
|
+
applog(self, :error, "UID for '#{self.uid}' does not exist")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# gid must exist if specified
|
|
72
|
+
if self.gid
|
|
73
|
+
begin
|
|
74
|
+
Etc.getgrnam(self.gid)
|
|
75
|
+
rescue ArgumentError
|
|
76
|
+
valid = false
|
|
77
|
+
applog(self, :error, "GID for '#{self.gid}' does not exist")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# pid dir must exist if specified
|
|
82
|
+
if !@tracking_pid && !File.exist?(File.dirname(self.pid_file))
|
|
83
|
+
valid = false
|
|
84
|
+
applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# pid dir must be writable if specified
|
|
88
|
+
if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file))
|
|
89
|
+
valid = false
|
|
90
|
+
applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# log dir must exist
|
|
94
|
+
if !File.exist?(File.dirname(self.log))
|
|
95
|
+
valid = false
|
|
96
|
+
applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# log file or dir must be writable
|
|
100
|
+
if File.exist?(self.log)
|
|
101
|
+
unless file_writable?(self.log)
|
|
102
|
+
valid = false
|
|
103
|
+
applog(self, :error, "Log file '#{self.log}' exists but is not writable by #{self.uid || Etc.getlogin}")
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
unless file_writable?(File.dirname(self.log))
|
|
107
|
+
valid = false
|
|
108
|
+
applog(self, :error, "Log directory '#{File.dirname(self.log)}' is not writable by #{self.uid || Etc.getlogin}")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# chroot directory must exist and have /dev/null in it
|
|
113
|
+
if self.chroot
|
|
114
|
+
if !File.directory?(self.chroot)
|
|
115
|
+
valid = false
|
|
116
|
+
LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not exist")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if !File.exist?(File.join(self.chroot, '/dev/null'))
|
|
120
|
+
valid = false
|
|
121
|
+
LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not contain '/dev/null'")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
valid
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
|
|
129
|
+
# No really, trust me. Use the instance variable.
|
|
130
|
+
def pid_file=(value)
|
|
131
|
+
# if value is nil, do the right thing
|
|
132
|
+
if value
|
|
133
|
+
@tracking_pid = false
|
|
134
|
+
else
|
|
135
|
+
@tracking_pid = true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
@pid_file = value
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def pid_file
|
|
142
|
+
@pid_file ||= default_pid_file
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Fetch the PID from pid_file. If the pid_file does not
|
|
146
|
+
# exist, then use the PID from the last time it was read.
|
|
147
|
+
# If it has never been read, then return nil.
|
|
148
|
+
#
|
|
149
|
+
# Returns Integer(pid) or nil
|
|
150
|
+
def pid
|
|
151
|
+
contents = File.read(self.pid_file).strip rescue ''
|
|
152
|
+
real_pid = contents =~ /^\d+$/ ? contents.to_i : nil
|
|
153
|
+
|
|
154
|
+
if real_pid
|
|
155
|
+
@pid = real_pid
|
|
156
|
+
real_pid
|
|
157
|
+
else
|
|
158
|
+
@pid
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Send the given signal to this process.
|
|
163
|
+
#
|
|
164
|
+
# Returns nothing
|
|
165
|
+
def signal(sig)
|
|
166
|
+
sig = sig.to_i if sig.to_i != 0
|
|
167
|
+
applog(self, :info, "#{self.name} sending signal '#{sig}' to pid #{self.pid}")
|
|
168
|
+
::Process.kill(sig, self.pid) rescue nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def start!
|
|
172
|
+
call_action(:start)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def stop!
|
|
176
|
+
call_action(:stop)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def restart!
|
|
180
|
+
call_action(:restart)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def default_pid_file
|
|
184
|
+
File.join(God.pid_file_directory, "#{self.name}.pid")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def call_action(action)
|
|
188
|
+
command = send(action)
|
|
189
|
+
|
|
190
|
+
if action == :stop && command.nil?
|
|
191
|
+
pid = self.pid
|
|
192
|
+
name = self.name
|
|
193
|
+
command = lambda do
|
|
194
|
+
applog(self, :info, "#{self.name} stop: default lambda killer")
|
|
195
|
+
|
|
196
|
+
::Process.kill('TERM', pid) rescue nil
|
|
197
|
+
applog(self, :info, "#{self.name} sent SIGTERM")
|
|
198
|
+
|
|
199
|
+
# Poll to see if it's dead
|
|
200
|
+
5.times do
|
|
201
|
+
begin
|
|
202
|
+
::Process.kill(0, pid)
|
|
203
|
+
rescue Errno::ESRCH
|
|
204
|
+
# It died. Good.
|
|
205
|
+
applog(self, :info, "#{self.name} process stopped")
|
|
206
|
+
return
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
sleep 1
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
::Process.kill('KILL', pid) rescue nil
|
|
213
|
+
applog(self, :info, "#{self.name} still alive; sent SIGKILL")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
if command.kind_of?(String)
|
|
218
|
+
pid = nil
|
|
219
|
+
|
|
220
|
+
if @tracking_pid
|
|
221
|
+
# double fork god-daemonized processes
|
|
222
|
+
# we don't want to wait for them to finish
|
|
223
|
+
r, w = IO.pipe
|
|
224
|
+
begin
|
|
225
|
+
opid = fork do
|
|
226
|
+
STDOUT.reopen(w)
|
|
227
|
+
r.close
|
|
228
|
+
pid = self.spawn(command)
|
|
229
|
+
puts pid.to_s # send pid back to forker
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
::Process.waitpid(opid, 0)
|
|
233
|
+
w.close
|
|
234
|
+
pid = r.gets.chomp
|
|
235
|
+
ensure
|
|
236
|
+
# make sure the file descriptors get closed no matter what
|
|
237
|
+
r.close rescue nil
|
|
238
|
+
w.close rescue nil
|
|
239
|
+
end
|
|
240
|
+
else
|
|
241
|
+
# single fork self-daemonizing processes
|
|
242
|
+
# we want to wait for them to finish
|
|
243
|
+
pid = self.spawn(command)
|
|
244
|
+
status = ::Process.waitpid2(pid, 0)
|
|
245
|
+
exit_code = status[1] >> 8
|
|
246
|
+
|
|
247
|
+
if exit_code != 0
|
|
248
|
+
applog(self, :warn, "#{self.name} #{action} command exited with non-zero code = #{exit_code}")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
ensure_stop if action == :stop
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
|
|
255
|
+
File.open(default_pid_file, 'w') do |f|
|
|
256
|
+
f.write pid
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
@tracking_pid = true
|
|
260
|
+
@pid_file = default_pid_file
|
|
261
|
+
end
|
|
262
|
+
elsif command.kind_of?(Proc)
|
|
263
|
+
# lambda command
|
|
264
|
+
command.call
|
|
265
|
+
else
|
|
266
|
+
raise NotImplementedError
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Fork/exec the given command, returns immediately
|
|
271
|
+
# +command+ is the String containing the shell command
|
|
272
|
+
#
|
|
273
|
+
# Returns nothing
|
|
274
|
+
def spawn(command)
|
|
275
|
+
fork do
|
|
276
|
+
uid_num = Etc.getpwnam(self.uid).uid if self.uid
|
|
277
|
+
gid_num = Etc.getgrnam(self.gid).gid if self.gid
|
|
278
|
+
|
|
279
|
+
::Dir.chroot(self.chroot) if self.chroot
|
|
280
|
+
::Process.setsid
|
|
281
|
+
::Process.groups = [gid_num] if self.gid
|
|
282
|
+
::Process::Sys.setgid(gid_num) if self.gid
|
|
283
|
+
::Process::Sys.setuid(uid_num) if self.uid
|
|
284
|
+
Dir.chdir "/"
|
|
285
|
+
$0 = command
|
|
286
|
+
STDIN.reopen "/dev/null"
|
|
287
|
+
if self.log_cmd
|
|
288
|
+
STDOUT.reopen IO.popen(self.log_cmd, "a")
|
|
289
|
+
else
|
|
290
|
+
STDOUT.reopen file_in_chroot(self.log), "a"
|
|
291
|
+
end
|
|
292
|
+
STDERR.reopen STDOUT
|
|
293
|
+
|
|
294
|
+
# close any other file descriptors
|
|
295
|
+
3.upto(256){|fd| IO::new(fd).close rescue nil}
|
|
296
|
+
|
|
297
|
+
if self.env && self.env.is_a?(Hash)
|
|
298
|
+
self.env.each do |(key, value)|
|
|
299
|
+
ENV[key] = value
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
exec command unless command.empty?
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Ensure that a stop command actually stops the process. Force kill
|
|
308
|
+
# if necessary.
|
|
309
|
+
#
|
|
310
|
+
# Returns nothing
|
|
311
|
+
def ensure_stop
|
|
312
|
+
unless self.pid
|
|
313
|
+
applog(self, :warn, "#{self.name} stop called but pid is uknown")
|
|
314
|
+
return
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Poll to see if it's dead
|
|
318
|
+
10.times do
|
|
319
|
+
begin
|
|
320
|
+
::Process.kill(0, self.pid)
|
|
321
|
+
rescue Errno::ESRCH
|
|
322
|
+
# It died. Good.
|
|
323
|
+
return
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
sleep 1
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# last resort
|
|
330
|
+
::Process.kill('KILL', self.pid) rescue nil
|
|
331
|
+
applog(self, :warn, "#{self.name} process still running 10 seconds after stop command returned. Force killing.")
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
private
|
|
335
|
+
def file_in_chroot(file)
|
|
336
|
+
return file unless self.chroot
|
|
337
|
+
|
|
338
|
+
file.gsub(/^#{Regexp.escape(File.expand_path(self.chroot))}/, '')
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
data/lib/god/registry.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module God
|
|
2
|
+
def self.registry
|
|
3
|
+
@registry ||= Registry.new
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Registry
|
|
7
|
+
def initialize
|
|
8
|
+
@storage = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add(item)
|
|
12
|
+
# raise TypeError unless item.is_a? God::Process
|
|
13
|
+
@storage[item.name] = item
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def remove(item)
|
|
17
|
+
@storage.delete(item.name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def size
|
|
21
|
+
@storage.size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def [](name)
|
|
25
|
+
@storage[name]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def reset
|
|
29
|
+
@storage.clear
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module God
|
|
2
|
+
|
|
3
|
+
class SimpleLogger
|
|
4
|
+
DEBUG = 2
|
|
5
|
+
INFO = 4
|
|
6
|
+
WARN = 8
|
|
7
|
+
ERROR = 16
|
|
8
|
+
FATAL = 32
|
|
9
|
+
|
|
10
|
+
SEV_LABEL = {DEBUG => 'DEBUG',
|
|
11
|
+
INFO => 'INFO',
|
|
12
|
+
WARN => 'WARN',
|
|
13
|
+
ERROR => 'ERROR',
|
|
14
|
+
FATAL => 'FATAL'}
|
|
15
|
+
|
|
16
|
+
attr_accessor :datetime_format, :level
|
|
17
|
+
|
|
18
|
+
def initialize(io)
|
|
19
|
+
@io = io
|
|
20
|
+
@level = INFO
|
|
21
|
+
@datetime_format = "%Y-%m-%d %H:%M:%S"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output(level, msg)
|
|
25
|
+
return if level < self.level
|
|
26
|
+
|
|
27
|
+
time = Time.now.strftime(self.datetime_format)
|
|
28
|
+
label = SEV_LABEL[level]
|
|
29
|
+
@io.print("#{label[0..0]} [#{time}] #{label.rjust(5)}: #{msg}\n")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fatal(msg)
|
|
33
|
+
self.output(FATAL, msg)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def error(msg)
|
|
37
|
+
self.output(ERROR, msg)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def warn(msg)
|
|
41
|
+
self.output(WARN, msg)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def info(msg)
|
|
45
|
+
self.output(INFO, msg)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def debug(msg)
|
|
49
|
+
self.output(DEBUG, msg)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
data/lib/god/socket.rb
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
|
|
3
|
+
module God
|
|
4
|
+
|
|
5
|
+
# The God::Server oversees the DRb server which dishes out info on this God daemon.
|
|
6
|
+
class Socket
|
|
7
|
+
attr_reader :port
|
|
8
|
+
|
|
9
|
+
# The location of the socket for a given port
|
|
10
|
+
# +port+ is the port number
|
|
11
|
+
#
|
|
12
|
+
# Returns String (file location)
|
|
13
|
+
def self.socket_file(port)
|
|
14
|
+
"/tmp/god.#{port}.sock"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The address of the socket for a given port
|
|
18
|
+
# +port+ is the port number
|
|
19
|
+
#
|
|
20
|
+
# Returns String (drb address)
|
|
21
|
+
def self.socket(port)
|
|
22
|
+
"drbunix://#{self.socket_file(port)}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The location of the socket for this Server
|
|
26
|
+
#
|
|
27
|
+
# Returns String (file location)
|
|
28
|
+
def socket_file
|
|
29
|
+
self.class.socket_file(@port)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The address of the socket for this Server
|
|
33
|
+
#
|
|
34
|
+
# Returns String (drb address)
|
|
35
|
+
def socket
|
|
36
|
+
self.class.socket(@port)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Create a new Server and star the DRb server
|
|
40
|
+
# +port+ is the port on which to start the DRb service (default nil)
|
|
41
|
+
def initialize(port = nil)
|
|
42
|
+
@port = port
|
|
43
|
+
start
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns true
|
|
47
|
+
def ping
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Forward API calls to God
|
|
52
|
+
#
|
|
53
|
+
# Returns whatever the forwarded call returns
|
|
54
|
+
def method_missing(*args, &block)
|
|
55
|
+
God.send(*args, &block)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Stop the DRb server and delete the socket file
|
|
59
|
+
#
|
|
60
|
+
# Returns nothing
|
|
61
|
+
def stop
|
|
62
|
+
DRb.stop_service
|
|
63
|
+
FileUtils.rm_f(self.socket_file)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Start the DRb server. Abort if there is already a running god instance
|
|
69
|
+
# on the socket.
|
|
70
|
+
#
|
|
71
|
+
# Returns nothing
|
|
72
|
+
def start
|
|
73
|
+
begin
|
|
74
|
+
@drb ||= DRb.start_service(self.socket, self)
|
|
75
|
+
applog(nil, :info, "Started on #{DRb.uri}")
|
|
76
|
+
rescue Errno::EADDRINUSE
|
|
77
|
+
applog(nil, :info, "Socket already in use")
|
|
78
|
+
DRb.start_service
|
|
79
|
+
server = DRbObject.new(nil, self.socket)
|
|
80
|
+
|
|
81
|
+
begin
|
|
82
|
+
Timeout.timeout(5) do
|
|
83
|
+
server.ping
|
|
84
|
+
end
|
|
85
|
+
abort "Socket #{self.socket} already in use by another instance of god"
|
|
86
|
+
rescue StandardError, Timeout::Error
|
|
87
|
+
applog(nil, :info, "Socket is stale, reopening")
|
|
88
|
+
File.delete(self.socket_file) rescue nil
|
|
89
|
+
@drb ||= DRb.start_service(self.socket, self)
|
|
90
|
+
applog(nil, :info, "Started on #{DRb.uri}")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
data/lib/god/sugar.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class Numeric
|
|
2
|
+
def seconds
|
|
3
|
+
self
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
alias :second :seconds
|
|
7
|
+
|
|
8
|
+
def minutes
|
|
9
|
+
self * 60
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
alias :minute :minutes
|
|
13
|
+
|
|
14
|
+
def hours
|
|
15
|
+
self * 3600
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
alias :hour :hours
|
|
19
|
+
|
|
20
|
+
def days
|
|
21
|
+
self * 86400
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
alias :day :days
|
|
25
|
+
|
|
26
|
+
def kilobytes
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias :kilobyte :kilobytes
|
|
31
|
+
|
|
32
|
+
def megabytes
|
|
33
|
+
self * 1024
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias :megabyte :megabytes
|
|
37
|
+
|
|
38
|
+
def gigabytes
|
|
39
|
+
self * (1024 ** 2)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
alias :gigabyte :gigabytes
|
|
43
|
+
|
|
44
|
+
def percent
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module God
|
|
2
|
+
module System
|
|
3
|
+
class PortablePoller
|
|
4
|
+
def initialize(pid)
|
|
5
|
+
@pid = pid
|
|
6
|
+
end
|
|
7
|
+
# Memory usage in kilobytes (resident set size)
|
|
8
|
+
def memory
|
|
9
|
+
ps_int('rss')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Percentage memory usage
|
|
13
|
+
def percent_memory
|
|
14
|
+
ps_float('pmem')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Percentage CPU usage
|
|
18
|
+
def percent_cpu
|
|
19
|
+
ps_float('pcpu')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def ps_int(keyword)
|
|
25
|
+
`ps -o #{keyword}= -p #{@pid}`.to_i
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ps_float(keyword)
|
|
29
|
+
`ps -o #{keyword}= -p #{@pid}`.to_f
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ps_string(keyword)
|
|
33
|
+
`ps -o #{keyword}= -p #{@pid}`.strip
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def time_string_to_seconds(text)
|
|
37
|
+
_, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/)
|
|
38
|
+
(minutes.to_i * 60) + seconds.to_i
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module God
|
|
2
|
+
module System
|
|
3
|
+
|
|
4
|
+
class Process
|
|
5
|
+
def initialize(pid)
|
|
6
|
+
@pid = pid.to_i
|
|
7
|
+
@poller = fetch_system_poller.new(@pid)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Return true if this process is running, false otherwise
|
|
11
|
+
def exists?
|
|
12
|
+
!!::Process.kill(0, @pid) rescue false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Memory usage in kilobytes (resident set size)
|
|
16
|
+
def memory
|
|
17
|
+
@poller.memory
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Percentage memory usage
|
|
21
|
+
def percent_memory
|
|
22
|
+
@poller.percent_memory
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Percentage CPU usage
|
|
26
|
+
def percent_cpu
|
|
27
|
+
@poller.percent_cpu
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def fetch_system_poller
|
|
33
|
+
if SlashProcPoller.usable?
|
|
34
|
+
SlashProcPoller
|
|
35
|
+
else
|
|
36
|
+
PortablePoller
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|