pytest-benchmem 0.2.0__tar.gz → 0.3.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_benchmem-0.3.0/.github/workflows/ci.yaml +102 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/docs.yaml +2 -2
- pytest_benchmem-0.3.0/.github/workflows/nightly.yaml +40 -0
- pytest_benchmem-0.3.0/.readthedocs.yaml +11 -0
- pytest_benchmem-0.3.0/.release-please-manifest.json +3 -0
- pytest_benchmem-0.3.0/CHANGELOG.md +151 -0
- pytest_benchmem-0.3.0/PKG-INFO +219 -0
- pytest_benchmem-0.3.0/README.md +191 -0
- pytest_benchmem-0.3.0/docs/compare-plot.md +269 -0
- pytest_benchmem-0.3.0/docs/dims.md +105 -0
- pytest_benchmem-0.3.0/docs/getting-started.md +71 -0
- pytest_benchmem-0.3.0/docs/index.md +81 -0
- pytest_benchmem-0.3.0/docs/javascripts/notebook-prompts.js +54 -0
- pytest_benchmem-0.3.0/docs/metrics.md +170 -0
- pytest_benchmem-0.3.0/docs/reference.md +189 -0
- pytest_benchmem-0.3.0/docs/stylesheets/extra.css +757 -0
- pytest_benchmem-0.3.0/docs/sweeps.md +54 -0
- pytest_benchmem-0.3.0/docs_macros.py +44 -0
- pytest_benchmem-0.3.0/mkdocs.yml +108 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/pyproject.toml +9 -1
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/__init__.py +2 -3
- pytest_benchmem-0.3.0/src/pytest_benchmem/cli.py +346 -0
- pytest_benchmem-0.3.0/src/pytest_benchmem/combined.py +275 -0
- pytest_benchmem-0.3.0/src/pytest_benchmem/compare.py +474 -0
- pytest_benchmem-0.3.0/src/pytest_benchmem/format.py +75 -0
- pytest_benchmem-0.3.0/src/pytest_benchmem/memray.py +297 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/plotting.py +207 -28
- pytest_benchmem-0.3.0/src/pytest_benchmem/pytest_plugin.py +871 -0
- pytest_benchmem-0.3.0/src/pytest_benchmem/snapshot.py +359 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/sweep.py +8 -3
- pytest_benchmem-0.3.0/src/pytest_benchmem/tables.py +260 -0
- pytest_benchmem-0.3.0/tests/test_cli.py +318 -0
- pytest_benchmem-0.3.0/tests/test_combined.py +54 -0
- pytest_benchmem-0.3.0/tests/test_compare.py +337 -0
- pytest_benchmem-0.3.0/tests/test_distribution.py +70 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/test_inline_compare.py +6 -4
- pytest_benchmem-0.3.0/tests/test_memray.py +165 -0
- pytest_benchmem-0.3.0/tests/test_plotting.py +356 -0
- pytest_benchmem-0.3.0/tests/test_plugin.py +564 -0
- pytest_benchmem-0.3.0/tests/test_snapshot.py +298 -0
- pytest_benchmem-0.3.0/tests/test_tables.py +188 -0
- pytest_benchmem-0.2.0/.github/workflows/ci.yaml +0 -69
- pytest_benchmem-0.2.0/.release-please-manifest.json +0 -3
- pytest_benchmem-0.2.0/CHANGELOG.md +0 -73
- pytest_benchmem-0.2.0/PKG-INFO +0 -175
- pytest_benchmem-0.2.0/README.md +0 -148
- pytest_benchmem-0.2.0/docs/index.md +0 -51
- pytest_benchmem-0.2.0/docs/walkthrough.md +0 -227
- pytest_benchmem-0.2.0/mkdocs.yml +0 -41
- pytest_benchmem-0.2.0/src/pytest_benchmem/cli.py +0 -131
- pytest_benchmem-0.2.0/src/pytest_benchmem/compare.py +0 -217
- pytest_benchmem-0.2.0/src/pytest_benchmem/memray.py +0 -141
- pytest_benchmem-0.2.0/src/pytest_benchmem/pytest_plugin.py +0 -442
- pytest_benchmem-0.2.0/src/pytest_benchmem/snapshot.py +0 -175
- pytest_benchmem-0.2.0/tests/test_cli.py +0 -155
- pytest_benchmem-0.2.0/tests/test_compare.py +0 -145
- pytest_benchmem-0.2.0/tests/test_memray.py +0 -60
- pytest_benchmem-0.2.0/tests/test_plotting.py +0 -100
- pytest_benchmem-0.2.0/tests/test_plugin.py +0 -209
- pytest_benchmem-0.2.0/tests/test_snapshot.py +0 -155
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/dependabot.yml +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/pr-title.yaml +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/release.yaml +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.gitignore +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.pre-commit-config.yaml +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.release-please-config.json +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/LICENSE +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/py.typed +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/__init__.py +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/conftest.py +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/test_memray_platform.py +0 -0
- {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/test_sweep.py +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
name: Lint
|
|
16
|
+
runs-on: ubuntu-24.04
|
|
17
|
+
timeout-minutes: 5
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
- uses: astral-sh/setup-uv@v7
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
- run: uv sync --group dev
|
|
24
|
+
- run: uv run ruff check --output-format=github
|
|
25
|
+
- run: uv run ruff format --diff
|
|
26
|
+
|
|
27
|
+
typecheck:
|
|
28
|
+
name: Type check
|
|
29
|
+
runs-on: ubuntu-24.04
|
|
30
|
+
timeout-minutes: 5
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
- uses: astral-sh/setup-uv@v7
|
|
34
|
+
with:
|
|
35
|
+
enable-cache: true
|
|
36
|
+
- run: uv sync --group dev
|
|
37
|
+
- run: uv run mypy src/pytest_benchmem tests
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }})
|
|
41
|
+
runs-on: ${{ matrix.os }}
|
|
42
|
+
timeout-minutes: 10
|
|
43
|
+
strategy:
|
|
44
|
+
fail-fast: false
|
|
45
|
+
matrix:
|
|
46
|
+
os: [ubuntu-24.04]
|
|
47
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
48
|
+
include:
|
|
49
|
+
- os: macos-latest
|
|
50
|
+
python-version: "3.13"
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v6
|
|
53
|
+
- uses: astral-sh/setup-uv@v7
|
|
54
|
+
with:
|
|
55
|
+
enable-cache: true
|
|
56
|
+
- uses: actions/setup-python@v6
|
|
57
|
+
with:
|
|
58
|
+
python-version: ${{ matrix.python-version }}
|
|
59
|
+
- run: uv sync --group dev
|
|
60
|
+
- run: uv run pytest -q
|
|
61
|
+
|
|
62
|
+
smoke-install:
|
|
63
|
+
name: Smoke (built wheel)
|
|
64
|
+
runs-on: ubuntu-24.04
|
|
65
|
+
timeout-minutes: 10
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v6
|
|
68
|
+
- uses: astral-sh/setup-uv@v7
|
|
69
|
+
with:
|
|
70
|
+
enable-cache: true
|
|
71
|
+
- uses: actions/setup-python@v6
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.13"
|
|
74
|
+
- name: Build the wheel
|
|
75
|
+
run: uv build --wheel
|
|
76
|
+
- name: Install the built wheel into a clean env and use it
|
|
77
|
+
run: |
|
|
78
|
+
set -euxo pipefail
|
|
79
|
+
WHEEL="$(ls dist/*.whl)"
|
|
80
|
+
uv venv /tmp/smoke
|
|
81
|
+
# Only the built artifact (+ the plot extra for the CLI). Its declared
|
|
82
|
+
# deps must suffice on their own — no dev group to mask a missing one.
|
|
83
|
+
uv pip install --python /tmp/smoke/bin/python "pytest-benchmem[plot] @ file://$PWD/$WHEEL"
|
|
84
|
+
# 1) the pytest11 plugin records memory, from the installed wheel
|
|
85
|
+
printf 'def test_alloc(benchmark_memory):\n benchmark_memory(lambda: [0] * 500_000)\n' > /tmp/smoke_suite.py
|
|
86
|
+
/tmp/smoke/bin/python -m pytest /tmp/smoke_suite.py \
|
|
87
|
+
--benchmark-only --benchmark-json=/tmp/bench.json -p no:cacheprovider
|
|
88
|
+
# 2) the console script is on PATH and reads what the plugin produced
|
|
89
|
+
/tmp/smoke/bin/benchmem --help
|
|
90
|
+
cp /tmp/bench.json /tmp/bench2.json
|
|
91
|
+
/tmp/smoke/bin/benchmem compare /tmp/bench.json /tmp/bench2.json --columns peak
|
|
92
|
+
# 3) py.typed is packaged, so downstream type-checkers see our types
|
|
93
|
+
/tmp/smoke/bin/python -c "from importlib.resources import files; assert files('pytest_benchmem').joinpath('py.typed').is_file()"
|
|
94
|
+
|
|
95
|
+
ci-passed:
|
|
96
|
+
name: CI passed
|
|
97
|
+
if: always()
|
|
98
|
+
needs: [lint, typecheck, test, smoke-install]
|
|
99
|
+
runs-on: ubuntu-24.04
|
|
100
|
+
steps:
|
|
101
|
+
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
|
102
|
+
run: exit 1
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
- uses: astral-sh/setup-uv@v7
|
|
23
23
|
with:
|
|
24
24
|
enable-cache: true
|
|
25
|
-
# Build the
|
|
25
|
+
# Build the notebooks from their jupytext sources, then build+execute the site.
|
|
26
26
|
- run: uv sync --group docs
|
|
27
|
-
- run: uv run jupytext --to ipynb docs/
|
|
27
|
+
- run: uv run jupytext --to ipynb docs/compare-plot.md
|
|
28
28
|
- run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Deps range (scheduled)
|
|
2
|
+
|
|
3
|
+
# Verifies our declared dependency range still works, at both ends — neither is a
|
|
4
|
+
# per-PR concern (one is time-triggered, the other rarely regresses), so they run
|
|
5
|
+
# weekly instead of on every push:
|
|
6
|
+
# - floor: the lowest versions our pyproject allows (pytest>=7, pytest-benchmark>=4,
|
|
7
|
+
# memray>=1.11) — proves the declared support range is real.
|
|
8
|
+
# - latest: the newest *allowed* versions, ignoring the lock — catches an upstream
|
|
9
|
+
# release that breaks our coupling to their internals (memray._memray
|
|
10
|
+
# .compute_statistics, pytest-benchmark's pedantic signature) before a
|
|
11
|
+
# user does. (Upper bounds: memray<2, pytest-benchmark<6.)
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
schedule:
|
|
15
|
+
- cron: "0 6 * * 1" # Mondays 06:00 UTC
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deps:
|
|
20
|
+
name: Test (${{ matrix.label }})
|
|
21
|
+
runs-on: ubuntu-24.04
|
|
22
|
+
timeout-minutes: 10
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
include:
|
|
27
|
+
- label: floor deps
|
|
28
|
+
python: "3.11"
|
|
29
|
+
resolution: "--resolution lowest-direct"
|
|
30
|
+
- label: latest allowed deps
|
|
31
|
+
python: "3.13"
|
|
32
|
+
resolution: "--upgrade"
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v6
|
|
35
|
+
- uses: astral-sh/setup-uv@v7
|
|
36
|
+
- uses: actions/setup-python@v6
|
|
37
|
+
with:
|
|
38
|
+
python-version: ${{ matrix.python }}
|
|
39
|
+
- run: uv sync --group dev ${{ matrix.resolution }}
|
|
40
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
os: ubuntu-24.04
|
|
5
|
+
tools:
|
|
6
|
+
python: "3.13"
|
|
7
|
+
commands:
|
|
8
|
+
- pip install uv
|
|
9
|
+
- uv sync --group docs
|
|
10
|
+
- uv run jupytext --to ipynb docs/getting-started.md docs/metrics.md docs/dims.md docs/compare-plot.md docs/reference.md
|
|
11
|
+
- uv run mkdocs build --strict --site-dir "$READTHEDOCS_OUTPUT/html"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0](https://github.com/fluxopt/pytest-benchmem/compare/v0.2.1...v0.3.0) (2026-06-16)
|
|
4
|
+
|
|
5
|
+
This release reshapes how a memory measurement is taken, stored, and reported. Each
|
|
6
|
+
`benchmark_memory` run now **samples adaptively** and keeps its **full per-repeat series**
|
|
7
|
+
instead of a single number — so you can ask how noisy a metric is (`--stat
|
|
8
|
+
min|mean|max|median|stddev`) — and the terminal prints **one combined timing + memory
|
|
9
|
+
table**. `benchmem compare` is **rebuilt on pytest-benchmark's table model** (rows per
|
|
10
|
+
benchmark × run, a metric × stat column grid, relative `(×)` multipliers, `--group-by` /
|
|
11
|
+
`--columns`). Plus a `benchmem sweep` CLI for cross-version runs, `--where` / `--free-axes`
|
|
12
|
+
plot controls, action-scoped memory ceilings (`@pytest.mark.benchmem(max_peak=...)`), and
|
|
13
|
+
`--benchmark-memory-profile` to keep a memray `.bin` for regressions.
|
|
14
|
+
|
|
15
|
+
Consolidating the metric surface around the per-repeat series retired a few redundant
|
|
16
|
+
knobs — see the migration notes below.
|
|
17
|
+
|
|
18
|
+
### ⚠ BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
Pre-1.0 changes vs 0.2.1, each quick to migrate:
|
|
21
|
+
|
|
22
|
+
* **Saved memory JSON from 0.2.x no longer loads.** The blob under
|
|
23
|
+
`extra_info["benchmem"]` is now three flat per-repeat arrays (`peak_bytes` /
|
|
24
|
+
`allocations` / `total_bytes`), with the headline derived on read; the old denormalized
|
|
25
|
+
shape is not parsed. **Migrate:** re-run the suite with `--benchmark-memory` to
|
|
26
|
+
regenerate the runs. Timing-only pytest-benchmark files are unaffected. ([#75](https://github.com/fluxopt/pytest-benchmem/issues/75))
|
|
27
|
+
* **The `peak_max` metric is removed** — it was `peak` reduced by max (a stat of a stat), so
|
|
28
|
+
it couldn't itself take a `--stat`. **Migrate:** `--metric peak --stat max`. ([#75](https://github.com/fluxopt/pytest-benchmem/issues/75))
|
|
29
|
+
* **The `memory` alias for `peak` is removed** — it read like a category, not a synonym.
|
|
30
|
+
**Migrate:** `--metric peak`. ([#75](https://github.com/fluxopt/pytest-benchmem/issues/75))
|
|
31
|
+
* **The `gross` metric and the per-blob `mode` tag are removed** — both were leftovers of the
|
|
32
|
+
reverted RSS engine (`gross` had no producer and always errored; `mode` was a constant).
|
|
33
|
+
Reading a legacy blob ignores a stray `mode` key, so **no migration is needed**. ([#67](https://github.com/fluxopt/pytest-benchmem/issues/67))
|
|
34
|
+
|
|
35
|
+
### Features
|
|
36
|
+
|
|
37
|
+
**Memory measurement**
|
|
38
|
+
* **Adaptive sampling** — passes run until the peak floor settles instead of a fixed count; `--benchmark-memory-repeats` still forces a fixed count for reproducible gating. ([#97](https://github.com/fluxopt/pytest-benchmem/issues/97), [#79](https://github.com/fluxopt/pytest-benchmem/issues/79))
|
|
39
|
+
* **Per-repeat series** with `--stat` distributions, min/mean/max spread columns, and `--benchmark-memory-columns` / `-stats` selection. ([#72](https://github.com/fluxopt/pytest-benchmem/issues/72), [#76](https://github.com/fluxopt/pytest-benchmem/issues/76))
|
|
40
|
+
* **Action-scoped absolute ceilings** via `@pytest.mark.benchmem(max_peak=…, max_allocated=…, max_allocations=…)`. ([#86](https://github.com/fluxopt/pytest-benchmem/issues/86))
|
|
41
|
+
* **`--benchmark-memory-profile DIR`** keeps the memray `.bin` for regressions (or every measured benchmark), to render with `memray flamegraph`. ([#100](https://github.com/fluxopt/pytest-benchmem/issues/100), closes [#24](https://github.com/fluxopt/pytest-benchmem/issues/24))
|
|
42
|
+
* Actionable error when a memray `Tracker` is already active (e.g. pytest-memray on the same test). ([#89](https://github.com/fluxopt/pytest-benchmem/issues/89))
|
|
43
|
+
|
|
44
|
+
**Tables & compare**
|
|
45
|
+
* **One combined timing + memory table** by default. ([#65](https://github.com/fluxopt/pytest-benchmem/issues/65), [#68](https://github.com/fluxopt/pytest-benchmem/issues/68))
|
|
46
|
+
* **`benchmem compare` rebuilt on pytest-benchmark's table model** — rows per (benchmark × run), a metric × stat column grid with `--columns` / `--group-by` / `--metric both`, relative `(×)` multipliers, plus `--csv` / `--sort`. ([#98](https://github.com/fluxopt/pytest-benchmem/issues/98), [#101](https://github.com/fluxopt/pytest-benchmem/issues/101), [#74](https://github.com/fluxopt/pytest-benchmem/issues/74))
|
|
47
|
+
|
|
48
|
+
**Plotting**
|
|
49
|
+
* **`--where KEY=VALUE`** row filter, **`--free-axes x|y|both`** for faceted views, and min…max spread whiskers on scaling plots. ([#93](https://github.com/fluxopt/pytest-benchmem/issues/93), [#95](https://github.com/fluxopt/pytest-benchmem/issues/95), [#99](https://github.com/fluxopt/pytest-benchmem/issues/99))
|
|
50
|
+
|
|
51
|
+
**CLI**
|
|
52
|
+
* **`benchmem sweep`** — cross-version sweeps without a harness. ([#87](https://github.com/fluxopt/pytest-benchmem/issues/87))
|
|
53
|
+
* Caller-labelled snapshots, decoupling series names from filenames. ([#57](https://github.com/fluxopt/pytest-benchmem/issues/57))
|
|
54
|
+
|
|
55
|
+
### Bug Fixes
|
|
56
|
+
|
|
57
|
+
* `compare` no longer crashes when two runs share a file stem. ([#64](https://github.com/fluxopt/pytest-benchmem/issues/64))
|
|
58
|
+
|
|
59
|
+
## [0.2.1](https://github.com/fluxopt/pytest-benchmem/compare/v0.2.0...v0.2.1) (2026-06-13)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Features
|
|
63
|
+
|
|
64
|
+
* add `rss` memory mode — kernel resident high-water via a forked child ([#51](https://github.com/fluxopt/pytest-benchmem/issues/51)) ([c4ccd12](https://github.com/fluxopt/pytest-benchmem/commit/c4ccd1267b9b795d88f377e005b6b6f060ccc40a))
|
|
65
|
+
* add peak_max and gross metrics; error on metric/mode mismatch ([#55](https://github.com/fluxopt/pytest-benchmem/issues/55)) ([df45d1f](https://github.com/fluxopt/pytest-benchmem/commit/df45d1fc44ece79db297ae5b0dbc1537f5cb3e8f))
|
|
66
|
+
* built-in node.* dims (module/func/class/group), selectable in plots ([#54](https://github.com/fluxopt/pytest-benchmem/issues/54)) ([937ca0f](https://github.com/fluxopt/pytest-benchmem/commit/937ca0f470d467046d81d1db549a51cfa44dfdd0))
|
|
67
|
+
* tag memory blobs with a `mode` and refuse to mix modes ([#40](https://github.com/fluxopt/pytest-benchmem/issues/40)) ([4251e16](https://github.com/fluxopt/pytest-benchmem/commit/4251e16a79d4ec66d5b39a41c924633c256287ee))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Bug Fixes
|
|
71
|
+
|
|
72
|
+
* accept str and single-path snapshots in plot_* and load_long_df ([#52](https://github.com/fluxopt/pytest-benchmem/issues/52)) ([fdbc343](https://github.com/fluxopt/pytest-benchmem/commit/fdbc3432c8709ba0c3ed2f4c85df6ff4f45a4a30))
|
|
73
|
+
* keep only scalar dims, dropping unserializable and structured values ([#45](https://github.com/fluxopt/pytest-benchmem/issues/45)) ([71cb05d](https://github.com/fluxopt/pytest-benchmem/commit/71cb05d464d66bb55699e582103261f335a3e1a3))
|
|
74
|
+
* metric ergonomics — correct allocated label, accept memory alias ([#53](https://github.com/fluxopt/pytest-benchmem/issues/53)) ([a61c9be](https://github.com/fluxopt/pytest-benchmem/commit/a61c9befb6aae6feca5f504f9fb9e16b35ed5f3f))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Reverts
|
|
78
|
+
|
|
79
|
+
* remove the `rss` memory mode ([#51](https://github.com/fluxopt/pytest-benchmem/issues/51)) ([#56](https://github.com/fluxopt/pytest-benchmem/issues/56)) ([99d908d](https://github.com/fluxopt/pytest-benchmem/commit/99d908d582094d982f421cc381639f53b6585a56))
|
|
80
|
+
|
|
81
|
+
## [0.2.0](https://github.com/fluxopt/pytest-benchmem/compare/v0.1.0...v0.2.0) (2026-06-13)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### ⚠ BREAKING CHANGES
|
|
85
|
+
|
|
86
|
+
* mirror memray's metrics — add total memory allocated ([#39](https://github.com/fluxopt/pytest-benchmem/issues/39))
|
|
87
|
+
* memory regression gate, @pytest.mark.benchmem, bytes blob migration ([#32](https://github.com/fluxopt/pytest-benchmem/issues/32))
|
|
88
|
+
|
|
89
|
+
### Features
|
|
90
|
+
|
|
91
|
+
* empirical scaling fit — report O(n^x) over a size sweep ([#36](https://github.com/fluxopt/pytest-benchmem/issues/36)) ([4bc6e46](https://github.com/fluxopt/pytest-benchmem/commit/4bc6e4683142c46c9c60765dbf1f8e39adb3388f))
|
|
92
|
+
* memory regression gate, [@pytest](https://github.com/pytest).mark.benchmem, bytes blob migration ([#32](https://github.com/fluxopt/pytest-benchmem/issues/32)) ([654c582](https://github.com/fluxopt/pytest-benchmem/commit/654c582a15a8f8ef8e79a7c1d41767660d5f962a))
|
|
93
|
+
* mirror memray's metrics — add total memory allocated ([#39](https://github.com/fluxopt/pytest-benchmem/issues/39)) ([0b6fad6](https://github.com/fluxopt/pytest-benchmem/commit/0b6fad6f90ef124b1c80ca368c1d4db6fe5b34af))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Bug Fixes
|
|
97
|
+
|
|
98
|
+
* correct release-please pre-major key so feat! bumps 0.2.0 not 1.0.0 ([#41](https://github.com/fluxopt/pytest-benchmem/issues/41)) ([fac21e0](https://github.com/fluxopt/pytest-benchmem/commit/fac21e08bfaf5816f067fd78da57aa623c1b563d))
|
|
99
|
+
|
|
100
|
+
## [0.1.0](https://github.com/fluxopt/pytest-benchmem/compare/v0.1.0...v0.1.0) (2026-06-13)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
### ⚠ BREAKING CHANGES
|
|
104
|
+
|
|
105
|
+
* rename package peakbench → pytest-benchmem ([#10](https://github.com/fluxopt/pytest-benchmem/issues/10))
|
|
106
|
+
* pivot to the memory companion to pytest-benchmark ([#7](https://github.com/fluxopt/pytest-benchmem/issues/7))
|
|
107
|
+
|
|
108
|
+
### Features
|
|
109
|
+
|
|
110
|
+
* --benchmark-memory flag to augment existing benchmark() calls ([#16](https://github.com/fluxopt/pytest-benchmem/issues/16)) ([aff17c7](https://github.com/fluxopt/pytest-benchmem/commit/aff17c76ad742cedc963e7914bd575846ddc6035))
|
|
111
|
+
* pivot to the memory companion to pytest-benchmark ([#7](https://github.com/fluxopt/pytest-benchmem/issues/7)) ([52f2b6b](https://github.com/fluxopt/pytest-benchmem/commit/52f2b6b56af1ee61f84fcd0ab9dc0947004fd31d))
|
|
112
|
+
* resolve [#2](https://github.com/fluxopt/pytest-benchmem/issues/2) — drop select=, bless filter-before-convert ([89fbfcd](https://github.com/fluxopt/pytest-benchmem/commit/89fbfcdde439eb6f1535df8e65d2295b2ea69add))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### Miscellaneous Chores
|
|
116
|
+
|
|
117
|
+
* release pytest-benchmem as 0.1.0 ([#13](https://github.com/fluxopt/pytest-benchmem/issues/13)) ([8e51760](https://github.com/fluxopt/pytest-benchmem/commit/8e51760d7bdfe4e44ea77856e8a0c4e158de0099))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
### Code Refactoring
|
|
121
|
+
|
|
122
|
+
* rename package peakbench → pytest-benchmem ([#10](https://github.com/fluxopt/pytest-benchmem/issues/10)) ([1e48259](https://github.com/fluxopt/pytest-benchmem/commit/1e482590bef2767df1619bcae15a6d132f4cc610))
|
|
123
|
+
|
|
124
|
+
## [0.1.0](https://github.com/fluxopt/pytest-benchmem/compare/v0.0.1...v0.1.0) (2026-06-13)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
### ⚠ BREAKING CHANGES
|
|
128
|
+
|
|
129
|
+
* rename package peakbench → pytest-benchmem ([#10](https://github.com/fluxopt/pytest-benchmem/issues/10))
|
|
130
|
+
* pivot to the memory companion to pytest-benchmark ([#7](https://github.com/fluxopt/pytest-benchmem/issues/7))
|
|
131
|
+
|
|
132
|
+
### Features
|
|
133
|
+
|
|
134
|
+
* pivot to the memory companion to pytest-benchmark ([#7](https://github.com/fluxopt/pytest-benchmem/issues/7)) ([52f2b6b](https://github.com/fluxopt/pytest-benchmem/commit/52f2b6b56af1ee61f84fcd0ab9dc0947004fd31d))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### Miscellaneous Chores
|
|
138
|
+
|
|
139
|
+
* release pytest-benchmem as 0.1.0 ([#13](https://github.com/fluxopt/pytest-benchmem/issues/13)) ([8e51760](https://github.com/fluxopt/pytest-benchmem/commit/8e51760d7bdfe4e44ea77856e8a0c4e158de0099))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Code Refactoring
|
|
143
|
+
|
|
144
|
+
* rename package peakbench → pytest-benchmem ([#10](https://github.com/fluxopt/pytest-benchmem/issues/10)) ([1e48259](https://github.com/fluxopt/pytest-benchmem/commit/1e482590bef2767df1619bcae15a6d132f4cc610))
|
|
145
|
+
|
|
146
|
+
## 0.0.1 (2026-06-13)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
### Features
|
|
150
|
+
|
|
151
|
+
* resolve [#2](https://github.com/fluxopt/pytest-benchmem/issues/2) — drop select=, bless filter-before-convert ([89fbfcd](https://github.com/fluxopt/pytest-benchmem/commit/89fbfcdde439eb6f1535df8e65d2295b2ea69add))
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-benchmem
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The memory companion to pytest-benchmark: a memray peak-memory pass on the same test, plus dims-aware plots and cross-version sweeps.
|
|
5
|
+
Project-URL: Homepage, https://github.com/fluxopt/pytest-benchmem
|
|
6
|
+
Project-URL: Documentation, https://fluxopt.github.io/pytest-benchmem/
|
|
7
|
+
Project-URL: Repository, https://github.com/fluxopt/pytest-benchmem
|
|
8
|
+
Project-URL: Issues, https://github.com/fluxopt/pytest-benchmem/issues
|
|
9
|
+
Author: Felix Bumann
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: benchmark,memory,memray,performance,pytest,pytest-benchmark
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: Pytest
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: memray<2,>=1.11; platform_system == 'Linux' or platform_system == 'Darwin'
|
|
20
|
+
Requires-Dist: pytest-benchmark<6,>=4
|
|
21
|
+
Requires-Dist: pytest>=7
|
|
22
|
+
Requires-Dist: rich>=13
|
|
23
|
+
Provides-Extra: plot
|
|
24
|
+
Requires-Dist: pandas; extra == 'plot'
|
|
25
|
+
Requires-Dist: plotly>=5; extra == 'plot'
|
|
26
|
+
Requires-Dist: typer>=0.12; extra == 'plot'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# pytest-benchmem
|
|
30
|
+
|
|
31
|
+
[](https://github.com/fluxopt/pytest-benchmem/actions/workflows/ci.yaml)
|
|
32
|
+

|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
**The memory companion to [pytest-benchmark].** It times your code; pytest-benchmem
|
|
36
|
+
adds a memray **peak-memory** pass to the *same test, in the same run* — one node
|
|
37
|
+
id, one JSON file, both metrics. Plus dims-aware plots and cross-version sweeps.
|
|
38
|
+
|
|
39
|
+
📖 **[Full documentation](https://fluxopt.github.io/pytest-benchmem/)** — getting
|
|
40
|
+
started, metrics, dims, compare & plot, sweeps, and the reference.
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
A **drop-in** for an existing pytest-benchmark suite: add `--benchmark-memory` and every
|
|
45
|
+
`benchmark(...)` call also records peak memory — no test changes.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import pytest
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize("n", [10_000, 100_000, 1_000_000])
|
|
52
|
+
def test_sort(benchmark, n): # an ordinary pytest-benchmark test, unchanged
|
|
53
|
+
benchmark(sorted, list(range(n, 0, -1)))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pytest --benchmark-only --benchmark-memory # both metrics, in pytest-benchmark's own table
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Name (time in us) Min Median │ peak (MiB)
|
|
62
|
+
──────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
test_sort[10000] 32.5830 (1.0) 41.2080 (1.0) │ 0.08
|
|
64
|
+
test_sort[100000] 321.2080 (9.86) 419.9160 (10.19) │ 0.76
|
|
65
|
+
test_sort[1000000] 3,669.2920 (112.61) 4,331.5421 (105.11) │ 7.63
|
|
66
|
+
|
|
67
|
+
memory (right of │): a separate, untimed pass, not the timed rounds • also available via --benchmark-memory-columns: allocated, allocs
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Left of the divider is pytest-benchmark's timing, untouched; right is pytest-benchmem's
|
|
71
|
+
memory. The two never overlap — memray measures peak on a *separate, untimed* call, so the
|
|
72
|
+
allocator hooks cost the timing nothing. It's opt-in at the run level: without the flag, your
|
|
73
|
+
suite runs exactly as before. Peak is the headline, so it shows by default; `allocated` and
|
|
74
|
+
`allocs` are one flag away (`--benchmark-memory-columns`).
|
|
75
|
+
|
|
76
|
+
Add `--benchmark-json=run.json` and both persist under one node id: timing in `stats`,
|
|
77
|
+
memory in `extra_info.benchmem`. Parametrize `params` become the dims the plots scale by —
|
|
78
|
+
see [Grouping by dims](https://fluxopt.github.io/pytest-benchmem/dims/).
|
|
79
|
+
|
|
80
|
+
## Memory on specific tests — the `benchmark_memory` fixture
|
|
81
|
+
|
|
82
|
+
`--benchmark-memory` measures the whole suite. To opt in *per test* instead — no run-level
|
|
83
|
+
flag — swap `benchmark` for the `benchmark_memory` fixture on just those tests; it's always
|
|
84
|
+
measured, and adds a `pedantic` form for explicit control:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
def test_sort(benchmark_memory):
|
|
88
|
+
benchmark_memory(sorted, list(range(1_000_000, 0, -1)))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Memory passes are **adaptive** by default. Peak is allocator demand, not wall-clock jitter, so
|
|
92
|
+
each pass is exact; passes exist only to find the floor (this is sequential sampling, not
|
|
93
|
+
calibration). pytest-benchmem runs until the **minimum** peak settles (≥2 passes, so
|
|
94
|
+
the first pass's one-time warmup — lazy imports, arena growth — is discarded; capped at 10),
|
|
95
|
+
then reports that min. Deterministic code settles in a few passes; noisy code runs more, where
|
|
96
|
+
it helps. Force a fixed, reproducible count with `--benchmark-memory-repeats=N` (suite-wide) or
|
|
97
|
+
`@pytest.mark.benchmem(repeats=N)` (per test); every pass is kept and the headline is the min.
|
|
98
|
+
|
|
99
|
+
> **Your benchmark must be safe to re-run.** memray measures on an extra call, after
|
|
100
|
+
> pytest-benchmark's timing rounds — so a side-effectful action (mutates a fixture, fills a
|
|
101
|
+
> cache, drains an iterator, writes a file) records its already-warmed state, not a cold
|
|
102
|
+
> run, silently. Benchmark a pure call, or use the fixture's `pedantic` form with a `setup`
|
|
103
|
+
> that rebuilds fresh state each round.
|
|
104
|
+
|
|
105
|
+
## Reading it back
|
|
106
|
+
|
|
107
|
+
Timing rides pytest-benchmark's own tooling (`pytest-benchmark compare`,
|
|
108
|
+
`--benchmark-histogram`) — pytest-benchmem doesn't reimplement it. For **memory**,
|
|
109
|
+
and dims-aware views over either metric:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
benchmem compare base.json head.json # per-id table: time+peak, all stats
|
|
113
|
+
benchmem plot base.json head.json --metric peak # interactive plotly view
|
|
114
|
+
|
|
115
|
+
# name the series/columns independently of the filenames (else the file stem):
|
|
116
|
+
benchmem plot v067.json v070.json v080.json -l 0.6.7 -l 0.7.0 -l 0.8.0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
id base.json head.json change (B)
|
|
121
|
+
--------------------------------------------------------------------
|
|
122
|
+
test_sort[10000] 824 KiB 848 KiB +2.9%
|
|
123
|
+
test_sort[100000] 7.6 MiB 7.4 MiB -2.6%
|
|
124
|
+
test_sort[1000000] 76 MiB 91 MiB +20.0%
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Gate CI on a memory regression** — exit non-zero past a threshold, mirroring
|
|
128
|
+
pytest-benchmark's `--benchmark-compare-fail=min:5%` grammar for memory:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# standalone, over two saved JSON files:
|
|
132
|
+
benchmem compare base.json head.json --fail-on peak:10% --fail-on allocated:10% --fail-on allocations:5%
|
|
133
|
+
|
|
134
|
+
# or inline in the pytest run, against a prior saved run (pytest-benchmark storage):
|
|
135
|
+
pytest --benchmark-only --benchmark-memory \
|
|
136
|
+
--benchmark-memory-compare --benchmark-memory-compare-fail=peak:10%
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Thresholds are percent (`peak:10%`) or absolute (`peak:5MiB`), on `peak`, `allocated`
|
|
140
|
+
(total bytes, catches churn peak hides), or `allocations` (count, near-deterministic and
|
|
141
|
+
often a better tripwire than peak bytes). Add `--benchmark-memory-profile DIR` to the inline
|
|
142
|
+
gate to keep the memray profile (`DIR/<id>.bin`) for each *regressing* id — render the call
|
|
143
|
+
paths behind the delta with `memray flamegraph` (the run prints the exact command), so a
|
|
144
|
+
failed gate is debuggable, not just red.
|
|
145
|
+
|
|
146
|
+
Or pull the numbers into your own analysis:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from pytest_benchmem import from_pytest_benchmark, memory_from_pytest_benchmark
|
|
150
|
+
|
|
151
|
+
_, timing, _ = from_pytest_benchmark("run.json") # seconds, from stats
|
|
152
|
+
_, memory, _ = memory_from_pytest_benchmark("run.json") # bytes, from extra_info.benchmem
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Outside pytest, `measure_peak(lambda: build_model(1000))` returns the bare peak in
|
|
156
|
+
bytes; `measure_memory(...)` returns the full `MemoryResult` (peak, spread,
|
|
157
|
+
allocation count) — a one-liner for a REPL or notebook.
|
|
158
|
+
|
|
159
|
+
## Where it sits
|
|
160
|
+
|
|
161
|
+
Its reason to exist is the gap nothing else fills cleanly: **memray-precision
|
|
162
|
+
memory benchmarking** of your own code, right where you already benchmark. ASV's
|
|
163
|
+
`peakmem` is coarse RSS sampling that misses numpy/C-allocation detail; CodSpeed
|
|
164
|
+
covers CI timing.
|
|
165
|
+
|
|
166
|
+
| Need | Reach for | pytest-benchmem |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| CI regression, per-PR dashboard | **CodSpeed** | — (don't rebuild it) |
|
|
169
|
+
| Local timing + A/B compare | **pytest-benchmark** | rides it (timing is its job) |
|
|
170
|
+
| Rigorous perf history across commits | **ASV** | — (heavier, RSS memory) |
|
|
171
|
+
| **Precise local peak memory (numpy/C allocs)** | **memray** | ⭐ the core |
|
|
172
|
+
| Assert a memory limit / catch a leak *in a test* | **[pytest-memray]** | — (use it; see below) |
|
|
173
|
+
| *Which* function allocated, any test (flamegraph) | **[pytest-memray]** or `memray` | — (use them) |
|
|
174
|
+
| *Where* a benchmark's memory **regressed** | — | ⭐ `--benchmark-memory-profile` (keeps the `.bin`) |
|
|
175
|
+
| Memory *in your pytest-benchmark tests* | — | ⭐ fixture **or** `--benchmark-memory` |
|
|
176
|
+
| **Track/compare/plot** memory across inputs, versions, commits | — | ⭐ compare · sweep · plot |
|
|
177
|
+
|
|
178
|
+
In short: if your core need is *precise local memory* over the benchmarks you already
|
|
179
|
+
write — timing, sweeps, and plots in one vocabulary — that's pytest-benchmem.
|
|
180
|
+
|
|
181
|
+
### With pytest-memray
|
|
182
|
+
|
|
183
|
+
[pytest-memray] is the closest neighbor, and the two are **complements, not
|
|
184
|
+
rivals** — both are thin layers over the same memray engine, pointed in opposite
|
|
185
|
+
directions:
|
|
186
|
+
|
|
187
|
+
- **pytest-memray** is a *guardrail*: `@pytest.mark.limit_memory("100MiB")`,
|
|
188
|
+
`limit_leaks`, and flamegraphs that fail or diagnose a test. It tracks the
|
|
189
|
+
**whole test** (fixtures, data construction, teardown) — the right scope for
|
|
190
|
+
*"did this test use too much / leak?"*.
|
|
191
|
+
- **pytest-benchmem** is a *benchmark*: it measures **only the benchmarked
|
|
192
|
+
action**, alongside timing under one node id, and lets you **compare, sweep,
|
|
193
|
+
and plot** that number across inputs, versions, and commits — *"how does memory
|
|
194
|
+
scale / change?"*. It has no leak detection by design — but when a memory gate
|
|
195
|
+
flags a regression, `--benchmark-memory-profile` keeps the offending ids' memray
|
|
196
|
+
`.bin`s, so *which* allocation grew is a `memray flamegraph` away.
|
|
197
|
+
|
|
198
|
+
They coexist in one suite. One caveat: memray won't nest two trackers, so don't run
|
|
199
|
+
`pytest --memray` and a benchmem fixture on the *same* test.
|
|
200
|
+
|
|
201
|
+
## Install
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
uv add pytest-benchmem # the fixture + flag + memray engine
|
|
205
|
+
uv add "pytest-benchmem[plot]" # + the plot/compare CLI (pandas, plotly, typer)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
pytest-benchmark and memray are core deps; memray is Linux/macOS only, so Windows
|
|
209
|
+
installs cleanly with timing-only (the memory pass raises a clear error there).
|
|
210
|
+
|
|
211
|
+
## Status
|
|
212
|
+
|
|
213
|
+
Early. Extracted from the linopy internal benchmark suite, where it's the local
|
|
214
|
+
memory-profiling layer. API may move before 1.0.
|
|
215
|
+
|
|
216
|
+
[pytest-benchmark]: https://pytest-benchmark.readthedocs.io
|
|
217
|
+
[pytest-memray]: https://pytest-memray.readthedocs.io
|
|
218
|
+
[CodSpeed]: https://codspeed.io
|
|
219
|
+
[ASV]: https://asv.readthedocs.io
|