| #!/usr/bin/env python2 |
| # |
| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # pylint: disable=cros-logging-import |
| |
| # This is the script to run specified benchmark with different toolchain |
| # settings. It includes the process of building benchmark locally and running |
| # benchmark on DUT. |
| |
| """Main script to run the benchmark suite from building to testing.""" |
| from __future__ import print_function |
| |
| import argparse |
| import config |
| import ConfigParser |
| import logging |
| import os |
| import subprocess |
| import sys |
| |
| logging.basicConfig(level=logging.INFO) |
| |
| def _parse_arguments(argv): |
| parser = argparse.ArgumentParser(description='Build and run specific ' |
| 'benchamrk') |
| parser.add_argument( |
| '-b', |
| '--bench', |
| action='append', |
| default=[], |
| help='Select which benchmark to run') |
| |
| # Only one of compiler directory and llvm prebuilts version can be indicated |
| # at the beginning, so set -c and -l into a exclusive group. |
| group = parser.add_mutually_exclusive_group() |
| |
| # The toolchain setting arguments has action of 'append', so that users |
| # could compare performance with several toolchain settings together. |
| group.add_argument( |
| '-c', |
| '--compiler_dir', |
| metavar='DIR', |
| action='append', |
| default=[], |
| help='Specify path to the compiler\'s bin directory. ' |
| 'You shall give several paths, each with a -c, to ' |
| 'compare performance differences in ' |
| 'each compiler.') |
| |
| parser.add_argument( |
| '-o', |
| '--build_os', |
| action='append', |
| default=[], |
| help='Specify the host OS to build the benchmark.') |
| |
| group.add_argument( |
| '-l', |
| '--llvm_prebuilts_version', |
| action='append', |
| default=[], |
| help='Specify the version of prebuilt LLVM. When ' |
| 'specific prebuilt version of LLVM already ' |
| 'exists, no need to pass the path to compiler ' |
| 'directory.') |
| |
| parser.add_argument( |
| '-f', |
| '--cflags', |
| action='append', |
| default=[], |
| help='Specify the cflags options for the toolchain. ' |
| 'Be sure to quote all the cflags with quotation ' |
| 'mark("") or use equal(=).') |
| parser.add_argument( |
| '--ldflags', |
| action='append', |
| default=[], |
| help='Specify linker flags for the toolchain.') |
| |
| parser.add_argument( |
| '-i', |
| '--iterations', |
| type=int, |
| default=1, |
| help='Specify how many iterations does the test ' |
| 'take.') |
| |
| # Arguments -s and -r are for connecting to DUT. |
| parser.add_argument( |
| '-s', |
| '--serials', |
| help='Comma separate list of device serials under ' |
| 'test.') |
| |
| parser.add_argument( |
| '-r', |
| '--remote', |
| default='localhost', |
| help='hostname[:port] if the ADB device is connected ' |
| 'to a remote machine. Ensure this workstation ' |
| 'is configured for passwordless ssh access as ' |
| 'users "root" or "adb"') |
| |
| # Arguments -frequency and -m are for device settings |
| parser.add_argument( |
| '--frequency', |
| type=int, |
| default=979200, |
| help='Specify the CPU frequency of the device. The ' |
| 'unit is KHZ. The available value is defined in' |
| 'cpufreq/scaling_available_frequency file in ' |
| 'device\'s each core directory. ' |
| 'The default value is 979200, which shows a ' |
| 'balance in noise and performance. Lower ' |
| 'frequency will slow down the performance but ' |
| 'reduce noise.') |
| |
| parser.add_argument( |
| '-m', |
| '--mode', |
| default='little', |
| help='User can specify whether \'little\' or \'big\' ' |
| 'mode to use. The default one is little mode. ' |
| 'The little mode runs on a single core of ' |
| 'Cortex-A53, while big mode runs on single core ' |
| 'of Cortex-A57.') |
| |
| # Configure file for benchmark test |
| parser.add_argument( |
| '-t', |
| '--test', |
| help='Specify the test settings with configuration ' |
| 'file.') |
| |
| # Whether to keep old json result or not |
| parser.add_argument( |
| '-k', |
| '--keep', |
| default='False', |
| help='User can specify whether to keep the old json ' |
| 'results from last run. This can be useful if you ' |
| 'want to compare performance differences in two or ' |
| 'more different runs. Default is False(off).') |
| |
| return parser.parse_args(argv) |
| |
| |
| # Clear old log files in bench suite directory |
| def clear_logs(): |
| logging.info('Removing old logfiles...') |
| for f in ['build_log', 'device_log', 'test_log']: |
| logfile = os.path.join(config.bench_suite_dir, f) |
| try: |
| os.remove(logfile) |
| except OSError: |
| logging.info('No logfile %s need to be removed. Ignored.', f) |
| logging.info('Old logfiles been removed.') |
| |
| |
| # Clear old json files in bench suite directory |
| def clear_results(): |
| logging.info('Clearing old json results...') |
| for bench in config.bench_list: |
| result = os.path.join(config.bench_suite_dir, bench + '.json') |
| try: |
| os.remove(result) |
| except OSError: |
| logging.info('no %s json file need to be removed. Ignored.', bench) |
| logging.info('Old json results been removed.') |
| |
| |
| # Use subprocess.check_call to run other script, and put logs to files |
| def check_call_with_log(cmd, log_file): |
| log_file = os.path.join(config.bench_suite_dir, log_file) |
| with open(log_file, 'a') as logfile: |
| log_header = 'Log for command: %s\n' % (cmd) |
| logfile.write(log_header) |
| try: |
| subprocess.check_call(cmd, stdout=logfile) |
| except subprocess.CalledProcessError: |
| logging.error('Error running %s, please check %s for more info.', |
| cmd, log_file) |
| raise |
| logging.info('Logs for %s are written to %s.', cmd, log_file) |
| |
| |
| def set_device(serials, remote, frequency): |
| setting_cmd = [ |
| os.path.join( |
| os.path.join(config.android_home, config.autotest_dir), |
| 'site_utils/set_device.py') |
| ] |
| setting_cmd.append('-r=' + remote) |
| setting_cmd.append('-q=' + str(frequency)) |
| |
| # Deal with serials. |
| # If there is no serails specified, try to run test on the only device. |
| # If specified, split the serials into a list and run test on each device. |
| if serials: |
| for serial in serials.split(','): |
| setting_cmd.append('-s=' + serial) |
| check_call_with_log(setting_cmd, 'device_log') |
| setting_cmd.pop() |
| else: |
| check_call_with_log(setting_cmd, 'device_log') |
| |
| logging.info('CPU mode and frequency set successfully!') |
| |
| |
| def log_ambiguous_args(): |
| logging.error('The count of arguments does not match!') |
| raise ValueError('The count of arguments does not match.') |
| |
| |
| # Check if the count of building arguments are log_ambiguous or not. The |
| # number of -c/-l, -f, and -os should be either all 0s or all the same. |
| def check_count(compiler, llvm_version, build_os, cflags, ldflags): |
| # Count will be set to 0 if no compiler or llvm_version specified. |
| # Otherwise, one of these two args length should be 0 and count will be |
| # the other one. |
| count = max(len(compiler), len(llvm_version)) |
| |
| # Check if number of cflags is 0 or the same with before. |
| if len(cflags) != 0: |
| if count != 0 and len(cflags) != count: |
| log_ambiguous_args() |
| count = len(cflags) |
| |
| if len(ldflags) != 0: |
| if count != 0 and len(ldflags) != count: |
| log_ambiguous_args() |
| count = len(ldflags) |
| |
| if len(build_os) != 0: |
| if count != 0 and len(build_os) != count: |
| log_ambiguous_args() |
| count = len(build_os) |
| |
| # If no settings are passed, only run default once. |
| return max(1, count) |
| |
| |
| # Build benchmark binary with toolchain settings |
| def build_bench(setting_no, bench, compiler, llvm_version, build_os, cflags, |
| ldflags): |
| # Build benchmark locally |
| build_cmd = ['./build_bench.py', '-b=' + bench] |
| if compiler: |
| build_cmd.append('-c=' + compiler[setting_no]) |
| if llvm_version: |
| build_cmd.append('-l=' + llvm_version[setting_no]) |
| if build_os: |
| build_cmd.append('-o=' + build_os[setting_no]) |
| if cflags: |
| build_cmd.append('-f=' + cflags[setting_no]) |
| if ldflags: |
| build_cmd.append('--ldflags=' + ldflags[setting_no]) |
| |
| logging.info('Building benchmark for toolchain setting No.%d...', |
| setting_no) |
| logging.info('Command: %s', build_cmd) |
| |
| try: |
| subprocess.check_call(build_cmd) |
| except: |
| logging.error('Error while building benchmark!') |
| raise |
| |
| |
| def run_and_collect_result(test_cmd, setting_no, i, bench, serial='default'): |
| |
| # Run autotest script for benchmark on DUT |
| check_call_with_log(test_cmd, 'test_log') |
| |
| logging.info('Benchmark with setting No.%d, iter.%d finished testing on ' |
| 'device %s.', setting_no, i, serial) |
| |
| # Rename results from the bench_result generated in autotest |
| bench_result = os.path.join(config.bench_suite_dir, 'bench_result') |
| if not os.path.exists(bench_result): |
| logging.error('No result found at %s, ' |
| 'please check test_log for details.', bench_result) |
| raise OSError('Result file %s not found.' % bench_result) |
| |
| new_bench_result = 'bench_result_%s_%s_%d_%d' % (bench, serial, |
| setting_no, i) |
| new_bench_result_path = os.path.join(config.bench_suite_dir, |
| new_bench_result) |
| try: |
| os.rename(bench_result, new_bench_result_path) |
| except OSError: |
| logging.error('Error while renaming raw result %s to %s', |
| bench_result, new_bench_result_path) |
| raise |
| |
| logging.info('Benchmark result saved at %s.', new_bench_result_path) |
| |
| |
| def test_bench(bench, setting_no, iterations, serials, remote, mode): |
| logging.info('Start running benchmark on device...') |
| |
| # Run benchmark and tests on DUT |
| for i in xrange(iterations): |
| logging.info('Iteration No.%d:', i) |
| test_cmd = [ |
| os.path.join( |
| os.path.join(config.android_home, config.autotest_dir), |
| 'site_utils/test_bench.py') |
| ] |
| test_cmd.append('-b=' + bench) |
| test_cmd.append('-r=' + remote) |
| test_cmd.append('-m=' + mode) |
| |
| # Deal with serials. If there is no serails specified, try to run test |
| # on the only device. If specified, split the serials into a list and |
| # run test on each device. |
| if serials: |
| for serial in serials.split(','): |
| test_cmd.append('-s=' + serial) |
| |
| run_and_collect_result(test_cmd, setting_no, i, bench, serial) |
| test_cmd.pop() |
| else: |
| run_and_collect_result(test_cmd, setting_no, i, bench) |
| |
| |
| def gen_json(bench, setting_no, iterations, serials): |
| bench_result = os.path.join(config.bench_suite_dir, 'bench_result') |
| |
| logging.info('Generating JSON file for Crosperf...') |
| |
| if not serials: |
| serials = 'default' |
| |
| for serial in serials.split(','): |
| |
| # Platform will be used as device lunch combo instead |
| #experiment = '_'.join([serial, str(setting_no)]) |
| experiment = config.product_combo |
| |
| # Input format: bench_result_{bench}_{serial}_{setting_no}_ |
| input_file = '_'.join([bench_result, bench, |
| serial, str(setting_no), '']) |
| gen_json_cmd = [ |
| './gen_json.py', '--input=' + input_file, |
| '--output=%s.json' % os.path.join(config.bench_suite_dir, bench), |
| '--bench=' + bench, '--platform=' + experiment, |
| '--iterations=' + str(iterations) |
| ] |
| |
| logging.info('Command: %s', gen_json_cmd) |
| if subprocess.call(gen_json_cmd): |
| logging.error('Error while generating JSON file, please check raw' |
| ' data of the results at %s.', input_file) |
| |
| |
| def gen_crosperf(infile, outfile): |
| # Set environment variable for crosperf |
| os.environ['PYTHONPATH'] = os.path.dirname(config.toolchain_utils) |
| |
| logging.info('Generating Crosperf Report...') |
| crosperf_cmd = [ |
| os.path.join(config.toolchain_utils, 'generate_report.py'), |
| '-i=' + infile, '-o=' + outfile, '-f' |
| ] |
| |
| # Run crosperf generate_report.py |
| logging.info('Command: %s', crosperf_cmd) |
| subprocess.call(crosperf_cmd) |
| |
| logging.info('Report generated successfully!') |
| logging.info('Report Location: ' + outfile + '.html at bench' |
| 'suite directory.') |
| |
| |
| def main(argv): |
| # Set environment variable for the local loacation of benchmark suite. |
| # This is for collecting testing results to benchmark suite directory. |
| os.environ['BENCH_SUITE_DIR'] = config.bench_suite_dir |
| |
| # Set Android type, used for the difference part between aosp and internal. |
| os.environ['ANDROID_TYPE'] = config.android_type |
| |
| # Set ANDROID_HOME for both building and testing. |
| os.environ['ANDROID_HOME'] = config.android_home |
| |
| # Set environment variable for architecture, this will be used in |
| # autotest. |
| os.environ['PRODUCT'] = config.product |
| |
| arguments = _parse_arguments(argv) |
| |
| bench_list = arguments.bench |
| if not bench_list: |
| bench_list = config.bench_list |
| |
| compiler = arguments.compiler_dir |
| build_os = arguments.build_os |
| llvm_version = arguments.llvm_prebuilts_version |
| cflags = arguments.cflags |
| ldflags = arguments.ldflags |
| iterations = arguments.iterations |
| serials = arguments.serials |
| remote = arguments.remote |
| frequency = arguments.frequency |
| mode = arguments.mode |
| keep = arguments.keep |
| |
| # Clear old logs every time before run script |
| clear_logs() |
| |
| if keep == 'False': |
| clear_results() |
| |
| # Set test mode and frequency of CPU on the DUT |
| set_device(serials, remote, frequency) |
| |
| test = arguments.test |
| # if test configuration file has been given, use the build settings |
| # in the configuration file and run the test. |
| if test: |
| test_config = ConfigParser.ConfigParser(allow_no_value=True) |
| if not test_config.read(test): |
| logging.error('Error while reading from building ' |
| 'configuration file %s.', test) |
| raise RuntimeError('Error while reading configuration file %s.' |
| % test) |
| |
| for setting_no, section in enumerate(test_config.sections()): |
| bench = test_config.get(section, 'bench') |
| compiler = [test_config.get(section, 'compiler')] |
| build_os = [test_config.get(section, 'build_os')] |
| llvm_version = [test_config.get(section, 'llvm_version')] |
| cflags = [test_config.get(section, 'cflags')] |
| ldflags = [test_config.get(section, 'ldflags')] |
| |
| # Set iterations from test_config file, if not exist, use the one |
| # from command line. |
| it = test_config.get(section, 'iterations') |
| if not it: |
| it = iterations |
| it = int(it) |
| |
| # Build benchmark for each single test configuration |
| build_bench(0, bench, compiler, llvm_version, |
| build_os, cflags, ldflags) |
| |
| test_bench(bench, setting_no, it, serials, remote, mode) |
| |
| gen_json(bench, setting_no, it, serials) |
| |
| for bench in config.bench_list: |
| infile = os.path.join(config.bench_suite_dir, bench + '.json') |
| if os.path.exists(infile): |
| outfile = os.path.join(config.bench_suite_dir, |
| bench + '_report') |
| gen_crosperf(infile, outfile) |
| |
| # Stop script if there is only config file provided |
| return 0 |
| |
| # If no configuration file specified, continue running. |
| # Check if the count of the setting arguments are log_ambiguous. |
| setting_count = check_count(compiler, llvm_version, build_os, |
| cflags, ldflags) |
| |
| for bench in bench_list: |
| logging.info('Start building and running benchmark: [%s]', bench) |
| # Run script for each toolchain settings |
| for setting_no in xrange(setting_count): |
| build_bench(setting_no, bench, compiler, llvm_version, |
| build_os, cflags, ldflags) |
| |
| # Run autotest script for benchmark test on device |
| test_bench(bench, setting_no, iterations, serials, remote, mode) |
| |
| gen_json(bench, setting_no, iterations, serials) |
| |
| infile = os.path.join(config.bench_suite_dir, bench + '.json') |
| outfile = os.path.join(config.bench_suite_dir, bench + '_report') |
| gen_crosperf(infile, outfile) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1:]) |