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

# Source Code for Module Products.ZenModel.MinMaxThreshold

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2007, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  from Products.ZenModel.ThresholdInstance import RRDThresholdInstance
     12
     13  __doc__= """MinMaxThreshold
     14  Make threshold comparisons dynamic by using TALES expresssions,
     15  rather than just number bounds checking.
     16  """
     17
     18  from AccessControl import Permissions
     19
     20  from Globals import InitializeClass
     21  from ThresholdClass import ThresholdClass
     22  from ThresholdInstance import ThresholdContext
     23  from Products.ZenEvents import Event
     24  from Products.ZenEvents.ZenEventClasses import Perf_Snmp
     25  from Products.ZenUtils.ZenTales import talesEval, talesEvalStr
     26  from Products.ZenEvents.Exceptions import pythonThresholdException, \
     27          rpnThresholdException
     28
     29  import logging
     30  log = logging.getLogger('zen.MinMaxCheck')
     31
     32  from Products.ZenUtils.Utils import unused, nanToNone
     33
     34  # Note:  this import is for backwards compatibility.
     35  # Import Products.ZenRRD.utils.rpneval directy.
     36  from Products.ZenRRD.utils import rpneval
     37
     38  NaN = float('nan')
     39
     40 -class MinMaxThreshold(ThresholdClass):
     41      """
     42      Threshold class that can evaluate RPNs and Python expressions
     43      """
     44
     45      minval = ""
     46      maxval = ""
     47      eventClass = Perf_Snmp
     48      severity = 3
     49      escalateCount = 0
     50
     51      _properties = ThresholdClass._properties + (
     52          {'id':'minval',        'type':'string',  'mode':'w'},
     53          {'id':'maxval',        'type':'string',  'mode':'w'},
     54          {'id':'escalateCount', 'type':'int',     'mode':'w'}
     55          )
     56
     57      factory_type_information = (
     58          {
     59          'immediate_view' : 'editRRDThreshold',
     60          'actions'        :
     61          (
     62          { 'id'            : 'edit'
     63            , 'name'          : 'Min/Max Threshold'
     64            , 'action'        : 'editRRDThreshold'
     65            , 'permissions'   : ( Permissions.view, )
     66            },
     67          )
     68          },
     69          )
     70
     71 -    def createThresholdInstance(self, context):
     72          """Return the config used by the collector to process min/max
     73          thresholds. (id, minval, maxval, severity, escalateCount)
     74          """
     75          mmt = MinMaxThresholdInstance(self.id,
     76                                        ThresholdContext(context),
     77                                        self.dsnames,
     78                                        minval=self.getMinval(context),
     79                                        maxval=self.getMaxval(context),
     80                                        eventClass=self.eventClass,
     81                                        severity=self.severity,
     82                                        escalateCount=self.escalateCount)
     83          return mmt
     84
     85 -    def getMinval(self, context):
     86          """Build the min value for this threshold.
     87          """
     88          minval = None
     89          if self.minval:
     90              try:
     91                  express = "python:%s" % self.minval
     92                  minval = talesEval(express, context)
     93              except:
     94                  msg= "User-supplied Python expression (%s) for minimum value caused error: %s" % \
     95                             ( self.minval,  self.dsnames )
     96                  log.error( msg )
     97                  raise pythonThresholdException(msg)
     98                  minval = None
     99          return nanToNone(minval)
    100
    101
    102 -    def getMaxval(self, context):
    103          """Build the max value for this threshold.
    104          """
    105          maxval = None
    106          if self.maxval:
    107              try:
    108                  express = "python:%s" % self.maxval
    109                  maxval = talesEval(express, context)
    110              except:
    111                  msg= "User-supplied Python expression (%s) for maximum value caused error: %s" % \
    112                             ( self.maxval,  self.dsnames )
    113                  log.error( msg )
    114                  raise pythonThresholdException(msg)
    115                  maxval = None
    116          return nanToNone(maxval)
    117
    118  InitializeClass(MinMaxThreshold)
    119  MinMaxThresholdClass = MinMaxThreshold
    120
    121
    122
    123 -class MinMaxThresholdInstance(RRDThresholdInstance):
    124      # Not strictly necessary, but helps when restoring instances from
    125      # pickle files that were not constructed with a count member.
    126      count = {}
    127
    128 -    def __init__(self, id, context, dpNames,
    129                   minval, maxval, eventClass, severity, escalateCount):
    130          RRDThresholdInstance.__init__(self, id, context, dpNames, eventClass, severity)
    131          self.count = {}
    132          self.minimum = minval
    133          self.maximum = maxval
    134          self.escalateCount = escalateCount
    135
    136 -    def countKey(self, dp):
    137          return ':'.join(self.context().key()) + ':' + dp
    138
    139 -    def getCount(self, dp):
    140          countKey = self.countKey(dp)
    141          if not countKey in self.count:
    142              return None
    143          return self.count[countKey]
    144
    145 -    def incrementCount(self, dp):
    146          countKey = self.countKey(dp)
    147          if not countKey in self.count:
    148              self.resetCount(dp)
    149          self.count[countKey] += 1
    150          return self.count[countKey]
    151
    152 -    def resetCount(self, dp):
    153          self.count[self.countKey(dp)] = 0
    154
    155 -    def checkRange(self, dp, value):
    156          'Check the value for min/max thresholds'
    157          log.debug("Checking %s %s against min %s and max %s",
    158                    dp, value, self.minimum, self.maximum)
    159          if value is None:
    160              return []
    161          if isinstance(value, basestring):
    162              value = float(value)
    163          thresh = None
    164
    165          # Handle all cases where both minimum and maximum are set.
    166          if self.maximum is not None and self.minimum is not None:
    167              if self.maximum >= self.minimum:
    168                  if value > self.maximum:
    169                      thresh = self.maximum
    170                      how = 'exceeded'
    171                  elif value < self.minimum:
    172                      thresh = self.minimum
    173                      how = 'not met'
    174              elif self.maximum < value < self.minimum:
    175                  thresh = self.maximum
    176                  how = 'violated'
    177
    178          # Handle simple cases where only minimum or maximum is set.
    179          elif self.maximum is not None and value > self.maximum:
    180              thresh = self.maximum
    181              how = 'exceeded'
    182          elif self.minimum is not None and value < self.minimum:
    183              thresh = self.minimum
    184              how = 'not met'
    185
    186          if thresh is not None:
    187              severity = self.severity
    188              count = self.incrementCount(dp)
    189              if self.escalateCount and count >= self.escalateCount:
    190                  severity = min(severity + 1, 5)
    191              summary = 'threshold of %s %s: current value %f' % (
    192                  self.name(), how, float(value))
    193              evtdict = self._create_event_dict(value, summary, severity, how)
    194              if self.escalateCount:
    195                  evtdict['escalation_count'] = count
    196              return self.processEvent(evtdict)
    197          else:
    198              summary = 'threshold of %s restored: current value %f' % (
    199                  self.name(), value)
    200              self.resetCount(dp)
    201              return self.processClearEvent(self._create_event_dict(value, summary, Event.Clear))
    202
    203 -    def _create_event_dict(self, current, summary, severity, how=None):
    204          event_dict = dict(device=self.context().deviceName,
    205                            summary=summary,
    206                            eventKey=self.id,
    207                            eventClass=self.eventClass,
    208                            component=self.context().componentName,
    209                            min=self.minimum,
    210                            max=self.maximum,
    211                            current=current,
    212                            severity=severity)
    213          deviceUrl = getattr(self.context(), "deviceUrl", None)
    214          if deviceUrl is not None:
    215              event_dict["zenoss.device.url"] = deviceUrl
    216          devicePath = getattr(self.context(), "devicePath", None)
    217          if devicePath is not None:
    218              event_dict["zenoss.device.path"] = devicePath
    219          if how is not None:
    220              event_dict['how'] = how
    221          return event_dict
    222
    223
    224 -    def processEvent(self, evt):
    225          """
    226          When a threshold condition is violated, pre-process it for (possibly) nicer
    227          formatting or more complicated logic.
    228
    229          @paramater evt: event
    230          @type evt: dictionary
    231          @rtype: list of dictionaries
    232          """
    233          return [evt]
    234
    235 -    def processClearEvent(self, evt):
    236          """
    237          When a threshold condition is restored, pre-process it for (possibly) nicer
    238          formatting or more complicated logic.
    239
    240          @paramater evt: event
    241          @type evt: dictionary
    242          @rtype: list of dictionaries
    243          """
    244          return [evt]
    245
    246 -    def raiseRPNExc( self ):
    247          """
    248          Raise an RPN exception, taking care to log all details.
    249          """
    250          msg= "The following RPN exception is from user-supplied code."
    251          log.exception( msg )
    252          raise rpnThresholdException(msg)
    253
    254
    255 -    def getGraphElements(self, template, context, gopts, namespace, color,
    256                           legend, relatedGps):
    257          """Produce a visual indication on the graph of where the
    258          threshold applies."""
    259          unused(template, namespace)
    260          if not color.startswith('#'):
    261              color = '#%s' % color
    262          minval = self.minimum
    263          if minval is None:
    264              minval = NaN
    265          maxval = self.maximum
    266          if maxval is None:
    267              maxval = NaN
    268          if not self.dataPointNames:
    269              return gopts
    270          gp = relatedGps[self.dataPointNames[0]]
    271
    272          # Attempt any RPN expressions
    273          rpn = getattr(gp, 'rpn', None)
    274          if rpn:
    275              try:
    276                  rpn = talesEvalStr(rpn, context)
    277              except:
    278                  self.raiseRPNExc()
    279                  return gopts
    280
    281              try:
    282                  minval = rpneval(minval, rpn)
    283              except:
    284                  minval= 0
    285                  self.raiseRPNExc()
    286
    287              try:
    288                  maxval = rpneval(maxval, rpn)
    289              except:
    290                  maxval= 0
    291                  self.raiseRPNExc()
    292
    293          minstr = self.setPower(minval)
    294          maxstr = self.setPower(maxval)
    295
    296          minval = nanToNone(minval)
    297          maxval = nanToNone(maxval)
    298          if legend:
    299              gopts.append(
    300                  "HRULE:%s%s:%s\\j" % (minval or maxval, color, legend))
    301          elif minval is not None and maxval is not None:
    302              if minval == maxval:
    303                  gopts.append(
    304                      "HRULE:%s%s:%s not equal to %s\\j" % (minval, color,
    305                          self.getNames(relatedGps), minstr))
    306              elif minval < maxval:
    307                  gopts.append(
    308                      "HRULE:%s%s:%s not within %s and %s\\j" % (minval, color,
    309                          self.getNames(relatedGps), minstr, maxstr))
    310                  gopts.append("HRULE:%s%s" % (maxval, color))
    311              elif minval > maxval:
    312                  gopts.append(
    313                      "HRULE:%s%s:%s between %s and %s\\j" % (minval, color,
    314                          self.getNames(relatedGps), maxstr, minstr))
    315                  gopts.append("HRULE:%s%s" % (maxval, color))
    316          elif minval is not None :
    317              gopts.append(
    318                  "HRULE:%s%s:%s less than %s\\j" % (minval, color,
    319                      self.getNames(relatedGps), minstr))
    320          elif maxval is not None:
    321              gopts.append(
    322                  "HRULE:%s%s:%s greater than %s\\j" % (maxval, color,
    323                      self.getNames(relatedGps), maxstr))
    324
    325          return gopts
    326
    327
    328 -    def getNames(self, relatedGps):
    329          names = sorted(set(x.split('_', 1)[1] for x in self.dataPointNames))
    330          return ', '.join(names)
    331
    332 -    def setPower(self, number):
    333          powers = ("k", "M", "G")
    334          if number < 1000: return number
    335          for power in powers:
    336              number = number / 1000.0
    337              if number < 1000:
    338                  return "%0.2f%s" % (number, power)
    339          return "%.2f%s" % (number, powers[-1])
    340
    341 -    def _checkImpl(self, dataPoint, value):
    342          return self.checkRange(dataPoint, value)
    343
    344  from twisted.spread import pb
    345  pb.setUnjellyableForClass(MinMaxThresholdInstance, MinMaxThresholdInstance)
    346