VirtualBox

Changeset 97279 in vbox


Ignore:
Timestamp:
Oct 24, 2022 2:36:36 PM (2 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
154267
Message:

ValKit/analysis: Added some crude value result analysis into the mix. Improved output a little.

Location:
trunk/src/VBox/ValidationKit/analysis
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/analysis/analyze.py

    r97267 r97279  
    146146    print(oWrapper.fill('Removes any leaf tests that are without any values or sub-tests.  This is useful when '
    147147                        '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');
    148155
    149156    print('');
     
    307314    fBrief                  = True;
    308315    cPctPrecision           = 2;
     316    rdPctSameValue          = 0.1;
    309317    asTestFilters           = [];
    310318    asTestOutFilters        = [];
     
    362370            iBaseline = int(g_sOptArg);
    363371
     372        elif matchWithValue('--pct-same-value'):
     373            rdPctSameValue = float(g_sOptArg);
     374
    364375        # '--' starts a new collection.  If current one is empty, drop it.
    365376        elif sArg == '--':
     
    426437    # Produce the report.
    427438    #
    428     oTable = reporting.RunTable(iBaseline, fBrief, cPctPrecision);
     439    oTable = reporting.RunTable(iBaseline, fBrief, cPctPrecision, rdPctSameValue);
    429440    oTable.populateFromRuns([oCollection.oDistilled for oCollection in aoCollections],
    430441                            [oCollection.sName      for oCollection in aoCollections]);
  • trunk/src/VBox/ValidationKit/analysis/reader.py

    r97267 r97279  
    9797        "ns/roundtrip":     -2,
    9898        "ins":              +2,
    99         "ins/sec":          -1,
     99        "ins/sec":          +2,
    100100        "":                 +1, # Difficult to say what's best really.
    101101        "pp1k":             -2,
     
    127127        assert self.lValue is None or isinstance(self.lValue, (int, long)), "lValue=%s %s" % (self.lValue, type(self.lValue),);
    128128
    129         # Members set by processing.
    130         self.sDiff      = None;
    131 
    132129    def clone(self, oParentTest):
    133130        """
     
    150147        return False;
    151148
     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];
    152166
    153167    @staticmethod
     
    227241        self.sStatus        = None;
    228242        self.cErrors        = -1;
    229 
    230         # Members set by processing.
    231         self.sStatusDiff    = None;
    232         self.cErrorsDiff    = None;
    233243
    234244    def clone(self, oParent = None):
  • trunk/src/VBox/ValidationKit/analysis/reporting.py

    r97271 r97279  
    186186        self.iAlign = iAlign;
    187187
     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
    188192
    189193class RunRow(object):
     
    203207    # Format as Text:
    204208
    205     def formatNameAsText(self, cchWidth): # (int) -> str
     209    def formatNameAsText(self, cchWidth): # (int) -> TextElement
    206210        """ Format the row as text. """
    207211        _ = cchWidth;
    208         return  ' ' * (self.iLevel * 2) + self.sName;
     212        return TextElement(' ' * (self.iLevel * 2) + self.sName, g_kiAlignLeft);
    209213
    210214    def getColumnCountAsText(self, oTable):
     
    221225        return [ TextElement(),];
    222226
    223     def calcColumnWidthsForText(self, oTable):
     227    def calcColumnWidthsForText(self, oTable): # type: (RunTable) -> (bool, int, [])
    224228        """
    225229        Calculates the column widths for text rendering.
     
    237241            else:
    238242                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;
    240266
    241267    @staticmethod
     
    252278
    253279    @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
    254289    def formatDiffInPctAsText(lNumber, lBaseline, cPctPrecision):
    255290        """ Formats the difference between lNumber and lBaseline in precent as text. """
     
    265300                    lDiff  = -lDiff;
    266301                    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);
    284303        return '';
     304
     305
     306class 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
     319class RunFooterRow(RunHeaderRow):
     320    """
     321    Run table footer row.
     322    """
     323    def __init__(self, sName, asColumns):
     324        RunHeaderRow.__init__(self, sName, asColumns);
     325
     326
     327class 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
     342class 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
     354class RunFooterSeparatorRow(RunHeaderSeparatorRow):
     355    """
     356    Run table footer separator row.
     357    """
     358    def __init__(self):
     359        RunHeaderSeparatorRow.__init__(self);
    285360
    286361
     
    321396        RunTestRow.__init__(self, iLevel, oTest, iRun);
    322397
     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
    323406class RunTestEndRow(RunTestRow):
    324407    """
     
    355438    def formatNameAsText(self, cchWidth):
    356439        _ = cchWidth;
    357         return '';
     440        return TextElement('runtime', g_kiAlignRight);
    358441
    359442    def getColumnCountAsText(self, oTable):
     
    378461        return [ TextElement(), ];
    379462
     463
     464class 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
    380545class RunValueRow(RunRow):
    381546    """
     
    434599    """
    435600
    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.
    443609    def __populateFromValues(self, aaoValueRuns, iLevel): # type: ([reader.Value]) -> None
    444610        """
    445611        Internal worker for __populateFromRuns()
    446612
    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.
    448616        """
    449617        # Same as for __populateFromRuns, only no recursion.
     618        fAnalysisRow = False;
    450619        for iValueRun, aoValuesForRun in enumerate(aaoValueRuns):
    451620            while aoValuesForRun:
     
    462631                    if len(oRow.aoValues) <= iOtherRun:
    463632                        oRow.aoValues.append(None);
    464         return self;
     633
     634                fAnalysisRow = fAnalysisRow or oRow.oValue.canDoBetterCompare();
     635        return fAnalysisRow;
    465636
    466637    def __populateFromRuns(self, aaoTestRuns, iLevel): # type: ([reader.Test]) -> None
     
    468639        Internal worker for populateFromRuns()
    469640
    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.
    471642        """
    472643
     
    492663                        oStartRow.aoTests.append(None);
    493664
    494                 # Now recrusively do the subtests for it and then do the values.
     665                # Now recursively do the subtests for it and then do the values.
    495666                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);
    497669
    498670                # Add the end-test row for it.
    499671                self.aoRows.append(RunTestEndRow(oStartRow));
    500672                self.aoRows.append(RunTestEndRow2(oStartRow));
     673                if fValueAnalysisRow:
     674                    self.aoRows.append(RunTestValueAnalysisRow(oStartRow));
    501675
    502676        return self;
     
    523697            self.asColumns.append('#%u%s' % (iCol, ' (baseline)' if iCol == self.iBaseline else '',));
    524698
     699        self.aoRows = [
     700            RunHeaderSeparatorRow(),
     701            RunHeaderRow('Test / Value', self.asColumns),
     702            RunHeaderSeparatorRow(),
     703        ];
     704
    525705        #
    526706        # Now flatten the test trees into a table.
    527707        #
    528708        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
    529716        return self;
    530717
     
    551738        # Pass 2: Generate the output strings.
    552739        #
    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 = [];
    561741        for oRow in self.aoRows:
    562742            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));
    583744
    584745        return asRet;
Note: See TracChangeset for help on using the changeset viewer.

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