blob: 6e62b279c1dc383ed3fabdd5434412b228c8d8b1 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001# Copyright (c) 2016, Intel Corporation.
2#
3# This program is free software; you can redistribute it and/or modify it
4# under the terms and conditions of the GNU General Public License,
5# version 2, as published by the Free Software Foundation.
6#
7# This program is distributed in the hope it will be useful, but WITHOUT
8# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
9# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
10# more details.
11#
12"""Build performance test base classes and functionality"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -060013import json
14import logging
15import os
16import re
17import resource
Patrick Williamsc0f7c042017-02-23 20:41:17 -060018import socket
Brad Bishop6e60e8b2018-02-01 10:27:11 -050019import shutil
Patrick Williamsc0f7c042017-02-23 20:41:17 -060020import time
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021import unittest
Brad Bishop6e60e8b2018-02-01 10:27:11 -050022import xml.etree.ElementTree as ET
23from collections import OrderedDict
Patrick Williamsc0f7c042017-02-23 20:41:17 -060024from datetime import datetime, timedelta
25from functools import partial
26from multiprocessing import Process
27from multiprocessing import SimpleQueue
Brad Bishop6e60e8b2018-02-01 10:27:11 -050028from xml.dom import minidom
Patrick Williamsc0f7c042017-02-23 20:41:17 -060029
30import oe.path
31from oeqa.utils.commands import CommandError, runCmd, get_bb_vars
32from oeqa.utils.git import GitError, GitRepo
33
34# Get logger for this module
35log = logging.getLogger('build-perf')
36
37# Our own version of runCmd which does not raise AssertErrors which would cause
38# errors to interpreted as failures
Brad Bishop6e60e8b2018-02-01 10:27:11 -050039runCmd2 = partial(runCmd, assert_error=False, limit_exc_output=40)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060040
41
42class KernelDropCaches(object):
43 """Container of the functions for dropping kernel caches"""
44 sudo_passwd = None
45
46 @classmethod
47 def check(cls):
48 """Check permssions for dropping kernel caches"""
49 from getpass import getpass
50 from locale import getdefaultlocale
51 cmd = ['sudo', '-k', '-n', 'tee', '/proc/sys/vm/drop_caches']
52 ret = runCmd2(cmd, ignore_status=True, data=b'0')
53 if ret.output.startswith('sudo:'):
54 pass_str = getpass(
55 "\nThe script requires sudo access to drop caches between "
56 "builds (echo 3 > /proc/sys/vm/drop_caches).\n"
57 "Please enter your sudo password: ")
58 cls.sudo_passwd = bytes(pass_str, getdefaultlocale()[1])
59
60 @classmethod
61 def drop(cls):
62 """Drop kernel caches"""
63 cmd = ['sudo', '-k']
64 if cls.sudo_passwd:
65 cmd.append('-S')
66 input_data = cls.sudo_passwd + b'\n'
67 else:
68 cmd.append('-n')
69 input_data = b''
70 cmd += ['tee', '/proc/sys/vm/drop_caches']
71 input_data += b'3'
72 runCmd2(cmd, data=input_data)
73
74
75def str_to_fn(string):
76 """Convert string to a sanitized filename"""
77 return re.sub(r'(\W+)', '-', string, flags=re.LOCALE)
78
79
80class ResultsJsonEncoder(json.JSONEncoder):
81 """Extended encoder for build perf test results"""
82 unix_epoch = datetime.utcfromtimestamp(0)
83
84 def default(self, obj):
85 """Encoder for our types"""
86 if isinstance(obj, datetime):
87 # NOTE: we assume that all timestamps are in UTC time
88 return (obj - self.unix_epoch).total_seconds()
89 if isinstance(obj, timedelta):
90 return obj.total_seconds()
91 return json.JSONEncoder.default(self, obj)
92
93
94class BuildPerfTestResult(unittest.TextTestResult):
95 """Runner class for executing the individual tests"""
96 # List of test cases to run
97 test_run_queue = []
98
99 def __init__(self, out_dir, *args, **kwargs):
100 super(BuildPerfTestResult, self).__init__(*args, **kwargs)
101
102 self.out_dir = out_dir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600103 self.hostname = socket.gethostname()
104 self.product = os.getenv('OE_BUILDPERFTEST_PRODUCT', 'oe-core')
105 self.start_time = self.elapsed_time = None
106 self.successes = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600107
108 def addSuccess(self, test):
109 """Record results from successful tests"""
110 super(BuildPerfTestResult, self).addSuccess(test)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500111 self.successes.append(test)
112
113 def addError(self, test, err):
114 """Record results from crashed test"""
115 test.err = err
116 super(BuildPerfTestResult, self).addError(test, err)
117
118 def addFailure(self, test, err):
119 """Record results from failed test"""
120 test.err = err
121 super(BuildPerfTestResult, self).addFailure(test, err)
122
123 def addExpectedFailure(self, test, err):
124 """Record results from expectedly failed test"""
125 test.err = err
126 super(BuildPerfTestResult, self).addExpectedFailure(test, err)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600127
128 def startTest(self, test):
129 """Pre-test hook"""
130 test.base_dir = self.out_dir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600131 log.info("Executing test %s: %s", test.name, test.shortDescription())
132 self.stream.write(datetime.now().strftime("[%Y-%m-%d %H:%M:%S] "))
133 super(BuildPerfTestResult, self).startTest(test)
134
135 def startTestRun(self):
136 """Pre-run hook"""
137 self.start_time = datetime.utcnow()
138
139 def stopTestRun(self):
140 """Pre-run hook"""
141 self.elapsed_time = datetime.utcnow() - self.start_time
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600142
143 def all_results(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500144 compound = [('SUCCESS', t, None) for t in self.successes] + \
145 [('FAILURE', t, m) for t, m in self.failures] + \
146 [('ERROR', t, m) for t, m in self.errors] + \
147 [('EXPECTED_FAILURE', t, m) for t, m in self.expectedFailures] + \
148 [('UNEXPECTED_SUCCESS', t, None) for t in self.unexpectedSuccesses] + \
149 [('SKIPPED', t, m) for t, m in self.skipped]
150 return sorted(compound, key=lambda info: info[1].start_time)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600151
152
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500153 def write_buildstats_json(self):
154 """Write buildstats file"""
155 buildstats = OrderedDict()
156 for _, test, _ in self.all_results():
157 for key, val in test.buildstats.items():
158 buildstats[test.name + '.' + key] = val
159 with open(os.path.join(self.out_dir, 'buildstats.json'), 'w') as fobj:
160 json.dump(buildstats, fobj, cls=ResultsJsonEncoder)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600161
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600162
163 def write_results_json(self):
164 """Write test results into a json-formatted file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500165 results = OrderedDict([('tester_host', self.hostname),
166 ('start_time', self.start_time),
167 ('elapsed_time', self.elapsed_time),
168 ('tests', OrderedDict())])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600169
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500170 for status, test, reason in self.all_results():
171 test_result = OrderedDict([('name', test.name),
172 ('description', test.shortDescription()),
173 ('status', status),
174 ('start_time', test.start_time),
175 ('elapsed_time', test.elapsed_time),
176 ('measurements', test.measurements)])
177 if status in ('ERROR', 'FAILURE', 'EXPECTED_FAILURE'):
178 test_result['message'] = str(test.err[1])
179 test_result['err_type'] = test.err[0].__name__
180 test_result['err_output'] = reason
181 elif reason:
182 test_result['message'] = reason
183
184 results['tests'][test.name] = test_result
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600185
186 with open(os.path.join(self.out_dir, 'results.json'), 'w') as fobj:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500187 json.dump(results, fobj, indent=4,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600188 cls=ResultsJsonEncoder)
189
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500190 def write_results_xml(self):
191 """Write test results into a JUnit XML file"""
192 top = ET.Element('testsuites')
193 suite = ET.SubElement(top, 'testsuite')
194 suite.set('name', 'oeqa.buildperf')
195 suite.set('timestamp', self.start_time.isoformat())
196 suite.set('time', str(self.elapsed_time.total_seconds()))
197 suite.set('hostname', self.hostname)
198 suite.set('failures', str(len(self.failures) + len(self.expectedFailures)))
199 suite.set('errors', str(len(self.errors)))
200 suite.set('skipped', str(len(self.skipped)))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600201
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500202 test_cnt = 0
203 for status, test, reason in self.all_results():
204 test_cnt += 1
205 testcase = ET.SubElement(suite, 'testcase')
206 testcase.set('classname', test.__module__ + '.' + test.__class__.__name__)
207 testcase.set('name', test.name)
208 testcase.set('description', test.shortDescription())
209 testcase.set('timestamp', test.start_time.isoformat())
210 testcase.set('time', str(test.elapsed_time.total_seconds()))
211 if status in ('ERROR', 'FAILURE', 'EXP_FAILURE'):
212 if status in ('FAILURE', 'EXP_FAILURE'):
213 result = ET.SubElement(testcase, 'failure')
214 else:
215 result = ET.SubElement(testcase, 'error')
216 result.set('message', str(test.err[1]))
217 result.set('type', test.err[0].__name__)
218 result.text = reason
219 elif status == 'SKIPPED':
220 result = ET.SubElement(testcase, 'skipped')
221 result.text = reason
222 elif status not in ('SUCCESS', 'UNEXPECTED_SUCCESS'):
223 raise TypeError("BUG: invalid test status '%s'" % status)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600224
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500225 for data in test.measurements.values():
226 measurement = ET.SubElement(testcase, data['type'])
227 measurement.set('name', data['name'])
228 measurement.set('legend', data['legend'])
229 vals = data['values']
230 if data['type'] == BuildPerfTestCase.SYSRES:
231 ET.SubElement(measurement, 'time',
232 timestamp=vals['start_time'].isoformat()).text = \
233 str(vals['elapsed_time'].total_seconds())
234 attrib = dict((k, str(v)) for k, v in vals['iostat'].items())
235 ET.SubElement(measurement, 'iostat', attrib=attrib)
236 attrib = dict((k, str(v)) for k, v in vals['rusage'].items())
237 ET.SubElement(measurement, 'rusage', attrib=attrib)
238 elif data['type'] == BuildPerfTestCase.DISKUSAGE:
239 ET.SubElement(measurement, 'size').text = str(vals['size'])
240 else:
241 raise TypeError('BUG: unsupported measurement type')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500243 suite.set('tests', str(test_cnt))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600244
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500245 # Use minidom for pretty-printing
246 dom_doc = minidom.parseString(ET.tostring(top, 'utf-8'))
247 with open(os.path.join(self.out_dir, 'results.xml'), 'w') as fobj:
248 dom_doc.writexml(fobj, addindent=' ', newl='\n', encoding='utf-8')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600249
250
251class BuildPerfTestCase(unittest.TestCase):
252 """Base class for build performance tests"""
253 SYSRES = 'sysres'
254 DISKUSAGE = 'diskusage'
255 build_target = None
256
257 def __init__(self, *args, **kwargs):
258 super(BuildPerfTestCase, self).__init__(*args, **kwargs)
259 self.name = self._testMethodName
260 self.base_dir = None
261 self.start_time = None
262 self.elapsed_time = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500263 self.measurements = OrderedDict()
264 self.buildstats = OrderedDict()
265 # self.err is supposed to be a tuple from sys.exc_info()
266 self.err = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600267 self.bb_vars = get_bb_vars()
268 # TODO: remove 'times' and 'sizes' arrays when globalres support is
269 # removed
270 self.times = []
271 self.sizes = []
272
273 @property
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500274 def tmp_dir(self):
275 return os.path.join(self.base_dir, self.name + '.tmp')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600276
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500277 def shortDescription(self):
278 return super(BuildPerfTestCase, self).shortDescription() or ""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600279
280 def setUp(self):
281 """Set-up fixture for each test"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500282 if not os.path.isdir(self.tmp_dir):
283 os.mkdir(self.tmp_dir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600284 if self.build_target:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500285 self.run_cmd(['bitbake', self.build_target, '-c', 'fetchall'])
286
287 def tearDown(self):
288 """Tear-down fixture for each test"""
289 if os.path.isdir(self.tmp_dir):
290 shutil.rmtree(self.tmp_dir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291
292 def run(self, *args, **kwargs):
293 """Run test"""
294 self.start_time = datetime.now()
295 super(BuildPerfTestCase, self).run(*args, **kwargs)
296 self.elapsed_time = datetime.now() - self.start_time
297
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500298 def run_cmd(self, cmd):
299 """Convenience method for running a command"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600300 cmd_str = cmd if isinstance(cmd, str) else ' '.join(cmd)
301 log.info("Logging command: %s", cmd_str)
302 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500303 runCmd2(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600304 except CommandError as err:
305 log.error("Command failed: %s", err.retcode)
306 raise
307
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500308 def _append_measurement(self, measurement):
309 """Simple helper for adding measurements results"""
310 if measurement['name'] in self.measurements:
311 raise ValueError('BUG: two measurements with the same name in {}'.format(
312 self.__class__.__name__))
313 self.measurements[measurement['name']] = measurement
314
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600315 def measure_cmd_resources(self, cmd, name, legend, save_bs=False):
316 """Measure system resource usage of a command"""
317 def _worker(data_q, cmd, **kwargs):
318 """Worker process for measuring resources"""
319 try:
320 start_time = datetime.now()
321 ret = runCmd2(cmd, **kwargs)
322 etime = datetime.now() - start_time
323 rusage_struct = resource.getrusage(resource.RUSAGE_CHILDREN)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500324 iostat = OrderedDict()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600325 with open('/proc/{}/io'.format(os.getpid())) as fobj:
326 for line in fobj.readlines():
327 key, val = line.split(':')
328 iostat[key] = int(val)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500329 rusage = OrderedDict()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600330 # Skip unused fields, (i.e. 'ru_ixrss', 'ru_idrss', 'ru_isrss',
331 # 'ru_nswap', 'ru_msgsnd', 'ru_msgrcv' and 'ru_nsignals')
332 for key in ['ru_utime', 'ru_stime', 'ru_maxrss', 'ru_minflt',
333 'ru_majflt', 'ru_inblock', 'ru_oublock',
334 'ru_nvcsw', 'ru_nivcsw']:
335 rusage[key] = getattr(rusage_struct, key)
336 data_q.put({'ret': ret,
337 'start_time': start_time,
338 'elapsed_time': etime,
339 'rusage': rusage,
340 'iostat': iostat})
341 except Exception as err:
342 data_q.put(err)
343
344 cmd_str = cmd if isinstance(cmd, str) else ' '.join(cmd)
345 log.info("Timing command: %s", cmd_str)
346 data_q = SimpleQueue()
347 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500348 proc = Process(target=_worker, args=(data_q, cmd,))
349 proc.start()
350 data = data_q.get()
351 proc.join()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600352 if isinstance(data, Exception):
353 raise data
354 except CommandError:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500355 log.error("Command '%s' failed", cmd_str)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600356 raise
357 etime = data['elapsed_time']
358
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500359 measurement = OrderedDict([('type', self.SYSRES),
360 ('name', name),
361 ('legend', legend)])
362 measurement['values'] = OrderedDict([('start_time', data['start_time']),
363 ('elapsed_time', etime),
364 ('rusage', data['rusage']),
365 ('iostat', data['iostat'])])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600366 if save_bs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500367 self.save_buildstats(name)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600368
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500369 self._append_measurement(measurement)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600370
371 # Append to 'times' array for globalres log
372 e_sec = etime.total_seconds()
373 self.times.append('{:d}:{:02d}:{:05.2f}'.format(int(e_sec / 3600),
374 int((e_sec % 3600) / 60),
375 e_sec % 60))
376
377 def measure_disk_usage(self, path, name, legend, apparent_size=False):
378 """Estimate disk usage of a file or directory"""
379 cmd = ['du', '-s', '--block-size', '1024']
380 if apparent_size:
381 cmd.append('--apparent-size')
382 cmd.append(path)
383
384 ret = runCmd2(cmd)
385 size = int(ret.output.split()[0])
386 log.debug("Size of %s path is %s", path, size)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500387 measurement = OrderedDict([('type', self.DISKUSAGE),
388 ('name', name),
389 ('legend', legend)])
390 measurement['values'] = OrderedDict([('size', size)])
391 self._append_measurement(measurement)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600392 # Append to 'sizes' array for globalres log
393 self.sizes.append(str(size))
394
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500395 def save_buildstats(self, measurement_name):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600396 """Save buildstats"""
397 def split_nevr(nevr):
398 """Split name and version information from recipe "nevr" string"""
399 n_e_v, revision = nevr.rsplit('-', 1)
400 match = re.match(r'^(?P<name>\S+)-((?P<epoch>[0-9]{1,5})_)?(?P<version>[0-9]\S*)$',
401 n_e_v)
402 if not match:
403 # If we're not able to parse a version starting with a number, just
404 # take the part after last dash
405 match = re.match(r'^(?P<name>\S+)-((?P<epoch>[0-9]{1,5})_)?(?P<version>[^-]+)$',
406 n_e_v)
407 name = match.group('name')
408 version = match.group('version')
409 epoch = match.group('epoch')
410 return name, epoch, version, revision
411
412 def bs_to_json(filename):
413 """Convert (task) buildstats file into json format"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500414 bs_json = OrderedDict()
415 iostat = OrderedDict()
416 rusage = OrderedDict()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600417 with open(filename) as fobj:
418 for line in fobj.readlines():
419 key, val = line.split(':', 1)
420 val = val.strip()
421 if key == 'Started':
422 start_time = datetime.utcfromtimestamp(float(val))
423 bs_json['start_time'] = start_time
424 elif key == 'Ended':
425 end_time = datetime.utcfromtimestamp(float(val))
426 elif key.startswith('IO '):
427 split = key.split()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500428 iostat[split[1]] = int(val)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600429 elif key.find('rusage') >= 0:
430 split = key.split()
431 ru_key = split[-1]
432 if ru_key in ('ru_stime', 'ru_utime'):
433 val = float(val)
434 else:
435 val = int(val)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500436 rusage[ru_key] = rusage.get(ru_key, 0) + val
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600437 elif key == 'Status':
438 bs_json['status'] = val
439 bs_json['elapsed_time'] = end_time - start_time
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500440 bs_json['rusage'] = rusage
441 bs_json['iostat'] = iostat
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600442 return bs_json
443
444 log.info('Saving buildstats in JSON format')
445 bs_dirs = sorted(os.listdir(self.bb_vars['BUILDSTATS_BASE']))
446 if len(bs_dirs) > 1:
447 log.warning("Multiple buildstats found for test %s, only "
448 "archiving the last one", self.name)
449 bs_dir = os.path.join(self.bb_vars['BUILDSTATS_BASE'], bs_dirs[-1])
450
451 buildstats = []
452 for fname in os.listdir(bs_dir):
453 recipe_dir = os.path.join(bs_dir, fname)
454 if not os.path.isdir(recipe_dir):
455 continue
456 name, epoch, version, revision = split_nevr(fname)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500457 recipe_bs = OrderedDict((('name', name),
458 ('epoch', epoch),
459 ('version', version),
460 ('revision', revision),
461 ('tasks', OrderedDict())))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600462 for task in os.listdir(recipe_dir):
463 recipe_bs['tasks'][task] = bs_to_json(os.path.join(recipe_dir,
464 task))
465 buildstats.append(recipe_bs)
466
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500467 self.buildstats[measurement_name] = buildstats
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600468
469 def rm_tmp(self):
470 """Cleanup temporary/intermediate files and directories"""
471 log.debug("Removing temporary and cache files")
472 for name in ['bitbake.lock', 'conf/sanity_info',
473 self.bb_vars['TMPDIR']]:
474 oe.path.remove(name, recurse=True)
475
476 def rm_sstate(self):
477 """Remove sstate directory"""
478 log.debug("Removing sstate-cache")
479 oe.path.remove(self.bb_vars['SSTATE_DIR'], recurse=True)
480
481 def rm_cache(self):
482 """Drop bitbake caches"""
483 oe.path.remove(self.bb_vars['PERSISTENT_DIR'], recurse=True)
484
485 @staticmethod
486 def sync():
487 """Sync and drop kernel caches"""
488 log.debug("Syncing and dropping kernel caches""")
489 KernelDropCaches.drop()
490 os.sync()
491 # Wait a bit for all the dirty blocks to be written onto disk
492 time.sleep(3)
493
494
495class BuildPerfTestLoader(unittest.TestLoader):
496 """Test loader for build performance tests"""
497 sortTestMethodsUsing = None
498
499
500class BuildPerfTestRunner(unittest.TextTestRunner):
501 """Test loader for build performance tests"""
502 sortTestMethodsUsing = None
503
504 def __init__(self, out_dir, *args, **kwargs):
505 super(BuildPerfTestRunner, self).__init__(*args, **kwargs)
506 self.out_dir = out_dir
507
508 def _makeResult(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500509 return BuildPerfTestResult(self.out_dir, self.stream, self.descriptions,
510 self.verbosity)