extract cpu service from system stats, add --types filter to nova-stats
This commit is contained in:
parent
8628b4b27b
commit
29e06eadb5
7 changed files with 227 additions and 189 deletions
|
|
@ -4,9 +4,6 @@ import "../services" as S
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var cores
|
|
||||||
required property var coreMaxFreq
|
|
||||||
required property var coreTypes
|
|
||||||
required property var processes
|
required property var processes
|
||||||
required property color accentColor
|
required property color accentColor
|
||||||
|
|
||||||
|
|
@ -16,34 +13,32 @@ Column {
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active && !_coreActive) {
|
if (active && !_coreActive) {
|
||||||
_coreActive = true;
|
_coreActive = true;
|
||||||
S.SystemStats.coreConsumers++;
|
S.CpuService.coreConsumers++;
|
||||||
} else if (!active && _coreActive) {
|
} else if (!active && _coreActive) {
|
||||||
_coreActive = false;
|
_coreActive = false;
|
||||||
S.SystemStats.coreConsumers--;
|
S.CpuService.coreConsumers--;
|
||||||
}
|
}
|
||||||
if (active)
|
|
||||||
_cpuHistory = [];
|
|
||||||
}
|
}
|
||||||
Component.onDestruction: if (_coreActive)
|
Component.onDestruction: if (_coreActive)
|
||||||
S.SystemStats.coreConsumers--
|
S.CpuService.coreConsumers--
|
||||||
|
|
||||||
// Per-core rows
|
// Per-core rows
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.cores.length
|
model: S.CpuService.cores.length
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
required property int index
|
required property int index
|
||||||
width: root.width
|
width: root.width
|
||||||
|
|
||||||
readonly property int _u: root.cores[index]?.usage ?? 0
|
readonly property int _u: S.CpuService.cores[index]?.usage ?? 0
|
||||||
readonly property real _f: root.cores[index]?.freq_ghz ?? 0
|
readonly property real _f: S.CpuService.cores[index]?.freq_ghz ?? 0
|
||||||
readonly property color _barColor: S.Theme.loadColor(_u)
|
readonly property color _barColor: S.Theme.loadColor(_u)
|
||||||
readonly property bool _throttled: {
|
readonly property bool _throttled: {
|
||||||
const maxF = root.coreMaxFreq[index] ?? 0;
|
const maxF = S.CpuService.coreMaxFreq[index] ?? 0;
|
||||||
return maxF > 0 && _f < maxF * 0.85 && _u >= 60;
|
return maxF > 0 && _f < maxF * 0.85 && _u >= 60;
|
||||||
}
|
}
|
||||||
readonly property bool _isFirstECore: {
|
readonly property bool _isFirstECore: {
|
||||||
const types = root.coreTypes;
|
const types = S.CpuService.coreTypes;
|
||||||
if (!types.length || index >= types.length)
|
if (!types.length || index >= types.length)
|
||||||
return false;
|
return false;
|
||||||
if (types[index] !== "Efficiency")
|
if (types[index] !== "Efficiency")
|
||||||
|
|
@ -118,7 +113,7 @@ Column {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 32
|
width: 32
|
||||||
height: 10
|
height: 10
|
||||||
history: root.cores[parent.parent.index]?.history ?? []
|
history: S.CpuService.cores[parent.parent.index]?.history ?? []
|
||||||
strokeColor: parent.parent._barColor
|
strokeColor: parent.parent._barColor
|
||||||
colorAt: v => S.Theme.loadColor(v)
|
colorAt: v => S.Theme.loadColor(v)
|
||||||
active: root.active
|
active: root.active
|
||||||
|
|
@ -145,8 +140,8 @@ Column {
|
||||||
// Overall CPU utilization
|
// Overall CPU utilization
|
||||||
InfoRow {
|
InfoRow {
|
||||||
label: "Total"
|
label: "Total"
|
||||||
value: S.SystemStats.cpuUsage + "% @ " + S.SystemStats.cpuFreqGhz.toFixed(2) + " GHz"
|
value: S.CpuService.usage + "% @ " + S.CpuService.freqGhz.toFixed(2) + " GHz"
|
||||||
valueColor: S.Theme.loadColor(S.SystemStats.cpuUsage)
|
valueColor: S.Theme.loadColor(S.CpuService.usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
SparklineCanvas {
|
SparklineCanvas {
|
||||||
|
|
@ -155,23 +150,12 @@ Column {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 32
|
height: 32
|
||||||
history: root._cpuHistory
|
history: S.CpuService.history
|
||||||
strokeColor: root.accentColor
|
strokeColor: root.accentColor
|
||||||
colorAt: v => S.Theme.loadColor(v)
|
colorAt: v => S.Theme.loadColor(v)
|
||||||
active: root.active
|
active: root.active
|
||||||
}
|
}
|
||||||
|
|
||||||
property var _cpuHistory: []
|
|
||||||
Connections {
|
|
||||||
target: S.SystemStats
|
|
||||||
function onCpuUsageChanged() {
|
|
||||||
if (!root.active)
|
|
||||||
return;
|
|
||||||
const h = root._cpuHistory.concat([S.SystemStats.cpuUsage]);
|
|
||||||
root._cpuHistory = h.length > 60 ? h.slice(h.length - 60) : h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process list - hidden on lock screen (exposes running process names)
|
// Process list - hidden on lock screen (exposes running process names)
|
||||||
Column {
|
Column {
|
||||||
visible: !S.LockService.locked
|
visible: !S.LockService.locked
|
||||||
|
|
|
||||||
|
|
@ -186,9 +186,6 @@ PanelWindow {
|
||||||
|
|
||||||
C.CpuApplet {
|
C.CpuApplet {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
cores: S.SystemStats.cpuCores
|
|
||||||
coreMaxFreq: S.SystemStats.cpuCoreMaxFreq
|
|
||||||
coreTypes: S.SystemStats.cpuCoreTypes
|
|
||||||
processes: _cpuProcs.processes
|
processes: _cpuProcs.processes
|
||||||
accentColor: root._accent
|
accentColor: root._accent
|
||||||
active: _cpuCard.expanded
|
active: _cpuCard.expanded
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,18 @@ M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
active: S.Modules.cpu.enable
|
active: S.Modules.cpu.enable
|
||||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||||
tooltip: "CPU: " + S.SystemStats.cpuUsage + "% @ " + S.SystemStats.cpuFreqGhz.toFixed(2) + " GHz"
|
tooltip: "CPU: " + S.CpuService.usage + "% @ " + S.CpuService.freqGhz.toFixed(2) + " GHz"
|
||||||
panelNamespace: "nova-cpu"
|
panelNamespace: "nova-cpu"
|
||||||
panelContentWidth: 260
|
panelContentWidth: 260
|
||||||
panelComponent: Component {
|
panelComponent: Component {
|
||||||
C.CpuApplet {
|
C.CpuApplet {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
cores: root._cores
|
|
||||||
coreMaxFreq: root._coreMaxFreq
|
|
||||||
coreTypes: root._coreTypes
|
|
||||||
processes: root._procs.processes
|
processes: root._procs.processes
|
||||||
accentColor: root.accentColor
|
accentColor: root.accentColor
|
||||||
active: root._showPanel
|
active: root._showPanel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var _cores: S.SystemStats.cpuCores
|
|
||||||
readonly property var _coreMaxFreq: S.SystemStats.cpuCoreMaxFreq
|
|
||||||
readonly property var _coreTypes: S.SystemStats.cpuCoreTypes
|
|
||||||
|
|
||||||
property M.ProcessList _procs: M.ProcessList {
|
property M.ProcessList _procs: M.ProcessList {
|
||||||
sortBy: "cpu"
|
sortBy: "cpu"
|
||||||
active: root._showPanel
|
active: root._showPanel
|
||||||
|
|
@ -38,7 +31,7 @@ M.BarModule {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: S.SystemStats.cpuUsage.toString().padStart(2) + "%@" + S.SystemStats.cpuFreqGhz.toFixed(2)
|
label: S.CpuService.usage.toString().padStart(2) + "%@" + S.CpuService.freqGhz.toFixed(2)
|
||||||
minText: "99%@9.99"
|
minText: "99%@9.99"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
|
||||||
160
shell/services/CpuService.qml
Normal file
160
shell/services/CpuService.qml
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import "." as S
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int usage: 0
|
||||||
|
property real freqGhz: 0
|
||||||
|
property var cores: [] // [{usage, freq_ghz, history:[]}] - history only while coreConsumers > 0
|
||||||
|
property var coreMaxFreq: []
|
||||||
|
property var coreTypes: []
|
||||||
|
|
||||||
|
// Overall CPU history (60 samples)
|
||||||
|
property var history: []
|
||||||
|
|
||||||
|
// Per-core data gating - applets increment/decrement
|
||||||
|
property int coreConsumers: 0
|
||||||
|
onCoreConsumersChanged: {
|
||||||
|
if (coreConsumers > 0)
|
||||||
|
_coreGraceTimer.stop();
|
||||||
|
else
|
||||||
|
_coreGraceTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
property Timer _coreGraceTimer: Timer {
|
||||||
|
interval: 30000
|
||||||
|
onTriggered: root.cores = root.cores.map(() => ({
|
||||||
|
"usage": 0,
|
||||||
|
"freq_ghz": 0,
|
||||||
|
"history": []
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nova-stats process (cpu only)
|
||||||
|
property Process _proc: Process {
|
||||||
|
running: true
|
||||||
|
command: {
|
||||||
|
const ms = S.Modules.statsDaemon.interval;
|
||||||
|
return ms > 0 ? ["nova-stats", "--types", "cpu", "--interval", ms.toString()] : ["nova-stats", "--types", "cpu"];
|
||||||
|
}
|
||||||
|
stdout: SplitParser {
|
||||||
|
splitMarker: "\n"
|
||||||
|
onRead: line => {
|
||||||
|
try {
|
||||||
|
const ev = JSON.parse(line);
|
||||||
|
if (ev.type !== "cpu")
|
||||||
|
return;
|
||||||
|
root.usage = ev.usage;
|
||||||
|
root.freqGhz = ev.freq_ghz;
|
||||||
|
|
||||||
|
const h = root.history.concat([ev.usage]);
|
||||||
|
root.history = h.length > 60 ? h.slice(h.length - 60) : h;
|
||||||
|
|
||||||
|
if (root.coreConsumers > 0) {
|
||||||
|
const histLen = 16;
|
||||||
|
const prev = root.cores;
|
||||||
|
root.cores = ev.cores.map((c, i) => {
|
||||||
|
const oldHist = prev[i]?.history ?? [];
|
||||||
|
const hist = oldHist.concat([c.usage]);
|
||||||
|
return {
|
||||||
|
usage: c.usage,
|
||||||
|
freq_ghz: c.freq_ghz,
|
||||||
|
history: hist.length > histLen ? hist.slice(hist.length - histLen) : hist
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else if (root.cores.length !== ev.cores.length) {
|
||||||
|
root.cores = ev.cores.map(c => ({
|
||||||
|
"usage": c.usage,
|
||||||
|
"freq_ghz": c.freq_ghz,
|
||||||
|
"history": []
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One-time: per-core max freq
|
||||||
|
property Process _maxFreqProc: Process {
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", "ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | sort -V | while read d; do f=\"$d/cpufreq/cpuinfo_max_freq\"; [ -f \"$f\" ] && cat \"$f\" || echo 0; done"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.coreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One-time: P/E-core topology
|
||||||
|
property Process _coreTypesProc: Process {
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", String.raw`
|
||||||
|
if [ -f /sys/devices/cpu_core/cpus ] && [ -f /sys/devices/cpu_atom/cpus ]; then
|
||||||
|
core=$(cat /sys/devices/cpu_core/cpus)
|
||||||
|
atom=$(cat /sys/devices/cpu_atom/cpus)
|
||||||
|
echo "hybrid:$core:$atom"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | sort -V | while read d; do
|
||||||
|
f="$d/topology/core_type"
|
||||||
|
[ -f "$f" ] && cat "$f"
|
||||||
|
done
|
||||||
|
`]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const out = text.trim();
|
||||||
|
if (!out)
|
||||||
|
return;
|
||||||
|
if (out.startsWith("hybrid:")) {
|
||||||
|
const parts = out.split(":");
|
||||||
|
const coreRange = parts[1];
|
||||||
|
const atomRange = parts[2];
|
||||||
|
function expandRange(s) {
|
||||||
|
const cpus = new Set();
|
||||||
|
for (const part of s.split(",")) {
|
||||||
|
if (part.includes("-")) {
|
||||||
|
const [a, b] = part.split("-").map(Number);
|
||||||
|
for (let i = a; i <= b; i++)
|
||||||
|
cpus.add(i);
|
||||||
|
} else {
|
||||||
|
cpus.add(Number(part));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cpus;
|
||||||
|
}
|
||||||
|
const pCores = expandRange(coreRange);
|
||||||
|
const eCores = expandRange(atomRange);
|
||||||
|
const maxCpu = Math.max(...pCores, ...eCores);
|
||||||
|
const types = [];
|
||||||
|
for (let i = 0; i <= maxCpu; i++)
|
||||||
|
types.push(eCores.has(i) ? "Efficiency" : "Performance");
|
||||||
|
root.coreTypes = types;
|
||||||
|
} else {
|
||||||
|
const types = out.split("\n").filter(l => l).map(l => l.trim());
|
||||||
|
if (types.length > 0)
|
||||||
|
root.coreTypes = types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: infer P/E from max freq gap
|
||||||
|
function _inferCoreTypesFromFreq() {
|
||||||
|
if (coreTypes.length > 0 || coreMaxFreq.length < 2)
|
||||||
|
return;
|
||||||
|
const freqs = coreMaxFreq.filter(f => f > 0);
|
||||||
|
if (!freqs.length)
|
||||||
|
return;
|
||||||
|
const maxF = Math.max(...freqs);
|
||||||
|
const minF = Math.min(...freqs);
|
||||||
|
if (maxF > 0 && minF > 0 && (maxF - minF) / maxF > 0.15) {
|
||||||
|
const threshold = (maxF + minF) / 2;
|
||||||
|
coreTypes = coreMaxFreq.map(f => f >= threshold ? "Performance" : "Efficiency");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCoreMaxFreqChanged: Qt.callLater(_inferCoreTypesFromFreq)
|
||||||
|
}
|
||||||
|
|
@ -7,29 +7,6 @@ import "." as M
|
||||||
QtObject {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// ── CPU ──────────────────────────────────────────────────────────────
|
|
||||||
property int cpuUsage: 0
|
|
||||||
property real cpuFreqGhz: 0
|
|
||||||
property var cpuCores: [] // [{usage, freq_ghz, history:[]}] — only rebuilt while coreConsumers > 0
|
|
||||||
property int coreConsumers: 0
|
|
||||||
onCoreConsumersChanged: {
|
|
||||||
if (coreConsumers > 0)
|
|
||||||
_coreGraceTimer.stop();
|
|
||||||
else
|
|
||||||
_coreGraceTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
property var _coreGraceTimer: Timer {
|
|
||||||
interval: 30000
|
|
||||||
onTriggered: root.cpuCores = root.cpuCores.map(() => ({
|
|
||||||
"usage": 0,
|
|
||||||
"freq_ghz": 0,
|
|
||||||
"history": []
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
property var cpuCoreMaxFreq: []
|
|
||||||
property var cpuCoreTypes: []
|
|
||||||
|
|
||||||
// ── Temperature ──────────────────────────────────────────────────────
|
// ── Temperature ──────────────────────────────────────────────────────
|
||||||
property int tempCelsius: 0
|
property int tempCelsius: 0
|
||||||
property var tempHistory: [] // 150 samples @ 4s each ≈ 10 min
|
property var tempHistory: [] // 150 samples @ 4s each ≈ 10 min
|
||||||
|
|
@ -57,42 +34,19 @@ QtObject {
|
||||||
property var diskMounts: []
|
property var diskMounts: []
|
||||||
property int diskRootPct: 0
|
property int diskRootPct: 0
|
||||||
|
|
||||||
// nova-stats stream (cpu + mem)
|
// nova-stats stream (mem, temp, gpu - cpu handled by CpuService)
|
||||||
property var _statsProc: Process {
|
property var _statsProc: Process {
|
||||||
running: true
|
running: true
|
||||||
command: {
|
command: {
|
||||||
const ms = M.Modules.statsDaemon.interval;
|
const ms = M.Modules.statsDaemon.interval;
|
||||||
return ms > 0 ? ["nova-stats", "--interval", ms.toString()] : ["nova-stats"];
|
return ms > 0 ? ["nova-stats", "--types", "mem,temp,gpu", "--interval", ms.toString()] : ["nova-stats", "--types", "mem,temp,gpu"];
|
||||||
}
|
}
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
onRead: line => {
|
onRead: line => {
|
||||||
try {
|
try {
|
||||||
const ev = JSON.parse(line);
|
const ev = JSON.parse(line);
|
||||||
if (ev.type === "cpu") {
|
if (ev.type === "temp") {
|
||||||
root.cpuUsage = ev.usage;
|
|
||||||
root.cpuFreqGhz = ev.freq_ghz;
|
|
||||||
if (root.coreConsumers > 0) {
|
|
||||||
const histLen = 16;
|
|
||||||
const prev = root.cpuCores;
|
|
||||||
root.cpuCores = ev.cores.map((c, i) => {
|
|
||||||
const oldHist = prev[i]?.history ?? [];
|
|
||||||
const hist = oldHist.concat([c.usage]);
|
|
||||||
return {
|
|
||||||
usage: c.usage,
|
|
||||||
freq_ghz: c.freq_ghz,
|
|
||||||
history: hist.length > histLen ? hist.slice(hist.length - histLen) : hist
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else if (root.cpuCores.length !== ev.cores.length) {
|
|
||||||
// Keep count in sync so panel can size correctly before consumers activate
|
|
||||||
root.cpuCores = ev.cores.map(c => ({
|
|
||||||
"usage": c.usage,
|
|
||||||
"freq_ghz": c.freq_ghz,
|
|
||||||
"history": []
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else if (ev.type === "temp") {
|
|
||||||
root.tempCelsius = ev.celsius;
|
root.tempCelsius = ev.celsius;
|
||||||
const th = root.tempHistory.concat([ev.celsius]);
|
const th = root.tempHistory.concat([ev.celsius]);
|
||||||
root.tempHistory = th.length > 150 ? th.slice(th.length - 150) : th;
|
root.tempHistory = th.length > 150 ? th.slice(th.length - 150) : th;
|
||||||
|
|
@ -122,91 +76,6 @@ QtObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// One-time: per-core max freq (numerically sorted)
|
|
||||||
property var _maxFreqProc: Process {
|
|
||||||
running: true
|
|
||||||
command: ["sh", "-c", "ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | sort -V | while read d; do f=\"$d/cpufreq/cpuinfo_max_freq\"; [ -f \"$f\" ] && cat \"$f\" || echo 0; done"]
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
root.cpuCoreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// One-time: P/E-core topology
|
|
||||||
// Priority: cpu_core/cpu_atom sysfs > topology/core_type > freq-gap heuristic
|
|
||||||
property var _coreTypesProc: Process {
|
|
||||||
running: true
|
|
||||||
command: ["sh", "-c", String.raw`
|
|
||||||
# Intel hybrid: /sys/devices/cpu_core/cpus and cpu_atom/cpus give CPU ranges
|
|
||||||
if [ -f /sys/devices/cpu_core/cpus ] && [ -f /sys/devices/cpu_atom/cpus ]; then
|
|
||||||
core=$(cat /sys/devices/cpu_core/cpus)
|
|
||||||
atom=$(cat /sys/devices/cpu_atom/cpus)
|
|
||||||
echo "hybrid:$core:$atom"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
# Fallback: per-core topology/core_type
|
|
||||||
ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | sort -V | while read d; do
|
|
||||||
f="$d/topology/core_type"
|
|
||||||
[ -f "$f" ] && cat "$f"
|
|
||||||
done
|
|
||||||
`]
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
const out = text.trim();
|
|
||||||
if (!out)
|
|
||||||
return;
|
|
||||||
if (out.startsWith("hybrid:")) {
|
|
||||||
// Parse cpu_core/cpu_atom ranges into per-cpu type array
|
|
||||||
const parts = out.split(":");
|
|
||||||
const coreRange = parts[1];
|
|
||||||
const atomRange = parts[2];
|
|
||||||
function expandRange(s) {
|
|
||||||
const cpus = new Set();
|
|
||||||
for (const part of s.split(",")) {
|
|
||||||
if (part.includes("-")) {
|
|
||||||
const [a, b] = part.split("-").map(Number);
|
|
||||||
for (let i = a; i <= b; i++)
|
|
||||||
cpus.add(i);
|
|
||||||
} else {
|
|
||||||
cpus.add(Number(part));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpus;
|
|
||||||
}
|
|
||||||
const pCores = expandRange(coreRange);
|
|
||||||
const eCores = expandRange(atomRange);
|
|
||||||
const maxCpu = Math.max(...pCores, ...eCores);
|
|
||||||
const types = [];
|
|
||||||
for (let i = 0; i <= maxCpu; i++)
|
|
||||||
types.push(eCores.has(i) ? "Efficiency" : "Performance");
|
|
||||||
root.cpuCoreTypes = types;
|
|
||||||
} else {
|
|
||||||
// topology/core_type output
|
|
||||||
const types = out.split("\n").filter(l => l).map(l => l.trim());
|
|
||||||
if (types.length > 0)
|
|
||||||
root.cpuCoreTypes = types;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: infer P/E from max freq gap when no sysfs topology is available
|
|
||||||
function _inferCoreTypesFromFreq() {
|
|
||||||
if (cpuCoreTypes.length > 0 || cpuCoreMaxFreq.length < 2)
|
|
||||||
return;
|
|
||||||
const freqs = cpuCoreMaxFreq.filter(f => f > 0);
|
|
||||||
if (!freqs.length)
|
|
||||||
return;
|
|
||||||
const maxF = Math.max(...freqs);
|
|
||||||
const minF = Math.min(...freqs);
|
|
||||||
if (maxF > 0 && minF > 0 && (maxF - minF) / maxF > 0.15) {
|
|
||||||
const threshold = (maxF + minF) / 2;
|
|
||||||
cpuCoreTypes = cpuCoreMaxFreq.map(f => f >= threshold ? "Performance" : "Efficiency");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onCpuCoreMaxFreqChanged: Qt.callLater(_inferCoreTypesFromFreq)
|
|
||||||
|
|
||||||
// Disk via df
|
// Disk via df
|
||||||
property var _diskProc: Process {
|
property var _diskProc: Process {
|
||||||
id: diskProc
|
id: diskProc
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ NotifItem 1.0 NotifItem.qml
|
||||||
singleton BacklightService 1.0 BacklightService.qml
|
singleton BacklightService 1.0 BacklightService.qml
|
||||||
singleton BatteryService 1.0 BatteryService.qml
|
singleton BatteryService 1.0 BatteryService.qml
|
||||||
singleton BluetoothService 1.0 BluetoothService.qml
|
singleton BluetoothService 1.0 BluetoothService.qml
|
||||||
|
singleton CpuService 1.0 CpuService.qml
|
||||||
singleton DockState 1.0 DockState.qml
|
singleton DockState 1.0 DockState.qml
|
||||||
singleton IdleInhibitService 1.0 IdleInhibitService.qml
|
singleton IdleInhibitService 1.0 IdleInhibitService.qml
|
||||||
singleton LockService 1.0 LockService.qml
|
singleton LockService 1.0 LockService.qml
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
@ -21,29 +22,61 @@ fn parse_interval_ms() -> u64 {
|
||||||
1000
|
1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_types() -> HashSet<String> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let mut i = 1;
|
||||||
|
while i < args.len() {
|
||||||
|
if args[i] == "--types" {
|
||||||
|
if let Some(list) = args.get(i + 1) {
|
||||||
|
return list.split(',').map(|s| s.trim().to_string()).collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
HashSet::new() // empty = all types
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let interval = Duration::from_millis(parse_interval_ms());
|
let interval = Duration::from_millis(parse_interval_ms());
|
||||||
|
let types = parse_types();
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut out = io::BufWriter::new(stdout.lock());
|
let mut out = io::BufWriter::new(stdout.lock());
|
||||||
let mut prev: Vec<cpu::Sample> = vec![];
|
let mut prev: Vec<cpu::Sample> = vec![];
|
||||||
let mut freqs: Vec<f64> = vec![];
|
let mut freqs: Vec<f64> = vec![];
|
||||||
let mut gpu = gpu::detect_gpu();
|
|
||||||
let mut tick = 0u64;
|
let mut tick = 0u64;
|
||||||
|
|
||||||
|
let emit_cpu = types.is_empty() || types.contains("cpu");
|
||||||
|
let emit_mem = types.is_empty() || types.contains("mem");
|
||||||
|
let emit_temp = types.is_empty() || types.contains("temp");
|
||||||
|
let emit_graphics = types.is_empty() || types.contains("gpu");
|
||||||
|
|
||||||
|
let mut gpu = if emit_graphics {
|
||||||
|
gpu::detect_gpu()
|
||||||
|
} else {
|
||||||
|
gpu::GpuBackend::None
|
||||||
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
|
|
||||||
|
if emit_cpu {
|
||||||
let curr = cpu::read_stat();
|
let curr = cpu::read_stat();
|
||||||
if tick.is_multiple_of(2) {
|
if tick.is_multiple_of(2) {
|
||||||
freqs = cpu::read_core_freqs();
|
freqs = cpu::read_core_freqs();
|
||||||
mem::emit_mem(&mut out);
|
|
||||||
}
|
}
|
||||||
cpu::emit_cpu(&mut out, &prev, &curr, &freqs);
|
cpu::emit_cpu(&mut out, &prev, &curr, &freqs);
|
||||||
prev = curr;
|
prev = curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if emit_mem && tick.is_multiple_of(2) {
|
||||||
|
mem::emit_mem(&mut out);
|
||||||
|
}
|
||||||
|
|
||||||
if tick.is_multiple_of(4) {
|
if tick.is_multiple_of(4) {
|
||||||
|
if emit_temp {
|
||||||
temp::emit_temp(&mut out);
|
temp::emit_temp(&mut out);
|
||||||
// AMD/Intel read sysfs (instant); NVIDIA shells out so runs less often
|
}
|
||||||
|
if emit_graphics {
|
||||||
let emit = match &gpu {
|
let emit = match &gpu {
|
||||||
gpu::GpuBackend::Amd { .. } | gpu::GpuBackend::Intel { .. } => true,
|
gpu::GpuBackend::Amd { .. } | gpu::GpuBackend::Intel { .. } => true,
|
||||||
gpu::GpuBackend::Nvidia => tick.is_multiple_of(8),
|
gpu::GpuBackend::Nvidia => tick.is_multiple_of(8),
|
||||||
|
|
@ -53,6 +86,7 @@ fn main() {
|
||||||
gpu::emit_gpu(&mut out, &mut gpu);
|
gpu::emit_gpu(&mut out, &mut gpu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = out.flush();
|
let _ = out.flush();
|
||||||
tick += 1;
|
tick += 1;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue