Skip to content

Commit 91b0584

Browse files
committed
[libc++] Add a tool to produce historical libc++ benchmark results
This is extremely useful for analysis purposes like finding regressions. The ability to run such historical analysis locally is extremely useful for doing quick investigations that may involve non-mainstream libc++ configurations.
1 parent 152a216 commit 91b0584

File tree

2 files changed

+114
-12
lines changed

2 files changed

+114
-12
lines changed

libcxx/utils/benchmark-historical

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import logging
5+
import os
6+
import pathlib
7+
import subprocess
8+
import sys
9+
import tempfile
10+
11+
PARENT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
12+
13+
def directory_path(string):
14+
if os.path.isdir(string):
15+
return pathlib.Path(string)
16+
else:
17+
raise NotADirectoryError(string)
18+
19+
def whitespace_separated(stream):
20+
"""
21+
Iterate over a stream, yielding whitespace-delimited elements.
22+
"""
23+
for line in stream:
24+
for element in line.split():
25+
yield element
26+
27+
def resolve_commit(git_repo, commit):
28+
"""
29+
Resolve the full commit SHA from any tree-ish.
30+
"""
31+
return subprocess.check_output(['git', '-C', git_repo, 'rev-parse', commit], text=True).strip()
32+
33+
34+
def main(argv):
35+
parser = argparse.ArgumentParser(
36+
prog='benchmark-historical',
37+
description='Run the libc++ benchmarks against the commits provided on standard input and store the results in '
38+
'LNT format in a directory. This makes it easy to generate historical benchmark results of libc++ '
39+
'for analysis purposes. This script\'s usage is optimized to be run on a set of commits and then '
40+
're-run on a potentially-overlapping set of commits, such as after pulling new commits with Git.')
41+
parser.add_argument('--output', '-o', type=pathlib.Path, required=True,
42+
help='Path to the directory where the resulting .lnt files are stored.')
43+
parser.add_argument('--commit-list', type=argparse.FileType('r'), default=sys.stdin,
44+
help='Path to a file containing a whitespace separated list of commits to test. '
45+
'By default, this is read from standard input.')
46+
parser.add_argument('--overwrite', action='store_true',
47+
help='When the data for a commit already exists in the output directory, the tool normally skips it. '
48+
'This option instructs the tool to generate the data and overwrite it in the output directory.')
49+
parser.add_argument('lit_options', nargs=argparse.REMAINDER,
50+
help='Optional arguments passed to lit when running the tests. Should be provided last and '
51+
'separated from other arguments with a `--`.')
52+
parser.add_argument('--git-repo', type=directory_path, default=pathlib.Path(os.getcwd()),
53+
help='Optional path to the Git repository to use. By default, the current working directory is used.')
54+
parser.add_argument('--dry-run', action='store_true',
55+
help='Do not actually run anything, just print what would be done.')
56+
args = parser.parse_args(argv)
57+
58+
logging.getLogger().setLevel(logging.INFO)
59+
60+
# Gather lit options
61+
lit_options = []
62+
if args.lit_options:
63+
if args.lit_options[0] != '--':
64+
raise ArgumentError('For clarity, Lit options must be separated from other options by --')
65+
lit_options = args.lit_options[1:]
66+
67+
# Process commits one by one. Commits just need to be whitespace separated: we also handle
68+
# the case where there is more than one commit per line.
69+
for commit in whitespace_separated(args.commit_list):
70+
commit = resolve_commit(args.git_repo, commit) # resolve e.g. HEAD to a real SHA
71+
72+
output_file = args.output / (commit + '.lnt')
73+
if output_file.exists():
74+
if args.overwrite:
75+
logging.info(f'Will overwrite data for commit {commit} in {output_file}')
76+
else:
77+
logging.info(f'Data for commit {commit} already exists in {output_file}, skipping')
78+
continue
79+
else:
80+
logging.info(f'Benchmarking commit {commit}')
81+
82+
with tempfile.TemporaryDirectory() as build_dir:
83+
test_cmd = [PARENT_DIR / 'test-at-commit', '--git-repo', args.git_repo,
84+
'--build', build_dir,
85+
'--commit', commit]
86+
test_cmd += ['--'] + lit_options
87+
88+
if args.dry_run:
89+
pretty = ' '.join(str(a) for a in test_cmd)
90+
logging.info(f'Running {pretty}')
91+
continue
92+
93+
if subprocess.call(test_cmd) != 0:
94+
logging.error(f'Failed to run the tests for commit {commit}, skipping')
95+
continue
96+
97+
output_file.parent.mkdir(parents=True, exist_ok=True)
98+
consolidate_cmd = [(PARENT_DIR / 'consolidate-benchmarks'), build_dir, '--output', output_file]
99+
subprocess.check_call(consolidate_cmd)
100+
101+
if __name__ == '__main__':
102+
main(sys.argv[1:])

