aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/perf/PerfPanel.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/perf/PerfPanel.qml')
-rw-r--r--desktop/modules/perf/PerfPanel.qml93
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 -------------------------------------------------------