[libcxx-commits] [libcxx] [libc++][lnt] Allow retaining build artifacts in run-benchbot (PR #205146)
via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Jun 22 10:12:16 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Louis Dionne (ldionne)
<details>
<summary>Changes</summary>
Also, as a drive-by, introduce `--results-dir` to specify where to put the JSON results instead of using `--build-dir` for that.
Assisted by Claude
---
Full diff: https://github.com/llvm/llvm-project/pull/205146.diff
3 Files Affected:
- (modified) libcxx/utils/ci/lnt/README.md (+6-2)
- (modified) libcxx/utils/ci/lnt/run-benchbot (+37-14)
- (modified) libcxx/utils/ci/lnt/run-benchmarks (+42-13)
``````````diff
diff --git a/libcxx/utils/ci/lnt/README.md b/libcxx/utils/ci/lnt/README.md
index eedd98a082c2f..ac5cbc11f4c40 100644
--- a/libcxx/utils/ci/lnt/README.md
+++ b/libcxx/utils/ci/lnt/README.md
@@ -11,8 +11,12 @@ commits:
libcxx/utils/ci/lnt/run-benchbot --llvm-root <monorepo> <builder> -- <commit1> <commit2> ...
```
-Results are stored as JSON files in `<llvm-root>/build/<builder>/` by default. Use
-`--build-dir <dir>` to override the output directory.
+Results are stored as LNT JSON files in `<llvm-root>/build/<builder>/` by default.
+Use `--results-dir <dir>` to override where these reports are written.
+
+By default, build artifacts are stored in a temporary directory and discarded after
+each run. Pass `--build-dir <dir>` to keep them; artifacts for each run are then stored
+under `<dir>/<builder>/<commit>`.
To continuously poll for un-benchmarked commits and submit results to a LNT instance:
diff --git a/libcxx/utils/ci/lnt/run-benchbot b/libcxx/utils/ci/lnt/run-benchbot
index 52fb813b9321e..387853ac52b17 100755
--- a/libcxx/utils/ci/lnt/run-benchbot
+++ b/libcxx/utils/ci/lnt/run-benchbot
@@ -22,9 +22,12 @@ ${PROGNAME} [options] <BUILDER> [-- commit ...]
--llvm-root <DIR> Path to the root of the LLVM monorepo. By default, we try
to figure it out based on the current working directory.
---build-dir <DIR> The directory to use for storing benchmark results. By default,
- this is '<llvm-root>/build/<builder>'. Note that intermediate
- build results are never kept around.
+--results-dir <DIR> The directory to use for storing benchmark results (LNT JSON files).
+ By default, this is '<llvm-root>/build/<builder>'.
+
+--build-dir <DIR> Optional directory in which to keep build artifacts. Artifacts for each
+ run are stored under '<build-dir>/<builder>/<commit>'. By default, build
+ artifacts are stored in a temporary directory and discarded after each run.
[--lnt-url <URL>] The optional URL of the LNT instance to submit results to. By
default, results are not submitted to any LNT instance.
@@ -56,6 +59,10 @@ while [[ $# -gt 0 ]]; do
MONOREPO_ROOT="${2}"
shift; shift
;;
+ --results-dir)
+ RESULTS_DIR="${2}"
+ shift; shift
+ ;;
--build-dir)
BUILD_DIR="${2}"
shift; shift
@@ -81,7 +88,7 @@ while [[ $# -gt 0 ]]; do
done
MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}"
-BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build/${BUILDER}}"
+RESULTS_DIR="${RESULTS_DIR:=${MONOREPO_ROOT}/build/${BUILDER}}"
case "${BUILDER}" in
apple-m5-clang21)
@@ -109,19 +116,21 @@ echo "***********************************************"
LNT_TEST_SUITE=libcxx2
-mkdir -p "${BUILD_DIR}"
+mkdir -p "${RESULTS_DIR}"
-# Given path/to/abcdef.json, figures out the next available path,
-# considering path/to/abcdef.{1,2,3,etc}.json.
-next_available_path() {
- local path="${1}"
- local base="${path%.json}"
+# Find the smallest suffix such that both the artifacts and the results directory names are available.
+# This ensures that we can always match artifact directories with their results.
+# The first candidate is unsuffixed ('foo.json' / 'foo'), then '.1', '.2', and so on.
+select_run_suffix() {
+ local results_base="${1}" # e.g. <results-dir>/<commit>
+ local artifacts_base="${2}" # e.g. <build-dir>/<builder>/<commit>, or empty when not keeping artifacts
+ local suffix=""
local i=1
- while [ -f "${path}" ]; do
- path="${base}.${i}.json"
+ while [ -f "${results_base}${suffix}.json" ] || { [ -n "${artifacts_base}" ] && [ -e "${artifacts_base}${suffix}" ]; }; do
+ suffix=".${i}"
i=$((i + 1))
done
- echo "${path}"
+ echo "${suffix}"
}
run_benchmarks() {
@@ -135,7 +144,20 @@ run_benchmarks() {
filter_arg="--filter ${FILTER}"
fi
- local output=$(next_available_path "${BUILD_DIR}/${1}.json")
+ # Pick a suffix that is free for both the result report and (when keeping build artifacts) the
+ # artifacts directory, so 'foo.N.json' and its build artifacts in 'foo.N' always correspond.
+ local artifacts_base=""
+ if [ -n "${BUILD_DIR}" ]; then
+ artifacts_base="${BUILD_DIR}/${BUILDER}/${1}"
+ fi
+ local suffix=$(select_run_suffix "${RESULTS_DIR}/${1}" "${artifacts_base}")
+
+ local build_dir_arg=""
+ if [ -n "${BUILD_DIR}" ]; then
+ build_dir_arg="--build-dir ${artifacts_base}${suffix}"
+ fi
+
+ local output="${RESULTS_DIR}/${1}${suffix}.json"
${MONOREPO_ROOT}/libcxx/utils/ci/lnt/run-benchmarks \
--test-suite-commit ${BENCHMARK_SUITE_VERSION} \
--git-repo ${MONOREPO_ROOT} \
@@ -144,6 +166,7 @@ run_benchmarks() {
--benchmark-commit ${1} \
${spec_arg} \
${filter_arg} \
+ ${build_dir_arg} \
--output ${output}
if [ -n "${LNT_URL}" ]; then
diff --git a/libcxx/utils/ci/lnt/run-benchmarks b/libcxx/utils/ci/lnt/run-benchmarks
index 611bd9c4d45b9..ce4705c0fafa1 100755
--- a/libcxx/utils/ci/lnt/run-benchmarks
+++ b/libcxx/utils/ci/lnt/run-benchmarks
@@ -8,6 +8,7 @@
# ===----------------------------------------------------------------------===##
import argparse
+import contextlib
import json
import logging
import os
@@ -77,12 +78,17 @@ def main(argv):
parser.add_argument('--machine', type=str, required=True,
help='The name of the machine for reporting LNT results.')
parser.add_argument('--output', type=pathlib.Path, required=True,
- help='Path where the resulting LNT JSON report is written. The file is overwritten if it already exists.')
+ help='Path where the resulting LNT JSON report is written. It is an error for the file to '
+ 'already exist.')
parser.add_argument('--filter', type=str, required=False,
help="Optional test filter to pass to lit when running the benchmarks. This allows "
"running only a subset of the benchmarks.")
parser.add_argument('--spec-dir', type=pathlib.Path, required=False,
help='Optional path to a SPEC installation to use for benchmarking.')
+ parser.add_argument('--build-dir', type=pathlib.Path, required=False,
+ help='Optional directory in which to keep build artifacts. By default, a temporary directory '
+ 'is used and the build artifacts are discarded after the run. It is an error to specify '
+ 'a build directory that already exists.')
parser.add_argument('--git-repo', type=directory_path, default=os.getcwd(),
help='Optional path to the Git repository to use. By default, the current working directory is used.')
parser.add_argument('--dry-run', action='store_true',
@@ -121,17 +127,36 @@ def main(argv):
if enforce_success:
raise
- with tempfile.TemporaryDirectory() as build_dir:
- build_dir = pathlib.Path(build_dir)
+ # Fail fast before doing any expensive work: refuse to overwrite existing results or artifacts.
+ if args.output.exists():
+ sys.exit(f'error: output report {args.output} already exists; not overwriting it')
+ if args.build_dir is not None and args.build_dir.exists():
+ sys.exit(f'error: build directory {args.build_dir} already exists; not overwriting it')
+
+ with contextlib.ExitStack() as stack:
+ # LNT tooling is throwaway: always install it into a temporary directory.
+ venv = pathlib.Path(stack.enter_context(tempfile.TemporaryDirectory()))
+
+ # The build artifacts are kept in --build-dir when it is given, and stored in a temporary directory
+ # otherwise.
+ if args.build_dir is not None:
+ artifacts = args.build_dir
+ else:
+ artifacts = pathlib.Path(stack.enter_context(tempfile.TemporaryDirectory()))
+ if not args.dry_run:
+ artifacts.mkdir(parents=True, exist_ok=True)
+ logging.info(f'Storing build artifacts in {artifacts}')
logging.info('Installing LNT')
- run(['python3', '-m', 'venv', build_dir / '.venv'])
- run([build_dir / '.venv/bin/pip', 'install', 'llvm-lnt'])
+ run(['python3', '-m', 'venv', venv])
+ run([venv / 'bin/pip', 'install', 'llvm-lnt'])
logging.info(f'Building libc++ at commit {args.benchmark_commit}')
build_cmd = [args.git_repo / 'libcxx/utils/build-at-commit',
'--git-repo', args.git_repo,
- '--install-dir', build_dir / 'install',
+ '--install-dir', artifacts / 'libcxx-install',
+ '--tmp-src-dir', artifacts / 'libcxx-src',
+ '--tmp-build-dir', artifacts / 'libcxx-build',
'--commit', args.benchmark_commit,
'--', '-DCMAKE_BUILD_TYPE=RelWithDebInfo', f'-DCMAKE_CXX_COMPILER={args.compiler}']
run(build_cmd, enforce_success=False) # if the build fails, carry on: we'll fail later and submit empty LNT results
@@ -139,32 +164,36 @@ def main(argv):
logging.info(f'Running benchmarks from {args.test_suite_commit} against libc++ {args.benchmark_commit}')
cmd = [args.git_repo / 'libcxx/utils/test-at-commit',
'--git-repo', args.git_repo,
- '--build-dir', build_dir / 'bench',
+ '--build-dir', artifacts / 'benchmarks',
'--test-suite-commit', args.test_suite_commit,
- '--libcxx-installation', build_dir / 'install',
+ '--libcxx-installation', artifacts / 'libcxx-install',
'--',
'-j1', '--time-tests', '--test-output=failed',
'--param', f'compiler={args.compiler}',
'--param', 'optimization=speed',
'--param', 'std=c++26',
- build_dir / 'bench/libcxx/test/benchmarks']
+ artifacts / 'benchmarks/libcxx/test/benchmarks']
if args.spec_dir is not None:
cmd += ['--param', f'spec_dir={args.spec_dir}']
if args.filter is not None:
cmd += ['--filter', args.filter]
run(cmd, enforce_success=False) # some benchmarks may fail to build/run at some commits, and that's okay
- with open(build_dir / 'benchmarks.lnt', 'w') as f:
- run([args.git_repo / 'libcxx/utils/consolidate-benchmarks', build_dir / 'bench'], stdout=f)
+ consolidate = [args.git_repo / 'libcxx/utils/consolidate-benchmarks', artifacts / 'benchmarks']
+ if args.dry_run:
+ run(consolidate)
+ else:
+ with open(artifacts / 'benchmarks.lnt', 'w') as f:
+ run(consolidate, stdout=f)
logging.info('Creating JSON report for LNT')
order = len(subprocess.check_output(['git', '-C', args.git_repo, 'rev-list', args.benchmark_commit]).splitlines())
- importreport = [build_dir / '.venv/bin/lnt', 'importreport', '--order', str(order), '--machine', args.machine]
+ importreport = [venv / 'bin/lnt', 'importreport', '--order', str(order), '--machine', args.machine]
for arg in dict_to_params(gather_run_information(args)):
importreport += ['--run-info', arg]
for arg in dict_to_params(gather_machine_information(args)):
importreport += ['--machine-info', arg]
output = args.output.resolve()
- importreport += [build_dir / 'benchmarks.lnt', output]
+ importreport += [artifacts / 'benchmarks.lnt', output]
run(importreport)
logging.info(f'Report written to {output}')
``````````
</details>
https://github.com/llvm/llvm-project/pull/205146
More information about the libcxx-commits
mailing list