libcxx/utils/test-at-commit

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#!/usr/bin/env python3
22

33
import argparse
4-
import logging
54
import os
5+
import pathlib
66
import subprocess
77
import sys
88
import tempfile
99

10-
PARENT_DIR = os.path.dirname(os.path.abspath(__file__))
10+
PARENT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
1111

1212
LIT_CONFIG_FILE = """
1313
#
@@ -37,7 +37,7 @@ libcxx.test.config.configure(
3737

3838
def directory_path(string):
3939
if os.path.isdir(string):
40-
return string
40+
return pathlib.Path(string)
4141
else:
4242
raise NotADirectoryError(string)
4343

@@ -50,14 +50,14 @@ def main(argv):
5050
'performance data, bisect issues, and so on. '
5151
'A current limitation of this script is that it assumes the arguments passed to CMake when '
5252
'building the library.')
53-
parser.add_argument('--build', '-B', type=str, required=True,
53+
parser.add_argument('--build', '-B', type=pathlib.Path, required=True,
5454
help='Path to create the build directory for running the test suite at.')
5555
parser.add_argument('--commit', type=str, required=True,
5656
help='Commit to build libc++ at.')
5757
parser.add_argument('lit_options', nargs=argparse.REMAINDER,
5858
help='Optional arguments passed to lit when running the tests. Should be provided last and '
5959
'separated from other arguments with a `--`.')
60-
parser.add_argument('--git-repo', type=directory_path, default=os.getcwd(),
60+
parser.add_argument('--git-repo', type=directory_path, default=pathlib.Path(os.getcwd()),
6161
help='Optional path to the Git repository to use. By default, the current working directory is used.')
6262
args = parser.parse_args(argv)
6363

@@ -70,26 +70,26 @@ def main(argv):
7070

7171
with tempfile.TemporaryDirectory() as install_dir:
7272
# Build the library at the baseline
73-
build_cmd = [os.path.join(PARENT_DIR, 'build-at-commit'), '--git-repo', args.git_repo,
74-
'--install-dir', install_dir,
75-
'--commit', args.commit]
73+
build_cmd = [PARENT_DIR / 'build-at-commit', '--git-repo', args.git_repo,
74+
'--install-dir', install_dir,
75+
'--commit', args.commit]
7676
build_cmd += ['--', '-DCMAKE_BUILD_TYPE=RelWithDebInfo']
7777
subprocess.check_call(build_cmd)
7878

7979
# Configure the test suite in the specified build directory
80-
os.makedirs(args.build)
81-
lit_cfg = os.path.abspath(os.path.join(args.build, 'temp_lit_cfg.cfg.in'))
80+
args.build.mkdir(parents=True, exist_ok=True)
81+
lit_cfg = (args.build / 'temp_lit_cfg.cfg.in').absolute()
8282
with open(lit_cfg, 'w') as f:
8383
f.write(LIT_CONFIG_FILE.format(INSTALL_ROOT=install_dir))
8484

85-
test_suite_cmd = ['cmake', '-B', args.build, '-S', os.path.join(args.git_repo, 'runtimes'), '-G', 'Ninja']
85+
test_suite_cmd = ['cmake', '-B', args.build, '-S', args.git_repo / 'runtimes', '-G', 'Ninja']
8686
test_suite_cmd += ['-D', 'LLVM_ENABLE_RUNTIMES=libcxx;libcxxabi']
8787
test_suite_cmd += ['-D', 'LIBCXXABI_USE_LLVM_UNWINDER=OFF']
8888
test_suite_cmd += ['-D', f'LIBCXX_TEST_CONFIG={lit_cfg}']
8989
subprocess.check_call(test_suite_cmd)
9090

9191
# Run the specified tests against the produced baseline installation
92-
lit_cmd = [os.path.join(PARENT_DIR, 'libcxx-lit'), args.build] + lit_options
92+
lit_cmd = [PARENT_DIR / 'libcxx-lit', args.build] + lit_options
9393
subprocess.check_call(lit_cmd)
9494

9595
if __name__ == '__main__':

0 commit comments

Comments
 (0)