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 express = "python:%s" % self.minval 118 minval = talesEval(express, context) 119 except: 120 msg= "User-supplied Python expression (%s) for minimum value caused error: %s" % \ 121 ( self.minval, self.dsnames ) 122 log.error( msg ) 123 raise pythonThresholdException(msg) 124 minval = None 125 return nanToNone(minval)
126 127
128 - def getMaxval(self, context):
129 """Build the max value for this threshold. 130 """ 131 maxval = None 132 if self.maxval: 133 try: 134 express = "python:%s" % self.maxval 135 maxval = talesEval(express, context) 136 except: 137 msg= "User-supplied Python expression (%s) for maximum value caused error: %s" % \ 138 ( self.maxval, self.dsnames ) 139 log.error( msg ) 140 raise pythonThresholdException(msg) 141 maxval = None 142 return nanToNone(maxval)
143 144 InitializeClass(MinMaxThreshold) 145 MinMaxThresholdClass = MinMaxThreshold 146 147 148
149 -class MinMaxThresholdInstance(ThresholdInstance):
150 # Not strictly necessary, but helps when restoring instances from 151 # pickle files that were not constructed with a count member. 152 count = {} 153
154 - def __init__(self, id, context, dpNames, 155 minval, maxval, eventClass, severity, escalateCount):
156 self.count = {} 157 self._context = context 158 self.id = id 159 self.minimum = minval 160 self.maximum = maxval 161 self.eventClass = eventClass 162 self.severity = severity 163 self.escalateCount = escalateCount 164 self.dataPointNames = dpNames 165 self._rrdInfoCache = {}
166
167 - def name(self):
168 "return the name of this threshold (from the ThresholdClass)" 169 return self.id
170
171 - def context(self):
172 "Return an identifying context (device, or device and component)" 173 return self._context
174
175 - def dataPoints(self):
176 "Returns the names of the datapoints used to compute the threshold" 177 return self.dataPointNames
178
179 - def rrdInfoCache(self, dp):
180 if dp in self._rrdInfoCache: 181 return self._rrdInfoCache[dp] 182 data = rrdtool.info(self.context().path(dp)) 183 # handle both old and new style RRD versions 184 try: 185 # old style 1.2.x 186 value = data['step'], data['ds']['ds0']['type'] 187 except KeyError: 188 # new style 1.3.x 189 value = data['step'], data['ds[ds0].type'] 190 self._rrdInfoCache[dp] = value 191 return value
192
193 - def countKey(self, dp):
194 return(':'.join(self.context().key()) + ':' + dp)
195
196 - def getCount(self, dp):
197 countKey = self.countKey(dp) 198 if not self.count.has_key(countKey): 199 return None 200 return self.count[countKey]
201
202 - def incrementCount(self, dp):
203 countKey = self.countKey(dp) 204 if not self.count.has_key(countKey): 205 self.resetCount(dp) 206 self.count[countKey] += 1 207 return self.count[countKey]
208
209 - def resetCount(self, dp):
210 self.count[self.countKey(dp)] = 0
211
212 - def fetchLastValue(self, dp, cycleTime):
213 """ 214 Fetch the most recent value for a data point from the RRD file. 215 """ 216 startStop, names, values = rrdtool.fetch(self.context().path(dp), 217 'AVERAGE', '-s', 'now-%d' % (cycleTime*2), '-e', 'now') 218 values = [ v[0] for v in values if v[0] is not None ] 219 if values: return values[-1]
220
221 - def check(self, dataPoints):
222 """The given datapoints have been updated, so re-evaluate. 223 returns events or an empty sequence""" 224 unused(dataPoints) 225 result = [] 226 for dp in self.dataPointNames: 227 cycleTime, rrdType = self.rrdInfoCache(dp) 228 result.extend(self.checkRange( 229 dp, self.fetchLastValue(dp, cycleTime))) 230 return result
231
232 - def checkRaw(self, dataPoint, timeOf, value):
233 """A new datapoint has been collected, use the given _raw_ 234 value to re-evalue the threshold.""" 235 unused(timeOf) 236 result = [] 237 if value is None: return result 238 try: 239 cycleTime, rrdType = self.rrdInfoCache(dataPoint) 240 except Exception: 241 log.exception('Unable to read RRD file for %s' % dataPoint) 242 return result 243 if rrdType != 'GAUGE' and value is None: 244 value = self.fetchLastValue(dataPoint, cycleTime) 245 result.extend(self.checkRange(dataPoint, value)) 246 return result
247
248 - def checkRange(self, dp, value):
249 'Check the value for min/max thresholds' 250 log.debug("Checking %s %s against min %s and max %s", 251 dp, value, self.minimum, self.maximum) 252 if value is None: 253 return [] 254 if type(value) in types.StringTypes: 255 value = float(value) 256 thresh = None 257 258 # Handle all cases where both minimum and maximum are set. 259 if self.maximum is not None and self.minimum is not None: 260 if self.maximum >= self.minimum and value > self.maximum: 261 thresh = self.maximum 262 how = 'exceeded' 263 if self.maximum >= self.minimum and value < self.minimum: 264 thresh = self.maximum 265 how = 'not met' 266 elif self.maximum < self.minimum \ 267 and (value < self.minimum and value > self.maximum): 268 thresh = self.maximum 269 how = 'violated' 270 271 # Handle simple cases where only minimum or maximum is set. 272 elif self.maximum is not None and value > self.maximum: 273 thresh = self.maximum 274 how = 'exceeded' 275 elif self.minimum is not None and value < self.minimum: 276 thresh = self.minimum 277 how = 'not met' 278 279 if thresh is not None: 280 severity = self.severity 281 count = self.incrementCount(dp) 282 if self.escalateCount and count >= self.escalateCount: 283 severity = min(severity + 1, 5) 284 summary = 'threshold of %s %s: current value %.2f' % ( 285 self.name(), how, float(value)) 286 return [dict(device=self.context().deviceName, 287 summary=summary, 288 eventKey=self.id, 289 eventClass=self.eventClass, 290 component=self.context().componentName, 291 severity=severity)] 292 else: 293 count = self.getCount(dp) 294 if count is None or count > 0: 295 summary = 'threshold of %s restored: current value %.2f' % ( 296 self.name(), value) 297 self.resetCount(dp) 298 return [dict(device=self.context().deviceName, 299 summary=summary, 300 eventKey=self.id, 301 eventClass=self.eventClass, 302 component=self.context().componentName, 303 severity=Event.Clear)] 304 return []
305 306
307 - def raiseRPNExc( self ):
308 """ 309 Raise an RPN exception, taking care to log all details. 310 """ 311 msg= "The following RPN exception is from user-supplied code." 312 log.exception( msg ) 313 raise rpnThresholdException(msg)
314 315
316 - def getGraphElements(self, template, context, gopts, namespace, color, 317 legend, relatedGps):
318 """Produce a visual indication on the graph of where the 319 threshold applies.""" 320 unused(template, namespace) 321 if not color.startswith('#'): 322 color = '#%s' % color 323 minval = self.minimum 324 maxval = self.maximum 325 if not self.dataPointNames: 326 return gopts 327 gp = relatedGps[self.dataPointNames[0]] 328 329 # Attempt any RPN expressions 330 rpn = getattr(gp, 'rpn', None) 331 if rpn: 332 try: 333 rpn = talesEvalStr(rpn, context) 334 except: 335 self.raiseRPNExc() 336 return gopts 337 338 try: 339 minval = rpneval(minval, rpn) 340 except: 341 minval= 0 342 self.raiseRPNExc() 343 344 try: 345 maxval = rpneval(maxval, rpn) 346 except: 347 maxval= 0 348 self.raiseRPNExc() 349 350 minstr = self.setPower(minval) 351 maxstr = self.setPower(maxval) 352 353 if legend: 354 gopts.append( 355 "HRULE:%s%s:%s\\j" % (minval or maxval, color, legend)) 356 elif minval is not None and maxval is not None: 357 if minval == maxval: 358 gopts.append( 359 "HRULE:%s%s:%s not equal to %s\\j" % (minval, color, 360 self.getNames(relatedGps), minstr)) 361 elif minval < maxval: 362 gopts.append( 363 "HRULE:%s%s:%s not within %s and %s\\j" % (minval, color, 364 self.getNames(relatedGps), minstr, maxstr)) 365 gopts.append("HRULE:%s%s" % (maxval, color)) 366 elif minval > maxval: 367 gopts.append( 368 "HRULE:%s%s:%s between %s and %s\\j" % (minval, color, 369 self.getNames(relatedGps), maxstr, minstr)) 370 gopts.append("HRULE:%s%s" % (maxval, color)) 371 elif minval is not None : 372 gopts.append( 373 "HRULE:%s%s:%s less than %s\\j" % (minval, color, 374 self.getNames(relatedGps), minstr)) 375 elif maxval is not None: 376 gopts.append( 377 "HRULE:%s%s:%s greater than %s\\j" % (maxval, color, 378 self.getNames(relatedGps), maxstr)) 379 380 return gopts
381 382
383 - def getNames(self, relatedGps):
384 names = list(set([x.split('_', 1)[1] for x in self.dataPointNames])) 385 names.sort() 386 return ', '.join(names)
387
388 - def setPower(self, number):
389 powers = ("k", "M", "G") 390 if number < 1000: return number 391 for power in powers: 392 number = number / 1000.0 393 if number < 1000: 394 return "%0.2f%s" % (number, power) 395 return "%.2f%s" % (number, powers[-1])
396 397 from twisted.spread import pb 398 pb.setUnjellyableForClass(MinMaxThresholdInstance, MinMaxThresholdInstance) 399