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