/** * 进程监控指标采集(仅支持 Linux) * - CPU 占用率:两次采样 /proc//stat 与 /proc/stat 差值计算,内部休眠约 1 秒 * - 内存占用:/proc//status 的 VmRSS,单位 MB * - 句柄数:/proc//fd 目录条目数 * - 端口 -> pid:端口解析逻辑见 ../linux/portpid.js(含 docker 端口发布) */ const fs = require("fs"); const portpid = require("../linux/portpid"); const os = require("os"); function isLinux() { return os.type() === "Linux"; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function readPidTicks(pid) { const txt = fs.readFileSync(`/proc/${pid}/stat`, "utf8"); // 字段 14=utime 15=stime,comm 可能含空格,按最后一个 ) 截断 const rest = txt.slice(txt.lastIndexOf(")") + 1).trim().split(/\s+/); return Number(rest[11]) + Number(rest[12]); } function readCpuTotal() { const line = fs.readFileSync("/proc/stat", "utf8").split("\n")[0]; // user nice system idle iowait irq softirq steal(guest 已计入 user) const parts = line.trim().split(/\s+/).slice(1, 9).map(Number); let total = 0; for (const v of parts) { total += Number.isFinite(v) ? v : 0; } return total; } function readMemMB(pid) { const status = fs.readFileSync(`/proc/${pid}/status`, "utf8"); const m = status.match(/^VmRSS:\s+(\d+)\s+kB/m); if (!m) { return null; } return Math.round(Number(m[1]) / 1024 * 100) / 100; } function readHandleCount(pid) { return fs.readdirSync(`/proc/${pid}/fd`, { withFileTypes: true }).length; } /** * 采集单个进程的监控数据 * @param {number} pid * @returns {Promise<{pid:number,cpu:number,mem:number,handle:number}|null>} 进程不存在/不可读返回 null */ async function collectPid(pid) { try { const beforePid = readPidTicks(pid); const beforeTotal = readCpuTotal(); await sleep(1000); const afterPid = readPidTicks(pid); const afterTotal = readCpuTotal(); const totalDelta = afterTotal - beforeTotal; const pidDelta = afterPid - beforePid; const cpu = totalDelta > 0 ? Math.round(pidDelta / totalDelta * 10000) / 100 : 0; const mem = readMemMB(pid); const handle = readHandleCount(pid); return { pid, cpu, mem, handle }; } catch (e) { return null; } } module.exports = { isLinux, collectPid, findPortPid: portpid.findPortPid };