diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-24 10:09:11 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-24 10:09:11 +0200 |
| commit | c083b40500b0496e1e8e1a32fc31b70b5e9b960d (patch) | |
| tree | 18d4c6aa1a91084aeb7c7bfb3e6aae880f1ca160 /desktop/modules/perf/PerfPanel.qml | |
| parent | 7e14b275e5a9759f0877ef13b9ad338127bec728 (diff) | |
| download | quickshell-c083b40500b0496e1e8e1a32fc31b70b5e9b960d.tar.gz quickshell-c083b40500b0496e1e8e1a32fc31b70b5e9b960d.zip | |
perf: the five heaviest processes in the CPU card, by CPU or RAM
A table under RAM lists the top five processes with their CPU and
resident memory, sorted by either through a toggle in the card. The
toggle re-sorts the frame already held and is not saved.
The rows come from one `top -b -d2` that runs only while the page is
open and dies with it, checked with pgrep. ps was not used: its %CPU is
a lifetime average. top's first frame is the same lifetime figure, so it
is skipped. top marks no frame end but writes each frame in one burst, so
a frame is published once its output has been quiet for 150ms, and rows
appear about 2s after opening, not 4s as when waiting for the next
header.
SplitParser delivered every header after the first as "\ntop - ...", so
the header match is a regex that allows leading whitespace; AGENTS.md
records it. The parser is Perf.js procs(), covered in test_perf.js.
Columns are right-aligned at fixed widths with tabular figures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/perf/PerfPanel.qml')
| -rw-r--r-- | desktop/modules/perf/PerfPanel.qml | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/desktop/modules/perf/PerfPanel.qml b/desktop/modules/perf/PerfPanel.qml index aa2e49f..ff9f74b 100644 --- a/desktop/modules/perf/PerfPanel.qml +++ b/desktop/modules/perf/PerfPanel.qml @@ -131,6 +131,39 @@ Column { // --- CPU ---------------------------------------------------------------- + // The five heaviest processes, from one `top -b` streaming a frame every + // 2s for as long as the page is open; it dies with the panel. top marks + // no frame's end but writes each in one burst, so a frame is published + // once its output goes quiet. The first is skipped, since top computes + // it over each process's whole life rather than the last interval, so + // rows load about 2s after the page opens. + property var procs: [] + property string procBy: "cpu" + property string frame: "" + property int frames: 0 + + Process { + running: true + command: ["env", "LC_ALL=C", "top", "-b", "-d2", "-w512"] + stdout: SplitParser { + onRead: line => { + // Every header after the first reaches here with the blank + // line before it still attached, as "\ntop - ...". + if (/^\s*top - /.test(line)) { + panel.frames++; + panel.frame = ""; + } + panel.frame += line + "\n"; + frameEnd.restart(); + } + } + } + Timer { + id: frameEnd + interval: 150 + onTriggered: if (panel.frames >= 2) panel.procs = P.procs(panel.frame) + } + Card { title: "CPU" headline: Perf.load === null ? "--" : Math.round(Perf.load) + "%" @@ -174,6 +207,66 @@ Column { frac: Perf.mem ? Perf.mem.used / Perf.mem.total : 0 fill: panel.tone(panel.pct(Perf.mem ? Perf.mem.used : null, Perf.mem ? Perf.mem.total : 0), 25, 75) } + + // Sorted by CPU or RAM, the active one lit; switching re-sorts the + // frame already held rather than waiting for the next. + Item { + width: parent.width + height: byRow.implicitHeight + Label { text: "Processes"; color: Theme.subtext } + Row { + id: byRow + anchors.right: parent.right + spacing: 10 + Repeater { + model: [{ key: "cpu", text: "CPU" }, { key: "mem", text: "RAM" }] + delegate: Label { + required property var modelData + text: modelData.text + font.bold: panel.procBy === modelData.key + color: panel.procBy === modelData.key ? Theme.text : Theme.overlay + MouseArea { + anchors.fill: parent + anchors.margins: -4 + cursorShape: Qt.PointingHandCursor + onClicked: panel.procBy = modelData.key + } + } + } + } + } + Repeater { + model: panel.procs.slice().sort((a, b) => b[panel.procBy] - a[panel.procBy]).slice(0, 5) + // Two right-aligned columns of fixed width, so the numbers line + // up down the table whatever their length. + delegate: Item { + required property var modelData + width: parent.width + implicitHeight: nameText.implicitHeight + Label { + id: nameText + anchors { left: parent.left; right: cpuText.left; rightMargin: 8 } + text: modelData.name + elide: Text.ElideRight + } + Label { + id: cpuText + anchors.right: memText.left + width: panel.fs * 4 + horizontalAlignment: Text.AlignRight + text: modelData.cpu.toFixed(1) + "%" + font.features: { "tnum": 1 } + } + Label { + id: memText + anchors.right: parent.right + width: panel.fs * 4.5 + horizontalAlignment: Text.AlignRight + text: P.bytes(modelData.mem) + font.features: { "tnum": 1 } + } + } + } } // --- temperatures ------------------------------------------------------- |
