fix: 为 WebProbe 增加物理内存门禁
This commit is contained in:
@@ -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()}
|
||||
|
||||
Reference in New Issue
Block a user