import { spawn } from 'child_process' import { promises as fs } from 'fs' /** * 获取进程的内存使用情况(MB) */ export async function getProcessMemory(pid: number): Promise { const platform = process.platform if (platform === 'linux') { return getLinuxProcessTreeMemory(pid) } if (platform === 'win32') { return getWindowsProcessMemory(pid) } return getUnixProcessMemory(pid) } async function getLinuxProcessTreeMemory(pid: number): Promise { const visited = new Set() const queue: number[] = [pid] let totalKb = 0 while (queue.length > 0) { const current = queue.shift() if (!current || visited.has(current)) { continue } visited.add(current) const rssKb = await readLinuxVmRssKb(current) if (rssKb) { totalKb += rssKb } const children = await readLinuxChildPids(current) for (const child of children) { if (!visited.has(child)) { queue.push(child) } } } if (!totalKb) { return null } return Math.round(totalKb / 1024) } async function readLinuxVmRssKb(pid: number): Promise { try { const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8') const line = status.split(/\r?\n/).find((entry) => entry.startsWith('VmRSS:')) if (!line) { return null } const match = line.match(/VmRSS:\s+(\d+)/) if (!match) { return null } return parseInt(match[1], 10) } catch { return null } } async function readLinuxChildPids(pid: number): Promise { try { const children = await fs.readFile(`/proc/${pid}/task/${pid}/children`, 'utf-8') if (!children.trim()) { return [] } return children .trim() .split(/\s+/) .map((value) => parseInt(value, 10)) .filter((value) => !Number.isNaN(value)) } catch { return [] } } interface WindowsProcessInfo { ProcessId: number ParentProcessId: number WorkingSetSize: number } async function getWindowsProcessMemory(pid: number): Promise { const processList = await getWindowsProcessList() if (processList.length === 0) { return null } const byPid = new Map() const childrenByParent = new Map() for (const processInfo of processList) { byPid.set(processInfo.ProcessId, processInfo) const children = childrenByParent.get(processInfo.ParentProcessId) || [] children.push(processInfo.ProcessId) childrenByParent.set(processInfo.ParentProcessId, children) } const queue: number[] = [pid] const visited = new Set() let totalBytes = 0 while (queue.length > 0) { const current = queue.shift() if (!current || visited.has(current)) { continue } visited.add(current) const processInfo = byPid.get(current) if (processInfo && Number.isFinite(processInfo.WorkingSetSize)) { totalBytes += processInfo.WorkingSetSize } const children = childrenByParent.get(current) if (!children) { continue } for (const childPid of children) { if (!visited.has(childPid)) { queue.push(childPid) } } } if (totalBytes <= 0) { return null } return Math.round(totalBytes / 1024 / 1024) } async function getWindowsProcessList(): Promise { return new Promise((resolve) => { const command = spawn('powershell', [ '-NoProfile', '-Command', 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,WorkingSetSize | ConvertTo-Json -Compress' ]) let stdout = '' command.stdout.on('data', (data) => { stdout += data.toString() }) command.on('error', () => resolve([])) command.on('close', (code) => { if (code !== 0 || !stdout.trim()) { resolve([]) return } try { const parsed = JSON.parse(stdout) as WindowsProcessInfo | WindowsProcessInfo[] const list = Array.isArray(parsed) ? parsed : [parsed] const normalized = list .map((item) => ({ ProcessId: Number(item.ProcessId), ParentProcessId: Number(item.ParentProcessId), WorkingSetSize: Number(item.WorkingSetSize) })) .filter((item) => !Number.isNaN(item.ProcessId) && item.ProcessId > 0) resolve(normalized) } catch { resolve([]) } }) }) } async function getUnixProcessMemory(pid: number): Promise { return new Promise((resolve) => { const command = spawn('ps', ['-o', 'rss=', '-p', pid.toString()]) let stdout = '' command.stdout.on('data', (data) => { stdout += data.toString() }) command.on('error', () => resolve(null)) command.on('close', () => { const output = stdout.trim() if (!output) { resolve(null) return } const kb = parseInt(output, 10) if (Number.isNaN(kb)) { resolve(null) return } resolve(Math.round(kb / 1024)) }) }) }