Chart the Paperclip runtime run-queue and retry-age gauges (BLO-23450) - #1606
Chart the Paperclip runtime run-queue and retry-age gauges (BLO-23450)#1606allyblockcast[bot] wants to merge 1 commit into
Conversation
Adds a dashboard-as-code Grafana dashboard plotting paperclip_queued_run_oldest_age_seconds and paperclip_overdue_scheduled_retry_oldest_age_seconds side by side, which is BLO-22094's outstanding acceptance criterion: the park window and the queue window have to read together or neither number means anything on its own. BLO-29904 was filed on the premise that no dashboard-as-code path existed and that only a human with Grafana UI access could add a panel. That premise was already false when it was written. The path shipped on 2026-08-08 with BLO-20171 and has been carrying the review-request funnel dashboard in production since. The original search ran through GitHub code search, which returns zero hits on these private repos even for strings that demonstrably exist -- the git tree API disproves it immediately. No new mechanism is introduced here; this only uses the one already in place. Deliberate choices: - Separate template file rather than a second block in grafana-dashboard.yaml or a glob over dashboards/. A glob derives ConfigMap names from filenames, which would rename the live review-request-funnel ConfigMap -- deleting and recreating a resource currently serving a dashboard, to save twelve lines. - max by (agent_id), never sum(). Both gauges are emitted by every control-plane replica with identical values (measured 2026-09-02: 3 pods across service=paperclip and service=paperclip-workers, 46 agent series each). sum() would report three times the true age and still look plausible. - A refresher-health panel. If the refresh loop dies the age gauges serve their last value forever, and a frozen gauge is pixel-identical to a healthy one on every other panel. The runbook records the mechanism, the traps that produce a silently-empty dashboard, the manual paperclip-api deploy gate (these do NOT sync through Argo), and the fact that the ~13 dashboard JSONs in Blockcast/onprem-k8s are not deployed by anything. Verified: helm renders valid JSON with grafana_dashboard="1"; 9 new tests and the 11 existing dashboard tests pass. Both gauges confirmed resolving against the cluster Prometheus. Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 similar comment
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: a7427e2
Strong PR. I re-verified the load-bearing claims against the live cluster Prometheus rather than taking the description's word for them, and they hold:
| claim | independent check |
|---|---|
| all three charted metrics exist | paperclip_queued_run_oldest_age_seconds 138 series, paperclip_overdue_scheduled_retry_oldest_age_seconds 138, ..._refresh_success 3 |
agent_id label is real |
46 distinct agent_id values on both age gauges |
replica multiplication is real, so max by not sum is correct |
92 series on service=paperclip + 46 on service=paperclip-workers; refresh gauge resolves on exactly 3 pods (paperclip-0, paperclip-api-…-h2xml, paperclip-api-…-thtlp) |
| the stat-panel PromQL actually evaluates | count(max by (agent_id) (…) > 0) or vector(0) → 0 |
every grafanaDashboard.* values key referenced by the template exists |
enabled/sidecarLabel/sidecarLabelValue/datasourceUid/folder/extraLabels all present in values.yaml:480-492 |
| the new test file is actually executed by CI | .github/workflows/pr.yml:292 globs deploy/helm/paperclip/tests/*.test.mjs |
no uid collision today |
existing dashboard is paperclip-review-request-funnel, new one paperclip-runtime-queues |
Critical Issues (0)
None.
Important Issues (1)
-
[gstack/review + native-codex]
deploy/helm/paperclip/templates/grafana-dashboard-runtime.yaml:25(and the same instruction atrunbooks/grafana-dashboard-as-code.md:57) — the "add another dashboard" recipe tells the reader to change exactly two things (the ConfigMap name suffix and the.Files.Getpath) and does not mention the dashboard's internaluid. Grafana's file provisioner keys dashboards byuid, not by filename or ConfigMap name, so a reader who follows this literally — copyingruntime-run-queue-health.jsonas a starting point, which is the natural move given "Drop the JSON indashboards/" — ships two dashboards claimingpaperclip-runtime-queues. One silently overwrites or fails to provision the other; the ConfigMaps both apply cleanly and bothkubectl get cmchecks in the runbook pass.This is precisely the "deploys cleanly, renders wrong, fails nowhere" class the PR itself enumerates, and it is the one member of that class missing from the Traps list at
runbooks/grafana-dashboard-as-code.md:62. It is also currently unguarded: no test indeploy/helm/paperclip/tests/asserts anything about dashboarduids — the existinguidassertions ingrafana-dashboard.test.mjs:113and:338are about the datasource uid, which is a different field.- Add
uidto both copy-this-file instructions ("change three things"), add a bullet to the Traps section, and pin it with a test in the style already used here — render everydashboards/*.jsonthrough the chart and assert the set of.uidvalues has no duplicates. That test costs one render and makes the third dashboard safe rather than the second one lucky.
- Add
Suggestions (2)
-
[native-codex]
deploy/helm/paperclip/dashboards/runtime-run-queue-health.json:105— the refresher-health panel ismin(paperclip_overdue_scheduled_retry_age_metrics_refresh_success)with noor vector(0). Its sibling stat panel at:36defends exactly this case. If the series disappears entirely — refresh loop removed, metric renamed, all three replicas down —min()returns empty and the panel reads "No data" rather than STALE, even though total absence is strictly worse than the0case the panel was built to catch. It does fall back to the base threshold colour (red), so it fails in the alarming direction rather than the reassuring one, which is why this is a suggestion and not a blocker.min(...) or vector(0)makes the worst case read as the worst case. -
[pr-review-toolkit: tests]
deploy/helm/paperclip/dashboards/runtime-run-queue-health.json:70—max(max by (agent_id) (paperclip_queued_run_oldest_age_seconds))is exactly equivalent tomax(paperclip_queued_run_oldest_age_seconds); the outer baremax()already collapses every label includingagent_id, so the inner grouping is a no-op on this panel. Harmless as PromQL, but worth knowing that the "never summed across replicas" test atgrafana-dashboard-runtime.test.mjs:446is, for this one panel, matching a string that has no effect — the panel is correct for a different reason (the outermax) than the one the test asserts. The two timeseries panels are the cases where that test is doing real work.
Strengths
- The reasoning is in the tree, not just the PR body. Every test carries a comment naming the failure mode it prevents, and the template header explains the one-file-per-dashboard choice — including that folding into a glob would rename and therefore delete/recreate the live
paperclip-grafana-dashboard-review-request-funnelConfigMap. That is the kind of decision that is invisible and expensive to rediscover. - The refresher-health panel exists at all. "A stale gauge is pixel-identical to a healthy one" is the correct instinct, and most dashboards never get it.
- The CRD-vs-ConfigMap test (
:371) pins a non-obvious RBAC constraint — that a CRD-backed kind 403s the entirehelm upgrade, not just its own resource — which is the same trap keepingtemplates/prometheusrule.yamldisabled. - The template is a faithful structural copy of
grafana-dashboard.yaml, so it inherits the reviewed label/annotation/.Files.Gethandling rather than re-deriving it.editable: falseon the new dashboard is the right call for an as-code artifact. - The premise correction is worth more than the panel. "A code-search zero is not evidence of absence on a private repo" generalises well past this ticket, and the "merged is not live" note at the bottom of the runbook pre-empts the most likely follow-up confusion.
Recommended Action
- No Critical issues — nothing blocking merge on correctness grounds.
- Address the
uidguidance gap this cycle (docs bullet in two places + a duplicate-uidrender test). It is cheap now and is a silent production failure the second time someone uses this path. - Consider the two suggestions opportunistically.
Thinking Path
Linked Issues or Issue Description
What Changed
dashboards/runtime-run-queue-health.json— new dashboard Paperclip runtime — run queue & retry health (uid: paperclip-runtime-queues), 5 panels: the two age gauges side by side, plus overdue-agent count, worst queued age, and a refresher-health tile.templates/grafana-dashboard-runtime.yaml— renders it as agrafana_dashboard="1"ConfigMap. A separate file fromgrafana-dashboard.yaml, deliberately (see below).tests/grafana-dashboard-runtime.test.mjs— 9 tests.runbooks/grafana-dashboard-as-code.md+ index entry — the add-a-panel-by-PR flow, the traps, and the deploy gate.Three choices worth a reviewer's attention:
dashboards/. A glob derives ConfigMap names from filenames, which would rename the livepaperclip-grafana-dashboard-review-request-funnelConfigMap — deleting and recreating a resource currently serving a dashboard, to save twelve lines. It also keeps--show-only templates/grafana-dashboard.yamlin the existing tests reading exactly the document they were written against.max by (agent_id), neversum(). Both gauges are emitted by every control-plane replica with identical values (measured 2026-09-02: 3 pods acrossservice=paperclip×2 andservice=paperclip-workers×1, 46 agent series each).sum()would report 3× the true age and still look plausible. A test pins this.min(paperclip_overdue_scheduled_retry_age_metrics_refresh_success). If the refresh loop dies the gauges serve their last value forever, and a frozen gauge is pixel-identical to a healthy one on every other panel.Verification
grafana_dashboard: "1"present,__PAPERCLIP_DS_UID__substituted tocluster.clusterPrometheus:paperclip_queued_run_oldest_age_seconds139 series, max 922.137s;paperclip_overdue_scheduled_retry_oldest_age_seconds138 series;..._refresh_success= 1 on all 3 replicas.0rather than an absent series — not "no data".Risks
Low. Additive only — one new ConfigMap; no existing resource is renamed, modified, or deleted.
uid(paperclip-runtime-queues) and ConfigMap name are new — no collision with anything live.paperclip-apideploy. Merged is not live. The runbook documents the check.Model Used
Claude Opus 5 (
claude-opus-5), 1M context, extended thinking, with tool use — Paperclip MCP, read-only Kubernetes, Prometheus, and GitHub. Cluster facts in this PR were measured directly rather than recalled.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template