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
125
126
141
142 InitializeClass(MinMaxThreshold)
143 MinMaxThresholdClass = MinMaxThreshold
144
145
146
148
149
150 count = {}
151
152 - def __init__(self, id, context, dpNames,
153 minval, maxval, eventClass, severity, escalateCount):
164
166 "return the name of this threshold (from the ThresholdClass)"
167 return self.id
168
170 "Return an identifying context (device, or device and component)"
171 return self._context
172
174 "Returns the names of the datapoints used to compute the threshold"
175 return self.dataPointNames
176
178 if dp in self._rrdInfoCache:
179 return self._rrdInfoCache[dp]
180 data = rrdtool.info(self.context().path(dp))
181
182 try:
183
184 value = data['step'], data['ds']['ds0']['type']
185 except KeyError:
186
187 value = data['step'], data['ds[ds0].type']
188 self._rrdInfoCache[dp] = value
189 return value
190
192 return(':'.join(self.context().key()) + ':' + dp)
193
199
206
209
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):
229
230 - def checkRaw(self, dataPoint, timeOf, value):
245
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
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
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
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
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
382 names = list(set([x.split('_', 1)[1] for x in self.dataPointNames]))
383 names.sort()
384 return ', '.join(names)
385
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