Package Products :: Package ZenRRD :: Module RRDUtil |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenRRD.RRDUtil

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  __doc__ = """RRDUtil
     15
     16  Wrapper routines around the rrdtool library.
     17  """
     18
     19  import logging
     20  log = logging.getLogger("zen.RRDUtil")
     21
     22  import os
     23  import re
     24
     25  from Products.ZenUtils.Utils import zenPath
     26
     27
     28  EMPTY_RRD = zenPath('perf', 'empty.rrd')
     29
     30
     31 -def _checkUndefined(x):
     32      """
     33      Sanity check on the min, max values
     34
     35      @param x: RRD min or max value
     36      @type x: number
     37      @return: Either the number or 'U' (for undefined)
     38      @rtype: number or string
     39      """
     40      if x is None or x == '' or x == -1 or x == '-1':
     41          return 'U'
     42      return x
     43
     44
     45 -def convertToRRDTime(val):
     46      """
     47      Convert any value that is passed in to a string that is acceptable to use
     48      for RRDtool's start and end parameters. Raises ValueError if this is not
     49      possible.
     50
     51      See the AT-STYLE TIME SPECIFICATION and TIME REFERENCE SPECIFICATION
     52      sections of the following document.
     53
     54          http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html
     55
     56      Note: Currently this method is only fixing floats by turning them into
     57            strings with no decimal places.
     58      """
     59      # Integers are ok. This will also strip decimal precision.
     60      try:
     61          result = int(val)
     62          return str(result)
     63      except ValueError:
     64          pass
     65
     66      return str(val)
     67
     68
     69 -def fixMissingRRDs(gopts):
     70      """
     71      Parses a list of RRDtool gopts for DEFs. Runs all of the filenames
     72      referenced by those DEFs through the fixRRDFilename method to make sure
     73      that they will exist and not cause the rendering to fail.
     74      """
     75      fixed_gopts = []
     76
     77      def_match = re.compile(r'^DEF:([^=]+)=([^:]+)').match
     78      for gopt in gopts:
     79          match = def_match(gopt)
     80          if not match:
     81              fixed_gopts.append(gopt)
     82              continue
     83
     84          rrd_filename = match.group(2)
     85          fixed_gopts.append(gopt.replace(
     86              rrd_filename, fixRRDFilename(rrd_filename)))
     87
     88      return fixed_gopts
     89
     90
     91 -def fixRRDFilename(filename):
     92      """
     93      Attempting to render a graph containing a DEF referencing a non-existent
     94      filename will cause the entire graph to fail to render. This method is a
     95      helper to verify existence of an RRD file. If the file doesn't exist, a
     96      placeholder RRD filename with no values in it will be returned instead.
     97      """
     98      if os.path.isfile(filename):
     99          return filename
    100
    101      if not os.path.isfile(EMPTY_RRD):
    102          import rrdtool
    103          rrdtool.create(EMPTY_RRD, "--step", '300', 'DS:ds0:GAUGE:900:U:U',
    104              'RRA:AVERAGE:0.5:1:1', 'RRA:MAX:0.5:1:1')
    105
    106      return EMPTY_RRD
    107
    108
    109 -class RRDUtil:
    110      """
    111      Wrapper class around rrdtool
    112      """
    113
    114 -    def __init__(self, defaultRrdCreateCommand, defaultCycleTime):
    115          """
    116          Initializer
    117
    118          The RRD creation command is only used if the RRD file doesn't
    119          exist and no rrdCommand was specified with the save() method.
    120
    121          @param defaultRrdCreateCommand: RRD creation command
    122          @type defaultRrdCreateCommand: string
    123          @param defaultCycleTime: expected time to periodically collect data
    124          @type defaultCycleTime: integer
    125          """
    126          self.defaultRrdCreateCommand = defaultRrdCreateCommand
    127          self.defaultCycleTime = defaultCycleTime
    128          self.dataPoints = 0
    129          self.cycleDataPoints = 0
    130
    131
    132 -    def endCycle(self):
    133          """
    134          Report on the number of data points collected in a cycle,
    135          and reset the counter for a new cycle.
    136
    137          @return: number of data points collected during the cycle
    138          @rtype: number
    139          """
    140          result = self.cycleDataPoints
    141          self.cycleDataPoints = 0
    142          return result
    143
    144
    145 -    def performancePath(self, path):
    146          """
    147          Given a path, return its location from $ZENHOME and the
    148          perf/ directories.
    149
    150          @param path: name for a datapoint in a path (eg device/component/datasource_datapoint)
    151          @type path: string
    152          @return: absolute path
    153          @rtype: string
    154          """
    155          from Products.ZenModel.PerformanceConf import performancePath
    156          return performancePath(path)
    157
    158
    159 -    def getStep(self, cycleTime):
    160          """
    161          Return the step value for the provided cycleTime. This is a hook for
    162          altering the default step calculation.
    163          """
    164          return int(cycleTime)
    165
    166
    167 -    def getHeartbeat(self, cycleTime):
    168          """
    169          Return the heartbeat value for the provided cycleTime. This is a hook
    170          for altering the default heartbeat calculation.
    171          """
    172          return int(cycleTime) * 3
    173
    174
    175 -    def save(self, path, value, rrdType, rrdCommand=None, cycleTime=None,
    176               min='U', max='U'):
    177          """
    178          Save the value provided in the command to the RRD file specified in path.
    179
    180          If the RRD file does not exist, use the rrdType, rrdCommand, min and
    181          max parameters to create the file.
    182
    183          @param path: name for a datapoint in a path (eg device/component/datasource_datapoint)
    184          @type path: string
    185          @param value: value to store into the RRD file
    186          @type value: number
    187          @param rrdType: RRD data type (eg ABSOLUTE, DERIVE, COUNTER)
    188          @type rrdType: string
    189          @param rrdCommand: RRD file creation command
    190          @type rrdCommand: string
    191          @param cycleTime: length of a cycle
    192          @type cycleTime: number
    193          @param min: minimum value acceptable for this metric
    194          @type min: number
    195          @param max: maximum value acceptable for this metric
    196          @type max: number
    197          @return: the parameter value converted to a number
    198          @rtype: number or None
    199          """
    200          import rrdtool, os
    201
    202          if value is None: return None
    203
    204          self.dataPoints += 1
    205          self.cycleDataPoints += 1
    206
    207          if cycleTime is None:
    208              cycleTime = self.defaultCycleTime
    209
    210          filename = self.performancePath(path) + '.rrd'
    211          if not rrdCommand:
    212              rrdCommand = self.defaultRrdCreateCommand
    213          if not os.path.exists(filename):
    214              log.debug("Creating new RRD file %s", filename)
    215              dirname = os.path.dirname(filename)
    216              if not os.path.exists(dirname):
    217                  os.makedirs(dirname, 0750)
    218
    219              min, max = map(_checkUndefined, (min, max))
    220              dataSource = 'DS:%s:%s:%d:%s:%s' % (
    221                  'ds0', rrdType, self.getHeartbeat(cycleTime), min, max)
    222              rrdtool.create(str(filename), "--step",
    223                  str(self.getStep(cycleTime)),
    224                  str(dataSource), *rrdCommand.split())
    225
    226          if rrdType in ('COUNTER', 'DERIVE'):
    227              try:
    228                  value = long(value)
    229              except (TypeError, ValueError):
    230                  return None
    231          else:
    232              try:
    233                  value = float(value)
    234              except (TypeError, ValueError):
    235                  return None
    236          try:
    237              rrdtool.update(str(filename), 'N:%s' % value)
    238              log.debug('%s: %r', str(filename), value)
    239          except rrdtool.error, err:
    240              # may get update errors when updating too quickly
    241              log.error('rrdtool reported error %s %s', err, path)
    242
    243          if rrdType in ('COUNTER', 'DERIVE'):
    244              startStop, names, values = \
    245                  rrdtool.fetch(filename, 'AVERAGE',
    246                      '-s', 'now-%d' % (cycleTime*2),
    247                      '-e', 'now')
    248              values = [ v[0] for v in values if v[0] is not None ]
    249              if values: value = values[-1]
    250              else: value = None
    251          return value
    252