From 8c15dc1205d879591318cf365e15b014477329c1 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 27 Oct 2026 13:56:24 +0100 Subject: network: traffic, its busiest processes and the IPs on the page The rx/tx graph, the LAN address and the public IP with its pin move from the perf page's NET card to the network page, where they belong; the NET card is gone. Perf still samples and owns all of them: the graph opens on the last minute of history, and a public IP that differs from the pin still lights the perf tile's dot. Below the graph, the five busiest processes. nethogs is installed but cannot open any interface unprivileged, so the rates come from `ss -tinpH`: per-socket byte counters diffed every 2s while the page is open, summed by process name. That counts TCP only, and only the user's own processes, so QUIC and system services are missing. The list is held at five rows tall so the IP lines under it never move. Perf.js gains sockets() and netProcs(), with tests in test_perf.js. Co-Authored-By: Claude Opus 5.5 --- desktop/modules/perf/Perf.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'desktop/modules/perf/Perf.js') diff --git a/desktop/modules/perf/Perf.js b/desktop/modules/perf/Perf.js index 8294e94..03fd484 100644 --- a/desktop/modules/perf/Perf.js +++ b/desktop/modules/perf/Perf.js @@ -207,6 +207,49 @@ function procs(text) { return out; } +// One {key, name, rx, tx} per TCP socket in `ss -tinpH` output: a line +// naming the owner in users:((...)), then the -i line under it with the +// byte counters. Sockets ss names no owner for (other users', without root) +// are skipped. The key is both endpoints and the pid, so a reused port in +// another process is a new socket. +function sockets(text) { + const out = []; + let cur = null; + for (const line of text.split("\n")) { + const u = /^\S+\s+\d+\s+\d+\s+(\S+)\s+(\S+)\s+users:\(\("([^"]*)",pid=(\d+)/.exec(line); + if (u) { + cur = { key: u[1] + " " + u[2] + " " + u[4], name: u[3], rx: 0, tx: 0 }; + out.push(cur); + } else if (cur && /^\s/.test(line)) { + cur.rx = Number((/bytes_received:(\d+)/.exec(line) || [0, 0])[1]); + cur.tx = Number((/bytes_sent:(\d+)/.exec(line) || [0, 0])[1]); + cur = null; + } else { + cur = null; + } + } + return out; +} + +// Bytes/s per process name between two sockets() samples, busiest first, +// idle ones dropped. A socket new since the last sample counts from zero, +// since it opened inside the interval; one that closed takes its last +// bytes with it, which undercounts short transfers. +function netProcs(prev, cur, secs) { + if (!secs || secs <= 0) return []; + const before = {}; + for (const s of prev) before[s.key] = s; + const by = {}; + for (const s of cur) { + const p = before[s.key] || { rx: 0, tx: 0 }; + const e = by[s.name] || (by[s.name] = { name: s.name, rx: 0, tx: 0 }); + e.rx += Math.max(0, s.rx - p.rx) / secs; + e.tx += Math.max(0, s.tx - p.tx) / secs; + } + return Object.keys(by).map(k => by[k]).filter(e => e.rx + e.tx > 0) + .sort((a, b) => (b.rx + b.tx) - (a.rx + a.tx)); +} + function bytes(n) { if (n === null || n === undefined) return "--"; const u = ["B", "K", "M", "G", "T"]; -- cgit v1.2.3