From 995dea2cb59ec6c32c177cb6de1c0df5ed97b4ec Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Wed, 3 Apr 2024 12:59:56 +0100 Subject: [PATCH 01/27] Add redirect time --- CHANGELOG.md | 4 ++++ README.md | 11 ++++++++--- docs/upgrading-to-v4.md | 1 + src/attribution/onTTFB.ts | 19 +++++++++++++++++-- src/types/ttfb.ts | 11 ++++++++--- test/e2e/onTTFB-test.js | 15 ++++++++++++--- test/unit/index-test.js | 3 +++ 7 files changed, 53 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60aaa5b0..cff6f3b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### v4.0.0-beta.2 (???) + +- **[BREAKING]** Split `waitingDuration` in `TTFBAttribution` into `redirectDuration` and `cacheDuration` ([#??](https://github.com/GoogleChrome/web-vitals/pull/??)) + ### v4.0.0-beta.1 (2024-04-01) - **[BREAKING]** Rename `TTFBAttribution` fields from `*Time` to `*Duration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)) diff --git a/README.md b/README.md index e1c51a15..8a74f869 100644 --- a/README.md +++ b/README.md @@ -1024,11 +1024,16 @@ interface LCPAttribution { ```ts interface TTFBAttribution { /** - * The total time from when the user initiates loading the page to when the - * DNS lookup begins. This includes redirects, service worker startup, and + * The total time spent in redirects before the current page processes the + * request. Npote in future this may only include same-origin redirects. + */ + redirectDuration: number; + /** + * The total time spent looking up local caches for the navigation request. + * This includes service worker startup, service worker fetch processing, and * HTTP cache lookup times. */ - waitingDuration: number; + cacheDuration: number; /** * The total time to resolve the DNS for the current request. */ diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index 0a1e3102..a5c418bf 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -37,6 +37,7 @@ npm install web-vitals@next #### `TTFBAttribution` - **Renamed** `waitingTime` to `waitingDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). +- **Split** `waitingDuration` into `redirectDuration` and `cacheDuration` ([#??](https://github.com/GoogleChrome/web-vitals/pull/??)) - **Renamed** `dnsTime` to `dnsDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `connectionTime` to `connectionDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `requestTime` to `requestDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index fa99e1cb..6023d41b 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,6 +28,15 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; + const redirectEnd = Math.max( + navigationEntry.redirectEnd - activationStart, + 0, + ); + const cacheStart = Math.max( + (navigationEntry.fetchStart || navigationEntry.workerStart) - + activationStart, + 0, + ); const dnsStart = Math.max( navigationEntry.domainLookupStart - activationStart, 0, @@ -42,7 +51,12 @@ const attributeTTFB = (metric: TTFBMetric): void => { ); (metric as TTFBMetricWithAttribution).attribution = { - waitingDuration: dnsStart, + // Set redirectend to be based on fetchStart and workerStart to get most + // accurate redirect time available now. + // Note this may change in future. See + // https://github.com/w3c/navigation-timing/issues/160 + redirectDuration: redirectEnd || cacheStart, + cacheDuration: dnsStart - cacheStart, dnsDuration: connectStart - dnsStart, connectionDuration: requestStart - connectStart, requestDuration: metric.value - requestStart, @@ -52,7 +66,8 @@ const attributeTTFB = (metric: TTFBMetric): void => { } // Set an empty object if no other attribution has been set. (metric as TTFBMetricWithAttribution).attribution = { - waitingDuration: 0, + redirectDuration: 0, + cacheDuration: 0, dnsDuration: 0, connectionDuration: 0, requestDuration: 0, diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 719d4310..1974613b 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -31,11 +31,16 @@ export interface TTFBMetric extends Metric { */ export interface TTFBAttribution { /** - * The total time from when the user initiates loading the page to when the - * DNS lookup begins. This includes redirects, service worker startup, and + * The total time spent in redirects before the current page processes the + * request. Npote in future this may only include same-origin redirects. + */ + redirectDuration: number; + /** + * The total time spent looking up local caches for the navigation request. + * This includes service worker startup, service worker fetch processing, and * HTTP cache lookup times. */ - waitingDuration: number; + cacheDuration: number; /** * The total time to resolve the DNS for the current request. */ diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 95296e1d..57ce4b4d 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -261,7 +261,11 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( - ttfb.attribution.waitingDuration, + ttfb.attribution.redirectDuration, + navEntry.domainLookupStart, + ); + assert.strictEqual( + ttfb.attribution.redirectDuration, navEntry.domainLookupStart, ); assert.strictEqual( @@ -303,7 +307,11 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( - ttfb.attribution.waitingDuration, + ttfb.attribution.redirectDuration, + Math.max(0, navEntry.domainLookupStart - activationStart), + ); + assert.strictEqual( + ttfb.attribution.cacheDuration, Math.max(0, navEntry.domainLookupStart - activationStart), ); assert.strictEqual( @@ -346,7 +354,8 @@ describe('onTTFB()', async function () { assert.strictEqual(ttfb.navigationType, 'back-forward-cache'); assert.strictEqual(ttfb.entries.length, 0); - assert.strictEqual(ttfb.attribution.waitingDuration, 0); + assert.strictEqual(ttfb.attribution.redirectDuration, 0); + assert.strictEqual(ttfb.attribution.cacheDuration, 0); assert.strictEqual(ttfb.attribution.dnsDuration, 0); assert.strictEqual(ttfb.attribution.connectionDuration, 0); assert.strictEqual(ttfb.attribution.requestDuration, 0); diff --git a/test/unit/index-test.js b/test/unit/index-test.js index e85289aa..1f740c1e 100644 --- a/test/unit/index-test.js +++ b/test/unit/index-test.js @@ -1,5 +1,6 @@ import {describe, it} from 'node:test'; import assert from 'assert'; +const self = global; import { onCLS, onFCP, @@ -17,12 +18,14 @@ import { describe('index', () => { it('exports Web Vitals metrics functions', () => { + const self = global; [onCLS, onFCP, onFID, onINP, onLCP, onTTFB].forEach((onFn) => assert(typeof onFn === 'function'), ); }); it('exports Web Vitals metric thresholds', () => { + const self = global; assert.deepEqual(CLSThresholds, [0.1, 0.25]); assert.deepEqual(FCPThresholds, [1800, 3000]); assert.deepEqual(FIDThresholds, [100, 300]); From 24c01b3f6917a98863769c5d69bacfaa1112644b Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Wed, 3 Apr 2024 14:00:40 +0100 Subject: [PATCH 02/27] Fix unit tests --- src/lib/whenIdle.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/whenIdle.ts b/src/lib/whenIdle.ts index d1e4e498..e752c974 100644 --- a/src/lib/whenIdle.ts +++ b/src/lib/whenIdle.ts @@ -17,7 +17,9 @@ import {onHidden} from './onHidden.js'; import {runOnce} from './runOnce.js'; -const rIC = self.requestIdleCallback || self.setTimeout; +const rIC = globalThis + ? globalThis.requestIdleCallback || globalThis.setTimeout + : self.setTimeout; /** * Runs the passed callback during the next idle period, or immediately From c51489215b766552ff992cadd4a186bc926f9bea Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Wed, 3 Apr 2024 14:19:50 +0100 Subject: [PATCH 03/27] Clean up old code --- src/attribution/onTTFB.ts | 2 +- test/unit/index-test.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 6023d41b..a7ec25e5 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -56,7 +56,7 @@ const attributeTTFB = (metric: TTFBMetric): void => { // Note this may change in future. See // https://github.com/w3c/navigation-timing/issues/160 redirectDuration: redirectEnd || cacheStart, - cacheDuration: dnsStart - cacheStart, + cacheDuration: dnsStart - (redirectEnd || cacheStart), dnsDuration: connectStart - dnsStart, connectionDuration: requestStart - connectStart, requestDuration: metric.value - requestStart, diff --git a/test/unit/index-test.js b/test/unit/index-test.js index 1f740c1e..e85289aa 100644 --- a/test/unit/index-test.js +++ b/test/unit/index-test.js @@ -1,6 +1,5 @@ import {describe, it} from 'node:test'; import assert from 'assert'; -const self = global; import { onCLS, onFCP, @@ -18,14 +17,12 @@ import { describe('index', () => { it('exports Web Vitals metrics functions', () => { - const self = global; [onCLS, onFCP, onFID, onINP, onLCP, onTTFB].forEach((onFn) => assert(typeof onFn === 'function'), ); }); it('exports Web Vitals metric thresholds', () => { - const self = global; assert.deepEqual(CLSThresholds, [0.1, 0.25]); assert.deepEqual(FCPThresholds, [1800, 3000]); assert.deepEqual(FIDThresholds, [100, 300]); From 36d99818263975f1bdc1588dcedd2273fdff6889 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Wed, 3 Apr 2024 14:33:27 +0100 Subject: [PATCH 04/27] Fix tests --- src/attribution/onTTFB.ts | 2 +- test/e2e/onTTFB-test.js | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index a7ec25e5..768d86de 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -33,7 +33,7 @@ const attributeTTFB = (metric: TTFBMetric): void => { 0, ); const cacheStart = Math.max( - (navigationEntry.fetchStart || navigationEntry.workerStart) - + (navigationEntry.workerStart || navigationEntry.fetchStart) - activationStart, 0, ); diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 57ce4b4d..e4f79f3a 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -262,11 +262,18 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( ttfb.attribution.redirectDuration, - navEntry.domainLookupStart, + Math.max( + 0, + navEntry.redirectEnd || navEntry.workerStart || navEntry.fetchStart, + ), ); assert.strictEqual( - ttfb.attribution.redirectDuration, - navEntry.domainLookupStart, + ttfb.attribution.cacheDuration, + Math.max(0, navEntry.domainLookupStart) - + Math.max( + 0, + navEntry.redirectEnd || navEntry.workerStart || navEntry.fetchStart, + ), ); assert.strictEqual( ttfb.attribution.dnsDuration, @@ -308,11 +315,22 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( ttfb.attribution.redirectDuration, - Math.max(0, navEntry.domainLookupStart - activationStart), + Math.max( + 0, + (navEntry.redirectEnd || + navEntry.workerStart || + navEntry.fetchStart) - activationStart, + ), ); assert.strictEqual( ttfb.attribution.cacheDuration, - Math.max(0, navEntry.domainLookupStart - activationStart), + Math.max(0, navEntry.domainLookupStart - activationStart) - + Math.max( + 0, + (navEntry.redirectEnd || + navEntry.workerStart || + navEntry.fetchStart) - activationStart, + ), ); assert.strictEqual( ttfb.attribution.dnsDuration, From eeb3fc3de607c54686bf3aa31ab867bb12ab679c Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 00:09:32 +0100 Subject: [PATCH 05/27] globalThis better fix --- src/lib/whenIdle.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/whenIdle.ts b/src/lib/whenIdle.ts index e752c974..9abb30cd 100644 --- a/src/lib/whenIdle.ts +++ b/src/lib/whenIdle.ts @@ -17,15 +17,13 @@ import {onHidden} from './onHidden.js'; import {runOnce} from './runOnce.js'; -const rIC = globalThis - ? globalThis.requestIdleCallback || globalThis.setTimeout - : self.setTimeout; - /** * Runs the passed callback during the next idle period, or immediately * if the browser's visibility state is (or becomes) hidden. */ export const whenIdle = (cb: () => void): number => { + const rIC = self.requestIdleCallback || self.setTimeout; + let handle = -1; cb = runOnce(cb); // If the document is hidden, run the callback immediately, otherwise From 42957c8dd0e7e9da2ac1fb145a0214e30fb8e207 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 00:21:36 +0100 Subject: [PATCH 06/27] Add TTFB PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cff6f3b7..0e0c00b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### v4.0.0-beta.2 (???) -- **[BREAKING]** Split `waitingDuration` in `TTFBAttribution` into `redirectDuration` and `cacheDuration` ([#??](https://github.com/GoogleChrome/web-vitals/pull/??)) +- **[BREAKING]** Split `waitingDuration` in `TTFBAttribution` into `redirectDuration` and `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)) ### v4.0.0-beta.1 (2024-04-01) From f88e069ce8fbe1056048fa0176a823d46e3eef78 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 00:22:49 +0100 Subject: [PATCH 07/27] PR number --- docs/upgrading-to-v4.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index a5c418bf..206c3f0c 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -36,8 +36,7 @@ npm install web-vitals@next #### `TTFBAttribution` -- **Renamed** `waitingTime` to `waitingDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). -- **Split** `waitingDuration` into `redirectDuration` and `cacheDuration` ([#??](https://github.com/GoogleChrome/web-vitals/pull/??)) +- **Split** `waitingTime` into `redirectDuration` and `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)) - **Renamed** `dnsTime` to `dnsDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `connectionTime` to `connectionDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `requestTime` to `requestDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). From 7345b2089027e68a911911d175cc1c4b822d3f91 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 07:28:35 +0100 Subject: [PATCH 08/27] Review feedback --- README.md | 2 +- docs/upgrading-to-v4.md | 4 +++- src/attribution/onTTFB.ts | 22 ++++++++++------------ src/types/ttfb.ts | 2 +- test/e2e/onTTFB-test.js | 13 ++++--------- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 8a74f869..a409cb95 100644 --- a/README.md +++ b/README.md @@ -1025,7 +1025,7 @@ interface LCPAttribution { interface TTFBAttribution { /** * The total time spent in redirects before the current page processes the - * request. Npote in future this may only include same-origin redirects. + * request. Note in future this may only include same-origin redirects. */ redirectDuration: number; /** diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index 206c3f0c..e9bd2fd2 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -36,7 +36,9 @@ npm install web-vitals@next #### `TTFBAttribution` -- **Split** `waitingTime` into `redirectDuration` and `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)) +- **Removed** `waitingTime` as split into into `redirectDuration` and `cacheDuration` now (see [#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `redirectDuration` (see [#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `cacheDuration` (see [#458](https://github.com/GoogleChrome/web-vitals/pull/458)). - **Renamed** `dnsTime` to `dnsDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `connectionTime` to `connectionDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `requestTime` to `requestDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 768d86de..6fb0bce2 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,13 +28,15 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; + // Set redirectEnd to be based on redirectEnd or fetchStart or workerStart + // to get even when redirectEnd is not set in cross-origin instances + // Note this can result in small "redirect times" even when there are no + // redirects. Also this may change in future. See + // https://github.com/w3c/navigation-timing/issues/160 const redirectEnd = Math.max( - navigationEntry.redirectEnd - activationStart, - 0, - ); - const cacheStart = Math.max( - (navigationEntry.workerStart || navigationEntry.fetchStart) - - activationStart, + (navigationEntry.redirectEnd || + navigationEntry.workerStart || + navigationEntry.fetchStart) - activationStart, 0, ); const dnsStart = Math.max( @@ -51,12 +53,8 @@ const attributeTTFB = (metric: TTFBMetric): void => { ); (metric as TTFBMetricWithAttribution).attribution = { - // Set redirectend to be based on fetchStart and workerStart to get most - // accurate redirect time available now. - // Note this may change in future. See - // https://github.com/w3c/navigation-timing/issues/160 - redirectDuration: redirectEnd || cacheStart, - cacheDuration: dnsStart - (redirectEnd || cacheStart), + redirectDuration: redirectEnd, + cacheDuration: dnsStart - redirectEnd, dnsDuration: connectStart - dnsStart, connectionDuration: requestStart - connectStart, requestDuration: metric.value - requestStart, diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 1974613b..501a0267 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -32,7 +32,7 @@ export interface TTFBMetric extends Metric { export interface TTFBAttribution { /** * The total time spent in redirects before the current page processes the - * request. Npote in future this may only include same-origin redirects. + * request. Note in future this may only include same-origin redirects. */ redirectDuration: number; /** diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index e4f79f3a..88aefcca 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -262,18 +262,13 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( ttfb.attribution.redirectDuration, - Math.max( - 0, - navEntry.redirectEnd || navEntry.workerStart || navEntry.fetchStart, - ), + navEntry.redirectEnd || navEntry.workerStart || navEntry.fetchStart, ); assert.strictEqual( ttfb.attribution.cacheDuration, - Math.max(0, navEntry.domainLookupStart) - - Math.max( - 0, - navEntry.redirectEnd || navEntry.workerStart || navEntry.fetchStart, - ), + navEntry.domainLookupStart - navEntry.redirectEnd || + navEntry.workerStart || + navEntry.fetchStart, ); assert.strictEqual( ttfb.attribution.dnsDuration, From 98756db7ef9992bf73300078d830e796a806a44e Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 07:37:56 +0100 Subject: [PATCH 09/27] aMore feedback --- README.md | 5 +++-- src/attribution/onTTFB.ts | 19 +++++++++++-------- src/types/ttfb.ts | 5 +++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a409cb95..00c975b2 100644 --- a/README.md +++ b/README.md @@ -1024,8 +1024,9 @@ interface LCPAttribution { ```ts interface TTFBAttribution { /** - * The total time spent in redirects before the current page processes the - * request. Note in future this may only include same-origin redirects. + * The total time spent before checking local caches for the navigation. This + * is most likely redirects (hence the name) but can include small amount + * of browser processing time so may be non-zero even with no redirects. */ redirectDuration: number; /** diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 6fb0bce2..dd08b986 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,12 +28,11 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; - // Set redirectEnd to be based on redirectEnd or fetchStart or workerStart - // to get even when redirectEnd is not set in cross-origin instances - // Note this can result in small "redirect times" even when there are no - // redirects. Also this may change in future. See - // https://github.com/w3c/navigation-timing/issues/160 - const redirectEnd = Math.max( + // Set cacheStart to be based on redirectEnd or workerStart or fetchStart + // (whichever is set first) to get even when redirectEnd is not set in + // cross-origin instances and when a service worker is not present. + // fetchStart should always be set so it's the last fall back. + const cacheStart = Math.max( (navigationEntry.redirectEnd || navigationEntry.workerStart || navigationEntry.fetchStart) - activationStart, @@ -53,8 +52,12 @@ const attributeTTFB = (metric: TTFBMetric): void => { ); (metric as TTFBMetricWithAttribution).attribution = { - redirectDuration: redirectEnd, - cacheDuration: dnsStart - redirectEnd, + // Set redirectDuration to the time before cacheStart. + // See https://github.com/w3c/navigation-timing/issues/160 + // Note this can result in small "redirect times" even when there are no + // redirects + redirectDuration: cacheStart, + cacheDuration: dnsStart - cacheStart, dnsDuration: connectStart - dnsStart, connectionDuration: requestStart - connectStart, requestDuration: metric.value - requestStart, diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 501a0267..df5b609f 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -31,8 +31,9 @@ export interface TTFBMetric extends Metric { */ export interface TTFBAttribution { /** - * The total time spent in redirects before the current page processes the - * request. Note in future this may only include same-origin redirects. + * The total time spent before checking local caches for the navigation. This + * is most likely redirects (hence the name) but can include small amount + * of browser processing time so may be non-zero even with no redirects. */ redirectDuration: number; /** From ee7c8f5f3f7337ad5c96e411751c5ac190ec843f Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 19:21:00 +0100 Subject: [PATCH 10/27] Remove changelog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e0c00b5..60aaa5b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -### v4.0.0-beta.2 (???) - -- **[BREAKING]** Split `waitingDuration` in `TTFBAttribution` into `redirectDuration` and `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)) - ### v4.0.0-beta.1 (2024-04-01) - **[BREAKING]** Rename `TTFBAttribution` fields from `*Time` to `*Duration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)) From a3891ad03b8611f0fdee41d072e5f510e80cb216 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 4 Apr 2024 19:28:04 +0100 Subject: [PATCH 11/27] Remove redirectEnd --- src/attribution/onTTFB.ts | 7 +++---- test/e2e/onTTFB-test.js | 15 +++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index dd08b986..59db7e88 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,14 +28,13 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; - // Set cacheStart to be based on redirectEnd or workerStart or fetchStart + // Set cacheStart to be based on workerStart or fetchStart // (whichever is set first) to get even when redirectEnd is not set in // cross-origin instances and when a service worker is not present. // fetchStart should always be set so it's the last fall back. const cacheStart = Math.max( - (navigationEntry.redirectEnd || - navigationEntry.workerStart || - navigationEntry.fetchStart) - activationStart, + (navigationEntry.workerStart || navigationEntry.fetchStart) - + activationStart, 0, ); const dnsStart = Math.max( diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 88aefcca..5bcd097e 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -262,13 +262,12 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( ttfb.attribution.redirectDuration, - navEntry.redirectEnd || navEntry.workerStart || navEntry.fetchStart, + navEntry.workerStart || navEntry.fetchStart, ); assert.strictEqual( ttfb.attribution.cacheDuration, - navEntry.domainLookupStart - navEntry.redirectEnd || - navEntry.workerStart || - navEntry.fetchStart, + navEntry.domainLookupStart - + (navEntry.workerStart || navEntry.fetchStart), ); assert.strictEqual( ttfb.attribution.dnsDuration, @@ -312,9 +311,7 @@ describe('onTTFB()', async function () { ttfb.attribution.redirectDuration, Math.max( 0, - (navEntry.redirectEnd || - navEntry.workerStart || - navEntry.fetchStart) - activationStart, + (navEntry.workerStart || navEntry.fetchStart) - activationStart, ), ); assert.strictEqual( @@ -322,9 +319,7 @@ describe('onTTFB()', async function () { Math.max(0, navEntry.domainLookupStart - activationStart) - Math.max( 0, - (navEntry.redirectEnd || - navEntry.workerStart || - navEntry.fetchStart) - activationStart, + (navEntry.workerStart || navEntry.fetchStart) - activationStart, ), ); assert.strictEqual( From 18ed6cdc18ae30a2ccdd3bc4660198052d99fc5a Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Thu, 4 Apr 2024 20:34:18 -0700 Subject: [PATCH 12/27] Language tweaks --- README.md | 19 +++++++++++-------- docs/upgrading-to-v4.md | 40 +++++++++++++++++++-------------------- src/attribution/onTTFB.ts | 8 -------- src/types/ttfb.ts | 19 +++++++++++-------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 00c975b2..5a49f665 100644 --- a/README.md +++ b/README.md @@ -1024,15 +1024,18 @@ interface LCPAttribution { ```ts interface TTFBAttribution { /** - * The total time spent before checking local caches for the navigation. This - * is most likely redirects (hence the name) but can include small amount - * of browser processing time so may be non-zero even with no redirects. + * The total time spent resolving redirects before starting the next phase + * of the request (checking the cache or service worker). If there were no + * redirects, this duration will generally be close to zero (though it's + * usually not actually zero because some browser processing is required + * before the next request phase can begin). */ redirectDuration: number; /** - * The total time spent looking up local caches for the navigation request. - * This includes service worker startup, service worker fetch processing, and - * HTTP cache lookup times. + * The total time spent checking the HTTP cache for a match. If the page + * is controlled by a service worker, this duration will include service + * worker start up time as well as time processing the `fetch` event in the + * worker. */ cacheDuration: number; /** @@ -1051,8 +1054,8 @@ interface TTFBAttribution { requestDuration: number; /** * The `navigation` entry of the current page, which is useful for diagnosing - * general page load issues. This can be used to access `serverTiming` for example: - * navigationEntry?.serverTiming + * general page load issues. This can be used to access `serverTiming` for + * example: navigationEntry?.serverTiming */ navigationEntry?: PerformanceNavigationTiming; } diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index e9bd2fd2..741668ec 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -14,34 +14,34 @@ npm install web-vitals@next #### General -- **Removed** the "base+polyfill" build, which includes the FID polyfill and the Navigation Timing polyfill supporting legacy Safari browsers (see [#435](https://github.com/GoogleChrome/web-vitals/pull/435)). -- **Removed** all `getXXX()` functions that were deprecated in v3 (see [#435](https://github.com/GoogleChrome/web-vitals/pull/435)). +- **Removed** the "base+polyfill" build, which includes the FID polyfill and the Navigation Timing polyfill supporting legacy Safari browsers ([#435](https://github.com/GoogleChrome/web-vitals/pull/435)). +- **Removed** all `getXXX()` functions that were deprecated in v3 ([#435](https://github.com/GoogleChrome/web-vitals/pull/435)). #### `INPMetric` -- **Changed** `entries` to only include entries with matching `interactionId` that were processed within the same animation frame. Previously it included all entries with matching `interactionId` values, which could include entries not impacting INP (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Changed** `entries` to only include entries with matching `interactionId` that were processed within the same animation frame. Previously it included all entries with matching `interactionId` values, which could include entries not impacting INP ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). ### Attribution build #### `INPAttribution` -- **Renamed** `eventTarget` to `interactionTarget` (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Renamed** `eventTime` to `interactionTime` (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Renamed** `eventType` to `interactionType`. Also this property will now always be either "pointer" or "keyboard" (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Removed** `eventEntry` in favor of the new `processedEventEntries` array (see below), which includes all `event` entries processed within the same animation frame as the INP candidate interaction (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Renamed** `eventTarget` to `interactionTarget` ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Renamed** `eventTime` to `interactionTime` ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Renamed** `eventType` to `interactionType`. Also this property will now always be either "pointer" or "keyboard" ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Removed** `eventEntry` in favor of the new `processedEventEntries` array (see below), which includes all `event` entries processed within the same animation frame as the INP candidate interaction ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). #### `LCPAttribution` -- **Renamed** `resourceLoadTime` to `resourceLoadDuration` (see [#450](https://github.com/GoogleChrome/web-vitals/pull/450)). +- **Renamed** `resourceLoadTime` to `resourceLoadDuration` ([#450](https://github.com/GoogleChrome/web-vitals/pull/450)). #### `TTFBAttribution` -- **Removed** `waitingTime` as split into into `redirectDuration` and `cacheDuration` now (see [#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Added** `redirectDuration` (see [#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Added** `cacheDuration` (see [#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Renamed** `dnsTime` to `dnsDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). -- **Renamed** `connectionTime` to `connectionDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). -- **Renamed** `requestTime` to `requestDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)). +- **Removed** `waitingTime` in favor of splitting this duration into `redirectDuration` and `cacheDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Renamed** `dnsTime` to `dnsDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). +- **Renamed** `connectionTime` to `connectionDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). +- **Renamed** `requestTime` to `requestDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). +- **Added** `redirectDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). ## 🚀 New features @@ -53,9 +53,9 @@ No new features were introduced into the "standard" build, outside of the breaki #### `INPAttribution` -- **Added** `nextPaintTime`, which marks the timestamp of the next paint after the interaction (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Added** `inputDelay`, which measures the time from when the user interacted with the page until when the browser was first able to start processing event listeners for that interaction. (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Added** `processingDuration`, which measures the time from when the first event listener started running in response to the user interaction until when all event listener processing has finished (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Added** `presentationDelay`, which measures the time from when the browser finished processing all event listeners for the user interaction until the next frame is presented on the screen and visible to the user. (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Added** `processedEventEntries`, an array of `event` entries that were processed within the same animation frame as the INP candidate interaction (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). -- **Added** `longAnimationFrameEntries`, which includes any `long-animation-frame` entries that overlap with the INP candidate interaction (see [#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Added** `nextPaintTime`, which marks the timestamp of the next paint after the interaction ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Added** `inputDelay`, which measures the time from when the user interacted with the page until when the browser was first able to start processing event listeners for that interaction. ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Added** `processingDuration`, which measures the time from when the first event listener started running in response to the user interaction until when all event listener processing has finished ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Added** `presentationDelay`, which measures the time from when the browser finished processing all event listeners for the user interaction until the next frame is presented on the screen and visible to the user. ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Added** `processedEventEntries`, an array of `event` entries that were processed within the same animation frame as the INP candidate interaction ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). +- **Added** `longAnimationFrameEntries`, which includes any `long-animation-frame` entries that overlap with the INP candidate interaction ([#442](https://github.com/GoogleChrome/web-vitals/pull/442)). diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 59db7e88..b4a1db2b 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,10 +28,6 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; - // Set cacheStart to be based on workerStart or fetchStart - // (whichever is set first) to get even when redirectEnd is not set in - // cross-origin instances and when a service worker is not present. - // fetchStart should always be set so it's the last fall back. const cacheStart = Math.max( (navigationEntry.workerStart || navigationEntry.fetchStart) - activationStart, @@ -51,10 +47,6 @@ const attributeTTFB = (metric: TTFBMetric): void => { ); (metric as TTFBMetricWithAttribution).attribution = { - // Set redirectDuration to the time before cacheStart. - // See https://github.com/w3c/navigation-timing/issues/160 - // Note this can result in small "redirect times" even when there are no - // redirects redirectDuration: cacheStart, cacheDuration: dnsStart - cacheStart, dnsDuration: connectStart - dnsStart, diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index df5b609f..86bb3bbe 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -31,15 +31,18 @@ export interface TTFBMetric extends Metric { */ export interface TTFBAttribution { /** - * The total time spent before checking local caches for the navigation. This - * is most likely redirects (hence the name) but can include small amount - * of browser processing time so may be non-zero even with no redirects. + * The total time spent resolving redirects before starting the next phase + * of the request (checking the cache or service worker). If there were no + * redirects, this duration will generally be close to zero (though it's + * usually not actually zero because some browser processing is required + * before the next request phase can begin). */ redirectDuration: number; /** - * The total time spent looking up local caches for the navigation request. - * This includes service worker startup, service worker fetch processing, and - * HTTP cache lookup times. + * The total time spent checking the HTTP cache for a match. If the page + * is controlled by a service worker, this duration will include service + * worker start up time as well as time processing the `fetch` event in the + * worker. */ cacheDuration: number; /** @@ -58,8 +61,8 @@ export interface TTFBAttribution { requestDuration: number; /** * The `navigation` entry of the current page, which is useful for diagnosing - * general page load issues. This can be used to access `serverTiming` for example: - * navigationEntry?.serverTiming + * general page load issues. This can be used to access `serverTiming` for + * example: navigationEntry?.serverTiming */ navigationEntry?: PerformanceNavigationTiming; } From 501fa4e592e46500e38122f026c1192f90aadb81 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Thu, 11 Apr 2024 23:39:12 +0100 Subject: [PATCH 13/27] Update TTFB breakdown --- README.md | 13 ++++++ docs/upgrading-to-v4.md | 4 +- src/attribution/onTTFB.ts | 87 +++++++++++++++++++++++++++++++-------- src/types/ttfb.ts | 13 ++++++ 4 files changed, 99 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5a49f665..8297b9f2 100644 --- a/README.md +++ b/README.md @@ -1031,6 +1031,15 @@ interface TTFBAttribution { * before the next request phase can begin). */ redirectDuration: number; + /** + * The total time sent starting up the service worker (if present). + */ + swStartupDuration: number; + /** + * The total time the service worker (if present) spent handling the + * fetch event before dispatching it. + */ + swFetchEventDuration: number; /** * The total time spent checking the HTTP cache for a match. If the page * is controlled by a service worker, this duration will include service @@ -1046,6 +1055,10 @@ interface TTFBAttribution { * The total time to create the connection to the requested domain. */ connectionDuration: number; + /** + * The total unattributed time spent on browser processing. + */ + waitingDuration: number; /** * The total time from when the request was sent until the first byte of the * response was received. This includes network time as well as server diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index 741668ec..3a063d2e 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -36,12 +36,14 @@ npm install web-vitals@next #### `TTFBAttribution` -- **Removed** `waitingTime` in favor of splitting this duration into `redirectDuration` and `cacheDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Removed** `waitingTime` in favor of splitting this duration into `redirectDuration`, `cacheDuration`, `swStartupDuration`, and `swFetchEventDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). - **Renamed** `dnsTime` to `dnsDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `connectionTime` to `connectionDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Renamed** `requestTime` to `requestDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Added** `redirectDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). - **Added** `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `swStartupDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `swFetchEventDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). ## 🚀 New features diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index b4a1db2b..2d96a0c3 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -27,31 +27,81 @@ const attributeTTFB = (metric: TTFBMetric): void => { if (metric.entries.length) { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; + let redirectDuration = 0; + let swStartupDuration = 0; + let swFetchEventDuration = 0; + let cacheDuration = 0; + const dnsDuration = 0; + let connectionDuration = 0; + let waitingDuration = 0; - const cacheStart = Math.max( - (navigationEntry.workerStart || navigationEntry.fetchStart) - + const requestDuration = + metric.value - + Math.max(navigationEntry.requestStart - activationStart, 0); + + if (navigationEntry.workerStart) { + // Service worker-based timings + redirectDuration = navigationEntry.workerStart - activationStart; + + swStartupDuration = Math.max( + navigationEntry.fetchStart - + activationStart - + navigationEntry.workerStart - + activationStart, + 0, + ); + + swFetchEventDuration = + Math.max( + navigationEntry.connectEnd - + activationStart - + navigationEntry.workerStart - + activationStart, + 0, + ) - swStartupDuration; + } else { + // HTTP Cache-based timings + redirectDuration = Math.max( + navigationEntry.fetchStart - activationStart, + 0, + ); + cacheDuration = + Math.max(navigationEntry.domainLookupStart - activationStart, 0) - + redirectDuration; + } + + connectionDuration = Math.max( + navigationEntry.connectEnd - + activationStart - + navigationEntry.connectStart - activationStart, 0, ); - const dnsStart = Math.max( - navigationEntry.domainLookupStart - activationStart, - 0, - ); - const connectStart = Math.max( - navigationEntry.connectStart - activationStart, - 0, - ); - const requestStart = Math.max( - navigationEntry.requestStart - activationStart, + + waitingDuration = Math.max( + navigationEntry.requestStart - + activationStart - + navigationEntry.connectEnd - + activationStart, 0, ); + // If the redirect time is less than 20ms then it can't really redirect time and is a misreporting + // Attribute it to the waitingDuration to avoid confusion + if (redirectDuration < 20) { + waitingDuration = waitingDuration + redirectDuration; + redirectDuration = 0; + } + (metric as TTFBMetricWithAttribution).attribution = { - redirectDuration: cacheStart, - cacheDuration: dnsStart - cacheStart, - dnsDuration: connectStart - dnsStart, - connectionDuration: requestStart - connectStart, - requestDuration: metric.value - requestStart, + redirectDuration: redirectDuration, + swStartupDuration: swStartupDuration, + swFetchEventDuration: swFetchEventDuration, + cacheDuration: cacheDuration, + dnsDuration: dnsDuration, + connectionDuration: connectionDuration, + waitingDuration: waitingDuration, + requestDuration: requestDuration, navigationEntry: navigationEntry, }; return; @@ -59,10 +109,13 @@ const attributeTTFB = (metric: TTFBMetric): void => { // Set an empty object if no other attribution has been set. (metric as TTFBMetricWithAttribution).attribution = { redirectDuration: 0, + swStartupDuration: 0, + swFetchEventDuration: 0, cacheDuration: 0, dnsDuration: 0, connectionDuration: 0, requestDuration: 0, + waitingDuration: 0, }; }; diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 86bb3bbe..b485f264 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -38,6 +38,15 @@ export interface TTFBAttribution { * before the next request phase can begin). */ redirectDuration: number; + /** + * The total time sent starting up the service worker (if present). + */ + swStartupDuration: number; + /** + * The total time the service worker (if present) spent handling the + * fetch event before dispatching it. + */ + swFetchEventDuration: number; /** * The total time spent checking the HTTP cache for a match. If the page * is controlled by a service worker, this duration will include service @@ -53,6 +62,10 @@ export interface TTFBAttribution { * The total time to create the connection to the requested domain. */ connectionDuration: number; + /** + * The total unattributed time spent on browser processing. + */ + waitingDuration: number; /** * The total time from when the request was sent until the first byte of the * response was received. This includes network time as well as server From 8517801642137911bf370cc7b3a7c212258458a4 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 00:59:27 +0100 Subject: [PATCH 14/27] Simplify TTFB attribution to 4 breakdowns --- CONTRIBUTING.md | 4 +- README.md | 27 ++----------- docs/upgrading-to-v4.md | 12 +++--- src/attribution/onTTFB.ts | 76 ++++++++++--------------------------- src/types/ttfb.ts | 27 ++----------- test/e2e/onTTFB-test.js | 79 +++++++++++++++------------------------ 6 files changed, 67 insertions(+), 158 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 687fc0c1..d3125b7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,11 +30,11 @@ To test a subset of browsers or metrics, run the following in separate terminals - `npm run watch` - `npm run test:server` -- `npm run test:e2e -- --browsesr=chrome --metrics=TTFB` +- `npm run test:e2e -- --browsers=chrome --metrics=TTFB` The last command can be replaced as you see fit and include comma, separated values. For example: -- `npm run test:e2e -- --browsesr=chrome,firefox --metrics=TTFB,LCP` +- `npm run test:e2e -- --browsers=chrome,firefox --metrics=TTFB,LCP` To run an individual test, change `it('test name')` to `it.only('test name')`. diff --git a/README.md b/README.md index 8297b9f2..f1a79eb5 100644 --- a/README.md +++ b/README.md @@ -1032,33 +1032,14 @@ interface TTFBAttribution { */ redirectDuration: number; /** - * The total time sent starting up the service worker (if present). + * The total time spent in service work (if present), including startup and + * fetch event handling. */ - swStartupDuration: number; + swDuration: number; /** - * The total time the service worker (if present) spent handling the - * fetch event before dispatching it. - */ - swFetchEventDuration: number; - /** - * The total time spent checking the HTTP cache for a match. If the page - * is controlled by a service worker, this duration will include service - * worker start up time as well as time processing the `fetch` event in the - * worker. + * The total time spent checking the HTTP cache for a match. */ cacheDuration: number; - /** - * The total time to resolve the DNS for the current request. - */ - dnsDuration: number; - /** - * The total time to create the connection to the requested domain. - */ - connectionDuration: number; - /** - * The total unattributed time spent on browser processing. - */ - waitingDuration: number; /** * The total time from when the request was sent until the first byte of the * response was received. This includes network time as well as server diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index 3a063d2e..f80fddec 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -36,14 +36,14 @@ npm install web-vitals@next #### `TTFBAttribution` -- **Removed** `waitingTime` in favor of splitting this duration into `redirectDuration`, `cacheDuration`, `swStartupDuration`, and `swFetchEventDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Renamed** `dnsTime` to `dnsDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). -- **Renamed** `connectionTime` to `connectionDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). -- **Renamed** `requestTime` to `requestDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). +- **Removed** `waitingTime` in favor of splitting this duration into `redirectDuration`, `cacheDuration`, and `swDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Removed** `dnsTime` which now included in `requestDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Removed** `connectionTime` which is now included in `requestDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Removed** `requestTime` which is now included in `requestDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). - **Added** `redirectDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `swDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). - **Added** `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Added** `swStartupDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Added** `swFetchEventDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `requestDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). ## 🚀 New features diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 2d96a0c3..50b69b69 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,37 +28,16 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; let redirectDuration = 0; - let swStartupDuration = 0; - let swFetchEventDuration = 0; + let swDuration = 0; let cacheDuration = 0; - const dnsDuration = 0; - let connectionDuration = 0; - let waitingDuration = 0; - - const requestDuration = - metric.value - - Math.max(navigationEntry.requestStart - activationStart, 0); if (navigationEntry.workerStart) { // Service worker-based timings redirectDuration = navigationEntry.workerStart - activationStart; - swStartupDuration = Math.max( - navigationEntry.fetchStart - - activationStart - - navigationEntry.workerStart - - activationStart, - 0, - ); - - swFetchEventDuration = - Math.max( - navigationEntry.connectEnd - - activationStart - - navigationEntry.workerStart - - activationStart, - 0, - ) - swStartupDuration; + swDuration = + Math.max(navigationEntry.domainLookupStart - activationStart, 0) - + redirectDuration; } else { // HTTP Cache-based timings redirectDuration = Math.max( @@ -70,37 +49,26 @@ const attributeTTFB = (metric: TTFBMetric): void => { redirectDuration; } - connectionDuration = Math.max( - navigationEntry.connectEnd - - activationStart - - navigationEntry.connectStart - - activationStart, - 0, - ); - - waitingDuration = Math.max( - navigationEntry.requestStart - - activationStart - - navigationEntry.connectEnd - - activationStart, - 0, - ); - - // If the redirect time is less than 20ms then it can't really redirect time and is a misreporting - // Attribute it to the waitingDuration to avoid confusion - if (redirectDuration < 20) { - waitingDuration = waitingDuration + redirectDuration; + // If the redirect time is less than 20ms, or the document.referrer is set + // then it can't really be redirect time and is a misreporting. + // Remove and will pick it up in requestDuration + if ( + redirectDuration < 20 || + document.referrer.startsWith(document.location.origin) + ) redirectDuration = 0; - } + + // Set requestDuration to the remainder. + // This can include extra time at the start removed from redirectTime above + // And time between conectionEnd and requestStart + // So it's not equal to requestStart to responseStart + const requestDuration = + metric.value - redirectDuration - swDuration - cacheDuration; (metric as TTFBMetricWithAttribution).attribution = { redirectDuration: redirectDuration, - swStartupDuration: swStartupDuration, - swFetchEventDuration: swFetchEventDuration, + swDuration: swDuration, cacheDuration: cacheDuration, - dnsDuration: dnsDuration, - connectionDuration: connectionDuration, - waitingDuration: waitingDuration, requestDuration: requestDuration, navigationEntry: navigationEntry, }; @@ -109,13 +77,9 @@ const attributeTTFB = (metric: TTFBMetric): void => { // Set an empty object if no other attribution has been set. (metric as TTFBMetricWithAttribution).attribution = { redirectDuration: 0, - swStartupDuration: 0, - swFetchEventDuration: 0, + swDuration: 0, cacheDuration: 0, - dnsDuration: 0, - connectionDuration: 0, requestDuration: 0, - waitingDuration: 0, }; }; diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index b485f264..5656f41c 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -39,33 +39,14 @@ export interface TTFBAttribution { */ redirectDuration: number; /** - * The total time sent starting up the service worker (if present). + * The total time spent in service work (if present), including startup and + * fetch event handling. */ - swStartupDuration: number; + swDuration: number; /** - * The total time the service worker (if present) spent handling the - * fetch event before dispatching it. - */ - swFetchEventDuration: number; - /** - * The total time spent checking the HTTP cache for a match. If the page - * is controlled by a service worker, this duration will include service - * worker start up time as well as time processing the `fetch` event in the - * worker. + * The total time spent checking the HTTP cache for a match. */ cacheDuration: number; - /** - * The total time to resolve the DNS for the current request. - */ - dnsDuration: number; - /** - * The total time to create the connection to the requested domain. - */ - connectionDuration: number; - /** - * The total unattributed time spent on browser processing. - */ - waitingDuration: number; /** * The total time from when the request was sent until the first byte of the * response was received. This includes network time as well as server diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 5bcd097e..4745d45f 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -260,26 +260,24 @@ describe('onTTFB()', async function () { assertValidEntry(ttfb.entries[0]); const navEntry = ttfb.entries[0]; - assert.strictEqual( - ttfb.attribution.redirectDuration, - navEntry.workerStart || navEntry.fetchStart, - ); - assert.strictEqual( - ttfb.attribution.cacheDuration, - navEntry.domainLookupStart - - (navEntry.workerStart || navEntry.fetchStart), - ); - assert.strictEqual( - ttfb.attribution.dnsDuration, - navEntry.connectStart - navEntry.domainLookupStart, - ); - assert.strictEqual( - ttfb.attribution.connectionDuration, - navEntry.requestStart - navEntry.connectStart, - ); + + const activationStart = navEntry.activationStart || 0; + const swDuration = navEntry.workerStart + ? Math.max(navEntry.domainLookupStart - activationStart, 0) - + Math.max(navEntry.workerStart - activationStart, 0) + : 0; + const cacheDuration = navEntry.workerStart + ? 0 + : Math.max(navEntry.domainLookupStart - activationStart, 0) - + Math.max(navEntry.fetchStart - activationStart, 0); + assert.strictEqual(ttfb.attribution.redirectDuration, 0); + assert.strictEqual(ttfb.attribution.swDuration, swDuration); + assert.strictEqual(ttfb.attribution.cacheDuration, cacheDuration); assert.strictEqual( ttfb.attribution.requestDuration, - navEntry.responseStart - navEntry.requestStart, + Math.max(navEntry.responseStart - activationStart, 0) - + swDuration - + cacheDuration, ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -307,36 +305,22 @@ describe('onTTFB()', async function () { assertValidEntry(ttfb.entries[0]); const navEntry = ttfb.entries[0]; - assert.strictEqual( - ttfb.attribution.redirectDuration, - Math.max( - 0, - (navEntry.workerStart || navEntry.fetchStart) - activationStart, - ), - ); - assert.strictEqual( - ttfb.attribution.cacheDuration, - Math.max(0, navEntry.domainLookupStart - activationStart) - - Math.max( - 0, - (navEntry.workerStart || navEntry.fetchStart) - activationStart, - ), - ); - assert.strictEqual( - ttfb.attribution.dnsDuration, - Math.max(0, navEntry.connectStart - activationStart) - - Math.max(0, navEntry.domainLookupStart - activationStart), - ); - assert.strictEqual( - ttfb.attribution.connectionDuration, - Math.max(0, navEntry.requestStart - activationStart) - - Math.max(0, navEntry.connectStart - activationStart), - ); - + const swDuration = navEntry.workerStart + ? Math.max(navEntry.domainLookupStart - activationStart, 0) - + Math.max(navEntry.workerStart - activationStart, 0) + : 0; + const cacheDuration = navEntry.workerStart + ? 0 + : Math.max(navEntry.domainLookupStart - activationStart, 0) - + Math.max(navEntry.fetchStart - activationStart, 0); + assert.strictEqual(ttfb.attribution.redirectDuration, 0); + assert.strictEqual(ttfb.attribution.swDuration, swDuration); + assert.strictEqual(ttfb.attribution.cacheDuration, cacheDuration); assert.strictEqual( ttfb.attribution.requestDuration, - Math.max(0, navEntry.responseStart - activationStart) - - Math.max(0, navEntry.requestStart - activationStart), + Math.max(navEntry.responseStart - activationStart, 0) - + swDuration - + cacheDuration, ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -363,9 +347,8 @@ describe('onTTFB()', async function () { assert.strictEqual(ttfb.entries.length, 0); assert.strictEqual(ttfb.attribution.redirectDuration, 0); + assert.strictEqual(ttfb.attribution.swDuration, 0); assert.strictEqual(ttfb.attribution.cacheDuration, 0); - assert.strictEqual(ttfb.attribution.dnsDuration, 0); - assert.strictEqual(ttfb.attribution.connectionDuration, 0); assert.strictEqual(ttfb.attribution.requestDuration, 0); assert.strictEqual(ttfb.attribution.navigationEntry, undefined); }); From a76122aeb8a714b4db758681e5b51dcd064f8064 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 10:43:13 +0100 Subject: [PATCH 15/27] Revert some changes --- README.md | 26 +++++++++------- src/attribution/onTTFB.ts | 58 +++++++++++++++-------------------- src/types/ttfb.ts | 26 +++++++++------- test/e2e/onTTFB-test.js | 64 +++++++++++++++++++-------------------- 4 files changed, 86 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index f1a79eb5..d30225c3 100644 --- a/README.md +++ b/README.md @@ -1024,22 +1024,26 @@ interface LCPAttribution { ```ts interface TTFBAttribution { /** - * The total time spent resolving redirects before starting the next phase - * of the request (checking the cache or service worker). If there were no - * redirects, this duration will generally be close to zero (though it's - * usually not actually zero because some browser processing is required - * before the next request phase can begin). + * The total time from when the user initiates loading the page to when the + * fetch request is initiated. This is mostly redirect times but may contain + * some browser processing time so may be non-zero even without redirects. */ - redirectDuration: number; + waitingDuration: number; /** - * The total time spent in service work (if present), including startup and - * fetch event handling. + * The total time spent checking the HTTP cache for a match. This cannot be + * accurately measured for sites using service workers. */ - swDuration: number; + cacheDuration: number; /** - * The total time spent checking the HTTP cache for a match. + * The total time to resolve the DNS for the current request. This cannot be + * accurately measured for sites using service workers. */ - cacheDuration: number; + dnsDuration: number; + /** + * The total time to create the connection to the requested domain. This cannot be + * accurately measured for sites using service workers. + */ + connectionDuration: number; /** * The total time from when the request was sent until the first byte of the * response was received. This includes network time as well as server diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 50b69b69..077e69ea 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -27,48 +27,37 @@ const attributeTTFB = (metric: TTFBMetric): void => { if (metric.entries.length) { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; - let redirectDuration = 0; - let swDuration = 0; - let cacheDuration = 0; - if (navigationEntry.workerStart) { - // Service worker-based timings - redirectDuration = navigationEntry.workerStart - activationStart; + const waitingDuration = Math.max( + (navigationEntry.workerStart || navigationEntry.fetchStart) - + activationStart, + 0, + ); - swDuration = - Math.max(navigationEntry.domainLookupStart - activationStart, 0) - - redirectDuration; - } else { - // HTTP Cache-based timings - redirectDuration = Math.max( - navigationEntry.fetchStart - activationStart, + const cacheDuration = + Math.max(navigationEntry.domainLookupStart - activationStart, 0) - + Math.max( + (navigationEntry.workerStart || navigationEntry.fetchStart) - + activationStart, 0, ); - cacheDuration = - Math.max(navigationEntry.domainLookupStart - activationStart, 0) - - redirectDuration; - } - // If the redirect time is less than 20ms, or the document.referrer is set - // then it can't really be redirect time and is a misreporting. - // Remove and will pick it up in requestDuration - if ( - redirectDuration < 20 || - document.referrer.startsWith(document.location.origin) - ) - redirectDuration = 0; + const dnsDuration = + Math.max(navigationEntry.domainLookupEnd - activationStart, 0) - + Math.max(navigationEntry.domainLookupStart - activationStart, 0); + + const connectionDuration = + Math.max(navigationEntry.connectEnd - activationStart, 0) - + Math.max(navigationEntry.domainLookupEnd - activationStart, 0); - // Set requestDuration to the remainder. - // This can include extra time at the start removed from redirectTime above - // And time between conectionEnd and requestStart - // So it's not equal to requestStart to responseStart const requestDuration = - metric.value - redirectDuration - swDuration - cacheDuration; + metric.value - Math.max(navigationEntry.connectEnd - activationStart, 0); (metric as TTFBMetricWithAttribution).attribution = { - redirectDuration: redirectDuration, - swDuration: swDuration, + waitingDuration: waitingDuration, cacheDuration: cacheDuration, + dnsDuration: dnsDuration, + connectionDuration: connectionDuration, requestDuration: requestDuration, navigationEntry: navigationEntry, }; @@ -76,9 +65,10 @@ const attributeTTFB = (metric: TTFBMetric): void => { } // Set an empty object if no other attribution has been set. (metric as TTFBMetricWithAttribution).attribution = { - redirectDuration: 0, - swDuration: 0, + waitingDuration: 0, cacheDuration: 0, + dnsDuration: 0, + connectionDuration: 0, requestDuration: 0, }; }; diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 5656f41c..db714adc 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -31,22 +31,26 @@ export interface TTFBMetric extends Metric { */ export interface TTFBAttribution { /** - * The total time spent resolving redirects before starting the next phase - * of the request (checking the cache or service worker). If there were no - * redirects, this duration will generally be close to zero (though it's - * usually not actually zero because some browser processing is required - * before the next request phase can begin). + * The total time from when the user initiates loading the page to when the + * fetch request is initiated. This is mostly redirect times but may contain + * some browser processing time so may be non-zero even without redirects. */ - redirectDuration: number; + waitingDuration: number; /** - * The total time spent in service work (if present), including startup and - * fetch event handling. + * The total time spent checking the HTTP cache for a match. This cannot be + * accurately measured for sites using service workers. */ - swDuration: number; + cacheDuration: number; /** - * The total time spent checking the HTTP cache for a match. + * The total time to resolve the DNS for the current request. This cannot be + * accurately measured for sites using service workers. */ - cacheDuration: number; + dnsDuration: number; + /** + * The total time to create the connection to the requested domain. This cannot be + * accurately measured for sites using service workers. + */ + connectionDuration: number; /** * The total time from when the request was sent until the first byte of the * response was received. This includes network time as well as server diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 4745d45f..c320b431 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -260,24 +260,22 @@ describe('onTTFB()', async function () { assertValidEntry(ttfb.entries[0]); const navEntry = ttfb.entries[0]; - - const activationStart = navEntry.activationStart || 0; - const swDuration = navEntry.workerStart - ? Math.max(navEntry.domainLookupStart - activationStart, 0) - - Math.max(navEntry.workerStart - activationStart, 0) - : 0; - const cacheDuration = navEntry.workerStart - ? 0 - : Math.max(navEntry.domainLookupStart - activationStart, 0) - - Math.max(navEntry.fetchStart - activationStart, 0); - assert.strictEqual(ttfb.attribution.redirectDuration, 0); - assert.strictEqual(ttfb.attribution.swDuration, swDuration); - assert.strictEqual(ttfb.attribution.cacheDuration, cacheDuration); + assert.strictEqual(ttfb.attribution.waitingDuration, navEntry.fetchStart); + assert.strictEqual( + ttfb.attribution.cacheDuration, + navEntry.domainLookupStart - navEntry.fetchStart, + ); + assert.strictEqual( + ttfb.attribution.dnsDuration, + navEntry.domainLookupEnd - navEntry.domainLookupStart, + ); + assert.strictEqual( + ttfb.attribution.connectionDuration, + navEntry.connectEnd - navEntry.domainLookupEnd, + ); assert.strictEqual( ttfb.attribution.requestDuration, - Math.max(navEntry.responseStart - activationStart, 0) - - swDuration - - cacheDuration, + ttfb.value - navEntry.connectEnd, ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -305,22 +303,23 @@ describe('onTTFB()', async function () { assertValidEntry(ttfb.entries[0]); const navEntry = ttfb.entries[0]; - const swDuration = navEntry.workerStart - ? Math.max(navEntry.domainLookupStart - activationStart, 0) - - Math.max(navEntry.workerStart - activationStart, 0) - : 0; - const cacheDuration = navEntry.workerStart - ? 0 - : Math.max(navEntry.domainLookupStart - activationStart, 0) - - Math.max(navEntry.fetchStart - activationStart, 0); - assert.strictEqual(ttfb.attribution.redirectDuration, 0); - assert.strictEqual(ttfb.attribution.swDuration, swDuration); - assert.strictEqual(ttfb.attribution.cacheDuration, cacheDuration); + assert.strictEqual( + ttfb.attribution.waitingDuration, + Math.max(0, navEntry.fetchStart - activationStart), + ); + assert.strictEqual( + ttfb.attribution.dnsDuration, + Math.max(0, navEntry.connectStart - activationStart) - + Math.max(0, navEntry.domainLookupStart - activationStart), + ); + assert.strictEqual( + ttfb.attribution.connectionDuration, + Math.max(0, navEntry.connectEnd - activationStart) - + Math.max(0, navEntry.domainLookupEnd - activationStart), + ); assert.strictEqual( ttfb.attribution.requestDuration, - Math.max(navEntry.responseStart - activationStart, 0) - - swDuration - - cacheDuration, + ttfb.value - Math.max(0, navEntry.connectEnd - activationStart), ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -346,9 +345,10 @@ describe('onTTFB()', async function () { assert.strictEqual(ttfb.navigationType, 'back-forward-cache'); assert.strictEqual(ttfb.entries.length, 0); - assert.strictEqual(ttfb.attribution.redirectDuration, 0); - assert.strictEqual(ttfb.attribution.swDuration, 0); + assert.strictEqual(ttfb.attribution.waitingDuration, 0); assert.strictEqual(ttfb.attribution.cacheDuration, 0); + assert.strictEqual(ttfb.attribution.dnsDuration, 0); + assert.strictEqual(ttfb.attribution.connectionDuration, 0); assert.strictEqual(ttfb.attribution.requestDuration, 0); assert.strictEqual(ttfb.attribution.navigationEntry, undefined); }); From 9163ce0247639da1a3fa3b197dc657dacbe0fee7 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 11:03:22 +0100 Subject: [PATCH 16/27] Clean up --- docs/upgrading-to-v4.md | 9 +++---- src/attribution/onTTFB.ts | 50 ++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/docs/upgrading-to-v4.md b/docs/upgrading-to-v4.md index f80fddec..5cd35d57 100644 --- a/docs/upgrading-to-v4.md +++ b/docs/upgrading-to-v4.md @@ -36,14 +36,11 @@ npm install web-vitals@next #### `TTFBAttribution` -- **Removed** `waitingTime` in favor of splitting this duration into `redirectDuration`, `cacheDuration`, and `swDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Removed** `dnsTime` which now included in `requestDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Removed** `connectionTime` which is now included in `requestDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Removed** `requestTime` which is now included in `requestDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Removed** `waitingTime` in favor of splitting this duration into `waitingDuration` and `cacheDuration` (see below) ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Renamed** `requestTime` to `requestDuration` ([#453](https://github.com/GoogleChrome/web-vitals/pull/453)). - **Added** `redirectDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Added** `swDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). +- **Added** `waitingDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). - **Added** `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). -- **Added** `requestDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)). ## 🚀 New features diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 077e69ea..71d80e41 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,37 +28,33 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; - const waitingDuration = Math.max( - (navigationEntry.workerStart || navigationEntry.fetchStart) - - activationStart, + const fetchStart = Math.max( + navigationEntry.fetchStart - activationStart, + 0, + ); + const dnsStart = Math.max( + navigationEntry.domainLookupStart - activationStart, + 0, + ); + const dnsEnd = Math.max( + navigationEntry.domainLookupStart - activationStart, + 0, + ); + const connectEnd = Math.max( + navigationEntry.connectEnd - activationStart, + 0, + ); + const responseStart = Math.max( + navigationEntry.responseStart - activationStart, 0, ); - - const cacheDuration = - Math.max(navigationEntry.domainLookupStart - activationStart, 0) - - Math.max( - (navigationEntry.workerStart || navigationEntry.fetchStart) - - activationStart, - 0, - ); - - const dnsDuration = - Math.max(navigationEntry.domainLookupEnd - activationStart, 0) - - Math.max(navigationEntry.domainLookupStart - activationStart, 0); - - const connectionDuration = - Math.max(navigationEntry.connectEnd - activationStart, 0) - - Math.max(navigationEntry.domainLookupEnd - activationStart, 0); - - const requestDuration = - metric.value - Math.max(navigationEntry.connectEnd - activationStart, 0); (metric as TTFBMetricWithAttribution).attribution = { - waitingDuration: waitingDuration, - cacheDuration: cacheDuration, - dnsDuration: dnsDuration, - connectionDuration: connectionDuration, - requestDuration: requestDuration, + waitingDuration: fetchStart, + cacheDuration: dnsStart - fetchStart, + dnsDuration: dnsEnd - dnsStart, + connectionDuration: connectEnd - dnsEnd, + requestDuration: responseStart - connectEnd, navigationEntry: navigationEntry, }; return; From ab8d97988830450a1110e2d0e1d666c97a97d44b Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 11:10:16 +0100 Subject: [PATCH 17/27] More cleanup --- src/attribution/onTTFB.ts | 14 +++++--------- test/e2e/onTTFB-test.js | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 71d80e41..f411e058 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -37,15 +37,11 @@ const attributeTTFB = (metric: TTFBMetric): void => { 0, ); const dnsEnd = Math.max( - navigationEntry.domainLookupStart - activationStart, - 0, - ); - const connectEnd = Math.max( - navigationEntry.connectEnd - activationStart, + navigationEntry.domainLookupEnd - activationStart, 0, ); - const responseStart = Math.max( - navigationEntry.responseStart - activationStart, + const requestStart = Math.max( + navigationEntry.requestStart - activationStart, 0, ); @@ -53,8 +49,8 @@ const attributeTTFB = (metric: TTFBMetric): void => { waitingDuration: fetchStart, cacheDuration: dnsStart - fetchStart, dnsDuration: dnsEnd - dnsStart, - connectionDuration: connectEnd - dnsEnd, - requestDuration: responseStart - connectEnd, + connectionDuration: requestStart - dnsEnd, + requestDuration: metric.value - requestStart, navigationEntry: navigationEntry, }; return; diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index c320b431..e231570c 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -271,11 +271,11 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.connectionDuration, - navEntry.connectEnd - navEntry.domainLookupEnd, + navEntry.requestStart - navEntry.domainLookupEnd, ); assert.strictEqual( ttfb.attribution.requestDuration, - ttfb.value - navEntry.connectEnd, + ttfb.value - navEntry.requestStart, ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -314,12 +314,12 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.connectionDuration, - Math.max(0, navEntry.connectEnd - activationStart) - + Math.max(0, navEntry.requestStart - activationStart) - Math.max(0, navEntry.domainLookupEnd - activationStart), ); assert.strictEqual( ttfb.attribution.requestDuration, - ttfb.value - Math.max(0, navEntry.connectEnd - activationStart), + ttfb.value - Math.max(0, navEntry.requestStart - activationStart), ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); From 93c698a7583a8b89e43936aa0fafb4e5304bcaf3 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 11:22:23 +0100 Subject: [PATCH 18/27] Revert some more changes --- src/attribution/onTTFB.ts | 8 ++++---- test/e2e/onTTFB-test.js | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index f411e058..916180b3 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -36,8 +36,8 @@ const attributeTTFB = (metric: TTFBMetric): void => { navigationEntry.domainLookupStart - activationStart, 0, ); - const dnsEnd = Math.max( - navigationEntry.domainLookupEnd - activationStart, + const connectStart = Math.max( + navigationEntry.connectStart - activationStart, 0, ); const requestStart = Math.max( @@ -48,8 +48,8 @@ const attributeTTFB = (metric: TTFBMetric): void => { (metric as TTFBMetricWithAttribution).attribution = { waitingDuration: fetchStart, cacheDuration: dnsStart - fetchStart, - dnsDuration: dnsEnd - dnsStart, - connectionDuration: requestStart - dnsEnd, + dnsDuration: connectStart - dnsStart, + connectionDuration: requestStart - connectStart, requestDuration: metric.value - requestStart, navigationEntry: navigationEntry, }; diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index e231570c..5157636d 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -267,11 +267,11 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.dnsDuration, - navEntry.domainLookupEnd - navEntry.domainLookupStart, + navEntry.connectStart - navEntry.domainLookupStart, ); assert.strictEqual( ttfb.attribution.connectionDuration, - navEntry.requestStart - navEntry.domainLookupEnd, + navEntry.requestStart - navEntry.connectStart, ); assert.strictEqual( ttfb.attribution.requestDuration, @@ -307,6 +307,11 @@ describe('onTTFB()', async function () { ttfb.attribution.waitingDuration, Math.max(0, navEntry.fetchStart - activationStart), ); + assert.strictEqual( + ttfb.attribution.cacheDuration, + Math.max(0, navEntry.domainLookupStart - activationStart) - + Math.max(0, navEntry.fetchStart - activationStart), + ); assert.strictEqual( ttfb.attribution.dnsDuration, Math.max(0, navEntry.connectStart - activationStart) - @@ -315,7 +320,7 @@ describe('onTTFB()', async function () { assert.strictEqual( ttfb.attribution.connectionDuration, Math.max(0, navEntry.requestStart - activationStart) - - Math.max(0, navEntry.domainLookupEnd - activationStart), + Math.max(0, navEntry.connectStart - activationStart), ); assert.strictEqual( ttfb.attribution.requestDuration, From 5c1fd8dc7022af9376f8f1a237454e71477fd5c4 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 11:27:07 +0100 Subject: [PATCH 19/27] more reverts to make PR cleaner --- README.md | 4 ++-- src/types/ttfb.ts | 4 ++-- test/e2e/onTTFB-test.js | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d30225c3..bcaa74bb 100644 --- a/README.md +++ b/README.md @@ -1040,8 +1040,8 @@ interface TTFBAttribution { */ dnsDuration: number; /** - * The total time to create the connection to the requested domain. This cannot be - * accurately measured for sites using service workers. + * The total time to create the connection to the requested domain. This + * cannot be accurately measured for sites using service workers. */ connectionDuration: number; /** diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index db714adc..dee43bf7 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -47,8 +47,8 @@ export interface TTFBAttribution { */ dnsDuration: number; /** - * The total time to create the connection to the requested domain. This cannot be - * accurately measured for sites using service workers. + * The total time to create the connection to the requested domain. This + * cannot be accurately measured for sites using service workers. */ connectionDuration: number; /** diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 5157636d..be536eab 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -275,7 +275,7 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.requestDuration, - ttfb.value - navEntry.requestStart, + navEntry.responseStart - navEntry.requestStart, ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -317,6 +317,7 @@ describe('onTTFB()', async function () { Math.max(0, navEntry.connectStart - activationStart) - Math.max(0, navEntry.domainLookupStart - activationStart), ); + assert.strictEqual( ttfb.attribution.connectionDuration, Math.max(0, navEntry.requestStart - activationStart) - @@ -324,7 +325,8 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.requestDuration, - ttfb.value - Math.max(0, navEntry.requestStart - activationStart), + Math.max(0, navEntry.responseStart - activationStart) - + Math.max(0, navEntry.requestStart - activationStart), ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); From 8e0ede21977604fcdc971f45fb0dc09c8f904370 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 11:29:01 +0100 Subject: [PATCH 20/27] Space --- test/e2e/onTTFB-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index be536eab..9b5b2c93 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -317,7 +317,6 @@ describe('onTTFB()', async function () { Math.max(0, navEntry.connectStart - activationStart) - Math.max(0, navEntry.domainLookupStart - activationStart), ); - assert.strictEqual( ttfb.attribution.connectionDuration, Math.max(0, navEntry.requestStart - activationStart) - From ecd4dca4aeaa37e2166756eeb96b7d8da436f0a9 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 12:08:15 +0100 Subject: [PATCH 21/27] Move to wait end --- README.md | 14 +++++++------- src/attribution/onTTFB.ts | 17 +++++++++-------- src/types/ttfb.ts | 14 +++++++------- test/e2e/onTTFB-test.js | 26 ++++++++++++++++++-------- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index bcaa74bb..43a5643b 100644 --- a/README.md +++ b/README.md @@ -1025,23 +1025,23 @@ interface LCPAttribution { interface TTFBAttribution { /** * The total time from when the user initiates loading the page to when the - * fetch request is initiated. This is mostly redirect times but may contain - * some browser processing time so may be non-zero even without redirects. + * page starts to handle the request. This is mostly redirect time but may + * contain browser processing time so may be non-zero even without redirects. */ waitingDuration: number; /** - * The total time spent checking the HTTP cache for a match. This cannot be - * accurately measured for sites using service workers. + * The total time spent checking the HTTP cache for a match. For sites using + * service workers this time represents the total service worker time. */ cacheDuration: number; /** - * The total time to resolve the DNS for the current request. This cannot be - * accurately measured for sites using service workers. + * The total time to resolve the DNS for the requested domain. This cannot + * always be accurately measured for requests using service workers. */ dnsDuration: number; /** * The total time to create the connection to the requested domain. This - * cannot be accurately measured for sites using service workers. + * cannot always be accurately measured for requests using service workers. */ connectionDuration: number; /** diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 916180b3..4a049f5f 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -28,8 +28,9 @@ const attributeTTFB = (metric: TTFBMetric): void => { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; - const fetchStart = Math.max( - navigationEntry.fetchStart - activationStart, + const waitEnd = Math.max( + (navigationEntry.workerStart || navigationEntry.fetchStart) - + activationStart, 0, ); const dnsStart = Math.max( @@ -40,17 +41,17 @@ const attributeTTFB = (metric: TTFBMetric): void => { navigationEntry.connectStart - activationStart, 0, ); - const requestStart = Math.max( - navigationEntry.requestStart - activationStart, + const connectEnd = Math.max( + navigationEntry.connectEnd - activationStart, 0, ); (metric as TTFBMetricWithAttribution).attribution = { - waitingDuration: fetchStart, - cacheDuration: dnsStart - fetchStart, + waitingDuration: waitEnd, + cacheDuration: dnsStart - waitEnd, dnsDuration: connectStart - dnsStart, - connectionDuration: requestStart - connectStart, - requestDuration: metric.value - requestStart, + connectionDuration: connectEnd - connectStart, + requestDuration: metric.value - connectEnd, navigationEntry: navigationEntry, }; return; diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index dee43bf7..768a5378 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -32,23 +32,23 @@ export interface TTFBMetric extends Metric { export interface TTFBAttribution { /** * The total time from when the user initiates loading the page to when the - * fetch request is initiated. This is mostly redirect times but may contain - * some browser processing time so may be non-zero even without redirects. + * page starts to handle the request. This is mostly redirect time but may + * contain browser processing time so may be non-zero even without redirects. */ waitingDuration: number; /** - * The total time spent checking the HTTP cache for a match. This cannot be - * accurately measured for sites using service workers. + * The total time spent checking the HTTP cache for a match. For sites using + * service workers this time represents the total service worker time. */ cacheDuration: number; /** - * The total time to resolve the DNS for the current request. This cannot be - * accurately measured for sites using service workers. + * The total time to resolve the DNS for the requested domain. This cannot + * always be accurately measured for requests using service workers. */ dnsDuration: number; /** * The total time to create the connection to the requested domain. This - * cannot be accurately measured for sites using service workers. + * cannot always be accurately measured for requests using service workers. */ connectionDuration: number; /** diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 9b5b2c93..01833a78 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -260,10 +260,14 @@ describe('onTTFB()', async function () { assertValidEntry(ttfb.entries[0]); const navEntry = ttfb.entries[0]; - assert.strictEqual(ttfb.attribution.waitingDuration, navEntry.fetchStart); + assert.strictEqual( + ttfb.attribution.waitingDuration, + navEntry.workerStart || navEntry.fetchStart, + ); assert.strictEqual( ttfb.attribution.cacheDuration, - navEntry.domainLookupStart - navEntry.fetchStart, + navEntry.domainLookupStart - + (navEntry.workerStart || navEntry.fetchStart), ); assert.strictEqual( ttfb.attribution.dnsDuration, @@ -271,11 +275,11 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.connectionDuration, - navEntry.requestStart - navEntry.connectStart, + navEntry.connectEnd - navEntry.connectStart, ); assert.strictEqual( ttfb.attribution.requestDuration, - navEntry.responseStart - navEntry.requestStart, + navEntry.responseStart - navEntry.connectEnd, ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); @@ -305,12 +309,18 @@ describe('onTTFB()', async function () { const navEntry = ttfb.entries[0]; assert.strictEqual( ttfb.attribution.waitingDuration, - Math.max(0, navEntry.fetchStart - activationStart), + Math.max( + 0, + (navEntry.workerStart || navEntry.fetchStart) - activationStart, + ), ); assert.strictEqual( ttfb.attribution.cacheDuration, Math.max(0, navEntry.domainLookupStart - activationStart) - - Math.max(0, navEntry.fetchStart - activationStart), + Math.max( + 0, + (navEntry.workerStart || navEntry.fetchStart) - activationStart, + ), ); assert.strictEqual( ttfb.attribution.dnsDuration, @@ -319,13 +329,13 @@ describe('onTTFB()', async function () { ); assert.strictEqual( ttfb.attribution.connectionDuration, - Math.max(0, navEntry.requestStart - activationStart) - + Math.max(0, navEntry.connectEnd - activationStart) - Math.max(0, navEntry.connectStart - activationStart), ); assert.strictEqual( ttfb.attribution.requestDuration, Math.max(0, navEntry.responseStart - activationStart) - - Math.max(0, navEntry.requestStart - activationStart), + Math.max(0, navEntry.connectEnd - activationStart), ); assert.deepEqual(ttfb.attribution.navigationEntry, navEntry); From 4c6f60fc2cf088e383b4b45afd56413c59800f83 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 15:35:46 +0100 Subject: [PATCH 22/27] More fixes --- README.md | 8 ++++---- src/attribution/onTTFB.ts | 14 +++++++++++++- src/types/ttfb.ts | 8 ++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 43a5643b..05c25da1 100644 --- a/README.md +++ b/README.md @@ -1030,18 +1030,18 @@ interface TTFBAttribution { */ waitingDuration: number; /** - * The total time spent checking the HTTP cache for a match. For sites using - * service workers this time represents the total service worker time. + * The total time spent checking the HTTP cache for a match. For navigations + * using service workers this time represents the total service worker time. */ cacheDuration: number; /** * The total time to resolve the DNS for the requested domain. This cannot - * always be accurately measured for requests using service workers. + * be measured for requests using service workers and will be 0. */ dnsDuration: number; /** * The total time to create the connection to the requested domain. This - * cannot always be accurately measured for requests using service workers. + * cannot be measured for requests using service workers and will be 0. */ connectionDuration: number; /** diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 4a049f5f..3e5002c0 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -24,10 +24,16 @@ import { } from '../types.js'; const attributeTTFB = (metric: TTFBMetric): void => { - if (metric.entries.length) { + // Only attribute if you have a non-zero value and a navigationEntry entry + // E.g. For cross-origin redirects, Safari has a workerStart time but no + // TTFB time, so do not provide attribute for that. + if (metric.value && metric.entries.length) { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; + // Measure from workerStart or fetchStart so any service worker startup + // time is included in cacheDuration (which also includes other sw time + // anyway, that cannot be accurately split out cross-browser). const waitEnd = Math.max( (navigationEntry.workerStart || navigationEntry.fetchStart) - activationStart, @@ -49,8 +55,14 @@ const attributeTTFB = (metric: TTFBMetric): void => { (metric as TTFBMetricWithAttribution).attribution = { waitingDuration: waitEnd, cacheDuration: dnsStart - waitEnd, + // dnsEnd usually equals connectStart but use connectStart over dnsEnd + // for dnsDuration in case there ever is a gap. dnsDuration: connectStart - dnsStart, connectionDuration: connectEnd - connectStart, + // There is often a gap between connectEnd and requestStart. Attribute + // that to requestDuration so connectionDuration remains 0 for + // service worker controlled requests were connectStart and connectEnd + // are the same. requestDuration: metric.value - connectEnd, navigationEntry: navigationEntry, }; diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 768a5378..b8a8af3b 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -37,18 +37,18 @@ export interface TTFBAttribution { */ waitingDuration: number; /** - * The total time spent checking the HTTP cache for a match. For sites using - * service workers this time represents the total service worker time. + * The total time spent checking the HTTP cache for a match. For navigations + * using service workers this time represents the total service worker time. */ cacheDuration: number; /** * The total time to resolve the DNS for the requested domain. This cannot - * always be accurately measured for requests using service workers. + * be measured for requests using service workers and will be 0. */ dnsDuration: number; /** * The total time to create the connection to the requested domain. This - * cannot always be accurately measured for requests using service workers. + * cannot be measured for requests using service workers and will be 0. */ connectionDuration: number; /** From 25c29bded74fd40690f5e62b91f86ffc10148f92 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 15:36:38 +0100 Subject: [PATCH 23/27] Typo --- src/attribution/onTTFB.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 3e5002c0..e25a5037 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -26,7 +26,7 @@ import { const attributeTTFB = (metric: TTFBMetric): void => { // Only attribute if you have a non-zero value and a navigationEntry entry // E.g. For cross-origin redirects, Safari has a workerStart time but no - // TTFB time, so do not provide attribute for that. + // TTFB time, so do not provide attribution for that. if (metric.value && metric.entries.length) { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; From 2aff58b7a35736451412930c7bc097a5c9857a73 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 16:03:23 +0100 Subject: [PATCH 24/27] Fix test --- src/attribution/onTTFB.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index e25a5037..100dc423 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -24,10 +24,11 @@ import { } from '../types.js'; const attributeTTFB = (metric: TTFBMetric): void => { - // Only attribute if you have a non-zero value and a navigationEntry entry + // Only attribute if you have a non-zero responseStart value and a + // navigationEntry entry. // E.g. For cross-origin redirects, Safari has a workerStart time but no // TTFB time, so do not provide attribution for that. - if (metric.value && metric.entries.length) { + if (metric.entries.length && metric.entries[0].responseStart) { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; From d2fb0aed6189985f1509886d3819961e34a3247e Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Fri, 12 Apr 2024 17:26:36 +0100 Subject: [PATCH 25/27] Remove unnecessary check --- src/attribution/onTTFB.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/attribution/onTTFB.ts b/src/attribution/onTTFB.ts index 100dc423..7c1876e4 100644 --- a/src/attribution/onTTFB.ts +++ b/src/attribution/onTTFB.ts @@ -24,11 +24,7 @@ import { } from '../types.js'; const attributeTTFB = (metric: TTFBMetric): void => { - // Only attribute if you have a non-zero responseStart value and a - // navigationEntry entry. - // E.g. For cross-origin redirects, Safari has a workerStart time but no - // TTFB time, so do not provide attribution for that. - if (metric.entries.length && metric.entries[0].responseStart) { + if (metric.entries.length) { const navigationEntry = metric.entries[0]; const activationStart = navigationEntry.activationStart || 0; From 51213c2ee1c4f624b1c73a87063265b9a8a73779 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 12 Apr 2024 13:26:59 -0700 Subject: [PATCH 26/27] Update timing comments --- README.md | 17 +++++++++-------- src/types/ttfb.ts | 19 ++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 05c25da1..09e88598 100644 --- a/README.md +++ b/README.md @@ -1022,26 +1022,27 @@ interface LCPAttribution { #### TTFB `attribution`: ```ts -interface TTFBAttribution { +export interface TTFBAttribution { /** * The total time from when the user initiates loading the page to when the - * page starts to handle the request. This is mostly redirect time but may - * contain browser processing time so may be non-zero even without redirects. + * page starts to handle the request. Large values here are typically due + * to HTTP redirects, though other browser processing contributes to this + * duration as well (so even without redirect it's generally not zero). */ waitingDuration: number; /** * The total time spent checking the HTTP cache for a match. For navigations - * using service workers this time represents the total service worker time. + * the handled via service worker, this time usually includes the service + * worker start-up time as well as time processing `fetch` event listeners, + * with some exceptions: https://github.com/w3c/navigation-timing/issues/199 */ cacheDuration: number; /** - * The total time to resolve the DNS for the requested domain. This cannot - * be measured for requests using service workers and will be 0. + * The total time to resolve the DNS for the requested domain. */ dnsDuration: number; /** - * The total time to create the connection to the requested domain. This - * cannot be measured for requests using service workers and will be 0. + * The total time to create the connection to the requested domain. */ connectionDuration: number; /** diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index b8a8af3b..0acc8d76 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -28,27 +28,32 @@ export interface TTFBMetric extends Metric { * An object containing potentially-helpful debugging information that * can be sent along with the TTFB value for the current page visit in order * to help identify issues happening to real-users in the field. + * + * NOTE: these values are primarily useful for page loads not handled via + * service worker, as browsers differ in what they report when service worker + * is involved, see: https://github.com/w3c/navigation-timing/issues/199 */ export interface TTFBAttribution { /** * The total time from when the user initiates loading the page to when the - * page starts to handle the request. This is mostly redirect time but may - * contain browser processing time so may be non-zero even without redirects. + * page starts to handle the request. Large values here are typically due + * to HTTP redirects, though other browser processing contributes to this + * duration as well (so even without redirect it's generally not zero). */ waitingDuration: number; /** * The total time spent checking the HTTP cache for a match. For navigations - * using service workers this time represents the total service worker time. + * the handled via service worker, this time usually includes the service + * worker start-up time as well as time processing `fetch` event listeners, + * with some exceptions: https://github.com/w3c/navigation-timing/issues/199 */ cacheDuration: number; /** - * The total time to resolve the DNS for the requested domain. This cannot - * be measured for requests using service workers and will be 0. + * The total time to resolve the DNS for the requested domain. */ dnsDuration: number; /** - * The total time to create the connection to the requested domain. This - * cannot be measured for requests using service workers and will be 0. + * The total time to create the connection to the requested domain. */ connectionDuration: number; /** From 8436f53f45c2e6052821ce7b2106aa83183c0921 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 12 Apr 2024 13:35:50 -0700 Subject: [PATCH 27/27] Fix typo --- README.md | 6 +++--- src/types/ttfb.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 09e88598..a685bffc 100644 --- a/README.md +++ b/README.md @@ -1032,9 +1032,9 @@ export interface TTFBAttribution { waitingDuration: number; /** * The total time spent checking the HTTP cache for a match. For navigations - * the handled via service worker, this time usually includes the service - * worker start-up time as well as time processing `fetch` event listeners, - * with some exceptions: https://github.com/w3c/navigation-timing/issues/199 + * handled via service worker, this duration usually includes service worker + * start-up time as well as time processing `fetch` event listeners, with + * some exceptions, see: https://github.com/w3c/navigation-timing/issues/199 */ cacheDuration: number; /** diff --git a/src/types/ttfb.ts b/src/types/ttfb.ts index 0acc8d76..2f7097c7 100644 --- a/src/types/ttfb.ts +++ b/src/types/ttfb.ts @@ -43,9 +43,9 @@ export interface TTFBAttribution { waitingDuration: number; /** * The total time spent checking the HTTP cache for a match. For navigations - * the handled via service worker, this time usually includes the service - * worker start-up time as well as time processing `fetch` event listeners, - * with some exceptions: https://github.com/w3c/navigation-timing/issues/199 + * handled via service worker, this duration usually includes service worker + * start-up time as well as time processing `fetch` event listeners, with + * some exceptions, see: https://github.com/w3c/navigation-timing/issues/199 */ cacheDuration: number; /**