1
2
3
4
5
6
7
8
9
10
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
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
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
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
126
127
143
144 InitializeClass(MinMaxThreshold)
145 MinMaxThresholdClass = MinMaxThreshold
146
147
148
150
151
152 count = {}
153
154 - def __init__(self, id, context, dpNames,
155 minval, maxval, eventClass, severity, escalateCount):
166
168 "return the name of this threshold (from the ThresholdClass)"
169 return self.id
170
172 "Return an identifying context (device, or device and component)"
173 return self._context
174
176 "Returns the names of the datapoints used to compute the threshold"
177 return self.dataPointNames
178
180 if dp in self._rrdInfoCache:
181 return self._rrdInfoCache[dp]
182 data = rrdtool.info(self.context().path(dp))
183
184 try:
185
186 value = data['step'], data['ds']['ds0']['type']
187 except KeyError:
188
189 value = data['step'], data['ds[ds0].type']
190 self._rrdInfoCache[dp] = value
191 return value
192
194 return(':'.join(self.context().key()) + ':' + dp)
195
201
208
211
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):
231
232 - def checkRaw(self, dataPoint, timeOf, value):
247
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
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
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
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
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
384 names = list(set([x.split('_', 1)[1] for x in self.dataPointNames]))
385 names.sort()
386 return ', '.join(names)
387
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