From 62f04ce2405a795d1fc0572e284fa142cec89263 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Sat, 1 Aug 2026 16:27:03 +0200 Subject: [PATCH] fix(studio): count logical media fetches, not ranged reads A media element pulls one file as a series of ranged reads. Counting raw HTTP requests reported a file fetched once in eight chunks as "8x amplification", and the number got WORSE when a fix deferred a source so the browser later fetched it in pieces: 19 requests became 37 for byte identical content, and the ratio climbed from 2.38x to 4.63x while the actual cost was unchanged. Ranged reads of one URL now collapse into a single logical fetch. Raw and ranged counts stay in the output as diagnostics. Same tree, same fixture: 4.63x becomes 1.13x, which is what the transferred bytes said all along. --- .../studio/tests/e2e/studio-runtime-cost.mjs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/studio/tests/e2e/studio-runtime-cost.mjs b/packages/studio/tests/e2e/studio-runtime-cost.mjs index 7f28f064b8..e9c3ce1389 100644 --- a/packages/studio/tests/e2e/studio-runtime-cost.mjs +++ b/packages/studio/tests/e2e/studio-runtime-cost.mjs @@ -553,7 +553,9 @@ async function recordRequests(client) { }; await client.send("Network.enable"); client.on("Network.requestWillBeSent", ({ requestId, request, type }) => { - pending.set(requestId, { url: request.url, type }); + const headers = request.headers ?? {}; + const ranged = Boolean(headers.Range ?? headers.range); + pending.set(requestId, { url: request.url, type, ranged }); activity.at = Date.now(); }); client.on("Network.loadingFinished", ({ requestId, encodedDataLength }) => @@ -575,11 +577,39 @@ async function waitForNetworkQuiet(activity) { } } +/** + * A media element fetches one file as a series of ranged reads — that is normal + * playback behaviour, not duplication. Counting raw HTTP requests therefore + * reports a file pulled once in eight chunks as "8x amplification", and gets + * WORSE when a fix defers a source so it is later fetched in pieces. + * + * What this gate is for is wasted fetching: the same source pulled from scratch + * by independent subsystems that do not share a cache. So ranged reads of one + * URL collapse into a single logical fetch, and the ratio is built from those. + * Raw counts stay in the output as diagnostics. + */ +function countLogicalFetches(requests) { + const rangedUrls = new Set(); + let fetches = 0; + for (const request of requests) { + const url = request.url.split("?")[0]; + if (!request.ranged) { + fetches += 1; + continue; + } + if (rangedUrls.has(url)) continue; + rangedUrls.add(url); + fetches += 1; + } + return fetches; +} + function summariseRequests(completed, wallClockMs) { const media = completed.filter((request) => MEDIA_EXTENSIONS.test(request.url)); const images = completed.filter((request) => IMAGE_EXTENSIONS.test(request.url)); const distinct = (requests) => new Set(requests.map((request) => request.url.split("?")[0])).size; const distinctMediaSources = distinct(media); + const mediaFetches = countLogicalFetches(media); if (distinctMediaSources === 0) { throw new Error("No media requests observed; the project under test carries no media"); } @@ -589,9 +619,11 @@ function summariseRequests(completed, wallClockMs) { requests: completed.length, bytes: sumBytes(completed), mediaRequests: media.length, + mediaRangedRequests: media.filter((request) => request.ranged).length, + mediaFetches, mediaBytes: sumBytes(media), distinctMediaSources, - mediaRequestsPerDistinctSource: round(media.length / distinctMediaSources, 2), + mediaRequestsPerDistinctSource: round(mediaFetches / distinctMediaSources, 2), imageRequests: images.length, distinctImageSources: distinct(images), };