feat(grafana): fill the two empty rows, graph the metrics nobody was watching, and alert on the observability path - #184
Merged
Conversation
added 2 commits
September 3, 2026 05:55
…watching, and alert on the observability path The main dashboard advertised two sections it never filled — "Is storage and cold storage healthy?" and "Is anything failing?" were rows with no panels under them, so the two questions an operator asks during an incident had a heading and nothing else. The alerting directory shipped with no rules at all, so nothing paged. context-guru: 27 -> 45 panels. The two empty rows now hold what they promise: local database against cold storage, filesystem in use, cold storage reachability, sessions archived, dropped capture events; and compaction-model failures, cache-write churn, tokens wasted recovering offloaded content, buffered streams, tenants disabled, expand bounces over time, buffered against streamed responses, requests refused by reason, refusals per tenant, expand recovery failures by cause. Four value panels join the savings row — net saved this month, extraction net value, cache-frozen headroom, and the prefix-cache and total-avoided figures that previously sat orphaned below the last row. service SLO: 16 -> 19 panels, all three in "Correctness of the observability path itself", which is exactly where this week's outage would have shown up and did not. cg_metrics_age_seconds is the series that matters: /metrics had a cache TTL shorter than the scrape interval, so it could not answer a single scrape and the target sat down for hours while every cg_* panel read "No data" — a state indistinguishable, from the dashboard, from the service being dead. An exposition age that stops advancing says which one it is in one number. alerting: three rules, each for a failure that was previously silent — the service being down, more than 10% of requests refused, and the metrics exposition ageing past its refresh interval. Every panel's PromQL was run against this deployment's Prometheus before being committed: 83 of 86 Prometheus queries return series on live data, and the three that do not are the two new cg_metrics_* series plus their timeseries, which only exist once the proxy carrying them is deployed. The 17 Loki queries were checked against Loki, which is healthy and ingesting. All three alert expressions return series. Both dashboard uids are unchanged, so provisioning and every existing link still resolve. Colours are untouched: blue #3987e5 actual, orange #d95926 comparison, aqua #199e70 savings, validated CVD-safe on Grafana dark at all-pairs deltaE 9.4. No fourth colour was needed. Note on drift: the deployed copy of context-guru.json came from d9e2f24, which is not on main — 41 panels that only ever existed on the box. This change is a strict superset of it, verified panel by panel, so landing it brings that work into the repo rather than reverting it. context-guru-logs.json and context-guru-host.json are deliberately untouched. Signed-off-by: Osher Elhadad <Osher.Elhadad@ibm.com>
…ponse, not renderMetrics() TestDashboardsOnlyQueryMetricsWeExport failed on this branch (both build-test and purego, same single test) reporting cg_metrics_age_seconds and cg_metrics_render_seconds as unexported. They are exported — #183 ships them — but not by renderMetrics(), which is what the test inspected. Those two are appended PER RESPONSE in writeMetrics rather than built into the cached body, and that is deliberate: the body is shared by every scrape served from one render, so its age differs for each of them. Baking an age into the cached body would make it report the same figure to every scrape, which is the exact lie those two series exist to expose. So the test was asserting against the wrong artifact. Its own doc comment says the property under test is "every cg_* name any dashboard queries must appear in the rendered exposition" — and what a scraper actually receives is the response. It now issues a real request through metricsHandler from loopback (metricsAllowed gates on that absent a METRICS_TOKEN) and asserts on that body, which covers both body-level and response-level series. Strictly stronger, not looser: verified by pointing a panel at a nonexistent cg_totally_bogus_metric_xyz, which the test still catches and names. Signed-off-by: Osher Elhadad <Osher.Elhadad@ibm.com>
OsherElhadad
force-pushed
the
feat/grafana-dashboards-0902
branch
from
September 3, 2026 06:05
52dcc93 to
991f48c
Compare
This was referenced Sep 3, 2026
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 main dashboard had two sections an operator reaches for during an incident —
"Is storage and cold storage healthy?" and "Is anything failing?" — that
rendered as bare header bars. Both rows were
collapsed: truewith their panelsnested inside them (5 and 9 respectively), so the content existed but was
invisible until someone clicked. The alerting directory shipped with no rules, so
nothing paged.
This is the companion to #183, which fixes the outage itself. That outage is the
argument for this PR:
/metricshad a cache TTL shorter than the scrape interval,so it never served a scrape, the Prometheus target sat down for hours, and every
cg_*panel read "No data" — indistinguishable, from the dashboard, from theservice being dead. Nothing in Grafana said which.
What changed
context-guru: 41 → 45 panels (4 new), plus three layout bugs fixed.what the live deployment already had. Someone had expanded them in Grafana's UI and
it was never exported back to the repo.
gridPos.y=22and "Am I saving tokens and money?" aty=0, so the dashboardrendered saving above up/healthy, contradicting the file's own
__comment("Rows answer questions in order: is it up, is it saving..."). Fixed.
gridPosslotting them into row 7's band — file hygiene, reordered, no visual change.
aqua (
#199e70, reserved for values that cannot go negative) for its ≥0 step, on avalue that can go negative. Its simpler twin does red<0/blue≥0 correctly. Fixed.
cg_net_saved_usd— whose own HELP textcalls it "the number to alert on" and which was graphed nowhere), Extraction: net
value, Cache-frozen headroom, Expand recovery failures, by cause. Plus an
archived-bytes column on the per-tenant table.
context-guru service SLO: 16 → 19 panels (3 new), all in "Correctness of theobservability path itself" — exactly where this week's outage would have shown up and
did not.
cg_metrics_age_secondsis the one that matters: an exposition age that stopsadvancing distinguishes "slightly stale numbers" from "numbers stopped updating
entirely" in a single number. Those two series come from #183.
Alerting: three rules for failures that were previously silent — service down,
Validation
Every panel's PromQL was run against this deployment's Prometheus before being
committed, because an unvalidated panel is the bug this PR exists to fix:
the new
cg_metrics_*panels, which only exist once fix(dash): stop the dashboard retry storm that took the service down #183 is deployed — it now is,and they return real values (age sawtoothing under ~65s, render ~1.2–1.4s).
cg_net_saved_usd=9.2305;cg_extract_net_value_usd=-0.3784(extract_llm is currently underwater, which no panel could showbefore);
cg_frozen_tokens_total=457,478,707;cg_tenant_archived_bytespertenant e.g.
1,608,835.lines per 5-minute window).
edited files. Both dashboard
uids unchanged, so provisioning and existing linksstill resolve. Datasource uids match the provisioned
cg-prom/cg-loki.Colour
Untouched: blue
#3987e5actual, orange#d95926comparison/baseline, aqua#199e70savings — previously validated CVD-safe on Grafana dark at all-pairs ΔE 9.4. The one
colour change is the bug fix above, which brings a panel back into that scheme
rather than departing from it.
Drift worth knowing about
This PR changes the deployed dashboard's visible layout, not just its panel set:
row order changes and two rows stop being collapsed. Worth telling whoever expanded
those rows live, in case they have other un-exported UI edits.
The deployed copy of
context-guru.jsoncame fromd9e2f24, which is not onmain—41 panels that only ever existed on the box. This change is a verified strict superset,
so landing it brings that work into the repo rather than reverting it.
context-guru-host.jsonis byte-identical to live and untouched.context-guru-logs.jsonis untouched and the repo is already ahead of live there.Deliberately not added
No panel over a metric that does not exist, and no wall of panels for its own sake.
Left out: the extract_llm drill-down series (
cg_extract_calls_total,gate_declines_total,latency_ms) until the new net-value panel says someone needsthem;
cg_llm_tokens_total(token twin of an existing dollar panel);cg_saved_usd/cg_baseline_cost_usd(inputs to the net figure, already visible inaggregate).
The one thing an operator still cannot see in Grafana is the dashboard API's own 5xx
rate. During this outage nginx logged 15,700 503s while Grafana looked fine. I
grepped
proxy/anddash/for any HTTP status/route instrumentation — there is none;cg_dash_events_totalis the transcript-capture pipeline, and therefusalReasonssetcovers LLM-proxy refusals, not dash route handlers. Closing that needs a
cg_dash_http_requests_total{status}-style counter in middleware that does not existtoday. That is a code change and belongs with #183's family, not here.