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

Source Code for Module 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 
 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 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 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 if self.maximum is not None and value > self.maximum: 256 thresh = self.maximum 257 how = 'exceeded' 258 if self.minimum is not None and value < self.minimum: 259 thresh = self.minimum 260 how = 'not met' 261 if thresh is not None: 262 severity = self.severity 263 count = self.incrementCount(dp) 264 if self.escalateCount and count >= self.escalateCount: 265 severity = min(severity + 1, 5) 266 summary = 'threshold of %s %s: current value %.2f' % ( 267 self.name(), how, float(value)) 268 return [dict(device=self.context().deviceName, 269 summary=summary, 270 eventKey=self.id, 271 eventClass=self.eventClass, 272 component=self.context().componentName, 273 severity=severity)] 274 else: 275 count = self.getCount(dp) 276 if count is None or count > 0: 277 summary = 'Threshold of %s restored: current value: %.2f' % ( 278 self.name(), value) 279 self.resetCount(dp) 280 return [dict(device=self.context().deviceName, 281 summary=summary, 282 eventKey=self.id, 283 eventClass=self.eventClass, 284 component=self.context().componentName, 285 severity=Event.Clear)] 286 return []
287 288
289 - def raiseRPNExc( self ):
290 """ 291 Raise an RPN exception, taking care to log all details. 292 """ 293 msg= "The following RPN exception is from user-supplied code." 294 log.exception( msg ) 295 raise rpnThresholdException(msg)
296 297
298 - def getGraphElements(self, template, context, gopts, namespace, color, 299 legend, relatedGps):
300 """Produce a visual indication on the graph of where the 301 threshold applies.""" 302 unused(template, namespace) 303 if not color.startswith('#'): 304 color = '#%s' % color 305 minval = self.minimum 306 maxval = self.maximum 307 if not self.dataPointNames: 308 return gopts 309 gp = relatedGps[self.dataPointNames[0]] 310 311 # Attempt any RPN expressions 312 rpn = getattr(gp, 'rpn', None) 313 if rpn: 314 try: 315 rpn = talesEvalStr(rpn, context) 316 except: 317 self.raiseRPNExc() 318 return gopts 319 320 try: 321 minval = rpneval(minval, rpn) 322 except: 323 minval= 0 324 self.raiseRPNExc() 325 326 try: 327 maxval = rpneval(maxval, rpn) 328 except: 329 maxval= 0 330 self.raiseRPNExc() 331 332 result = [] 333 if minval: 334 result += [ 335 "HRULE:%s%s:%s\\j" % (minval, color, 336 legend or self.getMinLabel(minval, relatedGps)), 337 ] 338 if maxval: 339 result += [ 340 "HRULE:%s%s:%s\\j" % (maxval, color, 341 legend or self.getMaxLabel(maxval, relatedGps)) 342 ] 343 return gopts + result
344 345
346 - def getMinLabel(self, minval, relatedGps):
347 """build a label for a min threshold""" 348 return "%s < %s" % (self.getNames(relatedGps), self.setPower(minval))
349 350
351 - def getMaxLabel(self, maxval, relatedGps):
352 """build a label for a max threshold""" 353 return "%s > %s" % (self.getNames(relatedGps), self.setPower(maxval))
354
355 - def getNames(self, relatedGps):
356 legends = [ getattr(gp, 'legend', gp) for gp in relatedGps.values() ] 357 return ', '.join(legends)
358
359 - def setPower(self, number):
360 powers = ("k", "M", "G") 361 if number < 1000: return number 362 for power in powers: 363 number = number / 1000.0 364 if number < 1000: 365 return "%0.2f%s" % (number, power) 366 return "%.2f%s" % (number, powers[-1])
367 368 from twisted.spread import pb 369 pb.setUnjellyableForClass(MinMaxThresholdInstance, MinMaxThresholdInstance) 370