Changeset 61254 in vbox for trunk/src/VBox/ValidationKit
- Timestamp:
- May 28, 2016 1:38:58 AM (9 years ago)
- svn:sync-xref-src-repo-rev:
- 107546
- Location:
- trunk/src/VBox/ValidationKit/testmanager
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/testmanager/core/db.py
r61217 r61254 333 333 334 334 aasExplain = None; 335 if self._oExplainCursor is not None :335 if self._oExplainCursor is not None and not sBound.startswith('DROP'): 336 336 try: 337 337 if config.g_kfWebUiSqlTraceExplainTiming: -
trunk/src/VBox/ValidationKit/testmanager/core/report.py
r56763 r61254 34 34 from testmanager.core.build import BuildCategoryData; 35 35 from testmanager.core.dbobjcache import DatabaseObjCache; 36 from testmanager.core.failurereason import FailureReasonLogic; 36 37 from testmanager.core.testbox import TestBoxData; 37 38 from common import constants; … … 126 127 return sWhere; 127 128 129 def getPeriodStart(self, iPeriod): 130 """ Gets the python timestamp for the start of the given period. """ 131 from datetime import timedelta; 132 tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp(); 133 cHoursStart = (self.cPeriods - iPeriod ) * self.cHoursPerPeriod; 134 return tsNow - timedelta(hours = cHoursStart); 135 136 def getPeriodEnd(self, iPeriod): 137 """ Gets the python timestamp for the end of the given period. """ 138 from datetime import timedelta; 139 tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp(); 140 cHoursEnd = (self.cPeriods - iPeriod - 1) * self.cHoursPerPeriod; 141 return tsNow - timedelta(hours = cHoursEnd); 142 128 143 def getExtraWhereExprForPeriod(self, iPeriod): 129 144 """ … … 157 172 return '%dh ago' % (iPeriod * self.cHoursPerPeriod, ); 158 173 174 def getStraightPeriodDesc(self, iPeriod): 175 """ 176 Returns the period description, usually for graph data. 177 """ 178 iWickedPeriod = self.cPeriods - iPeriod - 1; 179 return self.getPeriodDesc(iWickedPeriod); 180 181 182 class ReportFailureReasonRow(object): 183 """ Simpler to return this than muck about with stupid arrays. """ 184 def __init__(self, aoRow, oReason): 185 self.idFailureReason = aoRow[0]; 186 self.cHits = aoRow[1]; 187 self.tsMin = aoRow[2]; 188 self.tsMax = aoRow[3]; 189 self.oReason = oReason; # FailureReasonDataEx 190 191 class ReportFailureReasonTransient(object): 192 """ Details the first or last occurence of a reason. """ 193 def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, oReason, iPeriod, fEnter): 194 self.idBuild = idBuild; # Build ID. 195 self.iRevision = iRevision; # SVN revision for build. 196 self.sRepository = sRepository; # SVN repository for build. 197 self.idTestSet = idTestSet; # Test set. 198 self.idTestResult = idTestResult; # Test result. 199 self.tsDone = tsDone; # When the test set was done. 200 self.oReason = oReason; # FailureReasonDataEx 201 self.iPeriod = iPeriod; # Data set period. 202 self.fEnter = fEnter; # True if enter event, False if leave event. 203 204 class ReportFailureReasonPeriod(object): 205 """ A period in ReportFailureReasonSet. """ 206 def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo): 207 self.oSet = oSet # Reference to the parent ReportFailureReasonSet. 208 self.iPeriod = iPeriod; 209 self.sDesc = sDesc; 210 self.aoRows = []; # Rows in order the database returned them. 211 self.dById = {}; # Same as aoRows but indexed by idFailureReason. 212 self.cHits = 0; # Total number of hits. 213 self.dFirst = {}; # The reasons seen for the first time (idFailureReason key). 214 self.dLast = {}; # The reasons seen for the last time (idFailureReason key). 215 self.tsStart = tsFrom; 216 self.tsEnd = tsTo; 217 self.tsMin = tsTo; 218 self.tsMax = tsFrom; 219 220 class ReportFailureReasonSet(object): 221 """ What ReportLazyModel.getFailureReasons returns. """ 222 def __init__(self): 223 self.aoPeriods = []; # Periods in ascending order (time wise). 224 self.dReasons = {}; # FailureReasonDataEx objected indexted by idFailureReason 225 self.dTotals = {}; # Totals per reason, indexed by idFailureReason. 226 self.cHits = 0; # Total number of hits in all periods and all reasons. 227 self.cMaxRowHits = 0; # Max hits in a row. 228 self.diFirst = {}; # The period number a reason was first seen (idFailureReason key). 229 self.diLast = {}; # The period number a reason was last seen (idFailureReason key). 230 self.aoEnterInfo = []; # Array of ReportFailureReasonTransient order by iRevision. Excludes the first period. 231 self.aoLeaveInfo = []; # Array of ReportFailureReasonTransient order in descending order by iRevision. Excludes last. 159 232 160 233 … … 191 264 192 265 adPeriods = []; 193 for iPeriod in range(self.cPeriods):266 for iPeriod in xrange(self.cPeriods): 194 267 self._oDb.execute('SELECT enmStatus, COUNT(TestSets.idTestSet)\n' 195 268 'FROM TestSets' + self.getExtraSubjectTables() +'\n' … … 219 292 220 293 return adPeriods; 294 295 296 def getFailureReasons(self): 297 """ 298 Gets the failure reasons of the subject in the specified period. 299 300 Returns an array of data per period (0 is the oldes, self.cPeriods-1 is 301 the latest) where each entry is a dicationary using failure reason ID as 302 key. The dictionary contains a tuple where the first element is the 303 number of occurences and the second is the corresponding 304 FailureReasonDataEx object from the cache. 305 306 Note that reason IDs may not be present in every period, we only return 307 those with actual occurences. 308 """ 309 310 # Create a temporary table 311 sTsNow = 'CURRENT_TIMESTAMP' if self.tsNow is None else self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)); 312 sTsFirst = '(%s - interval \'%s hours\')' \ 313 % (sTsNow, self.cHoursPerPeriod * self.cPeriods,); 314 self._oDb.execute('CREATE TEMPORARY TABLE TmpReasons ON COMMIT DROP AS\n' 315 'SELECT TestResultFailures.idFailureReason AS idFailureReason,\n' 316 ' TestResultFailures.idTestResult AS idTestResult,\n' 317 ' TestSets.idTestSet AS idTestSet,\n' 318 ' TestSets.tsDone AS tsDone,\n' 319 ' TestSets.tsCreated AS tsCreated,\n' 320 ' TestSets.idBuild AS idBuild\n' 321 'FROM TestResultFailures,\n' 322 ' TestResults,\n' 323 ' TestSets' + self.getExtraSubjectTables() + '\n' 324 'WHERE TestResultFailures.idTestResult = TestResults.idTestResult\n' 325 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' 326 ' AND TestResultFailures.tsEffective >= ' + sTsFirst + '\n' 327 ' AND TestResults.enmStatus <> \'running\'\n' 328 ' AND TestResults.enmStatus <> \'success\'\n' 329 ' AND TestResults.tsCreated >= ' + sTsFirst + '\n' 330 ' AND TestResults.tsCreated < ' + sTsNow + '\n' 331 ' AND TestResults.idTestSet = TestSets.idTestSet\n' 332 ' AND TestSets.tsDone >= ' + sTsFirst + '\n' 333 ' AND TestSets.tsDone < ' + sTsNow + '\n' 334 + self.getExtraSubjectWhereExpr()); 335 self._oDb.execute('SELECT idFailureReason FROM TmpReasons;'); 336 337 338 # Retrieve the period results. 339 oFailureReasonLogic = FailureReasonLogic(self._oDb); 340 oSet = ReportFailureReasonSet(); 341 for iPeriod in xrange(self.cPeriods): 342 #cHoursStarted = (self.cPeriods - iPeriod) * self.cHoursPerPeriod; 343 #if self.tsNow is None: 344 # sTsFirst = '(CURRENT_TIMESTAMP - interval \'%u hours\')' % (cHoursStarted,); 345 # 346 #else: 347 # sTsFirst = '(%s - interval \'%u hours\')' \ 348 # % ( self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)), cHoursStarted,) ; 349 # 350 #self._oDb.execute('SELECT TestResultFailures.idFailureReason,\n' 351 # ' COUNT(TestResultFailures.idTestResult),\n' 352 # ' MIN(TestSets.tsDone),\n' 353 # ' MAX(TestSets.tsDone)\n' 354 # 'FROM TestResultFailures,\n' 355 # ' TestResults,\n' 356 # ' TestSets' + self.getExtraSubjectTables() + '\n' 357 # 'WHERE TestResultFailures.idTestResult = TestResults.idTestResult\n' 358 # ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' 359 # ' AND TestResultFailures.tsEffective >= ' + sTsFirst + '\n' 360 # ' AND TestResults.enmStatus <> \'running\'\n' 361 # ' AND TestResults.enmStatus <> \'success\'\n' 362 # ' AND TestResults.tsCreated >= ' + sTsFirst + '\n' 363 # ' AND TestResults.idTestSet = TestSets.idTestSet\n' 364 # + self.getExtraSubjectWhereExpr() 365 # + self.getExtraWhereExprForPeriod(iPeriod) 366 # + 'GROUP BY TestResultFailures.idFailureReason\n'); 367 368 self._oDb.execute('SELECT idFailureReason,\n' 369 ' COUNT(idTestResult),\n' 370 ' MIN(tsDone),\n' 371 ' MAX(tsDone)\n' 372 'FROM TmpReasons\n' 373 'WHERE TRUE\n' 374 + self.getExtraWhereExprForPeriod(iPeriod).replace('TestSets.', '') 375 + 'GROUP BY idFailureReason\n'); 376 377 aaoRows = self._oDb.fetchAll() 378 379 oPeriod = ReportFailureReasonPeriod(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod), 380 self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod)); 381 oSet.aoPeriods.append(oPeriod); 382 for aoRow in aaoRows: 383 oReason = oFailureReasonLogic.cachedLookup(aoRow[0]); 384 oPeriodRow = ReportFailureReasonRow(aoRow, oReason); 385 oPeriod.aoRows.append(oPeriodRow); 386 oPeriod.dById[oPeriodRow.idFailureReason] = oPeriodRow; 387 oPeriod.cHits += oPeriodRow.cHits; 388 if oPeriodRow.idFailureReason in oSet.dReasons: 389 oSet.dTotals[oPeriodRow.idFailureReason] += oPeriodRow.cHits; 390 else: 391 oSet.dTotals[oPeriodRow.idFailureReason] = oPeriodRow.cHits; 392 oSet.dReasons[oPeriodRow.idFailureReason] = oReason; 393 oSet.diFirst[oPeriodRow.idFailureReason] = iPeriod; 394 oPeriod.dFirst[oPeriodRow.idFailureReason] = oReason; 395 if oPeriodRow.cHits > oSet.cMaxRowHits: 396 oSet.cMaxRowHits = oPeriodRow.cHits; 397 if oPeriodRow.tsMin < oPeriod.tsMin: 398 oPeriod.tsMin = oPeriodRow.tsMin; 399 if oPeriodRow.tsMax > oPeriod.tsMax: 400 oPeriod.tsMax = oPeriodRow.tsMax; 401 oSet.cHits += oPeriod.cHits; 402 403 # 404 # construct the diLast and dLast bits. 405 # 406 for iPeriod in xrange(self.cPeriods - 1, 0, -1): 407 oPeriod = oSet.aoPeriods[iPeriod]; 408 for oRow in oPeriod.aoRows: 409 if oRow.idFailureReason not in oSet.diLast: 410 oSet.diLast[oRow.idFailureReason] = iPeriod; 411 oPeriod.dLast[oRow.idFailureReason] = oRow.oReason; 412 413 # 414 # For reasons entering after the first period, look up the build and 415 # test set it first occured with. 416 # 417 for iPeriod in xrange(1, self.cPeriods): 418 oPeriod = oSet.aoPeriods[iPeriod]; 419 for oReason in oPeriod.dFirst.values(): 420 oSet.aoEnterInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = True)); 421 oSet.aoEnterInfo = sorted(oSet.aoEnterInfo, key = lambda oTrans: oTrans.iRevision); 422 423 # Ditto for reasons leaving before the last. 424 for iPeriod in xrange(self.cPeriods - 1): 425 oPeriod = oSet.aoPeriods[iPeriod]; 426 for oReason in oPeriod.dLast.values(): 427 oSet.aoLeaveInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = False)); 428 oSet.aoLeaveInfo = sorted(oSet.aoLeaveInfo, key = lambda oTrans: oTrans.iRevision, reverse = True); 429 430 self._oDb.execute('DROP TABLE TmpReasons\n'); 431 return oSet; 432 433 434 def _getEdgeFailureReasonOccurence(self, oReason, iPeriod, fEnter = True): 435 """ 436 Helper for the failure reason report that finds the oldest or newest build 437 (SVN rev) and test set (start time) it occured with. 438 439 If fEnter is set the oldest occurence is return, if fEnter clear the newest 440 is is returned. 441 442 Returns ReportFailureReasonTransient instant. 443 444 """ 445 sSorting = 'ASC' if fEnter else 'DESC'; 446 self._oDb.execute('SELECT TmpReasons.idTestResult,\n' 447 ' TmpReasons.idTestSet,\n' 448 ' TmpReasons.tsDone,\n' 449 ' TmpReasons.idBuild,\n' 450 ' Builds.iRevision,\n' 451 ' BuildCategories.sRepository\n' 452 'FROM TmpReasons,\n' 453 ' Builds,\n' 454 ' BuildCategories\n' 455 'WHERE TmpReasons.idFailureReason = %s\n' 456 ' AND TmpReasons.idBuild = Builds.idBuild\n' 457 ' AND Builds.tsExpire > TmpReasons.tsCreated\n' 458 ' AND Builds.tsEffective <= TmpReasons.tsCreated\n' 459 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' 460 'ORDER BY Builds.iRevision ' + sSorting + ',\n' 461 ' TmpReasons.tsCreated ' + sSorting + '\n' 462 'LIMIT 1\n' 463 , ( oReason.idFailureReason, )); 464 aoRow = self._oDb.fetchOne(); 465 if aoRow is None: 466 return ReportFailureReasonTransient(-1, -1, 'internal-error', -1, -1, 467 self._oDb.getCurrentTimestamp(), oReason, iPeriod); 468 return ReportFailureReasonTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5], 469 idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2], 470 oReason = oReason, iPeriod = iPeriod, fEnter = fEnter); 471 472 473 221 474 222 475 class ReportGraphModel(ReportModelBase): # pylint: disable=R0903 -
trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py
r61220 r61254 31 31 32 32 # Validation Kit imports. 33 from testmanager.webui.wuicontentbase import WuiContentBase; 34 from testmanager.webui.wuihlpgraph import WuiHlpGraphDataTable, WuiHlpBarGraph; 33 from common import webutils; 34 from testmanager.webui.wuicontentbase import WuiContentBase, WuiSvnLinkWithTooltip; 35 from testmanager.webui.wuihlpgraph import WuiHlpGraphDataTable, WuiHlpBarGraph; 35 36 from testmanager.core.report import ReportModelBase; 36 37 … … 132 133 """ 133 134 134 def generateReportBody(self): 135 # Mockup. 136 self._sTitle = 'Success rate'; 137 return '<p>Graph showing COUNT(idFailureReason) grouped by time period.</p>' \ 138 '<p>New reasons per period, tracked down to build revision.</p>' \ 139 '<p>Show graph content in table form.</p>'; 135 def _formatEdgeOccurence(self, oTransient): 136 """ 137 Helper for formatting the transients. 138 oTransient is of type ReportFailureReasonTransient. 139 """ 140 sHtml = u'<li>'; 141 if oTransient.fEnter: sHtml += 'Since '; 142 else: sHtml += 'Till '; 143 sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml(); 144 sHtml += u', %s: ' % (self.formatTsShort(oTransient.tsDone),); 145 sHtml += u'%s / %s' % (webutils.escapeElem(oTransient.oReason.oCategory.sShort), 146 webutils.escapeElem(oTransient.oReason.sShort),); 147 sHtml += u'</li>\n'; 148 149 return sHtml; 150 151 152 def generateReportBody(self): 153 self._sTitle = 'Failure reasons'; 154 155 sHtml = u''; 156 157 # 158 # The array of periods we get have the oldest period first [0]. 159 # 160 oSet = self._oModel.getFailureReasons(); 161 162 # 163 # List failure reasons starting or stopping to appear within the data set. 164 # 165 dtFirstLast = {}; 166 for iPeriod, oPeriod in enumerate(oSet.aoPeriods): 167 for oRow in oPeriod.aoRows: 168 tIt = dtFirstLast.get(oRow.idFailureReason, (iPeriod, iPeriod)); 169 #sHtml += u'<!-- %d: %d,%d -- %d -->\n' % (oRow.idFailureReason, tIt[0], tIt[1], iPeriod); 170 dtFirstLast[oRow.idFailureReason] = (tIt[0], iPeriod); 171 172 sHtml += '<!-- \n'; 173 for iPeriod, oPeriod in enumerate(oSet.aoPeriods): 174 sHtml += ' iPeriod=%d tsStart=%s tsEnd=%s\n' % (iPeriod, oPeriod.tsStart, oPeriod.tsEnd,); 175 sHtml += ' tsMin=%s tsMax=%s\n' % (oPeriod.tsMin, oPeriod.tsMax,); 176 sHtml += ' %d / %s\n' % (oPeriod.iPeriod, oPeriod.sDesc,) 177 sHtml += '-->\n'; 178 179 sHtml += u'<h4>Changes:</h4>\n' \ 180 u'<ul>\n'; 181 if len(oSet.aoEnterInfo) == 0 and len(oSet.aoLeaveInfo) == 0: 182 sHtml += u'<li> No changes</li>\n'; 183 else: 184 for oTransient in oSet.aoEnterInfo: 185 sHtml += self._formatEdgeOccurence(oTransient); 186 for oTransient in oSet.aoLeaveInfo: 187 sHtml += self._formatEdgeOccurence(oTransient); 188 sHtml += u'</ul>\n'; 189 190 # 191 # Graph. 192 # 193 if True: # pylint: disable=W0125 194 aidSorted = sorted(oSet.dReasons, key = lambda idReason: oSet.dTotals[idReason], reverse = True); 195 else: 196 aidSorted = sorted(oSet.dReasons, 197 key = lambda idReason: '%s / %s' % (oSet.dReasons[idReason].oCategory.sShort, 198 oSet.dReasons[idReason].sShort,)); 199 200 asNames = []; 201 for idReason in aidSorted: 202 oReason = oSet.dReasons[idReason]; 203 asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) ) 204 oTable = WuiHlpGraphDataTable('Period', asNames); 205 206 for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)): 207 aiValues = []; 208 for idReason in aidSorted: 209 oRow = oPeriod.dById.get(idReason, None); 210 iValue = oRow.cHits if oRow is not None else 0; 211 aiValues.append(iValue); 212 oTable.addRow(oPeriod.sDesc, aiValues); 213 214 oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp); 215 oGraph.setRangeMax(max(oSet.cMaxRowHits + 1, 3)); 216 sHtml += oGraph.renderGraph(); 217 218 # 219 # Table form necessary? 220 # 221 #sHtml += u'<p>TODO: Show graph content in table form.</p>'; 222 223 return sHtml; 140 224 141 225 … … 152 236 oSuccessRate = WuiReportSuccessRate(self._oModel, self._dParams, fSubReport = True, 153 237 fnDPrint = self._fnDPrint, oDisp = self._oDisp); 154 sHtml += oSuccessRate.show()[1]; 238 239 240 241 oFailureReasons = WuiReportFailureReasons(self._oModel, self._dParams, fSubReport = True, 242 fnDPrint = self._fnDPrint, oDisp = self._oDisp); 243 for oReport in [oSuccessRate, oFailureReasons, ]: 244 (sTitle, sContent) = oReport.show(); 245 sHtml += '<br>'; # drop this layout hack 246 sHtml += '<div>'; 247 sHtml += '<h3>%s</h3>\n' % (webutils.escapeElem(sTitle),); 248 sHtml += sContent; 249 sHtml += '</div>'; 155 250 156 251 return sHtml;
Note:
See TracChangeset
for help on using the changeset viewer.