Make mojo_benchmark understand the output it is producing. This patch: - refactors the logic in mojo_benchmark so that obtaining the output produced by `benchmark.mojo` is separated from its analysis and presentation - makes `mojo_benchmark` parse the measurements results, as opposed to just streaming the output produced by `benchmark.mojo`. This is a prerequisite for producing the chart data to be uploaded to the performance dashboard, see domokit/mojo#474. As a bonus, we now notice when measurement result is missing and return non-zero exit code. R=viettrungluu@chromium.org Review URL: https://codereview.chromium.org/1392173006 . Cr-Mirrored-From: https://github.com/domokit/mojo Cr-Mirrored-Commit: 0acce94790935a282c3461c3fca2787e61bd2233
diff --git a/mojo_benchmark b/mojo_benchmark index 2c5f5e1..0f2126b 100755 --- a/mojo_benchmark +++ b/mojo_benchmark
@@ -10,6 +10,7 @@ import sys import time import os.path +import re from devtoolslib import shell_arguments from devtoolslib import shell_config @@ -60,6 +61,16 @@ # doesn't. _EXTRA_TIMEOUT = 20 +_MEASUREMENT_RESULT_FORMAT = r""" +^ # Beginning of the line. +measurement: # Hard-coded tag. +\s+(\S+) # Match measurement name. +\s+(\S+) # Match measurement result. +$ # End of the line. +""" + +_MEASUREMENT_REGEX = re.compile(_MEASUREMENT_RESULT_FORMAT, re.VERBOSE) + def _generate_benchmark_variants(benchmark_spec): """Generates benchmark specifications for individual variants of the given @@ -90,8 +101,11 @@ def _run_benchmark(shell, shell_args, name, app, duration_seconds, measurements, verbose, android, save_traces): - """Runs `benchmark.mojo` in shell with correct arguments, parses and - presents the benchmark results. + """Runs the given benchmark by running `benchmark.mojo` in mojo shell with + appropriate arguments and returns the produced output. + + Returns: + A tuple of (succeeded, error_msg, output). """ timeout = duration_seconds + _EXTRA_TIMEOUT benchmark_args = [] @@ -120,28 +134,37 @@ if verbose: print 'shell arguments: ' + str(shell_args) - print '[ %s ]' % name return_code, output, did_time_out = shell.run_and_get_output( shell_args, timeout=timeout) - output_lines = [line.strip() for line in output.split('\n')] - if return_code or did_time_out or 'benchmark succeeded' not in output_lines: - print 'timed out' if did_time_out else 'failed' - if return_code: - print 'Return code: ' + str(return_code) - print 'Output: ' - print output - print '-' * 72 - return False + if did_time_out: + return False, 'timed out', output + if return_code: + return False, 'return code: ' + str(return_code), output - # Echo measurement results. - for line in output_lines: - if line.strip().startswith('measurement:') or 'WARNING' in line: - print line - + # Pull the trace file even if some measurements are missing, as it can be + # useful in debugging. if device_output_file: shell.pull_file(device_output_file, output_file, remove_original=True) - return True + + return True, None, output + +def _parse_measurement_results(output): + """Parses the measurement results present in the benchmark output and returns + the dictionary of correctly recognized and parsed results. + """ + measurement_results = {} + output_lines = [line.strip() for line in output.split('\n')] + for line in output_lines: + match = re.match(_MEASUREMENT_REGEX, line) + if match: + measurement_name = match.group(1) + measurement_result = match.group(2) + try: + measurement_results[measurement_name] = float(measurement_result) + except ValueError: + pass + return measurement_results def main(): @@ -168,7 +191,7 @@ benchmark_list_params = {"target_os": target_os} exec script_args.benchmark_list_file in benchmark_list_params - succeeded = True + exit_code = 0 for benchmark_spec in benchmark_list_params['benchmarks']: for variant_spec in _generate_benchmark_variants(benchmark_spec): name = variant_spec['name'] @@ -176,11 +199,38 @@ duration = variant_spec['duration'] shell_args = variant_spec.get('shell-args', []) + common_shell_args measurements = variant_spec['measurements'] - _run_benchmark(shell, shell_args, name, app, duration, measurements, - script_args.verbose, script_args.android, - script_args.save_traces) + benchmark_succeeded, benchmark_error, output = _run_benchmark( + shell, shell_args, name, app, duration, measurements, + script_args.verbose, script_args.android, + script_args.save_traces) - return 0 if succeeded else 1 + print '[ %s ]' % name + + some_measurements_failed = False + if benchmark_succeeded: + measurement_results = _parse_measurement_results(output) + # Iterate over the list of specs, not the dictionary, to detect missing + # results and preserve the required order. + for measurement_spec in measurements: + if measurement_spec in measurement_results: + print '%s %s' % (measurement_spec, + measurement_results[measurement_spec]) + else: + print '%s ?' % measurement_spec + some_measurements_failed = True + + if not benchmark_succeeded or some_measurements_failed: + if not benchmark_succeeded: + print 'benchmark failed: ' + benchmark_error + if some_measurements_failed: + print 'some measurements failed' + print 'output: ' + print '-' * 72 + print output + print '-' * 72 + exit_code = 1 + + return exit_code if __name__ == '__main__': sys.exit(main())