From cf8d691c73f17767f9ed6057a4bc89f1d70de701 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:11:25 +0800 Subject: [PATCH] fix(workbench): improve rum resource timing coverage (#1936) --- .../src/utils/workbench-performance.ts | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/web/hwlab-cloud-web/src/utils/workbench-performance.ts b/web/hwlab-cloud-web/src/utils/workbench-performance.ts index b699c73e..0918fe42 100644 --- a/web/hwlab-cloud-web/src/utils/workbench-performance.ts +++ b/web/hwlab-cloud-web/src/utils/workbench-performance.ts @@ -141,6 +141,7 @@ const loadingStates = new Map(); let openJourney: OpenJourneyState | null = null; let queue: WorkbenchPerformanceEvent[] = []; let installed = false; +let resourceTimingBufferConfigured = false; let flushTimer: number | null = null; let uiTrace: WorkbenchUiTrace | null = null; @@ -224,9 +225,10 @@ function resourceTimingForApiRequest(route: string, startedAtEpochMs: number, en const lowerBound = startedAtEpochMs - 2000; const upperBound = endedAtEpochMs + 2000; const candidates = entries - .filter((entry) => resourceEntryMatches(entry, target, timeOrigin, lowerBound, upperBound)) - .sort((left, right) => Math.abs(timeOrigin + right.startTime - startedAtEpochMs) - Math.abs(timeOrigin + left.startTime - startedAtEpochMs)); - const entry = candidates.at(-1); + .map((entry) => resourceTimingCandidate(entry, target, timeOrigin, lowerBound, upperBound, startedAtEpochMs)) + .filter((candidate): candidate is ResourceTimingCandidate => candidate !== null) + .sort((left, right) => Number(right.queryExact) - Number(left.queryExact) || left.initiatorRank - right.initiatorRank || left.startDistanceMs - right.startDistanceMs); + const entry = candidates[0]?.entry; if (!entry) return {}; const result: Partial = { resourceDurationMs: roundedNonNegative(entry.duration), @@ -242,19 +244,39 @@ function resourceTimingForApiRequest(route: string, startedAtEpochMs: number, en return Object.fromEntries(Object.entries(result).filter(([, value]) => value !== undefined)) as Partial; } -function resourceEntryMatches(entry: PerformanceResourceTiming, target: URL, timeOrigin: number, lowerBound: number, upperBound: number): boolean { - if (!entry || entry.initiatorType !== "fetch") return false; +interface ResourceTimingCandidate { + entry: PerformanceResourceTiming; + queryExact: boolean; + initiatorRank: number; + startDistanceMs: number; +} + +function resourceTimingCandidate(entry: PerformanceResourceTiming, target: URL, timeOrigin: number, lowerBound: number, upperBound: number, startedAtEpochMs: number): ResourceTimingCandidate | null { + if (!entry) return null; let url: URL; try { url = new URL(entry.name); } catch { - return false; + return null; } - if (url.origin !== target.origin) return false; - if (url.pathname !== target.pathname) return false; - if (target.search && url.search !== target.search) return false; + if (url.origin !== target.origin) return null; + if (url.pathname !== target.pathname) return null; const started = timeOrigin + entry.startTime; - return started >= lowerBound && started <= upperBound; + if (started < lowerBound || started > upperBound) return null; + return { + entry, + queryExact: url.search === target.search, + initiatorRank: resourceInitiatorRank(entry.initiatorType), + startDistanceMs: Math.abs(started - startedAtEpochMs) + }; +} + +function resourceInitiatorRank(value: string): number { + const initiator = safeText(value).toLowerCase(); + if (initiator === "fetch") return 0; + if (initiator === "xmlhttprequest") return 1; + if (!initiator) return 2; + return 3; } function roundedDelta(end: number, start: number): number | undefined { @@ -549,6 +571,7 @@ function enrichWorkbenchEventForFlush(event: WorkbenchPerformanceEvent): Workben function ensureInstalled(): void { if (installed || typeof document === "undefined") return; installed = true; + configureResourceTimingBuffer(); currentWorkbenchUiTrace(); document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { @@ -562,6 +585,12 @@ function ensureInstalled(): void { }); } +function configureResourceTimingBuffer(): void { + if (resourceTimingBufferConfigured || typeof performance === "undefined" || typeof performance.setResourceTimingBufferSize !== "function") return; + resourceTimingBufferConfigured = true; + performance.setResourceTimingBufferSize(2000); +} + function afterNextPaint(callback: () => void): void { if (typeof requestAnimationFrame !== "function") { callback();