diff options
| author | tv <tv@krebsco.de> | 2026-11-04 22:15:50 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-04 22:15:50 +0100 |
| commit | f8da04220992bd64caa66522b3a689eb1b03fbbc (patch) | |
| tree | 06814148a92f88d9e8ba0a1148111814c6bcea2e | |
| parent | f137a43b44684959a5e1de1f1e54566d8995926f (diff) | |
dashboard: hop delays
| -rw-r--r-- | docs/dashboard.md | 1 | ||||
| -rw-r--r-- | src/api/dashboard/dashboard.html | 38 |
2 files changed, 36 insertions, 3 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md index 27c3a1e..98f4077 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -213,6 +213,7 @@ Every peer this node knows, with: | seen | How long ago this node last heard from it. | | via | The address this node sends to it at, and whether `direct` or `relayed`. | | rtt | The round trip on that path. | +| hop | How long its sync messages take to come, by its clock and this node's, its clock offset taken off (`hopDelay` in [`measurements.md`](measurements.md)): this minute's mean, then the last 15 minutes' means as bars, oldest first (a minute without messages blank). Good to half the round trip, but the trend exactly: bars growing mean the link, or a queue on it, gets slower. All figures in the tooltip. | | clock | How far its clock is ahead of this node's (behind: `−`), give or take (in the tooltip); in orange beyond `--clock-skew-warn`, for sure ([`measurements.md`](measurements.md), Clocks). | | neighbour | Whether this node keeps contact with it (HELLOs, keepalives, sync); others it only answers. | | reaches | How many members besides this node it reaches, as it said ([`peers.md`](peers.md)); `(+n)`: of those, how many this node doesn't reach itself, which it is a way to. By name in the tooltip, those marked `+`. Blank until it said so in this session. | diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 94e3dfb..876c386 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -32,6 +32,8 @@ header h1 { font-size: 16px; margin: 0; } #connection { margin-left: auto; color: var(--muted); } #clock-warning, #peers .clock-off { color: var(--warn); } + /* A minute's bar each, the last 15: a link's trend at a glance. */ + #peers .spark { font-family: ui-monospace, monospace; letter-spacing: 0; opacity: 0.7; } #connection.live { color: var(--ok); } #connection.down { color: var(--bad); } /* A dot in the status's colour that breathes once with every event. */ @@ -429,9 +431,11 @@ function renderPeers() { const table = document.getElementById("peers"); const headers = { reaches: "How many members besides this node it reaches (an active " + "session with each), as it said; +: those of them this node doesn't reach itself (it " + - "is a way to them)" }; - const rows = [el("tr", {}, ...["peer", "status", "seen", "via", "rtt", "clock", "neighbour", - "reaches", "same epoch"] + "is a way to them)", hop: "How long its sync messages take to come, by its clock and " + + "this node's (its offset taken off): this minute's mean, and the last 15 minutes' as " + + "a line; good to half the round trip, but the trend exactly" }; + const rows = [el("tr", {}, ...["peer", "status", "seen", "via", "rtt", "hop", "clock", + "neighbour", "reaches", "same epoch"] .map(h => el("th", { title: headers[h] || "" }, h)))]; // Whom this node reaches, to tell those a peer reaches and it doesn't. const ours = new Set(state.peers.localReaches || []); @@ -450,6 +454,7 @@ function renderPeers() { el("td", {}, age(aged("lastSeenSecsAgo", peer.lastSeenSecsAgo))), el("td", { class: "id" }, via), el("td", {}, rtt), + hopCell(peer.hopDelay), // Its clock against this node's, flagged beyond the limit (`--clock-skew-warn`). el("td", { class: clockOff(peer.clock) ? "clock-off" : "", title: peer.clock ? "ahead of this node's by " + peer.clock.offsetMs + " ms, give or take " + @@ -469,6 +474,33 @@ function renderPeers() { ? (rows[8].offsetTop + rows[8].offsetHeight) + "px" : ""; } +// How long a peer's sync messages take to come (`hopDelay`): the latest minute's mean, +// and the last 15 minutes' as a line of bars (a minute without messages blank), each +// figure in the tooltip. +function hopCell(hop) { + if (!hop) return el("td", { title: "no sync message from it yet while its clock was known" }, ""); + const minutes = hop.byMinuteMs || []; + const known = minutes.filter(ms => typeof ms === "number"); + const latest = known.length ? known[known.length - 1] : null; + const low = Math.min(...known), high = Math.max(...known); + const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588"; + const line = minutes.map(ms => typeof ms !== "number" ? " " + : bars[high > low ? Math.round((ms - low) / (high - low) * (bars.length - 1)) : 0]).join(""); + const within = ms => ms === null || ms === undefined ? "beyond the last bucket" : "within " + ms + " ms"; + const title = [ + hop.count + " messages since this node started, mean " + hop.meanMs + " ms", + "half " + within(hop.halfWithinMs) + ", 99% " + within(hop.p99WithinMs), + hop.halfRttMs !== null && hop.halfRttMs !== undefined + ? "half the round trip: " + hop.halfRttMs + " ms (beyond it: waiting in queues, or the clock offset's error)" + : "no recent round trip", + hop.belowZero ? hop.belowZero + " took less than nothing by the clocks (the offset's error)" : "", + "the last 15 minutes' means, oldest first: " + + minutes.map(ms => typeof ms === "number" ? ms : "–").join(", "), + ].filter(Boolean).join("\n"); + return el("td", { title }, + latest === null ? "" : latest + " ms ", el("span", { class: "spark" }, line)); +} + // Whom a peer reaches (`reaches`, as it said; null: it hasn't said in this session), as // a count, and those of them this node doesn't reach (`ours`), marked: the peer is a way // to them. Each by name in the tooltip. |
