aboutsummaryrefslogtreecommitdiffstats
path: root/dashboards/panels.py
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2026-09-02 11:22:36 -0500
committerTim Keller <tjk@tjkeller.xyz>2026-09-02 11:22:36 -0500
commita74c2877571afbfd04584b8821c754e9050b9e72 (patch)
tree2296d2d49285d2d2bd55e641f46bb3387c861108 /dashboards/panels.py
parentfe770e9f386e091dbca7cf587cfe0131f10bd728 (diff)
downloadtelemetry-flake-a74c2877571afbfd04584b8821c754e9050b9e72.tar.xz
telemetry-flake-a74c2877571afbfd04584b8821c754e9050b9e72.zip
fix drive rows surviving a drive replacement
The repeated per-drive sections are driven by $ata_device/$sas_device, which were label_values(smartctl:row{...}, row). label_values resolves against /api/v1/label/<l>/values over the *dashboard's* time range -- 24h by default -- so it returns every row line seen in that window, not the ones that exist now. Replace a drive and the window still holds the line the old drive produced. Both lines carry the same variable value, because the regex reduces them to the bare device name that the panels select on, and Grafana's metricNamesToVariableValues ends in uniqBy(options, 'value'), which keeps whichever series Prometheus returned first. The dashboard then renders one row headed with the removed drive's model and serial, no row for its replacement, and panels underneath showing the new drive's numbers. query_result() is the only variable form the Prometheus datasource evaluates as an instant query (metric_find_query.ts: queryResultQuery hits /api/v1/query at range.to), so switch both device variables to it and re-anchor the regex on the row= label in the line Grafana renders for a vector result. Verified against a real Prometheus loading rules/smartctl.yml, with a fixture that replaces a SAS drive and a SATA drive in place: the old form headed sde with OLDBAD99 and sdz with the 870 EVO it no longer contains, the new form lists exactly the drives in the machine. The host variable deliberately stays on label_values -- its window covering the time range is what keeps an unreachable machine selectable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKczN1f66kmLxNBLX37voQ
Diffstat (limited to 'dashboards/panels.py')
-rw-r--r--dashboards/panels.py13
1 files changed, 12 insertions, 1 deletions
diff --git a/dashboards/panels.py b/dashboards/panels.py
index faa60f7..919cb3c 100644
--- a/dashboards/panels.py
+++ b/dashboards/panels.py
@@ -237,6 +237,12 @@ def row(title, y, collapsed=False, children=None, repeat=None):
return r
+# Which editor tab Grafana opens the variable on. Only the UI reads this: at query time
+# PrometheusVariableSupport.query() takes the `query` string and hands it straight to
+# PrometheusMetricFindQuery, which re-parses it. Derive it so the two cannot disagree.
+QRY_TYPES = {"label_names(": 0, "label_values(": 1, "metrics(": 2, "query_result(": 3}
+
+
def query_var(name, query, label=None, multi=True, include_all=True, all_value=".*",
current_all=True, hide=0, regex=None):
"""A Prometheus-backed template variable.
@@ -244,13 +250,18 @@ def query_var(name, query, label=None, multi=True, include_all=True, all_value="
`regex` filters and rewrites the returned strings. Named capture groups `value` and
`text` split one string into what queries substitute and what the UI shows -- nest
them (`(?<text>(?<value>\\S+) .*)`) to show a superset of what you select on.
+
+ `label_values(...)` resolves against /api/v1/label/<l>/values over the *dashboard's
+ time range*, so it lists every value seen in that window, not the ones that exist
+ now. `query_result(...)` is an instant query at the range's end instead. Prefer it
+ whenever a stale option would be actively wrong rather than merely extra.
"""
v = {
"name": name,
"label": label or name,
"type": "query",
"datasource": DS,
- "query": {"qryType": 1, "query": query,
+ "query": {"qryType": QRY_TYPES.get(query.split("(")[0] + "(", 4), "query": query,
"refId": "PrometheusVariableQueryEditor-VariableQuery"},
"refresh": 1,
"includeAll": include_all,