Skip to content

docs(merge-queue): document the per-state duration metric - #339

Merged
pv72895 merged 3 commits into
mainfrom
docs/merge-queue-state-duration-metric
Sep 16, 2026
Merged

pv72895 merged 3 commits into
mainfrom
docs/merge-queue-state-duration-metric

Conversation

@pv72895

@pv72895 pv72895 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Documents mq_pr_state_duration_1h_seconds, which trunk-io/trunk#33921 adds to /v1/getMergeQueueMetrics.

What changed

The metric itself. A row in the rolling-window metrics table, plus a new Per-state durations section. mq_pr_wait_duration_1h_seconds and mq_pr_test_duration_1h_seconds each measure exactly one state; the new metric measures every state a PR passes through, with the state on a state label.

The section documents the label values (not_ready, pending, testing, tests_passed, pending_failure, and the three terminal states) and the two behaviors that would otherwise bite someone writing their first query against it:

  • Terminal states measure something different. merged/failed/cancelled are only ever left by a re-submit, so those series are time-until-resubmit — days, landing in +Inf. Aggregating across states without filtering them out wrecks the quantile, so the section says to filter and the example queries do.
  • An absent series is not a zero. A state series only exists once some PR actually exited that state in the window, which behaves differently in PromQL from the zero-filled histogram the older wait/test metrics emit. It also notes explicitly that those two metrics are unchanged and are not going away, since this endpoint is scraped by customer Prometheus configs.

Percentile gauges. _p50/_p75/_p90/_p95/_p99 companion gauges are documented for the first time. The endpoint has emitted them for every duration histogram since before this PR — the page only ever mentioned _bucket/_sum/_count. #33921 adds them for the new metric too, and describing it accurately meant describing them.

Examples. A per-state PromQL block (percentile gauge read directly, mean-per-state with terminal states excluded, a pending_failure alert, histogram_quantile over one state's buckets) and a sample exposition block. The sample # HELP lines are copied verbatim from the strings in #33921; the intermediate buckets are elided with a comment rather than silently dropped.

Notes

  • No changelog entry — #33921 is still open. Happy to add one when it ships.
  • openapi.json describes the endpoint as an opaque text/plain body and doesn't enumerate metric names, so nothing to update there.
  • The diff is 60 added lines and one touched line; trunk check wants to reformat the whole file (pre-existing bullet-style issues on main), which I deliberately left out to keep this reviewable.

🤖 Generated with Claude Code

trunk-io/trunk#33921 adds `mq_pr_state_duration_1h_seconds` to
/v1/getMergeQueueMetrics: a histogram plus `_p{50,75,90,95,99}` gauges,
labelled by `state`, covering every queue state rather than just the two
that `mq_pr_wait_duration_1h_seconds` and `mq_pr_test_duration_1h_seconds`
already cover.

Adds it to the rolling-window metric table, plus a Per-state durations
section covering the `state` label values, the two behaviors that surprise
people (terminal states measure time-until-resubmit, and a state series is
absent rather than zero when nothing left that state), example PromQL, and
sample exposition.

Also documents the `_p50`/`_p75`/`_p90`/`_p95`/`_p99` companion gauges,
which the endpoint has always emitted for every duration histogram but
which the page never mentioned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
trunk 🟢 Ready View Preview Sep 16, 2026, 4:13 PM

A reader-perspective review of the page found four defects in the
material added by the previous commit, plus one pre-existing error.

The alert example could not fire when it mattered. Every duration here is
recorded when a PR *leaves* a state, so during an active stall nothing
exits and the series thins out or vanishes — the `pending_failure` p95
alert would only have fired after recovery. Replaced it with a note that
this family is retrospective, and that stall detection belongs on the
point-in-time gauges.

The `histogram_quantile` example aggregated `by (le)`, which drops the
`state` label the panel is supposed to break out by; now `by (le, state)`.

The "where is the time going" example excluded only the terminal states,
which silently mixed pre-queue `not_ready` time into a breakdown of
in-queue time. Time in queue is exactly pending + testing + tests_passed +
pending_failure, so the examples and the Warning now select those four by
name. That also fixes a contradiction: an open label set means a deny-list
regex picks up whatever state is added next.

"Carrying the same labels as the histogram" implied `_p95{le=...}`; the
percentile gauges carry every label except `le`. Also documented that they
are computed over raw observations rather than buckets — which is the real
argument for them, since a bucket-derived quantile past the 7200s top
boundary is extrapolation — and that they cannot be re-aggregated across
repos.

Added which metric to prefer for new dashboards, since the page documented
two ways to get pending duration and recommended neither.

Pre-existing, corrected against the strings the endpoint emits: the sample
output gave `mq_testing_slots_active` the HELP text of `mq_prs_testing`
("PRs currently in TESTING state"), which are different numbers whenever
batching is on. Added the missing `mq_prs_testing` sample alongside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Validated every example on the page against a local Prometheus fed by a
real staging scrape (api.trunk-staging.io, 9 repos) plus a synthetic
mq_pr_state_duration_1h_seconds built to the schema in trunk-io/trunk#33921,
which is not deployed to staging yet. Three pre-existing examples returned
zero series — in Grafana they render as "No data", which is why nobody
noticed.

Failure rate: `ignoring(conclusion) sum(...)` cannot match. A bare `sum()`
strips every label, so the left side keeps repo/branch/queue_type and the
right side has none. Now sums `without (conclusion)` to keep a per-repo
denominator, and restricts it to the three exit conclusions — this metric
also carries non-terminal `conclusion` values, so the old denominator
counted PENDING and TESTING transitions as part of the failure-rate base.
Returns 2 series with plausible values where it returned nothing.

Restart ratio: `conclusion` exists only on the right-hand vector, so the
two never match. Added `ignoring(conclusion)`; returns 4 series.

P90 wait: `sum(...) by (le)` drops `repo`, silently blending every scraped
repo into one number — and the page recommends scraping a whole org. Now
`by (le, repo)`.

The five per-state examples added earlier all evaluate correctly, including
the group_left share-of-total (the four shares sum to 1.0) and the
`ignoring(state)` restart ratio. Two controls back the guidance: a p95 over
all states returns 7200 (pinned to the top bucket by the terminal states)
against 5400 for the in-queue states alone, and the `by (le)` form the
earlier commit removed does collapse all four states into a single line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Docs PR opened: #340

Added a changelog entry announcing the new Merge Queue per-state duration Prometheus metric and its companion percentile gauges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant