docs: document profiling scripts

This commit is contained in:
Peter Steinberger
2026-06-04 23:48:20 -04:00
parent 57f8d71c50
commit 26bc069308
7 changed files with 91 additions and 14 deletions

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node
// Generates npm-shrinkwrap.json files that mirror pnpm lock policy for
// published packages while stripping dev-only dependency state.
import { execFileSync } from "node:child_process";
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
@@ -384,6 +386,9 @@ function packageJsonForShrinkwrap(packageJson, shrinkwrapOverrides) {
return normalized;
}
/**
* Resolves the npm command invocation used by shrinkwrap generation.
*/
export function createNpmShrinkwrapCommand(args, options = {}) {
return resolveNpmRunner({
comSpec: options.comSpec,
@@ -395,6 +400,9 @@ export function createNpmShrinkwrapCommand(args, options = {}) {
});
}
/**
* Reads a positive integer env override for shrinkwrap subprocess limits.
*/
export function readPositiveIntEnv(name, fallback, env = process.env) {
const text = String(env[name] ?? fallback).trim();
if (!/^\d+$/u.test(text)) {
@@ -407,6 +415,9 @@ export function readPositiveIntEnv(name, fallback, env = process.env) {
return value;
}
/**
* Builds execFileSync options with bounded timeout and output buffer limits.
*/
export function createNpmShrinkwrapExecOptions(invocation, cwd, env = process.env) {
return {
cwd,
@@ -1299,6 +1310,8 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
}
export {
// Test-facing helpers cover lockfile normalization, override merging, and
// changed-package detection without invoking npm.
collectCurrentShrinkwrapOverrides,
collectOverrideViolations,
collectPnpmLockViolations,

View File

@@ -1,3 +1,5 @@
// Measures gateway RPC round-trip time by launching an isolated local gateway
// and writing qa-lab-compatible summary artifacts.
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
@@ -10,7 +12,9 @@ import { fileURLToPath, pathToFileURL } from "node:url";
const DEFAULT_METHODS = ["health", "config.get"];
const DEFAULT_ITERATIONS = 10;
/** Maximum time to wait for a spawned gateway to become reachable. */
export const READY_TIMEOUT_MS = 120_000;
/** Per-probe timeout used while polling gateway readiness endpoints. */
export const READY_PROBE_TIMEOUT_MS = 1_000;
const PARENT_TERMINATION_SIGNALS = ["SIGHUP", "SIGINT", "SIGTERM"];
const IS_DIRECT_RUN =
@@ -100,6 +104,9 @@ function formatErrorMessage(error) {
return String(error);
}
/**
* Polls readiness endpoints while also failing fast if the child exits.
*/
export async function waitForGatewayReady({
child,
fetchImpl = fetch,
@@ -165,6 +172,9 @@ function resolveOpenClawLaunchArgs(repoRoot, sourceEntryExists = existsSync) {
return [path.join(repoRoot, "openclaw.mjs")];
}
/**
* Signals the gateway process group on POSIX so spawned children are cleaned up.
*/
export function signalGatewayProcess(child, signal, killProcess = defaultKillProcess) {
if (process.platform !== "win32" && typeof child.pid === "number") {
try {
@@ -187,6 +197,9 @@ export function signalGatewayProcess(child, signal, killProcess = defaultKillPro
}
}
/**
* Checks process-group liveness without treating an already-exited child as an error.
*/
export function isGatewayProcessAlive(child, killProcess = defaultKillProcess) {
if (process.platform !== "win32" && typeof child.pid === "number") {
try {
@@ -210,6 +223,9 @@ function signalGatewayProcessForParentExit(child, signal, killProcess) {
}
}
/**
* Installs parent-process cleanup handlers for a spawned gateway.
*/
export function installGatewayParentCleanup(
child,
{ killProcess = defaultKillProcess, processLike = process } = {},
@@ -255,6 +271,9 @@ async function waitForGatewayExit(child, timeoutMs, killProcess = defaultKillPro
return !isGatewayProcessAlive(child, killProcess);
}
/**
* Stops the gateway with SIGTERM first and SIGKILL after the grace window.
*/
export async function stopGateway(child, options = {}) {
if (!isGatewayProcessAlive(child, options.killProcess)) {
return;
@@ -275,6 +294,9 @@ async function closeFileHandles(handles) {
}
}
/**
* Starts an isolated loopback gateway with temp HOME/state directories.
*/
export async function startGateway({
configPath,
env = process.env,
@@ -354,6 +376,9 @@ export async function startGateway({
return child;
}
/**
* Removes the temporary root used by the RPC RTT probe.
*/
export async function cleanupTempRoot(tempRoot, { rmImpl = fs.rm } = {}) {
try {
await rmImpl(tempRoot, { force: true, recursive: true });

View File

@@ -1,3 +1,5 @@
// Prepares declaration and entry-shim artifacts that prove plugin package
// boundary imports resolve through public package surfaces.
import { spawn } from "node:child_process";
import fs from "node:fs";
import path, { resolve } from "node:path";
@@ -248,6 +250,9 @@ function isRelevantTypeInput(filePath) {
return TYPE_INPUT_EXTENSIONS.has(path.extname(filePath));
}
/**
* Parses the artifact preparation mode from CLI arguments.
*/
export function parseMode(argv = process.argv.slice(2)) {
const modeArg = argv.find((arg) => arg.startsWith("--mode="));
const mode = modeArg?.slice("--mode=".length) ?? "all";
@@ -257,6 +262,9 @@ export function parseMode(argv = process.argv.slice(2)) {
return mode;
}
/**
* Reads the root shim timeout override for long package-boundary builds.
*/
export function resolveBoundaryRootShimsTimeoutMs(env = process.env) {
const raw = env.OPENCLAW_PLUGIN_SDK_BOUNDARY_ROOT_SHIMS_TIMEOUT_MS?.trim();
if (!raw) {
@@ -309,6 +317,9 @@ function collectOldestMtime(paths, params = {}) {
return Number.isFinite(oldestMtimeMs) ? oldestMtimeMs : null;
}
/**
* Compares input and output mtimes to skip fresh generated artifacts.
*/
export function isArtifactSetFresh(params) {
const newestInputMtimeMs = collectNewestMtime(params.inputPaths, {
rootDir: params.rootDir,
@@ -335,6 +346,9 @@ function writeStampFile(relativePath) {
fs.writeFileSync(filePath, `${new Date().toISOString()}\n`, "utf8");
}
/**
* Prefixes streamed child output line-by-line without breaking partial chunks.
*/
export function createPrefixedOutputWriter(label, target) {
let buffered = "";
const prefix = `[${label}] `;
@@ -412,6 +426,9 @@ function installNodeStepParentSignalForwarders() {
});
}
/**
* Runs one artifact step with timeout, abort propagation, and prefixed output.
*/
export function runNodeStep(label, args, timeoutMs, params = {}) {
const abortController = params.abortController;
const spawnImpl = params.spawnImpl ?? spawn;
@@ -516,6 +533,9 @@ export function runNodeStep(label, args, timeoutMs, params = {}) {
});
}
/**
* Runs independent artifact steps together and aborts siblings on first failure.
*/
export async function runNodeStepsInParallel(steps) {
const abortController = new AbortController();
const results = await Promise.allSettled(
@@ -529,6 +549,9 @@ export async function runNodeStepsInParallel(steps) {
}
}
/**
* Chooses serial or parallel artifact execution based on local heavy-check policy.
*/
export async function runNodeSteps(steps, env = process.env) {
if (!isLocalCheckEnabled(env)) {
await runNodeStepsInParallel(steps);

View File

@@ -1,3 +1,5 @@
// Configures this checkout's Git hooks path during package prepare when git
// and the hooks directory are available.
import { spawnSync } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -21,6 +23,9 @@ function runGit(spawn, gitBin, args, cwd, stdio) {
});
}
/**
* Installs the repo-local hooks path and returns a structured reason if skipped.
*/
export function configurePrepareGitHooks(params = {}) {
const cwd = params.cwd ?? DEFAULT_PACKAGE_ROOT;
const exists = params.existsSync ?? existsSync;
@@ -32,13 +37,11 @@ export function configurePrepareGitHooks(params = {}) {
return { configured: false, reason: "missing-hooks-dir" };
}
const worktree = runGit(
spawn,
gitBin,
["rev-parse", "--is-inside-work-tree"],
cwd,
["ignore", "pipe", "ignore"],
);
const worktree = runGit(spawn, gitBin, ["rev-parse", "--is-inside-work-tree"], cwd, [
"ignore",
"pipe",
"ignore",
]);
const missingGitReason = getMissingGitReason(worktree.error);
if (missingGitReason) {
return { configured: false, reason: missingGitReason };
@@ -47,13 +50,11 @@ export function configurePrepareGitHooks(params = {}) {
return { configured: false, reason: "not-worktree" };
}
const configured = runGit(
spawn,
gitBin,
["config", "core.hooksPath", "git-hooks"],
cwd,
["ignore", "ignore", "pipe"],
);
const configured = runGit(spawn, gitBin, ["config", "core.hooksPath", "git-hooks"], cwd, [
"ignore",
"ignore",
"pipe",
]);
const configMissingGitReason = getMissingGitReason(configured.error);
if (configMissingGitReason) {
return { configured: false, reason: configMissingGitReason };

View File

@@ -1,5 +1,10 @@
// Installs a process-wide warning filter for dependency warnings that are known
// noise in current toolchains.
const warningFilterKey = Symbol.for("openclaw.warning-filter");
/**
* Suppresses punycode deprecation warnings while preserving all other warnings.
*/
export function installProcessWarningFilter() {
if (globalThis[warningFilterKey]?.installed) {
return;

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env node
// Profiles peak RSS for built bundled plugin entrypoints and emits a JSON
// report suitable for extension memory budget review.
import { spawn } from "node:child_process";
import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
@@ -53,6 +55,9 @@ function parsePositiveInt(raw, flagName) {
return parsed;
}
/**
* Parses extension memory profiler options after pnpm's optional separator.
*/
export function parseArgs(argv) {
const args = stripLeadingPackageManagerSeparator(argv);
const options = {
@@ -171,6 +176,9 @@ function summarizeStderr(stderr, lines = 8, maxChars = STDERR_PREVIEW_MAX_CHARS)
)}`;
}
/**
* Runs one import scenario in a child process and captures bounded output plus RSS.
*/
export async function runCase({
repoRoot,
env,

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env node
// Profiles selected tsgo graphs and writes diagnostics/trace artifacts for
// TypeScript graph size and performance investigations.
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";