490 lines
17 KiB
Python
Executable File
490 lines
17 KiB
Python
Executable File
#!/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:])
|