Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Selection

Selection answers one question: which stored runs are eligible for this analysis?

It answers it entirely from storage keys and git topology. No excluded object is ever fetched or read — which is what keeps analysis fast against a store holding years of history, and why the stage exists as a stage at all.

Terms used here

TermWhat it means
merge baseThe newest commit a branch and its base still share.

The funnel

flowchart TD
    A["Every object in the store"] --> B["Discriminant filter<br/>engine · target triple · machine key"]
    B --> C["On the analyzed history?<br/>first-parent ancestry of the context"]
    C --> D["Dirty admission<br/>which side of the merge base?"]
    D --> E["--since cutoff<br/>by committer date"]
    E --> F["Sort by key, assign ordinals"]
    F --> G["Fetch and parse only these"]
one store, one query: what survived and what did not commit position 0 5 10 15 20 criterion / linux-x64 / a1b2 callgrind / linux-x64 / a1b2 criterion / macos-arm64 / 7c6d clean run dirty run excluded run no run

The funnel below traces one worked query — a host-local analyze — against this store. Two of its inputs drive the removals that would otherwise look arbitrary: the discriminant filters resolve to this host’s own partitions, so a foreign partition measured on another machine is dropped; and --since resolves to a cutoff date part way through the history, dropping everything committed before it. Each row is one selection stage and what it removed.

StageWhat it removes from this storeObjects removedStill eligible
Every run object in this store54
Discriminant filterevery object of criterion / macos-arm64 / 7c6d, a partition the query’s discriminant filters do not name2034
On the analyzed historyruns recorded at commits that are not on the context’s first-parent line331
Dirty admission1 dirty run, recorded at or before the merge base (commit 19)130
--sinceeverything still eligible that is older than commit 6822
Fetched and parsed22

32 runs removed and 22 runs account for all 54 run objects this store held. Only those survivors are fetched and parsed; every other run was decided on its storage key and the commit’s place in the topology alone.

The store in this worked example holds only runs — stored benchmark measurements (see Shape of the data). A blessing, a recorded acceptance of a change (see Reconstruction), is a separate object kind stored alongside them: it is set apart during discriminant selection and follows its own path, so it never enters the run-only topology, dirty-admission, and window stages counted here.

The grid draws the analyzed first-parent line, so the runs the on-history stage removed have no column in it — having no place on that line is exactly why they were removed. Partitions are labeled by engine, target triple, and machine key, with the triples shortened to fit.

Target, base, and the split

Two refs frame every analysis:

  • the context (--context, default HEAD) whose history is analyzed, and
  • the base (--base, default the detected default branch).

The tool walks the context’s first-parent ancestry and splits it at the merge base with the base. Everything at or before the merge base is base-side; everything after is target-side.

flowchart LR
    b0["·"] --> b1["·"] --> MB["merge base"]
    MB --> t1["·"] --> T["context = HEAD"]
    MB -. "base branch continues" .-> B["base tip"]
    subgraph baseside["base-side"]
        b0
        b1
        MB
    end
    subgraph targetside["target-side"]
        t1
        T
    end

Everything in the left box is base-side (at or before the merge base); everything in the right box is target-side (after it).

If the base cannot be resolved, or shares no ancestor with the context, that is a hard error — not a silent fallback. The usual cause is a shallow CI clone; fetch full history, or pass an explicit --base.

Mode is derived, not chosen

From the shape of the history it is handed, selection derives an analysis mode: the question the rest of the pipeline will answer about the series. There are two, and later stages — detection above all — branch on the one selection chose:

  • History mode — does a level move somewhere in an official branch’s own history?
  • Branch mode — is the analyzed context commit off the base it would merge into?

There is no flag to force the choice. The tool reads two signals:

Context commit equals the merge baseDirty run admitted on the context commitMode
yesnohistory
yesyesbranch
nonobranch
noyesbranch

The second signal is the surprising one. A run recorded from a dirty working tree on top of the base tip selects branch mode, because that is what it is: work that is not in the base’s history yet, sitting on top of it. An unnamed feature branch.

Note it is the admitted run that decides this, not the state of your checkout. A dirty checkout with no dirty run in the analysis still analyzes as history.

Dirty admission

A dirty run is one measured with uncommitted changes. Where it is allowed depends on which side of the merge base it sits:

