diff options
| author | tv <tv@krebsco.de> | 2026-11-02 20:02:23 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-02 20:02:23 +0100 |
| commit | d29372f452aab3b505278a887822d4a3455e88a2 (patch) | |
| tree | 003719f8879f2c8ec059d1af9093d5ace0198006 | |
| parent | eaf680d539e0c6545bc6bd517428ecc71244d6f3 (diff) | |
dashboard: add B/s
| -rw-r--r-- | docs/dashboard.md | 5 | ||||
| -rw-r--r-- | src/api/dashboard/dashboard.html | 65 |
2 files changed, 47 insertions, 23 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md index 4e57c21..2d55db1 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -262,9 +262,10 @@ two other nodes, a node this one exchanges nothing with), it says so. Whenever w shows changes (turned on or off, another focus, or none), the console goes to the newest message and follows from there. The choice is kept in the browser. -After it, `msg/s` gives the messages per second sent and received, as load averages +At the right of the console's title, `msg/s` gives the messages per second sent and received, as load averages do: over the last 1, 5 and 15 minutes (exponentially weighted), since the page was -opened, from the message counts each update brings. With the filter on, they are those +opened, from the message counts each update brings; below it, the bytes per second +alike, in `B/s`, `KiB/s` or `MiB/s`, whichever the largest of the three needs. With the filter on, they are those with the node or over the link in focus; `–` where there are none. ## While you read diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 0ae99f4..ee22b3e 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -79,9 +79,16 @@ margin-left: 1em; cursor: pointer; } section h2 label.choice input { vertical-align: -2px; margin: 0 3px 0 0; } section.collapsed h2 label.choice { display: none; } - /* The console's rates, after its filter. */ - section h2 .rates { font-size: 12px; text-transform: none; letter-spacing: 0; + /* The console's rates, at the right of its title: messages and bytes per second, a + row each, small enough for both to fit the title's line, centred on it. Columns as + wide as their widest content will be, so the numbers stay put as they change. */ + section h2 .rates { float: right; display: grid; + grid-template-columns: repeat(3, minmax(5ch, auto)) minmax(5.5ch, auto); + column-gap: 5px; height: 18px; align-content: center; + font-size: 9px; line-height: 9px; text-transform: none; letter-spacing: 0; margin-left: 1em; font-variant-numeric: tabular-nums; } + section h2 .rates span { text-align: right; } + section h2 .rates span.unit { text-align: left; } section.collapsed h2 .rates { display: none; } /* The right column dragged off to the side: the map takes the width. */ @media (min-width: 901px) { @@ -262,7 +269,7 @@ <div id="column-hint" class="note">Drag the space left of these boxes to the right to hide the column.</div> <section id="console-box"> - <h2>Console <label class="choice" title="Only the messages with the node or over the link in focus (clicked on the map or in the peers); all while nothing is"><input type="checkbox" id="console-filter">filter selected</label><span id="console-rates" class="rates" title="Messages per second sent and received, as load averages are: over the last 1, 5 and 15 minutes (exponentially weighted), since the page was opened; with the filter, those with the node or over the link in focus">msg/s –</span></h2> + <h2>Console <label class="choice" title="Only the messages with the node or over the link in focus (clicked on the map or in the peers); all while nothing is"><input type="checkbox" id="console-filter">filter selected</label><span id="console-rates" class="rates" title="Messages and bytes per second sent and received, as load averages are: over the last 1, 5 and 15 minutes (exponentially weighted), since the page was opened; with the filter, those with the node or over the link in focus"></span></h2> <div class="scroll" id="console-scroll"><div id="console" class="console"></div> <div id="console-none" class="note" hidden>No information available</div></div> </section> @@ -1809,35 +1816,41 @@ function trafficArrived() { if (flows.length > 0 && flowFrame === null) flowFrame = requestAnimationFrame(drawFlows); } -// --- The console's rates: messages per second sent and received, per peer and in all, -// averaged as load averages are, over 1, 5 and 15 minutes (each sample weighs in by -// how long ago it was, `e^(-age/window)`), from the counts each update brings. The -// first sample is taken as it is, so they needn't climb from 0 first. --- +// --- The console's rates: messages and bytes per second sent and received, per peer +// and in all, averaged as load averages are, over 1, 5 and 15 minutes (each sample +// weighs in by how long ago it was, `e^(-age/window)`), from the counts each update +// brings. The first sample is taken as it is, so they needn't climb from 0 first. --- const RATE_WINDOWS_S = [60, 300, 900]; -// By peer, and in all (`null`): the averages; and when the counts were last taken. +// By peer, and in all (`null`): the averages (`messages`, `bytes`: per window); and when +// the counts were last taken. const rates = new Map(); let ratesAt = null; function measureRates(current) { const now = performance.now(); if (lastTraffic !== null && ratesAt !== null && now > ratesAt) { const secs = (now - ratesAt) / 1000; - const total = (counts, way) => Object.values(counts[way]) - .reduce((sum, count) => sum + count.messages, 0); - let all = 0; - const sample = (key, rate) => { + // Of `what` (`messages`, `bytes`), sent and received, by how much each grew. + const grown = (counts, before, what) => ["sent", "received"].reduce((sum, way) => { + const total = of => Object.values(of[way]).reduce((sum, count) => sum + count[what], 0); + return sum + Math.max(0, total(counts) - total(before)); + }, 0); + const average = (known, rate) => known ? known.map((average, i) => + rate + (average - rate) * Math.exp(-secs / RATE_WINDOWS_S[i])) : RATE_WINDOWS_S.map(() => rate); + const sample = (key, messages, bytes) => { const known = rates.get(key); - rates.set(key, known ? known.map((average, i) => - rate + (average - rate) * Math.exp(-secs / RATE_WINDOWS_S[i])) : RATE_WINDOWS_S.map(() => rate)); + rates.set(key, { messages: average(known && known.messages, messages / secs), + bytes: average(known && known.bytes, bytes / secs) }); }; + let messagesInAll = 0, bytesInAll = 0; for (const [id, counts] of current) { const before = lastTraffic.get(id); if (!before) continue; - const grown = Math.max(0, total(counts, "sent") - total(before, "sent")) + - Math.max(0, total(counts, "received") - total(before, "received")); - sample(id, grown / secs); - all += grown; + const messages = grown(counts, before, "messages"), bytes = grown(counts, before, "bytes"); + sample(id, messages, bytes); + messagesInAll += messages; + bytesInAll += bytes; } - sample(null, all / secs); + sample(null, messagesInAll, bytesInAll); // Peers gone from the list: their averages too. for (const key of [...rates.keys()]) if (key !== null && !current.has(key)) rates.delete(key); } @@ -1851,8 +1864,18 @@ function showRates() { const peer = filtering ? peerOf(focused) : null; const shown = filtering && peer === null ? undefined : rates.get(peer); const format = rate => rate < 100 ? rate.toFixed(2) : String(Math.round(rate)); - text(document.getElementById("console-rates"), - "msg/s " + (shown ? shown.map(format).join(" ") : "–")); + // Bytes in the unit the largest of the three needs, all three alike. + const largest = shown ? Math.max(...shown.bytes) : 0; + const [unit, per] = largest < 1024 ? ["B/s", 1] : largest < 1048576 ? ["KiB/s", 1024] + : ["MiB/s", 1048576]; + const row = (values, unit) => [...(values ? values.map(format) : ["–", "", ""]), unit]; + const cells = [...row(shown && shown.messages, "msg/s"), + ...row(shown && shown.bytes.map(bytes => bytes / per), unit)]; + const box = document.getElementById("console-rates"); + if (box.childElementCount !== cells.length) { + box.replaceChildren(...cells.map((_, i) => el("span", { class: i % 4 === 3 ? "unit" : "" }))); + } + cells.forEach((cell, i) => text(box.children[i], cell)); } // --- Console: the messages this node exchanged with its peers, one by one, as the |
