VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuigraphwiz.py

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuigraphwiz.py 106061 2024-09-16 14:03:52Z vboxsync $
3
4"""
5Test Manager WUI - Graph Wizard
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2024 Oracle and/or its affiliates.
11
12This file is part of VirtualBox base platform packages, as
13available from https://www.virtualbox.org.
14
15This program is free software; you can redistribute it and/or
16modify it under the terms of the GNU General Public License
17as published by the Free Software Foundation, in version 3 of the
18License.
19
20This program is distributed in the hope that it will be useful, but
21WITHOUT ANY WARRANTY; without even the implied warranty of
22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23General Public License for more details.
24
25You should have received a copy of the GNU General Public License
26along with this program; if not, see <https://www.gnu.org/licenses>.
27
28The contents of this file may alternatively be used under the terms
29of the Common Development and Distribution License Version 1.0
30(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
31in the VirtualBox distribution, in which case the provisions of the
32CDDL are applicable instead of those of the GPL.
33
34You may elect to license modified versions of this file under the
35terms and conditions of either the GPL or the CDDL or both.
36
37SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
38"""
39__version__ = "$Revision: 106061 $"
40
41# Python imports.
42import functools;
43
44# Validation Kit imports.
45from testmanager.webui.wuimain import WuiMain;
46from testmanager.webui.wuihlpgraph import WuiHlpLineGraphErrorbarY, WuiHlpGraphDataTableEx;
47from testmanager.webui.wuireport import WuiReportBase;
48
49from common import utils, webutils;
50from common import constants;
51
52
53class WuiGraphWiz(WuiReportBase):
54 """Construct a graph for analyzing test results (values) across builds and testboxes."""
55
56 ## @name Series name parts.
57 ## @{
58 kfSeriesName_TestBox = 1;
59 kfSeriesName_Product = 2;
60 kfSeriesName_Branch = 4;
61 kfSeriesName_BuildType = 8;
62 kfSeriesName_OsArchs = 16;
63 kfSeriesName_TestCase = 32;
64 kfSeriesName_TestCaseArgs = 64;
65 kfSeriesName_All = 127;
66 ## @}
67
68
69 def __init__(self, oModel, dParams, fSubReport = False, fnDPrint = None, oDisp = None):
70 WuiReportBase.__init__(self, oModel, dParams, fSubReport = fSubReport, fnDPrint = fnDPrint, oDisp = oDisp);
71
72 # Select graph implementation.
73 if dParams[WuiMain.ksParamGraphWizImpl] == 'charts':
74 from testmanager.webui.wuihlpgraphgooglechart import WuiHlpLineGraphErrorbarY as MyGraph;
75 self.oGraphClass = MyGraph;
76 elif dParams[WuiMain.ksParamGraphWizImpl] == 'matplotlib':
77 from testmanager.webui.wuihlpgraphmatplotlib import WuiHlpLineGraphErrorbarY as MyGraph;
78 self.oGraphClass = MyGraph;
79 else:
80 self.oGraphClass = WuiHlpLineGraphErrorbarY;
81
82
83 #
84 def _figureSeriesNameBits(self, aoSeries):
85 """ Figures out the method (bitmask) to use when naming series. """
86 if len(aoSeries) <= 1:
87 return WuiGraphWiz.kfSeriesName_TestBox;
88
89 # Start with all and drop unnecessary specs one-by-one.
90 fRet = WuiGraphWiz.kfSeriesName_All;
91
92 if [oSrs.idTestBox for oSrs in aoSeries].count(aoSeries[0].idTestBox) == len(aoSeries):
93 fRet &= ~WuiGraphWiz.kfSeriesName_TestBox;
94
95 if [oSrs.idBuildCategory for oSrs in aoSeries].count(aoSeries[0].idBuildCategory) == len(aoSeries):
96 fRet &= ~WuiGraphWiz.kfSeriesName_Product;
97 fRet &= ~WuiGraphWiz.kfSeriesName_Branch;
98 fRet &= ~WuiGraphWiz.kfSeriesName_BuildType;
99 fRet &= ~WuiGraphWiz.kfSeriesName_OsArchs;
100 else:
101 if [oSrs.oBuildCategory.sProduct for oSrs in aoSeries].count(aoSeries[0].oBuildCategory.sProduct) == len(aoSeries):
102 fRet &= ~WuiGraphWiz.kfSeriesName_Product;
103 if [oSrs.oBuildCategory.sBranch for oSrs in aoSeries].count(aoSeries[0].oBuildCategory.sBranch) == len(aoSeries):
104 fRet &= ~WuiGraphWiz.kfSeriesName_Branch;
105 if [oSrs.oBuildCategory.sType for oSrs in aoSeries].count(aoSeries[0].oBuildCategory.sType) == len(aoSeries):
106 fRet &= ~WuiGraphWiz.kfSeriesName_BuildType;
107
108 # Complicated.
109 fRet &= ~WuiGraphWiz.kfSeriesName_OsArchs;
110 daTestBoxes = {};
111 for oSeries in aoSeries:
112 if oSeries.idTestBox in daTestBoxes:
113 daTestBoxes[oSeries.idTestBox].append(oSeries);
114 else:
115 daTestBoxes[oSeries.idTestBox] = [oSeries,];
116 for aoSeriesPerTestBox in daTestBoxes.values():
117 if len(aoSeriesPerTestBox) >= 0:
118 asOsArches = aoSeriesPerTestBox[0].oBuildCategory.asOsArches;
119 for i in range(1, len(aoSeriesPerTestBox)):
120 if aoSeriesPerTestBox[i].oBuildCategory.asOsArches != asOsArches:
121 fRet |= WuiGraphWiz.kfSeriesName_OsArchs;
122 break;
123
124 if aoSeries[0].oTestCaseArgs is None:
125 fRet &= ~WuiGraphWiz.kfSeriesName_TestCaseArgs;
126 if [oSrs.idTestCase for oSrs in aoSeries].count(aoSeries[0].idTestCase) == len(aoSeries):
127 fRet &= ~WuiGraphWiz.kfSeriesName_TestCase;
128 else:
129 fRet &= ~WuiGraphWiz.kfSeriesName_TestCase;
130 if [oSrs.idTestCaseArgs for oSrs in aoSeries].count(aoSeries[0].idTestCaseArgs) == len(aoSeries):
131 fRet &= ~WuiGraphWiz.kfSeriesName_TestCaseArgs;
132
133 return fRet;
134
135 def _getSeriesNameFromBits(self, oSeries, fBits):
136 """ Creates a series name from bits (kfSeriesName_xxx). """
137 assert fBits != 0;
138 sName = '';
139
140 if fBits & WuiGraphWiz.kfSeriesName_Product:
141 if sName: sName += ' / ';
142 sName += oSeries.oBuildCategory.sProduct;
143
144 if fBits & WuiGraphWiz.kfSeriesName_Branch:
145 if sName: sName += ' / ';
146 sName += oSeries.oBuildCategory.sBranch;
147
148 if fBits & WuiGraphWiz.kfSeriesName_BuildType:
149 if sName: sName += ' / ';
150 sName += oSeries.oBuildCategory.sType;
151
152 if fBits & WuiGraphWiz.kfSeriesName_OsArchs:
153 if sName: sName += ' / ';
154 sName += ' & '.join(oSeries.oBuildCategory.asOsArches);
155
156 if fBits & WuiGraphWiz.kfSeriesName_TestCaseArgs:
157 if sName: sName += ' / ';
158 if oSeries.idTestCaseArgs is not None:
159 sName += oSeries.oTestCase.sName + ':#' + str(oSeries.idTestCaseArgs);
160 else:
161 sName += oSeries.oTestCase.sName;
162 elif fBits & WuiGraphWiz.kfSeriesName_TestCase:
163 if sName: sName += ' / ';
164 sName += oSeries.oTestCase.sName;
165
166 if fBits & WuiGraphWiz.kfSeriesName_TestBox:
167 if sName: sName += ' / ';
168 sName += oSeries.oTestBox.sName;
169
170 return sName;
171
172 def _calcGraphName(self, oSeries, fSeriesName, sSampleName):
173 """ Constructs a name for the graph. """
174 fGraphName = ~fSeriesName & ( WuiGraphWiz.kfSeriesName_TestBox
175 | WuiGraphWiz.kfSeriesName_Product
176 | WuiGraphWiz.kfSeriesName_Branch
177 | WuiGraphWiz.kfSeriesName_BuildType
178 );
179 sName = self._getSeriesNameFromBits(oSeries, fGraphName);
180 if sName: sName += ' - ';
181 sName += sSampleName;
182 return sName;
183
184 def _calcSampleName(self, oCollection):
185 """ Constructs a name for a sample source (collection). """
186 if oCollection.sValue is not None:
187 asSampleName = [oCollection.sValue, 'in',];
188 elif oCollection.sType == self._oModel.ksTypeElapsed:
189 asSampleName = ['Elapsed time', 'for', ];
190 elif oCollection.sType == self._oModel.ksTypeResult:
191 asSampleName = ['Error count', 'for',];
192 else:
193 return 'Invalid collection type: "%s"' % (oCollection.sType,);
194
195 sTestName = ', '.join(oCollection.asTests if oCollection.asTests[0] else oCollection.asTests[1:]);
196 if sTestName == '':
197 # Use the testcase name if there is only one for all series.
198 if not oCollection.aoSeries:
199 return asSampleName[0];
200 if len(oCollection.aoSeries) > 1:
201 idTestCase = oCollection.aoSeries[0].idTestCase;
202 for oSeries in oCollection.aoSeries:
203 if oSeries.idTestCase != idTestCase:
204 return asSampleName[0];
205 sTestName = oCollection.aoSeries[0].oTestCase.sName;
206 return ' '.join(asSampleName) + ' ' + sTestName;
207
208
209 def _splitSeries(self, aoSeries):
210 """
211 Splits the data series (ReportGraphModel.DataSeries) into one or more graphs.
212
213 Returns an array of data series arrays.
214 """
215 # Must be at least two series for something to be splittable.
216 if len(aoSeries) <= 1:
217 if not aoSeries:
218 return [];
219 return [aoSeries,];
220
221 # Split on unit.
222 dUnitSeries = {};
223 for oSeries in aoSeries:
224 if oSeries.iUnit not in dUnitSeries:
225 dUnitSeries[oSeries.iUnit] = [];
226 dUnitSeries[oSeries.iUnit].append(oSeries);
227
228 # Sort the per-unit series since the build category was only sorted by ID.
229 for iUnit in dUnitSeries:
230 def mycmp(oSelf, oOther):
231 """ __cmp__ like function. """
232 iCmp = utils.stricmp(oSelf.oBuildCategory.sProduct, oOther.oBuildCategory.sProduct);
233 if iCmp != 0:
234 return iCmp;
235 iCmp = utils.stricmp(oSelf.oBuildCategory.sBranch, oOther.oBuildCategory.sBranch);
236 if iCmp != 0:
237 return iCmp;
238 iCmp = utils.stricmp(oSelf.oBuildCategory.sType, oOther.oBuildCategory.sType);
239 if iCmp != 0:
240 return iCmp;
241 iCmp = utils.stricmp(oSelf.oTestBox.sName, oOther.oTestBox.sName);
242 if iCmp != 0:
243 return iCmp;
244 return 0;
245 dUnitSeries[iUnit] = sorted(dUnitSeries[iUnit], key = functools.cmp_to_key(mycmp));
246
247 # Split the per-unit series up if necessary.
248 cMaxPerGraph = self._dParams[WuiMain.ksParamGraphWizMaxPerGraph];
249 aaoRet = [];
250 for aoUnitSeries in dUnitSeries.values():
251 while len(aoUnitSeries) > cMaxPerGraph:
252 aaoRet.append(aoUnitSeries[:cMaxPerGraph]);
253 aoUnitSeries = aoUnitSeries[cMaxPerGraph:];
254 if aoUnitSeries:
255 aaoRet.append(aoUnitSeries);
256
257 return aaoRet;
258
259 def _configureGraph(self, oGraph):
260 """
261 Configures oGraph according to user parameters and other config settings.
262
263 Returns oGraph.
264 """
265 oGraph.setWidth(self._dParams[WuiMain.ksParamGraphWizWidth])
266 oGraph.setHeight(self._dParams[WuiMain.ksParamGraphWizHeight])
267 oGraph.setDpi(self._dParams[WuiMain.ksParamGraphWizDpi])
268 oGraph.setErrorBarY(self._dParams[WuiMain.ksParamGraphWizErrorBarY]);
269 oGraph.setFontSize(self._dParams[WuiMain.ksParamGraphWizFontSize]);
270 if hasattr(oGraph, 'setXkcdStyle'):
271 oGraph.setXkcdStyle(self._dParams[WuiMain.ksParamGraphWizXkcdStyle]);
272
273 return oGraph;
274
275 def _generateInteractiveForm(self):
276 """
277 Generates the HTML for the interactive form.
278 Returns (sTopOfForm, sEndOfForm)
279 """
280
281 #
282 # The top of the form.
283 #
284 sTop = '<form action="#" method="get" id="graphwiz-form">\n' \
285 ' <input type="hidden" name="%s" value="%s"/>\n' \
286 ' <input type="hidden" name="%s" value="%u"/>\n' \
287 % ( WuiMain.ksParamAction, WuiMain.ksActionGraphWiz,
288 WuiMain.ksParamGraphWizSrcTestSetId, self._dParams[WuiMain.ksParamGraphWizSrcTestSetId],
289 );
290
291 sTop += ' <div id="graphwiz-nav">\n';
292 sTop += ' <script type="text/javascript">\n' \
293 ' window.onresize = function(){ return graphwizOnResizeRecalcWidth("graphwiz-nav", "%s"); }\n' \
294 ' window.onload = function(){ return graphwizOnLoadRememberWidth("graphwiz-nav"); }\n' \
295 ' </script>\n' \
296 % ( WuiMain.ksParamGraphWizWidth, );
297
298 #
299 # Top: First row.
300 #
301 sTop += ' <div id="graphwiz-top-1">\n';
302
303 # time.
304 sNow = self._dParams[WuiMain.ksParamEffectiveDate];
305 if sNow is None: sNow = '';
306 sTop += ' <div id="graphwiz-time">\n';
307 sTop += ' <label for="%s">Starting:</label>\n' \
308 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-time-input"/>\n' \
309 % ( WuiMain.ksParamEffectiveDate,
310 WuiMain.ksParamEffectiveDate, WuiMain.ksParamEffectiveDate, sNow, );
311
312 sTop += ' <input type="hidden" name="%s" value="%u"/>\n' % ( WuiMain.ksParamReportPeriods, 1, );
313 sTop += ' <label for="%s"> Going back:\n' \
314 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-period-input"/>\n' \
315 % ( WuiMain.ksParamReportPeriodInHours,
316 WuiMain.ksParamReportPeriodInHours, WuiMain.ksParamReportPeriodInHours,
317 utils.formatIntervalHours(self._dParams[WuiMain.ksParamReportPeriodInHours]) );
318 sTop += ' </div>\n';
319
320 # Graph options top row.
321 sTop += ' <div id="graphwiz-top-options-1">\n';
322
323 # graph type.
324 sTop += ' <label for="%s">Graph:</label>\n' \
325 ' <select name="%s" id="%s">\n' \
326 % ( WuiMain.ksParamGraphWizImpl, WuiMain.ksParamGraphWizImpl, WuiMain.ksParamGraphWizImpl, );
327 for (sImpl, sDesc) in WuiMain.kaasGraphWizImplCombo:
328 sTop += ' <option value="%s"%s>%s</option>\n' \
329 % (sImpl, ' selected' if sImpl == self._dParams[WuiMain.ksParamGraphWizImpl] else '', sDesc);
330 sTop += ' </select>\n';
331
332 # graph size.
333 sTop += ' <label for="%s">Graph size:</label>\n' \
334 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-pixel-input"> x\n' \
335 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-pixel-input">\n' \
336 ' <label for="%s">Dpi:</label>'\
337 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-dpi-input">\n' \
338 ' <button type="button" onclick="%s">Defaults</button>\n' \
339 % ( WuiMain.ksParamGraphWizWidth,
340 WuiMain.ksParamGraphWizWidth, WuiMain.ksParamGraphWizWidth, self._dParams[WuiMain.ksParamGraphWizWidth],
341 WuiMain.ksParamGraphWizHeight, WuiMain.ksParamGraphWizHeight, self._dParams[WuiMain.ksParamGraphWizHeight],
342 WuiMain.ksParamGraphWizDpi,
343 WuiMain.ksParamGraphWizDpi, WuiMain.ksParamGraphWizDpi, self._dParams[WuiMain.ksParamGraphWizDpi],
344 webutils.escapeAttr('return graphwizSetDefaultSizeValues("graphwiz-nav", "%s", "%s", "%s");'
345 % ( WuiMain.ksParamGraphWizWidth, WuiMain.ksParamGraphWizHeight,
346 WuiMain.ksParamGraphWizDpi )),
347 );
348
349 sTop += ' </div>\n'; # (options row 1)
350
351 sTop += ' </div>\n'; # (end of row 1)
352
353 #
354 # Top: Second row.
355 #
356 sTop += ' <div id="graphwiz-top-2">\n';
357
358 # Submit
359 sFormButton = '<button type="submit">Refresh</button>\n';
360 sTop += ' <div id="graphwiz-top-submit">' + sFormButton + '</div>\n';
361
362
363 # Options.
364 sTop += ' <div id="graphwiz-top-options-2">\n';
365
366 sTop += ' <input type="checkbox" name="%s" id="%s" value="1"%s/>\n' \
367 ' <label for="%s">Tabular data</label>\n' \
368 % ( WuiMain.ksParamGraphWizTabular, WuiMain.ksParamGraphWizTabular,
369 ' checked' if self._dParams[WuiMain.ksParamGraphWizTabular] else '',
370 WuiMain.ksParamGraphWizTabular);
371
372 if hasattr(self.oGraphClass, 'setXkcdStyle'):
373 sTop += ' <input type="checkbox" name="%s" id="%s" value="1"%s/>\n' \
374 ' <label for="%s">xkcd-style</label>\n' \
375 % ( WuiMain.ksParamGraphWizXkcdStyle, WuiMain.ksParamGraphWizXkcdStyle,
376 ' checked' if self._dParams[WuiMain.ksParamGraphWizXkcdStyle] else '',
377 WuiMain.ksParamGraphWizXkcdStyle);
378 elif self._dParams[WuiMain.ksParamGraphWizXkcdStyle]:
379 sTop += ' <input type="hidden" name="%s" id="%s" value="1"/>\n' \
380 % ( WuiMain.ksParamGraphWizXkcdStyle, WuiMain.ksParamGraphWizXkcdStyle, );
381
382 if not hasattr(self.oGraphClass, 'kfNoErrorBarsSupport'):
383 sTop += ' <input type="checkbox" name="%s" id="%s" value="1"%s title="%s"/>\n' \
384 ' <label for="%s">Error bars,</label>\n' \
385 ' <label for="%s">max: </label>\n' \
386 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-maxerrorbar-input" title="%s"/>\n' \
387 % ( WuiMain.ksParamGraphWizErrorBarY, WuiMain.ksParamGraphWizErrorBarY,
388 ' checked' if self._dParams[WuiMain.ksParamGraphWizErrorBarY] else '',
389 'Error bars shows some of the max and min results on the Y-axis.',
390 WuiMain.ksParamGraphWizErrorBarY,
391 WuiMain.ksParamGraphWizMaxErrorBarY,
392 WuiMain.ksParamGraphWizMaxErrorBarY, WuiMain.ksParamGraphWizMaxErrorBarY,
393 self._dParams[WuiMain.ksParamGraphWizMaxErrorBarY],
394 'Maximum number of Y-axis error bar per graph. (Too many makes it unreadable.)'
395 );
396 else:
397 if self._dParams[WuiMain.ksParamGraphWizErrorBarY]:
398 sTop += '<input type="hidden" name="%s" id="%s" value="1">\n' \
399 % ( WuiMain.ksParamGraphWizErrorBarY, WuiMain.ksParamGraphWizErrorBarY, );
400 sTop += '<input type="hidden" name="%s" id="%s" value="%u">\n' \
401 % ( WuiMain.ksParamGraphWizMaxErrorBarY, WuiMain.ksParamGraphWizMaxErrorBarY,
402 self._dParams[WuiMain.ksParamGraphWizMaxErrorBarY], );
403
404 sTop += ' <label for="%s">Font size: </label>\n' \
405 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-fontsize-input"/>\n' \
406 % ( WuiMain.ksParamGraphWizFontSize,
407 WuiMain.ksParamGraphWizFontSize, WuiMain.ksParamGraphWizFontSize,
408 self._dParams[WuiMain.ksParamGraphWizFontSize], );
409
410 sTop += ' <label for="%s">Data series: </label>\n' \
411 ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-maxpergraph-input" title="%s"/>\n' \
412 % ( WuiMain.ksParamGraphWizMaxPerGraph,
413 WuiMain.ksParamGraphWizMaxPerGraph, WuiMain.ksParamGraphWizMaxPerGraph,
414 self._dParams[WuiMain.ksParamGraphWizMaxPerGraph],
415 'Max data series per graph.' );
416
417 sTop += ' </div>\n'; # (options row 2)
418
419 sTop += ' </div>\n'; # (end of row 2)
420
421 sTop += ' </div>\n'; # end of top.
422
423 #
424 # The end of the page selection.
425 #
426 sEnd = ' <div id="graphwiz-end-selection">\n';
427
428 #
429 # Testbox selection
430 #
431 aidTestBoxes = list(self._dParams[WuiMain.ksParamGraphWizTestBoxIds]);
432 sEnd += ' <div id="graphwiz-testboxes" class="graphwiz-end-selection-group">\n' \
433 ' <h3>TestBox Selection:</h3>\n' \
434 ' <ol class="tmgraph-testboxes">\n';
435
436 # Get a list of eligible testboxes from the DB.
437 for oTestBox in self._oModel.getEligibleTestBoxes():
438 try: aidTestBoxes.remove(oTestBox.idTestBox);
439 except: sChecked = '';
440 else: sChecked = ' checked';
441 sEnd += ' <li><input type="checkbox" name="%s" value="%s" id="gw-tb-%u"%s/>' \
442 '<label for="gw-tb-%u">%s</label></li>\n' \
443 % ( WuiMain.ksParamGraphWizTestBoxIds, oTestBox.idTestBox, oTestBox.idTestBox, sChecked,
444 oTestBox.idTestBox, oTestBox.sName);
445
446 # List testboxes that have been checked in a different period or something.
447 for idTestBox in aidTestBoxes:
448 oTestBox = self._oModel.oCache.getTestBox(idTestBox);
449 sEnd += ' <li><input type="checkbox" name="%s" value="%s" id="gw-tb-%u" checked/>' \
450 '<label for="gw-tb-%u">%s</label></li>\n' \
451 % ( WuiMain.ksParamGraphWizTestBoxIds, oTestBox.idTestBox, oTestBox.idTestBox,
452 oTestBox.idTestBox, oTestBox.sName);
453
454 sEnd += ' </ol>\n' \
455 ' </div>\n';
456
457 #
458 # Build category selection.
459 #
460 aidBuildCategories = list(self._dParams[WuiMain.ksParamGraphWizBuildCatIds]);
461 sEnd += ' <div id="graphwiz-buildcategories" class="graphwiz-end-selection-group">\n' \
462 ' <h3>Build Category Selection:</h3>\n' \
463 ' <ol class="tmgraph-buildcategories">\n';
464 for oBuildCat in self._oModel.getEligibleBuildCategories():
465 try: aidBuildCategories.remove(oBuildCat.idBuildCategory);
466 except: sChecked = '';
467 else: sChecked = ' checked';
468 sEnd += ' <li><input type="checkbox" name="%s" value="%s" id="gw-bc-%u" %s/>' \
469 '<label for="gw-bc-%u">%s / %s / %s / %s</label></li>\n' \
470 % ( WuiMain.ksParamGraphWizBuildCatIds, oBuildCat.idBuildCategory, oBuildCat.idBuildCategory, sChecked,
471 oBuildCat.idBuildCategory,
472 oBuildCat.sProduct, oBuildCat.sBranch, oBuildCat.sType, ' & '.join(oBuildCat.asOsArches) );
473 assert not aidBuildCategories; # SQL should return all currently selected.
474
475 sEnd += ' </ol>\n' \
476 ' </div>\n';
477
478 #
479 # Testcase variations.
480 #
481 sEnd += ' <div id="graphwiz-testcase-variations" class="graphwiz-end-selection-group">\n' \
482 ' <h3>Miscellaneous:</h3>\n' \
483 ' <ol>';
484
485 sEnd += ' <li>\n' \
486 ' <input type="checkbox" id="%s" name="%s" value="1"%s/>\n' \
487 ' <label for="%s">Separate by testcase variation.</label>\n' \
488 ' </li>\n' \
489 % ( WuiMain.ksParamGraphWizSepTestVars, WuiMain.ksParamGraphWizSepTestVars,
490 ' checked' if self._dParams[WuiMain.ksParamGraphWizSepTestVars] else '',
491 WuiMain.ksParamGraphWizSepTestVars );
492
493
494 sEnd += ' <li>\n' \
495 ' <lable for="%s">Test case ID:</label>\n' \
496 ' <input type="text" id="%s" name="%s" value="%s" readonly/>\n' \
497 ' </li>\n' \
498 % ( WuiMain.ksParamGraphWizTestCaseIds,
499 WuiMain.ksParamGraphWizTestCaseIds, WuiMain.ksParamGraphWizTestCaseIds,
500 ','.join([str(i) for i in self._dParams[WuiMain.ksParamGraphWizTestCaseIds]]), );
501
502 sEnd += ' </ol>\n' \
503 ' </div>\n';
504
505 #sEnd += ' <h3>&nbsp;</h3>\n';
506
507 #
508 # Finish up the form.
509 #
510 sEnd += ' <div id="graphwiz-end-submit"><p>' + sFormButton + '</p></div>\n';
511 sEnd += ' </div>\n' \
512 '</form>\n';
513
514 return (sTop, sEnd);
515
516 def generateReportBody(self):
517 fInteractive = not self._fSubReport;
518
519 # Quick mockup.
520 self._sTitle = 'Graph Wizzard';
521
522 sHtml = '';
523 sHtml += '<h2>Incomplete code - no complaints yet, thank you!!</h2>\n';
524
525 #
526 # Create a form for altering the data we're working with.
527 #
528 if fInteractive:
529 (sTopOfForm, sEndOfForm) = self._generateInteractiveForm();
530 sHtml += sTopOfForm;
531 del sTopOfForm;
532
533 #
534 # Emit the graphs. At least one per sample source.
535 #
536 sHtml += ' <div id="graphwiz-graphs">\n';
537 iGraph = 0;
538 aoCollections = self._oModel.fetchGraphData();
539 for iCollection, oCollection in enumerate(aoCollections):
540 # Name the graph and add a checkbox for removing it.
541 sSampleName = self._calcSampleName(oCollection);
542 sHtml += ' <div class="graphwiz-collection" id="graphwiz-source-%u">\n' % (iCollection,);
543 if fInteractive:
544 sHtml += ' <div class="graphwiz-src-select">\n' \
545 ' <input type="checkbox" name="%s" id="%s" value="%s:%s%s" checked class="graphwiz-src-input">\n' \
546 ' <label for="%s">%s</label>\n' \
547 ' </div>\n' \
548 % ( WuiMain.ksParamReportSubjectIds, WuiMain.ksParamReportSubjectIds, oCollection.sType,
549 ':'.join([str(idStr) for idStr in oCollection.aidStrTests]),
550 ':%u' % oCollection.idStrValue if oCollection.idStrValue else '',
551 WuiMain.ksParamReportSubjectIds, sSampleName );
552
553 if oCollection.aoSeries:
554 #
555 # Split the series into sub-graphs as needed and produce SVGs.
556 #
557 aaoSeries = self._splitSeries(oCollection.aoSeries);
558 for aoSeries in aaoSeries:
559 # Gather the data for this graph. (Most big stuff is passed by
560 # reference, so there shouldn't be any large memory penalty for
561 # repacking the data here.)
562 sYUnit = None;
563 if aoSeries[0].iUnit < len(constants.valueunit.g_asNames) and aoSeries[0].iUnit > 0:
564 sYUnit = constants.valueunit.g_asNames[aoSeries[0].iUnit];
565 oData = WuiHlpGraphDataTableEx(sXUnit = 'Build revision', sYUnit = sYUnit);
566
567 fSeriesName = self._figureSeriesNameBits(aoSeries);
568 for oSeries in aoSeries:
569 sSeriesName = self._getSeriesNameFromBits(oSeries, fSeriesName);
570 asHtmlTooltips = None;
571 if len(oSeries.aoRevInfo) == len(oSeries.aiRevisions):
572 asHtmlTooltips = [];
573 for i, oRevInfo in enumerate(oSeries.aoRevInfo):
574 sPlusMinus = '';
575 if oSeries.acSamples[i] > 1:
576 sPlusMinus = ' (+%s/-%s; %u samples)' \
577 % ( utils.formatNumber(oSeries.aiErrorBarAbove[i]),
578 utils.formatNumber(oSeries.aiErrorBarBelow[i]),
579 oSeries.acSamples[i])
580 sTooltip = '<table class=\'graphwiz-tt\'><tr><td>%s:</td><td>%s %s %s</td></tr>'\
581 '<tr><td>Rev:</td><td>r%s</td></tr>' \
582 % ( sSeriesName,
583 utils.formatNumber(oSeries.aiValues[i]),
584 sYUnit, sPlusMinus,
585 oSeries.aiRevisions[i],
586 );
587 if oRevInfo.sAuthor is not None:
588 sMsg = oRevInfo.sMessage[:80].strip();
589 #if sMsg.find('\n') >= 0:
590 # sMsg = sMsg[:sMsg.find('\n')].strip();
591 sTooltip += '<tr><td>Author:</td><td>%s</td></tr>' \
592 '<tr><td>Date:</td><td>%s</td><tr>' \
593 '<tr><td>Message:</td><td>%s%s</td></tr>' \
594 % ( oRevInfo.sAuthor,
595 self.formatTsShort(oRevInfo.tsCreated),
596 sMsg, '...' if len(oRevInfo.sMessage) > len(sMsg) else '');
597 sTooltip += '</table>';
598 asHtmlTooltips.append(sTooltip);
599 oData.addDataSeries(sSeriesName, oSeries.aiRevisions, oSeries.aiValues, asHtmlTooltips,
600 oSeries.aiErrorBarBelow, oSeries.aiErrorBarAbove);
601 # Render the data into a graph.
602 oGraph = self.oGraphClass('tmgraph-%u' % (iGraph,), oData, self._oDisp);
603 self._configureGraph(oGraph);
604
605 oGraph.setTitle(self._calcGraphName(aoSeries[0], fSeriesName, sSampleName));
606 sHtml += ' <div class="graphwiz-graph" id="graphwiz-graph-%u">\n' % (iGraph,);
607 sHtml += oGraph.renderGraph();
608 sHtml += '\n </div>\n';
609 iGraph += 1;
610
611 #
612 # Emit raw tabular data if requested.
613 #
614 if self._dParams[WuiMain.ksParamGraphWizTabular]:
615 sHtml += ' <div class="graphwiz-tab-div" id="graphwiz-tab-%u">\n' \
616 ' <table class="tmtable graphwiz-tab">\n' \
617 % (iCollection, );
618 for aoSeries in aaoSeries:
619 if aoSeries[0].iUnit < len(constants.valueunit.g_asNames) and aoSeries[0].iUnit > 0:
620 sUnit = constants.valueunit.g_asNames[aoSeries[0].iUnit];
621 else:
622 sUnit = str(aoSeries[0].iUnit);
623
624 for iSeries, oSeries in enumerate(aoSeries):
625 sColor = self.oGraphClass.calcSeriesColor(iSeries);
626
627 sHtml += '<thead class="tmheader">\n' \
628 ' <tr class="graphwiz-tab graphwiz-tab-new-series-row">\n' \
629 ' <th colspan="5"><span style="background-color:%s;">&nbsp;&nbsp;</span> %s</th>\n' \
630 ' </tr>\n' \
631 ' <tr class="graphwiz-tab graphwiz-tab-col-hdr-row">\n' \
632 ' <th>Revision</th><th>Value (%s)</th><th>&Delta;max</th><th>&Delta;min</th>' \
633 '<th>Samples</th>\n' \
634 ' </tr>\n' \
635 '</thead>\n' \
636 % ( sColor,
637 self._getSeriesNameFromBits(oSeries, self.kfSeriesName_All & ~self.kfSeriesName_OsArchs),
638 sUnit );
639
640 for i, iRevision in enumerate(oSeries.aiRevisions):
641 sHtml += ' <tr class="%s"><td>r%s</td><td>%s</td><td>+%s</td><td>-%s</td><td>%s</td></tr>\n' \
642 % ( 'tmodd' if i & 1 else 'tmeven',
643 iRevision, oSeries.aiValues[i],
644 oSeries.aiErrorBarAbove[i], oSeries.aiErrorBarBelow[i],
645 oSeries.acSamples[i]);
646 sHtml += ' </table>\n' \
647 ' </div>\n';
648 else:
649 sHtml += '<i>No results.</i>\n';
650 sHtml += ' </div>\n'
651 sHtml += ' </div>\n';
652
653 #
654 # Finish the form.
655 #
656 if fInteractive:
657 sHtml += sEndOfForm;
658
659 return sHtml;
660
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette