#!/usr/bin/env python3 """Runs every dashboard query twice: straight against Prometheus, and through Grafana's /api/ds/query with the provisioned datasource (what a panel really does). Prints one line per panel query and exits non-zero if any query errors or returns no series. Usage: verify-dashboard.py [output-file]""" import base64, json, pathlib, sys, time, urllib.parse, urllib.request root = pathlib.Path(__file__).resolve().parent.parent dash = json.loads((root / "dashboards" / "spring-ai-observability.json").read_text()) auth = "Basic " + base64.b64encode(b"admin:admin").decode() def get(url, data=None, headers=None): req = urllib.request.Request(url, data=data, headers=headers or {}) with urllib.request.urlopen(req, timeout=30) as r: return json.loads(r.read()) lines, bad = [], 0 now = int(time.time()) lines.append(f"{'panel':<58} {'prom series':>11} {'grafana frames':>14}") for p in dash["panels"]: for t in p["targets"]: q = urllib.parse.urlencode({"query": t["expr"]}) prom = get("http://127.0.0.1:9090/api/v1/query?" + q) n_prom = len(prom["data"]["result"]) if prom["status"] == "success" else -1 body = json.dumps({"queries": [{"refId": t["refId"], "datasource": t["datasource"], "expr": t["expr"], "instant": False, "range": True, "interval": "", "intervalMs": 5000, "maxDataPoints": 200}], "from": str((now - 600) * 1000), "to": str(now * 1000)}).encode() gf = get("http://127.0.0.1:3000/api/ds/query", body, {"Content-Type": "application/json", "Authorization": auth}) res = gf["results"][t["refId"]] n_gf = -1 if "error" in res else len(res.get("frames", [])) name = f"{p['title'][:48]} [{t['refId']}]" lines.append(f"{name:<58} {n_prom:>11} {n_gf:>14}") if n_prom < 1 or n_gf < 1: bad += 1 out = "\n".join(lines) print(out) if len(sys.argv) > 1: pathlib.Path(sys.argv[1]).write_text("# Every dashboard query, run against Prometheus and through Grafana's query API after scripts/load.sh\n\n" + out + "\n") sys.exit(1 if bad else 0)