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