Name the alert's grid point and read boundaries off the base tables - #1017
Draft
epompeii wants to merge 6 commits into
Draft
Name the alert's grid point and read boundaries off the base tables#1017epompeii wants to merge 6 commits into
epompeii wants to merge 6 commits into
Conversation
epompeii
force-pushed
the
u/ep/parameters-api/alerts
branch
from
August 26, 2026 08:14
9bba446 to
5459312
Compare
Contributor
|
| Project | Bencher |
| Branch | u/ep/parameters-api/alerts |
| Testbed | intel-v1 |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result microseconds (碌s) (Result 螖%) | Upper Boundary microseconds (碌s) (Limit %) |
|---|---|---|---|
| Adapter::Json | 馃搱 view plot 馃毞 view threshold | 5.26 碌s(+10.48%)Baseline: 4.76 碌s | 5.56 碌s (94.55%) |
| Adapter::Magic (JSON) | 馃搱 view plot 馃毞 view threshold | 5.08 碌s(+9.94%)Baseline: 4.62 碌s | 5.34 碌s (95.06%) |
| Adapter::Magic (Rust) | 馃搱 view plot 馃毞 view threshold | 27.89 碌s(+6.94%)Baseline: 26.08 碌s | 28.42 碌s (98.16%) |
| Adapter::Rust | 馃搱 view plot 馃毞 view threshold | 4.66 碌s(+26.26%)Baseline: 3.69 碌s | 5.31 碌s (87.71%) |
| Adapter::RustBench | 馃搱 view plot 馃毞 view threshold | 4.66 碌s(+26.46%)Baseline: 3.68 碌s | 5.29 碌s (88.15%) |
epompeii
force-pushed
the
u/ep/parameters-api/alerts
branch
from
August 26, 2026 09:10
5459312 to
e79420c
Compare
epompeii
force-pushed
the
u/ep/parameters-api/alerts
branch
from
August 27, 2026 03:46
e79420c to
96aae30
Compare
The perf query answered per benchmark and returned one scalar per point. A benchmark now has grid points, and a measure now has named scalars, so it answers per grid point and returns all of them. `parameters` is a new query parameter: a comma separated list of URL encoded parameter sets. A grid point is queried when at least one of them is a subset of its set, which makes the filter an OR across its elements and an AND within one. Leaving it off queries every grid point. The filter resolves to parameter set row identifiers in Rust, over the benchmark's own sets, so what reaches SQL is an indexed lookup and never a JSON predicate. A line is now keyed per grid point. `JsonPerfMetrics` carries the parameter set it plots, between the benchmark and the measure, because that is the order the dimensions run in: branch, testbed, benchmark, parameters, measure, metric. Two grid points of one benchmark are two lines. Each point carries `metrics`, every named scalar the measure ingested, keyed by name. Each entry carries its value and the thresholds that gated it, with the boundary each produced and any alert it raised. That list is a list from birth, because a later layer lets several thresholds gate one named scalar. The deprecated fields keep saying what they always said. `metric` is still the triple, rebuilt from the `value` row and its `lower_value` and `upper_value` siblings, and the singular `threshold`, `boundary`, and `alert` are still the ones that gated the `value` row. A project that only ever reported the triple gets back every field it got back before, unchanged. `metric` is optional, following the report response. A measure that names no `value` still measured something, and it is stored, billed, and queryable, so it is still a point on the line: it carries its named scalars in `metrics` and leaves the triple absent rather than disappearing from the response that serves it. Nothing an older client can produce reaches that case, so the field is present for every one of them. The plot leaves such a point out of the line it draws, because a measurement with no point estimate has nothing to place on the axis. Reads move off the `metric_boundary` view onto the `metric` table. All of a grid point's named scalars for one measure sit together on the index over the report benchmark, the measure, and the name, so one bounded range read returns every one of them, where the view had to seek each conventional name separately and could only ever return those three. The boundary, its threshold, its model, and its alert are chained flat with explicit `ON` clauses: SQLite cannot flatten a compound right operand of an outer join, and the nested form makes it scan the whole boundary table once per request. The migration byte compatibility test drops its perf leg. It captures responses on both sides of the metric migration, which only works for a reader that goes through the view, and the report response already sat out for the same reason. What the perf response owes older clients is pinned where that response lives.
The image endpoint takes `parameters`, the same comma separated list of URL
encoded parameter sets the query endpoint takes, spelled the same way and read by
the same code path. The image query struct mirrors the query struct field for
field, doc comment included, so the image is the plot of the query it mirrors.
A line is a grid point, so the key names the grid point when the benchmark name no
longer says which line is which. A benchmark whose every line in the image plots
the empty parameter set keeps the bare benchmark name it has always had. One non
empty set among a benchmark's lines names them all, the empty set among them
included, which reads `{}`. The set is spelled in its canonical form and follows
the benchmark name on the same row, so the wrapping the key text already does for
a long benchmark name loses the tail of the set spelling first and the name never.
A project that never reported a parameter set is drawn from the inputs it was
always drawn from: every line of every such benchmark is labeled exactly as
before, which the fixtures pin, and nothing else on the draw path moved.
A point whose measure named no `value` has no point estimate to place on the axis,
so the plot leaves it out of the line. That already held; a fixture now pins it.
`GET /v0/projects/{project}/metrics/{metric}` resolved through the
`metric_boundary` view, which drives on `name = 'value'`, so the UUID of a
`lower_value`, an `upper_value`, or any other named scalar was a 404 even though
the report response hands those UUIDs out. The lookup moves onto the `metric`
table, so every metric row UUID resolves.
The response now describes the addressed row. It gains `name` and `value`, the
addressed row's own name and scalar, and `parameter`, the grid point it was
measured under. `metric` becomes optional and is present only when the addressed
row is the `value` row: reconstructing the triple around a bound or a named
scalar would assert numbers the address does not name. `threshold`, `boundary`,
and `alert` describe the boundary attached to the addressed row itself.
A `value` row address is unchanged but for those three additions, which a
fixture pins key by key.
A report payload can now say what format it is written in. `bmf_version` is a top level key of the payload, next to `results`, and it accepts exactly 0 and 1. An absent key is version 0, so every payload that ingests today ingests unchanged. Any other value is refused with a message that names the accepted versions. At version 1 the `json` adapter node tries its `json_v1` leaf first and falls back to `json_v0`; at version 0 it tries them in today's order. That reorders the attempts rather than filtering the payload, so version 1 refuses no v0 shape and version 0 still ingests a v1 shape through the second attempt. `magic` inherits the preference through the node, an explicitly named leaf is unaffected because it is already an exact statement, and no non-JSON adapter reads the key. An empty payload is now foldable at any version. It is the one payload both leaves claim, so at version 1 it parses as v1, and since fold is all or nothing across the array it would otherwise disable fold for every v0 iteration beside it, changing the number of metric rows a report writes. The fold refusal exists because a pooled statistic cannot be recomputed from per iteration values, and a payload that reported nothing has none, so it has nothing to refuse over. `BmfVersion` is a validated newtype in `bencher_valid` that replaces the enum of the same name in `bencher_adapter`. The payload's declared version and the version a leaf parsed are two values of one type, and they have to be able to differ: a payload that declares version 1 may still hold v0 results. `/v0/run` carries the key because `JsonNewRun` gains the field and the conversion into the report payload forwards it. A job based run does not: its results are the runner's own output rather than the submitted payload. The v1 thresholds shape and the project gate are later layers, and the CLI gains no flag here.
Each project now declares the highest BMF payload version it accepts. The field is `bmf_version` on the project, it defaults to 0 everywhere, and it is visible to everyone that can see the project. Only a server admin can move it: a PATCH that carries the field from anyone else is forbidden, and a PATCH that does not carry it is the patch it has always been. The gate is a plain setting, so an admin can lower it again just as easily. The gate is a maximum, not an exact match. A project at version 1 still ingests a payload that declares version 0 and a payload that declares nothing at all, so raising the gate refuses nothing that ingested before it moved. Ingest checks the gate twice, once for each way a payload states its version, and both refusals are the same class and name both versions, the payload's and the project's. The `bmf_version` key a payload declares is checked before anything is created for the report, because later layers hang payload shapes off the declared version and none of them should reach a project that does not accept them. The version the results actually parsed as is checked after parsing, because a payload can reach a v1 leaf without declaring anything: the `json_v1` adapter names the leaf outright, and the `magic` and `json` nodes fall back to it. Without the second check the gate would be decorative. That second check is a deliberate behavior change for those two undeclared paths on a project still at version 0. They are not a documented way to send v1 results, and a gate a payload can walk around is not a gate. Report ingest, `/v0/run`, and a job based run share one check rather than three copies of it. A job declares nothing, since its results are the runner's own output rather than a submitted payload, but the project gate still applies to what those results parse as.
An alert said which benchmark and which measure it fired on, and a benchmark now has grid points, so two grid points of one benchmark under one threshold raised two alerts that read identically. `JsonAlert` gains `parameter`, the grid point the alert fired on, between the benchmark and the metric, because that is the order the dimensions run in. Every surface that returns an alert carries it: the alerts list, the alert detail, and the alerts a report response embeds. The perf response's slim alert reference is unchanged; the line it hangs off already names its grid point. Both alert readers move off the `metric_boundary` view and onto the base tables. An alert names its boundary, the boundary names its metric row, and that row names the report it landed in and the grid point it was measured under, so each hop is an identifier seek off the hop before it. The metric triple comes from the `value` row the boundary was computed for plus its `lower_value` and `upper_value` siblings, the same assembly the metric endpoint does, now shared rather than written twice. The move is what a later layer needs. The view carries at most one boundary per metric row, and a metric row is about to be allowed several, so a reader that reached its boundary through the view would fan out one alert into one per boundary. No reader joins a boundary through the view any more. Every field an alert carried before carries exactly what it carried before, `metric` included: it is still required, and it is still the triple built around the row the boundary was computed for. The alert responses are pinned against that shape by fixture, key set and values both. The view itself stays, and the migration that holds its column list unchanged stays pinned to it. Its Rust model goes, because nothing reads it now, and the migration test reads the view directly rather than through an endpoint that no longer does.
epompeii
force-pushed
the
u/ep/parameters-api/alerts
branch
from
August 27, 2026 05:11
96aae30 to
46545ce
Compare
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.
The field
JsonAlertgainsparameter, the grid point the alert fired on. It sits betweenbenchmarkandmetric, because that is the order the dimensions run in: branch, testbed, benchmark, parameters, measure, metric.Two grid points of one benchmark under one threshold used to raise two alerts that read identically: same benchmark, same measure, same threshold, nothing to tell them apart. Now the alert names its grid point.
Every surface that returns an alert carries it:
GET /v0/projects/{project}/alertsGET /v0/projects/{project}/alerts/{alert}, and itsPATCHresponseThe perf response's
JsonPerfAlertis unchanged. It is a slim reference, and the line it hangs off already names its grid point.The reader moves
Both alert readers move off the
metric_boundaryview and onto the base tables. An alert names its boundary, the boundary names its metric row, and that row names the report it landed in and the grid point it was measured under, so each hop is an identifier seek off the hop before it.The metric triple is assembled from the
valuerow the boundary was computed for plus itslower_valueandupper_valuesiblings, which is the same assembly the metric endpoint does. That assembly is now shared rather than written twice.The move is what the next layer needs. The view carries at most one boundary per metric row, and a metric row is about to be allowed several, so a reader that reached its boundary through the view's left join would fan one alert out into one per boundary.
No reader joins a boundary through the view
One match, in
services/console/src/chunks/engineering/sqlite-performance-tuning/bonus-bug-rust-left-join.mdx: a code sample quoted in a blog post, not a query. There is no other reference anywhere in the tree.The view itself stays, and the migration that holds its column list unchanged stays pinned to it. Its Rust model is gone, because nothing reads it now.
Compatibility
Every field an alert carried before carries exactly what it carried before.
metricincluded: it is still required, and it is still the triple built around the row the boundary was computed for.The claim is stated as a fixture rather than asserted. A two grid point history under one threshold is pinned key set and values both, against what the previous shape gave that same fixture: the key set is the old one plus
parameter, and every old key holds its old value. Three more fixtures pin the triple for a metric row with both bounds, one bound, and no bounds. The report response's embedded alerts and the alerts endpoint's alerts are pinned equal for the same report.cargo gen-typesregenerates the OpenAPI spec and the TypeScript types.