Repository navigation
runner: show pool peaks for the Criterion SQL harness suites - #38
Merged
Merged
Conversation
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
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The "Memory Pool Peaks" section does not appear for the suites that
bench.shruns through the Criterion SQL harness (cargo bench --bench sql, e.g.spill_views,wide_schema,predicate_eval), even when the trigger setsDATAFUSION_RUNTIME_MEMORY_LIMIT. The comment also gives no reason. Example: apache/datafusion#23565 (comment) is aspill_viewsrun withDATAFUSION_RUNTIME_MEMORY_LIMIT: "512M"that has no pool-peak section and no note.The runner reads
pool_peak_bytesonly from the results JSON thatdfbenchwrites. The Criterion SQL harness wrote no results JSON. Also, suites that set their own limit withSET datafusion.runtime.memory_limit(likespill_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 takesbench.shfrommain. Until then, these suites get the note described below.Change
results/<side>/criterion/<file>.json.snapshot_json_files/collect_newnow 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 incritcmp.bench.sh compare/compare_detailread onlyresults/<side>/*.json, so there is no second timing table andresults_have_jsonis unchanged. A suite that sets its own limit in SQL shows peaks withoutDATAFUSION_RUNTIME_MEMORY_LIMIT.baseline: ref:set to an older release): that side's column isn/a, and a note names the side. The "reported nopool_peak_bytes" note is now shown only for a side that wrote results JSON without the field.dfbenchsuites: output unchanged.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_viewsdata on both sides gives the table and no note, with and without a requested limitcriterion_suite_with_results_json_on_one_side_notes_the_other: an older base gives a base column ofn/aand the one-side note, with no other notescriterion_bench_with_limit_gets_a_note: no results JSON on either side gives the note (updated text)data_present_renders_the_table_as_before:dfbenchoutput is unchangedcollect_new_reads_the_criterion_results_subdir: a file incriterion/is collected, with sourcespill_viewsThe 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 warningsandcargo test(215 passed) pass locally.🤖 Generated with Claude Code