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) }