[email protected] | 5bb2512e | 2013-03-20 21:00:46 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Prepare tests that require re-baselining for input to make_expectations.py. |
| 7 | |
| 8 | The regularly running perf-AV tests require re-baselineing of expectations |
| 9 | about once a week. The steps involved in rebaselining are: |
| 10 | |
| 11 | 1.) Identify the tests to update, based off reported e-mail results. |
| 12 | 2.) Figure out reva and revb values, which is the starting and ending revision |
| 13 | numbers for the range that we should use to obtain new thresholds. |
| 14 | 3.) Modify lines in perf_expectations.json referring to the tests to be updated, |
| 15 | so that they may be used as input to make_expectations.py. |
| 16 | |
| 17 | This script automates the last step above. |
| 18 | |
| 19 | Here's a sample line from perf_expectations.json: |
| 20 | |
| 21 | "win-release/media_tests_av_perf/fps/tulip2.m4a": {"reva": 163299, \ |
| 22 | "revb": 164141, "type": "absolute", "better": "higher", "improve": 0, \ |
| 23 | "regress": 0, "sha1": "54d94538"}, |
| 24 | |
| 25 | To get the above test ready for input to make_expectations.py, it should become: |
| 26 | |
| 27 | "win-release/media_tests_av_perf/fps/tulip2.m4a": {"reva": <new reva>, \ |
| 28 | "revb": <new revb>, "type": "absolute", "better": "higher", "improve": 0, \ |
| 29 | "regress": 0}, |
| 30 | |
| 31 | Examples: |
| 32 | |
| 33 | 1.) To update the test specified above and get baseline |
| 34 | values using the revision range 12345 and 23456, run this script with a command |
| 35 | line like this: |
| 36 | python update_perf_expectations.py -f \ |
| 37 | win-release/media_tests_av_perf/fps/tulip2.m4a --reva 12345 --revb 23456 |
| 38 | Or, using an input file, |
| 39 | where the input file contains a single line with text |
| 40 | win-release/media_tests_av_perf/fps/tulip2.m4a |
| 41 | run with this command line: |
| 42 | python update_perf_expectations.py -i input.txt --reva 12345 --revb 23456 |
| 43 | |
| 44 | 2.) Let's say you want to update all seek tests on windows, and get baseline |
| 45 | values using the revision range 12345 and 23456. |
| 46 | Run this script with this command line: |
| 47 | python update_perf_expectations.py -f win-release/media_tests_av_perf/seek/ \ |
| 48 | --reva 12345 --revb 23456 |
| 49 | Or: |
| 50 | python update_perf_expectations.py -f win-release/.*/seek/ --reva 12345 \ |
| 51 | --revb 23456 |
| 52 | |
| 53 | Or, using an input file, |
| 54 | where the input file contains a single line with text win-release/.*/seek/: |
| 55 | python update_perf_expectations.py -i input.txt --reva 12345 --revb 23456 |
| 56 | |
| 57 | 3.) Similarly, if you want to update seek tests on all platforms |
| 58 | python update_perf_expectations.py -f .*-release/.*/seek/ --reva 12345 \ |
| 59 | --revb 23456 |
| 60 | |
| 61 | """ |
| 62 | |
| 63 | import logging |
| 64 | from optparse import OptionParser |
| 65 | import os |
| 66 | import re |
| 67 | |
| 68 | import make_expectations as perf_ex_lib |
| 69 | |
| 70 | # Default logging is INFO. Use --verbose to enable DEBUG logging. |
| 71 | _DEFAULT_LOG_LEVEL = logging.INFO |
| 72 | |
| 73 | |
| 74 | def GetTestsToUpdate(contents, all_test_keys): |
| 75 | """Parses input contents and obtains tests to be re-baselined. |
| 76 | |
| 77 | Args: |
| 78 | contents: string containing contents of input file. |
| 79 | all_test_keys: list of keys of test dictionary. |
| 80 | Returns: |
| 81 | A list of keys for tests that should be updated. |
| 82 | """ |
| 83 | # Each line of the input file specifies a test case to update. |
| 84 | tests_list = [] |
| 85 | for test_case_filter in contents.splitlines(): |
| 86 | # Skip any empty lines. |
| 87 | if test_case_filter: |
| 88 | # Sample expected line: |
| 89 | # win-release/media_tests_av_perf/seek/\ |
| 90 | # CACHED_BUFFERED_SEEK_NoConstraints_crowd1080.ogv |
| 91 | # Or, if reg-ex, then sample line: |
| 92 | # win-release/media-tests_av_perf/seek* |
| 93 | # Skip any leading spaces if they exist in the input file. |
| 94 | logging.debug('Trying to match %s', test_case_filter) |
| 95 | tests_list.extend(GetMatchingTests(test_case_filter.strip(), |
| 96 | all_test_keys)) |
| 97 | return tests_list |
| 98 | |
| 99 | |
| 100 | def GetMatchingTests(tests_to_update, all_test_keys): |
| 101 | """Parses input reg-ex filter and obtains tests to be re-baselined. |
| 102 | |
| 103 | Args: |
| 104 | tests_to_update: reg-ex string specifying tests to be updated. |
| 105 | all_test_keys: list of keys of tests dictionary. |
| 106 | Returns: |
| 107 | A list of keys for tests that should be updated. |
| 108 | """ |
| 109 | tests_list = [] |
| 110 | search_string = re.compile(tests_to_update) |
| 111 | # Get matching tests from the dictionary of tests |
| 112 | for test_key in all_test_keys: |
| 113 | if search_string.match(test_key): |
| 114 | tests_list.append(test_key) |
| 115 | logging.debug('%s will be updated', test_key) |
| 116 | logging.info('%s tests found matching reg-ex: %s', len(tests_list), |
| 117 | tests_to_update) |
| 118 | return tests_list |
| 119 | |
| 120 | |
| 121 | def PrepareTestsForUpdate(tests_to_update, all_tests, reva, revb): |
| 122 | """Modifies value of tests that are to re-baselined: |
| 123 | Set reva and revb values to specified new values. Remove sha1. |
| 124 | |
| 125 | Args: |
| 126 | tests_to_update: list of tests to be updated. |
| 127 | all_tests: dictionary of all tests. |
| 128 | reva: oldest revision in range to use for new values. |
| 129 | revb: newest revision in range to use for new values. |
| 130 | Raises: |
| 131 | ValueError: If reva or revb are not valid ints, or if either |
| 132 | of them are negative. |
| 133 | """ |
| 134 | reva = int(reva) |
| 135 | revb = int(revb) |
| 136 | |
| 137 | if reva < 0 or revb < 0: |
| 138 | raise ValueError('Revision values should be positive.') |
| 139 | # Ensure reva is less than revb. |
| 140 | # (this is similar to the check done in make_expectations.py) |
| 141 | if revb < reva: |
| 142 | temp = revb |
| 143 | revb = reva |
| 144 | reva = temp |
| 145 | for test_key in tests_to_update: |
| 146 | # Get original test from the dictionary of tests |
| 147 | test_value = all_tests[test_key] |
| 148 | if test_value: |
| 149 | # Sample line in perf_expectations.json: |
| 150 | # "linux-release/media_tests _av_perf/dropped_frames/crowd360.webm":\ |
| 151 | # {"reva": 155180, "revb": 155280, "type": "absolute", \ |
| 152 | # "better": "lower", "improve": 0, "regress": 3, "sha1": "276ba29c"}, |
| 153 | # Set new revision range |
| 154 | test_value['reva'] = reva |
| 155 | test_value['revb'] = revb |
| 156 | # Remove sha1 to indicate this test requires an update |
| 157 | # Check first to make sure it exist. |
| 158 | if 'sha1' in test_value: |
| 159 | del test_value['sha1'] |
| 160 | else: |
| 161 | logging.warning('%s does not exist.', test_key) |
| 162 | logging.info('Done preparing tests for update.') |
| 163 | |
| 164 | |
| 165 | def GetCommandLineOptions(): |
| 166 | """Parse command line arguments. |
| 167 | |
| 168 | Returns: |
| 169 | An options object containing command line arguments and their values. |
| 170 | """ |
| 171 | parser = OptionParser() |
| 172 | |
| 173 | parser.add_option('--reva', dest='reva', type='int', |
| 174 | help='Starting revision of new range.', |
| 175 | metavar='START_REVISION') |
| 176 | parser.add_option('--revb', dest='revb', type='int', |
| 177 | help='Ending revision of new range.', |
| 178 | metavar='END_REVISION') |
| 179 | parser.add_option('-f', dest='tests_filter', |
| 180 | help='Regex to use for filtering tests to be updated. ' |
| 181 | 'At least one of -filter or -input_file must be provided. ' |
| 182 | 'If both are provided, then input-file is used.', |
| 183 | metavar='FILTER', default='') |
| 184 | parser.add_option('-i', dest='input_file', |
| 185 | help='Optional path to file with reg-exes for tests to' |
| 186 | ' update. If provided, it overrides the filter argument.', |
| 187 | metavar='INPUT_FILE', default='') |
| 188 | parser.add_option('--config', dest='config_file', |
| 189 | default=perf_ex_lib.DEFAULT_CONFIG_FILE, |
| 190 | help='Set the config file to FILE.', metavar='FILE') |
| 191 | parser.add_option('-v', dest='verbose', action='store_true', default=False, |
| 192 | help='Enable verbose output.') |
| 193 | options = parser.parse_args()[0] |
| 194 | return options |
| 195 | |
| 196 | |
| 197 | def Main(): |
| 198 | """Main driver function.""" |
| 199 | options = GetCommandLineOptions() |
| 200 | |
| 201 | _SetLogger(options.verbose) |
| 202 | # Do some command-line validation |
| 203 | if not options.input_file and not options.tests_filter: |
| 204 | logging.error('At least one of input-file or test-filter must be provided.') |
| 205 | exit(1) |
| 206 | if options.input_file and options.tests_filter: |
| 207 | logging.error('Specify only one of input file or test-filter.') |
| 208 | exit(1) |
| 209 | if not options.reva or not options.revb: |
| 210 | logging.error('Start and end revision of range must be specified.') |
| 211 | exit(1) |
| 212 | |
| 213 | # Load config. |
| 214 | config = perf_ex_lib.ConvertJsonIntoDict( |
| 215 | perf_ex_lib.ReadFile(options.config_file)) |
| 216 | |
| 217 | # Obtain the perf expectations file from the config file. |
| 218 | perf_file = os.path.join( |
| 219 | os.path.dirname(options.config_file), config['perf_file']) |
| 220 | |
| 221 | # We should have all the information we require now. |
| 222 | # On to the real thang. |
| 223 | # First, get all the existing tests from the original perf_expectations file. |
| 224 | all_tests = perf_ex_lib.ConvertJsonIntoDict( |
| 225 | perf_ex_lib.ReadFile(perf_file)) |
| 226 | all_test_keys = all_tests.keys() |
| 227 | # Remove the load key, because we don't want to modify it. |
| 228 | all_test_keys.remove('load') |
| 229 | # Keep tests sorted, like in the original file. |
| 230 | all_test_keys.sort() |
| 231 | |
| 232 | # Next, get all tests that have been identified for an update. |
| 233 | tests_to_update = [] |
| 234 | if options.input_file: |
| 235 | # Tests to update have been specified in an input_file. |
| 236 | # Get contents of file. |
| 237 | tests_filter = perf_ex_lib.ReadFile(options.input_file) |
| 238 | elif options.tests_filter: |
| 239 | # Tests to update have been specified as a reg-ex filter. |
| 240 | tests_filter = options.tests_filter |
| 241 | |
| 242 | # Get tests to update based on filter specified. |
| 243 | tests_to_update = GetTestsToUpdate(tests_filter, all_test_keys) |
| 244 | logging.info('Done obtaining matching tests.') |
| 245 | |
| 246 | # Now, prepare tests for update. |
| 247 | PrepareTestsForUpdate(tests_to_update, all_tests, options.reva, options.revb) |
| 248 | |
| 249 | # Finally, write modified tests back to perf_expectations file. |
| 250 | perf_ex_lib.WriteJson(perf_file, all_tests, all_test_keys, |
| 251 | calculate_sha1=False) |
| 252 | logging.info('Done writing tests for update to %s.', perf_file) |
| 253 | |
| 254 | |
| 255 | def _SetLogger(verbose): |
| 256 | log_level = _DEFAULT_LOG_LEVEL |
| 257 | if verbose: |
| 258 | log_level = logging.DEBUG |
| 259 | logging.basicConfig(level=log_level, format='%(message)s') |
| 260 | |
| 261 | |
| 262 | if __name__ == '__main__': |
| 263 | Main() |