fix: web-probe 报警阈值强制走 YAML (#731)
* fix(hwlab): show runtime blockers in node status * fix: require yaml web-probe alert thresholds --------- Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -8731,15 +8731,16 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
const pagePerformance = record(analysis?.pagePerformance);
|
||||
const pagePerformanceSummary = record(pagePerformance?.summary);
|
||||
const alertThresholds = record(analysis?.alertThresholds ?? pagePerformanceSummary?.alertThresholds ?? value.alertThresholds);
|
||||
const budgetLabel = (rawValue: unknown, fallbackMs: number): string => {
|
||||
const budgetLabel = (rawValue: unknown): string => {
|
||||
const parsed = Number(rawValue);
|
||||
const ms = Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackMs;
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return "unconfigured";
|
||||
const ms = parsed;
|
||||
return ms % 1000 === 0 ? `${Math.round(ms / 1000)}s` : `${ms}ms`;
|
||||
};
|
||||
const sameOriginApiBudgetLabel = budgetLabel(alertThresholds?.sameOriginApiSlowMs ?? pagePerformanceSummary?.budgetMs, 10000);
|
||||
const partialApiBudgetLabel = budgetLabel(alertThresholds?.partialApiSlowMs, 10000);
|
||||
const streamOpenBudgetLabel = budgetLabel(alertThresholds?.longLivedStreamOpenSlowMs, 10000);
|
||||
const loadingBudgetLabel = budgetLabel(alertThresholds?.visibleLoadingSlowMs, 10000);
|
||||
const sameOriginApiBudgetLabel = budgetLabel(alertThresholds?.sameOriginApiSlowMs ?? pagePerformanceSummary?.budgetMs);
|
||||
const partialApiBudgetLabel = budgetLabel(alertThresholds?.partialApiSlowMs);
|
||||
const streamOpenBudgetLabel = budgetLabel(alertThresholds?.longLivedStreamOpenSlowMs);
|
||||
const loadingBudgetLabel = budgetLabel(alertThresholds?.visibleLoadingSlowMs);
|
||||
const promptNetwork = record(analysis?.promptNetwork);
|
||||
const loading = record(sampleMetrics?.loading);
|
||||
const loadingSummary = record(loading?.summary);
|
||||
|
||||
@@ -1881,27 +1881,33 @@ function positiveNumber(value, fallback) {
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function requiredPositiveThreshold(raw, key) {
|
||||
const parsed = Number(raw?.[key]);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON requires positive " + key + "; configure config/hwlab-node-lanes.yaml webProbe.alertThresholds");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseAlertThresholds(value) {
|
||||
if (!value) {
|
||||
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON is required; configure config/hwlab-node-lanes.yaml webProbe.alertThresholds for the selected node/lane");
|
||||
}
|
||||
const raw = (() => {
|
||||
try { return value ? JSON.parse(value) : {}; } catch { return {}; }
|
||||
try { return JSON.parse(value); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error))); }
|
||||
})();
|
||||
const fallback = {
|
||||
sameOriginApiSlowMs: 10000,
|
||||
partialApiSlowMs: 10000,
|
||||
longLivedStreamOpenSlowMs: 10000,
|
||||
visibleLoadingSlowMs: 10000,
|
||||
turnTimingSampleSlackSeconds: 3,
|
||||
sessionRailFallbackRatio: 0.5,
|
||||
};
|
||||
const sessionRailFallbackRatio = positiveNumber(raw.sessionRailFallbackRatio, fallback.sessionRailFallbackRatio);
|
||||
const sessionRailFallbackRatio = requiredPositiveThreshold(raw, "sessionRailFallbackRatio");
|
||||
if (sessionRailFallbackRatio > 1) {
|
||||
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON sessionRailFallbackRatio must be <= 1");
|
||||
}
|
||||
return {
|
||||
sameOriginApiSlowMs: positiveNumber(raw.sameOriginApiSlowMs, fallback.sameOriginApiSlowMs),
|
||||
partialApiSlowMs: positiveNumber(raw.partialApiSlowMs, fallback.partialApiSlowMs),
|
||||
longLivedStreamOpenSlowMs: positiveNumber(raw.longLivedStreamOpenSlowMs, fallback.longLivedStreamOpenSlowMs),
|
||||
visibleLoadingSlowMs: positiveNumber(raw.visibleLoadingSlowMs, fallback.visibleLoadingSlowMs),
|
||||
turnTimingSampleSlackSeconds: positiveNumber(raw.turnTimingSampleSlackSeconds, fallback.turnTimingSampleSlackSeconds),
|
||||
sessionRailFallbackRatio: Math.min(1, sessionRailFallbackRatio),
|
||||
source: value ? "yaml-env" : "runner-default",
|
||||
sameOriginApiSlowMs: requiredPositiveThreshold(raw, "sameOriginApiSlowMs"),
|
||||
partialApiSlowMs: requiredPositiveThreshold(raw, "partialApiSlowMs"),
|
||||
longLivedStreamOpenSlowMs: requiredPositiveThreshold(raw, "longLivedStreamOpenSlowMs"),
|
||||
visibleLoadingSlowMs: requiredPositiveThreshold(raw, "visibleLoadingSlowMs"),
|
||||
turnTimingSampleSlackSeconds: requiredPositiveThreshold(raw, "turnTimingSampleSlackSeconds"),
|
||||
sessionRailFallbackRatio,
|
||||
source: "yaml-env",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2216,27 +2222,33 @@ function positiveNumber(value, fallback) {
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function requiredPositiveThreshold(raw, key) {
|
||||
const parsed = Number(raw?.[key]);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON requires positive " + key + "; configure config/hwlab-node-lanes.yaml webProbe.alertThresholds");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseAlertThresholds(value) {
|
||||
if (!value) {
|
||||
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON is required; configure config/hwlab-node-lanes.yaml webProbe.alertThresholds for the selected node/lane");
|
||||
}
|
||||
const raw = (() => {
|
||||
try { return value ? JSON.parse(value) : {}; } catch { return {}; }
|
||||
try { return JSON.parse(value); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error))); }
|
||||
})();
|
||||
const fallback = {
|
||||
sameOriginApiSlowMs: 10000,
|
||||
partialApiSlowMs: 10000,
|
||||
longLivedStreamOpenSlowMs: 10000,
|
||||
visibleLoadingSlowMs: 10000,
|
||||
turnTimingSampleSlackSeconds: 3,
|
||||
sessionRailFallbackRatio: 0.5,
|
||||
};
|
||||
const sessionRailFallbackRatio = positiveNumber(raw.sessionRailFallbackRatio, fallback.sessionRailFallbackRatio);
|
||||
const sessionRailFallbackRatio = requiredPositiveThreshold(raw, "sessionRailFallbackRatio");
|
||||
if (sessionRailFallbackRatio > 1) {
|
||||
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON sessionRailFallbackRatio must be <= 1");
|
||||
}
|
||||
return {
|
||||
sameOriginApiSlowMs: positiveNumber(raw.sameOriginApiSlowMs, fallback.sameOriginApiSlowMs),
|
||||
partialApiSlowMs: positiveNumber(raw.partialApiSlowMs, fallback.partialApiSlowMs),
|
||||
longLivedStreamOpenSlowMs: positiveNumber(raw.longLivedStreamOpenSlowMs, fallback.longLivedStreamOpenSlowMs),
|
||||
visibleLoadingSlowMs: positiveNumber(raw.visibleLoadingSlowMs, fallback.visibleLoadingSlowMs),
|
||||
turnTimingSampleSlackSeconds: positiveNumber(raw.turnTimingSampleSlackSeconds, fallback.turnTimingSampleSlackSeconds),
|
||||
sessionRailFallbackRatio: Math.min(1, sessionRailFallbackRatio),
|
||||
source: value ? "yaml-env" : "analyzer-default",
|
||||
sameOriginApiSlowMs: requiredPositiveThreshold(raw, "sameOriginApiSlowMs"),
|
||||
partialApiSlowMs: requiredPositiveThreshold(raw, "partialApiSlowMs"),
|
||||
longLivedStreamOpenSlowMs: requiredPositiveThreshold(raw, "longLivedStreamOpenSlowMs"),
|
||||
visibleLoadingSlowMs: requiredPositiveThreshold(raw, "visibleLoadingSlowMs"),
|
||||
turnTimingSampleSlackSeconds: requiredPositiveThreshold(raw, "turnTimingSampleSlackSeconds"),
|
||||
sessionRailFallbackRatio,
|
||||
source: "yaml-env",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5019,8 +5031,8 @@ function renderMarkdown(report) {
|
||||
: "- 无页面 provenance segment。";
|
||||
const ordinaryPerformanceItems = Array.isArray(report.pagePerformance?.sameOriginApiByPath) ? report.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream !== true) : [];
|
||||
const streamPerformanceItems = Array.isArray(report.pagePerformance?.sameOriginApiByPath) ? report.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream === true) : [];
|
||||
const sameOriginApiBudgetMs = Number(report.alertThresholds?.sameOriginApiSlowMs ?? report.pagePerformance?.summary?.budgetMs ?? 10000);
|
||||
const streamOpenBudgetMs = Number(report.alertThresholds?.longLivedStreamOpenSlowMs ?? 10000);
|
||||
const sameOriginApiBudgetMs = Number(report.alertThresholds?.sameOriginApiSlowMs ?? report.pagePerformance?.summary?.budgetMs);
|
||||
const streamOpenBudgetMs = Number(report.alertThresholds?.longLivedStreamOpenSlowMs);
|
||||
const performanceLines = ordinaryPerformanceItems.length > 0
|
||||
? ordinaryPerformanceItems.slice(0, 80).map((item) => "- " + item.path + " kind=" + (item.routeKind || "same-origin-api") + " budgetMetric=" + (item.budgetMetric || "durationMs") + " samples=" + item.sampleCount + " p50=" + (item.p50Ms ?? "-") + "ms p75=" + (item.p75Ms ?? "-") + "ms p95=" + (item.p95Ms ?? "-") + "ms max=" + (item.maxMs ?? "-") + "ms >budget=" + (item.overBudgetCount ?? item.overFiveSecondCount ?? 0) + " budgetMs=" + (item.budgetMs ?? sameOriginApiBudgetMs) + " legacy>5s=" + (item.overFiveSecondCount ?? 0) + " window=" + (item.firstAt || "-") + ".." + (item.lastAt || "-")).join("\n")
|
||||
: "- 无同源 API Resource Timing 样本。";
|
||||
@@ -5083,7 +5095,7 @@ function renderMarkdown(report) {
|
||||
+ "- overFiveSecondSegmentCount: " + (loadingSummary.overFiveSecondSegmentCount ?? 0) + "\n"
|
||||
+ "- longestContinuousSeconds: " + (loadingSummary.longestContinuousSeconds ?? 0) + "\n"
|
||||
+ "- currentContinuousSeconds: " + (loadingSummary.currentContinuousSeconds ?? 0) + "\n"
|
||||
+ "- budgetSeconds: " + (loadingSummary.budgetSeconds ?? (Number(report.alertThresholds?.visibleLoadingSlowMs ?? 10000) / 1000)) + "\n"
|
||||
+ "- budgetSeconds: " + (loadingSummary.budgetSeconds ?? (Number.isFinite(Number(report.alertThresholds?.visibleLoadingSlowMs)) ? Number(report.alertThresholds.visibleLoadingSlowMs) / 1000 : "unconfigured")) + "\n"
|
||||
+ "- policy: 该指标只能证明用户真实看到“加载中”的持续时间;修复必须降低真实请求/投影/渲染耗时,禁止提前展示未加载完内容来压低该指标。\n\n"
|
||||
+ "#### Loading segments\n\n" + loadingSegmentLines + "\n\n"
|
||||
+ "#### Loading owners\n\n" + loadingOwnerLines + "\n\n"
|
||||
|
||||
Reference in New Issue
Block a user