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

# Source Code for Module Products.ZenRRD.utils

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