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 or (at your
      8  # option) any later version as published by 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, rrd_daemon_running
     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 -def read(path, consolidationFunction, start, end):
    109      import rrdtool
    110      try:
    111          return rrdtool.fetch(path, consolidationFunction, start, end)
    112      except rrdtool.error, err:
    113          import sys
    114          err_str = '%s: %s' % (err.__class__.__name__, err)
    115          msg = 'Failed to read RRD file %s. %s' % (path, err_str)
    116          raise StandardError(msg), None, sys.exc_info()[2]
    117
    118 -class RRDUtil:
    119      """
    120      Wrapper class around rrdtool
    121      """
    122
    123 -    def __init__(self, defaultRrdCreateCommand, defaultCycleTime):
    124          """
    125          Initializer
    126
    127          The RRD creation command is only used if the RRD file doesn't
    128          exist and no rrdCommand was specified with the save() method.
    129
    130          @param defaultRrdCreateCommand: RRD creation command
    131          @type defaultRrdCreateCommand: string
    132          @param defaultCycleTime: expected time to periodically collect data
    133          @type defaultCycleTime: integer
    134          """
    135          self.defaultRrdCreateCommand = defaultRrdCreateCommand
    136          self.defaultCycleTime = defaultCycleTime
    137          self.dataPoints = 0
    138          self.cycleDataPoints = 0
    139
    140
    141 -    def endCycle(self):
    142          """
    143          Report on the number of data points collected in a cycle,
    144          and reset the counter for a new cycle.
    145
    146          @return: number of data points collected during the cycle
    147          @rtype: number
    148          """
    149          result = self.cycleDataPoints
    150          self.cycleDataPoints = 0
    151          return result
    152
    153
    154 -    def performancePath(self, path):
    155          """
    156          Given a path, return its location from $ZENHOME and the
    157          perf/ directories.
    158
    159          @param path: name for a datapoint in a path (eg device/component/datasource_datapoint)
    160          @type path: string
    161          @return: absolute path
    162          @rtype: string
    163          """
    164          from Products.ZenModel.PerformanceConf import performancePath
    165          return performancePath(path)
    166
    167
    168 -    def getStep(self, cycleTime):
    169          """
    170          Return the step value for the provided cycleTime. This is a hook for
    171          altering the default step calculation.
    172          """
    173          return int(cycleTime)
    174
    175
    176 -    def getHeartbeat(self, cycleTime):
    177          """
    178          Return the heartbeat value for the provided cycleTime. This is a hook
    179          for altering the default heartbeat calculation.
    180          """
    181          return int(cycleTime) * 3
    182
    183
    184 -    def save(self, path, value, rrdType, rrdCommand=None, cycleTime=None,
    185               min='U', max='U', useRRDDaemon=True):
    186          """
    187          Save the value provided in the command to the RRD file specified in path.
    188
    189          If the RRD file does not exist, use the rrdType, rrdCommand, min and
    190          max parameters to create the file.
    191
    192          @param path: name for a datapoint in a path (eg device/component/datasource_datapoint)
    193          @type path: string
    194          @param value: value to store into the RRD file
    195          @type value: number
    196          @param rrdType: RRD data type (eg ABSOLUTE, DERIVE, COUNTER)
    197          @type rrdType: string
    198          @param rrdCommand: RRD file creation command
    199          @type rrdCommand: string
    200          @param cycleTime: length of a cycle
    201          @type cycleTime: number
    202          @param min: minimum value acceptable for this metric
    203          @type min: number
    204          @param max: maximum value acceptable for this metric
    205          @type max: number
    206          @return: the parameter value converted to a number
    207          @rtype: number or None
    208          """
    209          import rrdtool, os
    210
    211          daemon_args = ()
    212          if useRRDDaemon:
    213              daemon = rrd_daemon_running()
    214              if daemon:
    215                  daemon_args = ('--daemon', daemon)
    216
    217          if value is None: return None
    218
    219          self.dataPoints += 1
    220          self.cycleDataPoints += 1
    221
    222          if cycleTime is None:
    223              cycleTime = self.defaultCycleTime
    224
    225          filename = self.performancePath(path) + '.rrd'
    226          if not rrdCommand:
    227              rrdCommand = self.defaultRrdCreateCommand
    228          if not os.path.exists(filename):
    229              log.debug("Creating new RRD file %s", filename)
    230              dirname = os.path.dirname(filename)
    231              if not os.path.exists(dirname):
    232                  os.makedirs(dirname, 0750)
    233
    234              min, max = map(_checkUndefined, (min, max))
    235              dataSource = 'DS:%s:%s:%d:%s:%s' % (
    236                  'ds0', rrdType, self.getHeartbeat(cycleTime), min, max)
    237              rrdtool.create(str(filename), "--step",
    238                  str(self.getStep(cycleTime)),
    239                  str(dataSource), *rrdCommand.split())
    240
    241          if rrdType in ('COUNTER', 'DERIVE'):
    242              try:
    243                  value = long(value)
    244              except (TypeError, ValueError):
    245                  return None
    246          else:
    247              try:
    248                  value = float(value)
    249              except (TypeError, ValueError):
    250                  return None
    251          try:
    252              rrdtool.update(str(filename), *(daemon_args + ('N:%s' % value,)))
    253              log.debug('%s: %r', str(filename), value)
    254          except rrdtool.error, err:
    255              # may get update errors when updating too quickly
    256              log.error('rrdtool reported error %s %s', err, path)
    257
    258          if rrdType in ('COUNTER', 'DERIVE'):
    259              startStop, names, values = \
    260                  rrdtool.fetch(filename, 'AVERAGE',
    261                      '-s', 'now-%d' % (cycleTime*2),
    262                      '-e', 'now', *daemon_args)
    263              values = [ v[0] for v in values if v[0] is not None ]
    264              if values: value = values[-1]
    265              else: value = None
    266          return value
    267