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

# Source Code for Module Products.ZenModel.GraphDefinition

      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__="""GraphDefinition
     15
     16  GraphDefinition defines the global options for a graph.
     17  """
     18
     19  import sys
     20  from sets import Set
     21  import string
     22
     23  from Products.ZenRelations.RelSchema import *
     24  from Products.ZenModel.ZenossSecurity import ZEN_MANAGE_DMD
     25  from Globals import InitializeClass
     26  from AccessControl import ClassSecurityInfo, Permissions
     27  from ZenModelRM import ZenModelRM
     28  from Products.ZenWidgets import messaging
     29  from ZenPackable import ZenPackable
     30  import logging
     31  log = logging.getLogger("zen.Device")
     32  from Acquisition import aq_base
     33
     34
     35 -def manage_addGraphDefinition(context, id, REQUEST = None):
     36      """
     37      This is here so that Zope will let us copy/paste/rename graph points.
     38      """
     39      if REQUEST:
     40          messaging.IMessageSender(self).sendToBrowser(
     41              'Unsupported',
     42              'That operation is not supported.',
     43              priority=messaging.WARNING
     44          )
     45          context.callZenScreen(REQUEST)
     46
     47 -class FakeContext:
     48      isFake = True
     49 -    def __init__(self, name):
     50          self.id = name
     51 -    def __getattr__(self, name):
     52          return FakeContext(name)
     53 -    def __call__(self, *unused, **ignored):
     54          return self
     55 -    def __getitem__(self, key):
     56          return FakeContext(key)
     57 -    def __str__(self):
     58          return self.id
     59 -    def __repr__(self):
     60          return self.id
     61 -    def device(self):
     62          return self
     63 -    def __nonzero__(self):
     64          return True
     65 -    def rrdPath(self):
     66          return 'rrdPath'
     67 -    def getRRDTemplates(self):
     68          return []
     69
     70 -class GraphDefinition(ZenModelRM, ZenPackable):
     71      '''
     72      '''
     73
     74      meta_type = 'GraphDefinition'
     75
     76      height = 100
     77      width = 500
     78      units = ""
     79      log = False
     80      base = False
     81      #summary = True
     82      miny = -1
     83      maxy = -1
     84      custom = ""
     85      hasSummary = True
     86      sequence = 0
     87
     88      _properties = (
     89          {'id':'height', 'type':'int', 'mode':'w'},
     90          {'id':'width', 'type':'int', 'mode':'w'},
     91          {'id':'units', 'type':'string', 'mode':'w'},
     92          {'id':'log', 'type':'boolean', 'mode':'w'},
     93          {'id':'base', 'type':'boolean', 'mode':'w'},
     94          #{'id':'summary', 'type':'boolean', 'mode':'w'},
     95          {'id':'miny', 'type':'int', 'mode':'w'},
     96          {'id':'maxy', 'type':'int', 'mode':'w'},
     97          {'id':'custom', 'type':'text', 'mode':'w'},
     98          {'id':'hasSummary', 'type':'boolean', 'mode':'w'},
     99          {'id':'sequence', 'type':'long', 'mode':'w'},
    100          )
    101
    102      _relations =  (
    103          ("rrdTemplate",
    104              ToOne(ToManyCont,"Products.ZenModel.RRDTemplate", "graphDefs")),
    105          ('report',
    106              ToOne(ToManyCont, 'Products.ZenModel.MultiGraphReport', 'graphDefs')),
    107          ('graphPoints',
    108              ToManyCont(ToOne, 'Products.ZenModel.GraphPoint', 'graphDef')),
    109          # Remove this relationship after version 2.1
    110          ('reportClass',
    111              ToOne(ToManyCont, 'Products.ZenModel.MultiGraphReportClass', 'graphDefs')),
    112          )
    113
    114
    115      # Screen action bindings (and tab definitions)
    116      factory_type_information = (
    117      {
    118          'immediate_view' : 'editGraphDefinition',
    119          'actions'        :
    120          (
    121              { 'id'            : 'edit'
    122              , 'name'          : 'Graph Definition'
    123              , 'action'        : 'editGraphDefinition'
    124              , 'permissions'   : ( Permissions.view, )
    125              },
    126              { 'id'            : 'editCustom'
    127              , 'name'          : 'Graph Custom Definition'
    128              , 'action'        : 'editCustGraphDefinition'
    129              , 'permissions'   : ( Permissions.view, )
    130              },
    131              { 'id'            : 'viewCommands'
    132              , 'name'          : 'Graph Commands'
    133              , 'action'        : 'viewGraphCommands'
    134              , 'permissions'   : ( Permissions.view, )
    135              },
    136          )
    137      },
    138      )
    139
    140      security = ClassSecurityInfo()
    141
    142      ## Basic stuff
    143
    144 -    def getGraphPoints(self, includeThresholds=True):
    145          ''' Return ordered list of graph points
    146          '''
    147          def cmpGraphPoints(a, b):
    148              try:
    149                  a = int(a.sequence)
    150              except ValueError:
    151                  a = sys.maxint
    152              try:
    153                  b = int(b.sequence)
    154              except ValueError:
    155                  b = sys.maxint
    156              return cmp(a, b)
    157          gps =  [gp for gp in self.graphPoints()
    158                  if includeThresholds or not gp.isThreshold]
    159          gps.sort(cmpGraphPoints)
    160          return gps
    161
    162
    163 -    def getThresholdGraphPoints(self):
    164          ''' Get ordered list of threshold graph points
    165          '''
    166          gps = [gp for gp in self.getGraphPoints() if gp.isThreshold]
    167          return gps
    168
    169
    170 -    def isThresholdGraphed(self, threshId):
    171          ''' Return true if there is a thresholdgraphpoint with threshId=threshid
    172          '''
    173          for gp in self.getThresholdGraphPoints():
    174              if gp.threshId == threshId:
    175                  return True
    176          return False
    177
    178
    179 -    def isDataPointGraphed(self, dpName):
    180          ''' Return true if there is at least one graphpoint with a dsName
    181          equal to dpName.
    182          '''
    183          from DataPointGraphPoint import DataPointGraphPoint
    184          for gp in self.getGraphPoints(includeThresholds=False):
    185              if isinstance(gp, DataPointGraphPoint):
    186                  if gp.dpName == dpName:
    187                      return True
    188          return False
    189
    190
    191      ## GUI Support
    192
    193
    194 -    def breadCrumbs(self, terminator='dmd'):
    195          """Return the breadcrumb links for this object add ActionRules list.
    196          [('url','id'), ...]
    197          """
    198          if self.rrdTemplate():
    199              from RRDTemplate import crumbspath
    200              crumbs = super(GraphDefinition, self).breadCrumbs(terminator)
    201              return crumbspath(self.rrdTemplate(), crumbs, -2)
    202          return ZenModelRM.breadCrumbs(self, terminator)
    203
    204
    205 -    def checkValidId(self, id, prep_id = False):
    206          """Checks a valid id
    207          """
    208          # RRD docs say that limit on vnames is 255 characters and that
    209          # A-Za-z0-9_ are the valid characters.  Zenoss reserves - for it's own
    210          # use.  Limiting to 200 instead just to leave room for whatever.
    211          # http://oss.oetiker.ch/rrdtool/doc/rrdgraph_data.en.html
    212          if len(id) > 200:
    213              return 'GraphPoint names can not be longer than 200 characters.'
    214          allowed = Set(list(string.ascii_letters)
    215                          + list(string.digits)
    216                          + ['_'])
    217          attempted = Set(list(id))
    218          if not attempted.issubset(allowed):
    219              return 'Only letters, digits and underscores are allowed' + \
    220                      ' in GraphPoint names.'
    221          return ZenModelRM.checkValidId(self, id, prep_id)
    222
    223
    224 -    def getGraphPointDescriptions(self):
    225          return [gi.getDescription() for gi in self.graphPoints()]
    226
    227
    228 -    def getGraphPointsNames(self):
    229          ''' Return list of graph point ids
    230          '''
    231          return [gp.id for gp in self.getGraphPoints()]
    232
    233
    234 -    def getGraphPointNamesString(self):
    235          """
    236          Return a string that lists the names of the graphpoints used in this
    237          graph definition.  If this graph definition has a perf template then
    238          note in the string which graphpoints are broken (in that they refer
    239          to nonexistent datapoints.)
    240          """
    241          names = []
    242          for gp in self.getGraphPoints():
    243              if hasattr(aq_base(gp), 'isBroken') and gp.isBroken():
    244                  names.append('%s(<span style="color: red">missing</span>)' %
    245                                                                      gp.id)
    246              else:
    247                  names.append(gp.id)
    248          return ', '.join(names)
    249
    250
    251 -    def getGraphPointOptions(self):
    252          ''' Used by dialog_addGraphPoint to construct the list of
    253          available graphpoint types.
    254          '''
    255          return (('DefGraphPoint', 'DEF'),
    256                  ('VdefGraphPoint', 'VDEF'),
    257                  ('CdefGraphPoint', 'CDEF'),
    258                  ('PrintGraphPoint', 'PRINT'),
    259                  ('GprintGraphPoint', 'GPRINT'),
    260                  ('CommentGraphPoint', 'COMMENT'),
    261                  ('VruleGraphPoint', 'VRULE'),
    262                  ('HruleGraphPoint', 'HRULE'),
    263                  ('LineGraphPoint', 'LINE'),
    264                  ('AreaGraphPoint', 'AREA'),
    265                  ('TickGraphPoint', 'TICK'),
    266                  ('ShiftGraphPoint', 'SHIFT'))
    267
    268
    269 -    def createGraphPoint(self, cls, newId):
    270          ''' Create the graphpoint with the given id or something similar
    271          and add to self.graphPoints
    272          '''
    273          def getUniqueId(container, base):
    274                  ids = container.objectIds()
    275                  new = base
    276                  i = 2
    277                  while new in ids:
    278                      new = '%s%s' % (base, i)
    279                      i += 1
    280                  return new
    281          newId = getUniqueId(self.graphPoints, newId)
    282          gp = cls(newId)
    283          # Set sequence
    284          if gp.isThreshold:
    285              gp.sequence = -1
    286          else:
    287              gp.sequence = len(self.graphPoints())
    288          # Set legend for graph points on multigraph reports
    289          if self.report() and hasattr(gp, 'legend'):
    290              # For MultiGraphReports we use a fancier legend
    291              # to differentiate when you have multiple devices/graphpoints
    292              # on a single graph
    293              gp.legend = gp.DEFAULT_MULTIGRAPH_LEGEND
    294          self.graphPoints._setObject(gp.id, gp)
    295          gp = self.graphPoints._getOb(gp.id)
    296          if gp.sequence == -1:
    297              self.manage_resequenceGraphPoints()
    298          return gp
    299
    300
    301      security.declareProtected(ZEN_MANAGE_DMD, 'manage_addCustomGraphPoint')
    302 -    def manage_addCustomGraphPoint(self, new_id, flavor, REQUEST=None):
    303          ''' Create a new graphpoint of the given class and id
    304          '''
    305          exec 'import %s' % flavor
    306          cls = eval('%s.%s' % (flavor, flavor))
    307          gp = self.createGraphPoint(cls, new_id)
    308          if REQUEST:
    309              url = '%s/graphPoints/%s' % (self.getPrimaryUrlPath(), gp.id)
    310              REQUEST['RESPONSE'].redirect(url)
    311              return self.callZenScreen(REQUEST)
    312          return gp
    313
    314
    315      security.declareProtected(ZEN_MANAGE_DMD, 'manage_addDataPointGraphPoints')
    316 -    def manage_addDataPointGraphPoints(self, dpNames=None,
    317                                         includeThresholds=False,
    318                                         REQUEST=None):
    319          ''' Create new graph points
    320          The migrate script graphDefinitions and friends depends on the first
    321          element in newGps being the DataPointGraphPoint when only one
    322          name is passed in dpNames.
    323          '''
    324          if not dpNames:
    325              if REQUEST:
    326                  messaging.IMessageSender(self).sendToBrowser(
    327                      'Error',
    328                      'No graph points were selected.',
    329                      priority=messaging.WARNING
    330                  )
    331                  return self.callZenScreen(REQUEST)
    332          else:
    333              from DataPointGraphPoint import DataPointGraphPoint
    334              newGps = []
    335              for dpName in dpNames:
    336                  dpId = dpName.split('_', 1)[-1]
    337                  gp = self.createGraphPoint(DataPointGraphPoint, dpId)
    338                  gp.dpName = dpName
    339                  newGps.append(gp)
    340              if includeThresholds:
    341                  for dpName in dpNames:
    342                      newGps += self.addThresholdsForDataPoint(dpName)
    343              if REQUEST:
    344                  messaging.IMessageSender(self).sendToBrowser(
    345                      'Graph Points Added',
    346                      '%s graph point%s were added.' % (len(newGps),
    347                          len(newGps) > 1 and 's' or '')
    348                  )
    349                  return self.callZenScreen(REQUEST)
    350              return newGps
    351
    352
    353 -    def addThresholdsForDataPoint(self, dpName):
    354          ''' Make sure that Threshold graph points exist for all thresholds
    355          that use the given dpName.
    356          Return a list of all graphpoints created by this call.
    357          '''
    358          from ThresholdGraphPoint import ThresholdGraphPoint
    359          newGps = []
    360          for thresh in self.rrdTemplate().thresholds():
    361              if thresh.canGraph(self) \
    362                      and dpName in thresh.dsnames \
    363                      and not self.isThresholdGraphed(thresh.id):
    364                  gp = self.createGraphPoint(ThresholdGraphPoint, thresh.id)
    365                  gp.threshId = thresh.id
    366                  newGps.append(gp)
    367          return newGps
    368
    369
    370      security.declareProtected(ZEN_MANAGE_DMD, 'manage_addThresholdGraphPoints')
    371 -    def manage_addThresholdGraphPoints(self, threshNames, REQUEST=None):
    372          ''' Create new graph points
    373          '''
    374          from ThresholdGraphPoint import ThresholdGraphPoint
    375          newGps = []
    376          for threshName in threshNames:
    377              #thresh = getattr(self.rrdTemplate.thresholds, threshName)
    378              gp = self.createGraphPoint(ThresholdGraphPoint, threshName)
    379              gp.threshId = threshName
    380              newGps.append(gp)
    381          if REQUEST:
    382              messaging.IMessageSender(self).sendToBrowser(
    383                  'Graph Points Added',
    384                  '%s graph point%s were added.' % (len(newGps),
    385                      len(newGps) > 1 and 's' or '')
    386              )
    387              return self.callZenScreen(REQUEST)
    388          return newGps
    389
    390
    391      security.declareProtected(ZEN_MANAGE_DMD, 'manage_deleteGraphPoints')
    392 -    def manage_deleteGraphPoints(self, ids=(), REQUEST=None):
    393          ''' Deleted given graphpoints
    394          '''
    395          num = 0
    396          for id in ids:
    397              if getattr(self.graphPoints, id, False):
    398                  num += 1
    399                  self.graphPoints._delObject(id)
    400              self.manage_resequenceGraphPoints()
    401          if REQUEST:
    402              messaging.IMessageSender(self).sendToBrowser(
    403                  'Graph Points Deleted',
    404                  '%s graph point%s were deleted.' % (num,
    405                      num > 1 and 's' or '')
    406              )
    407              return self.callZenScreen(REQUEST)
    408
    409
    410      security.declareProtected(ZEN_MANAGE_DMD, 'manage_resequenceGraphPoints')
    411 -    def manage_resequenceGraphPoints(self, seqmap=(), origseq=(), REQUEST=None):
    412          """Reorder the sequence of the GraphPoints.
    413          """
    414          from Products.ZenUtils.Utils import resequence
    415          return resequence(self, self.graphPoints(),
    416                              seqmap, origseq, REQUEST)
    417
    418
    419 -    def getDataPointOptions(self):
    420          ''' Return a list of (value, name) tuples for the list of datapoints
    421          which the user selects from to create new graphpoints.
    422          '''
    423          return [(dp.name(), dp.name())
    424                      for dp in self.rrdTemplate.getRRDDataPoints()]
    425
    426
    427 -    def getThresholdOptions(self):
    428          ''' Return a list of (value, name) tuples for the list of thresholds
    429          which the user selects from to create new graphpoints.
    430          '''
    431          return [(t.id, t.id) for t in self.rrdTemplate.thresholds()]
    432
    433
    434      ## Graphing Support
    435
    436
    437 -    def getGraphCmds(self, context, rrdDir, multiid=-1, upToPoint=None,
    438              includeSetup=True, includeThresholds=True,
    439              prefix='', cmds=None, idxOffset=0):
    440          """build the graph opts for a single rrdfile"""
    441          from Products.ZenUtils.ZenTales import talesEval
    442          if not cmds:
    443              cmds = []
    444          if includeSetup:
    445              cmds += self.graphsetup()
    446
    447          # Have to draw thresholds before data so that thresholds won't
    448          # obscure data (especially if threshold uses TICK)
    449          if includeThresholds:
    450              threshGps = [gp for gp in self.getThresholdGraphPoints()
    451                          if upToPoint is None or gp.sequence < upToPoint]
    452              if threshGps:
    453                  for index, gp in enumerate(threshGps):
    454                      try:
    455                          cmds = gp.getGraphCmds(cmds, context, rrdDir,
    456                                          self.hasSummary, index+idxOffset,
    457                                          multiid, prefix)
    458                      except (KeyError, NameError), e:
    459                          cmds.append('COMMENT: UNKNOWN VALUE IN '
    460                              'GRAPHPOINT %s\: %s' % (gp.id, str(e)))
    461          gpList = [gp for gp in self.getGraphPoints(includeThresholds=False)
    462                      if upToPoint is None or gp.sequence < upToPoint]
    463          for index, gp in enumerate(gpList):
    464              try:
    465                  cmds = gp.getGraphCmds(cmds, context, rrdDir,
    466                                          self.hasSummary, index+idxOffset,
    467                                          multiid, prefix)
    468              except (KeyError, NameError), e:
    469                  cmds.append('COMMENT: UNKNOWN VALUE IN GRAPHPOINT '
    470                          '%s\: %s' % (gp.id, str(e)))
    471          if self.custom and includeSetup \
    472              and not upToPoint:
    473              try:
    474                  res = talesEval("string:"+str(self.custom), context)
    475              except (KeyError, NameError), e:
    476                  res = 'COMMENT:UNKNOWN VALUE IN CUSTOM COMMANDS\: %s' % str(e)
    477              res = [l for l in res.split('\n') if l.strip()]
    478              cmds.extend(res)
    479              #if self.hasSummary:
    480              #    cmds = self.addSummary(cmds)
    481
    482          return cmds
    483
    484
    485 -    def getRRDVariables(self, upToPoint=None):
    486          ''' Return list of rrd variable names that are defined by DEF, CDEF
    487          or VDEF statements in the rrd commands.  If upToPoint is not None then
    488          only consider statements generated by graphoints where
    489          sequence < upToPoint
    490          '''
    491          cmds = self.getFakeGraphCmds(upToPoint=upToPoint)
    492          names = [line[line.find(':')+1:line.find('=')]
    493                      for line in cmds.split('\n')
    494                      if line[:line.find(':')] in ('DEF', 'CDEF', 'VDEF')]
    495          return names
    496
    497
    498 -    def getFakeGraphCmds(self, upToPoint=None):
    499          ''' Used to display the graph commands (or a reasonable likeness)
    500          to the user
    501          '''
    502          context = FakeContext('Context')
    503          cmds = self.getGraphCmds(context, context.rrdPath(), upToPoint=upToPoint)
    504          cmds = '\n'.join(cmds)
    505          return cmds
    506
    507
    508 -    def graphsetup(self):
    509          """Setup global graph parameters.
    510          """
    511          gopts = ['-F', '-E']
    512          if self.height:
    513              gopts.append('--height=%d' % self.height)
    514          if self.width:
    515              gopts.append('--width=%d' % self.width)
    516          if self.log:
    517              gopts.append('--logarithmic')
    518          if self.maxy > -1:
    519              gopts.append('--upper-limit=%d' % self.maxy)
    520              gopts.append('--rigid')
    521          if self.miny > -1:
    522              gopts.append('--lower-limit=%d' % self.miny)
    523              gopts.append('--rigid')
    524          # Always include a vertical label so that multiple graphs on page
    525          # align correctly.
    526          gopts.append('--vertical-label=%s' % (self.units or ' '))
    527          if self.units == 'percentage':
    528              if not self.maxy > -1:
    529                  gopts.append('--upper-limit=100')
    530              if not self.miny > -1:
    531                  gopts.append('--lower-limit=0')
    532          if self.base:
    533              gopts.append('--base=1024')
    534          gopts = [str(o) for o in gopts]
    535          return gopts
    536
    537
    538 -    def getDataPointGraphPoints(self, dpName):
    539          ''' Return a list of DataPointGraphPoints that use the given dpName
    540          '''
    541          from DataPointGraphPoint import DataPointGraphPoint
    542          return [gp for gp in self.graphPoints()
    543                  if isinstance(gp, DataPointGraphPoint)
    544                  and gp.dpName == dpName]
    545
    546
    547 -    def getUniqueDpNames(self, limit=None):
    548          '''
    549          Get a list of all unique datapoint names
    550          '''
    551          from sets import Set
    552          dpNames = Set()
    553          for t in self.dmd.Devices.getAllRRDTemplates():
    554              for ds in t.datasources():
    555                  # If we have a broken datasource (likely from a missing zenpack)
    556                  # then don't try to parse datapoints, you can't.
    557                  if hasattr(ds, 'datapoints'):
    558                      for dp in ds.datapoints():
    559                          dpNames.add(dp.name())
    560              if limit and len(dpNames) >= limit:
    561                  break
    562          dpNames = list(dpNames)
    563          dpNames.sort()
    564          return dpNames
    565
    566
    567 -    def getUniqueThresholdNames(self, limit=100):
    568          '''
    569          Get a list of all unique threshold names
    570          '''
    571          from sets import Set
    572          names = Set()
    573          for t in self.dmd.Devices.getAllRRDTemplates():
    574              for thresh in t.thresholds():
    575                  names.add(thresh.id)
    576                  if len(names) >= limit:
    577                      break
    578              if len(names) >= limit:
    579                  break
    580          names = list(names)
    581          names.sort()
    582          return names
    583
    584
    585  InitializeClass(GraphDefinition)
    586