fix: /metrics/history gives the availability of each site-to-site connection as the percentage of time it was up in slots of the view.
Release / release (push) Successful in 30s

This commit is contained in:
slawek committed 2026-10-25 18:49:47 +01:00
1 parent 25c151841b
commit 1cef4c044f
3 files changed
+45 -1

No files matched your search

+11
View File
@@ -912,6 +912,17 @@ How it works:
| `month` | 6 hours | hourly | | `month` | 6 hours | hourly |
| `year` | 1 month | monthly | | `year` | 1 month | monthly |
- **Site-to-site availability:** `site_to_site_up` of the same answer divides the view into slots of its own, `slot`, and gives for each slot and connection the percentage of the samples in which the connection was up, 0 to 100, as `points` of `{"time", "connections": {name: percent}}`:
| Range | Slot | Source |
|---|---|---|
| `hour` | 5 minutes | details |
| `day` | 1 hour | details |
| `month` | 1 day | hourly |
| `year` | 1 month | monthly |
A sample is taken every minute, so an interruption shorter than a minute may be missed; a slot without samples has no value, and the year view, like the others of its level, has the complete months only.
### Scraping ### Scraping
`GET /metrics` of the metrics service serves the last sample in the Prometheus text format, written by `prometheus_client`. `GET /metrics` of the metrics service serves the last sample in the Prometheus text format, written by `prometheus_client`.
+11
View File
@@ -1,3 +1,14 @@
vpn-router (0.19.1-1) unstable; urgency=medium
fix: /metrics/history gives the availability of each site-to-site connection as the percentage of time it was up in slots of the view.
* site_to_site_up of /metrics/history divides the view into slots of 5
minutes for an hour, 1 hour for a day, 1 day for a month and 1 month
for a year, and gives for each slot and connection the percentage of
the samples in which the connection was up.
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Mon, 26 Oct 2026 21:00:00 +0100
vpn-router (0.19.0-1) unstable; urgency=medium vpn-router (0.19.0-1) unstable; urgency=medium
feature: service accounts have a display name and a description, which the router sets for its internal ones. feature: service accounts have a display name and a description, which the router sets for its internal ones.
@@ -44,6 +44,15 @@ LEVELS = (
#: The kinds of VPN clients of metrics_vpn. #: The kinds of VPN clients of metrics_vpn.
VPN_KINDS = ('p2s', 'openvpn', 'wireguard') VPN_KINDS = ('p2s', 'openvpn', 'wireguard')
#: The time slots of the site-to-site availability of each view of history(): the summary level read (None: the
#: details), the slot as PostgreSQL starts it from time, and its length.
UP_SLOTS = {
'hour': (None, "date_bin('5 minutes', time, timestamptz '2000-01-01 00:00:00+00')", '5 minutes'),
'day': (None, "date_trunc('hour', time, 'UTC')", '1 hour'),
'month': ('hourly', "date_trunc('day', time, 'UTC')", '1 day'),
'year': ('monthly', "date_trunc('month', time, 'UTC')", '1 month'),
}
#: The views of history(): the summary level read (None: the details), the bucket and the span. #: The views of history(): the summary level read (None: the details), the bucket and the span.
RANGES = { RANGES = {
'hour': (None, '1 minute', '1 hour'), 'hour': (None, '1 minute', '1 hour'),
@@ -148,8 +157,13 @@ def history(connection, view):
each interface to its rx and tx, clients, which maps each kind of VPN client to its clients, each interface to its rx and tx, clients, which maps each kind of VPN client to its clients,
and site_to_site, which maps each site-to-site connection to the values of and site_to_site, which maps each site-to-site connection to the values of
metrics_site_to_site; a value without data is None. metrics_site_to_site; a value without data is None.
site_to_site_up is the availability of each site-to-site connection in the time slots of UP_SLOTS, oldest first:
for each slot its start in Unix seconds and, per connection, the percentage of its samples in which the connection
was up, 0 to 100; the summaries' averages of up give it for the month and the year.
""" """
level, bucket, span = RANGES[view] level, bucket, span = RANGES[view]
up_level, up_start, up_slot = UP_SLOTS[view]
def rows(table): def rows(table):
keys, values = TABLES[table] keys, values = TABLES[table]
@@ -182,4 +196,12 @@ def history(connection, view):
for row in rows('metrics_site_to_site'): for row in rows('metrics_site_to_site'):
point(row[0])['site_to_site'][row[1]] = pairs(row[2:], TABLES['metrics_site_to_site'][1]) point(row[0])['site_to_site'][row[1]] = pairs(row[2:], TABLES['metrics_site_to_site'][1])
empty = {value: {'avg': None, 'max': None} for value in TABLES['metrics_system'][1]} empty = {value: {'avg': None, 'max': None} for value in TABLES['metrics_system'][1]}
return {'range': view, 'bucket': bucket, 'points': [{**empty, **points[start]} for start in sorted(points)]}
slots = {}
for start, name, percent in connection.execute(
f"SELECT extract(epoch FROM {up_start})::bigint AS slot, connection, avg({'up' if up_level is None else 'up_avg'}) * 100 "
f"FROM {'metrics_site_to_site' if up_level is None else f'metrics_site_to_site_{up_level}'} "
f"WHERE time >= now() - interval '{span}' GROUP BY slot, connection ORDER BY slot").fetchall():
slots.setdefault(start, {'time': start, 'connections': {}})['connections'][name] = percent
return {'range': view, 'bucket': bucket, 'points': [{**empty, **points[start]} for start in sorted(points)],
'site_to_site_up': {'slot': up_slot, 'points': [slots[start] for start in sorted(slots)]}}