
























@@ -181,16 +181,65 @@ function formatCapturedOutput(label, buffer) {
181181182182export function runCommand(command, args, options = {}) {
183183return new Promise((resolve, reject) => {
184-const { timeoutKillGraceMs = 2000, timeoutMs = COMMAND_TIMEOUT_MS, ...spawnOptions } = options;
184+const {
185+ resourceLabel,
186+ resourceSampleIntervalMs = 1000,
187+ resourceSampleOptions,
188+ resourceSamples,
189+ sampleProcessImpl = sampleProcess,
190+ timeoutKillGraceMs = 2000,
191+ timeoutMs = COMMAND_TIMEOUT_MS,
192+ ...spawnOptions
193+} = options;
185194const child = childProcess.spawn(command, args, {
186195stdio: ["ignore", "pipe", "pipe"],
187196 ...spawnOptions,
188197detached: spawnOptions.detached ?? process.platform !== "win32",
189198});
199+const startedAt = Date.now();
190200let stdout = { text: "", truncatedChars: 0 };
191201let stderr = { text: "", truncatedChars: 0 };
192202let timedOut = false;
193203let forceKillTimer;
204+let sampleTimer;
205+let resourceSampleInFlight = null;
206+const commandLabel = resourceLabel ?? [command, ...args.slice(0, 2)].join(" ");
207+const shouldSampleResources = Array.isArray(resourceSamples);
208+const collectResourceSample = () => {
209+if (!shouldSampleResources || !child.pid) {
210+return null;
211+}
212+resourceSampleInFlight ??= Promise.resolve()
213+.then(() => sampleProcessImpl(child.pid, resourceSampleOptions ?? {}))
214+.then((sample) => {
215+if (sample) {
216+resourceSamples.push({
217+ ...sample,
218+elapsedMs: Date.now() - startedAt,
219+label: commandLabel,
220+});
221+}
222+})
223+.catch(() => {})
224+.finally(() => {
225+resourceSampleInFlight = null;
226+});
227+return resourceSampleInFlight;
228+};
229+const stopResourceSampling = async () => {
230+clearInterval(sampleTimer);
231+await resourceSampleInFlight?.catch(() => {});
232+};
233+if (shouldSampleResources) {
234+void collectResourceSample();
235+sampleTimer = setInterval(
236+() => {
237+void collectResourceSample();
238+},
239+Math.max(100, resourceSampleIntervalMs),
240+);
241+sampleTimer.unref?.();
242+}
194243const timer = setTimeout(() => {
195244timedOut = true;
196245signalProcessGroup(child, "SIGTERM");
@@ -206,35 +255,37 @@ export function runCommand(command, args, options = {}) {
206255child.on("error", (error) => {
207256clearTimeout(timer);
208257clearTimeout(forceKillTimer);
209-reject(error);
258+void stopResourceSampling().finally(() => reject(error));
210259});
211260child.on("close", (status, signal) => {
212261clearTimeout(timer);
213262clearTimeout(forceKillTimer);
214-if (status === 0) {
215-resolve({
216-stdout: stdout.text,
217-stderr: stderr.text,
218-stdoutTruncatedChars: stdout.truncatedChars,
219-stderrTruncatedChars: stderr.truncatedChars,
220-});
221-return;
222-}
223-const detail = [
224-formatCapturedOutput("stdout", stdout),
225-formatCapturedOutput("stderr", stderr),
226-]
227-.filter(Boolean)
228-.join("\n")
229-.trim();
230-const failure = timedOut
231- ? `timed out after ${timeoutMs}ms`
232- : `failed with ${signal || status}`;
233-reject(
234-new Error(
235-`${command} ${args.join(" ")} ${failure}${detail ? `\n${tailText(detail)}` : ""}`,
236-),
237-);
263+void stopResourceSampling().then(() => {
264+if (status === 0) {
265+resolve({
266+stdout: stdout.text,
267+stderr: stderr.text,
268+stdoutTruncatedChars: stdout.truncatedChars,
269+stderrTruncatedChars: stderr.truncatedChars,
270+});
271+return;
272+}
273+const detail = [
274+formatCapturedOutput("stdout", stdout),
275+formatCapturedOutput("stderr", stderr),
276+]
277+.filter(Boolean)
278+.join("\n")
279+.trim();
280+const failure = timedOut
281+ ? `timed out after ${timeoutMs}ms`
282+ : `failed with ${signal || status}`;
283+reject(
284+new Error(
285+`${command} ${args.join(" ")} ${failure}${detail ? `\n${tailText(detail)}` : ""}`,
286+),
287+);
288+});
238289});
239290});
240291}
@@ -262,6 +313,10 @@ async function runOpenClaw(runner, args, env, options = {}) {
262313return runCommand(command.command, command.args, {
263314 ...command.options,
264315 env,
316+resourceLabel: options.resourceLabel,
317+resourceSampleIntervalMs: options.resourceSampleIntervalMs,
318+resourceSampleOptions: options.resourceSampleOptions,
319+resourceSamples: options.resourceSamples,
265320timeoutMs: options.timeoutMs ?? COMMAND_TIMEOUT_MS,
266321});
267322}
@@ -1372,20 +1427,35 @@ export async function main() {
13721427let child;
1373142813741429const processSamples = [];
1430+const commandSamples = [];
1431+const commandResourceOptions = {
1432+resourceSampleIntervalMs: 500,
1433+resourceSamples: commandSamples,
1434+};
13751435let sampleInFlight = null;
13761436let sampleTimer;
13771437try {
13781438console.log(`Kitchen Sink RPC walk using ${PLUGIN_SPEC} via ${runner.label}`);
13791439await runOpenClaw(runner, ["plugins", "install", PLUGIN_SPEC], env, {
1440+ ...commandResourceOptions,
1441+resourceLabel: "plugins install",
13801442timeoutMs: INSTALL_TIMEOUT_MS,
13811443});
13821444runner = resolveOpenClawRunner();
13831445console.log(`Kitchen Sink RPC runtime runner: ${runner.label}`);
13841446configureKitchenSink(env, port);
1385-await runOpenClaw(runner, ["plugins", "enable", PLUGIN_ID], env, { timeoutMs: 60000 });
1447+await runOpenClaw(runner, ["plugins", "enable", PLUGIN_ID], env, {
1448+ ...commandResourceOptions,
1449+resourceLabel: "plugins enable",
1450+timeoutMs: 60000,
1451+});
13861452const inspect = parseJsonOutput(
1387-(await runOpenClaw(runner, ["plugins", "inspect", PLUGIN_ID, "--runtime", "--json"], env))
1388-.stdout,
1453+(
1454+await runOpenClaw(runner, ["plugins", "inspect", PLUGIN_ID, "--runtime", "--json"], env, {
1455+ ...commandResourceOptions,
1456+resourceLabel: "plugins inspect",
1457+})
1458+).stdout,
13891459);
13901460if (inspect?.plugin?.status !== "loaded") {
13911461throw new Error(`Kitchen Sink plugin did not inspect as loaded: ${JSON.stringify(inspect)}`);
@@ -1510,6 +1580,7 @@ export async function main() {
15101580const finalSample = await sampleGateway();
15111581assertResourceCeiling(finalSample);
15121582const peakSample = summarizeProcessSamples(processSamples);
1583+const commandPeakSample = summarizeProcessSamples(commandSamples);
15131584assertResourceCeiling(peakSample);
15141585assertNoErrorLogs(logPath);
15151586@@ -1521,6 +1592,7 @@ export async function main() {
15211592commands: commandNames,
15221593catalogTools: catalogToolIds.filter((id) => EXPECTED_TOOLS.includes(id)),
15231594 channelAccount,
1595+ commandPeakSample,
15241596 initialSample,
15251597 finalSample,
15261598 peakSample,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。