Skip to content

Commit a454655

Browse files
committed
feat: support memory profiling
1 parent dd528ae commit a454655

6 files changed

Lines changed: 36 additions & 31 deletions

File tree

src/pytest_codspeed/instruments/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Instrument(metaclass=ABCMeta):
2121
instrument: ClassVar[str]
2222

2323
@abstractmethod
24-
def __init__(self, config: CodSpeedConfig): ...
24+
def __init__(self, config: CodSpeedConfig, mode: MeasurementMode): ...
2525

2626
@abstractmethod
2727
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]: ...
@@ -57,6 +57,7 @@ def get_result_dict(
5757

5858
class MeasurementMode(str, Enum):
5959
Simulation = "simulation"
60+
Memory = "memory"
6061
WallTime = "walltime"
6162

6263
@classmethod
@@ -68,12 +69,12 @@ def _missing_(cls, value: object):
6869

6970

7071
def get_instrument_from_mode(mode: MeasurementMode) -> type[Instrument]:
71-
from pytest_codspeed.instruments.valgrind import (
72-
ValgrindInstrument,
72+
from pytest_codspeed.instruments.analysis import (
73+
AnalysisInstrument,
7374
)
7475
from pytest_codspeed.instruments.walltime import WallTimeInstrument
7576

76-
if mode == MeasurementMode.Simulation:
77-
return ValgrindInstrument
77+
if mode in (MeasurementMode.Simulation, MeasurementMode.Memory):
78+
return AnalysisInstrument
7879
else:
7980
return WallTimeInstrument

src/pytest_codspeed/instruments/valgrind.py renamed to src/pytest_codspeed/instruments/analysis.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,44 @@
1515
from pytest import Session
1616

1717
from pytest_codspeed.config import PedanticOptions
18-
from pytest_codspeed.instruments import P, T
18+
from pytest_codspeed.instruments import MeasurementMode, P, T
1919
from pytest_codspeed.plugin import BenchmarkMarkerOptions, CodSpeedConfig
2020

2121

22-
class ValgrindInstrument(Instrument):
23-
instrument = "valgrind"
22+
class AnalysisInstrument(Instrument):
23+
instrument = "analysis"
2424
instrument_hooks: InstrumentHooks | None
25+
mode: MeasurementMode
2526

26-
def __init__(self, config: CodSpeedConfig) -> None:
27+
def __init__(self, config: CodSpeedConfig, mode: MeasurementMode) -> None:
28+
self.mode = mode
2729
self.benchmark_count = 0
2830
try:
2931
self.instrument_hooks = InstrumentHooks()
3032
self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__)
3133
except RuntimeError as e:
3234
if os.environ.get("CODSPEED_ENV") is not None:
3335
raise Exception(
34-
"Failed to initialize CPU simulation instrument hooks"
36+
f"Failed to initialize {self.mode.value} instrument hooks"
3537
) from e
3638
self.instrument_hooks = None
3739

3840
self.should_measure = self.instrument_hooks is not None
3941

4042
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
4143
config = (
42-
f"mode: simulation, "
44+
f"instrument: {self.mode.value}, "
4345
f"callgraph: {'enabled' if SUPPORTS_PERF_TRAMPOLINE else 'not supported'}"
4446
)
45-
warnings = []
47+
warns = []
4648
if not self.should_measure:
47-
warnings.append(
49+
warns.append(
4850
"\033[1m"
4951
"NOTICE: codspeed is enabled, but no performance measurement"
5052
" will be made since it's running in an unknown environment."
5153
"\033[0m"
5254
)
53-
return config, warnings
55+
return config, warns
5456

5557
def measure(
5658
self,
@@ -73,14 +75,12 @@ def __codspeed_root_frame__() -> T:
7375
# Warmup CPython performance map cache
7476
__codspeed_root_frame__()
7577

76-
# Manually call the library function to avoid an extra stack frame. Also
77-
# call the callgrind markers directly to avoid extra overhead.
78-
self.instrument_hooks.lib.callgrind_start_instrumentation()
78+
self.instrument_hooks.start_benchmark()
7979
try:
8080
return __codspeed_root_frame__()
8181
finally:
8282
# Ensure instrumentation is stopped even if the test failed
83-
self.instrument_hooks.lib.callgrind_stop_instrumentation()
83+
self.instrument_hooks.stop_benchmark()
8484
self.instrument_hooks.set_executed_benchmark(uri)
8585

8686
def measure_pedantic(
@@ -92,8 +92,8 @@ def measure_pedantic(
9292
) -> T:
9393
if pedantic_options.rounds != 1 or pedantic_options.iterations != 1:
9494
warnings.warn(
95-
"Valgrind instrument ignores rounds and iterations settings "
96-
"in pedantic mode"
95+
f"{self.mode.value.capitalize()} instrument ignores rounds and "
96+
"iterations settings in pedantic mode"
9797
)
9898
if not self.instrument_hooks:
9999
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
@@ -117,11 +117,12 @@ def __codspeed_root_frame__(*args, **kwargs) -> T:
117117

118118
# Compute the actual result of the function
119119
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
120-
self.instrument_hooks.lib.callgrind_start_instrumentation()
120+
121+
self.instrument_hooks.start_benchmark()
121122
try:
122123
out = __codspeed_root_frame__(*args, **kwargs)
123124
finally:
124-
self.instrument_hooks.lib.callgrind_stop_instrumentation()
125+
self.instrument_hooks.stop_benchmark()
125126
self.instrument_hooks.set_executed_benchmark(uri)
126127
if pedantic_options.teardown is not None:
127128
pedantic_options.teardown(*args, **kwargs)
@@ -140,5 +141,5 @@ def report(self, session: Session) -> None:
140141
def get_result_dict(self) -> dict[str, Any]:
141142
return {
142143
"instrument": {"type": self.instrument},
143-
# bench results will be dumped by valgrind
144+
# bench results will be dumped by the runner
144145
}

src/pytest_codspeed/instruments/walltime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from pytest import Session
2525

2626
from pytest_codspeed.config import PedanticOptions
27-
from pytest_codspeed.instruments import P, T
27+
from pytest_codspeed.instruments import MeasurementMode, P, T
2828
from pytest_codspeed.plugin import BenchmarkMarkerOptions, CodSpeedConfig
2929

3030
DEFAULT_WARMUP_TIME_NS = 1_000_000_000
@@ -159,7 +159,7 @@ class WallTimeInstrument(Instrument):
159159
instrument = "walltime"
160160
instrument_hooks: InstrumentHooks | None
161161

162-
def __init__(self, config: CodSpeedConfig) -> None:
162+
def __init__(self, config: CodSpeedConfig, mode: MeasurementMode) -> None:
163163
try:
164164
self.instrument_hooks = InstrumentHooks()
165165
self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__)

src/pytest_codspeed/plugin.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,11 @@ def pytest_configure(config: pytest.Config):
112112
)
113113

114114
if os.environ.get("CODSPEED_ENV") is not None:
115-
if os.environ.get("CODSPEED_RUNNER_MODE") == "walltime":
115+
runner_mode = os.environ.get("CODSPEED_RUNNER_MODE")
116+
if runner_mode == "walltime":
116117
default_mode = MeasurementMode.WallTime.value
118+
elif runner_mode == "memory":
119+
default_mode = MeasurementMode.Memory.value
117120
else:
118121
default_mode = MeasurementMode.Simulation.value
119122
else:
@@ -142,7 +145,7 @@ def pytest_configure(config: pytest.Config):
142145
disabled_plugins=tuple(disabled_plugins),
143146
is_codspeed_enabled=is_codspeed_enabled,
144147
mode=mode,
145-
instrument=instrument(codspeed_config),
148+
instrument=instrument(codspeed_config, mode),
146149
config=codspeed_config,
147150
profile_folder=Path(profile_folder) if profile_folder else None,
148151
)

tests/test_pytest_plugin_cpu_instrumentation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def fixtured_child():
7676
with open(perf_filepath) as perf_file:
7777
lines = perf_file.readlines()
7878
assert any(
79-
"py::ValgrindInstrument.measure.<locals>.__codspeed_root_frame__" in line
79+
"py::AnalysisInstrument.measure.<locals>.__codspeed_root_frame__" in line
8080
for line in lines
8181
), "No root frame found in perf map"
8282
assert any("py::test_some_addition_marked" in line for line in lines), (
@@ -135,8 +135,8 @@ def foo():
135135
result = run_pytest_codspeed_with_mode(pytester, MeasurementMode.Simulation)
136136
result.stdout.fnmatch_lines(
137137
[
138-
"*UserWarning: Valgrind instrument ignores rounds and iterations settings "
139-
"in pedantic mode*"
138+
"*UserWarning: Simulation instrument ignores rounds and iterations settings"
139+
" in pedantic mode*"
140140
]
141141
)
142142
result.assert_outcomes(passed=1)

0 commit comments

Comments
 (0)