fix: reduce Windows hardware detection hangs

- Replace siCpu() with synchronous os.cpus() — no PowerShell needed
- Replace siMem() with os.totalmem() in detectSystemMemory
- Add 3s timeout to siMemLayout() and siGraphics() so they don't
  block indefinitely behind monitoring's PowerShell queue
- Slow memory polling to 3s on Windows (was 1s) to free up the
  shared PowerShell session for hardware detection queries
- Split SystemTab loading: render version info immediately,
  hardware info fills in as background tasks complete
This commit is contained in:
Egor 2026-05-05 16:38:54 -07:00
parent 350c607d7c
commit 0aa967be9e
4 changed files with 37 additions and 42 deletions

View file

@ -24,7 +24,13 @@ export const SystemTab = () => {
setVersionInfo(info); setVersionInfo(info);
setKoboldVersion(currentBackend?.version ?? null); setKoboldVersion(currentBackend?.version ?? null);
} catch (error) {
window.electronAPI.logs.logError('Failed to load system info', error as Error);
} finally {
setLoading(false);
}
try {
const [cpu, gpu, gpuCapabilities, gpuMemory, systemMemory] = await Promise.all([ const [cpu, gpu, gpuCapabilities, gpuMemory, systemMemory] = await Promise.all([
window.electronAPI.kobold.detectCPU(), window.electronAPI.kobold.detectCPU(),
window.electronAPI.kobold.detectGPU(), window.electronAPI.kobold.detectGPU(),
@ -41,9 +47,7 @@ export const SystemTab = () => {
systemMemory, systemMemory,
}); });
} catch (error) { } catch (error) {
window.electronAPI.logs.logError('Failed to load system info', error as Error); window.electronAPI.logs.logError('Failed to load hardware info', error as Error);
} finally {
setLoading(false);
} }
}; };

View file

@ -1,7 +1,8 @@
import { cpus as osCpus, totalmem } from 'node:os';
import { platform } from 'node:process'; import { platform } from 'node:process';
import { execa } from 'execa'; import { execa } from 'execa';
import { cpu as siCpu, mem as siMem, memLayout as siMemLayout } from 'systeminformation'; import { memLayout as siMemLayout } from 'systeminformation';
import type { import type {
BasicGPUInfo, BasicGPUInfo,
@ -33,36 +34,26 @@ let basicGPUInfoCache: BasicGPUInfo | null = null;
let gpuCapabilitiesCache: GPUCapabilities | null = null; let gpuCapabilitiesCache: GPUCapabilities | null = null;
let gpuMemoryInfoCache: GPUMemoryInfo[] | null = null; let gpuMemoryInfoCache: GPUMemoryInfo[] | null = null;
export async function detectCPU() { export function detectCPU() {
if (cpuCapabilitiesCache) { if (cpuCapabilitiesCache) {
return cpuCapabilitiesCache; return cpuCapabilitiesCache;
} }
const result = await safeExecute(async () => { const cpuList = osCpus();
const cpu = await siCpu(); const brand = cpuList[0]?.model;
const cores = cpuList.length;
const speed = (cpuList[0]?.speed ?? 0) / 1000;
const devices: { name: string; detailedName: string }[] = []; const devices: { name: string; detailedName: string }[] = [];
if (cpu.brand) { if (brand) {
const name = formatDeviceName(cpu.brand); const name = formatDeviceName(brand);
devices.push({
devices.push({ detailedName: `${name} (${cores} cores) @ ${speed} GHz`,
detailedName: `${name} (${cpu.cores} cores) @ ${cpu.speed} GHz`, name,
name, });
}); }
}
const capabilities = {
devices,
};
cpuCapabilitiesCache = capabilities;
return capabilities;
}, 'CPU detection failed');
cpuCapabilitiesCache = result ?? {
devices: [],
};
cpuCapabilitiesCache = { devices };
return cpuCapabilitiesCache; return cpuCapabilitiesCache;
} }
@ -370,10 +361,13 @@ export async function detectGPUMemory() {
} }
export const detectSystemMemory = async () => { export const detectSystemMemory = async () => {
try { const totalGB = (totalmem() / 1024 ** 3).toFixed(2);
const [memInfo, memLayout] = await Promise.all([siMem(), siMemLayout()]);
const totalGB = (memInfo.total / 1024 ** 3).toFixed(2); try {
const memLayout = await Promise.race([
siMemLayout(),
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
]);
let speed: number | undefined; let speed: number | undefined;
let type: string | undefined; let type: string | undefined;
@ -394,15 +388,8 @@ export const detectSystemMemory = async () => {
} }
} }
return { return { speed, totalGB, type };
speed,
totalGB,
type,
};
} catch { } catch {
const mem = await siMem(); return { totalGB };
return {
totalGB: (mem.total / 1024 ** 3).toFixed(2),
};
} }
}; };

View file

@ -61,6 +61,7 @@ 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 = 1000; const updateFrequency = 1000;
const memoryUpdateFrequency = platform === 'win32' ? 3000 : updateFrequency;
let mainWindow: BrowserWindow | null = null; let mainWindow: BrowserWindow | null = null;
let latestCpuMetrics: CpuMetrics | null = null; let latestCpuMetrics: CpuMetrics | null = null;
@ -87,7 +88,7 @@ export function startMonitoring(window: BrowserWindow) {
void collectAndSendMemoryMetrics(); void collectAndSendMemoryMetrics();
memoryInterval = setInterval(() => { memoryInterval = setInterval(() => {
void collectAndSendMemoryMetrics(); void collectAndSendMemoryMetrics();
}, updateFrequency); }, memoryUpdateFrequency);
if (platform === 'linux') { if (platform === 'linux') {
void collectAndSendGpuMetrics(); void collectAndSendGpuMetrics();

View file

@ -151,7 +151,10 @@ async function getLinuxGPUData() {
async function getWindowsGPUData() { async function getWindowsGPUData() {
try { try {
const graphics = await siGraphics(); const graphics = await Promise.race([
siGraphics(),
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
]);
const discreteControllers = graphics.controllers.filter( const discreteControllers = graphics.controllers.filter(
(controller) => controller.vram && controller.vram >= 1024, (controller) => controller.vram && controller.vram >= 1024,