mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
ea5f62f030
The benchmark library warns "Library was built as DEBUG" without NDEBUG. This enables debug assertions and disables optimizations in the benchmark framework, causing instrumentation overhead to show up in profiles.
214 lines
7.0 KiB
Python
Executable File
214 lines
7.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Set up CodSpeed's google_benchmark fork as a PlatformIO library.
|
|
|
|
CodSpeed requires their codspeed-cpp fork for CPU simulation instrumentation.
|
|
This script clones the repo and assembles a flat PlatformIO-compatible library
|
|
by combining google_benchmark sources, codspeed core, and instrument-hooks.
|
|
|
|
PlatformIO quirks addressed:
|
|
- .cc files renamed to .cpp (PlatformIO ignores .cc)
|
|
- All sources merged into one src/ dir (PlatformIO can't compile from
|
|
multiple source directories in a single library)
|
|
- library.json created with required CodSpeed preprocessor defines
|
|
|
|
Usage:
|
|
python script/setup_codspeed_lib.py [--output-dir DIR]
|
|
|
|
Prints JSON to stdout with lib_path for cpp_benchmark.py.
|
|
Git output goes to stderr.
|
|
|
|
See https://codspeed.io/docs/benchmarks/cpp#custom-build-systems
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
# Pin to a specific release for reproducibility
|
|
CODSPEED_CPP_REPO = "https://github.com/CodSpeedHQ/codspeed-cpp.git"
|
|
CODSPEED_CPP_SHA = "e633aca00da3d0ad14e7bf424d9cb47165a29028" # v2.1.0
|
|
|
|
DEFAULT_OUTPUT_DIR = "/tmp/codspeed-cpp"
|
|
|
|
# Well-known paths within the codspeed-cpp repository
|
|
GOOGLE_BENCHMARK_SUBDIR = "google_benchmark"
|
|
CORE_SUBDIR = "core"
|
|
INSTRUMENT_HOOKS_SUBDIR = Path(CORE_SUBDIR) / "instrument-hooks"
|
|
INSTRUMENT_HOOKS_INCLUDES = INSTRUMENT_HOOKS_SUBDIR / "includes"
|
|
INSTRUMENT_HOOKS_DIST = INSTRUMENT_HOOKS_SUBDIR / "dist" / "core.c"
|
|
CORE_CMAKE = Path(CORE_SUBDIR) / "CMakeLists.txt"
|
|
|
|
|
|
def _git(args: list[str], **kwargs: object) -> None:
|
|
"""Run a git command, sending output to stderr."""
|
|
subprocess.run(
|
|
["git", *args],
|
|
check=True,
|
|
stdout=kwargs.pop("stdout", sys.stderr),
|
|
stderr=kwargs.pop("stderr", sys.stderr),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
def _clone_repo(output_dir: Path) -> None:
|
|
"""Shallow-clone codspeed-cpp at the pinned SHA with submodules."""
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
_git(["init", str(output_dir)])
|
|
_git(["-C", str(output_dir), "remote", "add", "origin", CODSPEED_CPP_REPO])
|
|
_git(["-C", str(output_dir), "fetch", "--depth", "1", "origin", CODSPEED_CPP_SHA])
|
|
_git(
|
|
["-C", str(output_dir), "checkout", "FETCH_HEAD"],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
_git(
|
|
[
|
|
"-C",
|
|
str(output_dir),
|
|
"submodule",
|
|
"update",
|
|
"--init",
|
|
"--recursive",
|
|
"--depth",
|
|
"1",
|
|
]
|
|
)
|
|
|
|
|
|
def _read_codspeed_version(cmake_path: Path) -> str:
|
|
"""Extract CODSPEED_VERSION from core/CMakeLists.txt."""
|
|
if not cmake_path.exists():
|
|
return "0.0.0"
|
|
for line in cmake_path.read_text().splitlines():
|
|
if line.startswith("set(CODSPEED_VERSION"):
|
|
return line.split()[1].rstrip(")")
|
|
return "0.0.0"
|
|
|
|
|
|
def _rename_cc_to_cpp(src_dir: Path) -> None:
|
|
"""Rename .cc files to .cpp so PlatformIO compiles them."""
|
|
for cc_file in src_dir.glob("*.cc"):
|
|
cpp_file = cc_file.with_suffix(".cpp")
|
|
if not cpp_file.exists():
|
|
cc_file.rename(cpp_file)
|
|
|
|
|
|
def _copy_if_missing(src: Path, dest: Path) -> None:
|
|
"""Copy a file only if the destination doesn't already exist."""
|
|
if not dest.exists():
|
|
shutil.copy2(src, dest)
|
|
|
|
|
|
def _merge_codspeed_core_into_lib(core_src: Path, lib_src: Path) -> None:
|
|
"""Copy codspeed core sources into the benchmark library src/.
|
|
|
|
.cpp files get a ``codspeed_`` prefix to avoid name collisions with
|
|
google_benchmark's own sources. .h files keep their original names
|
|
since they're referenced by ``#include "walltime.h"`` etc.
|
|
"""
|
|
for src_file in core_src.iterdir():
|
|
if src_file.suffix == ".cpp":
|
|
_copy_if_missing(src_file, lib_src / f"codspeed_{src_file.name}")
|
|
elif src_file.suffix == ".h":
|
|
_copy_if_missing(src_file, lib_src / src_file.name)
|
|
|
|
|
|
def _write_library_json(
|
|
benchmark_dir: Path,
|
|
core_include: Path,
|
|
hooks_include: Path,
|
|
version: str,
|
|
project_root: Path,
|
|
) -> None:
|
|
"""Write a PlatformIO library.json with CodSpeed build flags."""
|
|
library_json = {
|
|
"name": "benchmark",
|
|
"version": "0.0.0",
|
|
"build": {
|
|
"flags": [
|
|
f"-I{core_include}",
|
|
f"-I{hooks_include}",
|
|
# google benchmark build flags
|
|
"-DNDEBUG",
|
|
"-DHAVE_STD_REGEX",
|
|
"-DHAVE_STEADY_CLOCK",
|
|
"-DBENCHMARK_STATIC_DEFINE",
|
|
# CodSpeed instrumentation flags
|
|
# https://codspeed.io/docs/benchmarks/cpp#custom-build-systems
|
|
"-DCODSPEED_ENABLED",
|
|
"-DCODSPEED_ANALYSIS",
|
|
f'-DCODSPEED_VERSION=\\"{version}\\"',
|
|
f'-DCODSPEED_ROOT_DIR=\\"{project_root}\\"',
|
|
'-DCODSPEED_MODE_DISPLAY=\\"simulation\\"',
|
|
],
|
|
"includeDir": "include",
|
|
},
|
|
}
|
|
(benchmark_dir / "library.json").write_text(
|
|
json.dumps(library_json, indent=2) + "\n"
|
|
)
|
|
|
|
|
|
def setup_codspeed_lib(output_dir: Path) -> None:
|
|
"""Clone codspeed-cpp and assemble a flat PlatformIO library.
|
|
|
|
The resulting library at ``output_dir/google_benchmark/`` contains:
|
|
- google_benchmark sources (.cc renamed to .cpp)
|
|
- codspeed core sources (prefixed ``codspeed_``)
|
|
- instrument-hooks C source (as ``instrument_hooks.c``)
|
|
- library.json with all required CodSpeed defines
|
|
|
|
Args:
|
|
output_dir: Directory to clone the repository into
|
|
"""
|
|
if not (output_dir / ".git").exists():
|
|
_clone_repo(output_dir)
|
|
|
|
benchmark_dir = output_dir / GOOGLE_BENCHMARK_SUBDIR
|
|
lib_src = benchmark_dir / "src"
|
|
core_dir = output_dir / CORE_SUBDIR
|
|
core_include = core_dir / "include"
|
|
hooks_include = output_dir / INSTRUMENT_HOOKS_INCLUDES
|
|
hooks_dist_c = output_dir / INSTRUMENT_HOOKS_DIST
|
|
project_root = Path(__file__).resolve().parent.parent
|
|
|
|
# 1. Rename .cc → .cpp (PlatformIO doesn't compile .cc)
|
|
_rename_cc_to_cpp(lib_src)
|
|
|
|
# 2. Merge codspeed core sources into the library
|
|
_merge_codspeed_core_into_lib(core_dir / "src", lib_src)
|
|
|
|
# 3. Copy instrument-hooks C source (provides instrument_hooks_* symbols)
|
|
if hooks_dist_c.exists():
|
|
_copy_if_missing(hooks_dist_c, lib_src / "instrument_hooks.c")
|
|
|
|
# 4. Write library.json
|
|
version = _read_codspeed_version(output_dir / CORE_CMAKE)
|
|
_write_library_json(
|
|
benchmark_dir, core_include, hooks_include, version, project_root
|
|
)
|
|
|
|
# Output JSON config for cpp_benchmark.py
|
|
print(json.dumps({"lib_path": str(benchmark_dir)}))
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
type=Path,
|
|
default=Path(DEFAULT_OUTPUT_DIR),
|
|
help=f"Directory to clone codspeed-cpp into (default: {DEFAULT_OUTPUT_DIR})",
|
|
)
|
|
args = parser.parse_args()
|
|
setup_codspeed_lib(args.output_dir)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|