fix: 为 WebProbe 增加物理内存门禁

This commit is contained in:
Codex
2026-07-13 07:10:26 +02:00
parent 95bf0b7de6
commit 0c22a05cee
18 changed files with 727 additions and 40 deletions
@@ -31,9 +31,14 @@ const targetPath = process.env.UNIDESK_WEB_OBSERVE_TARGET_PATH || "/workbench";
const sampleIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SAMPLE_INTERVAL_MS, 5000);
const screenshotIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS, 300000);
const screenshotCaptureTimeoutMs = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_SCREENSHOT_CAPTURE_TIMEOUT_MS, 15000, 1000, 120000);
const maxSamples = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SAMPLES, 0);
const maxSamples = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SAMPLES, 720, 1, 10000000);
const observerRefreshIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_OBSERVER_REFRESH_INTERVAL_MS, 180000);
const maxRunMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_RUN_MS, 0);
const maxRunMs = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_RUN_MS, 3600000, 1000, 86400000);
const maxScreenshots = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SCREENSHOTS, 48, 1, 10000);
const maxPerformanceEntries = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_PERFORMANCE_ENTRIES, 2000, 1, 100000);
const maxBrowserProcessHistoryEntries = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_BROWSER_PROCESS_HISTORY_ENTRIES, 360, 1, 100000);
const maxBrowserFreezeSignalHistoryEntries = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_BROWSER_FREEZE_SIGNAL_HISTORY_ENTRIES, 120, 1, 100000);
const responseBodyMaxBytes = boundedInteger(process.env.UNIDESK_WEB_OBSERVE_RESPONSE_BODY_MAX_BYTES, 524288, 1024, 16777216);
const viewport = parseViewport(process.env.UNIDESK_WEB_OBSERVE_VIEWPORT || "1440x900");
const browserProxyMode = parseBrowserProxyMode(process.env.UNIDESK_WEB_OBSERVE_BROWSER_PROXY_MODE || "auto");
const origin = {
@@ -92,6 +97,7 @@ let lastObserverRefreshAtMs = Date.now();
let sampleSeq = 0;
let commandSeq = 0;
let artifactSeq = 0;
let screenshotCount = 0;
let activeCommandId = null;
let activeCommandType = null;
let stopping = false;
@@ -107,11 +113,22 @@ let heartbeatPulseTimer = null;
let browserProcessMonitorStop = null;
let browserProcessMonitorSeq = 0;
let browserFreezeBlocker = null;
let shutdownSignal = null;
let browserResourcesClosePromise = null;
const browserProcessHistory = [];
const browserPageRuntimeBaselines = new Map();
const browserFreezeSignalHistory = [];
const jsonlRotation = { stamp: compactFileTimestamp(startedAt), files: [] };
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
process.once(signal, () => {
shutdownSignal = signal;
stopping = true;
terminalStatus = "stopping";
void closeBrowserResources("signal:" + signal);
});
}
try {
if (!password) throw new Error("missing HWLAB_WEB_PASS");
await prepareDirs();
@@ -152,18 +169,18 @@ try {
await writeManifest({ status: "running", auth: publicAuth(auth) });
await writeHeartbeat({ status: "running" });
while (!stopping) {
await drainOneCommand();
if (browserFreezeBlocker) break;
await samplePage("interval");
if (browserFreezeBlocker) break;
if (maxSamples > 0 && sampleSeq >= maxSamples) {
await appendJsonl(files.control, controlRecord({ id: "max-samples", type: "stop", source: "sampler" }, "completed", { reason: "max-samples", maxSamples }));
break;
}
if (maxRunMs > 0 && Date.now() - startedAtMs >= maxRunMs) {
await appendJsonl(files.control, controlRecord({ id: "max-run-ms", type: "stop", source: "sampler" }, "completed", { reason: "max-run-ms", maxRunMs, elapsedMs: Date.now() - startedAtMs }));
break;
}
if (maxSamples > 0 && sampleSeq >= maxSamples) {
await appendJsonl(files.control, controlRecord({ id: "max-samples", type: "stop", source: "sampler" }, "completed", { reason: "max-samples", maxSamples }));
break;
}
await drainOneCommand();
if (browserFreezeBlocker) break;
await samplePage("interval");
if (browserFreezeBlocker) break;
await sleep(sampleIntervalMs);
}
if (browserFreezeBlocker) {
@@ -186,7 +203,28 @@ try {
} finally {
if (browserProcessMonitorStop) browserProcessMonitorStop();
if (heartbeatPulseTimer) clearInterval(heartbeatPulseTimer);
if (browser) await browser.close().catch(() => {});
await closeBrowserResources(shutdownSignal === null ? "finally" : "signal:" + shutdownSignal);
}
async function closeBrowserResources(reason) {
if (browserResourcesClosePromise) return browserResourcesClosePromise;
browserResourcesClosePromise = (async () => {
stopping = true;
const closingObserverPage = observerPage;
const closingPage = page;
const closingContext = context;
const closingBrowser = browser;
observerPage = null;
page = null;
context = null;
browser = null;
if (closingObserverPage && !closingObserverPage.isClosed()) await closingObserverPage.close().catch(() => {});
if (closingPage && !closingPage.isClosed()) await closingPage.close().catch(() => {});
if (closingContext) await closingContext.close().catch(() => {});
if (closingBrowser) await closingBrowser.close().catch(() => {});
return { ok: true, reason, valuesRedacted: true };
})();
return browserResourcesClosePromise;
}
${nodeWebObserveRunnerRuntimeSource()}