VirtualBox

Ignore:
Timestamp:
Oct 24, 2022 12:09:44 AM (2 years ago)
Author:
vboxsync
Message:

ValKit/analysis: Adding better filtering and some documentation (--help) for the analyze tool. Some improvements for --option[:=]value parsing too.

File:
1 edited

Legend:

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

    r97266 r97267  
    4040__version__ = "$Revision$"
    4141
    42 
    43 import os.path
    44 import sys
     42# Standard python imports.
     43import re;
     44import os;
     45import textwrap;
     46import sys;
    4547
    4648# Only the main script needs to modify the path.
     
    5153
    5254# Validation Kit imports.
    53 from analysis import reader    ## @todo fix testanalysis/__init__.py.
     55from analysis import reader
    5456from analysis import reporting
    55 #from analysis import diff
    5657
    5758
    5859def usage():
    59     """ Display usage """
    60     print('usage: %s [options] <test1.xml/txt> <test2.xml/txt> [..] [-- <baseline1.file> [baseline2.file] [..]]'
    61           % (sys.argv[0]));
    62     print('')
    63     print('options:')
    64     print('  --filter <test-sub-string>')
     60    """
     61    Display usage.
     62    """
     63    # Set up the output wrapper.
     64    try:    cCols = os.get_terminal_size()[0] # since 3.3
     65    except: cCols = 79;
     66    oWrapper = textwrap.TextWrapper(width = cCols);
     67
     68    # Do the outputting.
     69    print('Tool for comparing test results.');
     70    print('');
     71    oWrapper.subsequent_indent = ' ' * (len('usage: ') + 4);
     72    print(oWrapper.fill('usage: analyze.py [options] [collection-1] -- [collection-2] [-- [collection3] [..]])'))
     73    oWrapper.subsequent_indent = '';
     74    print('');
     75    print(oWrapper.fill('This tool compares two or more result collections, using one as a baseline (first by default) '
     76                        'and showing how the results in others differs from it.'));
     77    print('');
     78    print(oWrapper.fill('The results (XML file) from one or more test runs makes up a collection.  A collection can be '
     79                        'named using the --name <name> option, or will get a sequential name automatically.  The baseline '
     80                        'collection will have "(baseline)" appended to its name.'));
     81    print('');
     82    print(oWrapper.fill('A test run produces one XML file, either via the testdriver/reporter.py machinery or via the IPRT '
     83                        'test.cpp code. In the latter case it can be enabled and controlled via IPRT_TEST_FILE.  A collection '
     84                        'consists of one or more of test runs (i.e. XML result files).  These are combined (aka distilled) '
     85                        'into a single set of results before comparing them with the others.  The --best and --avg options '
     86                        'controls how this combining is done.  The need for this is mainly to try counteract some of the '
     87                        'instability typically found in the restuls.  Just because one test run produces a better result '
     88                        'after a change does not necessarily mean this will always be the case and that the change was to '
     89                        'the better, it might just have been regular fluctuations in the test results.'));
     90
     91    oWrapper.initial_indent    = '      ';
     92    oWrapper.subsequent_indent = '      ';
     93    print('');
     94    print('Options governing combining (distillation):');
     95    print('  --avg, --average');
     96    print(oWrapper.fill('Picks the best result by calculating the average values across all the runs.'));
     97    print('');
     98    print('  --best');
     99    print(oWrapper.fill('Picks the best result from all the runs.  For values, this means making guessing what result is '
     100                        'better based on the unit.  This may not always lead to the right choices.'));
     101    print(oWrapper.initial_indent + 'Default: --best');
     102
     103    print('');
     104    print('Options relating to collections:');
     105    print('  --name <name>');
     106    print(oWrapper.fill('Sets the name of the current collection.  By default a collection gets a sequential number.'));
     107    print('');
     108    print('  --baseline <num>');
     109    print(oWrapper.fill('Sets collection given by <num> (0-based) as the baseline collection.'));
     110    print(oWrapper.initial_indent + 'Default: --baseline 0')
     111
     112    print('');
     113    print('Filtering options:');
     114    print('  --filter-test <substring>');
     115    print(oWrapper.fill('Exclude tests not containing any of the substrings given via the --filter-test option.  The '
     116                        'matching is done with full test name, i.e. all parent names are prepended with ", " as separator '
     117                        '(for example "tstIOInstr, CPUID EAX=1").'));
     118    print('');
     119    print('  --filter-test-out <substring>');
     120    print(oWrapper.fill('Exclude tests containing the given substring.  As with --filter-test, the matching is done against '
     121                        'the full test name.'));
     122    print('');
     123    print('  --filter-value <substring>');
     124    print(oWrapper.fill('Exclude values not containing any of the substrings given via the --filter-value option.  The '
     125                        'matching is done against the value name prefixed by the full test name and ": " '
     126                        '(for example "tstIOInstr, CPUID EAX=1: real mode, CPUID").'));
     127    print('');
     128    print('  --filter-value-out <substring>');
     129    print(oWrapper.fill('Exclude value containing the given substring.  As with --filter-value, the matching is done against '
     130                        'the value name prefixed by the full test name.'));
     131
     132    print('');
     133    print('  --regex-test <expr>');
     134    print(oWrapper.fill('Same as --filter-test except the substring matching is done via a regular expression.'));
     135    print('');
     136    print('  --regex-test-out <expr>');
     137    print(oWrapper.fill('Same as --filter-test-out except the substring matching is done via a regular expression.'));
     138    print('');
     139    print('  --regex-value <expr>');
     140    print(oWrapper.fill('Same as --filter-value except the substring matching is done via a regular expression.'));
     141    print('');
     142    print('  --regex-value-out <expr>');
     143    print(oWrapper.fill('Same as --filter-value-out except the substring matching is done via a regular expression.'));
     144    print('');
     145    print('  --filter-out-empty-leaf-tests');
     146    print(oWrapper.fill('Removes any leaf tests that are without any values or sub-tests.  This is useful when '
     147                        'only considering values, especially when doing additional value filtering.'));
     148
     149    print('');
     150    print('Output options:');
     151    print('  --brief, --verbose');
     152    print(oWrapper.fill('Whether to omit (--brief) the value for non-baseline runs and just get along with the difference.'));
     153    print(oWrapper.initial_indent + 'Default: --brief');
     154    print('');
     155    print('  --pct <num>, --pct-precision <num>');
     156    print(oWrapper.fill('Specifies the number of decimal place to use when formatting the difference as percent.'));
     157    print(oWrapper.initial_indent + 'Default: --pct 2');
    65158    return 1;
     159
    66160
    67161class ResultCollection(object):
     
    94188    def filterTests(self, asFilters):
    95189        """
    96         Filters all the test trees using asFilters.
     190        Keeps all the tests in the test trees sub-string matching asFilters (str or re).
    97191        """
    98192        for oTestTree in self.aoTestTrees:
    99193            oTestTree.filterTests(asFilters);
     194        return self;
     195
     196    def filterOutTests(self, asFilters):
     197        """
     198        Removes all the tests in the test trees sub-string matching asFilters (str or re).
     199        """
     200        for oTestTree in self.aoTestTrees:
     201            oTestTree.filterOutTests(asFilters);
     202        return self;
     203
     204    def filterValues(self, asFilters):
     205        """
     206        Keeps all the tests in the test trees sub-string matching asFilters (str or re).
     207        """
     208        for oTestTree in self.aoTestTrees:
     209            oTestTree.filterValues(asFilters);
     210        return self;
     211
     212    def filterOutValues(self, asFilters):
     213        """
     214        Removes all the tests in the test trees sub-string matching asFilters (str or re).
     215        """
     216        for oTestTree in self.aoTestTrees:
     217            oTestTree.filterOutValues(asFilters);
     218        return self;
     219
     220    def filterOutEmptyLeafTests(self):
     221        """
     222        Removes all the tests in the test trees that have neither child tests nor values.
     223        """
     224        for oTestTree in self.aoTestTrees:
     225            oTestTree.filterOutEmptyLeafTests();
    100226        return self;
    101227
     
    145271
    146272
     273# matchWithValue hacks.
     274g_asOptions = [];
     275g_iOptInd   = 1;
     276g_sOptArg   = '';
     277
     278def matchWithValue(sOption):
     279    """ Matches an option with a value, placing the value in g_sOptArg if it matches. """
     280    global g_asOptions, g_iOptInd, g_sOptArg;
     281    sArg = g_asOptions[g_iOptInd];
     282    if sArg.startswith(sOption):
     283        if len(sArg) == len(sOption):
     284            if g_iOptInd + 1 < len(g_asOptions):
     285                g_iOptInd += 1;
     286                g_sOptArg  = g_asOptions[g_iOptInd];
     287                return True;
     288
     289            print('syntax error: Option %s takes a value!' % (sOption,));
     290            raise Exception('syntax error: Option %s takes a value!' % (sOption,));
     291
     292        if sArg[len(sOption)] in ('=', ':'):
     293            g_sOptArg = sArg[len(sOption) + 1:];
     294            return True;
     295    return False;
    147296
    148297
     
    152301    # Parse arguments
    153302    #
    154     oCurCollection      = ResultCollection('#0');
    155     aoCollections       = [ oCurCollection, ];
    156     iBaseline           = 0;
    157     sDistillationMethod = 'best';
    158     fBrief              = True;
    159     cPctPrecision       = 2;
    160     asFilters           = [];
    161 
    162     iArg = 1;
    163     while iArg < len(asArgs):
    164         #print("dbg: iArg=%s '%s'" % (iArg, asArgs[iArg],));
    165         if asArgs[iArg].startswith('--help'):
     303    oCurCollection          = ResultCollection('#0');
     304    aoCollections           = [ oCurCollection, ];
     305    iBaseline               = 0;
     306    sDistillationMethod     = 'best';
     307    fBrief                  = True;
     308    cPctPrecision           = 2;
     309    asTestFilters           = [];
     310    asTestOutFilters        = [];
     311    asValueFilters          = [];
     312    asValueOutFilters       = [];
     313    fFilterOutEmptyLeafTest = True;
     314
     315    global g_asOptions, g_iOptInd, g_sOptArg;
     316    g_asOptions = asArgs;
     317    g_iOptInd   = 1;
     318    while g_iOptInd < len(asArgs):
     319        sArg      = asArgs[g_iOptInd];
     320        g_sOptArg = '';
     321        #print("dbg: g_iOptInd=%s '%s'" % (g_iOptInd, sArg,));
     322
     323        if sArg.startswith('--help'):
    166324            return usage();
    167         if asArgs[iArg] == '--filter':
    168             iArg += 1;
    169             asFilters.append(asArgs[iArg]);
    170         elif asArgs[iArg] == '--best':
     325
     326        if matchWithValue('--filter-test'):
     327            asTestFilters.append(g_sOptArg);
     328        elif matchWithValue('--filter-test-out'):
     329            asTestOutFilters.append(g_sOptArg);
     330        elif matchWithValue('--filter-value'):
     331            asValueFilters.append(g_sOptArg);
     332        elif matchWithValue('--filter-value-out'):
     333            asValueOutFilters.append(g_sOptArg);
     334
     335        elif matchWithValue('--regex-test'):
     336            asTestFilters.append(re.compile(g_sOptArg));
     337        elif matchWithValue('--regex-test-out'):
     338            asTestOutFilters.append(re.compile(g_sOptArg));
     339        elif matchWithValue('--regex-value'):
     340            asValueFilters.append(re.compile(g_sOptArg));
     341        elif matchWithValue('--regex-value-out'):
     342            asValueOutFilters.append(re.compile(g_sOptArg));
     343
     344        elif sArg == '--filter-out-empty-leaf-tests':
     345            fFilterOutEmptyLeafTest = True;
     346        elif sArg == '--no-filter-out-empty-leaf-tests':
     347            fFilterOutEmptyLeafTest = False;
     348
     349        elif sArg == '--best':
    171350            sDistillationMethod = 'best';
    172         elif asArgs[iArg] in ('--avg', '--average'):
     351        elif sArg in ('--avg', '--average'):
    173352            sDistillationMethod = 'avg';
    174         elif asArgs[iArg] == '--brief':
     353
     354        elif sArg == '--brief':
    175355            fBrief = True;
    176         elif asArgs[iArg] == '--verbose':
     356        elif sArg == '--verbose':
    177357            fBrief = False;
    178         elif asArgs[iArg] in ('--pct', '--pct-precision'):
    179             iArg += 1;
    180             cPctPrecision = int(asArgs[iArg]);
    181         elif asArgs[iArg] in ('--base', '--baseline'):
    182             iArg += 1;
    183             iBaseline = int(asArgs[iArg]);
     358
     359        elif matchWithValue('--pct') or matchWithValue('--pct-precision'):
     360            cPctPrecision = int(g_sOptArg);
     361        elif matchWithValue('--base') or matchWithValue('--baseline'):
     362            iBaseline = int(g_sOptArg);
     363
    184364        # '--' starts a new collection.  If current one is empty, drop it.
    185         elif asArgs[iArg] == '--':
     365        elif sArg == '--':
    186366            print("dbg: new collection");
    187367            #if oCurCollection.isEmpty():
     
    189369            oCurCollection = ResultCollection("#%s" % (len(aoCollections),));
    190370            aoCollections.append(oCurCollection);
     371
    191372        # Name the current result collection.
    192         elif asArgs[iArg] == '--name':
    193             iArg += 1;
    194             oCurCollection.sName = asArgs[iArg];
     373        elif matchWithValue('--name'):
     374            oCurCollection.sName = g_sOptArg;
     375
    195376        # Read in a file and add it to the current data set.
    196377        else:
    197             if not oCurCollection.append(asArgs[iArg]):
     378            if not oCurCollection.append(sArg):
    198379                return 1;
    199         iArg += 1;
     380        g_iOptInd += 1;
    200381
    201382    #
     
    218399
    219400    #
    220     # Apply filtering before distilling each collection into a single result
    221     # tree and comparing them to the first one.
    222     #
    223     if asFilters:
     401    # Apply filtering before distilling each collection into a single result tree.
     402    #
     403    if asTestFilters:
    224404        for oCollection in aoCollections:
    225             oCollection.filterTests(asFilters);
    226 
     405            oCollection.filterTests(asTestFilters);
     406    if asTestOutFilters:
     407        for oCollection in aoCollections:
     408            oCollection.filterOutTests(asTestOutFilters);
     409
     410    if asValueFilters:
     411        for oCollection in aoCollections:
     412            oCollection.filterValues(asValueFilters);
     413    if asValueOutFilters:
     414        for oCollection in aoCollections:
     415            oCollection.filterOutValues(asValueOutFilters);
     416
     417    if fFilterOutEmptyLeafTest:
     418        for oCollection in aoCollections:
     419            oCollection.filterOutEmptyLeafTests();
     420
     421    # Distillation.
    227422    for oCollection in aoCollections:
    228423        oCollection.distill(sDistillationMethod);
Note: See TracChangeset for help on using the changeset viewer.

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