bench

September 8, 2026 ยท View on GitHub

crabbox bench records and reports local benchmark timing observations. It is a local evidence workflow, not a provider leaderboard.

Benchmark rows are append-only JSONL records. They wrap the existing TimingReport payload with local comparison context such as command fingerprint, command display text, provider family/kind, repo fingerprint, and cold/warm state when known. Crabbox only writes this ledger when explicitly requested.

Default store:

<CrabboxStateDir()>/timings.jsonl

CrabboxStateDir() uses $XDG_STATE_HOME/crabbox when set; otherwise it uses the user config directory's crabbox/state directory.

Record from a run

Use run --timing-record to append the final timing report from a real run:

crabbox run --timing-record=default -- pnpm test
crabbox run --timing-record ./bench/timings.jsonl --provider aws -- pnpm test

Plain crabbox run remains non-recording by default.

Run and record a benchmark

bench run runs the same command for each selected provider and repeat, then records observations in the benchmark store:

crabbox bench run --providers aws,hetzner --repeats 3 -- pnpm test
crabbox bench run --provider aws --cold -- go test ./...

The command uses the same execution path as crabbox run, so provider setup, sync, command streaming, timing, and normal cleanup behavior stay centralized. If any provider or repeat fails, Crabbox continues the remaining attempts and exits non-zero after printing the number of recorded observations. Use --store <path> to write a specific JSONL store; the default is the local state store.

Record an existing timing JSON payload

bench record ingests one saved TimingReport JSON object from a file or stdin:

crabbox bench record --timing-json timing.json --command "pnpm test" --cold

--command or command args after -- set the command display text and command fingerprint used for grouping:

crabbox bench record --timing-json timing.json -- pnpm test

Report local observations

bench report reads the local store and groups observations by record source, provider, provider family/kind, machine type, command fingerprint, and cold/warm bucket. Records without a source use the unknown source bucket.

crabbox bench report
crabbox bench report --since 7d --providers aws,hetzner
crabbox bench report --command-fingerprint sha256:... --json

Human output includes successful sample count (n), median total duration, p95 total duration when enough samples exist, median sync and command duration, failure count, and an evidence marker. When records contain runner telemetry, the report also includes median and p95 runner totals plus deterministic runner and sync phase summaries. Duplicate phase names within one observation are summed before that observation contributes one sample. Runner phases with the same name remain separate when one is opaque and the other is not.

Only successful observations contribute duration and phase distributions. Failed observations contribute to failureCount only. P95 values require at least three samples for that specific metric or phase. Sync skip counts are counted once per successful observation. Legacy records without runner or phase fields omit those summaries.

bench report marks groups as insufficient_successful_samples until the group has at least --min-samples successful observations. The default is 2. runnerTotalN reports how many successful observations contained runner total telemetry.

JSON output uses the same grouped data:

{
  "schemaVersion": 1,
  "storePath": ".../timings.jsonl",
  "filters": {
    "since": "7d",
    "providers": ["aws"],
    "minSamples": 2
  },
  "groups": [
    {
      "source": "bench-run",
      "provider": "aws",
      "providerFamily": "aws",
      "providerKind": "ssh-lease",
      "machineType": "c7a.large",
      "commandFingerprint": "sha256:...",
      "n": 2,
      "runnerTotalN": 2,
      "medianTotalMs": 64000,
      "medianRunnerTotalMs": 67000,
      "medianSyncMs": 12000,
      "medianCommandMs": 45000,
      "runnerPhases": [
        {
          "name": "provider.acquire",
          "n": 2,
          "medianMs": 8500
        }
      ],
      "syncPhases": [
        {
          "name": "rsync",
          "n": 2,
          "medianMs": 9200
        }
      ],
      "failureCount": 0,
      "insufficientEvidence": false,
      "evidence": "sufficient_local_samples"
    }
  ]
}

Check runner timing policy

bench check applies one policy to every group selected by the existing store, provider, command fingerprint, and recency filters:

crabbox bench check --since 24h --providers aws,hetzner \
  --max-p95-runner-total 5s
crabbox bench check --command-fingerprint sha256:... \
  --min-samples 5 --max-failures 1 --max-p95-runner-total 8s --json

Every matched group must have at least --min-samples successful observations, no more than --max-failures, enough runner total observations to calculate a p95, and a p95 runner total at or below the required duration. Runner total evidence must contain at least three samples even when --min-samples is lower. The defaults are three successful samples and zero failures.

The command exits 0 only when every matched group passes. It exits 1 for no matches or any policy failure, including missing runner telemetry, and exits 2 for invalid flags, durations, or stores. With --json, schema version 1 output is written before a policy exit of 1. Check JSON is deterministic and excludes the store path, raw timing records, command text, command fingerprints, and lease or run IDs.

Treat bench check --json as local, private evidence. It can still identify the selected provider and machine type, so it is not a publication format. Public workflows must project the result through a separate exact-key allowlist instead of uploading or copying this JSON directly.

Privacy and interpretation

Timing records can include repo paths, workdirs, command display text, labels, artifact paths, and lease metadata because they preserve the existing TimingReport payload. Treat the store as local private state. Delete it when you no longer want the observations.

Reports say what happened locally for matching workloads. They do not claim that one provider is fastest globally, do not publish measurements, and do not infer cost unless a future record includes a defensible cost basis.

Flags

bench run:
--store default|path        JSONL store destination (default: default)
--provider <name>           run one provider
--providers a,b             run comma-separated providers
--repeats <n>               repeat count per provider (default: 1)
--cold                      mark observations as cold runs
--warm                      mark observations as warm/reused runs

bench record:
--store default|path        JSONL store destination (default: default)
--timing-json path|-        TimingReport JSON input, or stdin with -
--source <label>            record source label
--command <text>            command display text for grouping
--cold                      mark observation as a cold run
--warm                      mark observation as a warm/reused run
--repeat-index <n>          one-based repeat index when known

bench report:
--store default|path        JSONL store to read (default: default)
--provider <name>           include one provider
--providers a,b             include comma-separated providers
--command-fingerprint <id>  include one command fingerprint
--since <duration>          include records since 7d, 24h, etc.
--min-samples <n>           successful samples required for sufficient evidence
--json                      print machine-readable report JSON

bench check:
--store default|path        JSONL store to read (default: default)
--provider <name>           include one provider
--providers a,b             include comma-separated providers
--command-fingerprint <id>  include one command fingerprint
--since <duration>          include records since 7d, 24h, etc.
--min-samples <n>           successful samples required per group (default: 3)
--max-failures <n>          failed observations allowed per group (default: 0)
--max-p95-runner-total <d>  required positive p95 runner total limit
--json                      print deterministic machine-readable check JSON