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
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 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
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
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
347 """build a label for a min threshold"""
348 return "%s < %s" % (self.getNames(relatedGps), self.setPower(minval))
349
350
352 """build a label for a max threshold"""
353 return "%s > %s" % (self.getNames(relatedGps), self.setPower(maxval))
354
356 legends = [ getattr(gp, 'legend', gp) for gp in relatedGps.values() ]
357 return ', '.join(legends)
358
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