more system monitoring optimizations, trying for 1 update/sec on windows too

This commit is contained in:
Egor 2025-09-16 16:21:21 -07:00
parent ecff5ca4ae
commit a99f93ce79
5 changed files with 437 additions and 207 deletions

View file

@ -39,14 +39,14 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.35.0", "@eslint/js": "^9.35.0",
"@types/node": "^24.5.0", "@types/node": "^24.5.1",
"@types/react": "^19.1.13", "@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^19.1.9",
"@typescript-eslint/eslint-plugin": "^8.44.0", "@typescript-eslint/eslint-plugin": "^8.44.0",
"@typescript-eslint/parser": "^8.44.0", "@typescript-eslint/parser": "^8.44.0",
"@vitejs/plugin-react": "^5.0.2", "@vitejs/plugin-react": "^5.0.2",
"cross-env": "^10.0.0", "cross-env": "^10.0.0",
"electron": "^38.1.0", "electron": "^38.1.1",
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
"electron-vite": "^4.0.0", "electron-vite": "^4.0.0",
"eslint": "^9.35.0", "eslint": "^9.35.0",
@ -74,7 +74,7 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-error-boundary": "^6.0.0", "react-error-boundary": "^6.0.0",
"systeminformation": "^5.27.9", "systeminformation": "^5.27.10",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"yauzl": "^3.2.0", "yauzl": "^3.2.0",

View file

@ -65,14 +65,14 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
<> <>
<PerformanceBadge <PerformanceBadge
label="CPU" label="CPU"
value={`${cpuMetrics.usage.toFixed(1)}%`} value={`${cpuMetrics.usage}%`}
tooltipLabel={`${cpuMetrics.usage.toFixed(1)}%`} tooltipLabel={`${cpuMetrics.usage}%`}
/> />
<PerformanceBadge <PerformanceBadge
label="RAM" label="RAM"
value={`${memoryMetrics.usage.toFixed(1)}%`} value={`${memoryMetrics.usage}%`}
tooltipLabel={`${memoryMetrics.used.toFixed(1)} GB / ${memoryMetrics.total.toFixed(1)} GB (${memoryMetrics.usage.toFixed(1)}%)`} tooltipLabel={`${memoryMetrics.used.toFixed(2)} GB / ${memoryMetrics.total.toFixed(2)} GB (${memoryMetrics.usage}%)`}
/> />
</> </>
)} )}
@ -81,14 +81,14 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
<Group gap="xs" key={`gpu-${index}`}> <Group gap="xs" key={`gpu-${index}`}>
<PerformanceBadge <PerformanceBadge
label={`GPU${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`} label={`GPU${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`}
value={`${gpu.usage.toFixed(1)}%`} value={`${gpu.usage}%`}
tooltipLabel={`${gpu.usage.toFixed(1)}%`} tooltipLabel={`${gpu.usage}%`}
/> />
<PerformanceBadge <PerformanceBadge
label={`VRAM${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`} label={`VRAM${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`}
value={`${gpu.memoryUsage.toFixed(1)}%`} value={`${gpu.memoryUsage}%`}
tooltipLabel={`${gpu.memoryUsed.toFixed(1)} GB / ${gpu.memoryTotal.toFixed(1)} GB (${gpu.memoryUsage.toFixed(1)}%)`} tooltipLabel={`${gpu.memoryUsed.toFixed(2)} GB / ${gpu.memoryTotal.toFixed(2)} GB (${gpu.memoryUsage}%)`}
/> />
</Group> </Group>
))} ))}

View file

