diff options
| -rw-r--r-- | f3s/argocd-apps/prometheus.yaml | 126 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/additional-scrape-configs-secret.yaml | 29 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/epimetheus-dashboard.yaml | 481 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/freebsd-recording-rules.yaml | 44 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/grafana-datasources-configmap.yaml | 76 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/grafana-restart-hook.yaml | 76 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/openbsd-recording-rules.yaml | 38 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/persistent-volumes.yaml | 54 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/zfs-dashboards.yaml | 38 | ||||
| -rw-r--r-- | f3s/prometheus/manifests/zfs-recording-rules.yaml | 108 |
10 files changed, 1070 insertions, 0 deletions
diff --git a/f3s/argocd-apps/prometheus.yaml b/f3s/argocd-apps/prometheus.yaml new file mode 100644 index 0000000..4265168 --- /dev/null +++ b/f3s/argocd-apps/prometheus.yaml @@ -0,0 +1,126 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: prometheus + namespace: cicd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + # Source 1: Upstream Helm chart from prometheus-community + - repoURL: https://prometheus-community.github.io/helm-charts + chart: kube-prometheus-stack + targetRevision: 55.5.0 + helm: + releaseName: prometheus + valuesObject: + kubeEtcd: + enabled: true + endpoints: + - 192.168.2.120 + - 192.168.2.121 + - 192.168.2.122 + service: + enabled: true + port: 2381 + targetPort: 2381 + + kubeControllerManager: + enabled: true + endpoints: + - 192.168.2.120 + - 192.168.2.121 + - 192.168.2.122 + service: + enabled: true + port: 10257 + targetPort: 10257 + serviceMonitor: + enabled: true + https: true + insecureSkipVerify: true + + prometheus: + prometheusSpec: + additionalArgs: + - name: web.enable-remote-write-receiver + value: "" + - name: web.enable-admin-api + value: "" + enableFeatures: + - exemplar-storage + - otlp-write-receiver + additionalScrapeConfigs: [] + tsdb: + outOfOrderTimeWindow: 744h # 31 days + additionalScrapeConfigsSecret: + enabled: true + name: additional-scrape-configs + key: additional-scrape-configs.yaml + storageSpec: + volumeClaimTemplate: + spec: + storageClassName: "" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 10Gi + selector: + matchLabels: + type: local + app: prometheus + + grafana: + persistence: + enabled: true + type: pvc + existingClaim: "grafana-data-pvc" + + initChownData: + enabled: false + + podSecurityContext: + fsGroup: 911 + runAsUser: 911 + runAsGroup: 911 + + # Disable sidecar-based datasource provisioning + sidecar: + datasources: + enabled: false + + # Mount datasources ConfigMap directly to provisioning directory + extraVolumes: + - name: datasources-volume + configMap: + name: grafana-datasources-all + + extraVolumeMounts: + - name: datasources-volume + mountPath: /etc/grafana/provisioning/datasources + readOnly: true + + # Source 2: Additional manifests from Git repository + - repoURL: https://codeberg.org/snonux/conf.git + targetRevision: master + path: f3s/prometheus/manifests + + destination: + server: https://kubernetes.default.svc + namespace: monitoring + + syncPolicy: + automated: + prune: false # Manual pruning for safety on complex stack + selfHeal: true + syncOptions: + - CreateNamespace=false + - ServerSideApply=true + retry: + limit: 3 + backoff: + duration: 10s + factor: 2 + maxDuration: 3m diff --git a/f3s/prometheus/manifests/additional-scrape-configs-secret.yaml b/f3s/prometheus/manifests/additional-scrape-configs-secret.yaml new file mode 100644 index 0000000..a2d9534 --- /dev/null +++ b/f3s/prometheus/manifests/additional-scrape-configs-secret.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Secret +metadata: + name: additional-scrape-configs + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "1" +type: Opaque +stringData: + additional-scrape-configs.yaml: | + - job_name: 'node-exporter' + static_configs: + - targets: + - '192.168.2.130:9100' # f0 via WireGuard + - '192.168.2.131:9100' # f1 via WireGuard + - '192.168.2.132:9100' # f2 via WireGuard + labels: + os: freebsd + - targets: + - '192.168.2.110:9100' # blowfish via WireGuard + - '192.168.2.111:9100' # fishfinger via WireGuard + labels: + os: openbsd + + - job_name: 'pushgateway' + honor_labels: true + static_configs: + - targets: + - 'pushgateway.monitoring.svc.cluster.local:9091' diff --git a/f3s/prometheus/manifests/epimetheus-dashboard.yaml b/f3s/prometheus/manifests/epimetheus-dashboard.yaml new file mode 100644 index 0000000..2d5d68a --- /dev/null +++ b/f3s/prometheus/manifests/epimetheus-dashboard.yaml @@ -0,0 +1,481 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: epimetheus-dashboard + annotations: + argocd.argoproj.io/sync-wave: "4" + namespace: monitoring + labels: + grafana_dashboard: '1' + app.kubernetes.io/instance: prometheus + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: kube-prometheus-stack + release: prometheus +data: + epimetheus.json: |- + { + "id": null, + "uid": "epimetheus-test", + "title": "Epimetheus Test Metrics", + "tags": [ + "epimetheus", + "test", + "demo" + ], + "timezone": "browser", + "schemaVersion": 38, + "version": 1, + "refresh": "10s", + "panels": [ + { + "id": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "type": "timeseries", + "title": "Data Timeline - All Metrics Over Time", + "description": "Shows when metrics data is available - helps identify realtime vs historic data", + "targets": [ + { + "expr": "epimetheus_test_requests_total", + "legendFormat": "Requests Total", + "refId": "A" + }, + { + "expr": "epimetheus_test_active_connections", + "legendFormat": "Active Connections", + "refId": "B" + }, + { + "expr": "epimetheus_test_temperature_celsius", + "legendFormat": "Temperature", + "refId": "C" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "barAlignment": 0, + "lineWidth": 1, + "fillOpacity": 10, + "gradientMode": "none", + "spanNulls": false, + "showPoints": "auto", + "pointSize": 5, + "stacking": { + "mode": "none", + "group": "A" + }, + "axisPlacement": "auto", + "axisLabel": "", + "scaleDistribution": { + "type": "linear" + }, + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "legend": { + "showLegend": true, + "displayMode": "list", + "placement": "bottom", + "calcs": [] + } + } + }, + { + "id": 1, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "type": "graph", + "title": "Request Rate", + "targets": [ + { + "expr": "rate(epimetheus_test_requests_total[5m])", + "legendFormat": "Requests/sec", + "refId": "A" + } + ], + "yaxes": [ + { + "format": "reqps", + "label": "Requests per Second" + }, + { + "format": "short" + } + ], + "xaxis": { + "mode": "time" + }, + "lines": true, + "fill": 1, + "linewidth": 2, + "pointradius": 5, + "tooltip": { + "shared": true + } + }, + { + "id": 2, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "type": "stat", + "title": "Total Requests", + "targets": [ + { + "expr": "epimetheus_test_requests_total", + "legendFormat": "Total", + "refId": "A" + } + ], + "options": { + "graphMode": "area", + "colorMode": "value", + "justifyMode": "auto", + "textMode": "auto", + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ] + } + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "unit": "short" + } + } + }, + { + "id": 3, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "type": "gauge", + "title": "Active Connections", + "targets": [ + { + "expr": "epimetheus_test_active_connections", + "legendFormat": "Connections", + "refId": "A" + } + ], + "options": { + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "fieldConfig": { + "defaults": { + "min": 0, + "max": 100, + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 50, + "color": "yellow" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "unit": "short" + } + } + }, + { + "id": 4, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "type": "gauge", + "title": "Temperature (°C)", + "targets": [ + { + "expr": "epimetheus_test_temperature_celsius", + "legendFormat": "°C", + "refId": "A" + } + ], + "options": { + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "fieldConfig": { + "defaults": { + "min": 0, + "max": 50, + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "blue" + }, + { + "value": 20, + "color": "green" + }, + { + "value": 30, + "color": "yellow" + }, + { + "value": 35, + "color": "red" + } + ] + }, + "unit": "celsius" + } + } + }, + { + "id": 5, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 22 + }, + "type": "graph", + "title": "Request Duration Histogram", + "targets": [ + { + "expr": "histogram_quantile(0.50, rate(epimetheus_test_request_duration_seconds_bucket[5m]))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.90, rate(epimetheus_test_request_duration_seconds_bucket[5m]))", + "legendFormat": "p90", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, rate(epimetheus_test_request_duration_seconds_bucket[5m]))", + "legendFormat": "p99", + "refId": "C" + } + ], + "yaxes": [ + { + "format": "s", + "label": "Duration" + }, + { + "format": "short" + } + ], + "xaxis": { + "mode": "time" + }, + "lines": true, + "fill": 1, + "linewidth": 2, + "tooltip": { + "shared": true + } + }, + { + "id": 6, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 30 + }, + "type": "stat", + "title": "Average Request Duration", + "targets": [ + { + "expr": "rate(epimetheus_test_request_duration_seconds_sum[5m]) / rate(epimetheus_test_request_duration_seconds_count[5m])", + "legendFormat": "Avg Duration", + "refId": "A" + } + ], + "options": { + "graphMode": "area", + "colorMode": "value", + "textMode": "auto", + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ] + } + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "unit": "s", + "decimals": 3 + } + } + }, + { + "id": 7, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 30 + }, + "type": "bargauge", + "title": "Jobs Processed by Type", + "targets": [ + { + "expr": "sum by (job_type) (epimetheus_test_jobs_processed_total)", + "legendFormat": "{{job_type}}", + "refId": "A" + } + ], + "options": { + "orientation": "horizontal", + "displayMode": "gradient", + "showUnfilled": true + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "unit": "short" + } + } + }, + { + "id": 8, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 38 + }, + "type": "table", + "title": "Jobs Status Breakdown", + "targets": [ + { + "expr": "epimetheus_test_jobs_processed_total", + "legendFormat": "", + "refId": "A", + "format": "table", + "instant": true + } + ], + "options": { + "showHeader": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true, + "instance": true, + "job": true + }, + "indexByName": {}, + "renameByName": { + "job_type": "Job Type", + "status": "Status", + "Value": "Count" + } + } + } + ] + } + ], + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m" + ] + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [], + "liveNow": false, + "style": "dark" + } diff --git a/f3s/prometheus/manifests/freebsd-recording-rules.yaml b/f3s/prometheus/manifests/freebsd-recording-rules.yaml new file mode 100644 index 0000000..501a54c --- /dev/null +++ b/f3s/prometheus/manifests/freebsd-recording-rules.yaml @@ -0,0 +1,44 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: freebsd-memory-rules + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "3" + labels: + release: prometheus +spec: + groups: + # FreeBSD memory metrics mapped to Linux-compatible names + - name: freebsd-memory + rules: + # Synthetic MemTotal for FreeBSD (maps to size_bytes) + - record: node_memory_MemTotal_bytes + expr: node_memory_size_bytes{os="freebsd"} + labels: + os: freebsd + + # Synthetic MemAvailable for FreeBSD + # Available = free + inactive + cache (approximation) + - record: node_memory_MemAvailable_bytes + expr: node_memory_free_bytes{os="freebsd"} + node_memory_inactive_bytes{os="freebsd"} + node_memory_cache_bytes{os="freebsd"} + labels: + os: freebsd + + # Synthetic MemFree for FreeBSD + - record: node_memory_MemFree_bytes + expr: node_memory_free_bytes{os="freebsd"} + labels: + os: freebsd + + # Synthetic Buffers for FreeBSD + - record: node_memory_Buffers_bytes + expr: node_memory_buffer_bytes{os="freebsd"} + labels: + os: freebsd + + # Synthetic Cached for FreeBSD + - record: node_memory_Cached_bytes + expr: node_memory_cache_bytes{os="freebsd"} + labels: + os: freebsd diff --git a/f3s/prometheus/manifests/grafana-datasources-configmap.yaml b/f3s/prometheus/manifests/grafana-datasources-configmap.yaml new file mode 100644 index 0000000..5b03597 --- /dev/null +++ b/f3s/prometheus/manifests/grafana-datasources-configmap.yaml @@ -0,0 +1,76 @@ +# Unified Grafana Datasources ConfigMap +# This ConfigMap contains all datasources (Prometheus, Alertmanager, Loki, Tempo) +# and is directly mounted to /etc/grafana/provisioning/datasources/ +# +# Benefits: +# - Simple and direct (no sidecar complexity) +# - Grafana provisioning runs on startup +# - Field order preserved exactly as written +# - All datasources in one place +# - Fully IaC compliant + +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-datasources-all + namespace: monitoring + labels: + app.kubernetes.io/name: grafana + app.kubernetes.io/component: datasources + annotations: + argocd.argoproj.io/sync-wave: "1" +data: + datasources.yaml: | + apiVersion: 1 + datasources: + - name: Prometheus + type: prometheus + uid: prometheus + url: http://prometheus-kube-prometheus-prometheus.monitoring:9090/ + access: proxy + isDefault: true + editable: false + jsonData: + httpMethod: POST + timeInterval: 30s + - name: Alertmanager + type: alertmanager + uid: alertmanager + url: http://prometheus-kube-prometheus-alertmanager.monitoring:9093/ + access: proxy + editable: false + jsonData: + handleGrafanaManagedAlerts: false + implementation: prometheus + - name: Loki + type: loki + uid: loki + url: http://loki.monitoring.svc.cluster.local:3100 + access: proxy + isDefault: false + editable: false + jsonData: + maxLines: 1000 + - name: Tempo + type: tempo + uid: tempo + url: http://tempo.monitoring.svc.cluster.local:3200 + access: proxy + isDefault: false + editable: false + jsonData: + httpMethod: GET + tracesToLogsV2: + datasourceUid: loki + spanStartTimeShift: -1h + spanEndTimeShift: 1h + tracesToMetrics: + datasourceUid: prometheus + serviceMap: + datasourceUid: prometheus + nodeGraph: + enabled: true + search: + hide: false + lokiSearch: + datasourceUid: loki diff --git a/f3s/prometheus/manifests/grafana-restart-hook.yaml b/f3s/prometheus/manifests/grafana-restart-hook.yaml new file mode 100644 index 0000000..34d5867 --- /dev/null +++ b/f3s/prometheus/manifests/grafana-restart-hook.yaml @@ -0,0 +1,76 @@ +# ServiceAccount, Role, and RoleBinding for Grafana restart hook +apiVersion: v1 +kind: ServiceAccount +metadata: + name: grafana-restart-sa + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "0" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: grafana-restart-role + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "0" +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["list", "delete"] + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: grafana-restart-rb + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: grafana-restart-role +subjects: + - kind: ServiceAccount + name: grafana-restart-sa + namespace: monitoring +--- +# PostSync hook job that restarts Grafana pods to reload datasources +# This replicates the behavior of the manual "kubectl delete pod" step in the original Justfile +apiVersion: batch/v1 +kind: Job +metadata: + name: grafana-restart-hook + namespace: monitoring + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + argocd.argoproj.io/sync-wave: "10" +spec: + template: + spec: + serviceAccountName: grafana-restart-sa + restartPolicy: OnFailure + containers: + - name: kubectl + image: bitnami/kubectl:latest + command: + - /bin/sh + - -c + - | + echo "Waiting for Grafana deployment to be ready..." + kubectl wait --for=condition=available --timeout=300s deployment/prometheus-grafana -n monitoring || true + + echo "Restarting Grafana pods to reload datasources..." + kubectl delete pod -n monitoring -l app.kubernetes.io/name=grafana --ignore-not-found=true + + echo "Grafana restart triggered successfully" + echo "Waiting for Grafana to become ready again..." + sleep 10 + kubectl wait --for=condition=ready --timeout=180s pod -n monitoring -l app.kubernetes.io/name=grafana || true + + echo "Grafana restart complete" + backoffLimit: 2 diff --git a/f3s/prometheus/manifests/openbsd-recording-rules.yaml b/f3s/prometheus/manifests/openbsd-recording-rules.yaml new file mode 100644 index 0000000..7d88d4a --- /dev/null +++ b/f3s/prometheus/manifests/openbsd-recording-rules.yaml @@ -0,0 +1,38 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: openbsd-memory-rules + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "3" + labels: + release: prometheus +spec: + groups: + # OpenBSD memory metrics mapped to Linux-compatible names + - name: openbsd-memory + rules: + # Synthetic MemTotal for OpenBSD (maps to size_bytes) + - record: node_memory_MemTotal_bytes + expr: node_memory_size_bytes{os="openbsd"} + labels: + os: openbsd + + # Synthetic MemAvailable for OpenBSD + # Available = free + inactive + cache (approximation) + - record: node_memory_MemAvailable_bytes + expr: node_memory_free_bytes{os="openbsd"} + node_memory_inactive_bytes{os="openbsd"} + node_memory_cache_bytes{os="openbsd"} + labels: + os: openbsd + + # Synthetic MemFree for OpenBSD + - record: node_memory_MemFree_bytes + expr: node_memory_free_bytes{os="openbsd"} + labels: + os: openbsd + + # Synthetic Cached for OpenBSD + - record: node_memory_Cached_bytes + expr: node_memory_cache_bytes{os="openbsd"} + labels: + os: openbsd diff --git a/f3s/prometheus/manifests/persistent-volumes.yaml b/f3s/prometheus/manifests/persistent-volumes.yaml new file mode 100644 index 0000000..539ca3d --- /dev/null +++ b/f3s/prometheus/manifests/persistent-volumes.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: prometheus-data-pv + labels: + type: local + app: prometheus + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + capacity: + storage: 10Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: /data/nfs/k3svolumes/prometheus/data + type: Directory +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: grafana-data-pv + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + capacity: + storage: 2Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: /data/nfs/k3svolumes/grafana/data + type: Directory + claimRef: + namespace: monitoring + name: grafana-data-pvc +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: grafana-data-pvc + namespace: monitoring + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi diff --git a/f3s/prometheus/manifests/zfs-dashboards.yaml b/f3s/prometheus/manifests/zfs-dashboards.yaml new file mode 100644 index 0000000..7b41f09 --- /dev/null +++ b/f3s/prometheus/manifests/zfs-dashboards.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: freebsd-zfs-dashboards + annotations: + argocd.argoproj.io/sync-wave: "4" + namespace: monitoring + labels: + grafana_dashboard: '1' + app.kubernetes.io/instance: prometheus + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: kube-prometheus-stack + release: prometheus +data: + freebsd-zfs.json: "{\n \"title\": \"FreeBSD ZFS\",\n \"uid\": \"freebsd-zfs\",\n \"timezone\": \"utc\",\n \"schemaVersion\": 38,\n \"refresh\": \"30s\",\n \"tags\": [\n \"zfs\",\n \"freebsd\",\n \"storage\"\n ],\n \"editable\": true,\n \"templating\": {\n \"list\": [\n {\n \"name\": \"datasource\",\n \"type\": \"datasource\",\n \"query\": \"prometheus\",\n \"hide\": 0\n },\n {\n \"name\": \"instance\",\n \"type\": \"query\",\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"query\": \"label_values(node_zfs_arcstats_size_bytes{os=\\\"freebsd\\\"}, instance)\",\n \"refresh\": 2,\n \"hide\": 0,\n \"label\": \"FreeBSD Server\"\n },\n {\n \"name\": \"pool\",\n \"type\": \"query\",\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \ + \ \"query\": \"label_values(zfs_pool_size_bytes{instance=\\\"$instance\\\"}, pool)\",\n \"refresh\": 2,\n \"hide\": 0,\n \"label\": \"ZFS Pool\",\n \"includeAll\": true,\n \"multi\": false\n }\n ]\n },\n \"panels\": [\n {\n \"type\": \"row\",\n \"collapsed\": false,\n \"title\": \"Pool Overview\",\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n }\n },\n {\n \"type\": \"gauge\",\n \"title\": \"Pool Capacity\",\n \"gridPos\": {\n \"h\": 8,\n \"w\": 6,\n \"x\": 0,\n \"y\": 1\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"targets\": [\n {\n \"expr\": \"zfs_pool_capacity_percent{instance=\\\"$instance\\\",pool=~\\\"$pool\\\"}\",\n \"legendFormat\": \"{{pool}}\"\n }\n ],\n \"fieldConfig\": {\n \ + \ \"defaults\": {\n \"unit\": \"percent\",\n \"min\": 0,\n \"max\": 100,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 70\n },\n {\n \"color\": \"red\",\n \"value\": 85\n }\n ]\n }\n }\n }\n },\n {\n \"type\": \"stat\",\n \"title\": \"Pool Health\",\n \"gridPos\": {\n \"h\": 4,\n \"w\": 6,\n \"x\": 6,\n \"y\": 1\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"targets\": [\n {\n \"expr\": \"zfs_pool_health{instance=\\\"$instance\\\",pool=~\\\"$pool\\\"}\",\n \"legendFormat\": \"\ + {{pool}}\"\n }\n ],\n \"options\": {\n \"reduceOptions\": {\n \"values\": false,\n \"calcs\": [\n \"lastNotNull\"\n ]\n },\n \"orientation\": \"auto\",\n \"textMode\": \"value_and_name\",\n \"colorMode\": \"background\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"short\",\n \"mappings\": [\n {\n \"options\": {\n \"0\": {\n \"text\": \"ONLINE\",\n \"color\": \"green\"\n }\n },\n \"type\": \"value\"\n },\n {\n \"options\": {\n \"1\": {\n \"text\": \"DEGRADED\",\n \"color\": \"yellow\"\n }\n },\n \"type\": \"value\"\n },\n {\n\ + \ \"options\": {\n \"2\": {\n \"text\": \"FAULTED\",\n \"color\": \"red\"\n }\n },\n \"type\": \"value\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"yellow\",\n \"value\": 1\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n }\n },\n \"overrides\": []\n }\n },\n {\n \"type\": \"stat\",\n \"title\": \"Total Pool Size\",\n \"gridPos\": {\n \"h\": 4,\n \"w\": 6,\n \"x\": 12,\n \"y\": 1\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\ + \n },\n \"targets\": [\n {\n \"expr\": \"zfs_pool_size_bytes{instance=\\\"$instance\\\",pool=~\\\"$pool\\\"}\",\n \"legendFormat\": \"{{pool}}\"\n }\n ],\n \"options\": {\n \"reduceOptions\": {\n \"values\": false,\n \"calcs\": [\n \"lastNotNull\"\n ]\n },\n \"orientation\": \"auto\",\n \"textMode\": \"value_and_name\",\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n }\n },\n {\n \"type\": \"stat\",\n \"title\": \"Free Space\",\n \"gridPos\": {\n \"h\": 4,\n \"w\": 6,\n \"x\": 18,\n \"y\": 1\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"targets\": [\n\ + \ {\n \"expr\": \"zfs_pool_free_bytes{instance=\\\"$instance\\\",pool=~\\\"$pool\\\"}\",\n \"legendFormat\": \"{{pool}}\"\n }\n ],\n \"options\": {\n \"reduceOptions\": {\n \"values\": false,\n \"calcs\": |
