Files
esphome/script/cpp_benchmark.py
T
J. Nick Koston b2e78d3753 Define CODSPEED_ENABLED globally for benchmark source files
benchmark.h uses #ifdef CODSPEED_ENABLED to switch benchmark
registration to CodSpeed-instrumented variants. This define was
only in library.json (applied to library compilation) but not to
the benchmark .cpp files that #include <benchmark/benchmark.h>.
Without it, CodSpeed reports "No benchmarks found".
2026-03-16 21:36:25 -10:00

119 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Build and run C++ benchmarks for ESPHome components using Google Benchmark."""
import argparse
import json
import os
from pathlib import Path
import sys
from helpers import root_path
from test_helpers import (
BASE_CODEGEN_COMPONENTS,
PLATFORMIO_GOOGLE_BENCHMARK_LIB,
USE_TIME_TIMEZONE_FLAG,
build_and_run,
)
# Path to /tests/benchmarks/components
BENCHMARKS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "components"
# Path to /tests/benchmarks/core (always included, not a component)
CORE_BENCHMARKS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "core"
# Additional codegen components beyond the base set.
# json is needed because its to_code adds the ArduinoJson library
# (auto-loaded by api, but cpp_testing suppresses to_code unless listed).
BENCHMARK_CODEGEN_COMPONENTS = BASE_CODEGEN_COMPONENTS | {"json"}
PLATFORMIO_OPTIONS = {
"build_flags": [
USE_TIME_TIMEZONE_FLAG,
"-g", # debug symbols for profiling
],
# Use deep+ LDF mode to ensure PlatformIO detects the benchmark
# library dependency from nested includes.
"lib_ldf_mode": "deep+",
}
def run_benchmarks(selected_components: list[str], build_only: bool = False) -> int:
# Allow CI to override the benchmark library (e.g. with CodSpeed's fork).
# BENCHMARK_LIB_CONFIG is a JSON string from setup_codspeed_lib.py
# containing {"lib_path": "/path/to/google_benchmark"}.
lib_config_json = os.environ.get("BENCHMARK_LIB_CONFIG")
pio_options = PLATFORMIO_OPTIONS
if lib_config_json:
lib_config = json.loads(lib_config_json)
benchmark_lib = f"benchmark=symlink://{lib_config['lib_path']}"
# CODSPEED_ENABLED must be defined globally (not just in library build
# flags) because benchmark.h uses #ifdef CODSPEED_ENABLED to switch
# benchmark registration to CodSpeed-instrumented variants.
pio_options = {
**PLATFORMIO_OPTIONS,
"build_flags": PLATFORMIO_OPTIONS["build_flags"]
+ ["-DCODSPEED_ENABLED", "-DCODSPEED_SIMULATION"],
}
else:
benchmark_lib = PLATFORMIO_GOOGLE_BENCHMARK_LIB
return build_and_run(
selected_components=selected_components,
tests_dir=BENCHMARKS_DIR,
codegen_components=BENCHMARK_CODEGEN_COMPONENTS,
config_prefix="cppbench",
friendly_name="CPP Benchmarks",
libraries=benchmark_lib,
platformio_options=pio_options,
main_entry="main.cpp",
label="benchmarks",
build_only=build_only,
extra_include_dirs=[CORE_BENCHMARKS_DIR],
)
def main() -> None:
parser = argparse.ArgumentParser(
description="Build and run C++ benchmarks for ESPHome components."
)
parser.add_argument(
"components",
nargs="*",
help="List of components to benchmark (must have files in tests/benchmarks/components/).",
)
parser.add_argument(
"--all",
action="store_true",
help="Benchmark all components with benchmark files.",
)
parser.add_argument(
"--build-only",
action="store_true",
help="Only build, print binary path without running.",
)
args = parser.parse_args()
if args.all:
# Find all component directories that have .cpp files
components: list[str] = (
sorted(
d.name
for d in BENCHMARKS_DIR.iterdir()
if d.is_dir()
and d.name != "__pycache__"
and (any(d.glob("*.cpp")) or any(d.glob("*.h")))
)
if BENCHMARKS_DIR.is_dir()
else []
)
else:
components: list[str] = args.components
sys.exit(run_benchmarks(components, build_only=args.build_only))
if __name__ == "__main__":
main()