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.
- {pytest_codspeed-4.2.0b0/src/pytest_codspeed.egg-info → pytest_codspeed-4.4.0}/PKG-INFO +10 -9
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/README.md +8 -8
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/pyproject.toml +8 -2
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/__init__.py +2 -2
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/__init__.py +14 -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
- pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/__init__.py +197 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/build.py +13 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi +20 -0
- pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c +30987 -0
- pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/compat.h +36 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/core.h +31 -15
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/scripts/release.py +4 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/walltime.py +38 -4
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/plugin.py +18 -7
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0/src/pytest_codspeed.egg-info}/PKG-INFO +10 -9
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/SOURCES.txt +3 -1
- pytest_codspeed-4.4.0/tests/test_format_time.py +35 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin.py +1 -1
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin_cpu_instrumentation.py +7 -9
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin_walltime.py +56 -1
- pytest_codspeed-4.2.0b0/src/pytest_codspeed/instruments/hooks/__init__.py +0 -82
- pytest_codspeed-4.2.0b0/src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c +0 -8638
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/LICENSE +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/setup.cfg +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/setup.py +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/config.py +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/callgrind.h +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/valgrind.h +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/zig.h +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/py.typed +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/utils.py +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/dependency_links.txt +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/entry_points.txt +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/requires.txt +0 -0
- {pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
[](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml)
|
|
61
62
|
[](https://pypi.org/project/pytest-codspeed)
|
|
62
|
-

|
|
63
64
|
[](https://discord.com/invite/MxpaCfKSqF)
|
|
64
65
|
[](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.
|
|
121
|
-
codspeed:
|
|
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-
|
|
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@
|
|
162
|
-
- uses: actions/setup-python@
|
|
162
|
+
- uses: actions/checkout@v5
|
|
163
|
+
- uses: actions/setup-python@v6
|
|
163
164
|
with:
|
|
164
|
-
python-version: "3.
|
|
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:
|
|
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
|
[](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml)
|
|
5
5
|
[](https://pypi.org/project/pytest-codspeed)
|
|
6
|
-

|
|
7
7
|
[](https://discord.com/invite/MxpaCfKSqF)
|
|
8
8
|
[](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.
|
|
65
|
-
codspeed:
|
|
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-
|
|
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@
|
|
106
|
-
- uses: actions/setup-python@
|
|
105
|
+
- uses: actions/checkout@v5
|
|
106
|
+
- uses: actions/setup-python@v6
|
|
107
107
|
with:
|
|
108
|
-
python-version: "3.
|
|
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:
|
|
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.
|
|
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.
|
|
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} 🚀"
|
{pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/__init__.py
RENAMED
|
@@ -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
|
-
|
|
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.
|
|
65
|
-
|
|
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
|
|
70
|
-
return
|
|
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
|
|
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
|
|
22
|
-
instrument = "
|
|
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
|
-
|
|
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:
|
|
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
|
-
"
|
|
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
|
|
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()
|
{pytest_codspeed-4.2.0b0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/build.py
RENAMED
|
@@ -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]
|