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