| #!/usr/bin/env python |
| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Runner for Mojo application benchmarks.""" |
| |
| import argparse |
| import logging |
| import numpy |
| import sys |
| import time |
| |
| from devtoolslib import benchmark |
| from devtoolslib import perf_dashboard |
| from devtoolslib import shell_arguments |
| from devtoolslib import shell_config |
| from devtoolslib.utils import disable_output_buffering |
| |
| |
| _DESCRIPTION = """Runner for Mojo application benchmarks. |
| |
| |benchmark_list_file| has to be a valid Python program that sets a |benchmarks| |
| dictionary. For description of the required format see |
| https://github.com/domokit/devtools/blob/master/docs/mojo_benchmark.md . |
| """ |
| |
| _logger = logging.getLogger() |
| |
| _CACHE_SERVICE_URL = 'mojo:url_response_disk_cache' |
| _NETWORK_SERVICE_URL = 'mojo:network_service' |
| |
| _COLD_START_SHELL_ARGS = [ |
| '--args-for=%s %s' % (_CACHE_SERVICE_URL, '--clear'), |
| '--args-for=%s %s' % (_NETWORK_SERVICE_URL, '--clear'), |
| ] |
| |
| |
| def _generate_benchmark_variants(benchmark_spec): |
| """Generates benchmark specifications for individual variants of the given |
| benchmark: cold start and warm start. |
| |
| Returns: |
| A list of benchmark specs corresponding to individual variants of the given |
| benchmark. |
| """ |
| variants = [] |
| variants.append({ |
| 'variant_name': 'cold start', |
| 'app': benchmark_spec['app'], |
| 'duration': benchmark_spec['duration'], |
| 'measurements': benchmark_spec['measurements'], |
| 'shell-args': benchmark_spec.get('shell-args', |
| []) + _COLD_START_SHELL_ARGS}) |
| variants.append({ |
| 'variant_name': 'warm start', |
| 'app': benchmark_spec['app'], |
| 'duration': benchmark_spec['duration'], |
| 'measurements': benchmark_spec['measurements'], |
| 'shell-args': benchmark_spec.get('shell-args', [])}) |
| return variants |
| |
| |
| def _print_benchmark_error(outcome): |
| if not outcome.succeeded: |
| print 'benchmark failed: ' + outcome.error_str |
| if outcome.some_measurements_failed: |
| print 'some measurements failed' |
| print 'output: ' |
| print '-' * 72 |
| print outcome.output |
| print '-' * 72 |
| |
| |
| def _format_vector(results): |
| if not len(results): |
| return "med -, avg -, std-dev -, (no results)" |
| |
| return "med %f, avg %f, std-dev %f %s" % (numpy.median(results), |
| numpy.mean(results), |
| numpy.std(results), |
| str(results)) |
| |
| |
| def _print_results(benchmark_name, variant_name, results, measurements, |
| aggregate): |
| print '[ %s ] %s ' % (benchmark_name, variant_name) |
| for measurement in measurements: |
| print ' ' + measurement['name'] + ': ', |
| if measurement['spec'] in results: |
| if aggregate: |
| print _format_vector(results[measurement['spec']]) |
| else: |
| if len(results[measurement['spec']]) == 0: |
| print '?' |
| else: |
| print '%f' % results[measurement['spec']][0] |
| else: |
| print '?' |
| |
| |
| def _upload_results(benchmark_name, variant_name, results, measurements, |
| script_args): |
| anything_recorded = False |
| chart_data_recorder = perf_dashboard.ChartDataRecorder(script_args.test_name) |
| chart_name = benchmark_name + '__' + variant_name |
| |
| for measurement in measurements: |
| if measurement['spec'] in results: |
| if not results[measurement['spec']]: |
| continue |
| |
| if script_args.aggregate: |
| chart_data_recorder.record_vector( |
| perf_dashboard.normalize_label(chart_name), |
| perf_dashboard.normalize_label(measurement['name']), |
| 'ms', results[measurement['spec']]) |
| else: |
| chart_data_recorder.record_scalar( |
| perf_dashboard.normalize_label(chart_name), |
| perf_dashboard.normalize_label(measurement['name']), |
| 'ms', results[measurement['spec']][0]) |
| anything_recorded = True |
| |
| if not anything_recorded: |
| # Don't upload empty packets, see |
| # https://github.com/catapult-project/catapult/issues/1733 . |
| return True |
| |
| return perf_dashboard.upload_chart_data( |
| script_args.master_name, script_args.bot_name, |
| script_args.test_name, script_args.builder_name, |
| script_args.build_number, chart_data_recorder.get_chart_data(), |
| script_args.server_url, script_args.dry_run) |
| |
| |
| def _argparse_aggregate_type(value): |
| try: |
| cast_value = int(value) |
| except ValueError: |
| raise argparse.ArgumentTypeError('value is not a positive integer') |
| |
| if cast_value < 1: |
| raise argparse.ArgumentTypeError('value is not a positive integer') |
| return cast_value |
| |
| |
| def main(): |
| disable_output_buffering() |
| parser = argparse.ArgumentParser( |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| description=_DESCRIPTION) |
| parser.add_argument('benchmark_list_file', type=file, |
| help='a file listing benchmarks to run') |
| parser.add_argument('--aggregate', type=_argparse_aggregate_type, |
| help='aggregate results over multiple runs. The value ' |
| 'has to be a positive integer indicating the number of ' |
| 'runs.') |
| parser.add_argument('--save-all-traces', action='store_true', |
| help='save the traces produced by benchmarks to disk') |
| perf_dashboard.add_argparse_server_arguments(parser) |
| |
| # Common shell configuration arguments. |
| shell_config.add_shell_arguments(parser) |
| script_args = parser.parse_args() |
| config = shell_config.get_shell_config(script_args) |
| |
| try: |
| shell, common_shell_args = shell_arguments.get_shell(config, []) |
| except shell_arguments.ShellConfigurationException as e: |
| print e |
| return 1 |
| |
| target_os = 'android' if script_args.android else 'linux' |
| benchmark_list_params = {"target_os": target_os} |
| exec script_args.benchmark_list_file in benchmark_list_params |
| |
| exit_code = 0 |
| run_count = script_args.aggregate if script_args.aggregate else 1 |
| for benchmark_spec in benchmark_list_params['benchmarks']: |
| benchmark_name = benchmark_spec['name'] |
| variants = _generate_benchmark_variants(benchmark_spec) |
| variant_results = {variant_spec['variant_name']: {} |
| for variant_spec in variants} |
| |
| for _ in xrange(run_count): |
| for variant_spec in variants: |
| variant_name = variant_spec['variant_name'] |
| app = variant_spec['app'] |
| duration = variant_spec['duration'] |
| shell_args = variant_spec.get('shell-args', []) + common_shell_args |
| measurements = variant_spec['measurements'] |
| |
| output_file = None |
| if script_args.save_all_traces: |
| output_file = 'benchmark-%s-%s-%s.trace' % ( |
| benchmark_name.replace(' ', '_'), |
| variant_name.replace(' ', '_'), |
| time.strftime('%Y%m%d%H%M%S')) |
| |
| outcome = benchmark.run( |
| shell, shell_args, app, duration, measurements, script_args.verbose, |
| script_args.android, output_file) |
| |
| if not outcome.succeeded or outcome.some_measurements_failed: |
| _print_benchmark_error(outcome) |
| exit_code = 1 |
| |
| if outcome.succeeded: |
| for measurement_spec in outcome.results: |
| if measurement_spec not in variant_results[variant_name]: |
| variant_results[variant_name][measurement_spec] = [] |
| variant_results[variant_name][measurement_spec].append( |
| outcome.results[measurement_spec]) |
| |
| for variant_spec in variants: |
| variant_name = variant_spec['variant_name'] |
| _print_results(benchmark_name, variant_name, |
| variant_results[variant_name], |
| variant_spec['measurements'], script_args.aggregate) |
| |
| if script_args.upload: |
| upload_succeeded = _upload_results(benchmark_name, variant_name, |
| variant_results[variant_name], |
| variant_spec['measurements'], |
| script_args) |
| if not upload_succeeded: |
| exit_code = 1 |
| |
| return exit_code |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |