1
2
3
4
5
6
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
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
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
61 """
62 @return the alias that this column uses (if any)
63 """
64
65
66
67 return getattr(self._columnHandler, 'aliasName', None)
68
69
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
81 """
82 A handler that return RRD data for the value given the row context.
83 """
84
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
101 summary = extra['summary']
102
103
104
105
106 aliasDatapointPairs = extra['aliasDatapointPairs']
107 if aliasDatapointPairs is None or len(aliasDatapointPairs) == 0:
108 return None
109
110 value = None
111
112 perfObject = component or device
113 deviceTemplates = perfObject.getRRDTemplates()
114 for alias, datapoint in aliasDatapointPairs:
115 template = datapoint.datasource().rrdTemplate()
116
117
118
119 if template in deviceTemplates:
120 value = _fetchValueWithAlias(
121 perfObject, datapoint, alias, summary)
122
123
124 if value is not None:
125 break
126
127 return value
128
129
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):
164
165
167 """
168 A base class for performance report plugins that use aliases to
169 choose datapoints
170 """
171
178
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
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
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
249
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
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
275 columns = self.getColumns()
276 aliasColumns = filter(
277 lambda x: getAliasName(x) is not None, columns
278 )
279
280
281 aliasColumnMap = \
282 dict(zip(map(getAliasName, aliasColumns), aliasColumns))
283
284 columnDatapointsMap = {}
285
286
287
288 for column in columns:
289 columnDatapointsMap[column] = []
290
291
292
293 aliasDatapointPairs = \
294 getDataPointsByAliases(dmd, aliasColumnMap.keys())
295 for alias, datapoint in aliasDatapointPairs:
296
297
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
313 summary = Utilization.getSummaryArgs(dmd, args)
314
315
316
317 columnDatapointsMap = self._mapColumnsToDatapoints(dmd)
318
319
320 if args.get('deviceClass', '/') == '/':
321 return []
322
323
324
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