Skip to content

runner: show pool peaks for the Criterion SQL harness suites - #38

Merged
adriangb merged 2 commits into
mainfrom
pool-peak-criterion-note
Sep 23, 2026
Merged

adriangb merged 2 commits into
mainfrom
pool-peak-criterion-note

Conversation

@adriangb

@adriangb adriangb commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Problem

The "Memory Pool Peaks" section does not appear for the suites that bench.sh runs through the Criterion SQL harness (cargo bench --bench sql, e.g. spill_views, wide_schema, predicate_eval), even when the trigger sets DATAFUSION_RUNTIME_MEMORY_LIMIT. The comment also gives no reason. Example: apache/datafusion#23565 (comment) is a spill_views run with DATAFUSION_RUNTIME_MEMORY_LIMIT: "512M" that has no pool-peak section and no note.

The runner reads pool_peak_bytes only from the results JSON that dfbench writes. The Criterion SQL harness wrote no results JSON. Also, suites that set their own limit with SET datafusion.runtime.memory_limit (like spill_views) dropped the pool that records peaks. apache/datafusion#25644 fixes both problems on the DataFusion side.

This PR shows tables only after apache/datafusion#25644 is on DataFusion main. The runner takes bench.sh from main. Until then, these suites get the note described below.

Change

  1. Read the harness output. With bench: report per-query memory pool peaks from the Criterion SQL harness apache/datafusion#25644, the harness writes the same results JSON format, without timings, to results/<side>/criterion/<file>.json. snapshot_json_files / collect_new now also read that subdirectory, so these suites get the per-query table and the pool-vs-RSS row. Query ids are the Criterion ids, e.g. spill_views/q01_sort_string_1_distinct_repeated, the same as in critcmp. bench.sh compare / compare_detail read only results/<side>/*.json, so there is no second timing table and results_have_json is unchanged. A suite that sets its own limit in SQL shows peaks without DATAFUSION_RUNTIME_MEMORY_LIMIT.
  2. Explain missing data. Detection uses whether the invocation wrote a results JSON. There is no list of suite names.

The module docs, the section intro and the README describe the new behavior.

Tests

In controller/src/runner/pool_peak.rs:

  • criterion_suite_with_results_json_gets_the_table: spill_views data on both sides gives the table and no note, with and without a requested limit
  • criterion_suite_with_results_json_on_one_side_notes_the_other: an older base gives a base column of n/a and the one-side note, with no other notes
  • criterion_bench_with_limit_gets_a_note: no results JSON on either side gives the note (updated text)
  • data_present_renders_the_table_as_before: dfbench output is unchanged
  • collect_new_reads_the_criterion_results_subdir: a file in criterion/ is collected, with source spill_views

The input for the Criterion test cases matches the file that apache/datafusion#25644 wrote for ./bench.sh run spill_views. Peaks: q01 40.6 MiB, q02 39.8 MiB, q03 39.8 MiB, q04 102.6 MiB, q05 96.0 MiB. The limits are 40M and 96M.

cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test (215 passed) pass locally.

🤖 Generated with Claude Code

The "Memory Pool Peaks" section reads pool_peak_bytes only from the
dfbench results JSON. Criterion bench targets and the suites bench.sh
runs through the Criterion SQL harness write no such JSON, so the
section silently disappeared for them even when the trigger set
DATAFUSION_RUNTIME_MEMORY_LIMIT.

When a memory limit was requested (shared or per-side env) and a
requested benchmark left no results JSON on either side, render a note
that names it and says why. No limit: unchanged (no section). Data
present: the tables are unchanged, and the note is added only for the
benchmarks without JSON.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
apache/datafusion#25644 makes the Criterion SQL harness write a results
JSON (same format, no timings) to results/<side>/criterion/<file>.json.
bench.sh compare does not read that subdirectory, so there is no second
timing table next to critcmp.

The collector now also snapshots the criterion/ subdirectory, so those
suites get the per-query "Memory Pool Peaks" table. A suite that sets its
own limit in SQL (spill_views) gets it without
DATAFUSION_RUNTIME_MEMORY_LIMIT.

