Skip to content

Commit e5349da

Browse files
committed
Create borgmatic_metrics.py
1 parent 6f710f8 commit e5349da

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

borgmatic_metrics.py

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Description: Process borgmatic json output to collect backup metrics
4+
# Usage:
5+
# 1. Run "borgmatic --verbosity -1 --syslog-verbosity -1 --json > borgmatic.json"
6+
# 2. Run "borgmatic_metrics.py borgmatic.json | sponge /var/lib/node_exporter/borgmatic.prom"
7+
#
8+
9+
import json
10+
import datetime
11+
import sys
12+
13+
14+
METRIC_PREFIX = "borgmatic_backup_"
15+
16+
17+
def __fill_metric(metrics, name, labels, value, timestamp=None):
18+
real_name = f"{METRIC_PREFIX}{name}"
19+
if real_name not in metrics:
20+
metrics.update({real_name: []})
21+
22+
metric = {}
23+
metric.update({"labels": labels})
24+
metric.update({"value": value})
25+
if timestamp is not None:
26+
metric.update({"timestamp": timestamp})
27+
28+
metrics[real_name].append(metric)
29+
30+
31+
def __print_metrics(metrics, metrics_meta):
32+
for metric in metrics:
33+
metric_description = metrics_meta[metric]["help"]
34+
print(f"# HELP {metric} {metric_description}")
35+
36+
metric_type = metrics_meta[metric]["type"]
37+
print(f"# TYPE {metric} {metric_type}")
38+
39+
for entry in metrics[metric]:
40+
__labels3 = entry["labels"].items()
41+
__labels2 = [(k, f'"{v}"') for k, v in __labels3]
42+
__labels1 = ["=".join(x) for x in __labels2]
43+
labels = ",".join(__labels1)
44+
value = entry["value"]
45+
time = entry["timestamp"]
46+
print(f"{metric}{{{labels}}} {value} {time}")
47+
48+
49+
def process_borgmatic_results(results):
50+
metrics = {}
51+
metrics_meta = {
52+
f"{METRIC_PREFIX}success": {
53+
"type": "gauge",
54+
"help": "Whether a borgmatic operation succeeded.",
55+
},
56+
f"{METRIC_PREFIX}duration_seconds": {
57+
"type": "gauge",
58+
"help": "Duration of a borgmatic operation.",
59+
},
60+
f"{METRIC_PREFIX}latest_archive_files": {
61+
"type": "gauge",
62+
"help": "Number of files within latest backuped archive.",
63+
},
64+
f"{METRIC_PREFIX}latest_archive_original_size_bytes": {
65+
"type": "gauge",
66+
"help": "Original size of a latest backup archive.",
67+
},
68+
f"{METRIC_PREFIX}latest_archive_compressed_size_bytes": {
69+
"type": "gauge",
70+
"help": "Compressed size of a latest backup archive.",
71+
},
72+
f"{METRIC_PREFIX}latest_archive_deduplicated_size_bytes": {
73+
"type": "gauge",
74+
"help": "Deduplicated size of a latest backup archive.",
75+
},
76+
}
77+
78+
for entry in results:
79+
if not all(
80+
x in entry
81+
for x in ["archive", "cache", "encryption", "repository"]
82+
):
83+
# skip invalid objects within array of borgmatic response
84+
continue
85+
86+
labels = {
87+
"repository_id": entry["repository"]["id"],
88+
}
89+
90+
# converted from seconds to milliseconds
91+
metric_timestamp = int(
92+
datetime.datetime.strptime(
93+
entry["archive"]["end"], "%Y-%m-%dT%H:%M:%S.%f"
94+
).timestamp()
95+
* 1000
96+
)
97+
98+
metric_success = int(
99+
entry["archive"]["end"] == entry["repository"]["last_modified"]
100+
)
101+
__fill_metric(
102+
metrics, "success", labels, metric_success, metric_timestamp
103+
)
104+
105+
metric_duration_seconds = float(entry["archive"]["duration"])
106+
__fill_metric(
107+
metrics,
108+
"duration_seconds",
109+
labels,
110+
metric_duration_seconds,
111+
metric_timestamp,
112+
)
113+
114+
metric_latest_archive_files = int(entry["archive"]["stats"]["nfiles"])
115+
__fill_metric(
116+
metrics,
117+
"latest_archive_files",
118+
labels,
119+
metric_latest_archive_files,
120+
metric_timestamp,
121+
)
122+
123+
metric_latest_archive_original_size_bytes = int(
124+
entry["archive"]["stats"]["original_size"]
125+
)
126+
__fill_metric(
127+
metrics,
128+
"latest_archive_original_size_bytes",
129+
labels,
130+
metric_latest_archive_original_size_bytes,
131+
metric_timestamp,
132+
)
133+
134+
metric_latest_archive_compressed_size_bytes = int(
135+
entry["archive"]["stats"]["compressed_size"]
136+
)
137+
__fill_metric(
138+
metrics,
139+
"latest_archive_compressed_size_bytes",
140+
labels,
141+
metric_latest_archive_compressed_size_bytes,
142+
metric_timestamp,
143+
)
144+
145+
metric_latest_archive_deduplicated_size_bytes = int(
146+
entry["archive"]["stats"]["deduplicated_size"]
147+
)
148+
__fill_metric(
149+
metrics,
150+
"latest_archive_deduplicated_size_bytes",
151+
labels,
152+
metric_latest_archive_deduplicated_size_bytes,
153+
metric_timestamp,
154+
)
155+
156+
__print_metrics(metrics, metrics_meta)
157+
158+
159+
if __name__ == "__main__":
160+
results = []
161+
162+
if len(sys.argv) != 2:
163+
print(
164+
f"{sys.argv[0]}: You must specify ONLY full path to the file "
165+
"with borgmatic output in json format."
166+
)
167+
sys.exit(-1)
168+
169+
with open(sys.argv[1], "r") as f:
170+
results = json.load(f)
171+
172+
process_borgmatic_results(results)

0 commit comments

Comments
 (0)