#!/usr/bin/env python3 
 | 
# 
 | 
# Build performance test script 
 | 
# 
 | 
# Copyright (c) 2016, Intel Corporation. 
 | 
# 
 | 
# SPDX-License-Identifier: GPL-2.0-only 
 | 
# 
 | 
  
 | 
import argparse 
 | 
import errno 
 | 
import fcntl 
 | 
import json 
 | 
import logging 
 | 
import os 
 | 
import re 
 | 
import shutil 
 | 
import sys 
 | 
from datetime import datetime 
 | 
  
 | 
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/lib') 
 | 
import scriptpath 
 | 
scriptpath.add_oe_lib_path() 
 | 
scriptpath.add_bitbake_lib_path() 
 | 
import oeqa.buildperf 
 | 
from oeqa.buildperf import (BuildPerfTestLoader, BuildPerfTestResult, 
 | 
                            BuildPerfTestRunner, KernelDropCaches) 
 | 
from oeqa.utils.commands import runCmd 
 | 
from oeqa.utils.metadata import metadata_from_bb, write_metadata_file 
 | 
  
 | 
  
 | 
# Set-up logging 
 | 
LOG_FORMAT = '[%(asctime)s] %(levelname)s: %(message)s' 
 | 
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT, 
 | 
                    datefmt='%Y-%m-%d %H:%M:%S') 
 | 
log = logging.getLogger() 
 | 
  
 | 
  
 | 
def acquire_lock(lock_f): 
 | 
    """Acquire flock on file""" 
 | 
    log.debug("Acquiring lock %s", os.path.abspath(lock_f.name)) 
 | 
    try: 
 | 
        fcntl.flock(lock_f, fcntl.LOCK_EX | fcntl.LOCK_NB) 
 | 
    except IOError as err: 
 | 
        if err.errno == errno.EAGAIN: 
 | 
            return False 
 | 
        raise 
 | 
    log.debug("Lock acquired") 
 | 
    return True 
 | 
  
 | 
  
 | 
def pre_run_sanity_check(): 
 | 
    """Sanity check of build environment""" 
 | 
    build_dir = os.environ.get("BUILDDIR") 
 | 
    if not build_dir: 
 | 
        log.error("BUILDDIR not set. Please run the build environmnent setup " 
 | 
                  "script.") 
 | 
        return False 
 | 
    if os.getcwd() != build_dir: 
 | 
        log.error("Please run this script under BUILDDIR (%s)", build_dir) 
 | 
        return False 
 | 
  
 | 
    ret = runCmd('which bitbake', ignore_status=True) 
 | 
    if ret.status: 
 | 
        log.error("bitbake command not found") 
 | 
        return False 
 | 
    return True 
 | 
  
 | 
def setup_file_logging(log_file): 
 | 
    """Setup loggin to file""" 
 | 
    log_dir = os.path.dirname(log_file) 
 | 
    if not os.path.exists(log_dir): 
 | 
        os.makedirs(log_dir) 
 | 
    formatter = logging.Formatter(LOG_FORMAT) 
 | 
    handler = logging.FileHandler(log_file) 
 | 
    handler.setFormatter(formatter) 
 | 
    log.addHandler(handler) 
 | 
  
 | 
  
 | 
def archive_build_conf(out_dir): 
 | 
    """Archive build/conf to test results""" 
 | 
    src_dir = os.path.join(os.environ['BUILDDIR'], 'conf') 
 | 
    tgt_dir = os.path.join(out_dir, 'build', 'conf') 
 | 
    os.makedirs(os.path.dirname(tgt_dir)) 
 | 
    shutil.copytree(src_dir, tgt_dir) 
 | 
  
 | 
  
 | 
def update_globalres_file(result_obj, filename, metadata): 
 | 
    """Write results to globalres csv file""" 
 | 
    # Map test names to time and size columns in globalres 
 | 
    # The tuples represent index and length of times and sizes 
 | 
    # respectively 
 | 
    gr_map = {'test1': ((0, 1), (8, 1)), 
 | 
              'test12': ((1, 1), (None, None)), 
 | 
              'test13': ((2, 1), (9, 1)), 
 | 
              'test2': ((3, 1), (None, None)), 
 | 
              'test3': ((4, 3), (None, None)), 
 | 
              'test4': ((7, 1), (10, 2))} 
 | 
  
 | 
    values = ['0'] * 12 
 | 
    for status, test, _ in result_obj.all_results(): 
 | 
        if status in ['ERROR', 'SKIPPED']: 
 | 
            continue 
 | 
        (t_ind, t_len), (s_ind, s_len) = gr_map[test.name] 
 | 
        if t_ind is not None: 
 | 
            values[t_ind:t_ind + t_len] = test.times 
 | 
        if s_ind is not None: 
 | 
            values[s_ind:s_ind + s_len] = test.sizes 
 | 
  
 | 
    log.debug("Writing globalres log to %s", filename) 
 | 
    rev_info = metadata['layers']['meta'] 
 | 
    with open(filename, 'a') as fobj: 
 | 
        fobj.write('{},{}:{},{},'.format(metadata['hostname'], 
 | 
                                         rev_info['branch'], 
 | 
                                         rev_info['commit'], 
 | 
                                         rev_info['commit'])) 
 | 
        fobj.write(','.join(values) + '\n') 
 | 
  
 | 
  
 | 
