Development

This commit is contained in:
Matthew Grotke 2026-06-09 18:57:01 -04:00
parent d45a719ef1
commit 4bb485e4dd
2 changed files with 57 additions and 86 deletions

View file

@ -190,7 +190,7 @@
}, },
{ {
"type": "card", "type": "card",
"label": "DNS Caching", "label": "DNS Statistics",
"client_requirement": "client_is_viewer+", "client_requirement": "client_is_viewer+",
"items": [ "items": [
{ {
@ -198,86 +198,56 @@
"rows": [ "rows": [
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "Total Queries"},
"type": "grid_label", {"type": "grid_value", "text": "%DNS_STAT_QUERIES%"}
"text": "Total Queries"
},
{
"type": "grid_value",
"text": "%DNS_STAT_QUERIES%"
}
] ]
}, },
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "Cache Hits"},
"type": "grid_label", {"type": "grid_value", "text": "%DNS_STAT_HITS% (%DNS_STAT_HIT_RATE% hit rate)"}
"text": "Cache Hits"
},
{
"type": "grid_value",
"text": "%DNS_STAT_HITS% (%DNS_STAT_HIT_RATE% hit rate)"
}
] ]
}, },
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "Forwarded to Upstream"},
"type": "grid_label", {"type": "grid_value", "text": "%DNS_STAT_FORWARDED%"}
"text": "Forwarded"
},
{
"type": "grid_value",
"text": "%DNS_STAT_FORWARDED%"
}
] ]
}, },
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "Authoritative Answers"},
"type": "grid_label", {"type": "grid_value", "text": "%DNS_STAT_AUTH%"}
"text": "Cache Capacity"
},
{
"type": "grid_value",
"text": "%DNS_CACHE_SIZE% entries"
}
] ]
}, },
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "TCP Peak"},
"type": "grid_label", {"type": "grid_value", "text": "%DNS_STAT_TCP_PEAK%"}
"text": "Authoritative Answers"
},
{
"type": "grid_value",
"text": "%DNS_STAT_AUTH%"
}
] ]
}, },
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "Cache Capacity"},
"type": "grid_label", {"type": "grid_value", "text": "%DNS_CACHE_SIZE% entries"}
"text": "TCP Connections Peak"
},
{
"type": "grid_value",
"text": "%DNS_STAT_TCP_PEAK%"
}
] ]
}, },
{ {
"cells": [ "cells": [
{ {"type": "grid_label", "text": "DNS Providers"},
"type": "grid_label", {"type": "grid_value", "text": "%OVERVIEW_UPSTREAM_SERVERS%"}
"text": "DNS Providers" ]
}, },
{ {
"type": "grid_value", "cells": [
"text": "%OVERVIEW_UPSTREAM_SERVERS%" {"type": "grid_label", "text": "Tracking Since"},
} {"type": "grid_value", "text": "%DNS_METRICS_SINCE%"}
]
},
{
"cells": [
{"type": "grid_label", "text": "Last Updated"},
{"type": "grid_value", "text": "%DNS_METRICS_UPDATED%"}
] ]
} }
] ]

View file

@ -1,38 +1,37 @@
import re import json
import os import os
import config_utils import config_utils
import factory import factory
from pages.ddns.view import public_ip_info from pages.ddns.view import public_ip_info
from pages.dhcpleases.view import live_dhcp_leases from pages.dhcpleases.view import live_dhcp_leases
METRICS_FILE = f'{config_utils.CONFIGS_DIR}/.dns-metrics'
def get_dnsmasq_stats():
stats = {'queries': '-', 'hits': '-', 'hit_rate': '-', 'forwarded': '-', 'auth': '-', 'tcp_peak': '-'} def load_dns_metrics():
out = factory.run("journalctl -u 'dnsmasq-routlin-*' -n 200 --no-pager 2>/dev/null") empty = {'queries': '-', 'hits': '-', 'hit_rate': '-', 'forwarded': '-',
for line in reversed(out.splitlines()): 'auth': '-', 'tcp_peak': '-', 'updated': '-', 'since': '-', 'servers': []}
if 'queries forwarded' in line: try:
m = re.search(r'queries forwarded (\d+)', line) with open(METRICS_FILE) as f:
if m: data = json.load(f)
stats['forwarded'] = m.group(1) t = data.get('totals', {})
m = re.search(r'queries answered locally (\d+)', line) meta = data.get('metadata', {})
if m: fwd = t.get('queries_forwarded', 0)
stats['hits'] = m.group(1) hits = t.get('queries_answered_locally', 0)
fwd = int(stats['forwarded']) if stats['forwarded'] != '-' else 0 total = fwd + hits
hit = int(stats['hits']) if stats['hits'] != '-' else 0 return {
total = fwd + hit 'queries': f'{total:,}' if total else '-',
stats['queries'] = str(total) if total else '-' 'hits': f'{hits:,}' if hits else '-',
if total > 0: 'hit_rate': f'{hits / total * 100:.0f}%' if total > 0 else '-',
stats['hit_rate'] = f'{hit / total * 100:.0f}%' 'forwarded': f'{fwd:,}' if fwd else '-',
break 'auth': f'{t.get("queries_authoritative", 0):,}',
if 'auth answered' in line: 'tcp_peak': str(t.get('tcp_hwm', 0)),
m = re.search(r'auth answered (\d+)', line) 'updated': meta.get('last_recorded', '-'),
if m and stats['auth'] == '-': 'since': meta.get('first_recorded', '-'),
stats['auth'] = m.group(1) 'servers': t.get('servers', []),
if 'max TCP connections' in line: }
m = re.search(r'max TCP connections (\d+)', line) except Exception:
if m and stats['tcp_peak'] == '-': return empty
stats['tcp_peak'] = m.group(1)
return stats
def count_blocked_today(): def count_blocked_today():
@ -69,7 +68,7 @@ def collect_tokens(cfg):
vlan_names = [v.get('name', '') for v in vlans] vlan_names = [v.get('name', '') for v in vlans]
net = cfg.get('network_interfaces', {}) net = cfg.get('network_interfaces', {})
dns = cfg.get('upstream_dns', {}) dns = cfg.get('upstream_dns', {})
dns_stats = get_dnsmasq_stats() dns_stats = load_dns_metrics()
ddns = factory.load_ddns() ddns = factory.load_ddns()
ip_str, domains_sub, last_obtained = public_ip_info(ddns) ip_str, domains_sub, last_obtained = public_ip_info(ddns)
@ -94,4 +93,6 @@ def collect_tokens(cfg):
tokens['DNS_STAT_FORWARDED'] = dns_stats['forwarded'] tokens['DNS_STAT_FORWARDED'] = dns_stats['forwarded']
tokens['DNS_STAT_AUTH'] = dns_stats['auth'] tokens['DNS_STAT_AUTH'] = dns_stats['auth']
tokens['DNS_STAT_TCP_PEAK'] = dns_stats['tcp_peak'] tokens['DNS_STAT_TCP_PEAK'] = dns_stats['tcp_peak']
tokens['DNS_METRICS_UPDATED'] = dns_stats['updated']
tokens['DNS_METRICS_SINCE'] = dns_stats['since']
return tokens return tokens