browsertime 16.5.0 → 16.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
- ## 16.5.0 - 2022-05-11
3
+ ## 16.6.0 - 2022-05-20
4
+ ### Added
5
+ * Implemented experimental Interaction to next paint that's useful if you test user journeys [#1791](https://github.com/sitespeedio/browsertime/pull/1791).
6
+ * Track when the last CPU long task happen as explained by Andy Davies of the webperf Slack channel [#1789](https://github.com/sitespeedio/browsertime/pull/1789).
7
+
8
+ ## 16.5.0 - 2022-05-11
4
9
  ### Added
5
10
  * Make it possible to configure the max size in pixels for the filmstrip screenshots using `--videoParams.thumbsize` [#1787](https://github.com/sitespeedio/browsertime/pull/1787).
6
11
 
@@ -0,0 +1,117 @@
1
+ (function () {
2
+ // This is an updated version of
3
+ // https://github.com/GoogleChrome/web-vitals/blob/next/src/onINP.ts
4
+
5
+ const supported = PerformanceObserver.supportedEntryTypes;
6
+ if (!supported || supported.indexOf('event') === -1) {
7
+ return;
8
+ }
9
+
10
+ const observer = new PerformanceObserver(list => {});
11
+ // Event Timing entries have their durations rounded to the nearest 8ms,
12
+ // so a duration of 40ms would be any event that spans 2.5 or more frames
13
+ // at 60Hz. This threshold is chosen to strike a balance between usefulness
14
+ // and performance. Running this callback for any interaction that spans
15
+ // just one or two frames is likely not worth the insight that could be
16
+ // gained.
17
+ observer.observe({type: 'event', buffered: true, durationThreshold: 40});
18
+ const entries = observer.takeRecords();
19
+
20
+ // To prevent unnecessary memory usage on pages with lots of interactions,
21
+ // store at most 10 of the longest interactions to consider as INP candidates.
22
+ const MAX_INTERACTIONS_TO_CONSIDER = 10;
23
+
24
+ // A list of longest interactions on the page (by latency) sorted so the
25
+ // longest one is first. The list is as most MAX_INTERACTIONS_TO_CONSIDER long.
26
+ let longestInteractionList = [];
27
+
28
+ // A mapping of longest interactions by their interaction ID.
29
+ // This is used for faster lookup.
30
+ const longestInteractionMap = {};
31
+
32
+ const getInteractionCountForNavigation = () => {
33
+ // I guess the interactionCount is coming in Chrome later on
34
+ if (performance.interactionCount) {
35
+ return performance.interactionCount;
36
+ } else {
37
+ const observerForAll = new PerformanceObserver(list => {});
38
+ observerForAll.observe({
39
+ type: 'event',
40
+ buffered: true,
41
+ durationThreshold: 0
42
+ });
43
+ const allEntries = observerForAll.takeRecords();
44
+ let interactionCountEstimate = 0;
45
+ let minKnownInteractionId = Infinity;
46
+ let maxKnownInteractionId = 0;
47
+
48
+ for (let e of allEntries) {
49
+ if (e.interactionId) {
50
+ minKnownInteractionId = Math.min(
51
+ minKnownInteractionId,
52
+ e.interactionId
53
+ );
54
+ maxKnownInteractionId = Math.max(
55
+ maxKnownInteractionId,
56
+ e.interactionId
57
+ );
58
+
59
+ interactionCountEstimate = maxKnownInteractionId
60
+ ? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
61
+ : 0;
62
+ }
63
+ }
64
+ return interactionCountEstimate;
65
+ }
66
+ };
67
+
68
+ const estimateP98LongestInteraction = () => {
69
+ const candidateInteractionIndex = Math.min(
70
+ longestInteractionList.length - 1,
71
+ Math.floor(getInteractionCountForNavigation() / 50)
72
+ );
73
+
74
+ return longestInteractionList[candidateInteractionIndex];
75
+ };
76
+
77
+ if (entries.length > 0) {
78
+ for (let entry of entries) {
79
+ const minLongestInteraction =
80
+ longestInteractionList[longestInteractionList.length - 1];
81
+ const existingInteraction = longestInteractionMap[entry.interactionId];
82
+ if (
83
+ existingInteraction ||
84
+ longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||
85
+ entry.duration > minLongestInteraction.latency
86
+ ) {
87
+ // If the interaction already exists, update it. Otherwise create one.
88
+ if (existingInteraction) {
89
+ existingInteraction.entries.push(entry);
90
+ existingInteraction.latency = Math.max(
91
+ existingInteraction.latency,
92
+ entry.duration
93
+ );
94
+ } else {
95
+ const interaction = {
96
+ id: entry.interactionId,
97
+ latency: entry.duration,
98
+ entries: [entry]
99
+ };
100
+ longestInteractionMap[interaction.id] = interaction;
101
+ longestInteractionList.push(interaction);
102
+ }
103
+
104
+ // Sort the entries by latency (descending) and keep only the top ten.
105
+ longestInteractionList.sort((a, b) => b.latency - a.latency);
106
+ longestInteractionList
107
+ .splice(MAX_INTERACTIONS_TO_CONSIDER)
108
+ .forEach(i => {
109
+ delete longestInteractionMap[i.id];
110
+ });
111
+ }
112
+ }
113
+ return estimateP98LongestInteraction();
114
+ } else {
115
+ // nothing to report
116
+ }
117
+ })();
@@ -0,0 +1,116 @@
1
+ (function () {
2
+ // This is an updated version of
3
+ // https://github.com/GoogleChrome/web-vitals/blob/next/src/onINP.ts
4
+
5
+ const supported = PerformanceObserver.supportedEntryTypes;
6
+ if (!supported || supported.indexOf('event') === -1) {
7
+ return;
8
+ }
9
+
10
+ // To prevent unnecessary memory usage on pages with lots of interactions,
11
+ // store at most 10 of the longest interactions to consider as INP candidates.
12
+ const MAX_INTERACTIONS_TO_CONSIDER = 10;
13
+
14
+ // A list of longest interactions on the page (by latency) sorted so the
15
+ // longest one is first. The list is as most MAX_INTERACTIONS_TO_CONSIDER long.
16
+ let longestInteractionList = [];
17
+
18
+ // A mapping of longest interactions by their interaction ID.
19
+ // This is used for faster lookup.
20
+ const longestInteractionMap = {};
21
+
22
+ const getInteractionCountForNavigation = () => {
23
+ // I guess the interactionCount is coming in Chrome later on
24
+ if (performance.interactionCount) {
25
+ return performance.interactionCount;
26
+ } else {
27
+ const observerForAll = new PerformanceObserver(list => {});
28
+ observerForAll.observe({
29
+ type: 'event',
30
+ buffered: true,
31
+ durationThreshold: 0
32
+ });
33
+ const allEntries = observerForAll.takeRecords();
34
+ let interactionCountEstimate = 0;
35
+ let minKnownInteractionId = Infinity;
36
+ let maxKnownInteractionId = 0;
37
+
38
+ for (let e of allEntries) {
39
+ if (e.interactionId) {
40
+ minKnownInteractionId = Math.min(
41
+ minKnownInteractionId,
42
+ e.interactionId
43
+ );
44
+ maxKnownInteractionId = Math.max(
45
+ maxKnownInteractionId,
46
+ e.interactionId
47
+ );
48
+
49
+ interactionCountEstimate = maxKnownInteractionId
50
+ ? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
51
+ : 0;
52
+ }
53
+ }
54
+ return interactionCountEstimate;
55
+ }
56
+ };
57
+
58
+ const estimateP98LongestInteraction = () => {
59
+ const candidateInteractionIndex = Math.min(
60
+ longestInteractionList.length - 1,
61
+ Math.floor(getInteractionCountForNavigation() / 50)
62
+ );
63
+
64
+ return longestInteractionList[candidateInteractionIndex];
65
+ };
66
+
67
+ const observer = new PerformanceObserver(list => {});
68
+ // Event Timing entries have their durations rounded to the nearest 8ms,
69
+ // so a duration of 40ms would be any event that spans 2.5 or more frames
70
+ // at 60Hz. This threshold is chosen to strike a balance between usefulness
71
+ // and performance. Running this callback for any interaction that spans
72
+ // just one or two frames is likely not worth the insight that could be
73
+ // gained.
74
+ observer.observe({type: 'event', buffered: true, durationThreshold: 40});
75
+ const entries = observer.takeRecords();
76
+
77
+ if (entries.length > 0) {
78
+ for (let entry of entries) {
79
+ const minLongestInteraction =
80
+ longestInteractionList[longestInteractionList.length - 1];
81
+ const existingInteraction = longestInteractionMap[entry.interactionId];
82
+ if (
83
+ existingInteraction ||
84
+ longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||
85
+ entry.duration > minLongestInteraction.latency
86
+ ) {
87
+ // If the interaction already exists, update it. Otherwise create one.
88
+ if (existingInteraction) {
89
+ existingInteraction.latency = Math.max(
90
+ existingInteraction.latency,
91
+ entry.duration
92
+ );
93
+ } else {
94
+ const interaction = {
95
+ id: entry.interactionId,
96
+ latency: entry.duration
97
+ };
98
+ longestInteractionMap[interaction.id] = interaction;
99
+ longestInteractionList.push(interaction);
100
+ }
101
+
102
+ // Sort the entries by latency (descending) and keep only the top ten.
103
+ longestInteractionList.sort((a, b) => b.latency - a.latency);
104
+ longestInteractionList
105
+ .splice(MAX_INTERACTIONS_TO_CONSIDER)
106
+ .forEach(i => {
107
+ delete longestInteractionMap[i.id];
108
+ });
109
+ }
110
+ }
111
+ const inp = estimateP98LongestInteraction();
112
+ return inp.latency;
113
+ } else {
114
+ // nothing to report
115
+ }
116
+ })();
@@ -27,9 +27,13 @@ module.exports = function (result, options) {
27
27
  let longTasksBeforeFirstPaint = 0;
28
28
  let longTasksBeforeFirstContentfulPaint = 0;
29
29
  let longTasksAfterLoadEventEnd = 0;
30
+ let lastLongTask = 0;
30
31
 
31
32
  for (let longTask of result.browserScripts.pageinfo.longTask) {
32
33
  totalDuration += longTask.duration;
34
+ if (longTask.startTime > lastLongTask) {
35
+ lastLongTask = longTask.startTime;
36
+ }
33
37
  if (firstPaint && longTask.startTime < firstPaint) {
34
38
  longTasksBeforeFirstPaint++;
35
39
  totalDurationFirstPaint += longTask.duration;
@@ -61,6 +65,7 @@ module.exports = function (result, options) {
61
65
  totalDuration,
62
66
  totalBlockingTime: Number(totalBlockingTime.toFixed(0)),
63
67
  maxPotentialFid: Number(maxPotentialFid.toFixed(0)),
68
+ lastLongTask: Number(lastLongTask.toFixed(0)),
64
69
  beforeFirstPaint: {
65
70
  tasks: longTasksBeforeFirstPaint,
66
71
  totalDuration: totalDurationFirstPaint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "description": "Browsertime",
3
- "version": "16.5.0",
3
+ "version": "16.6.0",
4
4
  "bin": "./bin/browsertime.js",
5
5
  "dependencies": {
6
6
  "@cypress/xvfb": "1.2.4",