aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/dashboard/dashboard.html137
1 files changed, 126 insertions, 11 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index d872e9b..05c233c 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -3076,7 +3076,11 @@ function trafficArrived() {
received: messages(peer.traffic.received) });
}
}
- if (lastTraffic && dots) addFlows(self, current, lastTraffic, () => false);
+ // 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;
}
@@ -3128,20 +3132,35 @@ function remoteMessagesArrived(id, entry, list) {
const self = state.node.id;
const wall = Date.now(), gap = wall - (entry.messagesAt || 0);
entry.messagesAt = wall;
- const kept = [];
+ // 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.
+ if (list.length > 0) {
+ if (entry.lastSeq !== undefined && list[0].seq !== entry.lastSeq + 1) forgetDeliveries(id);
+ entry.lastSeq = list[list.length - 1].seq;
+ }
+ // For the console: its messages with others than this node (this node's own log has
+ // those), each message once. For the map: each message as it left, from its sender
+ // where the page reads the sender's messages (this node's own counts, or a
+ // subscribed node's), so it runs the way it went; else as it arrived.
+ const kept = [], running = [];
for (const message of list) {
- if (message.way === "refused" || message.peer === self) continue;
- if (message.way === "received" && message.peer !== id && subscriptions.has(message.peer) &&
- (subscribed.get(message.peer) || {}).messagesLive) continue;
+ if (message.way === "refused") continue;
const [from, to] = message.way === "sent" ? [id, message.peer] : [message.peer, id];
- kept.push({ ...message, seq: id + ":" + message.seq, node: id, from, to,
- related: message.related ? id + ":" + message.related : null });
+ const made = { ...message, seq: id + ":" + message.seq, node: id, from, to,
+ related: message.related ? id + ":" + message.related : null };
+ if (message.way === "sent") noteDelivery(from, to, message.counted, "sent");
+ else noteDelivery(from, to, message.counted, "got");
+ if (message.peer !== self && !(message.way === "received" && message.peer !== id &&
+ logging(message.peer))) kept.push(made);
+ if (message.way === "sent" || !(message.peer === self || logging(message.peer))) {
+ running.push(made);
+ }
}
- if (kept.length === 0) return;
- if (dotsNow(gap)) {
+ checkDeliveries();
+ if (dotsNow(gap) && running.length > 0) {
const now = performance.now();
const links = new Map();
- for (const message of kept) {
+ for (const message of running) {
const transport = schemeOf(message.address);
const key = message.from + " " + message.to + " " + (transport || "");
const link = links.get(key) ||
@@ -3161,7 +3180,103 @@ function remoteMessagesArrived(id, entry, list) {
}
if (flows.length > 0 && flowFrame === null) flowFrame = requestAnimationFrame(drawFlows);
}
- messagesArrived(kept);
+ if (kept.length > 0) messagesArrived(kept);
+}
+// Whether the page reads `id`'s messages one by one: a subscribed node's, while they
+// come.
+const logging = id => subscriptions.has(id) && (subscribed.get(id) || {}).messagesLive === true;
+
+// --- Messages that didn't arrive: where the page reads both ends of a link (this node's
+// counts, or a subscribed node's messages), what one sent the other should count
+// received. Sent but not received within `DELIVERY_GRACE_MS` (both come every 2 s, each
+// on its own tick), the link breathes red (`breatheLink`). By message, of every kind but
+// those the paths and identity layers count under the address rather than the node
+// (this node's counts leave them out). Kept from when both ends are read; forgotten
+// when either isn't any more, or a tick of either came short. ---
+const DELIVERY_GRACE_MS = 5000, UNCOUNTED = new Set(["paths", "identity"]);
+// By `from to`: `sent` and `got`, each a queue of [when, how many], the oldest first.
+const deliveries = new Map();
+const readHere = id => state !== null && (id === state.node.id || logging(id));
+function noteDelivery(from, to, kind, which, count = 1) {
+ if (UNCOUNTED.has(kind) || count <= 0) return;
+ const key = from + " " + to;
+ if (!flowsShown || !readHere(from) || !readHere(to)) { deliveries.delete(key); return; }
+ const record = deliveries.get(key) || { from, to, sent: [], got: [] };
+ record[which].push([performance.now(), count]);
+ deliveries.set(key, record);
+}
+function forgetDeliveries(id) {
+ for (const [key, record] of deliveries) {
+ if (record.from === id || record.to === id) deliveries.delete(key);
+ }
+}
+// This node's counts, as they grew since the last update: by peer, what it sent and
+// received, each kind counted.
+function deliveredHere(self, current, before) {
+ for (const [id, counts] of current) {
+ const was = before.get(id);
+ if (!was) continue;
+ for (const [way, from, to, which] of [["sent", self, id, "sent"], ["received", id, self, "got"]]) {
+ for (const [kind, count] of Object.entries(counts[way])) {
+ const earlier = was[way][kind] || { messages: 0 };
+ noteDelivery(from, to, kind, which, count.messages - earlier.messages);
+ }
+ }
+ }
+ checkDeliveries();
+}
+// Received messages settle the oldest sent first; what was sent longer ago than the
+// grace and is still unsettled didn't arrive. Received with nothing sent to settle (the
+// sender's tick not in yet) waits as long again, then goes.
+function checkDeliveries() {
+ const now = performance.now();
+ for (const [key, record] of deliveries) {
+ const { sent, got } = record;
+ while (sent.length > 0 && got.length > 0) {
+ const settled = Math.min(sent[0][1], got[0][1]);
+ sent[0][1] -= settled;
+ got[0][1] -= settled;
+ if (sent[0][1] === 0) sent.shift();
+ if (got[0][1] === 0) got.shift();
+ }
+ let lost = 0;
+ while (sent.length > 0 && sent[0][0] <= now - DELIVERY_GRACE_MS) lost += sent.shift()[1];
+ while (got.length > 0 && got[0][0] <= now - 2 * DELIVERY_GRACE_MS) got.shift();
+ if (lost > 0) breatheLink(record.from, record.to, lost);
+ if (sent.length === 0 && got.length === 0) deliveries.delete(key);
+ }
+}
+// A red halo along the link `from`–`to` (its line in use), in and out once: `lost`
+// messages sent over it didn't arrive. Below the flashes' layer's other breaths.
+const LOST_MS = 1200;
+function breatheLink(from, to, lost) {
+ if (document.hidden || !flowsShown) return;
+ const layer = document.getElementById("flashes");
+ const halo = svg("path", { fill: "none", stroke: "var(--bad)", "stroke-width": 10,
+ "stroke-linecap": "round", "pointer-events": "none" });
+ const title = svg("title");
+ title.textContent = lost + (lost === 1 ? " message " : " messages ") + name(from) +
+ " sent " + name(to) + " didn't arrive";
+ halo.append(title);
+ layer.prepend(halo);
+ const started = performance.now();
+ const step = now => {
+ const line = route(from, to, null);
+ const t = Math.min(1, Math.max(0, (now - started) / LOST_MS));
+ if (!line || t >= 1) {
+ halo.remove();
+ return;
+ }
+ const points = [];
+ for (let k = 0; k <= 12; k++) {
+ const point = line.at(k / 12);
+ points.push(point.x.toFixed(1) + " " + point.y.toFixed(1));
+ }
+ halo.setAttribute("d", "M" + points.join("L"));
+ halo.setAttribute("stroke-opacity", (.5 * Math.sin(Math.PI * t)).toFixed(2));
+ requestAnimationFrame(step);
+ };
+ requestAnimationFrame(step);
}
// --- The console's rates: messages and bytes per second sent and received, per peer