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

# Source Code for Module Products.ZenReports.AliasPlugin

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