A side older than #25644 writes no file for these suites. If neither side
wrote one, the note from the previous commit is shown, with updated text.
If only one side wrote one, that side's column is n/a and a note names
the side.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@adriangb adriangb changed the title runner: explain missing pool peaks for Criterion benchmarks runner: show pool peaks for the Criterion SQL harness suites Sep 23, 2026
@adriangb
adriangb merged commit 527fefd into main Sep 23, 2026
3 checks passed
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 23, 2026
…ess (apache#25644)

## Which issue does this PR close?

- None. This is follow-up work to
apache#23985, which added per-query
`pool_peak_bytes` to the `dfbench` results JSON.

## Rationale for this change

The suites that `bench.sh` runs through the Criterion SQL harness
(`cargo bench --bench sql`: `spill_views`, `wide_schema`,
`predicate_eval`, and others) do not report a per-query memory pool
peak. The harness prints one number to stdout and writes no results
JSON. For example, this benchmark bot run of `spill_views` with
`DATAFUSION_RUNTIME_MEMORY_LIMIT: "512M"` has no pool peak section:
apache#23565 (comment)

A second problem: a suite that sets its own limit with `SET
datafusion.runtime.memory_limit` records nothing, even if the output
were written. `spill_views` does this in its `init` script. The `SET`
makes `SessionContext` build a new `RuntimeEnv` with a new pool, and
that drops the `PeakRecordingPool` that `CommonOpt::runtime_env_builder`
installed.

## What changes are included in this PR?

Two commits:

1. **Keep the recorder across a SQL `SET`.** After a benchmark's `load`
and `init` steps, `prepare_benchmark` puts a `PeakRecordingPool` in
front of the session's pool again if the pool has a finite limit and no
recorder. The new `RuntimeEnv` is installed the same way `SET
datafusion.runtime.*` does it
(`SessionStateBuilder::from(state).with_runtime_env(..)`), so tables and
config are kept. The simple runner (`benchmark_runner -o`) also goes
through `prepare_benchmark`, so it gets this fix too.
2. **Write a results JSON from the Criterion harness.** When
`BENCH_RESULTS_FILE` is set, the harness writes the same JSON format as
`dfbench -o`. Each case is the Criterion id (for example,
`spill_views/q01_sort_string_1_distinct_repeated`, the same as in
`critcmp`). `pool_peak_bytes` covers every execution Criterion makes of
the query, including warm-up. The `load`, `init` and `assert` steps are
not included. `iterations` is empty, because Criterion keeps the
timings. `bench.sh` passes `results/<name>/criterion/<file>.json` at
each SQL-harness call site. The subdirectory keeps the file out of
`bench.sh compare`, which reads `results/<name>/*.json` as timing
results. This means no second timing table.

The benchmark bot reads the new file in
adriangb/datafusion-benchmarking#38. The bot
takes `bench.sh` from `main`, so it shows these tables only after this
PR is merged. Until then, and for any side that is older than this PR,
the bot shows a note that the pool peaks are not available.

Not changed here: `SET datafusion.runtime.memory_limit` always builds a
`GreedyMemoryPool` and drops any wrapper, so `--mem-pool-type` has no
effect on suites that set their own limit. The fix in this PR stays in
the benchmarks crate. A core change that keeps a pool wrapper across
`SET` is possible, but it is out of scope for this PR.

## What is the testing strategy for this PR?

New unit tests in `benchmarks/src/sql_benchmark_runner.rs`:

- `sql_memory_limit_keeps_the_peak_recorder`: a SQL `SET` replaces the
harness pool, and the recorder is put back and records the next query.
- `no_memory_limit_gets_no_recorder`: without any limit, nothing
changes.
- `criterion_harness_writes_pool_peak_per_case`: an end-to-end Criterion
run of a suite whose `init` sets the limit writes a JSON with a non-zero
peak and empty `iterations`.

If the rewrap is disabled, the first and third tests fail.

`./bench.sh run spill_views`, the command that the bot runs
(`SQL_CARGO_COMMAND="cargo bench --bench sql -- --save-baseline ..."`),
writes `results/<name>/criterion/spill_views.json`. The peaks are the
same with `DATAFUSION_RUNTIME_MEMORY_LIMIT=512M` and with no env limit,
because the suite's own limits apply (40M for q01 to q03, 96M for q04
and q05):

| Query | `pool_peak_bytes` |
| --- | --- |
| `spill_views/q01_sort_string_1_distinct_repeated` | 42614784 (40.6
MiB) |
| `spill_views/q02_sort_string_1000_distinct_repeated` | 41709504 (39.8
MiB) |
| `spill_views/q03_sort_binary_1000_distinct_repeated` | 41709504 (39.8
MiB) |
| `spill_views/q04_sort_string_all_distinct_distinct` | 107544576 (102.6
MiB) |
| `spill_views/q05_group_by_string_all_distinct_distinct` | 100633968
(96.0 MiB) |

The q01 and q04 peaks are a little above their limits. The pool allows
this: infallible `grow` calls are not limited, and the recorder counts
what the pool grants.

`./bench.sh compare` with two such result sets reads no file.
`sort_tpch` (dfbench, queries 1 and 2,
`DATAFUSION_RUNTIME_MEMORY_LIMIT=512M`) is unchanged. It writes
`results/<name>/sort_tpch1.json` with 5 iterations and `pool_peak_bytes`
for each query, and it writes no `criterion/` directory.

## Are there any user-facing changes?

