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 self.directory[condition] = metric
28 Timer.get.schedule(condition, 0)
29 when EventCondition, TriggerCondition
35 # Detach the condition from the hub and unschedule/deregister it
36 # +condition+ is the Condition to detach
39 def self.detach(condition)
40 self.mutex.synchronize do
41 self.directory.delete(condition)
45 Timer.get.unschedule(condition)
46 when EventCondition, TriggerCondition
52 # Trigger evaluation of the condition
53 # +condition+ is the Condition to evaluate
56 def self.trigger(condition)
57 self.mutex.synchronize do
60 self.handle_poll(condition)
61 when EventCondition, TriggerCondition
62 self.handle_event(condition)
69 # Asynchronously evaluate and handle the given poll condition. Handles logging
70 # notifications, and moving to the new state if necessary
71 # +condition+ is the Condition to handle
74 def self.handle_poll(condition)
75 metric = self.directory[condition]
77 # it's possible that the timer will trigger an event before it can be cleared
78 # by an exiting metric, in which case it should be ignored
85 watch.mutex.synchronize do
87 result = condition.test
90 messages = self.log(watch, metric, condition, result)
93 if condition.notify && self.trigger?(metric, result)
94 self.notify(condition, messages.last)
100 # get the destination
102 if result && condition.transition
107 metric.destination && metric.destination[result]
110 # transition or reschedule
115 rescue EventRegistrationFailedError
116 msg = watch.name + ' Event registration failed, moving back to previous state'
117 applog(watch, :info, msg)
124 Timer.get.schedule(condition)
127 rescue Exception => e
128 message = format("Unhandled exception (%s): %s\n%s",
129 e.class, e.message, e.backtrace.join("\n"))
130 applog(nil, :fatal, message)
135 # Asynchronously evaluate and handle the given event condition. Handles logging
136 # notifications, and moving to the new state if necessary
137 # +condition+ is the Condition to handle
140 def self.handle_event(condition)
141 metric = self.directory[condition]
143 # it's possible that the timer will trigger an event before it can be cleared
144 # by an exiting metric, in which case it should be ignored
145 return if metric.nil?
151 watch.mutex.synchronize do
153 messages = self.log(watch, metric, condition, true)
156 if condition.notify && self.trigger?(metric, true)
157 self.notify(condition, messages.last)
160 # get the destination
162 if condition.transition
167 metric.destination && metric.destination[true]
174 rescue Exception => e
175 message = format("Unhandled exception (%s): %s\n%s",
176 e.class, e.message, e.backtrace.join("\n"))
177 applog(nil, :fatal, message)
182 # Determine whether a trigger happened
183 # +metric+ is the Metric
184 # +result+ is the result from the condition's test
187 def self.trigger?(metric, result)
188 metric.destination && metric.destination[result]
191 # Log info about the condition and return the list of messages logged
192 # +watch+ is the Watch
193 # +metric+ is the Metric
194 # +condition+ is the Condition
195 # +result+ is the Boolean result of the condition test evaluation
198 def self.log(watch, metric, condition, result)
200 if self.trigger?(metric, result)
208 # log info if available
210 Array(condition.info).each do |condition_info|
211 messages << "#{watch.name} #{status} #{condition_info} (#{condition.base_name})"
212 applog(watch, :info, messages.last)
215 messages << "#{watch.name} #{status} (#{condition.base_name})"
216 applog(watch, :info, messages.last)
220 debug_message = watch.name + ' ' + condition.base_name + " [#{result}] " + self.dest_desc(metric, condition)
221 applog(watch, :debug, debug_message)
226 # Format the destination specification for use in debug logging
227 # +metric+ is the Metric
228 # +condition+ is the Condition
231 def self.dest_desc(metric, condition)
232 if condition.transition
233 {true => condition.transition}.inspect
235 if metric.destination
236 metric.destination.inspect
243 # Notify all recipeients of the given condition with the specified message
244 # +condition+ is the Condition
245 # +message+ is the String message to send
248 def self.notify(condition, message)
249 spec = Contact.normalize(condition.notify)
254 spec[:contacts].inject([]) do |acc, contact_name_or_group|
255 cons = Array(God.contacts[contact_name_or_group] || God.contact_groups[contact_name_or_group])
256 unmatched << contact_name_or_group if cons.empty?
261 # warn about unmatched contacts
262 unless unmatched.empty?
263 msg = "#{condition.watch.name} no matching contacts for '#{unmatched.join(", ")}'"
264 applog(condition.watch, :warn, msg)
267 # notify each contact
268 resolved_contacts.each do |c|
269 host = `hostname`.chomp rescue 'none'
270 c.notify(message, Time.now, spec[:priority], spec[:category], host)
272 msg = "#{condition.watch.name} #{c.info ? c.info : "notification sent for contact: #{c.name}"} (#{c.base_name})"
274 applog(condition.watch, :info, msg % [])