From f1bcfa84712a45d3237ea56c3c89e9dfaba19edb Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 17 Jan 2026 09:18:14 -0800 Subject: [PATCH] Add per-source backup size breakdown to dashboard - Update borgmatic-metrics script to collect size per source directory - Add horizontal bar gauge panel showing backup size by source - Sources: Pictures, Documents, devpi, Zettelkasten, Forgejo, Loki, PostgreSQL Co-Authored-By: Claude Opus 4.5 --- .../templates/borgmatic-metrics.sh.j2 | 32 +++++++++++++ .../grafana/files/dashboards/borgmatic.json | 47 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/ansible/roles/borgmatic_metrics/templates/borgmatic-metrics.sh.j2 b/ansible/roles/borgmatic_metrics/templates/borgmatic-metrics.sh.j2 index 08fbc9c..99ce1ca 100644 --- a/ansible/roles/borgmatic_metrics/templates/borgmatic-metrics.sh.j2 +++ b/ansible/roles/borgmatic_metrics/templates/borgmatic-metrics.sh.j2 @@ -125,6 +125,38 @@ borgmatic_last_archive_timestamp $last_timestamp # TYPE borgmatic_last_archive_duration_seconds gauge borgmatic_last_archive_duration_seconds ${last_duration:-0} EOF + + # Collect per-source-directory sizes + cat >> "$TEMP_FILE" << 'EOF' + +# HELP borgmatic_source_size_bytes Size of each backup source directory in bytes +# TYPE borgmatic_source_size_bytes gauge +EOF + + # List archive contents and group by source directory + $BORG_CMD list "${BORG_REPO}::${last_archive_name}" --format "{size} {path}{NL}" 2>/dev/null | awk ' + { + size = $1 + path = $2 + # Map paths to friendly source names + if (path ~ /^Users\/[^\/]+\/Pictures/) { source = "Pictures" } + else if (path ~ /^Users\/[^\/]+\/Documents/) { source = "Documents" } + else if (path ~ /^Users\/[^\/]+\/devpi/) { source = "devpi" } + else if (path ~ /^Users\/[^\/]+\/code\/personal\/zk/) { source = "Zettelkasten" } + else if (path ~ /^Users\/[^\/]+\/.config\/borgmatic/) { source = "borgmatic_config" } + else if (path ~ /^opt\/homebrew\/var\/forgejo/) { source = "Forgejo" } + else if (path ~ /^opt\/homebrew\/var\/loki/) { source = "Loki" } + else if (path ~ /^borgmatic\/postgresql_databases/) { source = "PostgreSQL" } + else if (path ~ /^borgmatic\//) { source = "borgmatic_metadata" } + else { source = "other" } + + totals[source] += size + } + END { + for (src in totals) { + printf "borgmatic_source_size_bytes{source=\"%s\"} %.0f\n", src, totals[src] + } + }' >> "$TEMP_FILE" fi # Atomic move diff --git a/ansible/roles/grafana/files/dashboards/borgmatic.json b/ansible/roles/grafana/files/dashboards/borgmatic.json index 59eb9a4..d2233ed 100644 --- a/ansible/roles/grafana/files/dashboards/borgmatic.json +++ b/ansible/roles/grafana/files/dashboards/borgmatic.json @@ -477,6 +477,53 @@ "title": "Unique Chunks", "type": "stat" }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "green", "value": null }] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 8, "x": 16, "y": 4 }, + "id": 14, + "options": { + "displayMode": "basic", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showUnfilled": true, + "valueMode": "color" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "expr": "topk(10, borgmatic_source_size_bytes)", + "legendFormat": "{{source}}", + "refId": "A" + } + ], + "title": "Backup Size by Source", + "description": "Breakdown of backup size by source directory", + "type": "bargauge" + }, { "datasource": { "type": "prometheus",