diff --git a/grafana/README.md b/grafana/README.md new file mode 100644 index 0000000..be31ae6 --- /dev/null +++ b/grafana/README.md @@ -0,0 +1,32 @@ +# Grafana dashboard + +`dashboard.json` is the operational dashboard for naas-api. Panels: + +| Row | Panels | +|---|---| +| Header | Pool fill % • entropy push rate (5m) • bytes served rate (5m) • reader errors (1h) | +| Throughput | Entropy push rate per device • Pool depth (pending vs capacity) | +| Output | Bytes served by format (stacked) • Health-check rejections per device | +| Errors | Reader errors per device (full-width bar) | + +Variables: `datasource` (Prometheus, defaults to UID `prom`/Mimir), `device` (multi-select from `naas_entropy_bytes_total`). + +## Import + +### Via UI +Grafana → Dashboards → New → Import → upload `dashboard.json`. Select the Prometheus datasource when prompted. + +### Via API +```sh +DS_UID=prom # adjust to your Prometheus/Mimir datasource UID +curl -fsS -u admin:"$GRAFANA_ADMIN_PASSWORD" \ + -H 'Content-Type: application/json' \ + -X POST https://grafana.example/api/dashboards/db \ + -d "$(jq --arg uid "$DS_UID" ' + {dashboard: ., overwrite: true, inputs: [{name:"DS_PROMETHEUS",type:"datasource",pluginId:"prometheus",value:$uid}]} + ' dashboard.json)" +``` + +## Provisioning via the lgtm-distributed Helm chart + +If you later enable the Grafana sidecar (`grafana.sidecar.dashboards.enabled: true`), drop this JSON into a ConfigMap labeled `grafana_dashboard: "1"` in the same namespace as Grafana — it will auto-load on the next sync. The naas-api repo is the source of truth; the ConfigMap can `kustomize build` against this file. diff --git a/grafana/dashboard.json b/grafana/dashboard.json new file mode 100644 index 0000000..3b30e47 --- /dev/null +++ b/grafana/dashboard.json @@ -0,0 +1,368 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": {"type": "grafana", "uid": "-- Grafana --"}, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Operational dashboard for naas-api: pool depth, per-device entropy throughput, response rates, health-check rejections and reader errors.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "liveNow": false, + "panels": [ + { + "id": 1, + "title": "Pool fill", + "type": "gauge", + "gridPos": {"x": 0, "y": 0, "w": 6, "h": 5}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "100 * naas_pool_chunks_pending / clamp_min(naas_pool_chunks_capacity, 1)", + "instant": true, + "legendFormat": "fill %" + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "red", "value": null}, + {"color": "orange", "value": 5}, + {"color": "green", "value": 50} + ] + } + }, + "overrides": [] + }, + "options": { + "showThresholdLabels": false, + "showThresholdMarkers": true, + "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false} + } + }, + { + "id": 2, + "title": "Entropy push rate (5m)", + "type": "stat", + "gridPos": {"x": 6, "y": 0, "w": 6, "h": 5}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "sum(rate(naas_entropy_bytes_total[5m]))", + "instant": true, + "legendFormat": "B/s" + } + ], + "fieldConfig": { + "defaults": { + "unit": "Bps", + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "red", "value": null}, + {"color": "green", "value": 1} + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, + "textMode": "auto" + } + }, + { + "id": 3, + "title": "Bytes served (5m)", + "type": "stat", + "gridPos": {"x": 12, "y": 0, "w": 6, "h": 5}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "sum(rate(naas_response_bytes_total[5m]))", + "instant": true, + "legendFormat": "B/s" + } + ], + "fieldConfig": { + "defaults": { + "unit": "Bps", + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "blue", "value": null} + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, + "textMode": "auto" + } + }, + { + "id": 4, + "title": "Reader errors (1h)", + "type": "stat", + "gridPos": {"x": 18, "y": 0, "w": 6, "h": 5}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "sum(increase(naas_reader_errors_total[1h]))", + "instant": true, + "legendFormat": "errors" + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "green", "value": null}, + {"color": "orange", "value": 1}, + {"color": "red", "value": 5} + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "none", + "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, + "textMode": "auto" + } + }, + { + "id": 5, + "title": "Entropy push rate (per device)", + "type": "timeseries", + "gridPos": {"x": 0, "y": 5, "w": 12, "h": 8}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "rate(naas_entropy_bytes_total{device=~\"$device\"}[1m])", + "legendFormat": "{{device}}" + } + ], + "fieldConfig": { + "defaults": { + "unit": "Bps", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 10, + "showPoints": "never", + "stacking": {"mode": "none", "group": "A"} + } + }, + "overrides": [] + }, + "options": { + "legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "max"]}, + "tooltip": {"mode": "multi", "sort": "desc"} + } + }, + { + "id": 6, + "title": "Pool depth", + "type": "timeseries", + "gridPos": {"x": 12, "y": 5, "w": 12, "h": 8}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "naas_pool_chunks_pending", + "legendFormat": "pending" + }, + { + "refId": "B", + "expr": "naas_pool_chunks_capacity", + "legendFormat": "capacity" + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 10, + "showPoints": "never" + } + }, + "overrides": [ + { + "matcher": {"id": "byName", "options": "capacity"}, + "properties": [ + {"id": "custom.lineStyle", "value": {"fill": "dash", "dash": [10, 10]}}, + {"id": "color", "value": {"mode": "fixed", "fixedColor": "gray"}}, + {"id": "custom.fillOpacity", "value": 0} + ] + } + ] + }, + "options": { + "legend": {"displayMode": "list", "placement": "bottom"}, + "tooltip": {"mode": "multi"} + } + }, + { + "id": 7, + "title": "Bytes served (by format)", + "type": "timeseries", + "gridPos": {"x": 0, "y": 13, "w": 12, "h": 8}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "rate(naas_response_bytes_total[1m])", + "legendFormat": "{{format}}" + } + ], + "fieldConfig": { + "defaults": { + "unit": "Bps", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 20, + "showPoints": "never", + "stacking": {"mode": "normal", "group": "A"} + } + }, + "overrides": [] + }, + "options": { + "legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "max"]}, + "tooltip": {"mode": "multi", "sort": "desc"} + } + }, + { + "id": 8, + "title": "Health-check rejections (per device)", + "type": "timeseries", + "gridPos": {"x": 12, "y": 13, "w": 12, "h": 8}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "rate(naas_health_rejections_total{device=~\"$device\"}[5m])", + "legendFormat": "{{device}}" + } + ], + "fieldConfig": { + "defaults": { + "unit": "ops", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 10, + "showPoints": "never" + } + }, + "overrides": [] + }, + "options": { + "legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "max"]}, + "tooltip": {"mode": "multi"} + } + }, + { + "id": 9, + "title": "Reader errors (per device)", + "type": "timeseries", + "gridPos": {"x": 0, "y": 21, "w": 24, "h": 6}, + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "targets": [ + { + "refId": "A", + "expr": "rate(naas_reader_errors_total{device=~\"$device\"}[5m])", + "legendFormat": "{{device}}" + } + ], + "fieldConfig": { + "defaults": { + "unit": "ops", + "custom": { + "drawStyle": "bars", + "lineWidth": 1, + "fillOpacity": 80, + "showPoints": "never" + }, + "color": {"mode": "fixed", "fixedColor": "red"} + }, + "overrides": [] + }, + "options": { + "legend": {"displayMode": "list", "placement": "bottom"}, + "tooltip": {"mode": "multi"} + } + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": ["naas-api", "rng", "entropy"], + "templating": { + "list": [ + { + "name": "datasource", + "type": "datasource", + "label": "Datasource", + "query": "prometheus", + "current": {"text": "Mimir", "value": "prom"}, + "hide": 0, + "refresh": 1 + }, + { + "name": "device", + "type": "query", + "label": "Device", + "datasource": {"type": "prometheus", "uid": "${datasource}"}, + "query": {"qryType": 1, "query": "label_values(naas_entropy_bytes_total, device)", "refId": "PrometheusVariableQueryEditor-VariableQuery"}, + "definition": "label_values(naas_entropy_bytes_total, device)", + "includeAll": true, + "multi": true, + "current": {"text": "All", "value": "$__all"}, + "refresh": 2, + "sort": 1, + "hide": 0 + } + ] + }, + "time": {"from": "now-1h", "to": "now"}, + "timepicker": {}, + "timezone": "browser", + "title": "naas-api", + "uid": "naas-api", + "version": 1, + "weekStart": "" +}