feat(labsim): live topology view with per-path latency
Some checks failed
CI/CD / lint (pull_request) Failing after 9s
CI/CD / test (pull_request) Failing after 9s
CI/CD / typecheck (pull_request) Failing after 24s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
Some checks failed
CI/CD / lint (pull_request) Failing after 9s
CI/CD / test (pull_request) Failing after 9s
CI/CD / typecheck (pull_request) Failing after 24s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
The Grafana heatmap of 1s and 0s said almost nothing, and the state timeline
was an unreadable pile of overlapping series labels. Replaced as the primary
view with a purpose-built page served by the exporter itself.
- Probe now captures ICMP RTT, exposed as labsim_rtt_ms{src,dst}. A path that
is up but slow is a different problem from one that is down, and a pass/fail
grid cannot show it.
- Exporter serves / (topology), /api/matrix (JSON) and /metrics.
- topology.html: node per VLAN in a ring, VyOS router in the centre because
every inter-VLAN packet really does traverse it, one line per pair coloured
green/red with the RTT on it. Hovering gives per-direction state. A node ring
goes red if anything to or from it is blocked. Side panels list blocked paths
and the slowest links. Refreshes every 5s, no dependencies.
Grafana stays for what it is actually good at — history of when a path flipped.
Label placement is deliberate: RTT captions sit ~32% along each edge with a
perpendicular nudge, because every diagonal of a 6-node mesh crosses the centre
and midpoint labels stack on the router node.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
This commit is contained in:
@@ -37,18 +37,25 @@ PROTOS = ("icmp", "tcp22", "tcp80")
|
||||
# Runs ON the guest. Keep it stdlib-only and quick — a hung probe delays the
|
||||
# whole sweep, so every check is hard-bounded by a timeout.
|
||||
PROBE = r'''
|
||||
import json, socket, subprocess, sys
|
||||
import json, re, socket, subprocess, sys
|
||||
targets = json.load(sys.stdin)
|
||||
out = {}
|
||||
for name, ip in targets.items():
|
||||
res = {}
|
||||
try:
|
||||
res["icmp"] = subprocess.run(
|
||||
["ping", "-c", "1", "-W", "1", ip],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=4
|
||||
).returncode == 0
|
||||
p = subprocess.run(["ping", "-c", "1", "-W", "1", ip],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=4)
|
||||
res["icmp"] = p.returncode == 0
|
||||
# RTT as well as pass/fail: a path that is up but slow is a different
|
||||
# problem from one that is down, and the grid alone cannot show it.
|
||||
res["rtt_ms"] = None
|
||||
if res["icmp"]:
|
||||
m = re.search(r"time[=<]\s*([0-9.]+)\s*ms", p.stdout.decode("utf-8", "replace"))
|
||||
if m:
|
||||
res["rtt_ms"] = float(m.group(1))
|
||||
except Exception:
|
||||
res["icmp"] = False
|
||||
res["rtt_ms"] = None
|
||||
for port in (22, 80):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(1.5)
|
||||
@@ -147,7 +154,7 @@ def render(vlans: list[dict], results: dict, prev: dict | None, protos: tuple[st
|
||||
print(row)
|
||||
|
||||
reach = sum(1 for s in results.values() if "__error__" not in s
|
||||
for d in s.values() for p in protos if d.get(p))
|
||||
for d in s.values() for p in protos if d.get(p) is True)
|
||||
total = sum(1 for s in results.values() if "__error__" not in s
|
||||
for _d in s.values() for _p in protos)
|
||||
print(f"\n reachable: {reach}/{total} "
|
||||
|
||||
Reference in New Issue
Block a user