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

# Source Code for Module Products.ZenRRD.utils

      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__="""utils
     15
     16  RRD utility functions
     17  """
     18
     19  from sets import Set
     20  from Acquisition import aq_chain
     21
     22  from Exceptions import RRDObjectNotFound, TooManyArgs
     23  import math
     24  import time
     25
     26 -def loadargs(obj, args):
     27      """
     28      Load data into a RRD Object
     29
     30      @param obj: RRD object
     31      @type obj: RRD object
     32      @param args: arguments
     33      @type args: list of strings
     34      """
     35      import string
     36      arglen = len(args)
     37      if arglen > len(obj._properties):
     38          raise TooManyArgs( "Too many args" )
     39      i = 0
     40      for arg in args:
     41          if i > arglen: break
     42          arg = arg.strip()
     43          att = obj._properties[i]['id']
     44          try:
     45              if arg != '':
     46                  if obj._properties[i]['type'] == 'lines':
     47                      value = map(string.strip, arg.split(','))
     48                      att = '_' + att
     49                  elif obj._properties[i]['type'] == 'int':
     50                      value = int(arg)
     51                  elif obj._properties[i]['type'] == 'long':
     52                      value = long(arg)
     53                  elif obj._properties[i]['type'] == 'float':
     54                      value = float(arg)
     55                  elif obj._properties[i]['type'] == 'boolean':
     56                      value = eval(arg)
     57                  else:
     58                      value = arg
     59                  if value is not None: setattr(obj,att,value)
     60          except:
     61              print "att = %s value = %s" % (att, arg)
     62              raise
     63          i += 1
     64
     65
     66 -def prefixid(idprefix, id):
     67      """
     68      See if prefix needs to be added to id
     69
     70      @param idprefix: prefix
     71      @type idprefix: string
     72      @param id: identifier
     73      @type id: string
     74      @return: add the prefix with a '-' in between
     75      @rtype: string
     76      """
     77      if id.find(idprefix) != 0:
     78          id = idprefix + '-' + id
     79      return id
     80
     81
     82 -def rootid(idprefix, id):
     83      """
     84      See if prefix needs to be removed from id
     85
     86      @param idprefix: prefix
     87      @type idprefix: string
     88      @param id: identifier
     89      @type id: string
     90      @return: remove the prefix with a '-' in between or return None
     91      @rtype: string or None
     92      """
     93      if idprefix[-1] != '-': idprefix += '-'
     94      if id.find(idprefix) == 0:
     95          return id[len(idprefix):]
     96
     97
     98 -def walkupconfig(context, name):
     99      """
    100      Given a Zope context, try to find the rrdconfig object
    101      for the name.
    102      Raises RRDObjectNotFound if not found.
    103
    104      @param context: Zope context
    105      @type context: Zope context object
    106      @param name: RRDView name
    107      @type name: string
    108      @return: rrdconfig object or None
    109      @rtype: rrdconfig object
    110      """
    111      if not name: return
    112      while 1:
    113          if hasattr(context, 'rrdconfig') and hasattr(context.rrdconfig, name):
    114              return getattr(context.rrdconfig, name)
    115          context = context.aq_parent
    116          if context.id == 'dmd':
    117              raise RRDObjectNotFound( "Object %s not found in context %s" % \
    118                                      (name, context.getPrimaryUrlPath()))
    119
    120
    121 -def templateNames(context):
    122      """
    123      Return template names in the given context
    124
    125      @param context: Zope context
    126      @type context: Zope context object
    127      @return: names of the templates
    128      @rtype: set of strings
    129      """
    130      names = Set()
    131      for obj in aq_chain(context):
    132          rrdconfig = getattr(obj, 'rrdconfig', None)
    133          if rrdconfig:
    134              names = names.union(rrdconfig.objectIds(spec='RRDTargetType'))
    135      return names
    136
    137
    138
    139 -def getRRDView(context, name):
    140      """
    141      Lookup an RRDView based on its name
    142
    143      @param context: Zope context
    144      @type context: Zope context object
    145      @param name: RRDView name
    146      @type name: string
    147      @return: rrdconfig object or None
    148      @rtype: rrdconfig object
    149      """
    150      return walkupconfig(context, 'RRDView-'+name)
    151
    152
    153 -def getRRDTargetType(context, name):
    154      """
    155      Lookup an rrdtargettype based on its name
    156
    157      @param context: Zope context
    158      @type context: Zope context object
    159      @param name: RRDView name
    160      @type name: string
    161      @return: rrdconfig object or None
    162      @rtype: rrdconfig object
    163      """
    164      return walkupconfig(context, 'RRDTargetType-'+name)
    165
    166
    167 -def getRRDDataSource(context, name):
    168      """
    169      Lookup an rrddatasource based on its name
    170
    171      @param context: Zope context
    172      @type context: Zope context object
    173      @param name: RRDView name
    174      @type name: string
    175      @return: rrdconfig object or None
    176      @rtype: rrdconfig object
    177      """
    178      return walkupconfig(context, 'RRDDataSource-'+name)
    179
    180
    181 -class rpnStack(object):
    182
    183      NAN = 1e5000 - 1e5000
    184      INF = 1e5000
    185
    186 -    def __init__(self, value):
    187          self.stack = [float(value)]
    188
    189 -    def sanitizePop(self):
    190          x = self.stack.pop()
    191          return 0 if x != x else x
    192
    193 -    def process(self, count, proc):
    194          args = []
    195          stack = self.stack
    196          for i in range(count):
    197              args.append(stack.pop())
    198          stack.append(proc(*args))
    199
    200      # Note:  0 does not pollute.  Sorry.
    201 -    def polluteProcess(self, count, condition, proc):
    202          stack = self.stack
    203          args = []
    204          polluted = False
    205          for i in range(count):
    206              x = stack.pop()
    207              if condition(x):
    208                  polluted = x
    209              args.append(x)
    210          args.insert(0, polluted)
    211          stack.append(proc(*args))
    212
    213 -    def dynamicProcess(self, proc):
    214          count = int(self.stack.pop())
    215          args = self.stack[-count:]
    216          self.stack = self.stack[0:-count]
    217          self.stack.extend(proc(args))
    218
    219 -    def isSpecial(self, x):
    220          return math.isinf(x) or x != x
    221
    222 -    def lt(self):
    223          self.polluteProcess(2, self.isSpecial,
    224              lambda p, y, x: 1.0 if not(p) and x < y else 0.0)
    225
    226 -    def le(self):
    227          self.polluteProcess(2, self.isSpecial,
    228              lambda p, y, x: 1.0 if not(p) and x <= y else 0.0)
    229
    230 -    def gt(self):
    231          self.polluteProcess(2, self.isSpecial,
    232              lambda p, y, x: 1.0 if not(p) and x > y else 0.0)
    233
    234 -    def ge(self):
    235          self.polluteProcess(2, self.isSpecial,
    236              lambda p, y, x: 1.0 if not(p) and x >= y else 0.0)
    237
    238 -    def eq(self):
    239          self.polluteProcess(2, self.isSpecial,
    240              lambda p, y, x: 1.0 if not(p) and x == y else 0.0)
    241
    242 -    def ne(self):
    243          self.polluteProcess(2, self.isSpecial,
    244              lambda p, y, x: 1.0 if not(p) and x != y else 0.0)
    245
    246 -    def un(self):
    247          self.process(1, lambda x: 1.0 if x != x else 0.0)
    248
    249 -    def isInf(self):
    250          self.process(1, lambda x: 1.0 if math.isinf(x) else 0.0)
    251
    252 -    def if_(self):
    253          self.process(3, lambda y, x, c: x if c != 0.0 else y)
    254
    255 -    def min_(self):
    256          self.polluteProcess(2,
    257              lambda x: x != x,
    258              lambda p, y, x: p if p else min(x, y)
    259          )
    260
    261 -    def max_(self):
    262          self.polluteProcess(2,
    263              lambda x: x != x,
    264              lambda p, y, x: p if p else max(x, y)
    265          )
    266
    267 -    def limit(self):
    268          self.polluteProcess(3,
    269              self.isSpecial,
    270              lambda p, y, x, v: v if not(p) and
    271                  (x <= v and v <= y or x >= v and v >= y) else self.NAN
    272          )
    273
    274 -    def mul(self):
    275          self.process(2, lambda y, x: x * y)
    276
    277 -    def div(self):
    278          self.process(2, lambda y, x: self.NAN if y == 0 else x / y)
    279
    280 -    def add(self):
    281          self.process(2, lambda y, x: x + y)
    282
    283 -    def sub(self):
    284          self.process(2, lambda y, x: x - y)
    285
    286 -    def mod(self):
    287          self.process(2, lambda y, x: self.NAN if y == 0 else math.fmod(x, y))
    288
    289 -    def addNaN(self):
    290          x = self.sanitizePop()
    291          y = self.sanitizePop()
    292          self.stack.append(x + y)
    293
    294 -    def sin(self):
    295          self.process(1, lambda x: math.sin(x))
    296
    297 -    def cos(self):
    298          self.process(1, lambda x: math.cos(x))
    299
    300 -    def log(self):
    301          self.process(1, lambda x: math.log(x))
    302
    303 -    def exp(self):
    304          self.process(1, lambda x: math.exp(x))
    305
    306 -    def sqrt(self):
    307          self.process(1, lambda x: math.sqrt(x))
    308
    309 -    def atan(self):
    310          self.process(1, lambda x: math.atan(x))
    311
    312 -    def atan2(self):
    313          self.process(2, lambda y, x: math.atan2(x, y))
    314
    315 -    def floor(self):
    316          self.process(1, lambda x: math.floor(x))
    317
    318 -    def ceil(self):
    319          self.process(1, lambda x: math.ceil(x))
    320
    321 -    def deg2rad(self):
    322          self.process(1, lambda x: math.radians(x))
    323
    324 -    def rad2deg(self):
    325          self.process(1, lambda x: math.degrees(x))
    326
    327 -    def abs(self):
    328          self.process(1, lambda x: abs(x))
    329
    330 -    def sort(self):
    331          self.dynamicProcess(lambda a: a.sort() or a)
    332
    333 -    def rev(self):
    334          self.dynamicProcess(lambda a: a.reverse() or a)
    335
    336 -    def avg(self):
    337          def average(a):
    338              total = count = 0
    339              for x in a:
    340                  if not(math.isnan(x)):
    341                      count += 1
    342                      total += x
    343              return [total / count] if count > 0 else [self.NAN]
    344          self.dynamicProcess(average)
    345
    346 -    def unkn(self):
    347          self.stack.append(self.NAN)
    348
    349 -    def inf(self):
    350          self.stack.append(self.INF)
    351
    352 -    def neginf(self):
    353          self.stack.append(-self.INF)
    354
    355 -    def time_(self):
    356          self.stack.append(time.time())
    357
    358 -    def dup(self):
    359          stack = self.stack
    360          stack.append(stack[-1])
    361
    362 -    def pop(self):
    363          self.stack.pop()
    364
    365 -    def exc(self):
    366          stack = self.stack
    367          stack[-1], stack[-2] = stack[-2], stack[-1]
    368      opcodes = {
    369          'LT': lt,
    370          'LE': le,
    371          'GT': gt,
    372          'GE': ge,
    373          'EQ': eq,
    374          'NE': ne,
    375          'UN': un,
    376          'ISINF': isInf,
    377          'IF': if_,
    378          'MIN': min_,
    379          'MAX': max_,
    380          'LIMIT': limit,
    381          '+': add,
    382          '-': sub,
    383          '*': mul,
    384          '/': div,
    385          '%': mod,
    386          'ADDNAN': addNaN,
    387          'SIN': sin,
    388          'COS': cos,
    389          'LOG': log,
    390          'EXP': exp,
    391          'SQRT': sqrt,
    392          'ATAN': atan,
    393          'ATAN2': atan2,
    394          'FLOOR': floor,
    395          'CEIL': ceil,
    396          'DEG2RAD': deg2rad,
    397          'RAD2DEG': rad2deg,
    398          'ABS': abs,
    399          'SORT': sort,
    400          'REV': rev,
    401          'AVG': avg,
    402          'UNKN': unkn,
    403          'INF': inf,
    404          'NEGINF': neginf,
    405          'TIME': time_,
    406          'DUP': dup,
    407          'POP': pop,
    408          'EXC': exc,
    409      }
    410
    411 -    def step(self, op):
    412          if op in self.opcodes:
    413              self.opcodes[op](self)
    414          else:
    415              self.stack.append(float(op))
    416
    417 -    def result(self):
    418          return self.stack.pop()
    419
    420
    421  # This is ONLY for the doctests of rpneval.  If you need to do serious floating
    422  # point nearness checking, consult someone who knows IEEE-754 in detail.  This
    423  # one blows up, sometimes subtlely.
    424 -def close(x, y):
    425      return (abs(x - y) / y) < 1e-15
    426
    427
    428 -def rpneval(value, rpn):
    429      """
    430      Simulate RPN evaluation as per
    431      http://oss.oetiker.ch/rrdtool/doc/rrdgraph_rpn.en.html
    432      Note:  because we only have one value, we won't support the entire API.
    433      >>> rpneval(2, '2,*')
    434      4.0
    435      >>> rpneval(7, '2,3,*,*')
    436      42.0
    437      >>> close(rpneval(19, '9,5,/,*,32,+'), 66.2)
    438      True
    439      >>> rpneval(1, '*')
    440      -1.0
    441      >>> rpneval(2, '-8,-')
    442      10.0
    443      >>> rpneval(3, '2,%')
    444      1.0
    445      >>> rpneval(1e5000 - 1e5000, 'UN')
    446      1.0
    447      >>> rpneval(70, '71,LT')
    448      1.0
    449      >>> rpneval(69, '69,LT')
    450      0.0
    451      >>> rpneval(68, 'inf,LT')
    452      0.0
    453      >>> rpneval(67, '67,LE')
    454      1.0
    455      >>> rpneval(66, '0,LE')
    456      0.0
    457      >>> rpneval(65, 'inf,LE')
    458      0.0
    459      >>> rpneval(64, '60,GT')
    460      1.0
    461      >>> rpneval(63, '63,GT')
    462      0.0
    463      >>> rpneval(63, 'neginf,GT')
    464      0.0
    465      >>> rpneval(61, '100,GE')
    466      0.0
    467      >>> rpneval(60, '60,GE')
    468      1.0
    469      >>> rpneval(59, 'neginf,GE')
    470      0.0
    471      >>> rpneval(58, '137,EQ')
    472      0.0
    473      >>> rpneval(57, '57,EQ')
    474      1.0
    475      >>> rpneval(56, 'inf,EQ')
    476      0.0
    477      >>> rpneval(55, '55,NE')
    478      0.0
    479      >>> rpneval(-1e5000, 'neginf,EQ')
    480      0.0
    481      >>> rpneval(1e5000 - 1e5000, 'unkn,EQ')
    482      0.0
    483      >>> rpneval(1e5000 - 1e5000, 'unkn,NE')
    484      0.0
    485      >>> rpneval(1e5000, 'inf,NE')
    486      0.0
    487      >>> rpneval(51, '51,NE')
    488      0.0
    489      >>> rpneval(50, ' 42    ,      NE ')
    490      1.0
    491      >>> rpneval(49, 'UN')
    492      0.0
    493      >>> rpneval(-1e5000, 'isINF')
    494      1.0
    495      >>> rpneval(1e5000, 'IsInF')
    496      1.0
    497      >>> rpneval(46, 'ISINF')
    498      0.0
    499      >>> rpneval(0, '1,2,if')
    500      2.0
    501      >>> rpneval(44, '1,2,if')
    502      1.0
    503      >>> rpneval(1e5000, '1,2,IF')
    504      1.0
    505      >>> rpneval(1e5000 - 1e5000, '1,2,iF')
    506      1.0
    507      >>> rpneval(41, '5,min')
    508      5.0
    509      >>> rpneval(40, 'neginf,min') == -1e5000
    510      True
    511      >>> rpneval(39, 'unkn,min')
    512      nan
    513      >>> rpneval(38, 'neginf,max')
    514      38.0
    515      >>> rpneval(37, 'inf,max') == 1e5000
    516      True
    517      >>> math.isnan(rpneval(36, 'unkn,max'))
    518      True
    519      >>> math.isnan(rpneval(35, '30,neginf,limit'))
    520      True
    521      >>> math.isnan(rpneval(34, '30,30.5,limit'))
    522      True
    523      >>> rpneval(33, '30,35,limit')
    524      33.0
    525      >>> rpneval(32, '464,+')
    526      496.0
    527      >>> rpneval(31, '5,-')
    528      26.0
    529      >>> rpneval(37, '18,*')
    530      666.0
    531      >>> close(rpneval(29, '5,/'), 5.8)
    532      True
    533      >>> math.isnan(rpneval(28, '0,/'))
    534      True
    535      >>> rpneval(27, '11,%')
    536      5.0
    537      >>> math.isnan(rpneval(26, '0,%'))
    538      True
    539      >>> rpneval(25, '0,0,/,addnan')
    540      25.0
    541      >>> close(rpneval(math.pi / 6, 'sin'), 0.5)
    542      True
    543      >>> close(rpneval(math.pi / 3, 'cos'), 0.5)
    544      True
    545      >>> rpneval(math.e, 'log') == 1
    546      True
    547      >>> rpneval(1, 'exp') == math.e
    548      True
    549      >>> rpneval(1.44, 'sqrt')
    550      1.2
    551      >>> rpneval(1, 'atan') == math.pi / 4
    552      True
    553      >>> rpneval(1, '0,atan2') == math.pi / 2
    554      True
    555      >>> rpneval(17.9, 'floor')
    556      17.0
    557      >>> rpneval(16.3, 'ceil')
    558      17.0
    559      >>> rpneval(15, 'deg2rad') == 15 * math.pi / 180
    560      True
    561      >>> rpneval(14, 'rad2deg') == 14 * 180 / math.pi
    562      True
    563      >>> rpneval(-13,'abs')
    564      13.0
    565      >>> rpneval(12, '5,7,3,sort,-,-')
    566      10.0
    567      >>> rpneval(11, '3,4,3,rev,-,+')
    568      -4.0
    569      >>> rpneval(10, '5,4,2,4,avg')
    570      5.25
    571      >>> rpneval(9, 'unkn')
    572      nan
    573      >>> rpneval(8, 'inf')
    574      inf
    575      >>> rpneval(7, 'neginf')
    576      -inf
    577      >>> rpneval(6, 'time') != 6
    578      True
    579      >>> rpneval(5, 'dup,-')
    580      0.0
    581      >>> rpneval(2, 'pop')
    582      -1.0
    583      >>> rpneval(4, '5,exc,-')
    584      1.0
    585      >>> rpneval(None, '2,*')
    586      None
    587      """
    588      if value is None: return value
    589      rpnOps = [op.strip().upper() for op in rpn.split(',')]
    590      stack = rpnStack(value)
    591      try:
    592          for op in rpnOps:
    593              stack.step(op)
    594          return stack.result()
    595      except IndexError:
    596          return -1.0
    597