def parse_args(argv): 
 | 
    """Parse command line arguments""" 
 | 
    parser = argparse.ArgumentParser( 
 | 
        formatter_class=argparse.ArgumentDefaultsHelpFormatter) 
 | 
  
 | 
    parser.add_argument('-D', '--debug', action='store_true', 
 | 
                        help='Enable debug level logging') 
 | 
    parser.add_argument('--globalres-file', 
 | 
                        type=os.path.abspath, 
 | 
                        help="Append results to 'globalres' csv file") 
 | 
    parser.add_argument('--lock-file', default='./oe-build-perf.lock', 
 | 
                        metavar='FILENAME', type=os.path.abspath, 
 | 
                        help="Lock file to use") 
 | 
    parser.add_argument('-o', '--out-dir', default='results-{date}', 
 | 
                        type=os.path.abspath, 
 | 
                        help="Output directory for test results") 
 | 
    parser.add_argument('-x', '--xml', action='store_true', 
 | 
                        help='Enable JUnit xml output') 
 | 
    parser.add_argument('--log-file', 
 | 
                        default='{out_dir}/oe-build-perf-test.log', 
 | 
                        help="Log file of this script") 
 | 
    parser.add_argument('--run-tests', nargs='+', metavar='TEST', 
 | 
                        help="List of tests to run") 
 | 
  
 | 
    return parser.parse_args(argv) 
 | 
  
 | 
  
 | 
def main(argv=None): 
 | 
    """Script entry point""" 
 | 
    args = parse_args(argv) 
 | 
  
 | 
    # Set-up log file 
 | 
    out_dir = args.out_dir.format(date=datetime.now().strftime('%Y%m%d%H%M%S')) 
 | 
    setup_file_logging(args.log_file.format(out_dir=out_dir)) 
 | 
  
 | 
    if args.debug: 
 | 
        log.setLevel(logging.DEBUG) 
 | 
  
 | 
    lock_f = open(args.lock_file, 'w') 
 | 
    if not acquire_lock(lock_f): 
 | 
        log.error("Another instance of this script is running, exiting...") 
 | 
        return 1 
 | 
  
 | 
    if not pre_run_sanity_check(): 
 | 
        return 1 
 | 
  
 | 
    # Check our capability to drop caches and ask pass if needed 
 | 
    KernelDropCaches.check() 
 | 
  
 | 
    # Load build perf tests 
 | 
    loader = BuildPerfTestLoader() 
 | 
    if args.run_tests: 
 | 
        suite = loader.loadTestsFromNames(args.run_tests, oeqa.buildperf) 
 | 
    else: 
 | 
        suite = loader.loadTestsFromModule(oeqa.buildperf) 
 | 
  
 | 
    # Save test metadata 
 | 
    metadata = metadata_from_bb() 
 | 
    log.info("Testing Git revision branch:commit %s:%s (%s)", 
 | 
             metadata['layers']['meta']['branch'], 
 | 
             metadata['layers']['meta']['commit'], 
 | 
             metadata['layers']['meta']['commit_count']) 
 | 
    if args.xml: 
 | 
        write_metadata_file(os.path.join(out_dir, 'metadata.xml'), metadata) 
 | 
    else: 
 | 
        with open(os.path.join(out_dir, 'metadata.json'), 'w') as fobj: 
 | 
            json.dump(metadata, fobj, indent=2) 
 | 
    archive_build_conf(out_dir) 
 | 
  
 | 
    runner = BuildPerfTestRunner(out_dir, verbosity=2) 
 | 
  
 | 
    # Suppress logger output to stderr so that the output from unittest 
 | 
    # is not mixed with occasional logger output 
 | 
    log.handlers[0].setLevel(logging.CRITICAL) 
 | 
  
 | 
    # Run actual tests 
 | 
    result = runner.run(suite) 
 | 
  
 | 
    # Restore logger output to stderr 
 | 
    log.handlers[0].setLevel(log.level) 
 | 
  
 | 
    if args.xml: 
 | 
        result.write_results_xml() 
 | 
    else: 
 | 
        result.write_results_json() 
 | 
    result.write_buildstats_json() 
 | 
    if args.globalres_file: 
 | 
        update_globalres_file(result, args.globalres_file, metadata) 
 | 
    if result.wasSuccessful(): 
 | 
        return 0 
 | 
  
 | 
    return 2 
 | 
  
 | 
  
 | 
if __name__ == '__main__': 
 | 
    sys.exit(main()) 
 |