Changeset 97279 in vbox
- Timestamp:
- Oct 24, 2022 2:36:36 PM (2 years ago)
- svn:sync-xref-src-repo-rev:
- 154267
- Location:
- trunk/src/VBox/ValidationKit/analysis
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/analysis/analyze.py
r97267 r97279 146 146 print(oWrapper.fill('Removes any leaf tests that are without any values or sub-tests. This is useful when ' 147 147 'only considering values, especially when doing additional value filtering.')); 148 149 print(''); 150 print('Analysis options:'); 151 print(' --pct-same-value <float>'); 152 print(oWrapper.fill('The threshold at which the percent difference between two values are considered the same ' 153 'during analysis.')); 154 print(oWrapper.initial_indent + 'Default: --pct-same-value 0.10'); 148 155 149 156 print(''); … … 307 314 fBrief = True; 308 315 cPctPrecision = 2; 316 rdPctSameValue = 0.1; 309 317 asTestFilters = []; 310 318 asTestOutFilters = []; … … 362 370 iBaseline = int(g_sOptArg); 363 371 372 elif matchWithValue('--pct-same-value'): 373 rdPctSameValue = float(g_sOptArg); 374 364 375 # '--' starts a new collection. If current one is empty, drop it. 365 376 elif sArg == '--': … … 426 437 # Produce the report. 427 438 # 428 oTable = reporting.RunTable(iBaseline, fBrief, cPctPrecision );439 oTable = reporting.RunTable(iBaseline, fBrief, cPctPrecision, rdPctSameValue); 429 440 oTable.populateFromRuns([oCollection.oDistilled for oCollection in aoCollections], 430 441 [oCollection.sName for oCollection in aoCollections]); -
trunk/src/VBox/ValidationKit/analysis/reader.py
r97267 r97279 97 97 "ns/roundtrip": -2, 98 98 "ins": +2, 99 "ins/sec": -1,99 "ins/sec": +2, 100 100 "": +1, # Difficult to say what's best really. 101 101 "pp1k": -2, … … 127 127 assert self.lValue is None or isinstance(self.lValue, (int, long)), "lValue=%s %s" % (self.lValue, type(self.lValue),); 128 128 129 # Members set by processing.130 self.sDiff = None;131 132 129 def clone(self, oParentTest): 133 130 """ … … 150 147 return False; 151 148 149 def canDoBetterCompare(self): 150 """ 151 Checks whether we can do a confident better-than comparsion of the value. 152 """ 153 return self.sUnit is not None and self.kdBestByUnit[self.sUnit] not in (-1, 0, 1); 154 155 def getBetterRelation(self): 156 """ 157 Returns +2 if larger values are definintely better. 158 Returns +1 if larger values are likely to be better. 159 Returns 0 if we have no clue. 160 Returns -1 if smaller values are likey to better. 161 Returns -2 if smaller values are definitely better. 162 """ 163 if self.sUnit is None: 164 return 0; 165 return self.kdBestByUnit[self.sUnit]; 152 166 153 167 @staticmethod … … 227 241 self.sStatus = None; 228 242 self.cErrors = -1; 229 230 # Members set by processing.231 self.sStatusDiff = None;232 self.cErrorsDiff = None;233 243 234 244 def clone(self, oParent = None): -
trunk/src/VBox/ValidationKit/analysis/reporting.py
r97271 r97279 186 186 self.iAlign = iAlign; 187 187 188 def asText(self, cchWidth): # type: (int) -> str 189 """ Pads the text to width of cchWidth characters. """ 190 return alignText(self.sText, cchWidth, self.iAlign); 191 188 192 189 193 class RunRow(object): … … 203 207 # Format as Text: 204 208 205 def formatNameAsText(self, cchWidth): # (int) -> str209 def formatNameAsText(self, cchWidth): # (int) -> TextElement 206 210 """ Format the row as text. """ 207 211 _ = cchWidth; 208 return ' ' * (self.iLevel * 2) + self.sName;212 return TextElement(' ' * (self.iLevel * 2) + self.sName, g_kiAlignLeft); 209 213 210 214 def getColumnCountAsText(self, oTable): … … 221 225 return [ TextElement(),]; 222 226 223 def calcColumnWidthsForText(self, oTable): 227 def calcColumnWidthsForText(self, oTable): # type: (RunTable) -> (bool, int, []) 224 228 """ 225 229 Calculates the column widths for text rendering. … … 237 241 else: 238 242 aoRetCols.append([len(oSubColumn.sText) for oSubColumn in aoSubColumns]); 239 return (False, len(self.formatNameAsText(0)) + self.iLevel * 2, aoRetCols); 243 return (False, len(self.formatNameAsText(0).sText), aoRetCols); 244 245 def renderAsText(self, oWidths, oTable): # type: (TextWidths, RunTable) -> str 246 """ 247 Renders the row as text. 248 249 Returns string. 250 """ 251 sRow = self.formatNameAsText(oWidths.cchName).asText(oWidths.cchName); 252 sRow = sRow + ' ' * (oWidths.cchName - min(len(sRow), oWidths.cchName)) + ' : '; 253 254 for iColumn in range(self.cColumns): 255 aoSubCols = self.formatColumnAsText(iColumn, oTable); 256 sCell = ''; 257 for iSub, oText in enumerate(aoSubCols): 258 cchWidth = oWidths.getColumnWidth(iColumn, len(aoSubCols), iSub); 259 if iSub > 0: 260 sCell += ' ' * oWidths.cchSubColSpacing; 261 sCell += oText.asText(cchWidth); 262 cchWidth = oWidths.getColumnWidth(iColumn); 263 sRow += (' | ' if iColumn > 0 else '') + ' ' * (cchWidth - min(cchWidth, len(sCell))) + sCell; 264 265 return sRow; 240 266 241 267 @staticmethod … … 252 278 253 279 @staticmethod 280 def formatPctAsText(chSign, rdPct, cPctPrecision): 281 """ Formats percentage value as text. """ 282 if rdPct >= 100: 283 return '%s%s%%' % (chSign, utils.formatNumber(int(rdPct + 0.5)),); 284 if rdPct * 100 + 0.5 >= 1: 285 return '%s%.*f%%' % (chSign, cPctPrecision, rdPct + 0.005,); 286 return '~' + chSign + '0.' + '0' * cPctPrecision + '%'; 287 288 @staticmethod 254 289 def formatDiffInPctAsText(lNumber, lBaseline, cPctPrecision): 255 290 """ Formats the difference between lNumber and lBaseline in precent as text. """ … … 265 300 lDiff = -lDiff; 266 301 chSign = '-'; 267 268 rdPct = lDiff / float(lBaseline); 269 #if rdPct * 100 >= 5: 270 # return '%s%s%%' % (chSign, utils.formatNumber(int(rdPct * 100 + 0.5)),); 271 #if rdPct * 1000 >= 5: 272 # return u'%s%s\u2030' % (chSign, int(rdPct * 1000 + 0.5),); 273 #if rdPct * 10000 >= 5: 274 # return u'%s%s\u2031' % (chSign, int(rdPct * 10000 + 0.5),); 275 #if rdPct * 1000000 >= 0.5: 276 # return u'%s%sppm' % (chSign, int(rdPct * 1000000 + 0.5),); 277 278 if rdPct * 100 >= 100: 279 return '%s%s%%' % (chSign, utils.formatNumber(int(rdPct * 100 + 0.5)),); 280 if rdPct * 10000 + 0.5 >= 1: 281 return '%s%.*f%%' % (chSign, cPctPrecision, rdPct * 100 + 0.005,); 282 283 return '~' + chSign + '0.' + '0' * cPctPrecision + '%'; 302 return RunRow.formatPctAsText(chSign, lDiff / float(lBaseline) * 100, cPctPrecision); 284 303 return ''; 304 305 306 class RunHeaderRow(RunRow): 307 """ 308 Run table header row. 309 """ 310 def __init__(self, sName, asColumns): # type: (str, [str]) -> None 311 RunRow.__init__(self, 0, sName); 312 self.asColumns = asColumns 313 self.cColumns = len(asColumns); 314 315 def formatColumnAsText(self, iColumn, oTable): # type: (int, RunTable) -> [TextElement] 316 return [TextElement(self.asColumns[iColumn], g_kiAlignCenter),]; 317 318 319 class RunFooterRow(RunHeaderRow): 320 """ 321 Run table footer row. 322 """ 323 def __init__(self, sName, asColumns): 324 RunHeaderRow.__init__(self, sName, asColumns); 325 326 327 class RunSeparatorRow(RunRow): 328 """ 329 Base class for separator rows. 330 """ 331 def __init__(self): 332 RunRow.__init__(self, 0, ''); 333 334 def calcTableWidthAsText(self, oWidths): 335 """ Returns the table width for when rendered as text. """ 336 cchWidth = oWidths.cchName; 337 for oCol in oWidths.aoColumns: 338 cchWidth += 3 + oCol.cch; 339 return cchWidth; 340 341 342 class RunHeaderSeparatorRow(RunSeparatorRow): 343 """ 344 Run table header separator row. 345 """ 346 def __init__(self): 347 RunSeparatorRow.__init__(self); 348 349 def renderAsText(self, oWidths, oTable): 350 _ = oTable; 351 return '=' * self.calcTableWidthAsText(oWidths); 352 353 354 class RunFooterSeparatorRow(RunHeaderSeparatorRow): 355 """ 356 Run table footer separator row. 357 """ 358 def __init__(self): 359 RunHeaderSeparatorRow.__init__(self); 285 360 286 361 … … 321 396 RunTestRow.__init__(self, iLevel, oTest, iRun); 322 397 398 def renderAsText(self, oWidths, oTable): 399 _ = oTable; 400 sRet = self.formatNameAsText(oWidths.cchName).asText(oWidths.cchName); 401 sRet += ' : '; 402 sRet += ' | '.join(['-' * oCol.cch for oCol in oWidths.aoColumns]); 403 return sRet; 404 405 323 406 class RunTestEndRow(RunTestRow): 324 407 """ … … 355 438 def formatNameAsText(self, cchWidth): 356 439 _ = cchWidth; 357 return '';440 return TextElement('runtime', g_kiAlignRight); 358 441 359 442 def getColumnCountAsText(self, oTable): … … 378 461 return [ TextElement(), ]; 379 462 463 464 class RunTestValueAnalysisRow(RunTestRow): 465 """ 466 Run table row with value analysis for a test, see if we have an improvement or not. 467 """ 468 def __init__(self, oStartRow): # type: (RunTestStartRow) -> None 469 RunTestRow.__init__(self, oStartRow.iLevel, oStartRow.oTest, oStartRow.iFirstRun, oStartRow.aoTests); 470 self.oStartRow = oStartRow # type: RunTestStartRow 471 self.cColumns = len(self.aoTests); 472 473 def formatNameAsText(self, cchWidth): 474 _ = cchWidth; 475 return TextElement('value analysis', g_kiAlignRight); 476 477 def formatColumnAsText(self, iColumn, oTable): 478 oBaseline = self.getBaseTest(oTable); 479 oTest = self.aoTests[iColumn]; 480 if not oTest or oTest is oBaseline: 481 return [TextElement(),]; 482 483 # 484 # This is a bit ugly, but it means we don't have to re-merge the values. 485 # 486 cTotal = 0; 487 cBetter = 0; 488 cWorse = 0; 489 cSame = 0; 490 cUncertain = 0; 491 rdPctTotal = 0.0; 492 493 iRow = oTable.aoRows.index(self.oStartRow); # ugly 494 while iRow < len(oTable.aoRows): 495 oRow = oTable.aoRows[iRow]; 496 if oRow is self: 497 break; 498 if isinstance(oRow, RunValueRow): 499 oValue = oRow.aoValues[iColumn]; 500 oBaseValue = oRow.getBaseValue(oTable); 501 if oValue is not None and oValue is not oBaseValue: 502 iBetter = oValue.getBetterRelation(); 503 if iBetter != 0: 504 lDiff = oValue.lValue - oBaseValue.lValue; 505 rdPct = abs(lDiff / float(oBaseValue.lValue) * 100); 506 if rdPct < oTable.rdPctSameValue: 507 cSame += 1; 508 else: 509 if lDiff > 0 if iBetter > 0 else lDiff < 0: 510 cBetter += 1; 511 rdPctTotal += rdPct; 512 else: 513 cWorse += 1; 514 rdPctTotal += -rdPct; 515 cUncertain += 1 if iBetter in (1, -1) else 0; 516 cTotal += 1; 517 iRow += 1; 518 519 # 520 # Format the result. 521 # 522 aoRet = []; 523 if not oTable.fBrief: 524 sText = u' \u2193%u' % (cWorse,); 525 sText = u' \u2248%u' % (cSame,) + alignTextRight(sText, 4); 526 sText = u'\u2191%u' % (cBetter,) + alignTextRight(sText, 8); 527 aoRet = [TextElement(sText),]; 528 529 if cSame >= cWorse and cSame >= cBetter: 530 sVerdict = 'same'; 531 elif cWorse >= cSame and cWorse >= cBetter: 532 sVerdict = 'worse'; 533 else: 534 sVerdict = 'better'; 535 if cUncertain > 0: 536 sVerdict = 'probably ' + sVerdict; 537 aoRet.append(TextElement(sVerdict)); 538 539 rdPctAvg = abs(rdPctTotal / cTotal); # Yes, average of the percentages! 540 aoRet.append(TextElement(self.formatPctAsText('+' if rdPctTotal >= 0 else '-', rdPctAvg, oTable.cPctPrecision))); 541 542 return aoRet; 543 544 380 545 class RunValueRow(RunRow): 381 546 """ … … 434 599 """ 435 600 436 def __init__(self, iBaseline = 0, fBrief = True, cPctPrecision = 2): # (int, bool, int) -> None 437 self.asColumns = [] # type: [str] ## Column names. 438 self.aoRows = [] # type: [RunRow] ## The table rows. 439 self.iBaseline = iBaseline # type: int ## Which column is the baseline when diffing things. 440 self.fBrief = fBrief # type: bool ## Whether to exclude the numerical values of non-baseline runs. 441 self.cPctPrecision = cPctPrecision # type: int ## Number of decimal points in diff percentage value. 442 601 def __init__(self, iBaseline = 0, fBrief = True, cPctPrecision = 2, rdPctSameValue = 0.10): # (int, bool, int, float) -> None 602 self.asColumns = [] # type: [str] ##< Column names. 603 self.aoRows = [] # type: [RunRow] ##< The table rows. 604 self.iBaseline = iBaseline # type: int ##< Which column is the baseline when diffing things. 605 self.fBrief = fBrief # type: bool ##< Whether to exclude the numerical values of non-baseline runs. 606 self.cPctPrecision = cPctPrecision # type: int ##< Number of decimal points in diff percentage value. 607 self.rdPctSameValue = rdPctSameValue # type: float ##< The percent value at which a value difference is considered 608 ## to be the same during value analysis. 443 609 def __populateFromValues(self, aaoValueRuns, iLevel): # type: ([reader.Value]) -> None 444 610 """ 445 611 Internal worker for __populateFromRuns() 446 612 447 This will modify the sub-lists inside aaoValueRuns, returning with the all empty. 613 This will modify the sub-lists inside aaoValueRuns, returning with them all empty. 614 615 Returns True if an value analysis row should be added, False if not. 448 616 """ 449 617 # Same as for __populateFromRuns, only no recursion. 618 fAnalysisRow = False; 450 619 for iValueRun, aoValuesForRun in enumerate(aaoValueRuns): 451 620 while aoValuesForRun: … … 462 631 if len(oRow.aoValues) <= iOtherRun: 463 632 oRow.aoValues.append(None); 464 return self; 633 634 fAnalysisRow = fAnalysisRow or oRow.oValue.canDoBetterCompare(); 635 return fAnalysisRow; 465 636 466 637 def __populateFromRuns(self, aaoTestRuns, iLevel): # type: ([reader.Test]) -> None … … 468 639 Internal worker for populateFromRuns() 469 640 470 This will modify the sub-lists inside aaoTestRuns, returning with the all empty.641 This will modify the sub-lists inside aaoTestRuns, returning with them all empty. 471 642 """ 472 643 … … 492 663 oStartRow.aoTests.append(None); 493 664 494 # Now rec rusively do the subtests for it and then do the values.665 # Now recursively do the subtests for it and then do the values. 495 666 self.__populateFromRuns( [list(oTest.aoChildren) if oTest else list() for oTest in oStartRow.aoTests], iLevel+1); 496 self.__populateFromValues([list(oTest.aoValues) if oTest else list() for oTest in oStartRow.aoTests], iLevel+1); 667 fValueAnalysisRow = self.__populateFromValues([list(oTest.aoValues) 668 if oTest else list() for oTest in oStartRow.aoTests], iLevel+1); 497 669 498 670 # Add the end-test row for it. 499 671 self.aoRows.append(RunTestEndRow(oStartRow)); 500 672 self.aoRows.append(RunTestEndRow2(oStartRow)); 673 if fValueAnalysisRow: 674 self.aoRows.append(RunTestValueAnalysisRow(oStartRow)); 501 675 502 676 return self; … … 523 697 self.asColumns.append('#%u%s' % (iCol, ' (baseline)' if iCol == self.iBaseline else '',)); 524 698 699 self.aoRows = [ 700 RunHeaderSeparatorRow(), 701 RunHeaderRow('Test / Value', self.asColumns), 702 RunHeaderSeparatorRow(), 703 ]; 704 525 705 # 526 706 # Now flatten the test trees into a table. 527 707 # 528 708 self.__populateFromRuns([[oTestRun,] for oTestRun in aoTestRuns], 0); 709 710 # 711 # Add a footer if there are a lot of rows. 712 # 713 if len(self.aoRows) - 2 > 40: 714 self.aoRows.extend([RunFooterSeparatorRow(), RunFooterRow('', self.asColumns),]); 715 529 716 return self; 530 717 … … 551 738 # Pass 2: Generate the output strings. 552 739 # 553 # Header 554 asRet = [ 555 alignTextCenter('Test / Value', oWidths.cchName) + ': ' 556 + ' | '.join([alignTextCenter(sText, oWidths.getColumnWidth(iCol)) for iCol, sText in enumerate(self.asColumns)]), 557 ]; 558 asRet.append('=' * len(asRet[0])); 559 560 # The table 740 asRet = []; 561 741 for oRow in self.aoRows: 562 742 if not oRow.fSkip: 563 sRow = oRow.formatNameAsText(oWidths.cchName); 564 sRow = sRow + ' ' * (oWidths.cchName - min(len(sRow), oWidths.cchName)) + ': '; 565 566 for iColumn in range(oRow.cColumns): 567 aoSubCols = oRow.formatColumnAsText(iColumn, self); 568 sCell = ''; 569 for iSub, oText in enumerate(aoSubCols): 570 cchWidth = oWidths.getColumnWidth(iColumn, len(aoSubCols), iSub); 571 if iSub > 0: 572 sCell += ' ' * oWidths.cchSubColSpacing; 573 sCell += alignText(oText.sText, cchWidth, oText.iAlign); 574 cchWidth = oWidths.getColumnWidth(iColumn); 575 sRow += (' | ' if iColumn > 0 else ' ') + ' ' * (cchWidth - min(cchWidth, len(sCell))) + sCell; 576 577 asRet.append(sRow); 578 579 # Footer? 580 if len(asRet) > 40: 581 asRet.append(asRet[1]); 582 asRet.append(asRet[0]); 743 asRet.append(oRow.renderAsText(oWidths, self)); 583 744 584 745 return asRet;
Note:
See TracChangeset
for help on using the changeset viewer.