Skip to content

Commit cb03c07

Browse files
committed
--wip-- [with ci]
1 parent c92304c commit cb03c07

File tree

5 files changed

+84
-20
lines changed

5 files changed

+84
-20
lines changed

src/pytest_codspeed/instruments/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from enum import Enum
55
from typing import TYPE_CHECKING
66

7+
from pytest_codspeed.plugin import BenchmarkMarkerOptions
8+
79
if TYPE_CHECKING:
810
from typing import Any, Callable, ClassVar, ParamSpec, TypeVar
911

@@ -27,6 +29,7 @@ def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]: ...
2729
@abstractmethod
2830
def measure(
2931
self,
32+
marker_options: BenchmarkMarkerOptions,
3033
name: str,
3134
uri: str,
3235
fn: Callable[P, T],
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit b003e5024d61cfb784d6ac6f3ffd7d61bf7b9ec9

src/pytest_codspeed/instruments/valgrind/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pytest_codspeed import __semver_version__
88
from pytest_codspeed.instruments import Instrument
99
from pytest_codspeed.instruments.valgrind._wrapper import get_lib
10+
from pytest_codspeed.plugin import BenchmarkMarkerOptions
1011

1112
if TYPE_CHECKING:
1213
from typing import Any, Callable
@@ -40,7 +41,7 @@ def __init__(self, config: CodSpeedConfig) -> None:
4041
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
4142
config = (
4243
f"mode: instrumentation, "
43-
f"callgraph: {'enabled' if SUPPORTS_PERF_TRAMPOLINE else 'not supported'}"
44+
f"callgraph: {'enabled' if SUPPORTS_PERF_TRAMPOLINE else 'not supported'}"
4445
)
4546
warnings = []
4647
if not self.should_measure:
@@ -54,6 +55,7 @@ def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
5455

5556
def measure(
5657
self,
58+
marker_options: BenchmarkMarkerOptions,
5759
name: str,
5860
uri: str,
5961
fn: Callable[P, T],

src/pytest_codspeed/instruments/walltime.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from rich.text import Text
1313

1414
from pytest_codspeed.instruments import Instrument
15+
from pytest_codspeed.plugin import BenchmarkMarkerOptions
1516

1617
if TYPE_CHECKING:
1718
from typing import Any, Callable
@@ -24,7 +25,7 @@
2425
DEFAULT_WARMUP_TIME_NS = 1_000_000_000
2526
DEFAULT_MAX_TIME_NS = 3_000_000_000
2627
TIMER_RESOLUTION_NS = get_clock_info("perf_counter").resolution * 1e9
27-
DEFAULT_MIN_ROUND_TIME_NS = TIMER_RESOLUTION_NS * 1_000_000
28+
DEFAULT_MIN_ROUND_TIME_NS = int(TIMER_RESOLUTION_NS * 1_000_000)
2829

2930
IQR_OUTLIER_FACTOR = 1.5
3031
STDEV_OUTLIER_FACTOR = 3
@@ -38,16 +39,35 @@ class BenchmarkConfig:
3839
max_rounds: int | None
3940

4041
@classmethod
41-
def from_codspeed_config(cls, config: CodSpeedConfig) -> BenchmarkConfig:
42+
def from_codspeed_config_and_marker_data(
43+
cls, config: CodSpeedConfig, marker_data: BenchmarkMarkerOptions
44+
) -> BenchmarkConfig:
45+
if marker_data.max_time is not None:
46+
max_time_ns = int(marker_data.max_time * 1e9)
47+
elif config.max_time_ns is not None:
48+
max_time_ns = config.max_time_ns
49+
else:
50+
max_time_ns = DEFAULT_MAX_TIME_NS
51+
52+
if marker_data.max_rounds is not None:
53+
max_rounds = marker_data.max_rounds
54+
elif config.max_rounds is not None:
55+
max_rounds = config.max_rounds
56+
else:
57+
max_rounds = None
58+
59+
if marker_data.min_time is not None:
60+
min_round_time_ns = int(marker_data.min_time * 1e9)
61+
else:
62+
min_round_time_ns = DEFAULT_MIN_ROUND_TIME_NS
63+
4264
return cls(
4365
warmup_time_ns=config.warmup_time_ns
4466
if config.warmup_time_ns is not None
4567
else DEFAULT_WARMUP_TIME_NS,
46-
min_round_time_ns=DEFAULT_MIN_ROUND_TIME_NS,
47-
max_time_ns=config.max_time_ns
48-
if config.max_time_ns is not None
49-
else DEFAULT_MAX_TIME_NS,
50-
max_rounds=config.max_rounds,
68+
min_round_time_ns=min_round_time_ns,
69+
max_time_ns=max_time_ns,
70+
max_rounds=max_rounds,
5171
)
5272

5373

@@ -202,6 +222,7 @@ def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
202222

203223
def measure(
204224
self,
225+
marker_options: BenchmarkMarkerOptions,
205226
name: str,
206227
uri: str,
207228
fn: Callable[P, T],
@@ -214,7 +235,9 @@ def measure(
214235
fn=fn,
215236
args=args,
216237
kwargs=kwargs,
217-
config=BenchmarkConfig.from_codspeed_config(self.config),
238+
config=BenchmarkConfig.from_codspeed_config_and_marker_data(
239+
self.config, marker_options
240+
),
218241
)
219242
self.benchmarks.append(bench)
220243
return out

src/pytest_codspeed/plugin.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ def pytest_addoption(parser: pytest.Parser):
5858
action="store",
5959
type=float,
6060
help=(
61-
"The time to warm up the benchmark for (in seconds), "
62-
"only for walltime mode"
61+
"The time to warm up the benchmark for (in seconds), only for walltime mode"
6362
),
6463
)
6564
group.addoption(
@@ -223,6 +222,43 @@ def has_benchmark_fixture(item: pytest.Item) -> bool:
223222
return "benchmark" in item_fixtures or "codspeed_benchmark" in item_fixtures
224223

225224

225+
@dataclass(frozen=True)
226+
class BenchmarkMarkerOptions:
227+
group: str | None = None
228+
"""The group name to use for the benchmark"""
229+
min_time: int | None = None
230+
"""The minimum time of a round (in seconds)"""
231+
max_time: int | None = None
232+
"""The maximum time to run the benchmark for (in seconds)"""
233+
max_rounds: int | None = None
234+
"""The maximum number of rounds to run the benchmark for. Takes precedence over
235+
max_time."""
236+
237+
238+
def get_benchmark_marker_options(item: pytest.Item) -> BenchmarkMarkerOptions:
239+
marker = item.get_closest_marker("codspeed_benchmark") or item.get_closest_marker(
240+
"benchmark"
241+
)
242+
if marker is None:
243+
return BenchmarkMarkerOptions()
244+
if len(marker.args) > 0:
245+
raise ValueError("Positional arguments are not allowed in the benchmark marker")
246+
247+
options = BenchmarkMarkerOptions(
248+
group=marker.kwargs.pop("group", None),
249+
max_time=marker.kwargs.pop("max_time", None),
250+
min_time=marker.kwargs.pop("min_time", None),
251+
max_rounds=marker.kwargs.pop("max_rounds", None),
252+
)
253+
254+
if len(marker.kwargs) > 0:
255+
raise ValueError(
256+
"Unknown kwargs passed to benchmark marker: "
257+
+ ", ".join(marker.kwargs.keys())
258+
)
259+
return options
260+
261+
226262
def has_benchmark_marker(item: pytest.Item) -> bool:
227263
return (
228264
item.get_closest_marker("codspeed_benchmark") is not None
@@ -254,7 +290,7 @@ def pytest_collection_modifyitems(
254290

255291
def _measure(
256292
plugin: CodSpeedPlugin,
257-
nodeid: str,
293+
node: pytest.Item,
258294
config: pytest.Config,
259295
fn: Callable[P, T],
260296
*args: P.args,
@@ -266,8 +302,9 @@ def _measure(
266302
gc.collect()
267303
gc.disable()
268304
try:
269-
uri, name = get_git_relative_uri_and_name(nodeid, config.rootpath)
270-
return plugin.instrument.measure(name, uri, fn, *args, **kwargs)
305+
uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath)
306+
marker_options = get_benchmark_marker_options(node)
307+
return plugin.instrument.measure(marker_options, name, uri, fn, *args, **kwargs)
271308
finally:
272309
# Ensure GC is re-enabled even if the test failed
273310
if is_gc_enabled:
@@ -276,13 +313,13 @@ def _measure(
276313

277314
def wrap_runtest(
278315
plugin: CodSpeedPlugin,
279-
nodeid: str,
316+
node: pytest.Item,
280317
config: pytest.Config,
281318
fn: Callable[P, T],
282319
) -> Callable[P, T]:
283320
@functools.wraps(fn)
284321
def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
285-
return _measure(plugin, nodeid, config, fn, *args, **kwargs)
322+
return _measure(plugin, node, config, fn, *args, **kwargs)
286323

287324
return wrapped
288325

@@ -299,7 +336,7 @@ def pytest_runtest_protocol(item: pytest.Item, nextitem: pytest.Item | None):
299336
return None
300337

301338
# Wrap runtest and defer to default protocol
302-
item.runtest = wrap_runtest(plugin, item.nodeid, item.config, item.runtest)
339+
item.runtest = wrap_runtest(plugin, item, item.config, item.runtest)
303340
return None
304341

305342

@@ -343,9 +380,7 @@ def __call__(self, func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T
343380
config = self._request.config
344381
plugin = get_plugin(config)
345382
if plugin.is_codspeed_enabled:
346-
return _measure(
347-
plugin, self._request.node.nodeid, config, func, *args, **kwargs
348-
)
383+
return _measure(plugin, self._request.node, config, func, *args, **kwargs)
349384
else:
350385
return func(*args, **kwargs)
351386

0 commit comments

Comments
 (0)