@ -1,4 +1,3 @@
import { platform } from 'process';
import si from 'systeminformation'; import si from 'systeminformation';
import { BrowserWindow } from 'electron'; import { BrowserWindow } from 'electron';
import { logError } from '@/main/modules/logging'; import { logError } from '@/main/modules/logging';
@ -46,7 +45,7 @@ let cpuInterval: ReturnType<typeof setInterval> | null = null;
let memoryInterval: ReturnType<typeof setInterval> | null = null; let memoryInterval: ReturnType<typeof setInterval> | null = null;
let gpuInterval: ReturnType<typeof setInterval> | null = null; let gpuInterval: ReturnType<typeof setInterval> | null = null;
let isRunning = false; let isRunning = false;
const updateFrequency = platform === 'win32' ? 2000 : 1000; const updateFrequency = 1000;
let mainWindow: BrowserWindow | null = null; let mainWindow: BrowserWindow | null = null;
export function startMonitoring(window: BrowserWindow) { export function startMonitoring(window: BrowserWindow) {
@ -91,7 +90,7 @@ async function collectAndSendCpuMetrics() {
try { try {
const cpuData = await si.currentLoad(); const cpuData = await si.currentLoad();
const metrics: CpuMetrics = { const metrics: CpuMetrics = {
usage: cpuData.currentLoad, usage: Math.round(cpuData.currentLoad),
}; };
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@ -108,9 +107,9 @@ async function collectAndSendMemoryMetrics() {
const usedBytes = memData.active || memData.used; const usedBytes = memData.active || memData.used;
const totalBytes = memData.total; const totalBytes = memData.total;
const metrics: MemoryMetrics = { const metrics: MemoryMetrics = {
used: Math.round((usedBytes / (1024 * 1024 * 1024)) * 10) / 10, used: usedBytes / (1024 * 1024 * 1024),
total: Math.round((totalBytes / (1024 * 1024 * 1024)) * 10) / 10, total: totalBytes / (1024 * 1024 * 1024),
usage: (usedBytes / totalBytes) * 100, usage: Math.round((usedBytes / totalBytes) * 100),
}; };
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@ -127,12 +126,12 @@ async function collectAndSendGpuMetrics() {
const metrics: GpuMetrics = { const metrics: GpuMetrics = {
gpus: gpuData.map((gpuInfo) => ({ gpus: gpuData.map((gpuInfo) => ({
name: gpuInfo.deviceName, name: gpuInfo.deviceName,
usage: gpuInfo.usage, usage: Math.round(gpuInfo.usage),
memoryUsed: gpuInfo.memoryUsed, memoryUsed: gpuInfo.memoryUsed,
memoryTotal: gpuInfo.memoryTotal, memoryTotal: gpuInfo.memoryTotal,
memoryUsage: memoryUsage:
gpuInfo.memoryTotal > 0 gpuInfo.memoryTotal > 0
? (gpuInfo.memoryUsed / gpuInfo.memoryTotal) * 100 ? Math.round((gpuInfo.memoryUsed / gpuInfo.memoryTotal) * 100)
: 0, : 0,
})), })),
}; };

View file

@ -3,6 +3,31 @@ import { join } from 'path';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { platform } from 'process'; import { platform } from 'process';
interface CachedGPUInfo {
deviceName: string;
devicePath: string;
memoryTotal: number;
}
interface WindowsCachedGPUInfo {
deviceName: string;
memoryTotal: number;
luid: string;
}
interface GPUData {
deviceName: string;
usage: number;
memoryUsed: number;
memoryTotal: number;
}
let linuxGpuCache: CachedGPUInfo[] | null = null;
let windowsGpuCache: WindowsCachedGPUInfo[] | null = null;
let linuxCachePromise: Promise<CachedGPUInfo[]> | null = null;
let windowsCachePromise: Promise<WindowsCachedGPUInfo[]> | null = null;
export async function getGPUData() { export async function getGPUData() {
if (platform === 'win32') { if (platform === 'win32') {
return getWindowsGPUData(); return getWindowsGPUData();
@ -13,19 +38,20 @@ export async function getGPUData() {
} }
} }
async function getWindowsGPUData() { async function initializeWindowsGPUCache() {
return new Promise< if (windowsGpuCache !== null) {
{ return windowsGpuCache;
deviceName: string; }
usage: number;
memoryUsed: number; if (windowsCachePromise !== null) {
memoryTotal: number; return windowsCachePromise;
}[] }
>((resolve) => {
windowsCachePromise = new Promise((resolve) => {
const script = ` const script = `
# Get GPU basic info and total VRAM from registry
$gpus = Get-WmiObject -Class Win32_VideoController | Where-Object {$_.Name -notlike "*Microsoft*"} $gpus = Get-WmiObject -Class Win32_VideoController | Where-Object {$_.Name -notlike "*Microsoft*"}
$correctVRAM = @{} $correctVRAM = @{}
$gpuToLuid = @{}
try { try {
$regPath = "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}" $regPath = "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}"
@ -42,196 +68,342 @@ try {
} }
} catch {} } catch {}
# Get VRAM usage from GPU Adapter Memory counters (what Task Manager uses)
$vramUsageByLuid = @{}
try { try {
$adapterMemory = Get-Counter "\\GPU Adapter Memory(*)\\Dedicated Usage" -ErrorAction SilentlyContinue $adapterMemory = Get-Counter "\\GPU Adapter Memory(*)\\Dedicated Usage" -ErrorAction SilentlyContinue
if ($adapterMemory) { if ($adapterMemory) {
foreach ($sample in $adapterMemory.CounterSamples) { foreach ($sample in $adapterMemory.CounterSamples) {
$instanceName = $sample.InstanceName $instanceName = $sample.InstanceName
$usageBytes = $sample.CookedValue if ($instanceName -match "luid_(0x[0-9a-fA-F]+_0x[0-9a-fA-F]+)_([^_]+)") {
$vramUsageByLuid[$instanceName] = $usageBytes
}
}
} catch {}
# Get GPU utilization from performance counters
$gpuUtilization = @{}
try {
$engineCounters = Get-Counter "\\GPU Engine(*)\\Utilization Percentage" -ErrorAction SilentlyContinue
if ($engineCounters) {
$utilizationByLuid = @{}
foreach ($sample in $engineCounters.CounterSamples) {
$instanceName = $sample.InstanceName
$utilization = $sample.CookedValue
if ($instanceName -match "luid_(0x[0-9a-fA-F]+_0x[0-9a-fA-F]+)") {
$luid = $matches[1] $luid = $matches[1]
if (-not $utilizationByLuid[$luid]) { $gpuNameFromCounter = $matches[2]
$utilizationByLuid[$luid] = 0
} foreach ($gpu in $gpus) {
$utilizationByLuid[$luid] = [Math]::Max($utilizationByLuid[$luid], $utilization) $cleanGpuName = $gpu.Name -replace '[^a-zA-Z0-9]', ''
} $cleanCounterName = $gpuNameFromCounter -replace '[^a-zA-Z0-9]', ''
}
if ($cleanGpuName -eq $cleanCounterName -or $gpu.Name -like "*$gpuNameFromCounter*") {
# Find the main GPU LUID (the one with actual VRAM usage) $gpuToLuid[$gpu.Name] = $luid
$mainLuid = "" break
$maxVramUsage = 0 }
foreach ($luid in $vramUsageByLuid.Keys) {
if ($vramUsageByLuid[$luid] -gt $maxVramUsage) {
$maxVramUsage = $vramUsageByLuid[$luid]
$mainLuid = $luid
}
}
# Extract LUID from main adapter
if ($mainLuid -match "luid_(0x[0-9a-fA-F]+_0x[0-9a-fA-F]+)") {
$mainLuidKey = $matches[1]
foreach ($gpu in $gpus) {
if ($utilizationByLuid[$mainLuidKey]) {
$gpuUtilization[$gpu.Name] = $utilizationByLuid[$mainLuidKey]
} }
} }
} }
} }
} catch {} } catch {}
# Output results
foreach ($gpu in $gpus) { foreach ($gpu in $gpus) {
$totalVRAM = $correctVRAM[$gpu.Name] $totalVRAM = $correctVRAM[$gpu.Name]
if (-not $totalVRAM -or $totalVRAM -le 0) { if (-not $totalVRAM -or $totalVRAM -le 0) {
$totalVRAM = $gpu.AdapterRAM $totalVRAM = $gpu.AdapterRAM
} }
# Get VRAM usage for the main GPU (highest usage LUID) $luid = $gpuToLuid[$gpu.Name]
$usedVRAM = 0 if ($luid) {
$maxUsage = 0 Write-Output "$($gpu.Name)|$totalVRAM|$luid"
foreach ($luid in $vramUsageByLuid.Keys) {
$usage = $vramUsageByLuid[$luid]
if ($usage -gt $maxUsage) {
$maxUsage = $usage
$usedVRAM = $usage
}
} }
$utilization = 0
if ($gpuUtilization[$gpu.Name]) {
$utilization = $gpuUtilization[$gpu.Name]
}
Write-Output "$($gpu.Name)|$totalVRAM|$usedVRAM|$utilization"
} }
`; `;
const powershell = spawn( let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
'powershell.exe', let powershellProcess: ReturnType<typeof spawn> | null = null;
[
'-NoProfile', const cleanup = () => {
'-NonInteractive', if (timeoutHandle) {
'-ExecutionPolicy', clearTimeout(timeoutHandle);
'Bypass', timeoutHandle = null;
'-Command',
script,
],
{
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
shell: false,
} }
); if (powershellProcess && !powershellProcess.killed) {
powershellProcess.kill('SIGTERM');
}
};
let output = ''; try {
powershellProcess = spawn(
'powershell.exe',
[
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-Command',
script,
],
{
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
shell: false,
}
);
powershell.stdout.on('data', (data) => { let output = '';
output += data.toString();
});
powershell.on('close', (code) => { if (powershellProcess.stdout) {
const gpus: { powershellProcess.stdout.on('data', (data) => {
deviceName: string; output += data.toString();
usage: number; });
memoryUsed: number; }
memoryTotal: number;
}[] = [];
if (code === 0 && output.trim()) { powershellProcess.on('close', (code) => {
const lines = output.trim().split('\n'); cleanup();
for (const line of lines) { const gpus = [];
const parts = line.split('|');
if (parts.length === 4 && parts[0]) {
const name = parts[0].trim();
const totalRAM = parseInt(parts[1]) || 0;
const usedRAM = parseInt(parts[2]) || 0;
const utilization = parseFloat(parts[3]) || 0;
gpus.push({ if (code === 0 && output.trim()) {
deviceName: name, const lines = output.trim().split('\n');
usage: Math.round(utilization * 100) / 100, for (const line of lines) {
memoryUsed: usedRAM > 0 ? usedRAM / (1024 * 1024 * 1024) : 0, const parts = line.split('|');
memoryTotal: totalRAM > 0 ? totalRAM / (1024 * 1024 * 1024) : 0, if (parts.length === 3 && parts[0]) {
}); const name = parts[0].trim();
const totalRAM = parseInt(parts[1]) || 0;
const luid = parts[2].trim();
if (name && luid && totalRAM >= 0) {
gpus.push({
deviceName: name,
memoryTotal:
totalRAM > 0 ? totalRAM / (1024 * 1024 * 1024) : 0,
luid,
});
}
}
} }
} }
}
resolve(gpus); windowsGpuCache = gpus;
}); windowsCachePromise = null;
resolve(gpus);
});
powershell.on('error', () => resolve([])); powershellProcess.on('error', () => {
cleanup();
windowsGpuCache = [];
windowsCachePromise = null;
resolve([]);
});
setTimeout(() => { timeoutHandle = setTimeout(() => {
powershell.kill('SIGTERM'); cleanup();
windowsGpuCache = [];
windowsCachePromise = null;
resolve([]);
}, 8000);
} catch {
cleanup();
windowsGpuCache = [];
windowsCachePromise = null;
resolve([]); resolve([]);
}, 5000); }
}); });
return windowsCachePromise;
} }
async function getLinuxGPUData() { async function getWindowsGPUData() {
try { try {
const drmPath = '/sys/class/drm'; const cachedGPUs = await initializeWindowsGPUCache();
const entries = await readdir(drmPath);
const cardEntries = entries.filter(
(entry) => entry.startsWith('card') && !entry.includes('-')
);
const gpus = []; return new Promise<GPUData[]>((resolve) => {
for (const card of cardEntries) { const script = `
const devicePath = join(drmPath, card, 'device'); $vramUsageByLuid = @{}
$utilizationByLuid = @{}
$job1 = $null
$job2 = $null
let deviceName = 'Unknown GPU'; try {
$job1 = Start-Job -ScriptBlock {
try {
$adapterMemory = Get-Counter "\\GPU Adapter Memory(*)\\Dedicated Usage" -ErrorAction SilentlyContinue
if ($adapterMemory) {
$result = @{}
foreach ($sample in $adapterMemory.CounterSamples) {
$instanceName = $sample.InstanceName
$usageBytes = $sample.CookedValue
if ($instanceName -match "luid_(0x[0-9a-fA-F]+_0x[0-9a-fA-F]+)") {
$luid = $matches[1]
$result[$luid] = $usageBytes
}
}
return $result
}
} catch {}
return @{}
}
$job2 = Start-Job -ScriptBlock {
try {
$engineCounters = Get-Counter "\\GPU Engine(*)\\Utilization Percentage" -ErrorAction SilentlyContinue
if ($engineCounters) {
$result = @{}
foreach ($sample in $engineCounters.CounterSamples) {
$instanceName = $sample.InstanceName
$utilization = $sample.CookedValue
if ($instanceName -match "luid_(0x[0-9a-fA-F]+_0x[0-9a-fA-F]+)") {
$luid = $matches[1]
if (-not $result[$luid]) {
$result[$luid] = 0
}
$result[$luid] = [Math]::Max($result[$luid], $utilization)
}
}
return $result
}
} catch {}
return @{}
}
if ($job1) { $vramUsageByLuid = Receive-Job $job1 -Wait }
if ($job2) { $utilizationByLuid = Receive-Job $job2 -Wait }
} catch {}
finally {
if ($job1) { Remove-Job $job1 -Force -ErrorAction SilentlyContinue }
if ($job2) { Remove-Job $job2 -Force -ErrorAction SilentlyContinue }
}
foreach ($luid in $vramUsageByLuid.Keys) {
$vramUsed = $vramUsageByLuid[$luid]
$utilization = 0
if ($utilizationByLuid[$luid]) {
$utilization = $utilizationByLuid[$luid]
}
Write-Output "$luid|$vramUsed|$utilization"
}
`;
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
let powershellProcess: ReturnType<typeof spawn> | null = null;
const cleanup = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
timeoutHandle = null;
}
if (powershellProcess && !powershellProcess.killed) {
powershellProcess.kill('SIGTERM');
}
};
try { try {
const deviceNameData = await readFile(`${devicePath}/device`, 'utf8'); powershellProcess = spawn(
const deviceId = deviceNameData.trim(); 'powershell.exe',
[
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-Command',
script,
],
{
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
shell: false,
}
);
const vendorData = await readFile(`${devicePath}/vendor`, 'utf8'); let output = '';
const vendorId = vendorData.trim();
const modalias = await readFile(`${devicePath}/modalias`, 'utf8'); if (powershellProcess.stdout) {
powershellProcess.stdout.on('data', (data) => {
if (vendorId === '0x1002') { output += data.toString();
deviceName = 'AMD GPU'; });
} else if (vendorId === '0x10de') {
deviceName = 'NVIDIA GPU';
} else if (vendorId === '0x8086') {
deviceName = 'Intel GPU';
} else {
deviceName = `GPU (${vendorId}:${deviceId})`;
} }
if (modalias.includes('i915')) { powershellProcess.on('close', (code) => {
deviceName = 'Intel GPU'; cleanup();
} else if (modalias.includes('amdgpu')) { const gpus: GPUData[] = [];
deviceName = 'AMD GPU';
} else if ( if (code === 0 && output.trim() && cachedGPUs.length > 0) {
modalias.includes('nouveau') || const luidToData = new Map<
modalias.includes('nvidia') string,
) { { vramUsed: number; utilization: number }
deviceName = 'NVIDIA GPU'; >();
}
const lines = output.trim().split('\n');
for (const line of lines) {
const parts = line.split('|');
if (parts.length === 3) {
const luid = parts[0].trim();
const vramUsed = Math.max(0, parseInt(parts[1]) || 0);
const utilization = Math.max(
0,
Math.min(100, parseFloat(parts[2]) || 0)
);
luidToData.set(luid, { vramUsed, utilization });
}
}
for (const cachedGPU of cachedGPUs) {
const gpuData = luidToData.get(cachedGPU.luid);
if (gpuData) {
gpus.push({
deviceName: cachedGPU.deviceName,
usage: gpuData.utilization,
memoryUsed:
gpuData.vramUsed > 0
? parseFloat(
(gpuData.vramUsed / (1024 * 1024 * 1024)).toFixed(2)
)
: 0,
memoryTotal: parseFloat(
Math.max(0, cachedGPU.memoryTotal).toFixed(2)
),
});
}
}
}
resolve(gpus);
});
powershellProcess.on('error', () => {
cleanup();
resolve([]);
});
timeoutHandle = setTimeout(() => {
cleanup();
resolve([]);
}, 8000);
} catch { } catch {
cleanup();
resolve([]);
}
});
} catch {
return [];
}
}
async function initializeLinuxGPUCache() {
if (linuxGpuCache !== null) {
return linuxGpuCache;
}
if (linuxCachePromise !== null) {
return linuxCachePromise;
}
linuxCachePromise = (async () => {
try {
const drmPath = '/sys/class/drm';
const entries = await readdir(drmPath);
const cardEntries = entries.filter(
(entry) => entry.startsWith('card') && !entry.includes('-')
);
const gpus = [];
for (const card of cardEntries) {
const devicePath = join(drmPath, card, 'device');
let deviceName = 'Unknown GPU';
try { try {
const modalias = await readFile(`${devicePath}/modalias`, 'utf8'); const [modalias] = await Promise.all([
readFile(`${devicePath}/modalias`, 'utf8').catch(() => ''),
]);
if (modalias.includes('i915')) { if (modalias.includes('i915')) {
deviceName = 'Intel GPU'; deviceName = 'Intel GPU';
} else if (modalias.includes('amdgpu')) { } else if (modalias.includes('amdgpu')) {
@ -241,37 +413,96 @@ async function getLinuxGPUData() {
modalias.includes('nvidia') modalias.includes('nvidia')
) { ) {
deviceName = 'NVIDIA GPU'; deviceName = 'NVIDIA GPU';
} else {
try {
const [vendorData, deviceData] = await Promise.all([
readFile(`${devicePath}/vendor`, 'utf8').catch(() => ''),
readFile(`${devicePath}/device`, 'utf8').catch(() => ''),
]);
const vendorId = vendorData.trim();
const deviceId = deviceData.trim();
if (vendorId === '0x1002') {
deviceName = 'AMD GPU';
} else if (vendorId === '0x10de') {
deviceName = 'NVIDIA GPU';
} else if (vendorId === '0x8086') {
deviceName = 'Intel GPU';
} else {
deviceName = `GPU (${vendorId}:${deviceId})`;
}
} catch {
void 0;
}
} }
} catch { } catch {
void 0; void 0;
} }
try {
const memTotalData = await readFile(
`${devicePath}/mem_info_vram_total`,
'utf8'
);
const memoryTotal = Math.max(
0,
(parseInt(memTotalData.trim(), 10) || 0) / (1024 * 1024 * 1024)
);
if (memoryTotal > 0) {
gpus.push({
deviceName,
devicePath,
memoryTotal,
});
}
} catch {
continue;
}
} }
try { linuxGpuCache = gpus;
const usageData = await readFile( linuxCachePromise = null;
`${devicePath}/gpu_busy_percent`, return gpus;
'utf8' } catch {
); linuxGpuCache = [];
const memUsedData = await readFile( linuxCachePromise = null;
`${devicePath}/mem_info_vram_used`, return [];
'utf8' }
); })();
const memTotalData = await readFile(
`${devicePath}/mem_info_vram_total`,
'utf8'
);
const usage = parseInt(usageData.trim(), 10) || 0; return linuxCachePromise;
const memoryUsed = }
(parseInt(memUsedData.trim(), 10) || 0) / (1024 * 1024 * 1024);
const memoryTotal = async function getLinuxGPUData() {
(parseInt(memTotalData.trim(), 10) || 0) / (1024 * 1024 * 1024); try {
const cachedGPUs = await initializeLinuxGPUCache();
const gpus: GPUData[] = [];
for (const cachedGPU of cachedGPUs) {
try {
const [usageData, memUsedData] = await Promise.all([
readFile(`${cachedGPU.devicePath}/gpu_busy_percent`, 'utf8'),
readFile(`${cachedGPU.devicePath}/mem_info_vram_used`, 'utf8'),
]);
const usage = Math.max(
0,
Math.min(100, parseInt(usageData.trim(), 10) || 0)
);
const memoryUsed = Math.max(
0,
(parseInt(memUsedData.trim(), 10) || 0) / (1024 * 1024 * 1024)
);
gpus.push({ gpus.push({
deviceName, deviceName: cachedGPU.deviceName,
usage, usage,
memoryUsed, memoryUsed: parseFloat(memoryUsed.toFixed(2)),
memoryTotal, memoryTotal: parseFloat(
Math.max(0, cachedGPU.memoryTotal).toFixed(2)
),
}); });
} catch { } catch {
continue; continue;

View file

@ -1287,12 +1287,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:*, @types/node@npm:^24.5.0": "@types/node@npm:*, @types/node@npm:^24.5.1":
version: 24.5.0 version: 24.5.1
resolution: "@types/node@npm:24.5.0" resolution: "@types/node@npm:24.5.1"
dependencies: dependencies:
undici-types: "npm:~7.12.0" undici-types: "npm:~7.12.0"
checksum: 10c0/c5beff68481e2cc667279a1478b34a1cfd048dbff914219cb5888967938d134907836b6c4d6d141dc862489cb09ef28f7d446c7a3b475181fd126c0fcd2916fa checksum: 10c0/5f0cb038be789b58170e616452ba1f8ebb85bf2fbce58a7e32b1eb08391f64f5e31a9cdbccefbfcd9e6d73b66b564b5e037a1d678ab20213559a32e1d7b6ce17
languageName: node languageName: node
linkType: hard linkType: hard
@ -2705,16 +2705,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"electron@npm:^38.1.0": "electron@npm:^38.1.1":
version: 38.1.0 version: 38.1.1
resolution: "electron@npm:38.1.0" resolution: "electron@npm:38.1.1"
dependencies: dependencies:
"@electron/get": "npm:^2.0.0" "@electron/get": "npm:^2.0.0"
"@types/node": "npm:^22.7.7" "@types/node": "npm:^22.7.7"
extract-zip: "npm:^2.0.1" extract-zip: "npm:^2.0.1"
bin: bin:
electron: cli.js electron: cli.js
checksum: 10c0/186082b15862b0e9617828ca395a79311ebe2a713bb2eb15a67cf6bea208e8cda892395a143e72915e189088ac71e91b29c1a46a0c4d343022cbc33004d4d302 checksum: 10c0/0b1e5955679de6b9a9a12fa7a51beaaa5dcf15655888670c39e3a49914f8dba580a1f94b46d83e7f8e5dc7fd22c16b130dd5040208d7a86431cf06e16933d9e8
languageName: node languageName: node
linkType: hard linkType: hard
@ -3648,7 +3648,7 @@ __metadata:
"@fontsource/inter": "npm:^5.2.7" "@fontsource/inter": "npm:^5.2.7"
"@mantine/core": "npm:^8.3.1" "@mantine/core": "npm:^8.3.1"
"@mantine/hooks": "npm:^8.3.1" "@mantine/hooks": "npm:^8.3.1"
"@types/node": "npm:^24.5.0" "@types/node": "npm:^24.5.1"
"@types/react": "npm:^19.1.13" "@types/react": "npm:^19.1.13"
"@types/react-dom": "npm:^19.1.9" "@types/react-dom": "npm:^19.1.9"
"@types/yauzl": "npm:^2.10.3" "@types/yauzl": "npm:^2.10.3"
@ -3657,7 +3657,7 @@ __metadata:
"@vitejs/plugin-react": "npm:^5.0.2" "@vitejs/plugin-react": "npm:^5.0.2"
axios: "npm:^1.12.2" axios: "npm:^1.12.2"
cross-env: "npm:^10.0.0" cross-env: "npm:^10.0.0"
electron: "npm:^38.1.0" electron: "npm:^38.1.1"
electron-builder: "npm:^26.0.12" electron-builder: "npm:^26.0.12"
electron-vite: "npm:^4.0.0" electron-vite: "npm:^4.0.0"
eslint: "npm:^9.35.0" eslint: "npm:^9.35.0"
@ -3676,7 +3676,7 @@ __metadata:
react-dom: "npm:^19.1.1" react-dom: "npm:^19.1.1"
react-error-boundary: "npm:^6.0.0" react-error-boundary: "npm:^6.0.0"
rollup-plugin-visualizer: "npm:^6.0.3" rollup-plugin-visualizer: "npm:^6.0.3"
systeminformation: "npm:^5.27.9" systeminformation: "npm:^5.27.10"
typescript: "npm:^5.9.2" typescript: "npm:^5.9.2"
vite: "npm:^7.1.5" vite: "npm:^7.1.5"
winston: "npm:^3.17.0" winston: "npm:^3.17.0"
@ -6694,12 +6694,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"systeminformation@npm:^5.27.9": "systeminformation@npm:^5.27.10":
version: 5.27.9 version: 5.27.10
resolution: "systeminformation@npm:5.27.9" resolution: "systeminformation@npm:5.27.10"
bin: bin:
systeminformation: lib/cli.js systeminformation: lib/cli.js
checksum: 10c0/d65aeb68e40d432fb1ec6e1723fa147959cbeb1b43a25c2014b768f0ee099266da6370b3f1593a6a7ca77643f58c9b754d470d225a7afd466d1108c846a67a3c checksum: 10c0/aaaafb3d5738150e4d63908b4516f712810ed9047dc69ba061eacb1c2aa2824bfc352a1dff596e9d81db8a75c2f27da1a3e5daaa88fffb658a3f5a68288a1d02
conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android) conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android)
languageName: node languageName: node
linkType: hard linkType: hard