xref: /OK3568_Linux_fs/yocto/poky/scripts/oe-build-perf-test (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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