1*4882a593Smuzhiyun#!/usr/bin/env python3 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Build performance test script 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# Copyright (c) 2016, Intel Corporation. 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport argparse 11*4882a593Smuzhiyunimport errno 12*4882a593Smuzhiyunimport fcntl 13*4882a593Smuzhiyunimport json 14*4882a593Smuzhiyunimport logging 15*4882a593Smuzhiyunimport os 16*4882a593Smuzhiyunimport re 17*4882a593Smuzhiyunimport shutil 18*4882a593Smuzhiyunimport sys 19*4882a593Smuzhiyunfrom datetime import datetime 20*4882a593Smuzhiyun 21*4882a593Smuzhiyunsys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/lib') 22*4882a593Smuzhiyunimport scriptpath 23*4882a593Smuzhiyunscriptpath.add_oe_lib_path() 24*4882a593Smuzhiyunscriptpath.add_bitbake_lib_path() 25*4882a593Smuzhiyunimport oeqa.buildperf 26*4882a593Smuzhiyunfrom oeqa.buildperf import (BuildPerfTestLoader, BuildPerfTestResult, 27*4882a593Smuzhiyun BuildPerfTestRunner, KernelDropCaches) 28*4882a593Smuzhiyunfrom oeqa.utils.commands import runCmd 29*4882a593Smuzhiyunfrom oeqa.utils.metadata import metadata_from_bb, write_metadata_file 30*4882a593Smuzhiyun 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun# Set-up logging 33*4882a593SmuzhiyunLOG_FORMAT = '[%(asctime)s] %(levelname)s: %(message)s' 34*4882a593Smuzhiyunlogging.basicConfig(level=logging.INFO, format=LOG_FORMAT, 35*4882a593Smuzhiyun datefmt='%Y-%m-%d %H:%M:%S') 36*4882a593Smuzhiyunlog = logging.getLogger() 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun 39*4882a593Smuzhiyundef acquire_lock(lock_f): 40*4882a593Smuzhiyun """Acquire flock on file""" 41*4882a593Smuzhiyun log.debug("Acquiring lock %s", os.path.abspath(lock_f.name)) 42*4882a593Smuzhiyun try: 43*4882a593Smuzhiyun fcntl.flock(lock_f, fcntl.LOCK_EX | fcntl.LOCK_NB) 44*4882a593Smuzhiyun except IOError as err: 45*4882a593Smuzhiyun if err.errno == errno.EAGAIN: 46*4882a593Smuzhiyun return False 47*4882a593Smuzhiyun raise 48*4882a593Smuzhiyun log.debug("Lock acquired") 49*4882a593Smuzhiyun return True 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun 52*4882a593Smuzhiyundef pre_run_sanity_check(): 53*4882a593Smuzhiyun """Sanity check of build environment""" 54*4882a593Smuzhiyun build_dir = os.environ.get("BUILDDIR") 55*4882a593Smuzhiyun if not build_dir: 56*4882a593Smuzhiyun log.error("BUILDDIR not set. Please run the build environmnent setup " 57*4882a593Smuzhiyun "script.") 58*4882a593Smuzhiyun return False 59*4882a593Smuzhiyun if os.getcwd() != build_dir: 60*4882a593Smuzhiyun log.error("Please run this script under BUILDDIR (%s)", build_dir) 61*4882a593Smuzhiyun return False 62*4882a593Smuzhiyun 63*4882a593Smuzhiyun ret = runCmd('which bitbake', ignore_status=True) 64*4882a593Smuzhiyun if ret.status: 65*4882a593Smuzhiyun log.error("bitbake command not found") 66*4882a593Smuzhiyun return False 67*4882a593Smuzhiyun return True 68*4882a593Smuzhiyun 69*4882a593Smuzhiyundef setup_file_logging(log_file): 70*4882a593Smuzhiyun """Setup loggin to file""" 71*4882a593Smuzhiyun log_dir = os.path.dirname(log_file) 72*4882a593Smuzhiyun if not os.path.exists(log_dir): 73*4882a593Smuzhiyun os.makedirs(log_dir) 74*4882a593Smuzhiyun formatter = logging.Formatter(LOG_FORMAT) 75*4882a593Smuzhiyun handler = logging.FileHandler(log_file) 76*4882a593Smuzhiyun handler.setFormatter(formatter) 77*4882a593Smuzhiyun log.addHandler(handler) 78*4882a593Smuzhiyun 79*4882a593Smuzhiyun 80*4882a593Smuzhiyundef archive_build_conf(out_dir): 81*4882a593Smuzhiyun """Archive build/conf to test results""" 82*4882a593Smuzhiyun src_dir = os.path.join(os.environ['BUILDDIR'], 'conf') 83*4882a593Smuzhiyun tgt_dir = os.path.join(out_dir, 'build', 'conf') 84*4882a593Smuzhiyun os.makedirs(os.path.dirname(tgt_dir)) 85*4882a593Smuzhiyun shutil.copytree(src_dir, tgt_dir) 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun 88*4882a593Smuzhiyundef update_globalres_file(result_obj, filename, metadata): 89*4882a593Smuzhiyun """Write results to globalres csv file""" 90*4882a593Smuzhiyun # Map test names to time and size columns in globalres 91*4882a593Smuzhiyun # The tuples represent index and length of times and sizes 92*4882a593Smuzhiyun # respectively 93*4882a593Smuzhiyun gr_map = {'test1': ((0, 1), (8, 1)), 94*4882a593Smuzhiyun 'test12': ((1, 1), (None, None)), 95*4882a593Smuzhiyun 'test13': ((2, 1), (9, 1)), 96*4882a593Smuzhiyun 'test2': ((3, 1), (None, None)), 97*4882a593Smuzhiyun 'test3': ((4, 3), (None, None)), 98*4882a593Smuzhiyun 'test4': ((7, 1), (10, 2))} 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun values = ['0'] * 12 101*4882a593Smuzhiyun for status, test, _ in result_obj.all_results(): 102*4882a593Smuzhiyun if status in ['ERROR', 'SKIPPED']: 103*4882a593Smuzhiyun continue 104*4882a593Smuzhiyun (t_ind, t_len), (s_ind, s_len) = gr_map[test.name] 105*4882a593Smuzhiyun if t_ind is not None: 106*4882a593Smuzhiyun values[t_ind:t_ind + t_len] = test.times 107*4882a593Smuzhiyun if s_ind is not None: 108*4882a593Smuzhiyun values[s_ind:s_ind + s_len] = test.sizes 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun log.debug("Writing globalres log to %s", filename) 111*4882a593Smuzhiyun rev_info = metadata['layers']['meta'] 112*4882a593Smuzhiyun with open(filename, 'a') as fobj: 113*4882a593Smuzhiyun fobj.write('{},{}:{},{},'.format(metadata['hostname'], 114*4882a593Smuzhiyun rev_info['branch'], 115*4882a593Smuzhiyun rev_info['commit'], 116*4882a593Smuzhiyun rev_info['commit'])) 117*4882a593Smuzhiyun fobj.write(','.join(values) + '\n') 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun 120*4882a593Smuzhiyundef parse_args(argv): 121*4882a593Smuzhiyun """Parse command line arguments""" 122*4882a593Smuzhiyun parser = argparse.ArgumentParser( 123*4882a593Smuzhiyun formatter_class=argparse.ArgumentDefaultsHelpFormatter) 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun parser.add_argument('-D', '--debug', action='store_true', 126*4882a593Smuzhiyun help='Enable debug level logging') 127*4882a593Smuzhiyun parser.add_argument('--globalres-file', 128*4882a593Smuzhiyun type=os.path.abspath, 129*4882a593Smuzhiyun help="Append results to 'globalres' csv file") 130*4882a593Smuzhiyun parser.add_argument('--lock-file', default='./oe-build-perf.lock', 131*4882a593Smuzhiyun metavar='FILENAME', type=os.path.abspath, 132*4882a593Smuzhiyun help="Lock file to use") 133*4882a593Smuzhiyun parser.add_argument('-o', '--out-dir', default='results-{date}', 134*4882a593Smuzhiyun type=os.path.abspath, 135*4882a593Smuzhiyun help="Output directory for test results") 136*4882a593Smuzhiyun parser.add_argument('-x', '--xml', action='store_true', 137*4882a593Smuzhiyun help='Enable JUnit xml output') 138*4882a593Smuzhiyun parser.add_argument('--log-file', 139*4882a593Smuzhiyun default='{out_dir}/oe-build-perf-test.log', 140*4882a593Smuzhiyun help="Log file of this script") 141*4882a593Smuzhiyun parser.add_argument('--run-tests', nargs='+', metavar='TEST', 142*4882a593Smuzhiyun help="List of tests to run") 143*4882a593Smuzhiyun 144*4882a593Smuzhiyun return parser.parse_args(argv) 145*4882a593Smuzhiyun 146*4882a593Smuzhiyun 147*4882a593Smuzhiyundef main(argv=None): 148*4882a593Smuzhiyun """Script entry point""" 149*4882a593Smuzhiyun args = parse_args(argv) 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun # Set-up log file 152*4882a593Smuzhiyun out_dir = args.out_dir.format(date=datetime.now().strftime('%Y%m%d%H%M%S')) 153*4882a593Smuzhiyun setup_file_logging(args.log_file.format(out_dir=out_dir)) 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun if args.debug: 156*4882a593Smuzhiyun log.setLevel(logging.DEBUG) 157*4882a593Smuzhiyun 158*4882a593Smuzhiyun lock_f = open(args.lock_file, 'w') 159*4882a593Smuzhiyun if not acquire_lock(lock_f): 160*4882a593Smuzhiyun log.error("Another instance of this script is running, exiting...") 161*4882a593Smuzhiyun return 1 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun if not pre_run_sanity_check(): 164*4882a593Smuzhiyun return 1 165*4882a593Smuzhiyun 166*4882a593Smuzhiyun # Check our capability to drop caches and ask pass if needed 167*4882a593Smuzhiyun KernelDropCaches.check() 168*4882a593Smuzhiyun 169*4882a593Smuzhiyun # Load build perf tests 170*4882a593Smuzhiyun loader = BuildPerfTestLoader() 171*4882a593Smuzhiyun if args.run_tests: 172*4882a593Smuzhiyun suite = loader.loadTestsFromNames(args.run_tests, oeqa.buildperf) 173*4882a593Smuzhiyun else: 174*4882a593Smuzhiyun suite = loader.loadTestsFromModule(oeqa.buildperf) 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun # Save test metadata 177*4882a593Smuzhiyun metadata = metadata_from_bb() 178*4882a593Smuzhiyun log.info("Testing Git revision branch:commit %s:%s (%s)", 179*4882a593Smuzhiyun metadata['layers']['meta']['branch'], 180*4882a593Smuzhiyun metadata['layers']['meta']['commit'], 181*4882a593Smuzhiyun metadata['layers']['meta']['commit_count']) 182*4882a593Smuzhiyun if args.xml: 183*4882a593Smuzhiyun write_metadata_file(os.path.join(out_dir, 'metadata.xml'), metadata) 184*4882a593Smuzhiyun else: 185*4882a593Smuzhiyun with open(os.path.join(out_dir, 'metadata.json'), 'w') as fobj: 186*4882a593Smuzhiyun json.dump(metadata, fobj, indent=2) 187*4882a593Smuzhiyun archive_build_conf(out_dir) 188*4882a593Smuzhiyun 189*4882a593Smuzhiyun runner = BuildPerfTestRunner(out_dir, verbosity=2) 190*4882a593Smuzhiyun 191*4882a593Smuzhiyun # Suppress logger output to stderr so that the output from unittest 192*4882a593Smuzhiyun # is not mixed with occasional logger output 193*4882a593Smuzhiyun log.handlers[0].setLevel(logging.CRITICAL) 194*4882a593Smuzhiyun 195*4882a593Smuzhiyun # Run actual tests 196*4882a593Smuzhiyun result = runner.run(suite) 197*4882a593Smuzhiyun 198*4882a593Smuzhiyun # Restore logger output to stderr 199*4882a593Smuzhiyun log.handlers[0].setLevel(log.level) 200*4882a593Smuzhiyun 201*4882a593Smuzhiyun if args.xml: 202*4882a593Smuzhiyun result.write_results_xml() 203*4882a593Smuzhiyun else: 204*4882a593Smuzhiyun result.write_results_json() 205*4882a593Smuzhiyun result.write_buildstats_json() 206*4882a593Smuzhiyun if args.globalres_file: 207*4882a593Smuzhiyun update_globalres_file(result, args.globalres_file, metadata) 208*4882a593Smuzhiyun if result.wasSuccessful(): 209*4882a593Smuzhiyun return 0 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun return 2 212*4882a593Smuzhiyun 213*4882a593Smuzhiyun 214*4882a593Smuzhiyunif __name__ == '__main__': 215*4882a593Smuzhiyun sys.exit(main()) 216*4882a593Smuzhiyun 217