5 # directory to hold conditions and their corresponding metric
6 # {condition => metric}
7 attr_accessor :directory
9 # mutex to keep the directory consistent
14 self.mutex = Monitor.new
16 # Attach the condition to the hub and schedule/register it
17 # +condition+ is the Condition to attach
18 # +metric+ is the Metric to which the condition belongs
21 def self.attach(condition, metric)
22 self.mutex.synchronize do
23 condition.phase = condition.watch.phase
25 self.directory[condition] = metric
29 Timer.get.schedule(condition, 0)
30 when EventCondition, TriggerCondition
36 # Detach the condition from the hub and unschedule/deregister it
37 # +condition+ is the Condition to detach
40 def self.detach(condition)
41 self.mutex.synchronize do
42 self.directory.delete(condition)
46 Timer.get.unschedule(condition)
47 when EventCondition, TriggerCondition
53 # Trigger evaluation of the condition
54 # +condition+ is the Condition to evaluate
55 # +phase+ is the phase of the Watch at the time the condition was scheduled
58 def self.trigger(condition, phase = nil)
59 self.mutex.synchronize do
62 self.handle_poll(condition, phase)
63 when EventCondition, TriggerCondition
64 self.handle_event(condition)
71 # Asynchronously evaluate and handle the given poll condition. Handles logging
72 # notifications, and moving to the new state if necessary
73 # +condition+ is the Condition to handle
74 # +phase+ is the phase of the Watch that should be matched
77 def self.handle_poll(condition, phase)
78 metric = self.directory[condition]
80 # it's possible that the timer will trigger an event before it can be cleared
81 # by an exiting metric, in which case it should be ignored
88 watch.mutex.synchronize do
89 # ensure this condition is still active when we finally get the mutex
90 if self.directory[condition] && phase == watch.phase
92 result = condition.test
95 messages = self.log(watch, metric, condition, result)
98 if condition.notify && self.trigger?(metric, result)
99 self.notify(condition, messages.last)
105 # get the destination
107 if result && condition.transition
112 metric.destination && metric.destination[result]
115 # transition or reschedule
120 rescue EventRegistrationFailedError
121 msg = watch.name + ' Event registration failed, moving back to previous state'
122 applog(watch, :info, msg)
129 Timer.get.schedule(condition)
133 rescue Exception => e
134 message = format("Unhandled exception (%s): %s\n%s",
135 e.class, e.message, e.backtrace.join("\n"))
136 applog(nil, :fatal, message)
141 # Asynchronously evaluate and handle the given event condition. Handles logging
142 # notifications, and moving to the new state if necessary
143 # +condition+ is the Condition to handle
146 def self.handle_event(condition)
147 metric = self.directory[condition]
149 # it's possible that the timer will trigger an event before it can be cleared
150 # by an exiting metric, in which case it should be ignored
151 return if metric.nil?
157 watch.mutex.synchronize do
158 # ensure this condition is still active when we finally get the mutex
159 if self.directory[condition]
161 messages = self.log(watch, metric, condition, true)
164 if condition.notify && self.trigger?(metric, true)
165 self.notify(condition, messages.last)
168 # get the destination
170 if condition.transition
175 metric.destination && metric.destination[true]
183 rescue Exception => e
184 message = format("Unhandled exception (%s): %s\n%s",
185 e.class, e.message, e.backtrace.join("\n"))
186 applog(nil, :fatal, message)
191 # Determine whether a trigger happened
192 # +metric+ is the Metric
193 # +result+ is the result from the condition's test
196 def self.trigger?(metric, result)
197 metric.destination && metric.destination[result]
200 # Log info about the condition and return the list of messages logged
201 # +watch+ is the Watch
202 # +metric+ is the Metric
203 # +condition+ is the Condition
204 # +result+ is the Boolean result of the condition test evaluation
207 def self.log(watch, metric, condition, result)
209 if self.trigger?(metric, result)
217 # log info if available
219 Array(condition.info).each do |condition_info|
220 messages << "#{watch.name} #{status} #{condition_info} (#{condition.base_name})"
221 applog(watch, :info, messages.last)
224 messages << "#{watch.name} #{status} (#{condition.base_name})"
225 applog(watch, :info, messages.last)
229 debug_message = watch.name + ' ' + condition.base_name + " [#{result}] " + self.dest_desc(metric, condition)
230 applog(watch, :debug, debug_message)
235 # Format the destination specification for use in debug logging
236 # +metric+ is the Metric
237 # +condition+ is the Condition
240 def self.dest_desc(metric, condition)
241 if condition.transition
242 {true => condition.transition}.inspect
244 if metric.destination
245 metric.destination.inspect
252 # Notify all recipeients of the given condition with the specified message
253 # +condition+ is the Condition
254 # +message+ is the String message to send
257 def self.notify(condition, message)
258 spec = Contact.normalize(condition.notify)
263 spec[:contacts].inject([]) do |acc, contact_name_or_group|
264 cons = Array(God.contacts[contact_name_or_group] || God.contact_groups[contact_name_or_group])
265 unmatched << contact_name_or_group if cons.empty?
270 # warn about unmatched contacts
271 unless unmatched.empty?
272 msg = "#{condition.watch.name} no matching contacts for '#{unmatched.join(", ")}'"
273 applog(condition.watch, :warn, msg)
276 # notify each contact
277 resolved_contacts.each do |c|
278 host = `hostname`.chomp rescue 'none'
279 c.notify(message, Time.now, spec[:priority], spec[:category], host)
281 msg = "#{condition.watch.name} #{c.info ? c.info : "notification sent for contact: #{c.name}"} (#{c.base_name})"
283 applog(condition.watch, :info, msg % [])