From 4bb485e4dd73d01c49b28418d16099c271086158 Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Tue, 9 Jun 2026 18:57:01 -0400 Subject: [PATCH] Development --- .../app/pages/overview/content.json | 84 ++++++------------- .../routlin-dash/app/pages/overview/view.py | 59 ++++++------- 2 files changed, 57 insertions(+), 86 deletions(-) diff --git a/docker/routlin-dash/app/pages/overview/content.json b/docker/routlin-dash/app/pages/overview/content.json index 83e070b..bbd75e8 100644 --- a/docker/routlin-dash/app/pages/overview/content.json +++ b/docker/routlin-dash/app/pages/overview/content.json @@ -190,7 +190,7 @@ }, { "type": "card", - "label": "DNS Caching", + "label": "DNS Statistics", "client_requirement": "client_is_viewer+", "items": [ { @@ -198,86 +198,56 @@ "rows": [ { "cells": [ - { - "type": "grid_label", - "text": "Total Queries" - }, - { - "type": "grid_value", - "text": "%DNS_STAT_QUERIES%" - } + {"type": "grid_label", "text": "Total Queries"}, + {"type": "grid_value", "text": "%DNS_STAT_QUERIES%"} ] }, { "cells": [ - { - "type": "grid_label", - "text": "Cache Hits" - }, - { - "type": "grid_value", - "text": "%DNS_STAT_HITS% (%DNS_STAT_HIT_RATE% hit rate)" - } + {"type": "grid_label", "text": "Cache Hits"}, + {"type": "grid_value", "text": "%DNS_STAT_HITS% (%DNS_STAT_HIT_RATE% hit rate)"} ] }, { "cells": [ - { - "type": "grid_label", - "text": "Forwarded" - }, - { - "type": "grid_value", - "text": "%DNS_STAT_FORWARDED%" - } + {"type": "grid_label", "text": "Forwarded to Upstream"}, + {"type": "grid_value", "text": "%DNS_STAT_FORWARDED%"} ] }, { "cells": [ - { - "type": "grid_label", - "text": "Cache Capacity" - }, - { - "type": "grid_value", - "text": "%DNS_CACHE_SIZE% entries" - } + {"type": "grid_label", "text": "Authoritative Answers"}, + {"type": "grid_value", "text": "%DNS_STAT_AUTH%"} ] }, { "cells": [ - { - "type": "grid_label", - "text": "Authoritative Answers" - }, - { - "type": "grid_value", - "text": "%DNS_STAT_AUTH%" - } + {"type": "grid_label", "text": "TCP Peak"}, + {"type": "grid_value", "text": "%DNS_STAT_TCP_PEAK%"} ] }, { "cells": [ - { - "type": "grid_label", - "text": "TCP Connections Peak" - }, - { - "type": "grid_value", - "text": "%DNS_STAT_TCP_PEAK%" - } + {"type": "grid_label", "text": "Cache Capacity"}, + {"type": "grid_value", "text": "%DNS_CACHE_SIZE% entries"} ] }, { "cells": [ - { - "type": "grid_label", - "text": "DNS Providers" - }, - { - "type": "grid_value", - "text": "%OVERVIEW_UPSTREAM_SERVERS%" - } + {"type": "grid_label", "text": "DNS Providers"}, + {"type": "grid_value", "text": "%OVERVIEW_UPSTREAM_SERVERS%"} + ] + }, + { + "cells": [ + {"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%"} ] } ] diff --git a/docker/routlin-dash/app/pages/overview/view.py b/docker/routlin-dash/app/pages/overview/view.py index e0bcad1..c13da0d 100644 --- a/docker/routlin-dash/app/pages/overview/view.py +++ b/docker/routlin-dash/app/pages/overview/view.py @@ -1,38 +1,37 @@ -import re +import json import os import config_utils import factory from pages.ddns.view import public_ip_info 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': '-'} - out = factory.run("journalctl -u 'dnsmasq-routlin-*' -n 200 --no-pager 2>/dev/null") - for line in reversed(out.splitlines()): - if 'queries forwarded' in line: - m = re.search(r'queries forwarded (\d+)', line) - if m: - stats['forwarded'] = m.group(1) - m = re.search(r'queries answered locally (\d+)', line) - if m: - stats['hits'] = m.group(1) - fwd = int(stats['forwarded']) if stats['forwarded'] != '-' else 0 - hit = int(stats['hits']) if stats['hits'] != '-' else 0 - total = fwd + hit - stats['queries'] = str(total) if total else '-' - if total > 0: - stats['hit_rate'] = f'{hit / total * 100:.0f}%' - break - if 'auth answered' in line: - m = re.search(r'auth answered (\d+)', line) - if m and stats['auth'] == '-': - stats['auth'] = m.group(1) - if 'max TCP connections' in line: - m = re.search(r'max TCP connections (\d+)', line) - if m and stats['tcp_peak'] == '-': - stats['tcp_peak'] = m.group(1) - return stats + +def load_dns_metrics(): + empty = {'queries': '-', 'hits': '-', 'hit_rate': '-', 'forwarded': '-', + 'auth': '-', 'tcp_peak': '-', 'updated': '-', 'since': '-', 'servers': []} + try: + with open(METRICS_FILE) as f: + data = json.load(f) + t = data.get('totals', {}) + meta = data.get('metadata', {}) + fwd = t.get('queries_forwarded', 0) + hits = t.get('queries_answered_locally', 0) + total = fwd + hits + return { + 'queries': f'{total:,}' if total else '-', + 'hits': f'{hits:,}' if hits else '-', + 'hit_rate': f'{hits / total * 100:.0f}%' if total > 0 else '-', + 'forwarded': f'{fwd:,}' if fwd else '-', + 'auth': f'{t.get("queries_authoritative", 0):,}', + 'tcp_peak': str(t.get('tcp_hwm', 0)), + 'updated': meta.get('last_recorded', '-'), + 'since': meta.get('first_recorded', '-'), + 'servers': t.get('servers', []), + } + except Exception: + return empty def count_blocked_today(): @@ -69,7 +68,7 @@ def collect_tokens(cfg): vlan_names = [v.get('name', '') for v in vlans] net = cfg.get('network_interfaces', {}) dns = cfg.get('upstream_dns', {}) - dns_stats = get_dnsmasq_stats() + dns_stats = load_dns_metrics() ddns = factory.load_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_AUTH'] = dns_stats['auth'] 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