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
+60
View File
@@ -148,6 +148,7 @@ export interface HwlabRuntimeWebProbeSpec {
readonly browserProxyMode?: "auto" | "direct";
readonly playwrightBrowsersPath?: string;
readonly defaultOrigin?: HwlabRuntimeWebProbeOriginName;
readonly resourcePolicy?: HwlabRuntimeWebProbeResourcePolicySpec;
readonly origins?: HwlabRuntimeWebProbeOriginsSpec;
readonly authLogin?: HwlabRuntimeWebProbeAuthLoginSpec;
readonly alertThresholds?: HwlabRuntimeWebProbeAlertThresholdsSpec;
@@ -158,6 +159,32 @@ export interface HwlabRuntimeWebProbeSpec {
readonly workbenchTraceReadability?: HwlabRuntimeWebProbeWorkbenchTraceReadabilitySpec;
}
export interface HwlabRuntimeWebProbeMemoryStartGuardSpec {
readonly metric: "MemAvailable";
readonly source: "/proc/meminfo";
readonly swapExcluded: true;
readonly thresholdBytes: number;
readonly manualComparator: "less-than-or-equal";
readonly sentinelComparator: "less-than";
readonly snapshotTimeoutSeconds: number;
}
export interface HwlabRuntimeWebProbeObserverLifecycleSpec {
readonly maxRunSeconds: number;
readonly maxSamples: number;
readonly maxScreenshots: number;
readonly maxPerformanceEntries: number;
readonly maxBrowserProcessHistoryEntries: number;
readonly maxBrowserFreezeSignalHistoryEntries: number;
readonly maxResponseBodyBytes: number;
readonly maxWebPerformanceBodyBytes: number;
}
export interface HwlabRuntimeWebProbeResourcePolicySpec {
readonly memoryStartGuard: HwlabRuntimeWebProbeMemoryStartGuardSpec;
readonly observerLifecycle: HwlabRuntimeWebProbeObserverLifecycleSpec;
}
export interface HwlabRuntimeWebProbeWorkbenchKafkaDebugReplaySpec {
readonly enabled: boolean;
readonly topic: string;
@@ -754,6 +781,11 @@ function booleanField(obj: Record<string, unknown>, key: string, path: string):
return value;
}
function literalTrueField(obj: Record<string, unknown>, key: string, path: string): true {
if (obj[key] !== true) throw new Error(`${path}.${key} must be true`);
return true;
}
function nonNegativeIntegerField(obj: Record<string, unknown>, key: string, path: string): number {
const value = obj[key];
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new Error(`${path}.${key} must be a non-negative integer`);
@@ -1410,6 +1442,7 @@ function webProbeConfig(value: unknown, path: string): HwlabRuntimeWebProbeSpec
...(browserProxyMode === undefined ? {} : { browserProxyMode }),
...(playwrightBrowsersPath === undefined ? {} : { playwrightBrowsersPath }),
...(defaultOrigin === undefined ? {} : { defaultOrigin }),
...(raw.resourcePolicy === undefined ? {} : { resourcePolicy: webProbeResourcePolicyConfig(raw.resourcePolicy, `${path}.resourcePolicy`) }),
...(origins === undefined ? {} : { origins }),
...(raw.authLogin === undefined ? {} : { authLogin: webProbeAuthLoginConfig(raw.authLogin, `${path}.authLogin`) }),
...(raw.alertThresholds === undefined ? {} : { alertThresholds: webProbeAlertThresholdsConfig(raw.alertThresholds, `${path}.alertThresholds`) }),
@@ -1425,6 +1458,33 @@ function webProbeConfig(value: unknown, path: string): HwlabRuntimeWebProbeSpec
};
}
function webProbeResourcePolicyConfig(value: unknown, path: string): HwlabRuntimeWebProbeResourcePolicySpec {
const raw = asRecord(value, path);
const memory = asRecord(raw.memoryStartGuard, `${path}.memoryStartGuard`);
const lifecycle = asRecord(raw.observerLifecycle, `${path}.observerLifecycle`);
return {
memoryStartGuard: {
metric: enumStringField(memory, "metric", `${path}.memoryStartGuard`, ["MemAvailable"] as const),
source: enumStringField(memory, "source", `${path}.memoryStartGuard`, ["/proc/meminfo"] as const),
swapExcluded: literalTrueField(memory, "swapExcluded", `${path}.memoryStartGuard`),
thresholdBytes: boundedIntegerField(memory, "thresholdBytes", `${path}.memoryStartGuard`, 1, Number.MAX_SAFE_INTEGER),
manualComparator: enumStringField(memory, "manualComparator", `${path}.memoryStartGuard`, ["less-than-or-equal"] as const),
sentinelComparator: enumStringField(memory, "sentinelComparator", `${path}.memoryStartGuard`, ["less-than"] as const),
snapshotTimeoutSeconds: boundedIntegerField(memory, "snapshotTimeoutSeconds", `${path}.memoryStartGuard`, 5, 120),
},
observerLifecycle: {
maxRunSeconds: boundedIntegerField(lifecycle, "maxRunSeconds", `${path}.observerLifecycle`, 1, 86_400),
maxSamples: boundedIntegerField(lifecycle, "maxSamples", `${path}.observerLifecycle`, 1, 10_000_000),
maxScreenshots: boundedIntegerField(lifecycle, "maxScreenshots", `${path}.observerLifecycle`, 1, 10_000),
maxPerformanceEntries: boundedIntegerField(lifecycle, "maxPerformanceEntries", `${path}.observerLifecycle`, 1, 100_000),
maxBrowserProcessHistoryEntries: boundedIntegerField(lifecycle, "maxBrowserProcessHistoryEntries", `${path}.observerLifecycle`, 1, 100_000),
maxBrowserFreezeSignalHistoryEntries: boundedIntegerField(lifecycle, "maxBrowserFreezeSignalHistoryEntries", `${path}.observerLifecycle`, 1, 100_000),
maxResponseBodyBytes: boundedIntegerField(lifecycle, "maxResponseBodyBytes", `${path}.observerLifecycle`, 1, 16 * 1024 * 1024),
maxWebPerformanceBodyBytes: boundedIntegerField(lifecycle, "maxWebPerformanceBodyBytes", `${path}.observerLifecycle`, 1, 16 * 1024 * 1024),
},
};
}
function webProbeWorkbenchKafkaDebugReplayConfig(value: unknown, path: string): HwlabRuntimeWebProbeWorkbenchKafkaDebugReplaySpec {
const raw = asRecord(value, path);
const topic = stringField(raw, "topic", path);