Add notify_when_flapping behavior, doc enhancements
[god.git] / lib / god.rb
blobacc04e79f588d4fb1f1a75670a96725c16aecbb7
1 $:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
3 require 'syslog'
5 # internal requires
6 require 'god/errors'
8 require 'god/system/process'
10 require 'god/behavior'
11 require 'god/behaviors/clean_pid_file'
12 require 'god/behaviors/notify_when_flapping'
14 require 'god/condition'
15 require 'god/conditions/timeline'
16 require 'god/conditions/process_running'
17 require 'god/conditions/process_exits'
18 require 'god/conditions/tries'
19 require 'god/conditions/memory_usage'
20 require 'god/conditions/cpu_usage'
21 require 'god/conditions/always'
23 require 'god/reporter'
24 require 'god/server'
25 require 'god/timer'
26 require 'god/hub'
28 require 'god/metric'
29 require 'god/watch'
31 require 'god/event_handler'
32 require 'god/registry'
33 require 'god/process'
35 require 'god/sugar'
37 $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
39 begin
40   Syslog.open('god')
41 rescue RuntimeError
42   Syslog.reopen('god')
43 end
45 God::EventHandler.load
47 module God
48   VERSION = '0.3.0'
49   
50   class << self
51     attr_accessor :inited, :running, :pending_watches, :host, :port
52     
53     # drb
54     attr_accessor :server
55     
56     # api
57     attr_accessor :watches, :groups
58   end
59   
60   def self.init
61     # only do this once
62     return if self.inited
63     
64     # variable init
65     self.watches = {}
66     self.groups = {}
67     self.pending_watches = []
68     
69     # yield to the config file
70     yield self if block_given?
71     
72     # instantiate server
73     self.server = Server.new(self.host, self.port)
74     
75     # init has been executed
76     self.inited = true
77     
78     # not yet running
79     self.running = false
80   end
81     
82   # Where pid files created by god will go by default
83   def self.pid_file_directory
84     @pid_file_directory ||= '/var/run/god'
85   end
86   
87   def self.pid_file_directory=(value)
88     @pid_file_directory = value
89   end
90   
91   # Instantiate a new, empty Watch object and pass it to the mandatory
92   # block. The attributes of the watch will be set by the configuration
93   # file.
94   def self.watch
95     self.init
96     
97     w = Watch.new
98     yield(w)
99     
100     # if running, completely remove the watch (if necessary) to
101     # prepare for the reload
102     existing_watch = self.watches[w.name]
103     if self.running && existing_watch
104       self.unwatch(existing_watch)
105     end
106     
107     # ensure the new watch has a unique name
108     if self.watches[w.name] || self.groups[w.name]
109       abort "Watch name '#{w.name}' already used for a Watch or Group"
110     end
111     
112     # add to list of watches
113     self.watches[w.name] = w
114     
115     # add to pending watches
116     self.pending_watches << w
117     
118     # add to group if specified
119     if w.group
120       # ensure group name hasn't been used for a watch already
121       if self.watches[w.group]
122         abort "Group name '#{w.group}' already used for a Watch"
123       end
124     
125       self.groups[w.group] ||= []
126       self.groups[w.group] << w
127     end
129     # register watch
130     w.register!
131   end
132   
133   def self.unwatch(watch)
134     # unmonitor
135     watch.unmonitor
136     
137     # unregister
138     watch.unregister!
139     
140     # remove from watches
141     self.watches.delete(watch.name)
142     
143     # remove from groups
144     if watch.group
145       self.groups[watch.group].delete(watch)
146     end
147   end
148   
149   def self.control(name, command)
150     # get the list of watches
151     watches = Array(self.watches[name] || self.groups[name])
152   
153     # do the command
154     case command
155       when "start", "monitor"
156         watches.each { |w| w.monitor }
157       when "restart"
158         watches.each { |w| w.move(:restart) }
159       when "stop"
160         watches.each { |w| w.unmonitor.action(:stop) }
161       when "unmonitor"
162         watches.each { |w| w.unmonitor }
163       else
164         raise InvalidCommandError.new
165     end
166     
167     watches
168   end
169   
170   def self.running_load(code)
171     eval(code)
172     self.pending_watches.each { |w| w.monitor if w.autostart? }
173     watches = self.pending_watches.dup
174     self.pending_watches.clear
175     watches
176   end
177   
178   def self.load(glob)
179     Dir[glob].each do |f|
180       Kernel.load f
181     end
182   end
183   
184   def self.start
185     # make sure there's something to do
186     if self.watches.nil? || self.watches.empty?
187       abort "You must specify at least one watch!"
188     end
189     
190     # start event handler system
191     EventHandler.start if EventHandler.loaded?
192     
193     # start the timer system
194     Timer.get
196     # start monitoring any watches set to autostart
197     self.watches.values.each { |w| w.monitor if w.autostart? }
198     
199     # clear pending watches
200     self.pending_watches.clear
201     
202     # mark as running
203     self.running = true
204     
205     # join the timer thread so we don't exit
206     Timer.get.join
207   end
208   
209   def self.at_exit
210     self.start
211   end
214 at_exit do
215   God.at_exit