| #!/usr/bin/env python3 | 
 | # Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file | 
 | # for details. All rights reserved. Use of this source code is governed by a | 
 | # BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | import argparse | 
 | import compiledump | 
 | import json | 
 | import os | 
 | import shutil | 
 | import subprocess | 
 | import sys | 
 |  | 
 | import utils | 
 | if utils.is_bot(): | 
 |     import upload_benchmark_data_to_google_storage | 
 |  | 
 | # A collection of benchmarks that should be run on the perf bot. | 
 | EXTERNAL_BENCHMARKS = { | 
 |     'ChromeApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'CraneApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'HelloWorld': { | 
 |         'targets': ['d8'] | 
 |     }, | 
 |     'HelloWorldNoLib': { | 
 |         'targets': ['d8'] | 
 |     }, | 
 |     'HelloWorldCf': { | 
 |         'targets': ['d8'] | 
 |     }, | 
 |     'HelloWorldCfNoLib': { | 
 |         'targets': ['d8'] | 
 |     }, | 
 |     'JetLaggedApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'JetNewsApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'JetCasterApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'JetChatApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'JetSnackApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'NowInAndroidApp': { | 
 |         'targets': ['d8', 'r8-full'] | 
 |     }, | 
 |     'NowInAndroidAppIncremental': { | 
 |         'targets': ['d8'], | 
 |         'subBenchmarks': { | 
 |             'd8': ['Dex', 'Merge'] | 
 |         } | 
 |     }, | 
 |     'NowInAndroidAppNoJ$': { | 
 |         'targets': ['d8'] | 
 |     }, | 
 |     'NowInAndroidAppNoJ$Incremental': { | 
 |         'targets': ['d8'], | 
 |         'subBenchmarks': { | 
 |             'd8': ['Dex'] | 
 |         } | 
 |     }, | 
 |     'OwlApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'R8': { | 
 |         'targets': ['retrace'] | 
 |     }, | 
 |     'ReplyApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 |     'TiviApp': { | 
 |         'targets': ['r8-full'] | 
 |     }, | 
 | } | 
 | # A collection of internal benchmarks that should be run on the internal bot. | 
 | INTERNAL_BENCHMARKS = { | 
 |     'SystemUIApp': {'targets': ['r8-full']}, | 
 | } | 
 | # A collection of benchmarks that should not be run on the bots, but can be used | 
 | # for running locally. | 
 | LOCAL_BENCHMARKS = { | 
 |     'SystemUIAppTreeShaking': {'targets': ['r8-full']}} | 
 | ALL_BENCHMARKS = {} | 
 | ALL_BENCHMARKS.update(EXTERNAL_BENCHMARKS) | 
 | ALL_BENCHMARKS.update(INTERNAL_BENCHMARKS) | 
 | ALL_BENCHMARKS.update(LOCAL_BENCHMARKS) | 
 | BUCKET = "r8-perf-results" | 
 | SAMPLE_BENCHMARK_RESULT_JSON = { | 
 |     'benchmark_name': '<benchmark_name>', | 
 |     'results': [{ | 
 |         'code_size': 0, | 
 |         'runtime': 0 | 
 |     }] | 
 | } | 
 |  | 
 |  | 
 | # Result structure on cloud storage | 
 | # gs://bucket/benchmark_results/APP/TARGET/GIT_HASH/result.json | 
 | #                                                   meta | 
 | # where results simply contains the result lines and | 
 | # meta contains information about the execution (machine) | 
 | def ParseOptions(): | 
 |     result = argparse.ArgumentParser() | 
 |     result.add_argument('--benchmark', | 
 |                         help='Specific benchmark(s) to measure.', | 
 |                         action='append') | 
 |     result.add_argument('--internal', | 
 |                         help='Run internal benchmarks.', | 
 |                         action='store_true', | 
 |                         default=False) | 
 |     result.add_argument('--iterations', | 
 |                         help='How many times run_benchmark is run.', | 
 |                         type=int, | 
 |                         default=1) | 
 |     result.add_argument('--iterations-inner', | 
 |                         help='How many iterations to run inside run_benchmark.', | 
 |                         type=int, | 
 |                         default=10) | 
 |     result.add_argument('--outdir', | 
 |                         help='Output directory for running locally.') | 
 |     result.add_argument('--skip-if-output-exists', | 
 |                         help='Skip if output exists.', | 
 |                         action='store_true', | 
 |                         default=False) | 
 |     result.add_argument( | 
 |         '--target', | 
 |         help='Specific target to run on.', | 
 |         choices=['d8', 'r8-full', 'r8-force', 'r8-compat', 'retrace']) | 
 |     result.add_argument('--verbose', | 
 |                         help='To enable verbose logging.', | 
 |                         action='store_true', | 
 |                         default=False) | 
 |     result.add_argument('--version', | 
 |                         '-v', | 
 |                         help='Use R8 hash for the run (default local build)') | 
 |     result.add_argument('--version-jar', | 
 |                         help='The r8lib.jar for the given version.') | 
 |     options, args = result.parse_known_args() | 
 |     if options.benchmark: | 
 |         options.benchmarks = options.benchmark | 
 |     elif options.internal: | 
 |         options.benchmarks = INTERNAL_BENCHMARKS.keys() | 
 |     else: | 
 |         options.benchmarks = EXTERNAL_BENCHMARKS.keys() | 
 |     options.quiet = not options.verbose | 
 |     del options.benchmark | 
 |     return options, args | 
 |  | 
 |  | 
 | def Build(options): | 
 |     utils.Print('Building', quiet=options.quiet) | 
 |     target = options.target or 'r8-full' | 
 |     build_cmd = GetRunCmd('N/A', target, options, ['--iterations', '0']) | 
 |     subprocess.check_call(build_cmd) | 
 |  | 
 |  | 
 | def GetRunCmd(benchmark, target, options, args, r8jar=None): | 
 |     base_cmd = [ | 
 |         'tools/run_benchmark.py', '--benchmark', benchmark, '--target', target | 
 |     ] | 
 |     if options.verbose: | 
 |         base_cmd.append('--verbose') | 
 |     if options.version and r8jar is not None: | 
 |         base_cmd.extend( | 
 |             ['--version', options.version, '--version-jar', r8jar, '--nolib']) | 
 |     return base_cmd + args | 
 |  | 
 |  | 
 | def MergeBenchmarkResultJsonFiles(benchmark_result_json_files): | 
 |     merged_benchmark_result_json = None | 
 |     for benchmark_result_json_file in benchmark_result_json_files: | 
 |         benchmark_result_json = ParseBenchmarkResultJsonFile( | 
 |             benchmark_result_json_file) | 
 |         if merged_benchmark_result_json is None: | 
 |             merged_benchmark_result_json = benchmark_result_json | 
 |         else: | 
 |             MergeBenchmarkResultJsonFile(merged_benchmark_result_json, | 
 |                                          benchmark_result_json) | 
 |     return merged_benchmark_result_json | 
 |  | 
 |  | 
 | def MergeBenchmarkResultJsonFile(merged_benchmark_result_json, | 
 |                                  benchmark_result_json): | 
 |     assert benchmark_result_json.keys() == SAMPLE_BENCHMARK_RESULT_JSON.keys() | 
 |     assert merged_benchmark_result_json[ | 
 |         'benchmark_name'] == benchmark_result_json['benchmark_name'] | 
 |     merged_benchmark_result_json['results'].extend( | 
 |         benchmark_result_json['results']) | 
 |  | 
 |  | 
 | def ParseBenchmarkResultJsonFile(result_json_file): | 
 |     with open(result_json_file, 'r') as f: | 
 |         lines = f.readlines() | 
 |         return json.loads(''.join(lines)) | 
 |  | 
 |  | 
 | def GetArtifactLocation(benchmark, target, version, filename): | 
 |     version_or_head = version or utils.get_HEAD_sha1() | 
 |     return f'{benchmark}/{target}/{version_or_head}/{filename}' | 
 |  | 
 |  | 
 | def GetGSLocation(filename, bucket=BUCKET): | 
 |     return f'gs://{bucket}/{filename}' | 
 |  | 
 |  | 
 | def ArchiveBenchmarkResult(benchmark, target, benchmark_result_json_files, | 
 |                            options, temp): | 
 |     result_file = os.path.join(temp, 'result_file') | 
 |     with open(result_file, 'w') as f: | 
 |         json.dump(MergeBenchmarkResultJsonFiles(benchmark_result_json_files), f) | 
 |     ArchiveOutputFile(result_file, | 
 |                       GetArtifactLocation(benchmark, target, options.version, | 
 |                                           'result.json'), | 
 |                       outdir=options.outdir) | 
 |  | 
 |  | 
 | def ArchiveOutputFile(file, dest, bucket=BUCKET, header=None, outdir=None): | 
 |     if outdir: | 
 |         dest_in_outdir = os.path.join(outdir, dest) | 
 |         os.makedirs(os.path.dirname(dest_in_outdir), exist_ok=True) | 
 |         shutil.copyfile(file, dest_in_outdir) | 
 |     else: | 
 |         utils.upload_file_to_cloud_storage(file, | 
 |                                            GetGSLocation(dest, bucket=bucket), | 
 |                                            header=header) | 
 |  | 
 |  | 
 | # Usage with historic_run.py: | 
 | # ./tools/historic_run.py | 
 | #     --cmd "perf.py --skip-if-output-exists --version" | 
 | #     --timeout -1 | 
 | #     --top 3373fd18453835bf49bff9f02523a507a2ebf317 | 
 | #     --bottom 7486f01e0622cb5935b77a92b59ddf1ca8dbd2e2 | 
 | def main(): | 
 |     options, args = ParseOptions() | 
 |     Build(options) | 
 |     any_failed = False | 
 |     with utils.TempDir() as temp: | 
 |         if options.version: | 
 |             # Download r8.jar once instead of once per run_benchmark.py invocation. | 
 |             download_options = argparse.Namespace(no_build=True, nolib=True) | 
 |             r8jar = options.version_jar or compiledump.download_distribution( | 
 |                 options.version, download_options, temp) | 
 |         else: | 
 |             r8jar = None | 
 |         for benchmark in options.benchmarks: | 
 |             benchmark_info = ALL_BENCHMARKS[benchmark] | 
 |             targets = [options.target | 
 |                       ] if options.target else benchmark_info['targets'] | 
 |             for target in targets: | 
 |                 sub_benchmarks = benchmark_info.get('subBenchmarks', {}) | 
 |                 sub_benchmarks_for_target = sub_benchmarks.get(target, []) | 
 |  | 
 |                 if options.skip_if_output_exists: | 
 |                     assert len(sub_benchmarks_for_target) == 0, 'Unimplemented' | 
 |                     if options.outdir: | 
 |                         raise NotImplementedError | 
 |                     output = GetGSLocation( | 
 |                         GetArtifactLocation(benchmark, target, options.version, | 
 |                                             'result.json')) | 
 |                     if utils.cloud_storage_exists(output): | 
 |                         print(f'Skipping run, {output} already exists.') | 
 |                         continue | 
 |  | 
 |                 # Run benchmark. | 
 |                 if sub_benchmarks_for_target: | 
 |                     benchmark_result_json_files = {} | 
 |                     for sub_benchmark in sub_benchmarks_for_target: | 
 |                         benchmark_result_json_files[sub_benchmark] = [] | 
 |                 else: | 
 |                     benchmark_result_json_files = [] | 
 |  | 
 |                 # Prepare out dir. | 
 |                 temp_benchmark_target = os.path.join(temp, benchmark, target) | 
 |                 os.makedirs(temp_benchmark_target) | 
 |  | 
 |                 failed = False | 
 |                 for i in range(options.iterations): | 
 |                     utils.Print( | 
 |                         f'Benchmarking {benchmark} ({i+1}/{options.iterations})', | 
 |                         quiet=options.quiet) | 
 |                     if sub_benchmarks_for_target: | 
 |                         benchmark_result_file = os.path.join( | 
 |                             temp_benchmark_target, f'result_{i}') | 
 |                         os.makedirs(benchmark_result_file) | 
 |                     else: | 
 |                         benchmark_result_file = os.path.join( | 
 |                             temp_benchmark_target, f'result_file_{i}') | 
 |                     iteration_cmd = GetRunCmd(benchmark, target, options, [ | 
 |                         '--iterations', | 
 |                         str(options.iterations_inner), '--output', | 
 |                         benchmark_result_file, '--no-build' | 
 |                     ], r8jar) | 
 |                     try: | 
 |                         subprocess.check_call(iteration_cmd) | 
 |                         if sub_benchmarks_for_target: | 
 |                             for sub_benchmark in sub_benchmarks_for_target: | 
 |                                 sub_benchmark_result_file = os.path.join( | 
 |                                     benchmark_result_file, | 
 |                                     benchmark + sub_benchmark) | 
 |                                 benchmark_result_json_files[ | 
 |                                     sub_benchmark].append( | 
 |                                         sub_benchmark_result_file) | 
 |                         else: | 
 |                             benchmark_result_json_files.append( | 
 |                                 benchmark_result_file) | 
 |                     except subprocess.CalledProcessError as e: | 
 |                         failed = True | 
 |                         any_failed = True | 
 |                         break | 
 |  | 
 |                 if failed: | 
 |                     continue | 
 |  | 
 |                 # Merge results and write output. | 
 |                 if sub_benchmarks_for_target: | 
 |                     for sub_benchmark in sub_benchmarks_for_target: | 
 |                         ArchiveBenchmarkResult( | 
 |                             benchmark + sub_benchmark, target, | 
 |                             benchmark_result_json_files[sub_benchmark], options, | 
 |                             temp) | 
 |                 else: | 
 |                     ArchiveBenchmarkResult(benchmark, target, | 
 |                                            benchmark_result_json_files, options, | 
 |                                            temp) | 
 |  | 
 |                 # Write metadata. | 
 |                 if utils.is_bot(): | 
 |                     meta_file = os.path.join(temp, "meta") | 
 |                     with open(meta_file, 'w') as f: | 
 |                         f.write("Produced by: " + | 
 |                                 os.environ.get('SWARMING_BOT_ID')) | 
 |                     ArchiveOutputFile(meta_file, | 
 |                                       GetArtifactLocation( | 
 |                                           benchmark, target, options.version, | 
 |                                           'meta'), | 
 |                                       outdir=options.outdir) | 
 |  | 
 |     # Only upload benchmark data when running on the perf bot. | 
 |     if utils.is_bot() and not options.internal: | 
 |         upload_benchmark_data_to_google_storage.run_bucket() | 
 |  | 
 |     if any_failed: | 
 |         return 1 | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     sys.exit(main()) |