forked from Imagelibrary/littlefs
Added perf.py a wrapper around Linux's perf tool for perf sampling
This provides 2 things:
1. perf integration with the bench/test runners - This is a bit tricky
with perf as it doesn't have its own way to combine perf measurements
across multiple processes. perf.py works around this by writing
everything to a zip file, using flock to synchronize. As a plus, free
compression!
2. Parsing and presentation of perf results in a format consistent with
the other CSV-based tools. This actually ran into a surprising number of
issues:
- We need to process raw events to get the information we want, this
ends up being a lot of data (~16MiB at 100Hz uncompressed), so we
paralellize the parsing of each decompressed perf file.
- perf reports raw addresses post-ASLR. It does provide sym+off which
is very useful, but to find the source of static functions we need to
reverse the ASLR by finding the delta the produces the best
symbol<->addr matches.
- This isn't related to perf, but decoding dwarf line-numbers is
really complicated. You basically need to write a tiny VM.
This also turns on perf measurement by default for the bench-runner, but at a
low frequency (100 Hz). This can be decreased or removed in the future
if it causes any slowdown.
This commit is contained in:
@@ -27,9 +27,13 @@ import time
|
||||
import toml
|
||||
|
||||
|
||||
RUNNER_PATH = 'runners/test_runner'
|
||||
RUNNER_PATH = './runners/test_runner'
|
||||
HEADER_PATH = 'runners/test_runner.h'
|
||||
|
||||
GDB_TOOL = ['gdb']
|
||||
VALGRIND_TOOL = ['valgrind']
|
||||
PERF_SCRIPT = ['./scripts/perf.py']
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1, nb=False):
|
||||
if path == '-':
|
||||
@@ -516,12 +520,25 @@ def find_runner(runner, **args):
|
||||
|
||||
# run under valgrind?
|
||||
if args.get('valgrind'):
|
||||
cmd[:0] = filter(None, [
|
||||
'valgrind',
|
||||
cmd[:0] = args['valgrind_tool'] + [
|
||||
'--leak-check=full',
|
||||
'--track-origins=yes',
|
||||
'--error-exitcode=4',
|
||||
'-q'])
|
||||
'-q']
|
||||
|
||||
# run under perf?
|
||||
if args.get('perf'):
|
||||
cmd[:0] = args['perf_script'] + list(filter(None, [
|
||||
'-R',
|
||||
'--perf-freq=%s' % args['perf_freq']
|
||||
if args.get('perf_freq') else None,
|
||||
'--perf-period=%s' % args['perf_period']
|
||||
if args.get('perf_period') else None,
|
||||
'--perf-events=%s' % args['perf_events']
|
||||
if args.get('perf_events') else None,
|
||||
'--perf-tool=%s' % args['perf_tool']
|
||||
if args.get('perf_tool') else None,
|
||||
'-o%s' % args['perf']]))
|
||||
|
||||
# other context
|
||||
if args.get('geometry'):
|
||||
@@ -799,9 +816,9 @@ def run_stage(name, runner_, ids, output_, **args):
|
||||
try:
|
||||
line = mpty.readline()
|
||||
except OSError as e:
|
||||
if e.errno == errno.EIO:
|
||||
break
|
||||
raise
|
||||
if e.errno != errno.EIO:
|
||||
raise
|
||||
break
|
||||
if not line:
|
||||
break
|
||||
last_stdout.append(line)
|
||||
@@ -1126,24 +1143,24 @@ def run(runner, test_ids=[], **args):
|
||||
cmd = runner_ + [failure.id]
|
||||
|
||||
if args.get('gdb_main'):
|
||||
cmd[:0] = ['gdb',
|
||||
cmd[:0] = args['gdb_tool'] + [
|
||||
'-ex', 'break main',
|
||||
'-ex', 'run',
|
||||
'--args']
|
||||
elif args.get('gdb_case'):
|
||||
path, lineno = find_path(runner_, failure.id, **args)
|
||||
cmd[:0] = ['gdb',
|
||||
cmd[:0] = args['gdb_tool'] + [
|
||||
'-ex', 'break %s:%d' % (path, lineno),
|
||||
'-ex', 'run',
|
||||
'--args']
|
||||
elif failure.assert_ is not None:
|
||||
cmd[:0] = ['gdb',
|
||||
cmd[:0] = args['gdb_tool'] + [
|
||||
'-ex', 'run',
|
||||
'-ex', 'frame function raise',
|
||||
'-ex', 'up 2',
|
||||
'--args']
|
||||
else:
|
||||
cmd[:0] = ['gdb',
|
||||
cmd[:0] = args['gdb_tool'] + [
|
||||
'-ex', 'run',
|
||||
'--args']
|
||||
|
||||
@@ -1188,6 +1205,7 @@ if __name__ == "__main__":
|
||||
argparse._ArgumentGroup._handle_conflict_ignore = lambda *_: None
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build and run tests.",
|
||||
allow_abbrev=False,
|
||||
conflict_handler='ignore')
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
@@ -1323,6 +1341,11 @@ if __name__ == "__main__":
|
||||
action='store_true',
|
||||
help="Drop into gdb on test failure but stop at the beginning "
|
||||
"of main.")
|
||||
test_parser.add_argument(
|
||||
'--gdb-tool',
|
||||
type=lambda x: x.split(),
|
||||
default=GDB_TOOL,
|
||||
help="Path to gdb tool to use. Defaults to %r." % GDB_TOOL)
|
||||
test_parser.add_argument(
|
||||
'--exec',
|
||||
type=lambda e: e.split(),
|
||||
@@ -1332,6 +1355,37 @@ if __name__ == "__main__":
|
||||
action='store_true',
|
||||
help="Run under Valgrind to find memory errors. Implicitly sets "
|
||||
"--isolate.")
|
||||
test_parser.add_argument(
|
||||
'--valgrind-tool',
|
||||
type=lambda x: x.split(),
|
||||
default=VALGRIND_TOOL,
|
||||
help="Path to Valgrind tool to use. Defaults to %r." % VALGRIND_TOOL)
|
||||
test_parser.add_argument(
|
||||
'--perf',
|
||||
help="Run under Linux's perf to sample performance counters, writing "
|
||||
"samples to this file.")
|
||||
test_parser.add_argument(
|
||||
'--perf-freq',
|
||||
help="perf sampling frequency. This is passed directly to the perf "
|
||||
"script.")
|
||||
test_parser.add_argument(
|
||||
'--perf-period',
|
||||
help="perf sampling period. This is passed directly to the perf "
|
||||
"script.")
|
||||
test_parser.add_argument(
|
||||
'--perf-events',
|
||||
help="perf events to record. This is passed directly to the perf "
|
||||
"script.")
|
||||
test_parser.add_argument(
|
||||
'--perf-script',
|
||||
type=lambda x: x.split(),
|
||||
default=PERF_SCRIPT,
|
||||
help="Path to the perf script to use. Defaults to %r." % PERF_SCRIPT)
|
||||
test_parser.add_argument(
|
||||
'--perf-tool',
|
||||
type=lambda x: x.split(),
|
||||
help="Path to the perf tool to use. This is passed directly to the "
|
||||
"perf script")
|
||||
|
||||
# compilation flags
|
||||
comp_parser = parser.add_argument_group('compilation options')
|
||||
@@ -1356,7 +1410,7 @@ if __name__ == "__main__":
|
||||
'-o', '--output',
|
||||
help="Output file.")
|
||||
|
||||
# runner + test_ids overlaps test_paths, so we need to do some munging here
|
||||
# runner/test_paths overlap, so need to do some munging here
|
||||
args = parser.parse_intermixed_args()
|
||||
args.test_paths = [' '.join(args.runner or [])] + args.test_ids
|
||||
args.runner = args.runner or [RUNNER_PATH]
|
||||
|
||||
Reference in New Issue
Block a user