SideClean runsDirty runs
Base-side (at or before the merge base)admittedrejected
Target-side (after the merge base)admittedadmitted
The base tip itselfadmittedadmitted, but only while the tree is currently dirty
a dirty run at the analyzed context commit is admitted while the tree is dirty; earlier base-side dirty runs are not every stored run ns commit position 98.0 100.0 102.0 104.0 106.0 108.0 110.0 112.0 114.0 116.0 118.0 0 5 10 15 20 analyzed context commit admitted for this analysis ns commit position 100.0 105.0 110.0 115.0 120.0 0 5 10 15 20 analyzed context commit dirty, base-side → excluded

Why base-side dirty runs are rejected: the base is the reference the analysis compares against. A measurement of somebody’s uncommitted experiment is not a description of the base’s history, and letting it in would corrupt the baseline for everyone.

--no-dirty removes dirty runs from the target side as well, so no dirty run is admitted anywhere — the base side already rejects them regardless.

The window

--since drops whole runs older than a cutoff, judged by each commit’s committer date.

The default is where the two modes diverge, and it is worth knowing which you are in:

  • History mode applies a six-month look-back by default, so a scheduled trend watch does not silently widen as history accumulates.
  • Branch mode applies no default. A branch is judged against its base’s recent commits, and a time window could only starve that comparison of evidence.

The cutoff is deliberately one-sided. There is no --until, because --context already anchors the newest edge of the timeline — a topology-first tool moves the tip by naming a commit, not by naming a date.

A --since shorter than your collection cadence will quietly turn a healthy store into a report full of unjudged series. The report’s coverage line is where that shows up.

Discriminant filters

Three discriminant filters restrict which discriminant sets participate: --engine, --target-triple, and --machine-key. Each is repeatable and unioned.

Omitting one auto-detects from the host — except --engine, which defaults to all of them, since a machine can produce output from several engines at once. The literal all disables a filter entirely.

Because auto-detection means the same command can search different data on different machines, every query prints a one-line effective selection summary to stderr, naming what was actually searched and which parts were defaulted. It is not opt-in.

Ordering is fixed before any parallelism

Runs that survive the preceding selection stages are sorted by storage key and assigned their positions before any object is fetched. Loading is then parallel, but the result is not affected by which worker finished first.

Two analyses of the same store therefore produce byte-identical reports. That is what makes a report diffable and a regression in the tool itself detectable.

What the other commands share

analyze, list runs, and examine run this same selection pipeline, so list genuinely previews the selected raw data. The columns below name concepts that later chapters introduce — benchmark prefixes, the ghost filter, and configured exclusions and blessings — so a linear reader is not expected to know them yet; they are forward references. The divergences are real, though, and worth knowing:

Discriminant filters, window, dirty rulesBenchmark prefixesGhost filterConfigured ignoresBlessings
analyzeyesyesyesyesyes
examineyesexact IDnonono
list runsyesnon/anon/a
pruneits own rulesn/an/anon/a

examine deliberately skips ghosts, configured ignores and blessings: it is a drill-down onto raw recorded data, and hiding points there would defeat its purpose. prune is a maintenance command with its own selection rules — notably no default --since — because deleting data by accident is much worse than analyzing too little.

Ignore matching needs benchmark IDs from the payload, so it happens after loading, not in this key-only selection stage. It leaves the selected run count unchanged.

A base merged into the branch is supported

Branch mode builds its comparison window from the base ref’s own recent first-parent history, anchored at the base ref. Whether the branch was rebased onto the base, the base was merged into the branch, or both happened during development does not change that comparison window.

The context commit’s first-parent line is still the line that list, examine, and prune display or maintain, and it is still divided at its fork point — the newest commit it shares with the base ref — into base-side history and the branch’s own commits. When the base was merged in, the merge base sits off that line, so the fork point is the newest shared commit rather than the merge base itself; base-side history is identified and preserved either way, so prune never deletes a shared commit without the base-branch opt-in.

The merge base is consulted only as a shared-history check. If no merge base can be resolved at all, the repository checkout is too shallow or the refs are disjoint, and selection stops with the existing merge-base-unavailable error.

What selection hands on

An ordered set of eligible objects, and the topology needed to place them. Next: Reconstruction.