fix: validate web-probe resource timing phases (#704)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -1408,6 +1408,11 @@ async function sampleOnePage(targetPage, { reason, groupSeq, pageRole, targetPag
|
||||
decodedBodySize: Number.isFinite(Number(entry.decodedBodySize)) ? Number(entry.decodedBodySize) : null,
|
||||
nextHopProtocol: entry.nextHopProtocol || null,
|
||||
responseStatus: Number.isFinite(Number(entry.responseStatus)) ? Number(entry.responseStatus) : null,
|
||||
serverTiming: Array.from(entry.serverTiming || []).slice(0, 8).map((item) => ({
|
||||
name: String(item.name || "").slice(0, 80),
|
||||
duration: Number.isFinite(Number(item.duration)) ? Math.round(Number(item.duration)) : null,
|
||||
description: String(item.description || "").slice(0, 120)
|
||||
})),
|
||||
});
|
||||
const url = location.href;
|
||||
const routeSessionMatch = url.match(/\/workbench\/sessions\/([^/?#]+)/u);
|
||||
@@ -2456,6 +2461,8 @@ function performanceEntryCompletedEpochMs(sample, entry) {
|
||||
}
|
||||
|
||||
function compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPath, rawPath, durationMs, streamOpenMs }) {
|
||||
const timingStatus = resourceTimingPhaseStatus(entry);
|
||||
const serverTiming = compactServerTiming(entry?.serverTiming);
|
||||
return {
|
||||
ts: entryTs ?? sample?.ts ?? null,
|
||||
sampleTs: sample?.ts ?? null,
|
||||
@@ -2475,10 +2482,16 @@ function compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPa
|
||||
tlsStartMs: roundFinite(entry?.secureConnectionStart),
|
||||
requestToResponseStartMs: phaseDeltaMs(entry, "responseStart", "requestStart"),
|
||||
responseTransferMs: phaseDeltaMs(entry, "responseEnd", "responseStart"),
|
||||
timingStatus: timingStatus.status,
|
||||
invalidTimingPhases: timingStatus.invalidPhases,
|
||||
partialTimingPhases: timingStatus.partialPhases,
|
||||
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,
|
||||
serverTiming,
|
||||
serverTimingNames: serverTiming.map((item) => item.name).filter(Boolean).slice(0, 8),
|
||||
otelTraceId: extractOtelTraceIdFromServerTiming(serverTiming),
|
||||
valuesRedacted: true
|
||||
};
|
||||
}
|
||||
@@ -2486,10 +2499,48 @@ function compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPa
|
||||
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;
|
||||
if (!Number.isFinite(end) || !Number.isFinite(start) || end <= 0 || start <= 0 || end < start) return null;
|
||||
return Math.round(end - start);
|
||||
}
|
||||
|
||||
function resourceTimingPhaseStatus(entry) {
|
||||
const pairs = [
|
||||
["requestToResponseStart", "requestStart", "responseStart"],
|
||||
["responseTransfer", "responseStart", "responseEnd"],
|
||||
];
|
||||
const invalidPhases = [];
|
||||
const partialPhases = [];
|
||||
for (const [label, startKey, endKey] of pairs) {
|
||||
const start = Number(entry?.[startKey]);
|
||||
const end = Number(entry?.[endKey]);
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0) {
|
||||
partialPhases.push(label);
|
||||
} else if (end < start) {
|
||||
invalidPhases.push(label);
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: invalidPhases.length > 0 ? "invalid" : (partialPhases.length > 0 ? "partial" : "complete"),
|
||||
invalidPhases,
|
||||
partialPhases,
|
||||
};
|
||||
}
|
||||
|
||||
function compactServerTiming(value) {
|
||||
const items = Array.isArray(value) ? value : [];
|
||||
return items.slice(0, 8).map((item) => ({
|
||||
name: truncate(String(item?.name || ""), 80),
|
||||
duration: Number.isFinite(Number(item?.duration)) ? Math.round(Number(item.duration)) : null,
|
||||
description: truncate(String(item?.description || ""), 120),
|
||||
})).filter((item) => item.name || item.description || item.duration !== null);
|
||||
}
|
||||
|
||||
function extractOtelTraceIdFromServerTiming(items) {
|
||||
const text = (Array.isArray(items) ? items : []).map((item) => [item.name, item.description].filter(Boolean).join(" ")).join(" ");
|
||||
const match = text.match(/\b[0-9a-f]{32}\b/iu);
|
||||
return match ? match[0].toLowerCase() : null;
|
||||
}
|
||||
|
||||
function roundFinite(value) {
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) ? Math.round(numeric) : null;
|
||||
|
||||
Reference in New Issue
Block a user