feat(grafana): six per-service dashboards

This commit is contained in:
kert
2026-05-01 11:39:12 -04:00
parent 6928c2e61c
commit 660dc46062
7 changed files with 121 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
{
"title": "CI",
"uid": "ci",
"tags": ["homelab", "stack"],
"timezone": "browser",
"schemaVersion": 39,
"time": {"from": "now-6h", "to": "now"},
"refresh": "30s",
"panels": [
{"id": 1, "title": "Active CI jobs (24h)", "type": "stat", "gridPos": {"x": 0, "y": 0, "w": 4, "h": 4}, "datasource": {"uid": "loki"}, "targets": [{"expr": "count_over_time({container=\"act-runner\", ci_event=\"job started\"}[24h])"}]},
{"id": 2, "title": "Job outcomes (24h)", "type": "timeseries", "gridPos": {"x": 4, "y": 0, "w": 20, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum by (ci_event) (count_over_time({container=\"act-runner\", ci_event=~\"job (started|finished|failed)\"}[24h]))"}]},
{"id": 3, "title": "Gitea push events", "type": "timeseries", "gridPos": {"x": 0, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum(rate({container=\"git\", gitea_event=\"push\"}[5m]))"}]},
{"id": 4, "title": "Webhook events", "type": "timeseries", "gridPos": {"x": 12, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum(rate({container=\"git\", gitea_event=\"webhook\"}[5m]))"}]}
]
}

View File

@@ -0,0 +1,16 @@
{
"title": "Data Lake",
"uid": "data-lake",
"tags": ["homelab", "stack"],
"timezone": "browser",
"schemaVersion": 39,
"time": {"from": "now-6h", "to": "now"},
"refresh": "30s",
"panels": [
{"id": 1, "title": "Trino queries (status)", "type": "timeseries", "gridPos": {"x": 0, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum by (trino_event) (rate({container=\"trino\"}[1m]))"}]},
{"id": 2, "title": "Trino query duration p95", "type": "timeseries", "gridPos": {"x": 12, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "histogram_quantile(0.95, sum by (le) (rate(trino_query_execution_seconds_bucket[5m])))"}]},
{"id": 3, "title": "Nessie/Polaris HTTP rate", "type": "timeseries", "gridPos": {"x": 0, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "sum by (job) (rate(http_server_requests_seconds_count{job=~\"nessie|polaris\"}[1m]))"}]},
{"id": 4, "title": "Nessie/Polaris error rate", "type": "timeseries", "gridPos": {"x": 12, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "sum by (job) (rate(http_server_requests_seconds_count{job=~\"nessie|polaris\",status=~\"5..\"}[1m]))"}]},
{"id": 5, "title": "RustFS S3 ops by op", "type": "timeseries", "gridPos": {"x": 0, "y": 16, "w": 24, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum by (s3_op) (rate({container=\"rustfs\"} | json [1m]))"}]}
]
}

View File

@@ -0,0 +1,16 @@
{
"title": "Data Pipelines",
"uid": "data-pipelines",
"tags": ["homelab", "stack"],
"timezone": "browser",
"schemaVersion": 39,
"time": {"from": "now-6h", "to": "now"},
"refresh": "30s",
"panels": [
{"id": 1, "title": "API request rate (status)", "type": "timeseries", "gridPos": {"x": 0, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "sum by (status) (rate(stack_http_server_requests_total[1m]))"}]},
{"id": 2, "title": "Mail-poller poll cycles", "type": "timeseries", "gridPos": {"x": 12, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum(rate({container=\"mail-poller\", mail_event=\"poll\"}[5m]))"}]},
{"id": 3, "title": "Mail-poller ingest count", "type": "timeseries", "gridPos": {"x": 0, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum(rate({container=\"mail-poller\", mail_event=\"ingest\"}[5m]))"}]},
{"id": 4, "title": "Pipeline errors", "type": "timeseries", "gridPos": {"x": 12, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum by (pipeline_name) (rate({container=\"api\", api_event=\"error\"}[5m]))"}]},
{"id": 5, "title": "Pipeline error tail", "type": "logs", "gridPos": {"x": 0, "y": 16, "w": 24, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "{container=\"api\", level=\"error\"}"}]}
]
}

View File

@@ -0,0 +1,16 @@
{
"title": "Gateway & Auth",
"uid": "gateway-auth",
"tags": ["homelab", "stack"],
"timezone": "browser",
"schemaVersion": 39,
"time": {"from": "now-6h", "to": "now"},
"refresh": "30s",
"panels": [
{"id": 1, "title": "Traefik request rate by status", "type": "timeseries", "gridPos": {"x": 0, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "sum by (code) (rate(traefik_service_requests_total[1m]))"}]},
{"id": 2, "title": "Traefik latency p95 (s)", "type": "timeseries", "gridPos": {"x": 12, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "histogram_quantile(0.95, sum by (le) (rate(traefik_service_request_duration_seconds_bucket[5m])))"}]},
{"id": 3, "title": "oauth2-proxy auth events", "type": "timeseries", "gridPos": {"x": 0, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum by (oauth_event) (rate({container=\"oauth2-proxy\"} | json [1m]))"}]},
{"id": 4, "title": "Gitea login + push events", "type": "timeseries", "gridPos": {"x": 12, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum by (gitea_event) (rate({container=\"git\"} | regexp `(?P<gitea_event>login|push|webhook)` [1m]))"}]},
{"id": 5, "title": "Failed auth tail", "type": "logs", "gridPos": {"x": 0, "y": 16, "w": 24, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "{container=~\"oauth2-proxy|git\"} |~ \"(?i)(failure|forbidden|401|403)\""}]}
]
}

View File

@@ -0,0 +1,17 @@
{
"title": "GPU & Notebooks",
"uid": "gpu-notebooks",
"tags": ["homelab", "stack"],
"timezone": "browser",
"schemaVersion": 39,
"time": {"from": "now-6h", "to": "now"},
"refresh": "30s",
"panels": [
{"id": 1, "title": "GPU utilization %", "type": "timeseries", "gridPos": {"x": 0, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "DCGM_FI_DEV_GPU_UTIL"}]},
{"id": 2, "title": "GPU memory used (MiB)", "type": "timeseries", "gridPos": {"x": 12, "y": 0, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "DCGM_FI_DEV_FB_USED"}]},
{"id": 3, "title": "GPU temp (°C)", "type": "timeseries", "gridPos": {"x": 0, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "DCGM_FI_DEV_GPU_TEMP"}]},
{"id": 4, "title": "GPU power (W)", "type": "timeseries", "gridPos": {"x": 12, "y": 8, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "DCGM_FI_DEV_POWER_USAGE"}]},
{"id": 5, "title": "Notebooks/Zotero CPU %", "type": "timeseries", "gridPos": {"x": 0, "y": 16, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "rate(container_cpu_usage_seconds_total{name=~\"notebooks|zotero\"}[1m]) * 100", "legendFormat": "{{name}}"}]},
{"id": 6, "title": "Notebooks/Zotero memory", "type": "timeseries", "gridPos": {"x": 12, "y": 16, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "container_memory_working_set_bytes{name=~\"notebooks|zotero\"} / 1024 / 1024", "legendFormat": "{{name}}"}]}
]
}

View File

@@ -0,0 +1,17 @@
{
"title": "Homelab Overview",
"uid": "homelab-overview",
"tags": ["homelab", "stack"],
"timezone": "browser",
"schemaVersion": 39,
"time": {"from": "now-6h", "to": "now"},
"refresh": "30s",
"panels": [
{"id": 1, "title": "Containers up", "type": "stat", "gridPos": {"x": 0, "y": 0, "w": 4, "h": 4}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "count(container_last_seen{name!=\"\"})"}]},
{"id": 2, "title": "Log volume / 1m", "type": "stat", "gridPos": {"x": 4, "y": 0, "w": 4, "h": 4}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum(rate({container=~\".+\"}[1m]))"}]},
{"id": 3, "title": "Error rate / 1m", "type": "stat", "gridPos": {"x": 8, "y": 0, "w": 4, "h": 4}, "datasource": {"uid": "loki"}, "targets": [{"expr": "sum(rate({level=\"error\"}[1m]))"}]},
{"id": 4, "title": "Container CPU %", "type": "timeseries", "gridPos": {"x": 0, "y": 4, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "rate(container_cpu_usage_seconds_total{name!=\"\"}[1m]) * 100", "legendFormat": "{{name}}"}]},
{"id": 5, "title": "Container memory (MiB)", "type": "timeseries", "gridPos": {"x": 12, "y": 4, "w": 12, "h": 8}, "datasource": {"uid": "prometheus"}, "targets": [{"expr": "container_memory_working_set_bytes{name!=\"\"} / 1024 / 1024", "legendFormat": "{{name}}"}]},
{"id": 6, "title": "Top error sources (1h)", "type": "logs", "gridPos": {"x": 0, "y": 12, "w": 24, "h": 8}, "datasource": {"uid": "loki"}, "targets": [{"expr": "{level=\"error\"} | json"}]}
]
}

View File

@@ -5,6 +5,7 @@ refs docs/superpowers/specs/2026-05-01-observability-port-design.md
from __future__ import annotations
import json
from pathlib import Path
import yaml
@@ -276,3 +277,26 @@ class TestSubdomainSwap:
class TestJaegerRemoved:
def test_jaeger_service_gone(self):
assert "jaeger" not in _services()
EXPECTED_DASHBOARDS = {
"homelab-overview",
"gateway-auth",
"data-lake",
"data-pipelines",
"ci",
"gpu-notebooks",
}
class TestDashboards:
def test_all_dashboards_exist_and_parse(self):
dash_dir = ROOT / "infra/grafana/dashboards"
files = {p.stem for p in dash_dir.glob("*.json")}
missing = EXPECTED_DASHBOARDS - files
assert not missing, f"missing dashboards: {missing}"
for name in EXPECTED_DASHBOARDS:
with (dash_dir / f"{name}.json").open() as f:
data = json.load(f)
assert "panels" in data, f"{name}: no panels[]"
assert data["panels"], f"{name}: empty panels[]"