blob: 0c1dcd939db2171c3a159bd5b4b2dcf12798150a [file] [log] [blame]
#!/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())