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.
Files changed (73) hide show
  1. pytest_benchmem-0.3.0/.github/workflows/ci.yaml +102 -0
  2. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/docs.yaml +2 -2
  3. pytest_benchmem-0.3.0/.github/workflows/nightly.yaml +40 -0
  4. pytest_benchmem-0.3.0/.readthedocs.yaml +11 -0
  5. pytest_benchmem-0.3.0/.release-please-manifest.json +3 -0
  6. pytest_benchmem-0.3.0/CHANGELOG.md +151 -0
  7. pytest_benchmem-0.3.0/PKG-INFO +219 -0
  8. pytest_benchmem-0.3.0/README.md +191 -0
  9. pytest_benchmem-0.3.0/docs/compare-plot.md +269 -0
  10. pytest_benchmem-0.3.0/docs/dims.md +105 -0
  11. pytest_benchmem-0.3.0/docs/getting-started.md +71 -0
  12. pytest_benchmem-0.3.0/docs/index.md +81 -0
  13. pytest_benchmem-0.3.0/docs/javascripts/notebook-prompts.js +54 -0
  14. pytest_benchmem-0.3.0/docs/metrics.md +170 -0
  15. pytest_benchmem-0.3.0/docs/reference.md +189 -0
  16. pytest_benchmem-0.3.0/docs/stylesheets/extra.css +757 -0
  17. pytest_benchmem-0.3.0/docs/sweeps.md +54 -0
  18. pytest_benchmem-0.3.0/docs_macros.py +44 -0
  19. pytest_benchmem-0.3.0/mkdocs.yml +108 -0
  20. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/pyproject.toml +9 -1
  21. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/__init__.py +2 -3
  22. pytest_benchmem-0.3.0/src/pytest_benchmem/cli.py +346 -0
  23. pytest_benchmem-0.3.0/src/pytest_benchmem/combined.py +275 -0
  24. pytest_benchmem-0.3.0/src/pytest_benchmem/compare.py +474 -0
  25. pytest_benchmem-0.3.0/src/pytest_benchmem/format.py +75 -0
  26. pytest_benchmem-0.3.0/src/pytest_benchmem/memray.py +297 -0
  27. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/plotting.py +207 -28
  28. pytest_benchmem-0.3.0/src/pytest_benchmem/pytest_plugin.py +871 -0
  29. pytest_benchmem-0.3.0/src/pytest_benchmem/snapshot.py +359 -0
  30. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/sweep.py +8 -3
  31. pytest_benchmem-0.3.0/src/pytest_benchmem/tables.py +260 -0
  32. pytest_benchmem-0.3.0/tests/test_cli.py +318 -0
  33. pytest_benchmem-0.3.0/tests/test_combined.py +54 -0
  34. pytest_benchmem-0.3.0/tests/test_compare.py +337 -0
  35. pytest_benchmem-0.3.0/tests/test_distribution.py +70 -0
  36. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/test_inline_compare.py +6 -4
  37. pytest_benchmem-0.3.0/tests/test_memray.py +165 -0
  38. pytest_benchmem-0.3.0/tests/test_plotting.py +356 -0
  39. pytest_benchmem-0.3.0/tests/test_plugin.py +564 -0
  40. pytest_benchmem-0.3.0/tests/test_snapshot.py +298 -0
  41. pytest_benchmem-0.3.0/tests/test_tables.py +188 -0
  42. pytest_benchmem-0.2.0/.github/workflows/ci.yaml +0 -69
  43. pytest_benchmem-0.2.0/.release-please-manifest.json +0 -3
  44. pytest_benchmem-0.2.0/CHANGELOG.md +0 -73
  45. pytest_benchmem-0.2.0/PKG-INFO +0 -175
  46. pytest_benchmem-0.2.0/README.md +0 -148
  47. pytest_benchmem-0.2.0/docs/index.md +0 -51
  48. pytest_benchmem-0.2.0/docs/walkthrough.md +0 -227
  49. pytest_benchmem-0.2.0/mkdocs.yml +0 -41
  50. pytest_benchmem-0.2.0/src/pytest_benchmem/cli.py +0 -131
  51. pytest_benchmem-0.2.0/src/pytest_benchmem/compare.py +0 -217
  52. pytest_benchmem-0.2.0/src/pytest_benchmem/memray.py +0 -141
  53. pytest_benchmem-0.2.0/src/pytest_benchmem/pytest_plugin.py +0 -442
  54. pytest_benchmem-0.2.0/src/pytest_benchmem/snapshot.py +0 -175
  55. pytest_benchmem-0.2.0/tests/test_cli.py +0 -155
  56. pytest_benchmem-0.2.0/tests/test_compare.py +0 -145
  57. pytest_benchmem-0.2.0/tests/test_memray.py +0 -60
  58. pytest_benchmem-0.2.0/tests/test_plotting.py +0 -100
  59. pytest_benchmem-0.2.0/tests/test_plugin.py +0 -209
  60. pytest_benchmem-0.2.0/tests/test_snapshot.py +0 -155
  61. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/dependabot.yml +0 -0
  62. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/dependabot-auto-merge.yaml +0 -0
  63. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/pr-title.yaml +0 -0
  64. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.github/workflows/release.yaml +0 -0
  65. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.gitignore +0 -0
  66. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.pre-commit-config.yaml +0 -0
  67. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/.release-please-config.json +0 -0
  68. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/LICENSE +0 -0
  69. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/src/pytest_benchmem/py.typed +0 -0
  70. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/__init__.py +0 -0
  71. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/conftest.py +0 -0
  72. {pytest_benchmem-0.2.0 → pytest_benchmem-0.3.0}/tests/test_memray_platform.py +0 -0
  73. {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 notebook from its jupytext source, then build+execute the site.
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/walkthrough.md
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,3 @@
1
+ {
2
+ ".": "0.3.0"
3
+ }
@@ -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
+ [![CI](https://github.com/fluxopt/pytest-benchmem/actions/workflows/ci.yaml/badge.svg)](https://github.com/fluxopt/pytest-benchmem/actions/workflows/ci.yaml)
32
+ ![Python](https://img.shields.io/badge/python-3.11%2B-blue)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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