diff options
| -rw-r--r-- | docs/openapi.yaml | 5 | ||||
| -rw-r--r-- | src/api/dashboard/dashboard.html | 23 | ||||
| -rw-r--r-- | src/swarm/skew.rs | 6 |
3 files changed, 30 insertions, 4 deletions
diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 6eab2d1..a5780e3 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1805,7 +1805,7 @@ components: clock: type: object description: This node's clock against its active peers' (`measurements.md`, Clocks). - required: [peers, offsetMs, behind, ahead, off] + required: [peers, offsetMs, behind, ahead, off, limitMs] properties: peers: type: integer @@ -1822,6 +1822,9 @@ components: off: type: boolean description: Ours differs from more than half of theirs that way. + limitMs: + type: integer + description: --clock-skew-warn. LockWaits: type: object diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 03fa177..153d7cb 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -25,6 +25,7 @@ border-bottom: 1px solid var(--line); flex-wrap: wrap; } header h1 { font-size: 16px; margin: 0; } #connection { margin-left: auto; color: var(--muted); } + #clock-warning, #peers .clock-off { color: var(--warn); } #connection.live { color: var(--ok); } #connection.down { color: var(--bad); } /* A dot in the status's colour that breathes once with every event. */ @@ -137,6 +138,7 @@ <header> <h1>continuum</h1> <span id="node" class="id"></span> + <span id="clock-warning"></span> <span id="connection"><span id="dot"></span><span id="connection-text">connecting…</span></span> </header> <main> @@ -220,6 +222,13 @@ function name(id) { const member = state.graph.members[id]; return (member && member.alias) || (state.peers.peers[id] || {}).alias || short(id); } +// Milliseconds as signed seconds: "+5.0 s". +const seconds = ms => (ms < 0 ? "−" : "+") + (Math.abs(ms) / 1000).toFixed(1) + " s"; +// Whether `clock` (a peer's, `/peers`) differs from this node's beyond the limit for sure. +function clockOff(clock) { + const limit = (state.stats.clock || {}).limitMs || 0; + return !!clock && limit > 0 && Math.abs(clock.offsetMs) > clock.withinMs + limit; +} function age(secs) { if (secs === null || secs === undefined) return ""; if (secs < 120) return secs + " s"; @@ -248,7 +257,8 @@ function focus(id) { // --- Peers --- function renderPeers() { const table = document.getElementById("peers"); - const rows = [el("tr", {}, ...["peer", "status", "seen", "via", "rtt", "neighbour", "same epoch"] + const rows = [el("tr", {}, ...["peer", "status", "seen", "via", "rtt", "clock", "neighbour", + "same epoch"] .map(h => el("th", {}, h)))]; for (const [id, peer] of Object.entries(state.peers.peers)) { const destination = peer.destination; @@ -265,6 +275,10 @@ function renderPeers() { el("td", {}, age(aged("lastSeenSecsAgo", peer.lastSeenSecsAgo))), el("td", { class: "id" }, via), el("td", {}, rtt), + // 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 " + + peer.clock.withinMs + " ms" : "" }, peer.clock ? seconds(peer.clock.offsetMs) : ""), el("td", {}, peer.neighbour ? "yes" : ""), el("td", {}, peer.sameEpoch === null || peer.sameEpoch === undefined ? "" : peer.sameEpoch ? "yes" : "no")); @@ -641,8 +655,10 @@ function renderGraph() { fill: member.self ? "var(--self)" : reported ? "var(--fg)" : "var(--panel)", stroke: member.self ? "var(--self)" : dormant ? "var(--bad)" : "var(--fg)", "stroke-width": dormant ? 2.5 : 1.5 }, pressed); + const clock = (state.peers.peers[id] || {}).clock; tooltip(circle, id + (reported ? "" : " (no links reported)") + - (dormant ? " (dormant)" : "")); + (dormant ? " (dormant)" : "") + + (clockOff(clock) ? " (clock " + seconds(clock.offsetMs) + " off this node's)" : "")); const label = drawing("name " + id, "text", upright({ x: p.x + 10, y: p.y + 4, "font-size": 12, fill: "var(--fg)", cursor: "pointer" }), pressed); text(label, name(id)); @@ -994,6 +1010,9 @@ function render(ages = false) { if (!state) return; document.getElementById("node").textContent = (state.node.alias ? state.node.alias + " · " : "") + state.node.id; + const skew = state.stats.clock; + document.getElementById("clock-warning").textContent = skew && skew.off + ? "clock " + seconds(skew.offsetMs) + " off its peers'" : ""; held = false; const boxes = [["peers-box", renderPeers], ["figures-box", renderStats]]; // Its ages are in tooltips only: drawn on events, not every second. diff --git a/src/swarm/skew.rs b/src/swarm/skew.rs index af11e5e..f5d9858 100644 --- a/src/swarm/skew.rs +++ b/src/swarm/skew.rs @@ -34,6 +34,8 @@ pub(crate) struct Skew { /// Whether ours is off: more than half of them differ from it beyond the limit, /// the same way. Never with a limit of 0. pub(crate) off: bool, + /// The limit (`--clock-skew-warn`). + pub(crate) limit_ms: u64, } /// Ours against the peers' clocks `offsets` (each ahead of ours), with `limit`. @@ -59,6 +61,7 @@ pub(crate) fn of(offsets: &[ClockOffset], limit: Duration) -> Skew { behind, ahead, off: limit_ms > 0 && (ahead * 2 > peers || behind * 2 > peers), + limit_ms, } } @@ -143,6 +146,7 @@ mod tests { // A limit of 0: never. assert!(!of(&[offset(60_000, 10)], Duration::ZERO).off); // No peers: nothing known. - assert_eq!(of(&[], limit), Skew::default()); + let none = of(&[], limit); + assert_eq!((none.peers, none.offset_ms, none.off), (0, None, false)); } } |
