Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split waitingDuration to make it easier to understand redirect delays #458

Merged
merged 29 commits into from Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Expand Up @@ -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')`.

Expand Down
20 changes: 14 additions & 6 deletions README.md
Expand Up @@ -1022,15 +1022,23 @@ interface LCPAttribution {
#### TTFB `attribution`:

```ts
interface TTFBAttribution {
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
* HTTP cache lookup times.
* 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 to resolve the DNS for the current request.
* The total time spent checking the HTTP cache for a match. For navigations
* 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;
/**
* The total time to resolve the DNS for the requested domain.
*/
dnsDuration: number;
/**
Expand All @@ -1045,8 +1053,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;
}
Expand Down
37 changes: 19 additions & 18 deletions docs/upgrading-to-v4.md
Expand Up @@ -14,32 +14,33 @@ 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`

- **Renamed** `waitingTime` to `waitingDuration` (see [#453](https://github.com/GoogleChrome/web-vitals/pull/453)).
- **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 `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** `waitingDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)).
- **Added** `cacheDuration` ([#458](https://github.com/GoogleChrome/web-vitals/pull/458)).

## 🚀 New features

Expand All @@ -51,9 +52,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)).
26 changes: 21 additions & 5 deletions src/attribution/onTTFB.ts
Expand Up @@ -28,6 +28,14 @@ const attributeTTFB = (metric: TTFBMetric): void => {
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) -
philipwalton marked this conversation as resolved.
Show resolved Hide resolved
activationStart,
0,
);
const dnsStart = Math.max(
navigationEntry.domainLookupStart - activationStart,
0,
Expand All @@ -36,23 +44,31 @@ 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: dnsStart,
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: requestStart - connectStart,
requestDuration: metric.value - requestStart,
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,
};
return;
}
// Set an empty object if no other attribution has been set.
(metric as TTFBMetricWithAttribution).attribution = {
waitingDuration: 0,
cacheDuration: 0,
dnsDuration: 0,
connectionDuration: 0,
requestDuration: 0,
Expand Down
22 changes: 17 additions & 5 deletions src/types/ttfb.ts
Expand Up @@ -28,16 +28,28 @@ 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
* DNS lookup begins. This includes redirects, service worker startup, and
* HTTP cache lookup times.
* 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 to resolve the DNS for the current request.
* The total time spent checking the HTTP cache for a match. For navigations
* 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;
/**
* The total time to resolve the DNS for the requested domain.
*/
dnsDuration: number;
/**
Expand All @@ -52,8 +64,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;
}
Expand Down
30 changes: 23 additions & 7 deletions test/e2e/onTTFB-test.js
Expand Up @@ -262,19 +262,24 @@ describe('onTTFB()', async function () {
const navEntry = ttfb.entries[0];
assert.strictEqual(
ttfb.attribution.waitingDuration,
navEntry.domainLookupStart,
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,
navEntry.connectEnd - navEntry.connectStart,
);
assert.strictEqual(
ttfb.attribution.requestDuration,
navEntry.responseStart - navEntry.requestStart,
navEntry.responseStart - navEntry.connectEnd,
);

assert.deepEqual(ttfb.attribution.navigationEntry, navEntry);
Expand Down Expand Up @@ -304,7 +309,18 @@ describe('onTTFB()', async function () {
const navEntry = ttfb.entries[0];
assert.strictEqual(
ttfb.attribution.waitingDuration,
Math.max(0, navEntry.domainLookupStart - activationStart),
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,
Expand All @@ -313,14 +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);
Expand All @@ -347,6 +362,7 @@ describe('onTTFB()', async function () {
assert.strictEqual(ttfb.entries.length, 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);
Expand Down