diff --git a/src/pytest_codspeed/instruments/analysis.py b/src/pytest_codspeed/instruments/analysis.py index 9c24f19..7fa3185 100644 --- a/src/pytest_codspeed/instruments/analysis.py +++ b/src/pytest_codspeed/instruments/analysis.py @@ -33,6 +33,7 @@ def __init__(self, config: CodSpeedConfig, mode: MeasurementMode) -> None: try: self.instrument_hooks = InstrumentHooks() self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__) + self.instrument_hooks.collect_and_write_python_environment() except RuntimeError as e: if os.environ.get("CODSPEED_ENV") is not None: raise Exception( diff --git a/src/pytest_codspeed/instruments/hooks/__init__.py b/src/pytest_codspeed/instruments/hooks/__init__.py index 70e8206..758775b 100644 --- a/src/pytest_codspeed/instruments/hooks/__init__.py +++ b/src/pytest_codspeed/instruments/hooks/__init__.py @@ -2,6 +2,7 @@ import os import sys +import sysconfig import warnings from typing import TYPE_CHECKING @@ -92,3 +93,43 @@ def set_feature(self, feature: int, enabled: bool) -> None: enabled: Whether to enable or disable the feature """ self.lib.instrument_hooks_set_feature(feature, enabled) + + def set_environment(self, section_name: str, key: str, value: str) -> None: + """Register a key-value pair under a named section for environment collection. + + Args: + section_name: The section name (e.g. "Python") + key: The key (e.g. "version") + value: The value (e.g. "3.13.12") + """ + ret = self.lib.instrument_hooks_set_environment( + self.instance, + section_name.encode("utf-8"), + key.encode("utf-8"), + value.encode("utf-8"), + ) + if ret != 0: + warnings.warn("Failed to set environment data", RuntimeWarning) + + def write_environment(self, pid: int | None = None) -> None: + """Flush all registered environment sections to disk. + + Writes to $CODSPEED_PROFILE_FOLDER/environment-.json. + + Args: + pid: Optional process ID (defaults to current process) + """ + if pid is None: + pid = os.getpid() + ret = self.lib.instrument_hooks_write_environment(self.instance, pid) + if ret != 0: + warnings.warn("Failed to write environment data", RuntimeWarning) + + def collect_and_write_python_environment(self) -> None: + """Collect Python toolchain information and write it to disk.""" + + for key, value in sysconfig.get_config_vars().items(): + self.set_environment( + "python", f"{key}", str(value) if value is not None else "" + ) + self.write_environment() diff --git a/src/pytest_codspeed/instruments/hooks/build.py b/src/pytest_codspeed/instruments/hooks/build.py index a8b91cd..bce27fd 100644 --- a/src/pytest_codspeed/instruments/hooks/build.py +++ b/src/pytest_codspeed/instruments/hooks/build.py @@ -36,6 +36,10 @@ void callgrind_stop_instrumentation(); void instrument_hooks_set_feature(uint64_t feature, bool enabled); + +uint8_t instrument_hooks_set_environment(InstrumentHooks *, const char *section_name, + const char *key, const char *value); +uint8_t instrument_hooks_write_environment(InstrumentHooks *, uint32_t pid); """) ffibuilder.set_source( @@ -47,6 +51,8 @@ "src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c", ], include_dirs=[str(includes_dir)], + # IMPORTANT: Keep in sync with instrument-hooks/ci.yml (COMMON_CFLAGS) + extra_compile_args=["-Wno-format-security"], ) if __name__ == "__main__": diff --git a/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi b/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi index 19e9d2e..860225b 100644 --- a/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi +++ b/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi @@ -31,5 +31,13 @@ class lib: def callgrind_stop_instrumentation() -> int: ... @staticmethod def instrument_hooks_set_feature(feature: int, enabled: bool) -> None: ... + @staticmethod + def instrument_hooks_set_environment( + hooks: InstrumentHooksPointer, section_name: bytes, key: bytes, value: bytes + ) -> int: ... + @staticmethod + def instrument_hooks_write_environment( + hooks: InstrumentHooksPointer, pid: int + ) -> int: ... LibType = type[lib] diff --git a/src/pytest_codspeed/instruments/hooks/instrument-hooks b/src/pytest_codspeed/instruments/hooks/instrument-hooks index 89fb72a..e86719c 160000 --- a/src/pytest_codspeed/instruments/hooks/instrument-hooks +++ b/src/pytest_codspeed/instruments/hooks/instrument-hooks @@ -1 +1 @@ -Subproject commit 89fb72a076ec71c9eca6eee9bca98bada4b4dfb4 +Subproject commit e86719c70c9c0b1646db182a7c748230e243dace diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index 17673a3..2ec14ee 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -163,6 +163,7 @@ def __init__(self, config: CodSpeedConfig, _mode: MeasurementMode) -> None: try: self.instrument_hooks = InstrumentHooks() self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__) + self.instrument_hooks.collect_and_write_python_environment() except RuntimeError as e: if os.environ.get("CODSPEED_ENV") is not None: warnings.warn(