mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
741 lines
22 KiB
QML
741 lines
22 KiB
QML
pragma Singleton
|
|
import Qt.labs.folderlistmodel
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
// Configuration
|
|
readonly property int minimumIntervalMs: 250
|
|
readonly property int defaultIntervalMs: 3000
|
|
|
|
function normalizeInterval(value) {
|
|
return Math.max(minimumIntervalMs, value || defaultIntervalMs);
|
|
}
|
|
|
|
// Public values
|
|
property real cpuUsage: 0
|
|
property real cpuTemp: 0
|
|
property real gpuTemp: 0
|
|
property bool gpuAvailable: false
|
|
property string gpuType: "" // "amd", "intel", "nvidia"
|
|
property real memGb: 0
|
|
property real memPercent: 0
|
|
property var diskPercents: ({})
|
|
property real rxSpeed: 0
|
|
property real txSpeed: 0
|
|
|
|
// Internal state for CPU calculation
|
|
property var prevCpuStats: null
|
|
|
|
// Internal state for network speed calculation
|
|
// Previous Bytes need to be stored as 'real' as they represent the total of bytes transfered
|
|
// since the computer started, so their value will easily overlfow a 32bit int.
|
|
property real prevRxBytes: 0
|
|
property real prevTxBytes: 0
|
|
property real prevTime: 0
|
|
|
|
// Cpu temperature is the most complex
|
|
readonly property var supportedTempCpuSensorNames: ["coretemp", "k10temp", "zenpower"]
|
|
property string cpuTempSensorName: ""
|
|
property string cpuTempHwmonPath: ""
|
|
// For Intel coretemp averaging of all cores/sensors
|
|
property var intelTempValues: []
|
|
property int intelTempFilesChecked: 0
|
|
property int intelTempMaxFiles: 20 // Will test up to temp20_input
|
|
|
|
// GPU temperature detection
|
|
// On dual-GPU systems, we prioritize discrete GPUs over integrated GPUs
|
|
// Priority: NVIDIA (opt-in) > AMD dGPU > Intel Arc > AMD iGPU
|
|
// Note: NVIDIA requires opt-in because nvidia-smi wakes the dGPU on laptops, draining battery
|
|
readonly property var supportedTempGpuSensorNames: ["amdgpu", "xe"]
|
|
property string gpuTempHwmonPath: ""
|
|
property var foundGpuSensors: [] // [{hwmonPath, type, hasDedicatedVram}]
|
|
property int gpuVramCheckIndex: 0
|
|
|
|
// --------------------------------------------
|
|
Component.onCompleted: {
|
|
Logger.i("SystemStat", "Service started with custom polling intervals");
|
|
|
|
// Kickoff the cpu name detection for temperature
|
|
cpuTempNameReader.checkNext();
|
|
|
|
// Kickoff the gpu sensor detection for temperature
|
|
gpuTempNameReader.checkNext();
|
|
}
|
|
|
|
// Re-run GPU detection when NVIDIA opt-in setting changes
|
|
Connections {
|
|
target: Settings.data.systemMonitor
|
|
function onEnableNvidiaGpuChanged() {
|
|
Logger.i("SystemStat", "NVIDIA opt-in setting changed, re-detecting GPUs");
|
|
restartGpuDetection();
|
|
}
|
|
}
|
|
|
|
function restartGpuDetection() {
|
|
// Reset GPU state
|
|
root.gpuAvailable = false;
|
|
root.gpuType = "";
|
|
root.gpuTempHwmonPath = "";
|
|
root.gpuTemp = 0;
|
|
root.foundGpuSensors = [];
|
|
root.gpuVramCheckIndex = 0;
|
|
|
|
// Restart GPU detection
|
|
gpuTempNameReader.currentIndex = 0;
|
|
gpuTempNameReader.checkNext();
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Timer for CPU usage
|
|
Timer {
|
|
id: cpuUsageTimer
|
|
interval: root.normalizeInterval(Settings.data.systemMonitor.cpuPollingInterval)
|
|
repeat: true
|
|
running: true
|
|
triggeredOnStart: true
|
|
onIntervalChanged: {
|
|
if (running) {
|
|
restart();
|
|
}
|
|
}
|
|
onTriggered: cpuStatFile.reload()
|
|
}
|
|
|
|
// Timer for CPU temperature
|
|
Timer {
|
|
id: cpuTempTimer
|
|
interval: root.normalizeInterval(Settings.data.systemMonitor.tempPollingInterval)
|
|
repeat: true
|
|
running: true
|
|
triggeredOnStart: true
|
|
onIntervalChanged: {
|
|
if (running) {
|
|
restart();
|
|
}
|
|
}
|
|
onTriggered: updateCpuTemperature()
|
|
}
|
|
|
|
// Timer for memory stats
|
|
Timer {
|
|
id: memoryTimer
|
|
interval: root.normalizeInterval(Settings.data.systemMonitor.memPollingInterval)
|
|
repeat: true
|
|
running: true
|
|
triggeredOnStart: true
|
|
onIntervalChanged: {
|
|
if (running) {
|
|
restart();
|
|
}
|
|
}
|
|
onTriggered: memInfoFile.reload()
|
|
}
|
|
|
|
// Timer for disk usage
|
|
Timer {
|
|
id: diskTimer
|
|
interval: root.normalizeInterval(Settings.data.systemMonitor.diskPollingInterval)
|
|
repeat: true
|
|
running: true
|
|
triggeredOnStart: true
|
|
onIntervalChanged: {
|
|
if (running) {
|
|
restart();
|
|
}
|
|
}
|
|
onTriggered: dfProcess.running = true
|
|
}
|
|
|
|
// Timer for network speeds
|
|
Timer {
|
|
id: networkTimer
|
|
interval: root.normalizeInterval(Settings.data.systemMonitor.networkPollingInterval)
|
|
repeat: true
|
|
running: true
|
|
triggeredOnStart: true
|
|
onIntervalChanged: {
|
|
if (running) {
|
|
restart();
|
|
}
|
|
}
|
|
onTriggered: netDevFile.reload()
|
|
}
|
|
|
|
// Timer for GPU temperature
|
|
Timer {
|
|
id: gpuTempTimer
|
|
interval: root.normalizeInterval(Settings.data.systemMonitor.gpuPollingInterval)
|
|
repeat: true
|
|
running: root.gpuAvailable
|
|
triggeredOnStart: true
|
|
onIntervalChanged: {
|
|
if (running) {
|
|
restart();
|
|
}
|
|
}
|
|
onTriggered: updateGpuTemperature()
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// FileView components for reading system files
|
|
FileView {
|
|
id: memInfoFile
|
|
path: "/proc/meminfo"
|
|
onLoaded: parseMemoryInfo(text())
|
|
}
|
|
|
|
FileView {
|
|
id: cpuStatFile
|
|
path: "/proc/stat"
|
|
onLoaded: calculateCpuUsage(text())
|
|
}
|
|
|
|
FileView {
|
|
id: netDevFile
|
|
path: "/proc/net/dev"
|
|
onLoaded: calculateNetworkSpeed(text())
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Process to fetch disk usage in percent
|
|
// Uses 'df' aka 'disk free'
|
|
// "-x efivarfs' skips efivarfs mountpoints, for which the `statfs` syscall may cause system-wide stuttering
|
|
Process {
|
|
id: dfProcess
|
|
command: ["df", "--output=target,pcent", "-x", "efivarfs"]
|
|
running: false
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const lines = text.trim().split('\n');
|
|
const newPercents = {};
|
|
// Start from line 1 (skip header)
|
|
for (var i = 1; i < lines.length; i++) {
|
|
const parts = lines[i].trim().split(/\s+/);
|
|
if (parts.length >= 2) {
|
|
const target = parts[0];
|
|
const percent = parseInt(parts[1].replace(/[^0-9]/g, '')) || 0;
|
|
newPercents[target] = percent;
|
|
}
|
|
}
|
|
root.diskPercents = newPercents;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// --------------------------------------------
|
|
// CPU Temperature
|
|
// It's more complex.
|
|
// ----
|
|
// #1 - Find a common cpu sensor name ie: "coretemp", "k10temp", "zenpower"
|
|
FileView {
|
|
id: cpuTempNameReader
|
|
property int currentIndex: 0
|
|
printErrors: false
|
|
|
|
function checkNext() {
|
|
if (currentIndex >= 16) {
|
|
// Check up to hwmon10
|
|
Logger.w("No supported temperature sensor found");
|
|
return;
|
|
}
|
|
|
|
//Logger.i("SystemStat", "---- Probing: hwmon", currentIndex)
|
|
cpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`;
|
|
cpuTempNameReader.reload();
|
|
}
|
|
|
|
onLoaded: {
|
|
const name = text().trim();
|
|
if (root.supportedTempCpuSensorNames.includes(name)) {
|
|
root.cpuTempSensorName = name;
|
|
root.cpuTempHwmonPath = `/sys/class/hwmon/hwmon${currentIndex}`;
|
|
Logger.i("SystemStat", `Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`);
|
|
} else {
|
|
currentIndex++;
|
|
Qt.callLater(() => {
|
|
// Qt.callLater is mandatory
|
|
checkNext();
|
|
});
|
|
}
|
|
}
|
|
|
|
onLoadFailed: function (error) {
|
|
currentIndex++;
|
|
Qt.callLater(() => {
|
|
// Qt.callLater is mandatory
|
|
checkNext();
|
|
});
|
|
}
|
|
}
|
|
|
|
// ----
|
|
// #2 - Read sensor value
|
|
FileView {
|
|
id: cpuTempReader
|
|
printErrors: false
|
|
|
|
onLoaded: {
|
|
const data = text().trim();
|
|
if (root.cpuTempSensorName === "coretemp") {
|
|
// For Intel, collect all temperature values
|
|
const temp = parseInt(data) / 1000.0;
|
|
//console.log(temp, cpuTempReader.path)
|
|
root.intelTempValues.push(temp);
|
|
Qt.callLater(() => {
|
|
// Qt.callLater is mandatory
|
|
checkNextIntelTemp();
|
|
});
|
|
} else {
|
|
// For AMD sensors (k10temp and zenpower), directly set the temperature
|
|
root.cpuTemp = Math.round(parseInt(data) / 1000.0);
|
|
}
|
|
}
|
|
onLoadFailed: function (error) {
|
|
Qt.callLater(() => {
|
|
// Qt.callLater is mandatory
|
|
checkNextIntelTemp();
|
|
});
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// --------------------------------------------
|
|
// GPU Temperature
|
|
// On dual-GPU systems (e.g., Intel iGPU + NVIDIA dGPU, or AMD APU + AMD dGPU),
|
|
// we scan all hwmon entries, then select the best GPU based on priority.
|
|
// ----
|
|
// #1 - Scan all hwmon entries to find GPU sensors
|
|
FileView {
|
|
id: gpuTempNameReader
|
|
property int currentIndex: 0
|
|
printErrors: false
|
|
|
|
function checkNext() {
|
|
if (currentIndex >= 16) {
|
|
// Finished scanning all hwmon entries
|
|
// Only check nvidia-smi if user has explicitly enabled NVIDIA monitoring (opt-in)
|
|
// because nvidia-smi wakes up the dGPU on laptops, draining battery
|
|
if (Settings.data.systemMonitor.enableNvidiaGpu) {
|
|
Logger.d("SystemStat", `Found ${root.foundGpuSensors.length} sysfs GPU sensor(s), checking nvidia-smi (opt-in enabled)`);
|
|
nvidiaSmiCheck.running = true;
|
|
} else {
|
|
Logger.d("SystemStat", `Found ${root.foundGpuSensors.length} sysfs GPU sensor(s), skipping nvidia-smi (opt-in disabled)`);
|
|
root.gpuVramCheckIndex = 0;
|
|
checkNextGpuVram();
|
|
}
|
|
return;
|
|
}
|
|
|
|
gpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`;
|
|
gpuTempNameReader.reload();
|
|
}
|
|
|
|
onLoaded: {
|
|
const name = text().trim();
|
|
if (root.supportedTempGpuSensorNames.includes(name)) {
|
|
// Collect this GPU sensor, don't stop - continue scanning for more
|
|
const hwmonPath = `/sys/class/hwmon/hwmon${currentIndex}`;
|
|
const gpuType = name === "amdgpu" ? "amd" : "intel";
|
|
root.foundGpuSensors.push({
|
|
"hwmonPath": hwmonPath,
|
|
"type": gpuType,
|
|
"hasDedicatedVram": false // Will be checked later for AMD
|
|
});
|
|
Logger.d("SystemStat", `Found ${name} GPU sensor at ${hwmonPath}`);
|
|
}
|
|
// Continue scanning regardless of whether we found a match
|
|
currentIndex++;
|
|
Qt.callLater(() => {
|
|
checkNext();
|
|
});
|
|
}
|
|
|
|
onLoadFailed: function (error) {
|
|
currentIndex++;
|
|
Qt.callLater(() => {
|
|
checkNext();
|
|
});
|
|
}
|
|
}
|
|
|
|
// ----
|
|
// #2 - Read GPU sensor value (AMD/Intel via sysfs)
|
|
FileView {
|
|
id: gpuTempReader
|
|
printErrors: false
|
|
|
|
onLoaded: {
|
|
const data = text().trim();
|
|
root.gpuTemp = Math.round(parseInt(data) / 1000.0);
|
|
}
|
|
}
|
|
|
|
// ----
|
|
// #3 - Check if nvidia-smi is available (for NVIDIA GPUs)
|
|
Process {
|
|
id: nvidiaSmiCheck
|
|
command: ["which", "nvidia-smi"]
|
|
running: false
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
if (text.trim().length > 0) {
|
|
// Add NVIDIA as a GPU option (always discrete, highest priority)
|
|
root.foundGpuSensors.push({
|
|
"hwmonPath": "",
|
|
"type": "nvidia",
|
|
"hasDedicatedVram": true // NVIDIA is always discrete
|
|
});
|
|
Logger.d("SystemStat", "Found NVIDIA GPU (nvidia-smi available)");
|
|
}
|
|
// After NVIDIA check, check VRAM for AMD GPUs to distinguish dGPU from iGPU
|
|
root.gpuVramCheckIndex = 0;
|
|
checkNextGpuVram();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----
|
|
// #4 - Check VRAM for AMD GPUs to distinguish dGPU from iGPU
|
|
// dGPUs have dedicated VRAM, iGPUs don't (use system RAM)
|
|
FileView {
|
|
id: gpuVramChecker
|
|
printErrors: false
|
|
|
|
onLoaded: {
|
|
// File exists and has content = dGPU with dedicated VRAM
|
|
const vramSize = parseInt(text().trim());
|
|
if (vramSize > 0) {
|
|
root.foundGpuSensors[root.gpuVramCheckIndex].hasDedicatedVram = true;
|
|
Logger.d("SystemStat", `GPU at ${root.foundGpuSensors[root.gpuVramCheckIndex].hwmonPath} has dedicated VRAM (dGPU)`);
|
|
}
|
|
root.gpuVramCheckIndex++;
|
|
Qt.callLater(() => {
|
|
checkNextGpuVram();
|
|
});
|
|
}
|
|
|
|
onLoadFailed: function (error) {
|
|
// File doesn't exist = iGPU (no dedicated VRAM)
|
|
// hasDedicatedVram is already false by default
|
|
root.gpuVramCheckIndex++;
|
|
Qt.callLater(() => {
|
|
checkNextGpuVram();
|
|
});
|
|
}
|
|
}
|
|
|
|
// ----
|
|
// #4 - Read GPU temperature via nvidia-smi (NVIDIA only)
|
|
Process {
|
|
id: nvidiaTempProcess
|
|
command: ["nvidia-smi", "--query-gpu=temperature.gpu", "--format=csv,noheader,nounits"]
|
|
running: false
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const temp = parseInt(text.trim());
|
|
if (!isNaN(temp)) {
|
|
root.gpuTemp = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// -------------------------------------------------------
|
|
// Parse memory info from /proc/meminfo
|
|
function parseMemoryInfo(text) {
|
|
if (!text)
|
|
return;
|
|
const lines = text.split('\n');
|
|
let memTotal = 0;
|
|
let memAvailable = 0;
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith('MemTotal:')) {
|
|
memTotal = parseInt(line.split(/\s+/)[1]) || 0;
|
|
} else if (line.startsWith('MemAvailable:')) {
|
|
memAvailable = parseInt(line.split(/\s+/)[1]) || 0;
|
|
}
|
|
}
|
|
|
|
if (memTotal > 0) {
|
|
const usageKb = memTotal - memAvailable;
|
|
root.memGb = (usageKb / 1048576).toFixed(1); // 1024*1024 = 1048576
|
|
root.memPercent = Math.round((usageKb / memTotal) * 100);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Calculate CPU usage from /proc/stat
|
|
function calculateCpuUsage(text) {
|
|
if (!text)
|
|
return;
|
|
const lines = text.split('\n');
|
|
const cpuLine = lines[0];
|
|
|
|
// First line is total CPU
|
|
if (!cpuLine.startsWith('cpu '))
|
|
return;
|
|
const parts = cpuLine.split(/\s+/);
|
|
const stats = {
|
|
"user": parseInt(parts[1]) || 0,
|
|
"nice": parseInt(parts[2]) || 0,
|
|
"system": parseInt(parts[3]) || 0,
|
|
"idle": parseInt(parts[4]) || 0,
|
|
"iowait": parseInt(parts[5]) || 0,
|
|
"irq": parseInt(parts[6]) || 0,
|
|
"softirq": parseInt(parts[7]) || 0,
|
|
"steal": parseInt(parts[8]) || 0,
|
|
"guest": parseInt(parts[9]) || 0,
|
|
"guestNice": parseInt(parts[10]) || 0
|
|
};
|
|
const totalIdle = stats.idle + stats.iowait;
|
|
const total = Object.values(stats).reduce((sum, val) => sum + val, 0);
|
|
|
|
if (root.prevCpuStats) {
|
|
const prevTotalIdle = root.prevCpuStats.idle + root.prevCpuStats.iowait;
|
|
const prevTotal = Object.values(root.prevCpuStats).reduce((sum, val) => sum + val, 0);
|
|
|
|
const diffTotal = total - prevTotal;
|
|
const diffIdle = totalIdle - prevTotalIdle;
|
|
|
|
if (diffTotal > 0) {
|
|
root.cpuUsage = (((diffTotal - diffIdle) / diffTotal) * 100).toFixed(1);
|
|
}
|
|
}
|
|
|
|
root.prevCpuStats = stats;
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Calculate RX and TX speed from /proc/net/dev
|
|
// Average speed of all interfaces excepted 'lo'
|
|
function calculateNetworkSpeed(text) {
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
const currentTime = Date.now() / 1000;
|
|
const lines = text.split('\n');
|
|
|
|
let totalRx = 0;
|
|
let totalTx = 0;
|
|
|
|
for (var i = 2; i < lines.length; i++) {
|
|
const line = lines[i].trim();
|
|
if (!line) {
|
|
continue;
|
|
}
|
|
|
|
const colonIndex = line.indexOf(':');
|
|
if (colonIndex === -1) {
|
|
continue;
|
|
}
|
|
|
|
const iface = line.substring(0, colonIndex).trim();
|
|
if (iface === 'lo') {
|
|
continue;
|
|
}
|
|
|
|
const statsLine = line.substring(colonIndex + 1).trim();
|
|
const stats = statsLine.split(/\s+/);
|
|
|
|
const rxBytes = parseInt(stats[0], 10) || 0;
|
|
const txBytes = parseInt(stats[8], 10) || 0;
|
|
|
|
totalRx += rxBytes;
|
|
totalTx += txBytes;
|
|
}
|
|
|
|
// Compute only if we have a previous run to compare to.
|
|
if (root.prevTime > 0) {
|
|
const timeDiff = currentTime - root.prevTime;
|
|
|
|
// Avoid division by zero if time hasn't passed.
|
|
if (timeDiff > 0) {
|
|
let rxDiff = totalRx - root.prevRxBytes;
|
|
let txDiff = totalTx - root.prevTxBytes;
|
|
|
|
// Handle counter resets (e.g., WiFi reconnect), which would cause a negative value.
|
|
if (rxDiff < 0) {
|
|
rxDiff = 0;
|
|
}
|
|
if (txDiff < 0) {
|
|
txDiff = 0;
|
|
}
|
|
|
|
root.rxSpeed = Math.round(rxDiff / timeDiff); // Speed in Bytes/s
|
|
root.txSpeed = Math.round(txDiff / timeDiff);
|
|
}
|
|
}
|
|
|
|
root.prevRxBytes = totalRx;
|
|
root.prevTxBytes = totalTx;
|
|
root.prevTime = currentTime;
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Helper function to format network speeds
|
|
function formatSpeed(bytesPerSecond) {
|
|
if (bytesPerSecond < 1024 * 1024) {
|
|
const kb = bytesPerSecond / 1024;
|
|
if (kb < 10) {
|
|
return kb.toFixed(1) + "KB";
|
|
} else {
|
|
return Math.round(kb) + "KB";
|
|
}
|
|
} else if (bytesPerSecond < 1024 * 1024 * 1024) {
|
|
return (bytesPerSecond / (1024 * 1024)).toFixed(1) + "MB";
|
|
} else {
|
|
return (bytesPerSecond / (1024 * 1024 * 1024)).toFixed(1) + "GB";
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Compact speed formatter for vertical bar display
|
|
function formatCompactSpeed(bytesPerSecond) {
|
|
if (!bytesPerSecond || bytesPerSecond <= 0)
|
|
return "0";
|
|
const units = ["", "K", "M", "G"];
|
|
let value = bytesPerSecond;
|
|
let unitIndex = 0;
|
|
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
value = value / 1024.0;
|
|
unitIndex++;
|
|
}
|
|
// Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded)
|
|
if (unitIndex < units.length - 1 && value >= 100) {
|
|
value = value / 1024.0;
|
|
unitIndex++;
|
|
}
|
|
const display = Math.round(value).toString();
|
|
return display + units[unitIndex];
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Function to start fetching and computing the cpu temperature
|
|
function updateCpuTemperature() {
|
|
// For AMD sensors (k10temp and zenpower), only use Tctl sensor
|
|
// temp1_input corresponds to Tctl (Temperature Control) on these sensors
|
|
if (root.cpuTempSensorName === "k10temp" || root.cpuTempSensorName === "zenpower") {
|
|
cpuTempReader.path = `${root.cpuTempHwmonPath}/temp1_input`;
|
|
cpuTempReader.reload();
|
|
} // For Intel coretemp, start averaging all available sensors/cores
|
|
else if (root.cpuTempSensorName === "coretemp") {
|
|
root.intelTempValues = [];
|
|
root.intelTempFilesChecked = 0;
|
|
checkNextIntelTemp();
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Function to check next Intel temperature sensor
|
|
function checkNextIntelTemp() {
|
|
if (root.intelTempFilesChecked >= root.intelTempMaxFiles) {
|
|
// Calculate average of all found temperatures
|
|
if (root.intelTempValues.length > 0) {
|
|
let sum = 0;
|
|
for (var i = 0; i < root.intelTempValues.length; i++) {
|
|
sum += root.intelTempValues[i];
|
|
}
|
|
root.cpuTemp = Math.round(sum / root.intelTempValues.length);
|
|
//Logger.i("SystemStat", `Averaged ${root.intelTempValues.length} CPU thermal sensors: ${root.cpuTemp}°C`)
|
|
} else {
|
|
Logger.w("SystemStat", "No temperature sensors found for coretemp");
|
|
root.cpuTemp = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check next temperature file
|
|
root.intelTempFilesChecked++;
|
|
cpuTempReader.path = `${root.cpuTempHwmonPath}/temp${root.intelTempFilesChecked}_input`;
|
|
cpuTempReader.reload();
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Function to check VRAM for each AMD GPU to determine if it's a dGPU
|
|
function checkNextGpuVram() {
|
|
// Skip non-AMD GPUs (NVIDIA and Intel Arc are always discrete)
|
|
while (root.gpuVramCheckIndex < root.foundGpuSensors.length) {
|
|
const gpu = root.foundGpuSensors[root.gpuVramCheckIndex];
|
|
if (gpu.type === "amd") {
|
|
// Check for dedicated VRAM at hwmonPath/device/mem_info_vram_total
|
|
gpuVramChecker.path = `${gpu.hwmonPath}/device/mem_info_vram_total`;
|
|
gpuVramChecker.reload();
|
|
return;
|
|
}
|
|
// Skip non-AMD GPUs
|
|
root.gpuVramCheckIndex++;
|
|
}
|
|
|
|
// All VRAM checks complete, now select the best GPU
|
|
selectBestGpu();
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Function to select the best GPU based on priority
|
|
// Priority: NVIDIA > AMD dGPU > Intel Arc > AMD iGPU
|
|
function selectBestGpu() {
|
|
if (root.foundGpuSensors.length === 0) {
|
|
Logger.d("SystemStat", "No GPU temperature sensor found");
|
|
return;
|
|
}
|
|
|
|
let best = null;
|
|
|
|
for (var i = 0; i < root.foundGpuSensors.length; i++) {
|
|
const gpu = root.foundGpuSensors[i];
|
|
|
|
// NVIDIA is always highest priority (always discrete)
|
|
if (gpu.type === "nvidia") {
|
|
best = gpu;
|
|
break;
|
|
}
|
|
|
|
// AMD dGPU is second priority
|
|
if (gpu.type === "amd" && gpu.hasDedicatedVram) {
|
|
best = gpu;
|
|
break;
|
|
}
|
|
|
|
// Intel Arc is third priority (always discrete)
|
|
if (gpu.type === "intel" && !best) {
|
|
best = gpu;
|
|
}
|
|
|
|
// AMD iGPU is lowest priority (fallback)
|
|
if (gpu.type === "amd" && !gpu.hasDedicatedVram && !best) {
|
|
best = gpu;
|
|
}
|
|
}
|
|
|
|
if (best) {
|
|
root.gpuTempHwmonPath = best.hwmonPath;
|
|
root.gpuType = best.type;
|
|
root.gpuAvailable = true;
|
|
|
|
const gpuDesc = best.type === "nvidia" ? "NVIDIA" : (best.type === "intel" ? "Intel Arc" : (best.hasDedicatedVram ? "AMD dGPU" : "AMD iGPU"));
|
|
Logger.i("SystemStat", `Selected ${gpuDesc} for temperature monitoring at ${best.hwmonPath || "nvidia-smi"}`);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Function to update GPU temperature
|
|
function updateGpuTemperature() {
|
|
if (root.gpuType === "nvidia") {
|
|
nvidiaTempProcess.running = true;
|
|
} else if (root.gpuType === "amd" || root.gpuType === "intel") {
|
|
gpuTempReader.path = `${root.gpuTempHwmonPath}/temp1_input`;
|
|
gpuTempReader.reload();
|
|
}
|
|
}
|
|
}
|