brokkr
August 25, 2026 · View on GitHub
Command orchestrator and development utility for pbfhogg, elivagar, nidhogg, litehtml-rs, and sluggrs. Single Rust binary that provides benchmarking, verification, profiling, visual reference testing, and operational commands across all projects.
Built with LLMs. See LLM.md.
Install
cargo install --path ~/Programs/brokkr
How it works
Run brokkr from any project root. It reads ./brokkr.toml to detect which project you're in and resolves datasets, paths, and host-specific configuration. Commands are project-gated - running a pbfhogg command from elivagar's root produces a clear error. (check is the exception - it works in any Rust+git repo, with or without brokkr.toml.)
cd ~/Programs/pbfhogg
brokkr inspect --tags --dataset denmark # run once, print timing
brokkr inspect --tags --dataset denmark --bench # 3 runs, store in DB
brokkr inspect --tags --dataset denmark --hotpath # function-level timing
brokkr verify sort # cross-validate against osmium
cd ~/Programs/elivagar
brokkr tilegen --dataset denmark --bench # full pipeline benchmark
brokkr pmtiles-writer --hotpath # micro-benchmark hotpath
cd ~/Programs/nidhogg
brokkr serve # start the nidhogg server
brokkr api --dataset denmark --bench # API query benchmark
cd ~/Programs/litehtml-rs
brokkr visual --all # visual reference tests
Commands
Measurement modes
Every measurable command supports these flags:
| Flag | Behavior |
|---|---|
| (none) | Build, run once, print timing. No DB storage. |
--bench | Full benchmark: lockfile, 3 runs, best-of-N stored in DB (all N per-iteration walls stored too, in execution order) |
--bench N | Same but N runs |
--hotpath | Function-level timing via hotpath feature (1 run) |
--hotpath N | Same but N runs |
--alloc | Per-function allocation tracking (1 run) |
--stop <marker> | Kill the child when this FIFO marker is emitted (bench any phase in isolation) |
All measured modes automatically attach a sidecar that samples /proc metrics at 100ms (see Sidecar profiler below).
All commands also accept --dataset, --variant, --commit, --features, --force, --verbose, --wait.
pbfhogg commands additionally accept --direct-io and --io-uring to enable O_DIRECT and io_uring I/O paths. These add the required cargo features to the build, pass the flags to the binary, and show up in the cli_args/brokkr_args columns of the results DB (query via brokkr results --grep direct-io). --direct-io works with all commands. --io-uring is only supported by apply-changes, sort, cat --dedupe, diff --format osc, repack, and degrade - brokkr rejects it for other commands before building. io_uring preflight checks run automatically.
Shared (all projects)
| Command | Description |
|---|---|
check | Run gremlin scan + dependency rules + clippy + tests (extra args forwarded to cargo test) |
test [-p <PKG>] <NAME> | Run one cargo test in release mode via cargo test -p <PKG> <NAME> with --include-ignored --nocapture --test-threads=1; streams output, prints a [test] PASS/FAIL footer per run. Package resolution: explicit -p > [test] default_package in brokkr.toml > built-in default (pbfhogg-cli, nidhogg). -N repeats, -j passes through to cargo, --raw disables filtering. Gated off for litehtml/sluggrs (use brokkr visual). |
env | Show hostname, kernel, governor, memory, drives, tool versions, dataset status |
results | Query the results database (.brokkr/results.db) |
invalidate | Hard-delete results + sidecar rows by UUID or commit prefix (dry-run unless -f) |
clean [--worktrees] | Remove scratch/temp files; --worktrees also purges persistent benchmark worktrees |
pmtiles-stats | PMTiles v3 file statistics |
history | Browse global command history |
preview | Run full pipeline (enrich → tilegen → ingest → serve) and open map viewer |
lock | Show who holds the benchmark lock |
check filters cargo output into one line per diagnostic. Compilation noise is stripped; each error or warning becomes error[CODE] file:line:col message or warning[rule] file:line:col message. Passing tests are aggregated (e.g. cargo test: 137 passed (4 suites, 1.45s)), failures become FAILED name location message. Use --raw for unfiltered cargo output, or --json for NDJSON with full-fidelity structured diagnostics (one JSON object per line). Falls back to raw output automatically if parsing fails.
A gremlin scan runs before clippy and fails the check if any banned Unicode character is found in tracked .rs/.toml/.md/.js/.sh files. Covers invisible/zero-width characters, non-breaking spaces, soft hyphen, line/paragraph separators, bidi marks/overrides/isolates, em/en dashes, typographic single and double quotes, plus U+0003, U+000B, and U+FFFC. Text mode prints one line per hit (file:line:col U+XXXX NAME); JSON mode emits gremlin / gremlin_summary events. See src/gremlins.rs for the full banned set.
If brokkr.toml contains [[dependency_rule]] entries, check then validates direct Cargo dependency boundaries from cargo metadata --no-deps. Example:
[[dependency_rule]]
name = "app-db-boundary"
from = "app"
forbid = ["db", "service-state"]
This rejects direct app -> db or app -> service-state dependencies before clippy/tests run. from and forbid each accept either one string or an array of package names; forbid can name workspace crates or external crates.
When many diagnostics are found at once (e.g. picking the checker up on an existing codebase), text mode caps each phase at --limit N entries (default 20) and prefers files changed on the current branch so the most actionable hits surface first. The cap applies to both the gremlin phase and the clippy phase (independently), with a trailer summarising what's hidden (+N more in this branch, +M in unchanged files (--triage to see)). Use --triage to see everything, or --limit N to override the cap. --raw and --json bypass the cap.
check runs clippy and tests against the same list of "active sweeps" - one cargo invocation per sweep, both phases. The sweep list is built from brokkr.toml, in priority order:
- CLI
--features X/--no-default-features→ a single ad-hoc sweep with those exact flags. Useful for spot-checking one combo without editingbrokkr.toml. Skips[[check]]and any profile entirely; nobuild_packages. - CLI
--profile <name>or[test].default_profile→ the named profile's resolved sweep list, with the profile's libtest filters (only/skip/include_ignored/test_threads/env) applied to the test phase. Each sweep references a[[check]]entry by name. [[check]]is configured but no profile applies → every[[check]]entry runs in declaration order, no libtest filters.- None of the above → one
--all-featuressweep, identical tocheck's pre-[[check]]behaviour. Projects that haven't migrated keep working unchanged.
Each [[check]] entry declares name, optional features = [...] (explicit list - the features = "all" sentinel is rejected), optional no_default_features, and optional build_packages = [...]. build_packages rebuilds those cargo packages with the entry's feature flags before the test phase, so tests/cli_*.rs integration tests don't silently invoke a stale binary built for a different feature set. Diagnostics from clippy are deduped across sweeps and tagged in text mode ([<sweep-name>] or [both]) and in JSON mode (sweeps: [...] field on each diagnostic, sweep field on the per-sweep summary). Cost is ~Nx cold (cargo keeps separate target/ per feature set); incremental is small.
check also works without a brokkr.toml, so you can drop it into any Rust+git repo and get the same clippy + tests + gremlins pipeline (single --all-features sweep, no profiles).
test [-p <PKG>] <NAME> is the narrow counterpart to check for drilling into one specific test. It invokes cargo test -p <PKG> <NAME> (no --test), so both unit tests and integration tests inside the selected package are matched by the name substring. Package resolution runs in order: explicit -p/--package on the command line, then [test] default_package in brokkr.toml, then the project's built-in default (pbfhogg-cli for pbfhogg, nidhogg for nidhogg). Multi-crate workspaces (e.g. ratatoskr) have no built-in default, so they either pass -p on every invocation or set default_package in their brokkr.toml. It always builds release and always adds --include-ignored --nocapture --test-threads=1. Sweep selection mirrors check's ladder: if [test].default_profile is set, the test runs against every [[check]] entry the profile references; else if [[check]] is non-empty, every entry runs in declaration order; else fall back to a single --all-features sweep. Profile-level libtest filters (only / skip / tests) are intentionally dropped here - the user's <NAME> is the filter, and combining the two would silently produce zero matches when the test lives in a mod the profile would otherwise skip. Each sweep's build_packages are rebuilt with matching feature flags before the test phase. The test's own println!/eprintln! streams live; cargo compile progress, warning and error blocks, and the test harness framing (running N tests, test foo ... ok, test result:) are stripped. Each run ends with a single [test] line - PASS / FAIL / BUILD FAILED / SKIP - that includes wall time and, on failure, the panic message and location. SKIP means the name didn't match any test in that sweep (usually because the test is #[cfg(feature = "...")]-gated and the feature isn't enabled here); as long as at least one sweep saw a real match, brokkr exits 0. Only when every sweep skips does the whole run exit non-zero with a "check the package/name" hint. Because cargo test <name> is a substring filter, identically-named tests in different modules of the same package all run together - use a more qualified name (module path) to disambiguate. -N <n> repeats the test per sweep (for flaky-test hunting), -j <n> sets cargo -j N for parallel compile, and --raw bypasses all filtering. Litehtml and sluggrs projects are rejected here - use brokkr visual for fixture tests there.
pbfhogg
Every pbfhogg CLI command is a top-level brokkr subcommand: inspect, check-refs, sort, cat, add-locations-to-ways, build-geocode-index, apply-changes, merge-changes, extract, tags-filter, getid, diff, repack, degrade, etc.
Several commands are flag-driven umbrellas that previously had separate subcommand names:
brokkr inspect- no flag: metadata;--nodes: node stats;--tags: tag frequencies (narrow with--type node|way|relation)brokkr cat- bare: indexdata-generation passthrough (no re-decode);--type way|relation: filtered full-decode;--dedupe: two-input dedupe path (supports--io-uring);--clean: full-decode / re-frame Framed path. Flags are orthogonal and combinable. Barecatdefaults to--variant rawsince that's the natural bootstrap input.brokkr tags-filter---filter EXPR(defaultw/highway=primary),-Rfor single-pass (drop referenced),--input-kind oscto read an OSC diff instead of a PBFbrokkr getid- hardcoded ID set;--add-referencedfor two-pass,--invertto negatebrokkr extract---strategy simple|complete|smartpicks the extract algorithmbrokkr diff---format default|oscpicks the output shape
add-locations-to-ways accepts --index-type (dense, sparse, external; default: hash). Lands in cli_args verbatim.
repack re-encodes a PBF with a configurable elements-per-blob cap (--elements-per-blob N, default 8000). degrade produces an adversarial PBF: --unsort (clear Sort.Type_then_ID and force one adjacent same-kind blob pair to overlap per kind), --strip-locations (drop LocationsOnWays), --strip-indexdata (clear per-blob indexdata). Flags compose; pbfhogg requires at least one transformation flag on degrade. Both write to scratch by default and overwrite each --bench iteration. Both also accept --as-snapshot KEY to promote the final iteration's artifact into the dataset graph - repack always writes under pbf.indexed; degrade writes under pbf.raw when --strip-indexdata is set, otherwise pbf.indexed. KEY=base is reserved (CLI sentinel for the dataset's primary data); existing keys are rejected unless --replace-snapshot is passed. Subsequent runs address the promoted artifact via --snapshot KEY on any snapshot-aware command (apply-changes, merge-changes, tags-filter --input-kind osc, diff, repack, degrade, diff-snapshots). Closing measurement loops blocked on adversarial inputs:
brokkr repack --dataset planet --elements-per-blob 8000 --as-snapshot packed-8k
brokkr getparents --dataset planet --snapshot packed-8k --bench
brokkr degrade --dataset planet --unsort --as-snapshot unsorted
brokkr sort --dataset planet --snapshot unsorted --bench 1
Multi-variant benchmarks: read, write, merge, extract (with --strategy, --modes, --compressions flags - --compressions is plural because the single-value --compression collides with pbfhogg's passthrough flag on write / merge).
merge-changes accepts --osc-seq <N> for a single OSC file (back-compat) or --osc-range LO..HI to merge a contiguous range of configured OSC entries in one invocation. The range form lands in cli_args verbatim, so brokkr results --command merge-changes --grep 'osc-range' finds the range-form runs.
brokkr diff (with or without --format osc) derives its second input by running apply-changes on the dataset's PBF + OSC and caching the result at <scratch>/<pbf-stem>-osc<seq>-bench-merged.osm.pbf. The cache key includes the OSC seq so different --osc-seq invocations don't silently reuse each other's merged files. In any measured mode (--bench/--hotpath/--alloc) the cache is rebuilt before the run so total invocation wall time is reproducible; pass --keep-cache to opt back into reuse. Run mode (no measurement flag) always reuses the cache for dev-loop speed. Cache hit/miss + age land in the result row's metadata as meta.merged_cache and meta.merged_cache_age_s.
diff-snapshots benchmarks pbfhogg's diff against two independent point-in-time snapshots of the same dataset (e.g. planet-20260223 vs planet-20260411). Unlike diff (with or without --format osc) - which derives its B side from apply-changes and therefore preserves blob-level byte equality with the A side - diff-snapshots forces every blob through full decode on both sides. Different working set, different peak memory, different wall time. The dataset's primary (legacy top-level) PBF is referenced as base; additional snapshots registered via brokkr download <region> --as-snapshot <key> are referenced by their snapshot key. The --format flag selects between summary diff (default) and OSC-format output. The --from/--to/--format choices are recorded verbatim in cli_args - query osc-only runs via brokkr results --command diff-snapshots --grep 'format osc'.
brokkr diff-snapshots --dataset planet --from base --to 20260411 --bench 1
brokkr diff-snapshots --dataset planet --from 20260411 --to 20260418 --format osc
suite pbfhogg runs the full benchmark suite.
Verification (brokkr verify <subcommand>): cross-validates against osmium, osmosis, and osmconvert. Subcommands: sort, cat, extract, multi-extract, tags-filter, getid-removeid, add-locations-to-ways, check-refs, merge (apply-changes), derive-changes (diff → osc roundtrip), renumber, diff, and all (runs them all). Verify subcommands keep their own short names; they don't mirror the consolidated CLI umbrella shape.
verify renumber is a special case. Most verify commands require pbfhogg's output to be byte-identical (or element-identical) with osmium's. renumber does not: pbfhogg's orphan-reference handling in relation members is a documented intentional deviation (see pbfhogg's DEVIATIONS.md and notes/renumber-planet-scale.md section 5b), so a small non-zero diff is expected and does not indicate a regression. The goal of the command is to separate "expected delta" from "actual regression" without a human having to triage every diff.
brokkr verify renumber # default: denmark
brokkr verify renumber --dataset europe --verbose # print detail on mismatch
brokkr verify renumber --start-id 1,1,1 # forwarded to both tools
Per run it renumbers the input PBF with both tools, runs pbfhogg diff -s -c -v on the two outputs, and classifies the result:
- Parses the
Summary: left=N right=M same=X different=Yline to get element counts. - Scans the detail output for
*n<id>/*w<id>/*r<id>block headers and counts diff blocks per element type. - Runs
pbfhogg inspecton the osmium output to recover the total relation count for the threshold check.
PASS when element counts match, no node or way block headers appear in the diff, and the total diff count stays under 0.10 * total_relations (sanity threshold - calibrated from measured rates like Denmark's 306 orphan-ref diffs ÷ 46,103 relations ≈ 0.66%; the threshold catches regressions that would typically be orders of magnitude higher without flagging normal transboundary delta).
FAIL when any of those three checks fire: divergent element counts, any node/way diff, or relation diffs that blow past the sanity threshold. On failure the diff log at target/verify/renumber/verify-renumber-<dataset>-diff.txt is preserved alongside both renumbered PBFs for human review. On success all three scratch files are removed. The --verbose flag additionally prints the first 50 lines of the diff to the terminal when any mismatch (expected or not) is found. verify all includes renumber as part of the pre-release sweep.
Other: download <region> [--osc-seq N] fetches datasets from Geofabrik. Accepts short aliases (denmark, europe) or full Geofabrik paths (europe/france, asia/japan/kanto). Skips files that already exist (checked against brokkr.toml filenames). --osc-seq N downloads all missing OSC diffs from the last configured seq through N, hashes them, and appends entries to brokkr.toml. New downloads use dated filenames matching the project convention (e.g. europe-20260329-seq4716.osc.gz).
download <region> --as-snapshot <key> registers a new historical snapshot of an existing dataset under [host.datasets.<region>.snapshot.<key>] instead of touching the dataset's primary pbf/osc tables. Requires the dataset to already exist (run brokkr download <region> first). The snapshot key must match [a-zA-Z0-9_-]+; base is reserved as the CLI sentinel for the dataset's legacy/primary data. Files are written with snapshot-specific names (<region>-<key>.osm.pbf, etc.) and the indexed PBF is generated automatically.
download <region> --refresh rotates the dataset to a newer upstream snapshot. HEAD-checks upstream Last-Modified first; no-ops if not newer than the existing pbf.raw's mtime / download_date (use --force to rotate anyway). On rotation: archives the existing primary pbf/osc tables under a [snapshot.<key>] block (key derived from download_date or file mtime as YYYYMMDD), downloads the new PBF, generates the indexed PBF via pbfhogg cat, updates download_date to today, and resets the OSC chain. Errors with a clear message if the derived snapshot key collides with an existing snapshot block. After refresh, the archived state is reachable via brokkr diff-snapshots --from <key> --to base and brokkr apply-changes --dataset <region> --snapshot <key> --osc-seq <N>.
Every measurable pbfhogg command accepts --snapshot <key> to read its input from a historical snapshot rather than the dataset's primary tables. Producers (apply-changes, merge-changes, tags-filter --input-kind osc, diff including --format osc, repack, degrade) consume both PBF and OSC from the snapshot's tables; read-side consumers (sort, cat, inspect, add-locations-to-ways, getid, getparents, renumber, check-refs, check-ids, time-filter, tags-filter, extract, multi-extract, build-geocode-index) read their PBF input from the snapshot's pbf.<variant> table. --snapshot base (or omitting the flag) preserves the existing behavior - script-friendly when parameterizing over snapshot keys. The --snapshot flag lands verbatim in cli_args, so brokkr results --grep 'snapshot 20260411' (or just --grep 20260411) finds every command run against that snapshot.
Calling plain brokkr download <region> against a dataset whose pbf.raw is already configured is a SKIP (no auto-refresh), and prints a multi-line message naming both --refresh and --as-snapshot so the user knows the alternatives without having to read the source.
elivagar
| Command | Description |
|---|---|
tilegen | Full tile generation pipeline (with all pipeline flags) |
pmtiles-writer | PMTiles writer micro-benchmark (--tiles N) |
node-store | SortedNodeStore micro-benchmark (--nodes N) |
planetiler | Planetiler comparison |
tilemaker | Tilemaker comparison |
suite elivagar runs the full benchmark suite.
Other: compare-tiles, download-ocean, download-natural-earth.
nidhogg
Server: serve, stop, status.
Operations: ingest, update, query, geocode.
Benchmarks: api (query performance), nid-ingest (ingest), tiles (tile serving).
Verification: batch, nid-geocode, readonly.
litehtml-rs
Visual reference testing and fixture preprocessing. All commands are top-level (no brokkr litehtml namespace). Shared visual testing commands (visual, list, approve, report, visual-status) dispatch to litehtml or sluggrs based on the detected project. visual was formerly named test; that name is now owned by the generic cargo single-test runner in the Shared table above.
| Subcommand | Description |
|---|---|
visual | Run fixtures against Chrome reference artifacts (pixel diff + element comparison) |
list | Show fixtures, tags, and approval state |
approve | Record current divergence as accepted baseline |
visual-status | Dashboard of all fixtures vs approved baselines |
report | Show results for a past test run |
prepare | Normalize raw email HTML into self-contained fixture (images → gray PNGs, inject Ahem font, strip external resources, pretty-print) |
html-extract | Extract sub-fixture by CSS selector (--selector) or sibling range (--from/--to) |
outline | Structural overview with section markers, content previews, and suggested selectors |
Fixture workflow:
brokkr prepare raw-email.html fixtures/email-prepared.html
brokkr outline fixtures/email-prepared.html --selectors
brokkr html-extract fixtures/email-prepared.html \
--from "div:nth-of-type(2) > table > tbody > tr > td > div:nth-of-type(4) > div" \
--to "div:nth-of-type(2) > table > tbody > tr > td > div:nth-of-type(7) > div" \
fixtures/creatine_products.html
brokkr visual creatine_products
prepare and html-extract shell out to a Node.js script (requires Node + pnpm; auto-installs dependencies on first use). Node is already required for Puppeteer-based Chrome capture.
Preview pipeline
brokkr preview runs the full data pipeline and opens a map viewer for visual inspection:
brokkr preview # full pipeline, default dataset/variant
brokkr preview --from tilegen # skip enrich, start from tile generation
brokkr preview --from serve --no-open # just restart server, don't open browser
brokkr preview --dataset japan --variant raw
Steps: enrich (pbfhogg add-locations-to-ways) → tilegen (elivagar run) → ingest (nidhogg ingest) → serve (nidhogg serve + browser). Use --from to skip upstream steps when iterating on a single project.
Requires a [hostname.preview] section in brokkr.toml pointing to each project's source tree:
[plantasjen.preview]
pbfhogg = "/home/folk/Programs/pbfhogg"
elivagar = "/home/folk/Programs/elivagar"
nidhogg = "/home/folk/Programs/nidhogg"
Artifacts are written to .brokkr/preview/ (enriched PBF, PMTiles, ingest data dir). Works from any of the three project roots.
Benchmark harness
All benchmarks run through BenchHarness, which provides:
- Exclusive lock - prevents parallel bench/verify/hotpath runs via lockfile
- SQLite storage - results stored in
.brokkr/results.dbper project with git commit, hostname, and full environment snapshot - Multiple timing modes - in-process (N runs, best-of-N), subprocess (external binary), and distribution (min/p50/p95/max)
- Retroactive benchmarking -
--commit <hash>builds and benchmarks old commits via a persistent git worktree (sibling.brokkr-worktree-<project>-<short>/). Reused on subsequent runs at the same commit so cargotarget/survives. Runbrokkr clean --worktreesto garbage collect. - OOM protection - memory availability checks before large-scale runs
Sidecar profiler
Every measured run (bench, hotpath, alloc) automatically samples /proc/{pid}/stat, /proc/{pid}/io, and /proc/{pid}/status at 100ms intervals. Data is stored in .brokkr/sidecar.db (gitignored - local to the machine that ran it). The main results in .brokkr/results.db stay small and git-tracked.
The child process receives BROKKR_MARKER_FIFO env var pointing to a named pipe for application phase markers and counters. Markers are lines of the form <timestamp_us> <name>, counters are <timestamp_us> @<name>=<value>. Markers are point-in-time bookmarks - the protocol has no notion of spans or pairs. brokkr sidecar <uuid> --durations derives pair durations from a FOO_START / FOO_END convention if the emitter happens to follow it, but nothing else assumes that structure.
--stop <marker> kills the child process as soon as the named marker is emitted, allowing benchmarks of individual phases without waiting for the full run to complete. The SIGKILL exit is treated as success.
Sidecar data is stored even when the child is OOM-killed - the /proc trajectory up to the kill is the most valuable use case.
Querying sidecar data
A UUID prefix is required (except for --compare) - use brokkr results to find one. The dirty pseudo-UUID resolves to the most recent failed/dirty-tree run.
brokkr sidecar <uuid> # per-phase summary (default view)
brokkr sidecar <uuid> --human # same, as a fixed-width table
brokkr sidecar <uuid> --samples # raw /proc samples (JSONL)
brokkr sidecar <uuid> --samples --fields rss,anon --every 10 # project + downsample
brokkr sidecar <uuid> --samples --where "majflt>0" --tail 20 # filter + range
brokkr sidecar <uuid> --samples --phase STAGE2 # filter to a marker phase
brokkr sidecar <uuid> --samples --range 10.0..82.0 # time window filter
brokkr sidecar <uuid> --markers # raw marker events (JSONL)
brokkr sidecar <uuid> --durations # START/END pair timings
brokkr sidecar <uuid> --counters # application counters
brokkr sidecar <uuid> --stalls # *_wait_ns counter stall attribution (see conventions below)
brokkr sidecar <uuid> --stat anon # min/max/avg/p50/p95 for a field
brokkr sidecar <uuid> --stat anon --phase STAGE2 # per-phase stat
brokkr sidecar --compare <uuid_a> <uuid_b> # phase-aligned comparison
brokkr sidecar dirty --stat anon # inspect last failed/dirty run
Filter flags (--phase, --range, --where) compose with --samples and --stat; --phase also composes with --counters. --grep <SUBSTR> filters --counters by counter name, and composes with --phase.
Sidecar conventions
The FIFO protocol is convention-free - brokkr doesn't mandate any naming scheme on markers or counters. Two optional conventions unlock richer views when emitters adopt them:
-
FOO_START/FOO_ENDmarker pairs. Used by--durationsto derive per-span timing. Brokkr pairs aFOO_STARTwith the nextFOO_ENDof the same base name; unpaired starts render as standalone markers. In--human, span names seen more than once collapse into a single aggregate row (NAME xN total … min/avg/max) so a high-frequency span can't bury the phase rows; the JSONL form stays one object per span. The split is on observed cardinality, not on the name.--stop FOO_ENDkills the child on the end marker verbatim;--stop -FOOand--stop FOOare shorthand aliases that resolve toFOO_END(the fallback form prints a one-line notice so the resolved name is visible). Markers are for the small set of true phase boundaries only - a high-frequency span emitted as marker pairs drowns every phase-oriented view at once (each marker is treated as a boundary), so per-event blocking spans belong in counters, not markers (see below). -
<category>_wait_nsstall counters. Accumulated blocking time is a counter concept, not a marker one. Emit a strictly-monotonic counter named<category>_wait_ns- one atomic add of the blocked nanoseconds per blocking event (channel sends, mutex waits, I/O backpressure, one-shot joins).brokkr sidecar <uuid> --stallstakes the max value per name (monotonic, so max is the cumulative total), strips_wait_nsfor the category, and reports each as ms +% of wall. The%can exceed 100 for a counter accumulated across concurrent threads - it's the average number of threads parked in that category at any instant (e.g. 430% ~= 4.3 threads in decode-send); single-threaded waits read as clean sub-100% fractions. The view spans projects: it rolls up pbfhogg'spipeline_decoded_send_wait_ns,pipeline_raw_send_wait_ns, ... alongside elivagar'ssort_chunk_write_wait_ns,assemble_partition_batch_wait_ns,pmtiles_write_wait_ns, ... in one table. Runs with no*_wait_nscounters produce a clear "no *_wait_ns counters" message rather than silent empty output.
Both conventions are additive - projects can opt in incrementally. Existing emissions keep working with no change, and the only required mechanical migration is whichever blocking points you want to instrument. (Historical note: --stalls originally paired WAIT_<CATEGORY>_START/_END markers; it moved to *_wait_ns counters so the marker stream stays reserved for phase boundaries and the stall view unifies with pbfhogg's existing wait counters. Brokkr no longer treats any WAIT_-prefixed marker specially.)
See notes/sidecar.md for the scoped plan these conventions were introduced with; LLM.md has broader history.
Results database
Query stored benchmarks with brokkr results:
brokkr results # table of last 20 results
brokkr results 0b74fb6f # look up by UUID prefix
brokkr results --command read # last 20 matching 'read'
brokkr results --commit a65a # filter by commit
brokkr results --mode hotpath # filter by measurement mode
brokkr results --grep pipelined # substring-match cli_args + brokkr_args
brokkr results --grep apply-changes --grep uring --grep zstd:1 # stack --grep: AND across terms
brokkr results --grep apply-changes --grep-v uring # exclude: the arm WITHOUT io_uring
brokkr results --dataset europe # filter by dataset (substring on input file)
brokkr results --command tags-filter --dataset eu # combine filters
brokkr results --meta merged_cache=miss # filter by runtime-observation metadata key
brokkr results --command diff-snapshots --grep 'format osc' # osc-only diff-snapshots runs
brokkr results --meta merged_cache=miss --command diff # cold-cache diff runs only
brokkr results --compare a65a 911c # compare two commits side-by-side
Columns that drive the filters:
command- the bare subcommand id (read,cat,diff-snapshots,api, …); nobench/hotpathprefix.mode- the measurement mode only (bench/hotpath/alloc).cli_args- literal subprocess argv (pbfhogg/elivagar/nidhogg). Every flag and axis the user passed is here verbatim.brokkr_args- literalbrokkr <...>invocation the user typed. Same-column-dual-view, recorded so--grepcan find either.meta.*(via--meta KEY=VALUE) - runtime observations only: resolved paths, detected modes, cache hit/miss. Anything derivable fromcli_argsorbrokkr_argsis NOT duplicated here.meta.prefix is implicit; multiple--metaflags AND together; rows missing the requested key are silently excluded. Available keys depend on the command - e.g.diffemitsmeta.merged_cache+meta.merged_cache_age_s; elivagar'stilegenemitsmeta.locations_on_ways_detected; historicalmeta.format/meta.index_type/meta.start_stagekeys were migrated out in v13 (they live incli_argsnow).
Use --grep for anything that's a flag/axis (--grep zstd:1, --grep 'snapshot 20260411', --grep 'index-type external'). Use --meta for genuine runtime observations.
--grep-v is the negative form: repeatable, and a row is excluded if it matches any term (--grep ANDs, --grep-v ORs). It's the only way to select an A/B arm defined by an absent flag - --grep uring finds the ON arm, but the OFF arm has no distinguishing token to match, so it needs --grep-v uring. Both are literal substring matches (% and _ are not wildcards), and both apply to --compare as well as the row listing.
brokkr results <uuid> additionally shows what a table row can't: the per-iteration walls of a --bench N run in execution order, and the prev.* pairs naming what ran immediately before. --compare A B annotates a pair with a host: line when the two runs saw different available memory, governor, or kernel.
The dataset column in the output table is the first dash-separated component of the input filename - europe-20260301-seq4714-with-indexdata.osm.pbf renders as europe. This is a display heuristic: filtering via --dataset always substring-matches the full input_file column, so filters still work even when the short name collapses distinct datasets (e.g. a hypothetical europe-west would display as europe). The full filename and size are shown in the single-result detail view (brokkr results <uuid>) as the input field.
The compare view shows timing, output size, peak RSS, rewrite ratio, and blob distribution columns as applicable. Hotpath comparisons include function-level timing diffs.
Invalidating results
A benchmark run under the wrong pretences (wrong dataset, wrong features, wrong git state, interrupted sidecar, …) produces numbers that will skew every future comparison. brokkr invalidate hard-deletes such runs from both .brokkr/results.db (runs + run_distribution + run_iterations + run_kv + hotpath_functions + hotpath_threads) and .brokkr/sidecar.db (samples, markers, summary, counters, meta, and any sidecar_latest pointer rows like dirty that resolve to a deleted UUID).
Dry-run by default - the command prints each matched UUID and tags whether sidecar data is present, then exits. Pass -f / --force to actually delete.
brokkr invalidate 0b74fb6f # preview by UUID prefix
brokkr invalidate 0b74fb6f -f # perform
brokkr invalidate --commit a65a # preview every run on matching commits
brokkr invalidate --commit a65a -f # perform
brokkr invalidate dirty -f # nuke the last dirty/failed run's sidecar
Sidecar-only UUIDs (dirty-tree or failed runs with no results DB row) are picked up too, so a single invalidate call cleans both sides. Deletions in .brokkr/results.db should be committed like any other change to that file - git history preserves the audit trail.
Quick runtime timing
By default, every measurable command builds and runs once with timing output - no DB, no harness overhead:
brokkr inspect --tags --dataset denmark
# [run] /path/to/pbfhogg inspect tags denmark.osm.pbf --min-count 999999999
# ... command output ...
# [run] elapsed=1234ms
Add --bench to enable the full harness with DB storage:
brokkr inspect --tags --dataset denmark --bench # 3 runs, best-of-N stored
brokkr inspect --tags --dataset denmark --bench 10 # 10 runs
For ad-hoc passthrough with raw args: brokkr passthrough -- <args>.
Configuration
Each project has a brokkr.toml in its root:
project = "pbfhogg"
# Host-specific config (matched by hostname)
[plantasjen]
data = "data"
scratch = "data/scratch"
target = "target"
port = 3033
drives.source = "nvme"
drives.data = "ssd"
features = ["linux-direct-io", "linux-io-uring"]
[plantasjen.datasets.denmark]
origin = "Geofabrik"
download_date = "2026-02-20"
bbox = "8.0,54.5,13.0,58.0"
[plantasjen.datasets.denmark.pbf.indexed]
file = "denmark-with-indexdata.osm.pbf"
xxhash = "a1b2c3d4e5f6..."
seq = 4704
[plantasjen.datasets.denmark.pbf.raw]
file = "denmark-raw.osm.pbf"
seq = 4704
[plantasjen.datasets.denmark.osc.4705]
file = "denmark-4705.osc.gz"
xxhash = "f1e2d3c4b5a6..."
# Optional historical snapshots - additional point-in-time captures of the
# same dataset, registered via `brokkr download denmark --as-snapshot <key>`.
# The legacy top-level pbf/osc data above is implicitly snapshot `base`.
# `diff-snapshots --from base --to 20260411` diffs the two.
[plantasjen.datasets.denmark.snapshot.20260411]
download_date = "2026-04-11"
seq = 4969
[plantasjen.datasets.denmark.snapshot.20260411.pbf.raw]
file = "denmark-20260411.osm.pbf"
xxhash = "..."
[plantasjen.datasets.denmark.snapshot.20260411.pbf.indexed]
file = "denmark-20260411-with-indexdata.osm.pbf"
xxhash = "..."
# Cross-project source trees for preview pipeline
[plantasjen.preview]
pbfhogg = "/home/folk/Programs/pbfhogg"
elivagar = "/home/folk/Programs/elivagar"
nidhogg = "/home/folk/Programs/nidhogg"
project- which project this is (pbfhogg,elivagar,nidhogg, orlitehtml-rs)[hostname.datasets.*]- named datasets with PBF variants, OSC diffs, PMTiles entries, and bounding box[hostname.datasets.*.snapshot.<key>]- additional historical snapshots of the dataset (different point-in-time PBFs of the same region). Each snapshot has its ownpbfand optionalosctables. The legacy top-level data is implicitly snapshotbase(a reserved name). Snapshot keys must match[a-zA-Z0-9_-]+. Snapshots are first-class fordiff-snapshotsand addressable via--snapshot <key>on every measurable pbfhogg command (apply-changes,merge-changes,tags-filter,diffincluding--format osc,repack,degrade,sort,cat,inspect,add-locations-to-ways,getid,getparents,renumber,check-refs,check-ids,time-filter,extract,multi-extract,build-geocode-index). Refresh-mode downloads (brokkr download <region> --refresh) populate snapshot blocks automatically by archiving the previous primary state. Generative commandsbrokkr repack --as-snapshot KEYandbrokkr degrade --as-snapshot KEYregister newly-produced artifacts under this same shapexxhash- optional XXH128 hash for file integrity checks (sha256accepted as alias during migration). Runbrokkr envto see computed hashes for updating config[hostname]- per-host path overrides, port, drive configuration, and default cargo features; defaults todata/,data/scratch/, and cargo target dirfeatures- cargo features appended to every build (all measurable commands,verify,serve,ingest,update). Not applied tocheck. CLI--featuresare additive on top[[check]]- top-level array of tables (not host-scoped). Each entry is one (clippy + test) sweep with the entry's feature flags. Fields:name(required, unique label - referenced by name from[test.profiles.*].sweeps),features = [...](explicit list; thefeatures = "all"sentinel is rejected so adding a new feature toCargo.tomldoesn't silently broaden coverage),no_default_features = true|false,build_packages = ["pbfhogg-cli", ...](rebuilt with the entry's features before the test phase, keepingtests/cli_*.rsCliInvokercalls honest). Multiple entries catch lints that feature-gated proc-macro rewrites mask under any single feature set. The legacy[check]table form (withconsumer_features) is rejected at parse time[[dependency_rule]]- top-level array of direct Cargo dependency boundary rules enforced bybrokkr check. Fields: optionalname,from = "app"orfrom = ["app", "rtsk"], andforbid = "db"orforbid = ["db", "rusqlite"][test]- top-level (not host-scoped).default_package = "..."is the cargo packagebrokkr testuses when no-p/--packageis given; required for multi-crate workspaces that lack a built-in default. Explicit CLI-palways wins.default_profile = "..."is the validation profilebrokkr checkuses when no--profileis passed - typicallytier1for fast inner-loop runs.[test.profiles.<name>]declares a profile withsweeps = [...](list of[[check]]entry names), optionaltests/only/skip/include_ignored/test_threads/env, and optionalextends = "<other>"(single-parent inheritance, child collections replace parent's). Profiles use Rust module paths as the annotation surface: tests insidemod tier2 { ... }/mod platform { ... }/mod serial { ... }are filtered by name substring (only = ["tier2::"],skip = ["platform::"]). Dangling sweep references (profile names a[[check]]entry that doesn't exist) are caught at parse time. The legacy[test.sweeps.*]map form is rejected; sweeps live in[[check]]now
License
Apache-2.0