pytest-codspeed 4.2.0b0__tar.gz → 4.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {pytest_codspeed-4.2.0b0/src/pytest_codspeed.egg-info → pytest_codspeed-4.4.0}/PKG-INFO +10 -9
  2. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/README.md +8 -8
  3. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/pyproject.toml +8 -2
  4. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/__init__.py +2 -2
  5. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/__init__.py +14 -6
  6. pytest_codspeed-4.2.0b0/src/pytest_codspeed/instruments/valgrind.py → pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/analysis.py +32 -10
  7. pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/__init__.py +197 -0
  8. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/build.py +13 -0
  9. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi +20 -0
  10. pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c +30987 -0
  11. pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/compat.h +36 -0
  12. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/core.h +31 -15
  13. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/scripts/release.py +4 -0
  14. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/walltime.py +38 -4
  15. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/plugin.py +18 -7
  16. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0/src/pytest_codspeed.egg-info}/PKG-INFO +10 -9
  17. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/SOURCES.txt +3 -1
  18. pytest_codspeed-4.4.0/tests/test_format_time.py +35 -0
  19. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin.py +1 -1
  20. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin_cpu_instrumentation.py +7 -9
  21. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin_walltime.py +56 -1
  22. pytest_codspeed-4.2.0b0/src/pytest_codspeed/instruments/hooks/__init__.py +0 -82
  23. pytest_codspeed-4.2.0b0/src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c +0 -8638
  24. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/LICENSE +0 -0
  25. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/setup.cfg +0 -0
  26. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/setup.py +0 -0
  27. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/config.py +0 -0
  28. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/callgrind.h +0 -0
  29. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/valgrind.h +0 -0
  30. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/zig.h +0 -0
  31. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/py.typed +0 -0
  32. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/utils.py +0 -0
  33. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/dependency_links.txt +0 -0
  34. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/entry_points.txt +0 -0
  35. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/requires.txt +0 -0
  36. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/top_level.txt +0 -0
  37. {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-codspeed
3
- Version: 4.2.0b0
3
+ Version: 4.4.0
4
4
  Summary: Pytest plugin to create CodSpeed benchmarks
5
5
  Author-email: Arthur Pastel <arthur@codspeed.io>
6
6
  License: The MIT License (MIT)
@@ -40,6 +40,7 @@ Classifier: Programming Language :: Python :: 3.10
40
40
  Classifier: Programming Language :: Python :: 3.11
41
41
  Classifier: Programming Language :: Python :: 3.12
42
42
  Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Programming Language :: Python :: 3.14
43
44
  Classifier: Topic :: Software Development :: Testing
44
45
  Classifier: Topic :: System :: Benchmark
45
46
  Classifier: Topic :: Utilities
@@ -59,7 +60,7 @@ Requires-Dist: pytest-xdist~=3.6.1; extra == "compat"
59
60
 
60
61
  [![CI](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml/badge.svg)](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml)
61
62
  [![PyPi Version](https://img.shields.io/pypi/v/pytest-codspeed?color=%2334D058&label=pypi)](https://pypi.org/project/pytest-codspeed)
62
- ![Python Version](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-informational.svg)
63
+ ![Python Version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-informational.svg)
63
64
  [![Discord](https://img.shields.io/badge/chat%20on-discord-7289da.svg)](https://discord.com/invite/MxpaCfKSqF)
64
65
  [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/CodSpeedHQ/pytest-codspeed)
65
66
 
@@ -117,10 +118,10 @@ If you want to run the benchmarks tests locally, you can use the `--codspeed` py
117
118
  ```sh
118
119
  $ pytest tests/ --codspeed
119
120
  ============================= test session starts ====================
120
- platform darwin -- Python 3.13.0, pytest-7.4.4, pluggy-1.5.0
121
- codspeed: 3.0.0 (enabled, mode: walltime, timer_resolution: 41.7ns)
121
+ platform darwin -- Python 3.14.0, pytest-7.4.4, pluggy-1.5.0
122
+ codspeed: 4.2.0 (enabled, mode: walltime, timer_resolution: 41.7ns)
122
123
  rootdir: /home/user/codspeed-test, configfile: pytest.ini
123
- plugins: codspeed-3.0.0
124
+ plugins: codspeed-4.2.0
124
125
  collected 1 items
125
126
 
126
127
  tests/test_sum_squares.py . [ 100%]
@@ -158,10 +159,10 @@ jobs:
158
159
  name: Run benchmarks
159
160
  runs-on: ubuntu-latest
160
161
  steps:
161
- - uses: actions/checkout@v4
162
- - uses: actions/setup-python@v5
162
+ - uses: actions/checkout@v5
163
+ - uses: actions/setup-python@v6
163
164
  with:
164
- python-version: "3.13"
165
+ python-version: "3.14"
165
166
 
166
167
  - name: Install dependencies
167
168
  run: pip install -r requirements.txt
@@ -169,7 +170,7 @@ jobs:
169
170
  - name: Run benchmarks
170
171
  uses: CodSpeedHQ/action@v4
171
172
  with:
172
- mode: instrumentation # or `walltime`
173
+ mode: simulation # or `walltime`
173
174
  token: ${{ secrets.CODSPEED_TOKEN }}
174
175
  run: pytest tests/ --codspeed
175
176
  ```
@@ -3,7 +3,7 @@
3
3
 
4
4
  [![CI](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml/badge.svg)](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml)
5
5
  [![PyPi Version](https://img.shields.io/pypi/v/pytest-codspeed?color=%2334D058&label=pypi)](https://pypi.org/project/pytest-codspeed)
6
- ![Python Version](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-informational.svg)
6
+ ![Python Version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-informational.svg)
7
7
  [![Discord](https://img.shields.io/badge/chat%20on-discord-7289da.svg)](https://discord.com/invite/MxpaCfKSqF)
8
8
  [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/CodSpeedHQ/pytest-codspeed)
9
9
 
@@ -61,10 +61,10 @@ If you want to run the benchmarks tests locally, you can use the `--codspeed` py
61
61
  ```sh
62
62
  $ pytest tests/ --codspeed
63
63
  ============================= test session starts ====================
64
- platform darwin -- Python 3.13.0, pytest-7.4.4, pluggy-1.5.0
65
- codspeed: 3.0.0 (enabled, mode: walltime, timer_resolution: 41.7ns)
64
+ platform darwin -- Python 3.14.0, pytest-7.4.4, pluggy-1.5.0
65
+ codspeed: 4.2.0 (enabled, mode: walltime, timer_resolution: 41.7ns)
66
66
  rootdir: /home/user/codspeed-test, configfile: pytest.ini
67
- plugins: codspeed-3.0.0
67
+ plugins: codspeed-4.2.0
68
68
  collected 1 items
69
69
 
70
70
  tests/test_sum_squares.py . [ 100%]
@@ -102,10 +102,10 @@ jobs:
102
102
  name: Run benchmarks
103
103
  runs-on: ubuntu-latest
104
104
  steps:
105
- - uses: actions/checkout@v4
106
- - uses: actions/setup-python@v5
105
+ - uses: actions/checkout@v5
106
+ - uses: actions/setup-python@v6
107
107
  with:
108
- python-version: "3.13"
108
+ python-version: "3.14"
109
109
 
110
110
  - name: Install dependencies
111
111
  run: pip install -r requirements.txt
@@ -113,7 +113,7 @@ jobs:
113
113
  - name: Run benchmarks
114
114
  uses: CodSpeedHQ/action@v4
115
115
  with:
116
- mode: instrumentation # or `walltime`
116
+ mode: simulation # or `walltime`
117
117
  token: ${{ secrets.CODSPEED_TOKEN }}
118
118
  run: pytest tests/ --codspeed
119
119
  ```
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.11",
25
25
  "Programming Language :: Python :: 3.12",
26
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
27
28
  "Topic :: Software Development :: Testing",
28
29
  "Topic :: System :: Benchmark",
29
30
  "Topic :: Utilities",
@@ -43,13 +44,18 @@ compat = [
43
44
  # "pytest-speed>=0.3.5",
44
45
  ]
45
46
 
47
+ [tool.uv]
48
+ # Python builds change with uv versions, and we are quite susceptible to that.
49
+ # We pin uv to to make sure reproducibility is maintained for any contributor.
50
+ required-version = "0.9.5"
51
+
46
52
  [tool.uv.sources]
47
53
  pytest-codspeed = { workspace = true }
48
54
 
49
55
  [dependency-groups]
50
56
  dev = [
51
57
  "pytest-codspeed",
52
- "mypy ~= 1.11.2",
58
+ "mypy ~= 1.18.2",
53
59
  "ruff ~= 0.11.12",
54
60
  "pytest ~= 7.0",
55
61
  "pytest-cov ~= 4.0.0",
@@ -71,7 +77,7 @@ version = { attr = "pytest_codspeed.__version__" }
71
77
 
72
78
 
73
79
  [tool.bumpver]
74
- current_version = "4.2.0-beta"
80
+ current_version = "4.4.0"
75
81
  version_pattern = "MAJOR.MINOR.PATCH[-TAG[NUM]]"
76
82
  commit_message = "Release v{new_version} 🚀"
77
83
  tag_message = "Release v{new_version} 🚀"
@@ -1,6 +1,6 @@
1
- __version__ = "4.2.0b0"
1
+ __version__ = "4.4.0"
2
2
  # We also have the semver version since __version__ is not semver compliant
3
- __semver_version__ = "4.2.0-beta"
3
+ __semver_version__ = "4.4.0"
4
4
 
5
5
  from .plugin import BenchmarkFixture
6
6
 
@@ -21,7 +21,7 @@ class Instrument(metaclass=ABCMeta):
21
21
  instrument: ClassVar[str]
22
22
 
23
23
  @abstractmethod
24
- def __init__(self, config: CodSpeedConfig): ...
24
+ def __init__(self, config: CodSpeedConfig, mode: MeasurementMode): ...
25
25
 
26
26
  @abstractmethod
27
27
  def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]: ...
@@ -56,17 +56,25 @@ class Instrument(metaclass=ABCMeta):
56
56
 
57
57
 
58
58
  class MeasurementMode(str, Enum):
59
- Instrumentation = "instrumentation"
59
+ Simulation = "simulation"
60
+ Memory = "memory"
60
61
  WallTime = "walltime"
61
62
 
63
+ @classmethod
64
+ def _missing_(cls, value: object):
65
+ # Accept "instrumentation" as deprecated alias for "simulation"
66
+ if value == "instrumentation":
67
+ return cls.Simulation
68
+ return None
69
+
62
70
 
63
71
  def get_instrument_from_mode(mode: MeasurementMode) -> type[Instrument]:
64
- from pytest_codspeed.instruments.valgrind import (
65
- ValgrindInstrument,
72
+ from pytest_codspeed.instruments.analysis import (
73
+ AnalysisInstrument,
66
74
  )
67
75
  from pytest_codspeed.instruments.walltime import WallTimeInstrument
68
76
 
69
- if mode == MeasurementMode.Instrumentation:
70
- return ValgrindInstrument
77
+ if mode in (MeasurementMode.Simulation, MeasurementMode.Memory):
78
+ return AnalysisInstrument
71
79
  else:
72
80
  return WallTimeInstrument
@@ -1,11 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  import warnings
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  from pytest_codspeed import __semver_version__
7
8
  from pytest_codspeed.instruments import Instrument
8
- from pytest_codspeed.instruments.hooks import InstrumentHooks
9
+ from pytest_codspeed.instruments.hooks import (
10
+ FEATURE_DISABLE_CALLGRIND_MARKERS,
11
+ InstrumentHooks,
12
+ )
9
13
  from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE
10
14
 
11
15
  if TYPE_CHECKING:
@@ -14,27 +18,34 @@ if TYPE_CHECKING:
14
18
  from pytest import Session
15
19
 
16
20
  from pytest_codspeed.config import PedanticOptions
17
- from pytest_codspeed.instruments import P, T
21
+ from pytest_codspeed.instruments import MeasurementMode, P, T
18
22
  from pytest_codspeed.plugin import BenchmarkMarkerOptions, CodSpeedConfig
19
23
 
20
24
 
21
- class ValgrindInstrument(Instrument):
22
- instrument = "valgrind"
25
+ class AnalysisInstrument(Instrument):
26
+ instrument = "analysis"
23
27
  instrument_hooks: InstrumentHooks | None
28
+ mode: MeasurementMode
24
29
 
25
- def __init__(self, config: CodSpeedConfig) -> None:
30
+ def __init__(self, config: CodSpeedConfig, mode: MeasurementMode) -> None:
31
+ self.mode = mode
26
32
  self.benchmark_count = 0
27
33
  try:
28
34
  self.instrument_hooks = InstrumentHooks()
29
35
  self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__)
30
- except RuntimeError:
36
+ self.instrument_hooks.collect_and_write_python_environment()
37
+ except RuntimeError as e:
38
+ if os.environ.get("CODSPEED_ENV") is not None:
39
+ raise Exception(
40
+ f"Failed to initialize {self.mode.value} instrument hooks"
41
+ ) from e
31
42
  self.instrument_hooks = None
32
43
 
33
44
  self.should_measure = self.instrument_hooks is not None
34
45
 
35
46
  def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
36
47
  config = (
37
- f"mode: instrumentation, "
48
+ f"mode: {self.mode.value}, "
38
49
  f"callgraph: {'enabled' if SUPPORTS_PERF_TRAMPOLINE else 'not supported'}"
39
50
  )
40
51
  warnings = []
@@ -68,6 +79,9 @@ class ValgrindInstrument(Instrument):
68
79
  # Warmup CPython performance map cache
69
80
  __codspeed_root_frame__()
70
81
 
82
+ self.instrument_hooks.set_feature(FEATURE_DISABLE_CALLGRIND_MARKERS, True)
83
+ self.instrument_hooks.start_benchmark()
84
+
71
85
  # Manually call the library function to avoid an extra stack frame. Also
72
86
  # call the callgrind markers directly to avoid extra overhead.
73
87
  self.instrument_hooks.lib.callgrind_start_instrumentation()
@@ -76,6 +90,7 @@ class ValgrindInstrument(Instrument):
76
90
  finally:
77
91
  # Ensure instrumentation is stopped even if the test failed
78
92
  self.instrument_hooks.lib.callgrind_stop_instrumentation()
93
+ self.instrument_hooks.stop_benchmark()
79
94
  self.instrument_hooks.set_executed_benchmark(uri)
80
95
 
81
96
  def measure_pedantic(
@@ -87,8 +102,8 @@ class ValgrindInstrument(Instrument):
87
102
  ) -> T:
88
103
  if pedantic_options.rounds != 1 or pedantic_options.iterations != 1:
89
104
  warnings.warn(
90
- "Valgrind instrument ignores rounds and iterations settings "
91
- "in pedantic mode"
105
+ f"{self.mode.value.capitalize()} instrument ignores rounds and "
106
+ "iterations settings in pedantic mode"
92
107
  )
93
108
  if not self.instrument_hooks:
94
109
  args, kwargs = pedantic_options.setup_and_get_args_kwargs()
@@ -112,11 +127,18 @@ class ValgrindInstrument(Instrument):
112
127
 
113
128
  # Compute the actual result of the function
114
129
  args, kwargs = pedantic_options.setup_and_get_args_kwargs()
130
+
131
+ self.instrument_hooks.set_feature(FEATURE_DISABLE_CALLGRIND_MARKERS, True)
132
+ self.instrument_hooks.start_benchmark()
133
+
134
+ # Manually call the library function to avoid an extra stack frame. Also
135
+ # call the callgrind markers directly to avoid extra overhead.
115
136
  self.instrument_hooks.lib.callgrind_start_instrumentation()
116
137
  try:
117
138
  out = __codspeed_root_frame__(*args, **kwargs)
118
139
  finally:
119
140
  self.instrument_hooks.lib.callgrind_stop_instrumentation()
141
+ self.instrument_hooks.stop_benchmark()
120
142
  self.instrument_hooks.set_executed_benchmark(uri)
121
143
  if pedantic_options.teardown is not None:
122
144
  pedantic_options.teardown(*args, **kwargs)
@@ -135,5 +157,5 @@ class ValgrindInstrument(Instrument):
135
157
  def get_result_dict(self) -> dict[str, Any]:
136
158
  return {
137
159
  "instrument": {"type": self.instrument},
138
- # bench results will be dumped by valgrind
160
+ # bench results will be dumped by the runner
139
161
  }
@@ -0,0 +1,197 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import platform
5
+ import shlex
6
+ import sys
7
+ import sysconfig
8
+ import warnings
9
+ from typing import TYPE_CHECKING
10
+
11
+ from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE
12
+
13
+ if TYPE_CHECKING:
14
+ from cffi import FFI
15
+
16
+ from .dist_instrument_hooks import InstrumentHooksPointer, LibType
17
+
18
+ # Feature flags for instrument hooks
19
+ FEATURE_DISABLE_CALLGRIND_MARKERS = 0
20
+
21
+
22
+ class InstrumentHooks:
23
+ """Zig library wrapper class providing benchmark measurement functionality."""
24
+
25
+ lib: LibType
26
+ ffi: FFI
27
+ instance: InstrumentHooksPointer
28
+
29
+ def __init__(self) -> None:
30
+ if os.environ.get("CODSPEED_ENV") is None:
31
+ raise RuntimeError(
32
+ "Can't run benchmarks outside of CodSpeed environment."
33
+ "Please set the CODSPEED_ENV environment variable."
34
+ )
35
+
36
+ try:
37
+ from .dist_instrument_hooks import ffi, lib # type: ignore
38
+ except ImportError as e:
39
+ raise RuntimeError(f"Failed to load instrument hooks library: {e}") from e
40
+ self.lib = lib
41
+ self.ffi = ffi
42
+
43
+ self.instance = self.lib.instrument_hooks_init()
44
+ if self.instance == 0:
45
+ raise RuntimeError("Failed to initialize CodSpeed instrumentation library.")
46
+
47
+ if SUPPORTS_PERF_TRAMPOLINE and not sys.is_stack_trampoline_active():
48
+ sys.activate_stack_trampoline("perf") # type: ignore
49
+
50
+ def __del__(self):
51
+ if hasattr(self, "lib") and hasattr(self, "instance"):
52
+ self.lib.instrument_hooks_deinit(self.instance)
53
+
54
+ def start_benchmark(self) -> None:
55
+ """Start a new benchmark measurement."""
56
+ ret = self.lib.instrument_hooks_start_benchmark(self.instance)
57
+ if ret != 0:
58
+ warnings.warn("Failed to start benchmark measurement", RuntimeWarning)
59
+
60
+ def stop_benchmark(self) -> None:
61
+ """Stop the current benchmark measurement."""
62
+ ret = self.lib.instrument_hooks_stop_benchmark(self.instance)
63
+ if ret != 0:
64
+ warnings.warn("Failed to stop benchmark measurement", RuntimeWarning)
65
+
66
+ def set_executed_benchmark(self, uri: str, pid: int | None = None) -> None:
67
+ """Set the executed benchmark URI and process ID.
68
+
69
+ Args:
70
+ uri: The benchmark URI string identifier
71
+ pid: Optional process ID (defaults to current process)
72
+ """
73
+ if pid is None:
74
+ pid = os.getpid()
75
+
76
+ ret = self.lib.instrument_hooks_set_executed_benchmark(
77
+ self.instance, pid, uri.encode("ascii")
78
+ )
79
+ if ret != 0:
80
+ warnings.warn("Failed to set executed benchmark", RuntimeWarning)
81
+
82
+ def set_integration(self, name: str, version: str) -> None:
83
+ """Set the integration name and version."""
84
+ ret = self.lib.instrument_hooks_set_integration(
85
+ self.instance, name.encode("ascii"), version.encode("ascii")
86
+ )
87
+ if ret != 0:
88
+ warnings.warn("Failed to set integration name and version", RuntimeWarning)
89
+
90
+ def is_instrumented(self) -> bool:
91
+ """Check if simulation is active."""
92
+ return self.lib.instrument_hooks_is_instrumented(self.instance)
93
+
94
+ def set_feature(self, feature: int, enabled: bool) -> None:
95
+ """Set a feature flag in the instrument hooks library.
96
+
97
+ Args:
98
+ feature: The feature flag to set
99
+ enabled: Whether to enable or disable the feature
100
+ """
101
+ self.lib.instrument_hooks_set_feature(feature, enabled)
102
+
103
+ def set_environment(self, section_name: str, key: str, value: str) -> None:
104
+ """Register a key-value pair under a named section for environment collection.
105
+
106
+ Args:
107
+ section_name: The section name (e.g. "Python")
108
+ key: The key (e.g. "version")
109
+ value: The value (e.g. "3.13.12")
110
+ """
111
+ ret = self.lib.instrument_hooks_set_environment(
112
+ self.instance,
113
+ section_name.encode("utf-8"),
114
+ key.encode("utf-8"),
115
+ value.encode("utf-8"),
116
+ )
117
+ if ret != 0:
118
+ warnings.warn("Failed to set environment data", RuntimeWarning)
119
+
120
+ def set_environment_list(
121
+ self, section_name: str, key: str, values: list[str]
122
+ ) -> None:
123
+ """Register a list of values under a named section for environment collection.
124
+
125
+ Args:
126
+ section_name: The section name (e.g. "python")
127
+ key: The key (e.g. "build_args")
128
+ values: The list of string values
129
+ """
130
+ encoded = [self.ffi.new("char[]", v.encode("utf-8")) for v in values]
131
+ c_values = self.ffi.new("char*[]", encoded)
132
+ ret = self.lib.instrument_hooks_set_environment_list(
133
+ self.instance,
134
+ section_name.encode("utf-8"),
135
+ key.encode("utf-8"),
136
+ c_values,
137
+ len(encoded),
138
+ )
139
+ if ret != 0:
140
+ warnings.warn("Failed to set environment list data", RuntimeWarning)
141
+
142
+ def write_environment(self, pid: int | None = None) -> None:
143
+ """Flush all registered environment sections to disk.
144
+
145
+ Writes to $CODSPEED_PROFILE_FOLDER/environment-<pid>.json.
146
+
147
+ Args:
148
+ pid: Optional process ID (defaults to current process)
149
+ """
150
+ if pid is None:
151
+ pid = os.getpid()
152
+ ret = self.lib.instrument_hooks_write_environment(self.instance, pid)
153
+ if ret != 0:
154
+ warnings.warn("Failed to write environment data", RuntimeWarning)
155
+
156
+ def collect_and_write_python_environment(self) -> None:
157
+ """Collect Python toolchain information and write it to disk."""
158
+ section = "python"
159
+ set_env = self.set_environment
160
+
161
+ # Core identity
162
+ set_env(section, "version", sys.version.strip())
163
+ set_env(section, "implementation", sys.implementation.name.strip())
164
+ set_env(section, "compiler", platform.python_compiler().strip())
165
+
166
+ config_vars = sysconfig.get_config_vars()
167
+
168
+ # Build arguments as a list
169
+ config_args = config_vars.get("CONFIG_ARGS", "")
170
+ if config_args:
171
+ build_args = [arg.strip() for arg in shlex.split(config_args)]
172
+ self.set_environment_list(section, "build_args", build_args)
173
+
174
+ # Performance-relevant build configuration as "KEY=value" list
175
+ _SYSCONFIG_KEYS = (
176
+ "abiflags",
177
+ "PY_ENABLE_SHARED",
178
+ "Py_GIL_DISABLED",
179
+ "Py_DEBUG",
180
+ "WITH_PYMALLOC",
181
+ "WITH_MIMALLOC",
182
+ "WITH_FREELISTS",
183
+ "HAVE_COMPUTED_GOTOS",
184
+ "Py_STATS",
185
+ "Py_TRACE_REFS",
186
+ "WITH_VALGRIND",
187
+ "WITH_DTRACE",
188
+ )
189
+ config_items = []
190
+ for key in _SYSCONFIG_KEYS:
191
+ value = config_vars.get(key)
192
+ if value is not None:
193
+ config_items.append(f"{key}={str(value).strip()}")
194
+ config_items.append(f"perf_trampoline={SUPPORTS_PERF_TRAMPOLINE}")
195
+ self.set_environment_list(section, "config", config_items)
196
+
197
+ self.write_environment()
@@ -34,6 +34,17 @@ uint64_t instrument_hooks_current_timestamp(void);
34
34
 
35
35
  void callgrind_start_instrumentation();
36
36
  void callgrind_stop_instrumentation();
37
+
38
+ void instrument_hooks_set_feature(uint64_t feature, bool enabled);
39
+
40
+ uint8_t instrument_hooks_set_environment(InstrumentHooks *, const char *section_name,
41
+ const char *key, const char *value);
42
+ uint8_t instrument_hooks_set_environment_list(InstrumentHooks *,
43
+ const char *section_name,
44
+ const char *key,
45
+ const char *const *values,
46
+ uint32_t count);
47
+ uint8_t instrument_hooks_write_environment(InstrumentHooks *, uint32_t pid);
37
48
  """)
38
49
 
39
50
  ffibuilder.set_source(
@@ -45,6 +56,8 @@ ffibuilder.set_source(
45
56
  "src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c",
46
57
  ],
47
58
  include_dirs=[str(includes_dir)],
59
+ # IMPORTANT: Keep in sync with instrument-hooks/ci.yml (COMMON_CFLAGS)
60
+ extra_compile_args=["-Wno-format-security"],
48
61
  )
49
62
 
50
63
  if __name__ == "__main__":
@@ -1,3 +1,5 @@
1
+ from cffi import FFI
2
+
1
3
  InstrumentHooksPointer = object
2
4
 
3
5
  class lib:
@@ -29,5 +31,23 @@ class lib:
29
31
  def callgrind_start_instrumentation() -> int: ...
30
32
  @staticmethod
31
33
  def callgrind_stop_instrumentation() -> int: ...
34
+ @staticmethod
35
+ def instrument_hooks_set_feature(feature: int, enabled: bool) -> None: ...
36
+ @staticmethod
37
+ def instrument_hooks_set_environment(
38
+ hooks: InstrumentHooksPointer, section_name: bytes, key: bytes, value: bytes
39
+ ) -> int: ...
40
+ @staticmethod
41
+ def instrument_hooks_set_environment_list(
42
+ hooks: InstrumentHooksPointer,
43
+ section_name: bytes,
44
+ key: bytes,
45
+ values: FFI.CData,
46
+ count: int,
47
+ ) -> int: ...
48
+ @staticmethod
49
+ def instrument_hooks_write_environment(
50
+ hooks: InstrumentHooksPointer, pid: int
51
+ ) -> int: ...
32
52
 
33
53
  LibType = type[lib]