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 os
20 import rrdtool
21 from AccessControl import Permissions
22
23 from Globals import InitializeClass
24 from ThresholdClass import ThresholdClass
25 from ThresholdInstance import ThresholdInstance, ThresholdContext
26 from Products.ZenEvents import Event
27 from Products.ZenEvents.ZenEventClasses import Perf_Snmp
28 from Products.ZenUtils.ZenTales import talesEval, talesEvalStr
29 from Products.ZenUtils.Utils import zenPath, rrd_daemon_running
30 from Products.ZenEvents.Exceptions import pythonThresholdException, \
31 rpnThresholdException
32
33 import logging
34 log = logging.getLogger('zen.MinMaxCheck')
35
36 from Products.ZenUtils.Utils import unused, nanToNone
37
38
39
40 from Products.ZenRRD.utils import rpneval
41
42 NaN = float('nan')
43
45 """
46 Threshold class that can evaluate RPNs and Python expressions
47 """
48
49 minval = ""
50 maxval = ""
51 eventClass = Perf_Snmp
52 severity = 3
53 escalateCount = 0
54
55 _properties = ThresholdClass._properties + (
56 {'id':'minval', 'type':'string', 'mode':'w'},
57 {'id':'maxval', 'type':'string', 'mode':'w'},
58 {'id':'eventClass', 'type':'string', 'mode':'w'},
59 {'id':'severity', 'type':'int', 'mode':'w'},
60 {'id':'escalateCount', 'type':'int', 'mode':'w'}
61 )
62
63 factory_type_information = (
64 {
65 'immediate_view' : 'editRRDThreshold',
66 'actions' :
67 (
68 { 'id' : 'edit'
69 , 'name' : 'Min/Max Threshold'
70 , 'action' : 'editRRDThreshold'
71 , 'permissions' : ( Permissions.view, )
72 },
73 )
74 },
75 )
76
78 """Return the config used by the collector to process min/max
79 thresholds. (id, minval, maxval, severity, escalateCount)
80 """
81 mmt = MinMaxThresholdInstance(self.id,
82 ThresholdContext(context),
83 self.dsnames,
84 minval=self.getMinval(context),
85 maxval=self.getMaxval(context),
86 eventClass=self.eventClass,
87 severity=self.severity,
88 escalateCount=self.escalateCount)
89 return mmt
90
106
107
123
124 InitializeClass(MinMaxThreshold)
125 MinMaxThresholdClass = MinMaxThreshold
126
127
128
130
131
132 count = {}
133
134 - def __init__(self, id, context, dpNames,
135 minval, maxval, eventClass, severity, escalateCount):
146
148 "return the name of this threshold (from the ThresholdClass)"
149 return self.id
150
152 "Return an identifying context (device, or device and component)"
153 return self._context
154
156 "Returns the names of the datapoints used to compute the threshold"
157 return self.dataPointNames
158
160 if dp in self._rrdInfoCache:
161 return self._rrdInfoCache[dp]
162
163 daemon_args = ()
164 daemon = rrd_daemon_running()
165 if daemon:
166 daemon_args = ('--daemon', daemon)
167
168 data = rrdtool.info(self.context().path(dp), *daemon_args)
169
170 try:
171
172 value = data['step'], data['ds']['ds0']['type']
173 except KeyError:
174
175 value = data['step'], data['ds[ds0].type']
176 self._rrdInfoCache[dp] = value
177 return value
178
180 return ':'.join(self.context().key()) + ':' + dp
181
187
194
197
199 """
200 Fetch the most recent value for a data point from the RRD file.
201 """
202 startStop, names, values = rrdtool.fetch(self.context().path(dp),
203 'AVERAGE', '-s', 'now-%d' % (cycleTime*2), '-e', 'now')
204 values = [ v[0] for v in values if v[0] is not None ]
205 if values: return values[-1]
206
207 - def check(self, dataPoints):
217
218 - def checkRaw(self, dataPoint, timeOf, value):
233
235 'Check the value for min/max thresholds'
236 log.debug("Checking %s %s against min %s and max %s",
237 dp, value, self.minimum, self.maximum)
238 if value is None:
239 return []
240 if isinstance(value, basestring):
241 value = float(value)
242 thresh = None
243
244
245 if self.maximum is not None and self.minimum is not None:
246 if self.maximum >= self.minimum and value > self.maximum:
247 thresh = self.maximum
248 how = 'exceeded'
249 if self.maximum >= self.minimum and value < self.minimum:
250 thresh = self.maximum
251 how = 'not met'
252 elif self.maximum < self.minimum \
253 and (value < self.minimum and value > self.maximum):
254 thresh = self.maximum
255 how = 'violated'
256
257
258 elif self.maximum is not None and value > self.maximum:
259 thresh = self.maximum
260 how = 'exceeded'
261 elif self.minimum is not None and value < self.minimum:
262 thresh = self.minimum
263 how = 'not met'
264
265 if thresh is not None:
266 severity = self.severity
267 count = self.incrementCount(dp)
268 if self.escalateCount and count >= self.escalateCount:
269 severity = min(severity + 1, 5)
270 summary = 'threshold of %s %s: current value %f' % (
271 self.name(), how, float(value))
272 return self.processEvent(dict(
273 device=self.context().deviceName,
274 summary=summary,
275 eventKey=self.id,
276 eventClass=self.eventClass,
277 component=self.context().componentName,
278 how=how,
279 min=self.minimum,
280 max=self.maximum,
281 current=value,
282 severity=severity))
283 else:
284 count = self.getCount(dp)
285 if count is None or count > 0:
286 summary = 'threshold of %s restored: current value %f' % (
287 self.name(), value)
288 self.resetCount(dp)
289 return self.processClearEvent(dict(
290 device=self.context().deviceName,
291 summary=summary,
292 eventKey=self.id,
293 eventClass=self.eventClass,
294 component=self.context().componentName,
295 min=self.minimum,
296 max=self.maximum,
297 current=value,
298 severity=Event.Clear))
299 return []
300
302 """
303 When a threshold condition is violated, pre-process it for (possibly) nicer
304 formatting or more complicated logic.
305
306 @paramater evt: event
307 @type evt: dictionary
308 @rtype: list of dictionaries
309 """
310 return [evt]
311
313 """
314 When a threshold condition is restored, pre-process it for (possibly) nicer
315 formatting or more complicated logic.
316
317 @paramater evt: event
318 @type evt: dictionary
319 @rtype: list of dictionaries
320 """
321 return [evt]
322
330
331
332 - def getGraphElements(self, template, context, gopts, namespace, color,
333 legend, relatedGps):
334 """Produce a visual indication on the graph of where the
335 threshold applies."""
336 unused(template, namespace)
337 if not color.startswith('#'):
338 color = '#%s' % color
339 minval = self.minimum
340 if minval is None:
341 minval = NaN
342 maxval = self.maximum
343 if maxval is None:
344 maxval = NaN
345 if not self.dataPointNames:
346 return gopts
347 gp = relatedGps[self.dataPointNames[0]]
348
349
350 rpn = getattr(gp, 'rpn', None)
351 if rpn:
352 try:
353 rpn = talesEvalStr(rpn, context)
354 except:
355 self.raiseRPNExc()
356 return gopts
357
358 try:
359 minval = rpneval(minval, rpn)
360 except:
361 minval= 0
362 self.raiseRPNExc()
363
364 try:
365 maxval = rpneval(maxval, rpn)
366 except:
367 maxval= 0
368 self.raiseRPNExc()
369
370 minstr = self.setPower(minval)
371 maxstr = self.setPower(maxval)
372
373 minval = nanToNone(minval)
374 maxval = nanToNone(maxval)
375 if legend:
376 gopts.append(
377 "HRULE:%s%s:%s\\j" % (minval or maxval, color, legend))
378 elif minval is not None and maxval is not None:
379 if minval == maxval:
380 gopts.append(
381 "HRULE:%s%s:%s not equal to %s\\j" % (minval, color,
382 self.getNames(relatedGps), minstr))
383 elif minval < maxval:
384 gopts.append(
385 "HRULE:%s%s:%s not within %s and %s\\j" % (minval, color,
386 self.getNames(relatedGps), minstr, maxstr))
387 gopts.append("HRULE:%s%s" % (maxval, color))
388 elif minval > maxval:
389 gopts.append(
390 "HRULE:%s%s:%s between %s and %s\\j" % (minval, color,
391 self.getNames(relatedGps), maxstr, minstr))
392 gopts.append("HRULE:%s%s" % (maxval, color))
393 elif minval is not None :
394 gopts.append(
395 "HRULE:%s%s:%s less than %s\\j" % (minval, color,
396 self.getNames(relatedGps), minstr))
397 elif maxval is not None:
398 gopts.append(
399 "HRULE:%s%s:%s greater than %s\\j" % (maxval, color,
400 self.getNames(relatedGps), maxstr))
401
402 return gopts
403
404
406 names = list(set(x.split('_', 1)[1] for x in self.dataPointNames))
407 names.sort()
408 return ', '.join(names)
409
411 powers = ("k", "M", "G")
412 if number < 1000: return number
413 for power in powers:
414 number = number / 1000.0
415 if number < 1000:
416 return "%0.2f%s" % (number, power)
417 return "%.2f%s" % (number, powers[-1])
418
419 from twisted.spread import pb
420 pb.setUnjellyableForClass(MinMaxThresholdInstance, MinMaxThresholdInstance)
421