blob: 7a4fb565d34efd3cd1030d0d226ccb53869d4d15 [file] [edit]
#!/usr/bin/env python3
# ===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===----------------------------------------------------------------------===##
import argparse
import glob
import pathlib
import subprocess
import sys
def main():
parser = argparse.ArgumentParser(
prog="run-spec-benchmark",
description="Build and run a SPEC CPU benchmark using a given compiler, then output results in LNT flat format.",
)
parser.add_argument(
"--spec-dir",
type=str,
required=True,
help="Path to the SPEC CPU installation directory.",
)
parser.add_argument(
"--benchmark",
type=str,
required=True,
help="Name of the SPEC benchmark to run (e.g. 520.omnetpp_r).",
)
parser.add_argument(
"--temp-dir",
type=str,
required=True,
help="Temporary directory for build artifacts and results.",
)
parser.add_argument(
"--clean",
action="store_true",
help="Clean up build artifacts after running. Useful to save disk space when running multiple benchmarks.",
)
parser.add_argument(
"compiler_and_flags",
nargs="+",
help="The C++ compiler followed by all flags (passed after --).",
)
args = parser.parse_args()
spec_dir = pathlib.Path(args.spec_dir)
temp_dir = pathlib.Path(args.temp_dir)
utils_dir = pathlib.Path(__file__).resolve().parent
cxx_and_flags = " ".join(args.compiler_and_flags)
# Generate the SPEC configuration file. We force C++17 since SPEC doesn't
# compile with newer standards.
temp_dir.mkdir(parents=True, exist_ok=True)
spec_config = temp_dir / "spec-config.cfg"
spec_config.write_text(
f"""\
default:
ignore_errors = 1
iterations = 1
label = spec-stdlib
log_line_width = 4096
makeflags = --jobs=8
mean_anyway = 1
output_format = csv
preenv = 0
reportable = 0
tune = base
copies = 1
threads = 1
CC = cc -O3 -std=c18 -Wno-implicit-function-declaration
CXX = {cxx_and_flags} -std=c++17 -w # we don't care about warnings in SPEC
CC_VERSION_OPTION = --version
CXX_VERSION_OPTION = --version
EXTRA_PORTABILITY = -DSPEC_NO_CXX17_SPECIAL_MATH_FUNCTIONS # libc++ doesn't implement the special math functions yet
"""
)
runcpu = str(spec_dir / "bin" / "runcpu")
config_flag = f"--config={spec_config}"
output_root_flag = f"--output_root={temp_dir}"
# Build the benchmark.
subprocess.check_call([runcpu, config_flag, "--action=build", output_root_flag, args.benchmark])
# Run the benchmark. Wrap in /usr/bin/time to collect resource usage.
time_output = temp_dir / "time.txt"
subprocess.check_call(["/usr/bin/time", "-l", "-o", str(time_output),
runcpu, config_flag, "--action=run", "--size=ref", output_root_flag, args.benchmark])
# Validate results: runcpu doesn't exit with an error code on build/run errors, so we
# need to check the CSV output for CE (Compilation Error) or RE (Runtime Error).
result_dir = temp_dir / "result"
csv_files = sorted(glob.glob(str(result_dir / "*.refrate.csv")))
log_files = sorted(glob.glob(str(result_dir / "*.log")))
if not csv_files:
for log_file in log_files:
print(pathlib.Path(log_file).read_text(), file=sys.stderr)
print("No SPEC CSV result files found -- this indicates a SPEC error.", file=sys.stderr)
sys.exit(1)
# Check for compilation or runtime errors in the results.
status_check = subprocess.run([str(utils_dir / "parse-spec-results"), "--extract=Base Status", "--keep-failed"] + csv_files,
capture_output=True, text=True,)
if status_check.returncode != 0:
for log_file in log_files:
print(pathlib.Path(log_file).read_text(), file=sys.stderr)
print("Failed to parse SPEC results.", file=sys.stderr)
sys.exit(1)
for line in status_check.stdout.splitlines():
if "CE" in line or "RE" in line:
for log_file in log_files:
print(pathlib.Path(log_file).read_text(), file=sys.stderr)
print(f"SPEC benchmark {args.benchmark} had a compilation or runtime error.", file=sys.stderr)
sys.exit(1)
# Parse results into LNT format and print to stdout.
benchmark_name = args.benchmark.replace(".", "_")
spec_results = subprocess.run([str(utils_dir / "parse-spec-results"), "--output-format=lnt"] + csv_files,
capture_output=True, text=True, check=True)
sys.stdout.write(spec_results.stdout)
time_results = subprocess.run([str(utils_dir / "parse-time-output"), str(time_output), f"--benchmark={benchmark_name}",
"--extract", "instructions", "max_rss", "cycles", "peak_memory"],
capture_output=True, text=True, check=True)
sys.stdout.write(time_results.stdout)
# Clean up build artifacts since they can be very large.
if args.clean:
benchspec_dir = temp_dir / "benchspec"
if benchspec_dir.exists():
import shutil
shutil.rmtree(benchspec_dir)
if __name__ == "__main__":
main()