Only for benchmark tooling. There is a new optional `BENCH_RESULTS_FILE`
env var for `cargo bench --bench sql` (documented in
`benchmarks/sql_benchmarks/README.md`). `bench.sh run` now writes
`results/<name>/criterion/*.json` for SQL-harness suites. A SQL-set
memory limit now reports a pool peak. There are no public API changes
outside the benchmarks crate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Omega359 pushed a commit to Omega359/arrow-datafusion that referenced this pull request Oct 11, 2026
…ess (apache#25644)

## Which issue does this PR close?

- None. This is follow-up work to
apache#23985, which added per-query
`pool_peak_bytes` to the `dfbench` results JSON.

## Rationale for this change

The suites that `bench.sh` runs through the Criterion SQL harness
(`cargo bench --bench sql`: `spill_views`, `wide_schema`,
`predicate_eval`, and others) do not report a per-query memory pool
peak. The harness prints one number to stdout and writes no results
JSON. For example, this benchmark bot run of `spill_views` with
`DATAFUSION_RUNTIME_MEMORY_LIMIT: "512M"` has no pool peak section:
apache#23565 (comment)

A second problem: a suite that sets its own limit with `SET
datafusion.runtime.memory_limit` records nothing, even if the output
were written. `spill_views` does this in its `init` script. The `SET`
makes `SessionContext` build a new `RuntimeEnv` with a new pool, and
that drops the `PeakRecordingPool` that `CommonOpt::runtime_env_builder`
installed.

## What changes are included in this PR?

Two commits:

1. **Keep the recorder across a SQL `SET`.** After a benchmark's `load`
and `init` steps, `prepare_benchmark` puts a `PeakRecordingPool` in
front of the session's pool again if the pool has a finite limit and no
recorder. The new `RuntimeEnv` is installed the same way `SET
datafusion.runtime.*` does it
(`SessionStateBuilder::from(state).with_runtime_env(..)`), so tables and
config are kept. The simple runner (`benchmark_runner -o`) also goes
through `prepare_benchmark`, so it gets this fix too.
2. **Write a results JSON from the Criterion harness.** When
`BENCH_RESULTS_FILE` is set, the harness writes the same JSON format as
`dfbench -o`. Each case is the Criterion id (for example,
`spill_views/q01_sort_string_1_distinct_repeated`, the same as in
`critcmp`). `pool_peak_bytes` covers every execution Criterion makes of
the query, including warm-up. The `load`, `init` and `assert` steps are
not included. `iterations` is empty, because Criterion keeps the
timings. `bench.sh` passes `results/<name>/criterion/<file>.json` at
each SQL-harness call site. The subdirectory keeps the file out of
`bench.sh compare`, which reads `results/<name>/*.json` as timing
results. This means no second timing table.

The benchmark bot reads the new file in
adriangb/datafusion-benchmarking#38. The bot
takes `bench.sh` from `main`, so it shows these tables only after this
PR is merged. Until then, and for any side that is older than this PR,
the bot shows a note that the pool peaks are not available.

Not changed here: `SET datafusion.runtime.memory_limit` always builds a
`GreedyMemoryPool` and drops any wrapper, so `--mem-pool-type` has no
effect on suites that set their own limit. The fix in this PR stays in
the benchmarks crate. A core change that keeps a pool wrapper across
`SET` is possible, but it is out of scope for this PR.

## What is the testing strategy for this PR?

New unit tests in `benchmarks/src/sql_benchmark_runner.rs`:

- `sql_memory_limit_keeps_the_peak_recorder`: a SQL `SET` replaces the
harness pool, and the recorder is put back and records the next query.
- `no_memory_limit_gets_no_recorder`: without any limit, nothing
changes.
- `criterion_harness_writes_pool_peak_per_case`: an end-to-end Criterion
run of a suite whose `init` sets the limit writes a JSON with a non-zero
peak and empty `iterations`.

If the rewrap is disabled, the first and third tests fail.

`./bench.sh run spill_views`, the command that the bot runs
(`SQL_CARGO_COMMAND="cargo bench --bench sql -- --save-baseline ..."`),
writes `results/<name>/criterion/spill_views.json`. The peaks are the
same with `DATAFUSION_RUNTIME_MEMORY_LIMIT=512M` and with no env limit,
because the suite's own limits apply (40M for q01 to q03, 96M for q04
and q05):

| Query | `pool_peak_bytes` |
| --- | --- |
| `spill_views/q01_sort_string_1_distinct_repeated` | 42614784 (40.6
MiB) |
| `spill_views/q02_sort_string_1000_distinct_repeated` | 41709504 (39.8
MiB) |
| `spill_views/q03_sort_binary_1000_distinct_repeated` | 41709504 (39.8
MiB) |
| `spill_views/q04_sort_string_all_distinct_distinct` | 107544576 (102.6
MiB) |
| `spill_views/q05_group_by_string_all_distinct_distinct` | 100633968
(96.0 MiB) |

The q01 and q04 peaks are a little above their limits. The pool allows
this: infallible `grow` calls are not limited, and the recorder counts
what the pool grants.

`./bench.sh compare` with two such result sets reads no file.
`sort_tpch` (dfbench, queries 1 and 2,
`DATAFUSION_RUNTIME_MEMORY_LIMIT=512M`) is unchanged. It writes
`results/<name>/sort_tpch1.json` with 5 iterations and `pool_peak_bytes`
for each query, and it writes no `criterion/` directory.

## Are there any user-facing changes?

Only for benchmark tooling. There is a new optional `BENCH_RESULTS_FILE`
env var for `cargo bench --bench sql` (documented in
`benchmarks/sql_benchmarks/README.md`). `bench.sh run` now writes
`results/<name>/criterion/*.json` for SQL-harness suites. A SQL-set
memory limit now reports a pool peak. There are no public API changes
outside the benchmarks crate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant