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

# Source Code for Module Products.ZenModel.GraphDefinition

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