Package Products :: Package ZenReports :: Module AliasPlugin |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenReports.AliasPlugin

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, 2009, 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  __doc__="""AliasPlugin
     14
     15  In order to more easily create reports, there is now a base class (AliasPlugin)
     16  that plugins can subclass and provide minimal information.  The plugin is
     17  meant to be run from an rpt file.
     18  """
     19  import Globals
     20
     21  from Products.ZenUtils.ZenTales import talesEval, talesEvalStr
     22  from Products.ZenModel.RRDDataPoint import getDataPointsByAliases
     23  from Products.ZenModel.RRDDataPointAlias import EVAL_KEY
     24  from Products.ZenReports import Utils, Utilization
     25
     26 -class Column( object ):
     27      """
     28      Represents a column in a report row.  Returns a value when given the
     29      context represented by the row.  For example, a brain-dead report
     30      might list the paths of all the devices in the system.  A Column object
     31      that represents the path column would know how to return the path given
     32      the device.
     33      """
     34 -    def __init__(self, columnName, columnHandler=None):
     35          """
     36          @param columnName: the name of the column
     37          @param columnHandler: optional object or method that knows
     38                                how to take the row context and return
     39                                the column value
     40          """
     41          self._columnName = columnName
     42          self._columnHandler = columnHandler
     43
     44 -    def getColumnName(self):
     45          return self._columnName
     46
     47 -    def getValue(self, device, component=None, extra=None ):
     48          """
     49          @param device: the device represented by this row
     50          @param component: the component represented by this row (if present)
     51          @param extra: extra context passed to the columnHandler that will
     52                        generate the column value
     53          """
     54          value = None
     55          if self._columnHandler is not None:
     56              value = self._columnHandler( device, component, extra )
     57          return value
     58
     59 -    def getAliasName(self):
     60          """
     61          @return the alias that this column uses (if any)
     62          """
     63          # This ugly type-check is needed for performance.  We
     64          # gather up aliases so we can get all the necessary datapoints
     65          # at one time.
     66          return getattr( self._columnHandler, 'aliasName', None )
     67
     68 -def _fetchValueWithAlias( entity, datapoint, alias, summary  ):
     69      """
     70      Generates the alias RPN formula with the entity as the context, then
     71      retrieves the RRD value of the datapoint with the RPN formula
     72      """
     73      if alias:
     74          summary['extraRpn'] = alias.evaluate( entity )
     75      return entity.getRRDValue( datapoint.id, **summary )
     76
     77
     78 -class RRDColumnHandler( object ):
     79      """
     80      A handler that return RRD data for the value given the row context
     81      """
     82 -    def __init__(self, aliasName ):
     83          """
     84          @param aliasName: the alias or datapoint name to fetch
     85          @param columnHandler: optional columnHandler method or object
     86                         for post-processing of the RRD data
     87          """
     88          self.aliasName = aliasName
     89
     90 -    def __call__( self, device, component=None, extra=None ):
     91          """
     92          @param device: the device represented by this row
     93          @param component: the component represented by this row (if present)
     94          @param extra: extra context passed to the columnHandler that will
     95                        generate the column value
     96          """
     97          # The summary dict has the request key/values
     98          summary=extra['summary']
     99
    100          # The aliasDatapointPairs are the datapoints that
    101          # have the desired values along with the aliases
    102          # that may have formulas for transforming them
    103          aliasDatapointPairs=extra['aliasDatapointPairs']
    104          if aliasDatapointPairs is None or len( aliasDatapointPairs ) == 0:
    105              return None
    106
    107          value = None
    108          # determine the row context-- device or device and component
    109          perfObject = component or device
    110          deviceTemplates = perfObject.getRRDTemplates()
    111          for alias, datapoint in aliasDatapointPairs:
    112              template = datapoint.datasource().rrdTemplate()
    113
    114              # Only fetch the value if the template is bound
    115              # to this device or component
    116              if template in deviceTemplates:
    117                  value = _fetchValueWithAlias( perfObject, datapoint,
    118                                                alias, summary )
    119              # Return the first value we find
    120              if value is not None:
    121                  break
    122
    123          return value
    124
    125
    126 -class PythonColumnHandler( object ):
    127      """
    128      A column handler accepts row context (like a device, component, or extra
    129      information) and returns the column value.  This class specifically
    130      executes a python expression that can use the row context.
    131
    132      The row context includes the device object ('device') and, if available,
    133      the component object ('component').  It also includes report information
    134      such as the start date ('start') and end date ('end') of the report.
    135      """
    136 -    def __init__(self, talesExpression, extraContext={}):
    137          """
    138          @param talesExpression: A python expression that can use the context
    139          """
    140          self._talesExpression = 'python:%s' % talesExpression
    141          self._extraContext = extraContext
    142
    143 -    def __call__(self, device, component=None, extra=None, value=None ):
    144          kw=dict( device=device, component=component, value=value )
    145          kw.update( self._extraContext )
    146          if extra is not None:
    147              kw.update(extra)
    148          return talesEval( self._talesExpression, device, kw )
    149
    150
    151 -class AliasPlugin( object ):
    152      """
    153      A base class for performance report plugins that use aliases to
    154      choose datapoints
    155      """
    156 -    def _getComponents(self, device, componentPath):
    157          componentPath='here/%s' % componentPath
    158          try:
    159              return talesEval( componentPath, device )
    160          except AttributeError:
    161              return []
    162
    163 -    def getColumns(self):
    164          """
    165          Return the mapping of aliases to column names.  This should be one
    166          to one.  This is unimplemented in the base class.
    167
    168          This is meant to be overridden.
    169          """
    170          raise Exception( 'Unimplemented: Only subclasses of AliasPlugin' +
    171                           ' should be instantiated directly' )
    172
    173 -    def getCompositeColumns(self):
    174          """
    175          Return columns that will be evaluated in order and have access to
    176          the previous column values.  TalesColumnHandlers will have the previous
    177          column values for this row in their context.
    178
    179          For example, if one column named 'cpuIdle' evaluates to .36,
    180          a composite column named 'cpuUtilized' can be created with
    181          a TalesColumnHandler with the expression '1 - cpuIdle'.
    182
    183          NOTE: These cannot be RRD columns (or rather, RRD columns will fail
    184                because no datapoints will be passed to them)
    185
    186          This is meant to be overridden (if needed).
    187          """
    188          return []
    189
    190 -    def getComponentPath(self):
    191          """
    192          If the rows in the report represent components, this is how to
    193          get from a device to the appropriate components.
    194
    195          This is meant to be overridden for component reports.
    196          """
    197          return None
    198
    199
    200 -    def _createRecord(self, device, component=None,
    201                        columnDatapointsMap={}, summary={} ):
    202          """
    203          Creates a record for the given row context
    204          (that is, the device and/or component)
    205
    206          @param device: the device for this row
    207          @param component: the (optional) component for this row
    208          @param columnDatapointsMap: a dict of Column objects to
    209                                      alias-datapoint pairs.  The first
    210                                      datapoint that gives a value for
    211                                      the context will be used.
    212          @param summary: a dict of report parameters like start date,
    213                          end date, and rrd summary function
    214          @rtype L{Utils.Record}
    215          """
    216          def localGetValue( device, component, extra ):
    217              try:
    218                  val=column.getValue( device, component,
    219                                       extra=extra )
    220              except TypeError:
    221                  val=None
    222              except NameError:
    223                  val=None
    224              return val
    225          columnValueMap = {}
    226          for column, aliasDatapointPairs in columnDatapointsMap.iteritems():
    227              columnName = column.getColumnName()
    228              extra=dict( aliasDatapointPairs=aliasDatapointPairs,
    229                          summary=summary )
    230              value = localGetValue( device, component, extra )
    231              columnValueMap[columnName] = value
    232
    233          # The composite columns cannot be RRD columns, because we do
    234          # not pass datapoints.
    235          extra=dict( summary=summary )
    236          extra.update( columnValueMap )
    237          for column in self.getCompositeColumns():
    238              columnName = column.getColumnName()
    239              value = localGetValue( device, component, extra )
    240              columnValueMap[columnName]=value
    241              extra.update( { columnName : value } )
    242
    243          return Utils.Record(device=device, component=component,
    244                              **columnValueMap)
    245
    246 -    def _mapColumnsToDatapoints( self, dmd ):
    247          """
    248          Create a map of columns->alias/datapoint pairs.  For each
    249          column we need both the datapoint/alias-- the datapoint to
    250          retrieve the rrd data and the alias to execute the alias
    251          formula to transform that data (to get the correct units).
    252
    253          Non-perf columns will be mapped to None
    254          """
    255          def getAliasName( column ):
    256              return column.getAliasName()
    257
    258          # First, split the columns into perf and non-perf columns
    259          columns = self.getColumns()
    260          nonAliasColumns = filter( lambda x: getAliasName( x ) is None,
    261                                    columns )
    262          aliasColumns = filter( lambda x: getAliasName( x ) is not None,
    263                                 columns )
    264
    265          # Second, map the alias names to their columns
    266          aliasColumnMap = dict( zip( map( getAliasName, aliasColumns ),
    267                                      aliasColumns ) )
    268
    269          columnDatapointsMap = {}
    270
    271          # Map the columns to empty list to ensure that
    272          # there will be placeholders for all columns
    273          # even if there are not aliased datapoints
    274          for column in columns:
    275              columnDatapointsMap[column] = []
    276
    277          # Fourth, match up the columns with the corresponding alias/datapoint
    278          # pairs
    279          aliasDatapointPairs = getDataPointsByAliases( dmd,
    280                                                        aliasColumnMap.keys() )
    281          for alias, datapoint in aliasDatapointPairs:
    282              if alias:
    283                  column = aliasColumnMap[ alias.id ]
    284              else:
    285                  # If the alias-datapoint pair is missing
    286                  # the alias, then the column's aliasName
    287                  # was really the datapoint name
    288                  column = aliasColumnMap[ datapoint.id ]
    289
    290              columnDatapointsMap[column].append( (alias,datapoint) )
    291
    292          return columnDatapointsMap
    293
    294 -    def run(self, dmd, args):
    295          """
    296          Generate the report using the columns and aliases
    297
    298          @param dmd the dmd context to access the context objects
    299          @param args the report args from the ui
    300
    301          @rtype a list of L{Utils.Record}s
    302          """
    303          # Get the summary arguments from the request args
    304          summary = Utilization.getSummaryArgs(dmd, args)
    305
    306          # Create a dict of column to the datapoint/alias pairs
    307          # that return a value for the column
    308          columnDatapointsMap = self._mapColumnsToDatapoints( dmd )
    309
    310          # Don't run against all devices, which kills large systems
    311          if args.get('deviceClass', '/') == '/':
    312              return []
    313
    314          componentPath = self.getComponentPath()
    315
    316          # Filter the device list down according to the
    317          # values from the filter widget
    318          report = []
    319          for device in Utilization.filteredDevices(dmd, args):
    320              if componentPath is None:
    321
    322                  record = self._createRecord( device, None,
    323                                               columnDatapointsMap, summary )
    324                  report.append(record)
    325
    326              else:
    327                  components = self._getComponents( device,
    328                                                    componentPath )
    329                  for component in components:
    330                      record = self._createRecord(device, component,
    331                                                  columnDatapointsMap, summary)
    332                      report.append(record)
    333
    334          return report
    335