
























@@ -0,0 +1,138 @@
1+import { spawn } from "node:child_process";
2+import fs from "node:fs";
3+import path from "node:path";
4+5+const [summaryPath, phase, separator, command, ...args] = process.argv.slice(2);
6+if (!summaryPath || !phase || separator !== "--" || !command) {
7+console.error("usage: measure.mjs <summary.tsv> <phase> -- <command> [args...]");
8+process.exit(2);
9+}
10+11+const pageSize = Number.parseInt(process.env.OPENCLAW_PROC_PAGE_SIZE || "4096", 10);
12+const clockTicks = Number.parseInt(process.env.OPENCLAW_PROC_CLK_TCK || "100", 10);
13+const pollMs = Number.parseInt(process.env.OPENCLAW_PLUGIN_LIFECYCLE_METRIC_POLL_MS || "100", 10);
14+15+if (!fs.existsSync("/proc")) {
16+console.error("plugin lifecycle resource sampler requires Linux /proc");
17+process.exit(2);
18+}
19+20+function readProcSnapshot() {
21+const stats = new Map();
22+for (const entry of fs.readdirSync("/proc", { withFileTypes: true })) {
23+if (!entry.isDirectory() || !/^\d+$/u.test(entry.name)) {
24+continue;
25+}
26+const pid = Number.parseInt(entry.name, 10);
27+const statPath = path.join("/proc", entry.name, "stat");
28+try {
29+const raw = fs.readFileSync(statPath, "utf8");
30+const closeParen = raw.lastIndexOf(")");
31+if (closeParen === -1) {
32+continue;
33+}
34+const fields = raw
35+.slice(closeParen + 2)
36+.trim()
37+.split(/\s+/u);
38+const ppid = Number.parseInt(fields[1] ?? "", 10);
39+const userTicks = Number.parseInt(fields[11] ?? "", 10);
40+const systemTicks = Number.parseInt(fields[12] ?? "", 10);
41+const rssPages = Number.parseInt(fields[21] ?? "", 10);
42+if (
43+!Number.isFinite(ppid) ||
44+!Number.isFinite(userTicks) ||
45+!Number.isFinite(systemTicks) ||
46+!Number.isFinite(rssPages)
47+) {
48+continue;
49+}
50+stats.set(pid, {
51+ ppid,
52+cpuTicks: userTicks + systemTicks,
53+rssBytes: Math.max(0, rssPages) * pageSize,
54+});
55+} catch {
56+// Processes can exit while /proc is being scanned.
57+}
58+}
59+return stats;
60+}
61+62+function descendantsOf(rootPid, stats) {
63+const children = new Map();
64+for (const [pid, stat] of stats.entries()) {
65+const siblings = children.get(stat.ppid) ?? [];
66+siblings.push(pid);
67+children.set(stat.ppid, siblings);
68+}
69+const seen = new Set([rootPid]);
70+const queue = [rootPid];
71+for (let index = 0; index < queue.length; index += 1) {
72+for (const child of children.get(queue[index]) ?? []) {
73+if (!seen.has(child)) {
74+seen.add(child);
75+queue.push(child);
76+}
77+}
78+}
79+return seen;
80+}
81+82+function sample(rootPid) {
83+const stats = readProcSnapshot();
84+const pids = descendantsOf(rootPid, stats);
85+let rssBytes = 0;
86+let cpuTicks = 0;
87+for (const pid of pids) {
88+const stat = stats.get(pid);
89+if (!stat) {
90+continue;
91+}
92+rssBytes += stat.rssBytes;
93+cpuTicks += stat.cpuTicks;
94+}
95+return { rssBytes, cpuTicks };
96+}
97+98+const started = performance.now();
99+const child = spawn(command, args, {
100+cwd: process.cwd(),
101+env: process.env,
102+stdio: "inherit",
103+});
104+105+let maxRssBytes = 0;
106+let maxCpuTicks = 0;
107+const updateMetrics = () => {
108+if (!child.pid) {
109+return;
110+}
111+const current = sample(child.pid);
112+maxRssBytes = Math.max(maxRssBytes, current.rssBytes);
113+maxCpuTicks = Math.max(maxCpuTicks, current.cpuTicks);
114+};
115+116+updateMetrics();
117+const interval = setInterval(updateMetrics, pollMs);
118+119+child.on("exit", (code, signal) => {
120+updateMetrics();
121+clearInterval(interval);
122+const wallMs = performance.now() - started;
123+const cpuSeconds = maxCpuTicks / clockTicks;
124+const maxRssKb = Math.round(maxRssBytes / 1024);
125+const cpuCoreRatio = wallMs > 0 ? cpuSeconds / (wallMs / 1000) : 0;
126+fs.appendFileSync(
127+summaryPath,
128+`${phase}\t${maxRssKb}\t${cpuSeconds.toFixed(3)}\t${wallMs.toFixed(0)}\t${cpuCoreRatio.toFixed(3)}\t${signal ?? ""}\n`,
129+);
130+console.log(
131+`plugin lifecycle resource: phase=${phase} max_rss_kb=${maxRssKb} cpu_s=${cpuSeconds.toFixed(3)} wall_ms=${wallMs.toFixed(0)} cpu_core_ratio=${cpuCoreRatio.toFixed(3)}`,
132+);
133+if (signal) {
134+process.kill(process.pid, signal);
135+return;
136+}
137+process.exit(code ?? 0);
138+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。