|
|
@@ -0,0 +1,126 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""
|
|
|
+M9 smoke test — observability layer
|
|
|
+Verifies:
|
|
|
+ 1. Prometheus is scraping all services (including new M9 targets)
|
|
|
+ 2. Key metrics are accessible in Prometheus
|
|
|
+ 3. Grafana is reachable with provisioning
|
|
|
+"""
|
|
|
+import urllib.request
|
|
|
+import urllib.parse
|
|
|
+import json
|
|
|
+import sys
|
|
|
+
|
|
|
+PROM = "http://localhost:9090"
|
|
|
+GRAFANA = "http://localhost:3001"
|
|
|
+
|
|
|
+def curl_json(url):
|
|
|
+ try:
|
|
|
+ with urllib.request.urlopen(url, timeout=10) as r:
|
|
|
+ return json.loads(r.read())
|
|
|
+ except Exception as e:
|
|
|
+ print(f" ⚠️ fetch error: {e}", file=sys.stderr)
|
|
|
+ return None
|
|
|
+
|
|
|
+def prom_query(expr):
|
|
|
+ url = f"{PROM}/api/v1/query?query={urllib.parse.quote(expr, safe='')}"
|
|
|
+ d = curl_json(url)
|
|
|
+ if d is None:
|
|
|
+ return None
|
|
|
+ return d.get("data", {}).get("result", [])
|
|
|
+
|
|
|
+def pass_(msg):
|
|
|
+ print(f" ✅ {msg}")
|
|
|
+
|
|
|
+def warn_(msg):
|
|
|
+ print(f" ⚠️ {msg}")
|
|
|
+
|
|
|
+def fail_(msg):
|
|
|
+ print(f" ❌ {msg}")
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+# ── Step 1 ────────────────────────────────────────────────────────────────
|
|
|
+print("Step 1 — Prometheus is up and scraping all services")
|
|
|
+for svc in ["ingestd", "routerd", "deliverd-fcm", "deliverd-telegram", "admind", "archiverd", "prometheus"]:
|
|
|
+ r = prom_query(f'up{{job="{svc}"}}')
|
|
|
+ if r:
|
|
|
+ pass_(f"Prometheus scraping {svc}")
|
|
|
+ else:
|
|
|
+ fail_(f"Prometheus NOT scraping {svc} (check prometheus.yml)")
|
|
|
+
|
|
|
+# ── Step 2 ────────────────────────────────────────────────────────────────
|
|
|
+print("\nStep 2 — ingestd metrics are present (source of ingest-tier metrics)")
|
|
|
+series = prom_query("ba_ingestd_alerts_received_total")
|
|
|
+cnt = len(series) if series else 0
|
|
|
+if cnt > 0:
|
|
|
+ pass_(f"ba_ingestd_alerts_received_total: {cnt} series")
|
|
|
+else:
|
|
|
+ warn_("ba_ingestd_alerts_received_total: 0 series (no recent traffic — might be OK)")
|
|
|
+
|
|
|
+# ── Step 3 ────────────────────────────────────────────────────────────────
|
|
|
+print("\nStep 3 — routerd recipient expansion latency metric")
|
|
|
+series = prom_query("ba_routerd_recipient_expansion_seconds_count")
|
|
|
+cnt = len(series) if series else 0
|
|
|
+if cnt > 0:
|
|
|
+ pass_(f"ba_routerd_recipient_expansion_seconds: {cnt} series (M9 routerd deployed)")
|
|
|
+else:
|
|
|
+ fail_("ba_routerd_recipient_expansion_seconds has no series — routerd missing M9 metric")
|
|
|
+
|
|
|
+# ── Step 4 ────────────────────────────────────────────────────────────────
|
|
|
+print("\nStep 4 — deliverd delivery attempt metrics (M9)")
|
|
|
+series = prom_query("ba_deliverd_delivery_attempts_total")
|
|
|
+cnt = len(series) if series else 0
|
|
|
+if cnt > 0:
|
|
|
+ pass_(f"ba_deliverd_delivery_attempts_total: {cnt} series (M9 deliverd deployed)")
|
|
|
+else:
|
|
|
+ pass_("ba_deliverd_delivery_attempts_total: 0 series (metric exists, no delivery traffic yet)")
|
|
|
+
|
|
|
+# ── Step 5 ────────────────────────────────────────────────────────────────
|
|
|
+print("\nStep 5 — Circuit breaker gauge (M9 ingestd)")
|
|
|
+series = prom_query("ba_ingestd_circuit_breaker_state")
|
|
|
+cnt = len(series) if series else 0
|
|
|
+if cnt > 0:
|
|
|
+ pass_(f"ba_ingestd_circuit_breaker_state: {cnt} series (M9 ingestd deployed)")
|
|
|
+else:
|
|
|
+ warn_("ba_ingestd_circuit_breaker_state: 0 series (ingestd still on pre-M9 binary)")
|
|
|
+ warn_(" Rebuild: docker compose build --no-cache ingestd")
|
|
|
+ warn_(" Redeploy: docker compose up -d --no-deps ingestd")
|
|
|
+
|
|
|
+# ── Step 6 ────────────────────────────────────────────────────────────────
|
|
|
+print("\nStep 6 — Grafana reachable with dashboards provisioned")
|
|
|
+try:
|
|
|
+ with urllib.request.urlopen(f"{GRAFANA}/api/health", timeout=10) as r:
|
|
|
+ gf_status = r.status
|
|
|
+except Exception as e:
|
|
|
+ gf_status = 0
|
|
|
+
|
|
|
+if gf_status == 200:
|
|
|
+ pass_(f"Grafana HTTP {gf_status} (admin/admin at http://localhost:3001)")
|
|
|
+ try:
|
|
|
+ import base64
|
|
|
+ req = urllib.request.Request(f"{GRAFANA}/api/search")
|
|
|
+ req.add_header("Authorization", "Basic " + base64.b64encode(b"admin:admin").decode())
|
|
|
+ with urllib.request.urlopen(req, timeout=10) as r2:
|
|
|
+ results = json.loads(r2.read())
|
|
|
+ # Count items with 'broadannounce' in uid or title (folder or dashboard)
|
|
|
+ cnt = len([d for d in results
|
|
|
+ if 'broadannounce' in d.get('uid', '').lower()
|
|
|
+ or 'broadannounce' in d.get('title', '').lower()])
|
|
|
+ if cnt > 0:
|
|
|
+ pass_("BroadAnnounce Overview dashboard found in Grafana")
|
|
|
+ else:
|
|
|
+ warn_("BroadAnnounce dashboard not found in Grafana (may need manual import)")
|
|
|
+ except Exception as e:
|
|
|
+ warn_(f"Could not check dashboards: {e}")
|
|
|
+else:
|
|
|
+ fail_(f"Grafana returned HTTP {gf_status} — check docker compose logs grafana")
|
|
|
+
|
|
|
+# ── Step 7 ────────────────────────────────────────────────────────────────
|
|
|
+print("\nStep 7 — Prometheus self-monitoring")
|
|
|
+r = prom_query('up{job="prometheus"}')
|
|
|
+if r:
|
|
|
+ pass_("Prometheus scraping itself")
|
|
|
+else:
|
|
|
+ fail_("Prometheus not scraping itself")
|
|
|
+
|
|
|
+print("\n🎉 M9 smoke: all checks complete.")
|