/
god.rb
298 lines (234 loc) · 6.34 KB
/
god.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
# core
require 'logger'
# stdlib
require 'syslog'
# internal requires
require 'god/errors'
require 'god/logger'
require 'god/system/process'
require 'god/dependency_graph'
require 'god/timeline'
require 'god/behavior'
require 'god/behaviors/clean_pid_file'
require 'god/behaviors/notify_when_flapping'
require 'god/condition'
require 'god/conditions/process_running'
require 'god/conditions/process_exits'
require 'god/conditions/tries'
require 'god/conditions/memory_usage'
require 'god/conditions/cpu_usage'
require 'god/conditions/always'
require 'god/conditions/lambda'
require 'god/conditions/degrading_lambda'
require 'god/reporter'
require 'god/server'
require 'god/timer'
require 'god/hub'
require 'god/metric'
require 'god/watch'
require 'god/event_handler'
require 'god/registry'
require 'god/process'
require 'god/sugar'
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
begin
Syslog.open('god')
rescue RuntimeError
Syslog.reopen('god')
end
God::EventHandler.load
module God
VERSION = '0.3.2'
LOG = Logger.new
LOG_BUFFER_SIZE_DEFAULT = 100
PID_FILE_DIRECTORY_DEFAULT = '/var/run/god'
DRB_PORT_DEFAULT = 17165
DRB_ALLOW_DEFAULT = ['localhost']
class << self
# user configurable
attr_accessor :host,
:port,
:allow,
:log_buffer_size,
:pid_file_directory
# internal
attr_accessor :inited,
:running,
:pending_watches,
:server,
:watches,
:groups
end
def self.init
if self.inited
abort "God.init must be called before any Watches"
end
self.internal_init
end
def self.internal_init
# only do this once
return if self.inited
# variable init
self.watches = {}
self.groups = {}
self.pending_watches = []
# set defaults
self.log_buffer_size = LOG_BUFFER_SIZE_DEFAULT
self.pid_file_directory = PID_FILE_DIRECTORY_DEFAULT
self.port = DRB_PORT_DEFAULT
self.allow = DRB_ALLOW_DEFAULT
# yield to the config file
yield self if block_given?
# init has been executed
self.inited = true
# not yet running
self.running = false
end
# Instantiate a new, empty Watch object and pass it to the mandatory
# block. The attributes of the watch will be set by the configuration
# file.
def self.watch
self.internal_init
w = Watch.new
yield(w)
# if running, completely remove the watch (if necessary) to
# prepare for the reload
existing_watch = self.watches[w.name]
if self.running && existing_watch
self.unwatch(existing_watch)
end
# ensure the new watch has a unique name
if self.watches[w.name] || self.groups[w.name]
abort "Watch name '#{w.name}' already used for a Watch or Group"
end
# ensure watch is internally valid
w.valid? || abort("Watch '#{w.name}' is not valid (see above)")
# add to list of watches
self.watches[w.name] = w
# add to pending watches
self.pending_watches << w
# add to group if specified
if w.group
# ensure group name hasn't been used for a watch already
if self.watches[w.group]
abort "Group name '#{w.group}' already used for a Watch"
end
self.groups[w.group] ||= []
self.groups[w.group] << w
end
# register watch
w.register!
end
def self.unwatch(watch)
# unmonitor
watch.unmonitor
# unregister
watch.unregister!
# remove from watches
self.watches.delete(watch.name)
# remove from groups
if watch.group
self.groups[watch.group].delete(watch)
end
end
def self.ping
true
end
def self.control(name, command)
# get the list of watches
watches = Array(self.watches[name] || self.groups[name])
# do the command
case command
when "start", "monitor"
watches.each { |w| w.monitor }
when "restart"
watches.each { |w| w.move(:restart) }
when "stop"
watches.each { |w| w.unmonitor.action(:stop) }
when "unmonitor"
watches.each { |w| w.unmonitor }
else
raise InvalidCommandError.new
end
watches
end
def self.stop_all
self.watches.each do |name, w|
w.unmonitor if w.state
w.action(:stop) if w.alive?
end
10.times do
return true unless self.watches.map { |name, w| w.alive? }.any?
sleep 1
end
return false
end
def self.terminate
exit!(0)
end
def self.status
self.watches.map do |name, w|
status = w.state || :unmonitored
"#{name}: #{status}"
end.join("\n")
end
def self.running_log(watch_name, since)
unless self.watches[watch_name]
raise NoSuchWatchError.new
end
LOG.watch_log_since(watch_name, since)
end
def self.running_load(code)
eval(code)
self.pending_watches.each { |w| w.monitor if w.autostart? }
watches = self.pending_watches.dup
self.pending_watches.clear
watches
end
def self.load(glob)
Dir[glob].each do |f|
Kernel.load f
end
end
def self.setup
# Make pid directory
unless test(?d, self.pid_file_directory)
begin
FileUtils.mkdir_p(self.pid_file_directory)
rescue Errno::EACCES => e
abort "Failed to create pid file directory: #{e.message}"
end
end
end
def self.validater
unless test(?w, self.pid_file_directory)
abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
end
end
def self.start
self.internal_init
self.setup
self.validater
# instantiate server
self.server = Server.new(self.host, self.port, self.allow)
# start event handler system
EventHandler.start if EventHandler.loaded?
# start the timer system
Timer.get
# start monitoring any watches set to autostart
self.watches.values.each { |w| w.monitor if w.autostart? }
# clear pending watches
self.pending_watches.clear
# mark as running
self.running = true
# join the timer thread so we don't exit
Timer.get.join
end
def self.at_exit
self.start
end
end
at_exit do
God.at_exit
end