From 8e5dda246c4684908613178034014e79537ebfc2 Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 9 Nov 2026 00:02:33 +0000 Subject: dashboard: messages as they come The node sent its messages to the page every 2 s (`messages` events, this node's and a subscribed node's), and this node's ran on the map from its counts, which come with the state: the map showed a message 2 to 4 s after it went, the messages of each 2 s spread evenly over the next. The message log now tells those waiting of each message logged (MessageLog::logged_after), and both streams send what was logged a tenth of a second after the first of it (MESSAGES_GATHERED): a busy node about ten events a second, a quiet one each message soon after it went. The page runs messages as their streams bring them, this node's from its own log too (with the addresses its PINGs went to), as far apart as they were sent; more than six on a link and way at once are shared out by kind over the moment, and after a gap only the last 2 s run. The counts still give the rates and what was delivered. --- src/api/dashboard/dashboard.html | 154 ++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 82 deletions(-) (limited to 'src/api/dashboard/dashboard.html') diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 05c233c..b7b45aa 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -2897,12 +2897,11 @@ function unpin(id) { graph.addEventListener("mousemove", event => { if (event.target === graph) hover(null); }); graph.addEventListener("mouseleave", () => hover(null)); -// --- Messages on the map: this node's traffic with each peer (`/peers`, `traffic`), -// as it grew since the last update, and each subscribed node's messages with its peers -// (`remoteMessagesArrived`), drawn as dots running along the link the way the messages -// went. Counts come with each update (every 2 s), so the dots are spread -// over the next 2 s, a few at most per link and way: a picture of the traffic, not a -// record of it. As dots, all at one speed (`flowSpeed`), so over a longer link they +// --- Messages on the map: this node's messages with its peers (`ownMessagesArrived`) +// and each subscribed node's (`remoteMessagesArrived`), as their streams bring them, +// soon after they went, drawn as dots running along the link the way the messages +// went, as far apart as they were sent, a few at most per link and way of a batch: a +// picture of the traffic, not a record of it. As dots, all at one speed (`flowSpeed`), so over a longer link they // arrive later; as flashes, each crosses its link as fast as the message did (half the // link's round trip) and leaves an afterglow that draws back into where it went // (`FLASH_GLOW_MS`). Dots are kept above everything else on the map (`flows`, after @@ -2980,9 +2979,6 @@ function oneWayMs(from, to) { return FLASH_UNKNOWN_MS; } let lastTraffic = null; -// When the last update's traffic came, by the wall clock (it runs on while the computer -// sleeps, unlike `performance.now()` on some systems). -let lastTrafficAt = 0; const flows = []; let flowFrame = null; // Messages and bytes by kind, of a peer's `traffic.sent` or `.received`. @@ -3058,17 +3054,12 @@ function dotsFor(kinds, total) { } return dots; } -// The messages exchanged with each peer since the last update, on the map (`flows`, if -// shown). +// This node's traffic with each peer, as the update brings it: its rates, and what its +// peers should have received (`deliveredHere`). The map runs messages from the message +// logs instead, as they come (`runMessages`). function trafficArrived() { if (!state) return; - const self = state.node.id, now = performance.now(); - // Only for an update in its time: not the first after a gap (the computer slept, the - // stream stalled), whose counts grew over all of it, nor those queued meanwhile and - // arriving at once with it, each of which would add its dots to the same 2 s. - const wall = Date.now(), gap = wall - lastTrafficAt; - lastTrafficAt = wall; - const dots = dotsNow(gap); + const self = state.node.id; const current = new Map(); for (const [id, peer] of Object.entries(state.peers.peers)) { if (peer.traffic) { @@ -3076,53 +3067,76 @@ function trafficArrived() { received: messages(peer.traffic.received) }); } } - // What a subscribed node sent this one runs from its own messages, as it left - // (`remoteMessagesArrived`), not from what this one counted received. - if (lastTraffic && dots) addFlows(self, current, lastTraffic, - (way, id) => way === "received" && logging(id)); if (lastTraffic) deliveredHere(self, current, lastTraffic); measureRates(current); lastTraffic = current; } -// Dots for `node`'s traffic with each peer, as it grew from `before` to `current` (by -// peer: `sent`, `received`, each messages and bytes by kind), spread over the next 2 s; -// but where `skip(way, peer)` (drawn already from another's). -function addFlows(node, current, before, skip) { +// Whether messages run on the map now: shown, and seen. +function flowsNow() { + return flowsShown && !calm.matches && !document.hidden && + !document.getElementById("map").classList.contains("collapsed"); +} +// Messages as a stream brought them, this node's or a subscribed node's (each named by +// both ends, `from` and `to`), run along their links as they come: started as far +// apart as they were sent (`at`, by the clock of the node that logged them, so only +// against each other), `FLOWS_AT_MOST` at most per link and way of a batch, shared out +// by kind (`dotsFor`) where more came. Of a batch that spans more than +// `FLOW_SPREAD_MS` (one after a gap: the computer slept, the stream stalled), only +// those of its last `FLOW_SPREAD_MS`. +function runMessages(list) { + if (!flowsNow() || list.length === 0) return; const now = performance.now(); - for (const [id, counts] of current) { - const was = before.get(id); - if (!was) continue; - for (const [way, from, to] of [["sent", node, id], ["received", id, node]]) { - if (skip(way, id)) continue; - const kinds = {}; - let total = 0; - for (const [kind, count] of Object.entries(counts[way])) { - const earlier = was[way][kind] || { messages: 0, bytes: 0 }; - const grown = Math.max(0, count.messages - earlier.messages); - if (grown > 0) { - kinds[kind] = { messages: grown, bytes: Math.max(0, count.bytes - earlier.bytes) }; - total += grown; - } + const latest = Math.max(...list.map(message => message.at)); + const recent = list.filter(message => latest - message.at <= FLOW_SPREAD_MS); + const first = Math.min(...recent.map(message => message.at)); + const links = new Map(); + for (const message of recent) { + const transport = schemeOf(message.address); + const key = message.from + " " + message.to + " " + (transport || ""); + const link = links.get(key) || + { from: message.from, to: message.to, transport, messages: [] }; + link.messages.push(message); + links.set(key, link); + } + for (const { from, to, transport, messages: sent } of links.values()) { + const start = message => now + (message.at - first); + if (sent.length <= FLOWS_AT_MOST) { + for (const message of sent) { + flows.push({ from, to, transport, start: start(message), kind: message.counted, + r: dotRadius(message.bytes) }); } - if (total === 0) continue; - const shown = dotsFor(kinds, total); - shown.forEach((dot, i) => { - flows.push({ from, to, start: now + i * FLOW_SPREAD_MS / shown.length, - kind: dot.kind, r: dotRadius(dot.bytes) }); - }); + continue; + } + const kinds = {}; + for (const message of sent) { + const kind = kinds[message.counted] || (kinds[message.counted] = { messages: 0, bytes: 0 }); + kind.messages += 1; + kind.bytes += message.bytes; } + const span = Math.max(...sent.map(message => message.at)) - Math.min(...sent.map(message => message.at)); + const from0 = start(sent.reduce((a, b) => (a.at <= b.at ? a : b))); + const shown = dotsFor(kinds, sent.length); + shown.forEach((dot, i) => { + flows.push({ from, to, transport, start: from0 + i * span / shown.length, + kind: dot.kind, r: dotRadius(dot.bytes) }); + }); } if (flows.length > 0 && flowFrame === null) flowFrame = requestAnimationFrame(drawFlows); } -// Whether dots are drawn now, for an update whose traffic came `gap` ms after the last: -// in its time (not the first after a gap, the computer asleep or the stream stalled, -// whose counts grew over all of it), shown, and seen. -function dotsNow(gap) { - return gap >= FLOW_SPREAD_MS / 4 && gap <= FLOW_SPREAD_MS * 2.5 && flowsShown && - !calm.matches && !document.hidden && - !document.getElementById("map").classList.contains("collapsed"); +// This node's messages, as its stream brings them (`messages` events): listed in the +// console, and run along its links, those it sent, and those it received from a node +// whose messages the page doesn't read (a subscribed node's run from it, as they left). +function ownMessagesArrived(list) { + messagesArrived(list); + if (!state) return; + const self = state.node.id; + runMessages(list.filter(message => message.way === "sent" || + (message.way === "received" && !logging(message.peer))) + .map(message => ({ ...message, + from: message.way === "sent" ? self : message.peer, + to: message.way === "sent" ? message.peer : self }))); } -// A subscribed node's messages (`follow`), those of a tick: listed in the console and +// A subscribed node's messages (`follow`), as they come: listed in the console and // run along its links, as this node's own. Its messages with this node are this node's // to show, and what it received from another subscribed node that one's (as what it // sent): each message once. Each named by both ends (`from`, `to`) and the node that @@ -3130,10 +3144,8 @@ function dotsNow(gap) { function remoteMessagesArrived(id, entry, list) { if (!state) return; const self = state.node.id; - const wall = Date.now(), gap = wall - (entry.messagesAt || 0); - entry.messagesAt = wall; - // A tick's messages come whole, those since the last, up to the latest 500: one - // missing between (a busy tick) leaves what was delivered unknown for now. + // Its messages come whole, those since the last event, up to the latest 500: one + // missing between (a busy moment) leaves what was delivered unknown for now. if (list.length > 0) { if (entry.lastSeq !== undefined && list[0].seq !== entry.lastSeq + 1) forgetDeliveries(id); entry.lastSeq = list[list.length - 1].seq; @@ -3157,29 +3169,7 @@ function remoteMessagesArrived(id, entry, list) { } } checkDeliveries(); - if (dotsNow(gap) && running.length > 0) { - const now = performance.now(); - const links = new Map(); - for (const message of running) { - const transport = schemeOf(message.address); - const key = message.from + " " + message.to + " " + (transport || ""); - const link = links.get(key) || - { from: message.from, to: message.to, transport, kinds: {}, total: 0 }; - const kind = link.kinds[message.counted] || (link.kinds[message.counted] = { messages: 0, bytes: 0 }); - kind.messages += 1; - kind.bytes += message.bytes; - link.total += 1; - links.set(key, link); - } - for (const { from, to, transport, kinds, total } of links.values()) { - const shown = dotsFor(kinds, total); - shown.forEach((dot, i) => { - flows.push({ from, to, transport, start: now + i * FLOW_SPREAD_MS / shown.length, - kind: dot.kind, r: dotRadius(dot.bytes) }); - }); - } - if (flows.length > 0 && flowFrame === null) flowFrame = requestAnimationFrame(drawFlows); - } + runMessages(running); if (kept.length > 0) messagesArrived(kept); } // Whether the page reads `id`'s messages one by one: a subscribed node's, while they @@ -3646,7 +3636,7 @@ function connect() { remakesArrived(); }); events.addEventListener("document", event => { pulse(); documentChanged(JSON.parse(event.data)); }); - events.addEventListener("messages", event => messagesArrived(JSON.parse(event.data))); + events.addEventListener("messages", event => ownMessagesArrived(JSON.parse(event.data))); // The node stops: it says so, and ends the stream. Back once it is. events.addEventListener("offline", () => { events.close(); live = false; -- cgit v1.3.1