import type { Client } from 'ssh2' import { execCommand, toFixedNum } from './common.js' function parseCpuLine(cpuLine: string): { total: number; idle: number } | undefined { const parts = cpuLine.trim().split(/\s+/).slice(1).map(Number) if (parts.length < 4 || parts.some((n) => !Number.isFinite(n))) return undefined const idle = parts[3] + (parts[4] ?? 0) const total = parts.reduce((sum, n) => sum + n, 0) return { total, idle } } export async function collectCpuMetrics( client: Client, ): Promise<{ percent: number | null; cores: number | null; load: [number, number, number] | null }> { let percent: number | null = null let cores: number | null = null let load: [number, number, number] | null = null try { const work = (async () => { const first = await execCommand(client, "grep '^cpu ' /proc/stat") await new Promise((r) => setTimeout(r, 500)) const [second, loadOut, coresOut] = await Promise.all([ execCommand(client, "grep '^cpu ' /proc/stat"), execCommand(client, 'cat /proc/loadavg'), execCommand(client, 'nproc 2>/dev/null || grep -c ^processor /proc/cpuinfo'), ]) const a = parseCpuLine(first.stdout) const b = parseCpuLine(second.stdout) if (a && b) { const totalDiff = b.total - a.total const idleDiff = b.idle - a.idle if (totalDiff > 0) { percent = toFixedNum(Math.min(100, Math.max(0, ((totalDiff - idleDiff) / totalDiff) * 100))) } } const loadParts = loadOut.stdout.trim().split(/\s+/).slice(0, 3).map(Number) if (loadParts.length === 3 && loadParts.every(Number.isFinite)) { load = loadParts as [number, number, number] } const coreCount = Number(coresOut.stdout.trim()) cores = Number.isFinite(coreCount) ? coreCount : null })() await Promise.race([work, new Promise((_, reject) => setTimeout(() => reject(new Error('cpu metrics timeout')), 25000))]) } catch { // best-effort; leave nulls } return { percent, cores, load } }