diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index a6dc028d..89ddc2c0 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -1706,18 +1706,25 @@ function buildPagePerformanceReport(samples, manifest) { const base = manifest?.baseUrl || "http://invalid.local"; const seen = new Set(); const groups = new Map(); + const sampleTimes = samples.map((sample) => Date.parse(sample?.ts || "")).filter(Number.isFinite); + const windowStartMs = sampleTimes.length > 0 ? Math.min(...sampleTimes) : null; + const windowEndMs = sampleTimes.length > 0 ? Math.max(...sampleTimes) : null; for (const sample of samples) { const entries = Array.isArray(sample?.performance) ? sample.performance : []; for (const entry of entries) { const durationMs = Number(entry?.duration); if (!Number.isFinite(durationMs) || durationMs < 0) continue; + const entryCompletedMs = performanceEntryCompletedEpochMs(sample, entry); + if (windowStartMs !== null && entryCompletedMs !== null && entryCompletedMs < windowStartMs) continue; + if (windowEndMs !== null && entryCompletedMs !== null && entryCompletedMs > windowEndMs + 1000) continue; + const entryTs = entryCompletedMs === null ? (sample.ts ?? null) : new Date(entryCompletedMs).toISOString(); const parsed = parsePerformanceUrl(entry?.name, base); if (!parsed.sameOrigin || !isApiLikePath(parsed.path)) continue; const normalizedPath = normalizeApiPath(parsed.path); const routeKind = classifyApiPerformanceRoute(normalizedPath, entry); const isLongLivedStream = routeKind === "same-origin-api-stream"; const streamOpenMs = streamOpenLatencyMs(entry); - const dedupeKey = [parsed.path, entry.initiatorType || "", entry.startTime ?? "", Math.round(durationMs)].join("|"); + const dedupeKey = [parsed.path, entry.initiatorType || "", sample?.pageProvenance?.pageLoadSeq ?? "", sample?.pageProvenance?.timeOrigin ?? "", entry.startTime ?? "", Math.round(durationMs)].join("|"); if (seen.has(dedupeKey)) continue; seen.add(dedupeKey); const group = groups.get(normalizedPath) || { @@ -1732,8 +1739,8 @@ function buildPagePerformanceReport(samples, manifest) { overFiveSecondCount: 0, streamLifetimeOverFiveSecondCount: 0, streamOpenOverFiveSecondCount: 0, - firstAt: sample.ts ?? null, - lastAt: sample.ts ?? null, + firstAt: entryTs, + lastAt: entryTs, firstSeq: sample.seq ?? null, lastSeq: sample.seq ?? null, initiatorTypes: [], @@ -1758,8 +1765,8 @@ function buildPagePerformanceReport(samples, manifest) { 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; + if (overBudget && group.slowSamples.length < 80) group.slowSamples.push(compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPath, rawPath: parsed.path, durationMs, streamOpenMs })); + group.lastAt = entryTs; group.lastSeq = sample.seq ?? null; if (parsed.path && !group.rawPathSamples.includes(parsed.path)) group.rawPathSamples.push(parsed.path); if (entry.initiatorType && !group.initiatorTypes.includes(entry.initiatorType)) group.initiatorTypes.push(entry.initiatorType); @@ -1826,9 +1833,20 @@ function buildPagePerformanceReport(samples, manifest) { }; } -function compactPagePerformanceSlowSample({ sample, entry, normalizedPath, rawPath, durationMs, streamOpenMs }) { +function performanceEntryCompletedEpochMs(sample, entry) { + const origin = Number(sample?.pageProvenance?.timeOrigin); + const responseEnd = Number(entry?.responseEnd); + const startTime = Number(entry?.startTime); + const offset = Number.isFinite(responseEnd) && responseEnd > 0 ? responseEnd : startTime; + if (Number.isFinite(origin) && origin > 0 && Number.isFinite(offset) && offset >= 0) return Math.round(origin + offset); + const sampleTs = Date.parse(sample?.ts || ""); + return Number.isFinite(sampleTs) ? sampleTs : null; +} + +function compactPagePerformanceSlowSample({ sample, entry, entryTs, normalizedPath, rawPath, durationMs, streamOpenMs }) { return { - ts: sample?.ts ?? null, + ts: entryTs ?? sample?.ts ?? null, + sampleTs: sample?.ts ?? null, seq: sample?.seq ?? null, path: normalizedPath ?? null, rawPath: rawPath ?? null,