fix(web-probe): expose slow API samples in observe analysis (#644)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-22 09:59:40 +08:00
committed by GitHub
parent 49f2926731
commit 1780cb8980
3 changed files with 143 additions and 21 deletions
@@ -1488,7 +1488,23 @@ function compactPerformanceItems(items) {
name: item?.name ?? null,
initiatorType: item?.initiatorType ?? null,
startTime: item?.startTime ?? null,
duration: item?.duration ?? null
duration: item?.duration ?? null,
workerStart: item?.workerStart ?? null,
redirectStart: item?.redirectStart ?? null,
redirectEnd: item?.redirectEnd ?? null,
fetchStart: item?.fetchStart ?? null,
domainLookupStart: item?.domainLookupStart ?? null,
domainLookupEnd: item?.domainLookupEnd ?? null,
connectStart: item?.connectStart ?? null,
connectEnd: item?.connectEnd ?? null,
secureConnectionStart: item?.secureConnectionStart ?? null,
requestStart: item?.requestStart ?? null,
responseStart: item?.responseStart ?? null,
responseEnd: item?.responseEnd ?? null,
transferSize: item?.transferSize ?? null,
encodedBodySize: item?.encodedBodySize ?? null,
decodedBodySize: item?.decodedBodySize ?? null,
nextHopProtocol: item?.nextHopProtocol ?? null
}));
}
@@ -1707,10 +1723,12 @@ function buildPagePerformanceReport(samples, manifest) {
lastSeq: sample.seq ?? null,
initiatorTypes: [],
pageAssetFingerprints: [],
slowSamples: [],
valuesRedacted: true
};
group.sampleCount += 1;
group.durationsMs.push(durationMs);
let overBudget = false;
if (isLongLivedStream) {
if (durationMs > 5000) group.streamLifetimeOverFiveSecondCount += 1;
if (streamOpenMs !== null) {
@@ -1718,11 +1736,14 @@ function buildPagePerformanceReport(samples, manifest) {
if (streamOpenMs > 5000) {
group.streamOpenOverFiveSecondCount += 1;
group.overFiveSecondCount += 1;
overBudget = true;
}
}
} else if (durationMs > 5000) {
group.overFiveSecondCount += 1;
overBudget = true;
}
if (overBudget && group.slowSamples.length < 80) group.slowSamples.push(compactPagePerformanceSlowSample({ sample, entry, normalizedPath, rawPath: parsed.path, durationMs, streamOpenMs }));
group.lastAt = sample.ts ?? null;
group.lastSeq = sample.seq ?? null;
if (parsed.path && !group.rawPathSamples.includes(parsed.path)) group.rawPathSamples.push(parsed.path);
@@ -1761,6 +1782,10 @@ function buildPagePerformanceReport(samples, manifest) {
initiatorTypes: group.initiatorTypes,
rawPathSamples: group.rawPathSamples.slice(0, 8),
pageAssetFingerprints: group.pageAssetFingerprints.slice(0, 8),
slowSamples: group.slowSamples
.slice()
.sort((a, b) => Number(b.durationMs ?? 0) - Number(a.durationMs ?? 0))
.slice(0, 12),
valuesRedacted: true
};
}).sort((a, b) => (b.overFiveSecondCount - a.overFiveSecondCount) || (Number(b.p95Ms ?? 0) - Number(a.p95Ms ?? 0)) || a.path.localeCompare(b.path));
@@ -1786,6 +1811,45 @@ function buildPagePerformanceReport(samples, manifest) {
};
}
function compactPagePerformanceSlowSample({ sample, entry, normalizedPath, rawPath, durationMs, streamOpenMs }) {
return {
ts: sample?.ts ?? null,
seq: sample?.seq ?? null,
path: normalizedPath ?? null,
rawPath: rawPath ?? null,
initiatorType: entry?.initiatorType ?? null,
durationMs: roundFinite(durationMs),
startTimeMs: roundFinite(entry?.startTime),
fetchStartMs: roundFinite(entry?.fetchStart),
requestStartMs: roundFinite(entry?.requestStart),
responseStartMs: roundFinite(entry?.responseStart),
responseEndMs: roundFinite(entry?.responseEnd),
streamOpenMs: roundFinite(streamOpenMs),
dnsMs: phaseDeltaMs(entry, "domainLookupEnd", "domainLookupStart"),
tcpMs: phaseDeltaMs(entry, "connectEnd", "connectStart"),
tlsStartMs: roundFinite(entry?.secureConnectionStart),
requestToResponseStartMs: phaseDeltaMs(entry, "responseStart", "requestStart"),
responseTransferMs: phaseDeltaMs(entry, "responseEnd", "responseStart"),
transferSize: Number.isFinite(Number(entry?.transferSize)) ? Number(entry.transferSize) : null,
encodedBodySize: Number.isFinite(Number(entry?.encodedBodySize)) ? Number(entry.encodedBodySize) : null,
decodedBodySize: Number.isFinite(Number(entry?.decodedBodySize)) ? Number(entry.decodedBodySize) : null,
nextHopProtocol: entry?.nextHopProtocol ?? null,
valuesRedacted: true
};
}
function phaseDeltaMs(entry, endKey, startKey) {
const end = Number(entry?.[endKey]);
const start = Number(entry?.[startKey]);
if (!Number.isFinite(end) || !Number.isFinite(start) || end <= 0 || start < 0 || end < start) return null;
return Math.round(end - start);
}
function roundFinite(value) {
const numeric = Number(value);
return Number.isFinite(numeric) ? Math.round(numeric) : null;
}
function classifyApiPerformanceRoute(normalizedPath, entry = {}) {
if (normalizedPath === "/v1/workbench/events") return "same-origin-api-stream";
if (String(entry?.initiatorType ?? "").toLowerCase() === "eventsource") return "same-origin-api-stream";