Package Products :: Package ZenModel :: Module MinMaxThreshold
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenModel.MinMaxThreshold

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 or (at your 
  8  # option) any later version as published by the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13   
 14  __doc__= """MinMaxThreshold 
 15  Make threshold comparisons dynamic by using TALES expresssions, 
 16  rather than just number bounds checking. 
 17  """ 
 18   
 19  import os 
 20  import rrdtool 
 21  from AccessControl import Permissions 
 22   
 23  from Globals import InitializeClass 
 24  from ThresholdClass import ThresholdClass 
 25  from ThresholdInstance import ThresholdInstance, ThresholdContext 
 26  from Products.ZenEvents import Event 
 27  from Products.ZenEvents.ZenEventClasses import Perf_Snmp 
 28  from Products.ZenUtils.ZenTales import talesEval, talesEvalStr 
 29  from Products.ZenUtils.Utils import zenPath, rrd_daemon_running 
 30  from Products.ZenEvents.Exceptions import pythonThresholdException, \ 
 31          rpnThresholdException 
 32   
 33  import logging 
 34  log = logging.getLogger('zen.MinMaxCheck') 
 35   
 36  from Products.ZenUtils.Utils import unused, nanToNone 
 37   
 38  # Note:  this import is for backwards compatibility. 
 39  # Import Products.ZenRRD.utils.rpneval directy. 
 40  from Products.ZenRRD.utils import rpneval 
 41   
 42  NaN = float('nan') 
 43   
44 -class MinMaxThreshold(ThresholdClass):
45 """ 46 Threshold class that can evaluate RPNs and Python expressions 47 """ 48 49 minval = "" 50 maxval = "" 51 eventClass = Perf_Snmp 52 severity = 3 53 escalateCount = 0 54 55 _properties = ThresholdClass._properties + ( 56 {'id':'minval', 'type':'string', 'mode':'w'}, 57 {'id':'maxval', 'type':'string', 'mode':'w'}, 58 {'id':'eventClass', 'type':'string', 'mode':'w'}, 59 {'id':'severity', 'type':'int', 'mode':'w'}, 60 {'id':'escalateCount', 'type':'int', 'mode':'w'} 61 ) 62 63 factory_type_information = ( 64 { 65 'immediate_view' : 'editRRDThreshold', 66 'actions' : 67 ( 68 { 'id' : 'edit' 69 , 'name' : 'Min/Max Threshold' 70 , 'action' : 'editRRDThreshold' 71 , 'permissions' : ( Permissions.view, ) 72 }, 73 ) 74 }, 75 ) 76
77 - def createThresholdInstance(self, context):
78 """Return the config used by the collector to process min/max 79 thresholds. (id, minval, maxval, severity, escalateCount) 80 """ 81 mmt = MinMaxThresholdInstance(self.id, 82 ThresholdContext(context), 83 self.dsnames, 84 minval=self.getMinval(context), 85 maxval=self.getMaxval(context), 86 eventClass=self.eventClass, 87 severity=self.severity, 88 escalateCount=self.escalateCount) 89 return mmt
90
91 - def getMinval(self, context):
92 """Build the min value for this threshold. 93 """ 94 minval = None 95 if self.minval: 96 try: 97 express = "python:%s" % self.minval 98 minval = talesEval(express, context) 99 except: 100 msg= "User-supplied Python expression (%s) for minimum value caused error: %s" % \ 101 ( self.minval, self.dsnames ) 102 log.error( msg ) 103 raise pythonThresholdException(msg) 104 minval = None 105 return nanToNone(minval)
106 107
108 - def getMaxval(self, context):
109 """Build the max value for this threshold. 110 """ 111 maxval = None 112 if self.maxval: 113 try: 114 express = "python:%s" % self.maxval 115 maxval = talesEval(express, context) 116 except: 117 msg= "User-supplied Python expression (%s) for maximum value caused error: %s" % \ 118 ( self.maxval, self.dsnames ) 119 log.error( msg ) 120 raise pythonThresholdException(msg) 121 maxval = None 122 return nanToNone(maxval)
123 124 InitializeClass(MinMaxThreshold) 125 MinMaxThresholdClass = MinMaxThreshold 126 127 128
129 -class MinMaxThresholdInstance(ThresholdInstance):
130 # Not strictly necessary, but helps when restoring instances from 131 # pickle files that were not constructed with a count member. 132 count = {} 133
134 - def __init__(self, id, context, dpNames, 135 minval, maxval, eventClass, severity, escalateCount):
136 self.count = {} 137 self._context = context 138 self.id = id 139 self.minimum = minval 140 self.maximum = maxval 141 self.eventClass = eventClass 142 self.severity = severity 143 self.escalateCount = escalateCount 144 self.dataPointNames = dpNames 145 self._rrdInfoCache = {}
146
147 - def name(self):
148 "return the name of this threshold (from the ThresholdClass)" 149 return self.id
150
151 - def context(self):
152 "Return an identifying context (device, or device and component)" 153 return self._context
154
155 - def dataPoints(self):
156 "Returns the names of the datapoints used to compute the threshold" 157 return self.dataPointNames
158
159 - def rrdInfoCache(self, dp):
160 if dp in self._rrdInfoCache: 161 return self._rrdInfoCache[dp] 162 163 daemon_args = () 164 daemon = rrd_daemon_running() 165 if daemon: 166 daemon_args = ('--daemon', daemon) 167 168 data = rrdtool.info(self.context().path(dp), *daemon_args) 169 # handle both old and new style RRD versions 170 try: 171 # old style 1.2.x 172 value = data['step'], data['ds']['ds0']['type'] 173 except KeyError: 174 # new style 1.3.x 175 value = data['step'], data['ds[ds0].type'] 176 self._rrdInfoCache[dp] = value 177 return value
178
179 - def countKey(self, dp):
180 return ':'.join(self.context().key()) + ':' + dp
181
182 - def getCount(self, dp):
183 countKey = self.countKey(dp) 184 if not countKey in self.count: 185 return None 186 return self.count[countKey]
187
188 - def incrementCount(self, dp):
189 countKey = self.countKey(dp) 190 if not countKey in self.count: 191 self.resetCount(dp) 192 self.count[countKey] += 1 193 return self.count[countKey]
194
195 - def resetCount(self, dp):
196 self.count[self.countKey(dp)] = 0
197
198 - def fetchLastValue(self, dp, cycleTime):
199 """ 200 Fetch the most recent value for a data point from the RRD file. 201 """ 202 startStop, names, values = rrdtool.fetch(self.context().path(dp), 203 'AVERAGE', '-s', 'now-%d' % (cycleTime*2), '-e', 'now') 204 values = [ v[0] for v in values if v[0] is not None ] 205 if values: return values[-1]
206
207 - def check(self, dataPoints):
208 """The given datapoints have been updated, so re-evaluate. 209 returns events or an empty sequence""" 210 unused(dataPoints) 211 result = [] 212 for dp in self.dataPointNames: 213 cycleTime, rrdType = self.rrdInfoCache(dp) 214 result.extend(self.checkRange( 215 dp, self.fetchLastValue(dp, cycleTime))) 216 return result
217
218 - def checkRaw(self, dataPoint, timeOf, value):
219 """A new datapoint has been collected, use the given _raw_ 220 value to re-evalue the threshold.""" 221 unused(timeOf) 222 result = [] 223 if value is None: return result 224 try: 225 cycleTime, rrdType = self.rrdInfoCache(dataPoint) 226 except Exception: 227 log.exception('Unable to read RRD file for %s' % dataPoint) 228 return result 229 if rrdType != 'GAUGE' and value is None: 230 value = self.fetchLastValue(dataPoint, cycleTime) 231 result.extend(self.checkRange(dataPoint, value)) 232 return result
233
234 - def checkRange(self, dp, value):
235 'Check the value for min/max thresholds' 236 log.debug("Checking %s %s against min %s and max %s", 237 dp, value, self.minimum, self.maximum) 238 if value is None: 239 return [] 240 if isinstance(value, basestring): 241 value = float(value) 242 thresh = None 243 244 # Handle all cases where both minimum and maximum are set. 245 if self.maximum is not None and self.minimum is not None: 246 if self.maximum >= self.minimum and value > self.maximum: 247 thresh = self.maximum 248 how = 'exceeded' 249 if self.maximum >= self.minimum and value < self.minimum: 250 thresh = self.maximum 251 how = 'not met' 252 elif self.maximum < self.minimum \ 253 and (value < self.minimum and value > self.maximum): 254 thresh = self.maximum 255 how = 'violated' 256 257 # Handle simple cases where only minimum or maximum is set. 258 elif self.maximum is not None and value > self.maximum: 259 thresh = self.maximum 260 how = 'exceeded' 261 elif self.minimum is not None and value < self.minimum: 262 thresh = self.minimum 263 how = 'not met' 264 265 if thresh is not None: 266 severity = self.severity 267 count = self.incrementCount(dp) 268 if self.escalateCount and count >= self.escalateCount: 269 severity = min(severity + 1, 5) 270 summary = 'threshold of %s %s: current value %f' % ( 271 self.name(), how, float(value)) 272 return self.processEvent(dict( 273 device=self.context().deviceName, 274 summary=summary, 275 eventKey=self.id, 276 eventClass=self.eventClass, 277 component=self.context().componentName, 278 how=how, 279 min=self.minimum, 280 max=self.maximum, 281 current=value, 282 severity=severity)) 283 else: 284 count = self.getCount(dp) 285 if count is None or count > 0: 286 summary = 'threshold of %s restored: current value %f' % ( 287 self.name(), value) 288 self.resetCount(dp) 289 return self.processClearEvent(dict( 290 device=self.context().deviceName, 291 summary=summary, 292 eventKey=self.id, 293 eventClass=self.eventClass, 294 component=self.context().componentName, 295 min=self.minimum, 296 max=self.maximum, 297 current=value, 298 severity=Event.Clear)) 299 return []
300
301 - def processEvent(self, evt):
302 """ 303 When a threshold condition is violated, pre-process it for (possibly) nicer 304 formatting or more complicated logic. 305 306 @paramater evt: event 307 @type evt: dictionary 308 @rtype: list of dictionaries 309 """ 310 return [evt]
311
312 - def processClearEvent(self, evt):
313 """ 314 When a threshold condition is restored, pre-process it for (possibly) nicer 315 formatting or more complicated logic. 316 317 @paramater evt: event 318 @type evt: dictionary 319 @rtype: list of dictionaries 320 """ 321 return [evt]
322
323 - def raiseRPNExc( self ):
324 """ 325 Raise an RPN exception, taking care to log all details. 326 """ 327 msg= "The following RPN exception is from user-supplied code." 328 log.exception( msg ) 329 raise rpnThresholdException(msg)
330 331
332 - def getGraphElements(self, template, context, gopts, namespace, color, 333 legend, relatedGps):
334 """Produce a visual indication on the graph of where the 335 threshold applies.""" 336 unused(template, namespace) 337 if not color.startswith('#'): 338 color = '#%s' % color 339 minval = self.minimum 340 if minval is None: 341 minval = NaN 342 maxval = self.maximum 343 if maxval is None: 344 maxval = NaN 345 if not self.dataPointNames: 346 return gopts 347 gp = relatedGps[self.dataPointNames[0]] 348 349 # Attempt any RPN expressions 350 rpn = getattr(gp, 'rpn', None) 351 if rpn: 352 try: 353 rpn = talesEvalStr(rpn, context) 354 except: 355 self.raiseRPNExc() 356 return gopts 357 358 try: 359 minval = rpneval(minval, rpn) 360 except: 361 minval= 0 362 self.raiseRPNExc() 363 364 try: 365 maxval = rpneval(maxval, rpn) 366 except: 367 maxval= 0 368 self.raiseRPNExc() 369 370 minstr = self.setPower(minval) 371 maxstr = self.setPower(maxval) 372 373 minval = nanToNone(minval) 374 maxval = nanToNone(maxval) 375 if legend: 376 gopts.append( 377 "HRULE:%s%s:%s\\j" % (minval or maxval, color, legend)) 378 elif minval is not None and maxval is not None: 379 if minval == maxval: 380 gopts.append( 381 "HRULE:%s%s:%s not equal to %s\\j" % (minval, color, 382 self.getNames(relatedGps), minstr)) 383 elif minval < maxval: 384 gopts.append( 385 "HRULE:%s%s:%s not within %s and %s\\j" % (minval, color, 386 self.getNames(relatedGps), minstr, maxstr)) 387 gopts.append("HRULE:%s%s" % (maxval, color)) 388 elif minval > maxval: 389 gopts.append( 390 "HRULE:%s%s:%s between %s and %s\\j" % (minval, color, 391 self.getNames(relatedGps), maxstr, minstr)) 392 gopts.append("HRULE:%s%s" % (maxval, color)) 393 elif minval is not None : 394 gopts.append( 395 "HRULE:%s%s:%s less than %s\\j" % (minval, color, 396 self.getNames(relatedGps), minstr)) 397 elif maxval is not None: 398 gopts.append( 399 "HRULE:%s%s:%s greater than %s\\j" % (maxval, color, 400 self.getNames(relatedGps), maxstr)) 401 402 return gopts
403 404
405 - def getNames(self, relatedGps):
406 names = list(set(x.split('_', 1)[1] for x in self.dataPointNames)) 407 names.sort() 408 return ', '.join(names)
409
410 - def setPower(self, number):
411 powers = ("k", "M", "G") 412 if number < 1000: return number 413 for power in powers: 414 number = number / 1000.0 415 if number < 1000: 416 return "%0.2f%s" % (number, power) 417 return "%.2f%s" % (number, powers[-1])
418 419 from twisted.spread import pb 420 pb.setUnjellyableForClass(MinMaxThresholdInstance, MinMaxThresholdInstance) 421