blob: 638e195efb726d10b76f5b347dfacebf71ee0582 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001#!/usr/bin/python3
2#
3# Build performance test script
4#
5# Copyright (c) 2016, Intel Corporation.
6#
7# This program is free software; you can redistribute it and/or modify it
8# under the terms and conditions of the GNU General Public License,
9# version 2, as published by the Free Software Foundation.
10#
11# This program is distributed in the hope it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14# more details.
15#
16"""Build performance test script"""
17import argparse
18import errno
19import fcntl
20import logging
21import os
22import shutil
23import sys
24import unittest
25from datetime import datetime
26
27sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/lib')
28import scriptpath
29scriptpath.add_oe_lib_path()
30import oeqa.buildperf
31from oeqa.buildperf import (BuildPerfTestLoader, BuildPerfTestResult,
32 BuildPerfTestRunner, KernelDropCaches)
33from oeqa.utils.commands import runCmd
34from oeqa.utils.git import GitRepo, GitError
35
36
37# Set-up logging
38LOG_FORMAT = '[%(asctime)s] %(levelname)s: %(message)s'
39logging.basicConfig(level=logging.INFO, format=LOG_FORMAT,
40 datefmt='%Y-%m-%d %H:%M:%S')
41log = logging.getLogger()
42
43
44def acquire_lock(lock_f):
45 """Acquire flock on file"""
46 log.debug("Acquiring lock %s", os.path.abspath(lock_f.name))
47 try:
48 fcntl.flock(lock_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
49 except IOError as err:
50 if err.errno == errno.EAGAIN:
51 return False
52 raise
53 log.debug("Lock acquired")
54 return True
55
56
57def pre_run_sanity_check():
58 """Sanity check of build environment"""
59 build_dir = os.environ.get("BUILDDIR")
60 if not build_dir:
61 log.error("BUILDDIR not set. Please run the build environmnent setup "
62 "script.")
63 return False
64 if os.getcwd() != build_dir:
65 log.error("Please run this script under BUILDDIR (%s)", build_dir)
66 return False
67
68 ret = runCmd('which bitbake', ignore_status=True)
69 if ret.status:
70 log.error("bitbake command not found")
71 return False
72 return True
73
74def init_git_repo(path):
75 """Check/create Git repository where to store results"""
76 path = os.path.abspath(path)
77 if os.path.isfile(path):
78 log.error("Invalid Git repo %s: path exists but is not a directory", path)
79 return False
80 if not os.path.isdir(path):
81 try:
82 os.mkdir(path)
83 except (FileNotFoundError, PermissionError) as err:
84 log.error("Failed to mkdir %s: %s", path, err)
85 return False
86 if not os.listdir(path):
87 log.info("Initializing a new Git repo at %s", path)
88 GitRepo.init(path)
89 try:
90 GitRepo(path, is_topdir=True)
91 except GitError:
92 log.error("No Git repository but a non-empty directory found at %s.\n"
93 "Please specify a Git repository, an empty directory or "
94 "a non-existing directory", path)
95 return False
96 return True
97
98
99def setup_file_logging(log_file):
100 """Setup loggin to file"""
101 log_dir = os.path.dirname(log_file)
102 if not os.path.exists(log_dir):
103 os.makedirs(log_dir)
104 formatter = logging.Formatter(LOG_FORMAT)
105 handler = logging.FileHandler(log_file)
106 handler.setFormatter(formatter)
107 log.addHandler(handler)
108
109
110def archive_build_conf(out_dir):
111 """Archive build/conf to test results"""
112 src_dir = os.path.join(os.environ['BUILDDIR'], 'conf')
113 tgt_dir = os.path.join(out_dir, 'build', 'conf')
114 os.makedirs(os.path.dirname(tgt_dir))
115 shutil.copytree(src_dir, tgt_dir)
116
117
118def parse_args(argv):
119 """Parse command line arguments"""
120 parser = argparse.ArgumentParser(
121 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
122
123 parser.add_argument('-D', '--debug', action='store_true',
124 help='Enable debug level logging')
125 parser.add_argument('--globalres-file',
126 type=os.path.abspath,
127 help="Append results to 'globalres' csv file")
128 parser.add_argument('--lock-file', default='./oe-build-perf.lock',
129 metavar='FILENAME', type=os.path.abspath,
130 help="Lock file to use")
131 parser.add_argument('-o', '--out-dir', default='results-{date}',
132 type=os.path.abspath,
133 help="Output directory for test results")
134 parser.add_argument('--log-file',
135 default='{out_dir}/oe-build-perf-test.log',
136 help="Log file of this script")
137 parser.add_argument('--run-tests', nargs='+', metavar='TEST',
138 help="List of tests to run")
139 parser.add_argument('--commit-results', metavar='GIT_DIR',
140 type=os.path.abspath,
141 help="Commit result data to a (local) git repository")
142 parser.add_argument('--commit-results-branch', metavar='BRANCH',
143 default="{git_branch}",
144 help="Commit results to branch BRANCH.")
145 parser.add_argument('--commit-results-tag', metavar='TAG',
146 default="{git_branch}/{git_commit_count}-g{git_commit}/{tag_num}",
147 help="Tag results commit with TAG.")
148
149 return parser.parse_args(argv)
150
151
152def main(argv=None):
153 """Script entry point"""
154 args = parse_args(argv)
155
156 # Set-up log file
157 out_dir = args.out_dir.format(date=datetime.now().strftime('%Y%m%d%H%M%S'))
158 setup_file_logging(args.log_file.format(out_dir=out_dir))
159
160 if args.debug:
161 log.setLevel(logging.DEBUG)
162
163 lock_f = open(args.lock_file, 'w')
164 if not acquire_lock(lock_f):
165 log.error("Another instance of this script is running, exiting...")
166 return 1
167
168 if not pre_run_sanity_check():
169 return 1
170 if args.commit_results:
171 if not init_git_repo(args.commit_results):
172 return 1
173
174 # Check our capability to drop caches and ask pass if needed
175 KernelDropCaches.check()
176
177 # Load build perf tests
178 loader = BuildPerfTestLoader()
179 if args.run_tests:
180 suite = loader.loadTestsFromNames(args.run_tests, oeqa.buildperf)
181 else:
182 suite = loader.loadTestsFromModule(oeqa.buildperf)
183
184 archive_build_conf(out_dir)
185 runner = BuildPerfTestRunner(out_dir, verbosity=2)
186
187 # Suppress logger output to stderr so that the output from unittest
188 # is not mixed with occasional logger output
189 log.handlers[0].setLevel(logging.CRITICAL)
190
191 # Run actual tests
192 result = runner.run(suite)
193
194 # Restore logger output to stderr
195 log.handlers[0].setLevel(log.level)
196
197 if args.globalres_file:
198 result.update_globalres_file(args.globalres_file)
199 if args.commit_results:
200 result.git_commit_results(args.commit_results,
201 args.commit_results_branch,
202 args.commit_results_tag)
203 if result.wasSuccessful():
204 return 0
205
206 return 2
207
208
209if __name__ == '__main__':
210 sys.exit(main())
211