fix: correct web probe browser memory policy
This commit is contained in:
@@ -82,6 +82,7 @@ let browserProcessMonitorStop = null;
|
||||
let browserProcessMonitorSeq = 0;
|
||||
let browserFreezeBlocker = null;
|
||||
const browserProcessHistory = [];
|
||||
const browserPageRuntimeBaselines = new Map();
|
||||
const browserFreezeSignalHistory = [];
|
||||
const jsonlRotation = { stamp: compactFileTimestamp(startedAt), files: [] };
|
||||
|
||||
@@ -316,41 +317,65 @@ async function enforceBrowserFreezePolicy(sample) {
|
||||
const processRssMb = Number(processSummary.maxProcessRssMb);
|
||||
const totalGrowthMb = Number(growth.totalRssGrowthMb);
|
||||
const processGrowthMb = Number(growth.maxProcessRssGrowthMb);
|
||||
if (Number.isFinite(totalRssMb) && totalRssMb >= browserFreezePolicy.memory.totalRssBlockerMb) {
|
||||
await triggerBrowserFreezeBlocker({
|
||||
kind: "memory-total-rss",
|
||||
rootCause: "frontend_browser_process_memory_pressure",
|
||||
observed: { totalRssMb, processRssMb, totalGrowthMb, processGrowthMb, valuesRedacted: true },
|
||||
threshold: { totalRssBlockerMb: browserFreezePolicy.memory.totalRssBlockerMb, valuesRedacted: true },
|
||||
sample: browserProcessSampleRef(sample),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (Number.isFinite(processRssMb) && processRssMb >= browserFreezePolicy.memory.processRssBlockerMb) {
|
||||
await triggerBrowserFreezeBlocker({
|
||||
kind: "memory-process-rss",
|
||||
rootCause: "frontend_browser_process_memory_pressure",
|
||||
observed: { totalRssMb, processRssMb, totalGrowthMb, processGrowthMb, valuesRedacted: true },
|
||||
threshold: { processRssBlockerMb: browserFreezePolicy.memory.processRssBlockerMb, valuesRedacted: true },
|
||||
sample: browserProcessSampleRef(sample),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(Number.isFinite(totalGrowthMb) && totalGrowthMb >= browserFreezePolicy.memory.growthBlockerMb)
|
||||
|| (Number.isFinite(processGrowthMb) && processGrowthMb >= browserFreezePolicy.memory.growthBlockerMb)
|
||||
) {
|
||||
await triggerBrowserFreezeBlocker({
|
||||
kind: "memory-rss-growth",
|
||||
rootCause: "frontend_browser_process_memory_leak_or_unbounded_render_growth",
|
||||
observed: { totalRssMb, processRssMb, totalGrowthMb, processGrowthMb, valuesRedacted: true },
|
||||
threshold: { growthBlockerMb: browserFreezePolicy.memory.growthBlockerMb, windowMs: browserFreezePolicy.blockerWindowMs, valuesRedacted: true },
|
||||
sample: browserProcessSampleRef(sample),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pageMetric of Array.isArray(sample.pages) ? sample.pages : []) {
|
||||
const effectiveMemory = pageMetric?.effectiveMemory && typeof pageMetric.effectiveMemory === "object" ? pageMetric.effectiveMemory : {};
|
||||
const effectiveHeapUsedMb = Number(effectiveMemory.effectiveHeapUsedMb);
|
||||
const effectiveJsHeapUsedMb = Number(effectiveMemory.effectiveJsHeapUsedMb);
|
||||
const heapGrowthMb = Number(effectiveMemory.heapUsedGrowthMb);
|
||||
const jsHeapGrowthMb = Number(effectiveMemory.jsHeapUsedGrowthMb);
|
||||
if (
|
||||
(Number.isFinite(effectiveHeapUsedMb) && effectiveHeapUsedMb >= browserFreezePolicy.memory.processRssBlockerMb)
|
||||
|| (Number.isFinite(effectiveJsHeapUsedMb) && effectiveJsHeapUsedMb >= browserFreezePolicy.memory.processRssBlockerMb)
|
||||
) {
|
||||
await triggerBrowserFreezeBlocker({
|
||||
kind: "memory-page-effective",
|
||||
rootCause: "frontend_browser_page_effective_memory_pressure",
|
||||
observed: {
|
||||
pageRole: pageMetric?.pageRole ?? null,
|
||||
pageId: pageMetric?.pageId ?? null,
|
||||
pageEpoch: pageMetric?.pageEpoch ?? null,
|
||||
totalRssMb,
|
||||
processRssMb,
|
||||
totalGrowthMb,
|
||||
processGrowthMb,
|
||||
effectiveHeapUsedMb: Number.isFinite(effectiveHeapUsedMb) ? effectiveHeapUsedMb : null,
|
||||
effectiveJsHeapUsedMb: Number.isFinite(effectiveJsHeapUsedMb) ? effectiveJsHeapUsedMb : null,
|
||||
baseline: pageMetric?.baseline ?? null,
|
||||
valuesRedacted: true,
|
||||
},
|
||||
threshold: { processRssBlockerMb: browserFreezePolicy.memory.processRssBlockerMb, policyScope: "per-page-effective-memory", valuesRedacted: true },
|
||||
sample: browserProcessSampleRef(sample),
|
||||
page: browserPageMetricRef(pageMetric),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(Number.isFinite(heapGrowthMb) && heapGrowthMb >= browserFreezePolicy.memory.growthBlockerMb)
|
||||
|| (Number.isFinite(jsHeapGrowthMb) && jsHeapGrowthMb >= browserFreezePolicy.memory.growthBlockerMb)
|
||||
) {
|
||||
await triggerBrowserFreezeBlocker({
|
||||
kind: "memory-page-effective-growth",
|
||||
rootCause: "frontend_browser_page_memory_leak_or_unbounded_render_growth",
|
||||
observed: {
|
||||
pageRole: pageMetric?.pageRole ?? null,
|
||||
pageId: pageMetric?.pageId ?? null,
|
||||
pageEpoch: pageMetric?.pageEpoch ?? null,
|
||||
totalRssMb,
|
||||
processRssMb,
|
||||
totalGrowthMb,
|
||||
processGrowthMb,
|
||||
heapGrowthMb: Number.isFinite(heapGrowthMb) ? heapGrowthMb : null,
|
||||
jsHeapGrowthMb: Number.isFinite(jsHeapGrowthMb) ? jsHeapGrowthMb : null,
|
||||
baseline: pageMetric?.baseline ?? null,
|
||||
valuesRedacted: true,
|
||||
},
|
||||
threshold: { growthBlockerMb: browserFreezePolicy.memory.growthBlockerMb, windowMs: browserFreezePolicy.blockerWindowMs, policyScope: "per-page-effective-memory", valuesRedacted: true },
|
||||
sample: browserProcessSampleRef(sample),
|
||||
page: browserPageMetricRef(pageMetric),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const responsiveness = pageMetric?.responsiveness && typeof pageMetric.responsiveness === "object" ? pageMetric.responsiveness : {};
|
||||
const responsivenessLatencyMs = Number(responsiveness.latencyMs);
|
||||
if (responsiveness.timeout === true || (Number.isFinite(responsivenessLatencyMs) && responsivenessLatencyMs >= browserFreezePolicy.responsiveness.latencyBlockerMs)) {
|
||||
@@ -546,6 +571,7 @@ function browserProcessSampleRef(sample) {
|
||||
function browserPageMetricRef(pageMetric) {
|
||||
const responsiveness = pageMetric?.responsiveness && typeof pageMetric.responsiveness === "object" ? pageMetric.responsiveness : {};
|
||||
const cdp = pageMetric?.cdp && typeof pageMetric.cdp === "object" ? pageMetric.cdp : {};
|
||||
const effectiveMemory = pageMetric?.effectiveMemory && typeof pageMetric.effectiveMemory === "object" ? pageMetric.effectiveMemory : {};
|
||||
return {
|
||||
pageRole: pageMetric?.pageRole ?? null,
|
||||
pageId: pageMetric?.pageId ?? null,
|
||||
@@ -555,6 +581,11 @@ function browserPageMetricRef(pageMetric) {
|
||||
responsivenessTimeout: responsiveness.timeout === true,
|
||||
cdpTimeoutCount: cdp.timeoutCount ?? null,
|
||||
cdpErrorCount: cdp.errorCount ?? null,
|
||||
effectiveHeapUsedMb: Number.isFinite(Number(effectiveMemory.effectiveHeapUsedMb)) ? Number(effectiveMemory.effectiveHeapUsedMb) : null,
|
||||
effectiveJsHeapUsedMb: Number.isFinite(Number(effectiveMemory.effectiveJsHeapUsedMb)) ? Number(effectiveMemory.effectiveJsHeapUsedMb) : null,
|
||||
heapUsedGrowthMb: Number.isFinite(Number(effectiveMemory.heapUsedGrowthMb)) ? Number(effectiveMemory.heapUsedGrowthMb) : null,
|
||||
jsHeapUsedGrowthMb: Number.isFinite(Number(effectiveMemory.jsHeapUsedGrowthMb)) ? Number(effectiveMemory.jsHeapUsedGrowthMb) : null,
|
||||
baselineCapturedAt: pageMetric?.baseline?.capturedAt ?? null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
@@ -771,11 +802,72 @@ async function collectBrowserPageRuntimeMetrics(targetPage, { pageRole, targetPa
|
||||
result.cdp.errorCount = 1;
|
||||
} finally {
|
||||
result.latencyMs = Date.now() - startedAtMs;
|
||||
applyBrowserPageRuntimeBaseline(result);
|
||||
if (session) await withHardTimeout(session.detach(), 1000, "CDP session detach exceeded 1000ms").catch(() => {});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function applyBrowserPageRuntimeBaseline(result) {
|
||||
const key = [result.pageRole || "unknown", result.pageId || "unknown", Number.isFinite(Number(result.pageEpoch)) ? Number(result.pageEpoch) : 0].join(":");
|
||||
const current = browserPageRuntimeMemorySnapshot(result);
|
||||
const existing = browserPageRuntimeBaselines.get(key);
|
||||
if (!existing && current) browserPageRuntimeBaselines.set(key, { ...current, capturedAt: new Date().toISOString(), source: "first-page-runtime-sample", valuesRedacted: true });
|
||||
const baseline = browserPageRuntimeBaselines.get(key) || null;
|
||||
result.baseline = baseline ? {
|
||||
capturedAt: baseline.capturedAt,
|
||||
source: baseline.source,
|
||||
heapUsedMb: baseline.heapUsedMb,
|
||||
jsHeapUsedMb: baseline.jsHeapUsedMb,
|
||||
domNodes: baseline.domNodes,
|
||||
valuesRedacted: true,
|
||||
} : null;
|
||||
result.effectiveMemory = browserPageEffectiveMemory(current, baseline);
|
||||
}
|
||||
|
||||
function browserPageRuntimeMemorySnapshot(result) {
|
||||
const heapUsedBytes = Number(result?.heapUsage?.usedSize);
|
||||
const metrics = result?.performance?.metrics && typeof result.performance.metrics === "object" ? result.performance.metrics : {};
|
||||
const jsHeapUsedBytes = Number(metrics.JSHeapUsedSize);
|
||||
const domNodes = Number(result?.domCounters?.nodes ?? metrics.Nodes);
|
||||
if (!Number.isFinite(heapUsedBytes) && !Number.isFinite(jsHeapUsedBytes) && !Number.isFinite(domNodes)) return null;
|
||||
return {
|
||||
heapUsedBytes: Number.isFinite(heapUsedBytes) ? heapUsedBytes : null,
|
||||
heapUsedMb: Number.isFinite(heapUsedBytes) ? bytesToMb(heapUsedBytes) : null,
|
||||
jsHeapUsedBytes: Number.isFinite(jsHeapUsedBytes) ? jsHeapUsedBytes : null,
|
||||
jsHeapUsedMb: Number.isFinite(jsHeapUsedBytes) ? bytesToMb(jsHeapUsedBytes) : null,
|
||||
domNodes: Number.isFinite(domNodes) ? domNodes : null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function browserPageEffectiveMemory(current, baseline) {
|
||||
if (!current) return { available: false, baselineAvailable: Boolean(baseline), valuesRedacted: true };
|
||||
const heapUsedGrowthBytes = numericDelta(current.heapUsedBytes, baseline?.heapUsedBytes);
|
||||
const jsHeapUsedGrowthBytes = numericDelta(current.jsHeapUsedBytes, baseline?.jsHeapUsedBytes);
|
||||
const domNodesGrowth = numericDelta(current.domNodes, baseline?.domNodes);
|
||||
return {
|
||||
available: true,
|
||||
baselineAvailable: Boolean(baseline),
|
||||
heapUsedMb: current.heapUsedMb,
|
||||
jsHeapUsedMb: current.jsHeapUsedMb,
|
||||
effectiveHeapUsedMb: Number.isFinite(heapUsedGrowthBytes) ? bytesToMb(heapUsedGrowthBytes) : current.heapUsedMb,
|
||||
effectiveJsHeapUsedMb: Number.isFinite(jsHeapUsedGrowthBytes) ? bytesToMb(jsHeapUsedGrowthBytes) : current.jsHeapUsedMb,
|
||||
heapUsedGrowthMb: Number.isFinite(heapUsedGrowthBytes) ? bytesToMb(heapUsedGrowthBytes) : null,
|
||||
jsHeapUsedGrowthMb: Number.isFinite(jsHeapUsedGrowthBytes) ? bytesToMb(jsHeapUsedGrowthBytes) : null,
|
||||
domNodes: current.domNodes,
|
||||
domNodesGrowth: Number.isFinite(domNodesGrowth) ? domNodesGrowth : null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function numericDelta(current, baseline) {
|
||||
const value = Number(current);
|
||||
const base = Number(baseline);
|
||||
if (!Number.isFinite(value) || !Number.isFinite(base)) return null;
|
||||
return value - base;
|
||||
}
|
||||
|
||||
function browserPageRuntimeMetricError(pageRole, targetPageId, pageEpoch, error) {
|
||||
return {
|
||||
pageRole,
|
||||
@@ -1610,7 +1702,7 @@ function chromiumLaunchOptionsForProxy(proxy) {
|
||||
}
|
||||
|
||||
function chromiumLowResourceArgs() {
|
||||
return ["--disable-gpu", "--disable-gpu-compositing", "--no-sandbox"];
|
||||
return [];
|
||||
}
|
||||
|
||||
function browserProcessEnvWithoutProxy() {
|
||||
|
||||
Reference in New Issue
Block a user