aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/perf/Perf.js
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-27 13:56:24 +0100
committerDanilo M. <danix@danix.xyz>2026-10-27 13:56:24 +0100
commit8c15dc1205d879591318cf365e15b014477329c1 (patch)
treef315c92394f8db96c36f56c8a93f9d8feedade20 /desktop/modules/perf/Perf.js
parent6b68cae8e0c4ec43c4a9a8a812a644b7bea746e7 (diff)
downloadquickshell-8c15dc1205d879591318cf365e15b014477329c1.tar.gz
quickshell-8c15dc1205d879591318cf365e15b014477329c1.zip
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 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/perf/Perf.js')
-rw-r--r--desktop/modules/perf/Perf.js43
1 files changed, 43 insertions, 0 deletions
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"];