Repository navigation
Config-driven benchmark campaign runner for the full-history bench subcommands #896
Description
TL;DR
Add tooling that runs a full benchmark campaign from one config file with one command. The runner builds a pinned binary, prepares every dataset, runs the configured ingest and query loops across datasets, chunks, and repetitions, records provenance, bundles the results, and optionally publishes them. A companion script bootstraps the bench box; another safeguards the bundle to object storage.
Why
The bench subcommands run one leg at a time — one ingest or query pass against one source. A campaign runs many legs across several datasets and chunks, repeated for statistics, and the results only compare if every leg used the identical binary, dataset preparation, and flags. Doing that by hand is slow and error-prone. This tooling makes a campaign reproducible: a config file names the binary, the datasets, and the loops, and the runner does the rest the same way every time.
The runner — campaign.sh
One command runs the whole campaign:
BENCH=/mnt/nvme/bench ./scripts/bench-devbox/campaign.sh my-campaign.cfg [--dry-run]
Steps, in order:
- Validate. The config is a sourced bash fragment. The runner rejects any unknown key and validates every value (enums, integers, Go durations, chunk lists, dataset entries) before it builds or runs anything.
- Build the binary under test.
REFis built into a versioned binarystellar-rpc-<sha>, then the original checkout is restored.REFselects only the binary — nothing else. A dirty work tree is refused whenREFis set, so the built commit is exact. The build is skipped if that binary already exists. - Prepare each dataset to a local cold pack root. Four source kinds converge on the same on-disk shape (
ledgers/,events/,txhash/):packs-local— a local cold pack root, used in place.packs-gs— ags://prefix, fetched once into$BENCH/golden/<name>/and reused.bsb-s3— an S3 bucket path; an untimed cold backfill materializes the golden root.fixture— a generated fixture pack plus an untimed cold ingest materialize the golden root.
Golden datasets are fetched or built once and reused across runs. Removing$BENCH/golden/<name>forces a re-fetch.
- Run the configured loops. Per
(dataset, chunk),RUNSrepetitions, each a fresh process with its own--outdirectory:INGEST=cold|both→bench-ingest cold(scratch wiped before each run).INGEST=hot|both→bench-ingest hot, optionally paced atCLOSE_INTERVAL. The last run's hot DB is kept for the hot query suite.QUERY=yes→bench-query coldagainst each frozen pack root, andbench-query hotagainst the kept hot DB (hot query needs a hot ingest, so it runs only whenINGESTishotorboth).
- Record provenance and bundle. Results land in
$BENCH/results/<run_id>/, whererun_id = <NAME>-<sha>-<stamp>, alongside the config,binary.txt,machine-metadata.txt, andmetadata.json. The directory is tarred to durable storage so the bundle survives an instance stop. - Publish (optional). When
PUBLISH_URIis set,publish.shuploads the bundle. A publish failure is not a benchmark failure — the data is already safe locally, so it exits with the exact retry command instead of corrupting the "campaign done" signal.
--dry-run prints every command with resolved paths and flags, and builds, downloads, and runs nothing.
Config format
The config sets only documented keys:
| Key | Default | Meaning |
|---|---|---|
NAME |
— (required) | Campaign name; charset [A-Za-z0-9._-]; part of the run_id. |
REF |
current checkout | Git ref to benchmark. Built into a versioned binary; checkout restored after. |
INGEST |
— (required) | cold | hot | both | none. |
QUERY |
— (required) | yes | no. Query-hot needs INGEST hot or both. |
CLOSE_INTERVAL |
0 |
Hot pacing interval, e.g. 2s. 0 = unpaced catch-up. |
RUNS |
5 |
Repetitions per (dataset, chunk) cell. |
QC |
1,4,16 |
Query concurrency sweep (used when QUERY=yes). |
COLD_ITERS / HOT_ITERS |
100 / 200 |
Query iteration counts. |
WORKERS |
1 |
bench-ingest cold --workers. |
HOT_NUM_LEDGERS |
0 |
Cap hot ingest at N ledgers. 0 = whole range. |
PUBLISH_URI |
empty | gs:// or s3:// root to publish the bundle to. Empty = keep local. |
DATASETS |
— (required) | Array of name|kind|location|chunks entries; chunks is a space-separated chunk-ID list. |
Box bootstrap — bootstrap.sh
Idempotent setup for an EC2 box with a local NVMe instance store (m6id class, Ubuntu 24.04). It formats and mounts the instance store, runs an fsync honesty probe and warns if the disk absorbs fsync (hot-commit numbers would be fiction on such a disk — the reason this box class exists), installs the toolchain and native libs, builds the daemon, and race-tests the bench package. It is safe to re-run after a stop/start, which wipes the instance store; the runner re-fetches golden packs.
Publishing — publish.sh
Safeguards a finished bundle to <dest-root>/<run_id>/. It dispatches by scheme (gs:// via gcloud storage rsync, s3:// via aws s3 sync). Published runs are immutable: it refuses a destination that already holds objects unless --force is given. --dry-run prints the cloud commands without running them.
What this delivers
- One config file plus one command runs a full ingest and query campaign end to end.
- Strict config validation and a
--dry-runthat prints the exact command plan. - Ref-pinned, versioned binary so results are reproducible and attributable to a commit.
- Four dataset source kinds behind one interface, with fetch-once golden reuse.
- Fresh process and output directory per run; scratch and hot DBs reset between runs.
- Self-describing bundle: config, binary identity, machine metadata (with fsync probe), and a machine-readable
metadata.json(run id, config, datasets, hardware). - Idempotent box bootstrap and an immutable, scheme-agnostic publish step.
Related
- Full-history benchmark commands — ingest across all data types #771 / Full-history query benchmark commands — reads across all data types #856 — the
bench-ingestandbench-querysubcommands this runner orchestrates. - e2e benchmark campaigns workflow — dispatch, run on EC2, publish results #868 — scheduled campaigns; the CI/dispatch layer that drives this runner on ephemeral EC2.
- Run the Phase 1 benchmark campaign — ingest and reads at 2 s blocks #872 / Run the Phase 2 benchmark campaign — ingest and reads at 1 s blocks #873 / Run the Phase 3 benchmark campaign — ingest and reads at 600 ms blocks #874 — the phase campaign runs, executed through this runner.
- Benchmarking infra: stellar-rpc-bench S3 bucket + benchmarking devbox #866 — results bucket the bundles publish to.
- Benchmark results explorer — browsable reports for campaign runs #871 — results explorer; consumes the published bundles and their
metadata.json.
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsDone
Activity
marwen-abid commentedon Aug 7, 2026
Shipped, closing manually:
invocation.json+ campaignmetadata.json): bench(full-history): add run metadata to benchmarks #907 — merged tofeature/full-historyJul 31, so thecloseskeyword never fired.