import * as fs from 'fs'; import * as os from 'os'; import { execSync } from 'child_process'; // CPU core count (cached at module load) const cpuCoreCount = typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length; // Cached system constants let clkTck: number | null = null; let pageSize: number | null = null; function getClkTck(): number { if (clkTck === null) { try { clkTck = parseInt(execSync('getconf CLK_TCK', { encoding: 'utf8' }).trim(), 10); } catch { clkTck = 100; // standard Linux default } } return clkTck; } function getPageSize(): number { if (pageSize === null) { try { pageSize = parseInt(execSync('getconf PAGESIZE', { encoding: 'utf8' }).trim(), 10); } catch { pageSize = 4096; // standard Linux default } } return pageSize; } // History for CPU delta tracking interface ISnapshot { utime: number; stime: number; timestamp: number; // hrtime in seconds } const history = new Map(); function readProcStat(pid: number): { utime: number; stime: number; rss: number } | null { try { const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'); // Format: pid (comm) state ppid ... fields // utime is field 14, stime is field 15, rss is field 24 (1-indexed) const closeParenIdx = stat.lastIndexOf(')'); if (closeParenIdx === -1) return null; const afterComm = stat.slice(closeParenIdx + 2); const fields = afterComm.split(' '); // fields[0] = state (field 3), so utime = fields[11] (field 14), stime = fields[12] (field 15), rss = fields[21] (field 24) const utime = parseInt(fields[11], 10); const stime = parseInt(fields[12], 10); const rss = parseInt(fields[21], 10); return { utime, stime, rss }; } catch { return null; } } function hrtimeSeconds(): number { const [sec, nsec] = process.hrtime(); return sec + nsec / 1e9; } export interface IPidUsageResult { cpu: number; // raw per-core CPU% (can exceed 100%) cpuCoreCount: number; // number of CPU cores on the machine cpuNormalizedPercent: number; // cpu / coreCount — 0-100% of total machine memory: number; // RSS in bytes } /** * Get CPU percentage and memory usage for the given PIDs. * CPU% is calculated as a delta between successive calls. */ export async function getPidUsage( pids: number[] ): Promise> { const tck = getClkTck(); const ps = getPageSize(); const result: Record = {}; for (const pid of pids) { const stat = readProcStat(pid); if (!stat) { continue; } const now = hrtimeSeconds(); const totalTicks = stat.utime + stat.stime; const memoryBytes = stat.rss * ps; const prev = history.get(pid); if (prev) { const elapsedSeconds = now - prev.timestamp; const ticksDelta = totalTicks - (prev.utime + prev.stime); const cpuSeconds = ticksDelta / tck; const cpuPercent = elapsedSeconds > 0 ? (cpuSeconds / elapsedSeconds) * 100 : 0; result[pid] = { cpu: cpuPercent, cpuCoreCount, cpuNormalizedPercent: cpuPercent / cpuCoreCount, memory: memoryBytes, }; } else { // First call for this PID — no delta available, report 0% cpu result[pid] = { cpu: 0, cpuCoreCount, cpuNormalizedPercent: 0, memory: memoryBytes, }; } // Update history history.set(pid, { utime: stat.utime, stime: stat.stime, timestamp: now, }); } return result; }