Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2017 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 | """Runs several telemetry benchmarks. |
| 7 | |
| 8 | This script attempts to emulate the contract of gtest-style tests |
| 9 | invoked via recipes. The main contract is that the caller passes the |
| 10 | argument: |
| 11 | |
| 12 | --isolated-script-test-output=[FILENAME] |
| 13 | |
| 14 | json is written to that file in the format detailed here: |
| 15 | https://www.chromium.org/developers/the-json-test-results-format |
| 16 | |
Kenneth Russell | 4027405 | 2017-11-14 00:57:44 | [diff] [blame] | 17 | Optional argument: |
| 18 | |
Kenneth Russell | a649a4612 | 2017-11-21 06:39:59 | [diff] [blame] | 19 | --isolated-script-test-filter=[TEST_NAMES] |
Kenneth Russell | 4027405 | 2017-11-14 00:57:44 | [diff] [blame] | 20 | |
Kenneth Russell | a649a4612 | 2017-11-21 06:39:59 | [diff] [blame] | 21 | is a double-colon-separated ("::") list of test names, to run just that subset |
| 22 | of tests. This list is forwarded to the run_telemetry_benchmark_as_googletest |
| 23 | script. |
Kenneth Russell | 4027405 | 2017-11-14 00:57:44 | [diff] [blame] | 24 | |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 25 | This script is intended to be the base command invoked by the isolate, |
| 26 | followed by a subsequent Python script. It could be generalized to |
| 27 | invoke an arbitrary executable. |
| 28 | |
| 29 | It currently runs several benchmarks. The benchmarks it will execute are |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 30 | based on the shard it is running on and the sharding_map_path. |
| 31 | |
| 32 | If this is executed with a non-telemetry perf test, the flag --non-telemetry |
| 33 | has to be passed in to the script so the script knows it is running |
| 34 | an executable and not the run_benchmark command. |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 35 | |
| 36 | The results of running the benchmark are put in separate directories per |
| 37 | benchmark. Two files will be present in each directory; perf_results.json, which |
| 38 | is the perf specific results (with unenforced format, could be histogram, |
| 39 | legacy, or chartjson), and test_results.json, which is a JSON test results |
| 40 | format file |
| 41 | (https://www.chromium.org/developers/the-json-test-results-format) |
| 42 | |
| 43 | This script was derived from run_telemetry_benchmark_as_googletest, and calls |
| 44 | into that script. |
| 45 | """ |
| 46 | |
| 47 | import argparse |
| 48 | import json |
| 49 | import os |
| 50 | import shutil |
| 51 | import sys |
Ned Nguyen | 972d234f | 2018-06-22 01:58:02 | [diff] [blame] | 52 | import time |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 53 | import tempfile |
| 54 | import traceback |
| 55 | |
| 56 | import common |
| 57 | |
| 58 | import run_telemetry_benchmark_as_googletest |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 59 | import run_gtest_perf_test |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 60 | |
| 61 | |
Emily Hanley | 6156dde | 2018-05-16 00:35:24 | [diff] [blame] | 62 | def get_sharding_map_path(args): |
| 63 | return os.path.join( |
Stephen Martinis | 6740ad0 | 2017-10-10 20:28:46 | [diff] [blame] | 64 | os.path.dirname(__file__), '..', '..', 'tools', 'perf', 'core', |
Emily Hanley | 6fff05f | 2018-06-04 17:03:50 | [diff] [blame] | 65 | 'shard_maps', args.test_shard_map_filename) |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 66 | |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 67 | def write_results( |
nednguyen | a8950e4 | 2018-05-18 22:42:06 | [diff] [blame] | 68 | perf_test_name, perf_results, json_test_results, benchmark_log, |
| 69 | isolated_out_dir, encoded): |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 70 | benchmark_path = os.path.join(isolated_out_dir, perf_test_name) |
| 71 | |
| 72 | os.makedirs(benchmark_path) |
| 73 | with open(os.path.join(benchmark_path, 'perf_results.json'), 'w') as f: |
| 74 | # non telemetry perf results are already json encoded |
| 75 | if encoded: |
| 76 | f.write(perf_results) |
| 77 | else: |
| 78 | json.dump(perf_results, f) |
| 79 | with open(os.path.join(benchmark_path, 'test_results.json'), 'w') as f: |
| 80 | json.dump(json_test_results, f) |
| 81 | |
nednguyen | a8950e4 | 2018-05-18 22:42:06 | [diff] [blame] | 82 | with open(os.path.join(benchmark_path, 'benchmark_log.txt'), 'w') as f: |
| 83 | f.write(benchmark_log) |
| 84 | |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 85 | |
Ned Nguyen | 972d234f | 2018-06-22 01:58:02 | [diff] [blame] | 86 | def print_duration(step, start): |
| 87 | print 'Duration of %s: %d seconds' % (step, time.time() - start) |
| 88 | |
| 89 | |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 90 | def execute_benchmark(benchmark, isolated_out_dir, |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 91 | args, rest_args, is_reference, stories=None): |
Ned Nguyen | 972d234f | 2018-06-22 01:58:02 | [diff] [blame] | 92 | start = time.time() |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 93 | # While we are between chartjson and histogram set we need |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 94 | # to determine which output format to look for or see if it was |
| 95 | # already passed in in which case that format applies to all benchmarks |
| 96 | # in this run. |
Ethan Kuefner | 4b0d7de | 2018-05-29 21:53:50 | [diff] [blame] | 97 | is_histograms = append_output_format(args, rest_args) |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 98 | # Insert benchmark name as first argument to run_benchmark call |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 99 | # which is the first argument in the rest_args. Also need to append |
Emily Hanley | 6156dde | 2018-05-16 00:35:24 | [diff] [blame] | 100 | # output format and smoke test mode. |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 101 | per_benchmark_args = (rest_args[:1] + [benchmark] + rest_args[1:]) |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 102 | benchmark_name = benchmark |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 103 | if is_reference: |
| 104 | # Need to parse out the browser to replace browser flag with |
| 105 | # reference build so we run it reference build as well |
| 106 | browser_index = 0 |
| 107 | for arg in per_benchmark_args: |
| 108 | if "browser" in arg: |
| 109 | break |
| 110 | browser_index = browser_index + 1 |
| 111 | per_benchmark_args[browser_index] = '--browser=reference' |
Emily Hanley | 84991a95 | 2018-02-22 18:12:14 | [diff] [blame] | 112 | # Now we need to add in the rest of the reference build args |
| 113 | per_benchmark_args.append('--max-failures=5') |
| 114 | per_benchmark_args.append('--output-trace-tag=_ref') |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 115 | benchmark_name = benchmark + '.reference' |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 116 | |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 117 | # If we are only running a subset of stories, add in the begin and end |
| 118 | # index. |
| 119 | if stories: |
| 120 | if 'begin' in stories.keys(): |
| 121 | per_benchmark_args.append( |
Ned Nguyen | 2ae5560 | 2018-07-25 18:41:08 | [diff] [blame] | 122 | ('--story-shard-begin-index=%d' % stories['begin'])) |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 123 | if 'end' in stories.keys(): |
| 124 | per_benchmark_args.append( |
Ned Nguyen | 2ae5560 | 2018-07-25 18:41:08 | [diff] [blame] | 125 | ('--story-shard-end-index=%d' % stories['end'])) |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 126 | |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 127 | # We don't care exactly what these are. In particular, the perf results |
| 128 | # could be any format (chartjson, legacy, histogram). We just pass these |
| 129 | # through, and expose these as results for this task. |
nednguyen | a8950e4 | 2018-05-18 22:42:06 | [diff] [blame] | 130 | rc, perf_results, json_test_results, benchmark_log = ( |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 131 | run_telemetry_benchmark_as_googletest.run_benchmark( |
| 132 | args, per_benchmark_args, is_histograms)) |
| 133 | |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 134 | write_results( |
nednguyen | a8950e4 | 2018-05-18 22:42:06 | [diff] [blame] | 135 | benchmark_name, perf_results, json_test_results, benchmark_log, |
| 136 | isolated_out_dir, False) |
Ned Nguyen | 972d234f | 2018-06-22 01:58:02 | [diff] [blame] | 137 | |
| 138 | print_duration('executing benchmark %s' % benchmark_name, start) |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 139 | return rc |
| 140 | |
| 141 | |
Ethan Kuefner | 4b0d7de | 2018-05-29 21:53:50 | [diff] [blame] | 142 | def append_output_format(args, rest_args): |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 143 | # We need to determine if the output format is already passed in |
| 144 | # or if we need to define it for this benchmark |
| 145 | perf_output_specified = False |
| 146 | is_histograms = False |
| 147 | if args.output_format: |
| 148 | for output_format in args.output_format: |
| 149 | if 'histograms' in output_format: |
| 150 | perf_output_specified = True |
| 151 | is_histograms = True |
| 152 | if 'chartjson' in output_format: |
| 153 | perf_output_specified = True |
| 154 | rest_args.append('--output-format=' + output_format) |
| 155 | # When crbug.com/744736 is resolved we no longer have to check |
| 156 | # the type of format per benchmark and can rely on it being passed |
| 157 | # in as an arg as all benchmarks will output the same format. |
| 158 | if not perf_output_specified: |
Ethan Kuefner | 4b0d7de | 2018-05-29 21:53:50 | [diff] [blame] | 159 | rest_args.append('--output-format=histograms') |
| 160 | is_histograms = True |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 161 | return is_histograms |
| 162 | |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 163 | def main(): |
| 164 | parser = argparse.ArgumentParser() |
| 165 | parser.add_argument( |
Stephen Martinis | 65e300c | 2017-10-10 00:20:34 | [diff] [blame] | 166 | '--isolated-script-test-output', required=True) |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 167 | # These two flags are passed in from the swarming recipe |
| 168 | # but will no longer be needed when we migrate to this new recipe. |
| 169 | # For now we need to recognize them so they don't get passed |
| 170 | # through to telemetry. |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 171 | parser.add_argument( |
| 172 | '--isolated-script-test-chartjson-output', required=False) |
| 173 | parser.add_argument( |
| 174 | '--isolated-script-test-perf-output', required=False) |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 175 | |
Kenneth Russell | 4027405 | 2017-11-14 00:57:44 | [diff] [blame] | 176 | parser.add_argument( |
Kenneth Russell | a649a4612 | 2017-11-21 06:39:59 | [diff] [blame] | 177 | '--isolated-script-test-filter', type=str, required=False) |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 178 | parser.add_argument('--xvfb', help='Start xvfb.', action='store_true') |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 179 | parser.add_argument('--non-telemetry', |
| 180 | help='Type of perf test', type=bool, default=False) |
Ned Nguyen | 3a1d09a1 | 2018-08-04 01:10:51 | [diff] [blame] | 181 | parser.add_argument('--gtest-benchmark-name', |
| 182 | help='Name of the gtest benchmark', type=str, |
| 183 | required=False) |
| 184 | |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 185 | parser.add_argument('--benchmarks', |
| 186 | help='Comma separated list of benchmark names' |
| 187 | ' to run in lieu of indexing into our benchmark bot maps', |
| 188 | required=False) |
Emily Hanley | 6156dde | 2018-05-16 00:35:24 | [diff] [blame] | 189 | # Some executions may have a different sharding scheme and/or set of tests. |
Emily Hanley | 6fff05f | 2018-06-04 17:03:50 | [diff] [blame] | 190 | # These files must live in src/tools/perf/core/shard_maps |
Emily Hanley | 6156dde | 2018-05-16 00:35:24 | [diff] [blame] | 191 | parser.add_argument('--test-shard-map-filename', type=str, required=False) |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 192 | parser.add_argument('--output-format', action='append') |
Emily Hanley | 18ae776 | 2018-05-14 17:07:58 | [diff] [blame] | 193 | parser.add_argument('--run-ref-build', |
| 194 | help='Run test on reference browser', action='store_true') |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 195 | |
| 196 | args, rest_args = parser.parse_known_args() |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 197 | isolated_out_dir = os.path.dirname(args.isolated_script_test_output) |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 198 | return_code = 0 |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 199 | |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 200 | if args.non_telemetry: |
Ned Nguyen | 3a1d09a1 | 2018-08-04 01:10:51 | [diff] [blame] | 201 | benchmark_name = args.gtest_benchmark_name |
| 202 | # Fallback to use the name of the executable if flag isn't set. |
| 203 | # TODO(crbug.com/870899): remove fallback logic and raise parser error if |
| 204 | # -non-telemetry is set but --gtest-benchmark-name is not set once pinpoint |
| 205 | # is converted to always pass --gtest-benchmark-name flag. |
| 206 | if not benchmark_name: |
| 207 | benchmark_name = rest_args[0] |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 208 | return_code, charts, output_json = run_gtest_perf_test.execute_perf_test( |
Ned Nguyen | 0933c72 | 2018-07-18 12:20:47 | [diff] [blame] | 209 | args, rest_args) |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 210 | |
nednguyen | a8950e4 | 2018-05-18 22:42:06 | [diff] [blame] | 211 | write_results(benchmark_name, charts, output_json, |
| 212 | benchmark_log='Not available for C++ perf test', |
| 213 | isolated_out_dir=isolated_out_dir, encoded=True) |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 214 | else: |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 215 | # If the user has supplied a list of benchmark names, execute those instead |
| 216 | # of the entire suite of benchmarks. |
| 217 | if args.benchmarks: |
Emily Hanley | 309422c | 2018-05-14 15:26:10 | [diff] [blame] | 218 | benchmarks = args.benchmarks.split(',') |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 219 | for benchmark in benchmarks: |
| 220 | return_code = (execute_benchmark( |
| 221 | benchmark, isolated_out_dir, args, rest_args, False) or return_code) |
| 222 | else: |
| 223 | # First determine what shard we are running on to know how to |
| 224 | # index into the bot map to get list of benchmarks to run. |
| 225 | total_shards = None |
| 226 | shard_index = None |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 227 | |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 228 | env = os.environ.copy() |
| 229 | if 'GTEST_TOTAL_SHARDS' in env: |
| 230 | total_shards = env['GTEST_TOTAL_SHARDS'] |
| 231 | if 'GTEST_SHARD_INDEX' in env: |
| 232 | shard_index = env['GTEST_SHARD_INDEX'] |
Emily Hanley | 69ccba2 | 2018-02-09 20:15:25 | [diff] [blame] | 233 | |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 234 | if not (total_shards or shard_index): |
| 235 | raise Exception('Shard indicators must be present for perf tests') |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 236 | |
Emily Hanley | 6156dde | 2018-05-16 00:35:24 | [diff] [blame] | 237 | sharding_map_path = get_sharding_map_path(args) |
Ned Nguyen | 372312b | 2018-05-30 20:43:11 | [diff] [blame] | 238 | |
| 239 | # Copy sharding map file to isolated_out_dir so that the collect script |
| 240 | # can collect it later. |
| 241 | shutil.copyfile( |
| 242 | sharding_map_path, |
| 243 | os.path.join(isolated_out_dir, 'benchmarks_shard_map.json')) |
| 244 | |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 245 | with open(sharding_map_path) as f: |
| 246 | sharding_map = json.load(f) |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 247 | sharding = sharding_map[shard_index]['benchmarks'] |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 248 | |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 249 | for benchmark, stories in sharding.iteritems(): |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 250 | # Need to run the benchmark twice on browser and reference build |
| 251 | return_code = (execute_benchmark( |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 252 | benchmark, isolated_out_dir, args, rest_args, |
| 253 | False, stories=stories) or return_code) |
Emily Hanley | 8a0edf5 | 2018-04-28 01:12:58 | [diff] [blame] | 254 | # We ignore the return code of the reference build since we do not |
| 255 | # monitor it. |
Emily Hanley | 18ae776 | 2018-05-14 17:07:58 | [diff] [blame] | 256 | if args.run_ref_build: |
Emily Hanley | fdff4bbd | 2018-05-25 18:13:58 | [diff] [blame] | 257 | execute_benchmark(benchmark, isolated_out_dir, args, rest_args, True, |
| 258 | stories=stories) |
Emily Hanley | 7282b54 | 2018-03-02 20:42:49 | [diff] [blame] | 259 | |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 260 | return return_code |
| 261 | |
Emily Hanley | 9f06503b | 2018-05-03 18:46:43 | [diff] [blame] | 262 | |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 263 | # This is not really a "script test" so does not need to manually add |
| 264 | # any additional compile targets. |
| 265 | def main_compile_targets(args): |
Stephen Martinis | 782c13f | 2017-09-29 02:30:14 | [diff] [blame] | 266 | json.dump([], args.output) |
Stephen Martinis | 0b18efa10 | 2017-09-25 22:55:12 | [diff] [blame] | 267 | |
| 268 | |
| 269 | if __name__ == '__main__': |
| 270 | # Conform minimally to the protocol defined by ScriptTest. |
| 271 | if 'compile_targets' in sys.argv: |
| 272 | funcs = { |
| 273 | 'run': None, |
| 274 | 'compile_targets': main_compile_targets, |
| 275 | } |
| 276 | sys.exit(common.run_script(sys.argv[1:], funcs)) |
| 277 | sys.exit(main()) |