lighthouse 12.0.0-dev.20240514 → 12.0.0-dev.20240515

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.
@@ -4,9 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import * as Lantern from '../lib/lantern/types/lantern.js';
8
7
  import {makeComputedArtifact} from './computed-artifact.js';
9
- import * as constants from '../config/constants.js';
10
8
  import {Simulator} from '../lib/lantern/simulator/simulator.js';
11
9
  import {NetworkAnalysis} from './network-analysis.js';
12
10
 
@@ -17,57 +15,8 @@ class LoadSimulator {
17
15
  * @return {Promise<Simulator>}
18
16
  */
19
17
  static async compute_(data, context) {
20
- const {throttlingMethod, throttling, precomputedLanternData} = data.settings;
21
18
  const networkAnalysis = await NetworkAnalysis.request(data.devtoolsLog, context);
22
-
23
- /** @type {Lantern.Simulation.Options} */
24
- const options = {
25
- additionalRttByOrigin: networkAnalysis.additionalRttByOrigin,
26
- serverResponseTimeByOrigin: networkAnalysis.serverResponseTimeByOrigin,
27
- observedThroughput: networkAnalysis.throughput,
28
- };
29
-
30
- // If we have precomputed lantern data, overwrite our observed estimates and use precomputed instead
31
- // for increased stability.
32
- if (precomputedLanternData) {
33
- options.additionalRttByOrigin = new Map(Object.entries(
34
- precomputedLanternData.additionalRttByOrigin));
35
- options.serverResponseTimeByOrigin = new Map(Object.entries(
36
- precomputedLanternData.serverResponseTimeByOrigin));
37
- }
38
-
39
- switch (throttlingMethod) {
40
- case 'provided':
41
- options.rtt = networkAnalysis.rtt;
42
- options.throughput = networkAnalysis.throughput;
43
- options.cpuSlowdownMultiplier = 1;
44
- options.layoutTaskMultiplier = 1;
45
- break;
46
- case 'devtools':
47
- if (throttling) {
48
- options.rtt =
49
- throttling.requestLatencyMs / constants.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR;
50
- options.throughput =
51
- throttling.downloadThroughputKbps * 1024 /
52
- constants.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR;
53
- }
54
-
55
- options.cpuSlowdownMultiplier = 1;
56
- options.layoutTaskMultiplier = 1;
57
- break;
58
- case 'simulate':
59
- if (throttling) {
60
- options.rtt = throttling.rttMs;
61
- options.throughput = throttling.throughputKbps * 1024;
62
- options.cpuSlowdownMultiplier = throttling.cpuSlowdownMultiplier;
63
- }
64
- break;
65
- default:
66
- // intentionally fallback to simulator defaults
67
- break;
68
- }
69
-
70
- return new Simulator(options);
19
+ return Simulator.createSimulator({...data.settings, networkAnalysis});
71
20
  }
72
21
 
73
22
  /**
@@ -5,11 +5,6 @@ declare const NetworkAnalysisComputed: typeof NetworkAnalysis & {
5
5
  }>) => Promise<import("../index.js").Artifacts.NetworkAnalysis>;
6
6
  };
7
7
  declare class NetworkAnalysis {
8
- /**
9
- * @param {Array<LH.Artifacts.NetworkRequest>} records
10
- * @return {LH.Util.StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>}
11
- */
12
- static computeRTTAndServerResponseTime(records: Array<LH.Artifacts.NetworkRequest>): LH.Util.StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>;
13
8
  /**
14
9
  * @param {LH.DevtoolsLog} devtoolsLog
15
10
  * @param {LH.Artifacts.ComputedContext} context
@@ -9,44 +9,6 @@ import {NetworkAnalyzer} from '../lib/lantern/simulator/network-analyzer.js';
9
9
  import {NetworkRecords} from './network-records.js';
10
10
 
11
11
  class NetworkAnalysis {
12
- /**
13
- * @param {Array<LH.Artifacts.NetworkRequest>} records
14
- * @return {LH.Util.StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>}
15
- */
16
- static computeRTTAndServerResponseTime(records) {
17
- // First pass compute the estimated observed RTT to each origin's servers.
18
- /** @type {Map<string, number>} */
19
- const rttByOrigin = new Map();
20
- for (const [origin, summary] of NetworkAnalyzer.estimateRTTByOrigin(records).entries()) {
21
- rttByOrigin.set(origin, summary.min);
22
- }
23
-
24
- // We'll use the minimum RTT as the assumed connection latency since we care about how much addt'l
25
- // latency each origin introduces as Lantern will be simulating with its own connection latency.
26
- const minimumRtt = Math.min(...Array.from(rttByOrigin.values()));
27
- // We'll use the observed RTT information to help estimate the server response time
28
- const responseTimeSummaries = NetworkAnalyzer.estimateServerResponseTimeByOrigin(records, {
29
- rttByOrigin,
30
- });
31
-
32
- /** @type {Map<string, number>} */
33
- const additionalRttByOrigin = new Map();
34
- /** @type {Map<string, number>} */
35
- const serverResponseTimeByOrigin = new Map();
36
- for (const [origin, summary] of responseTimeSummaries.entries()) {
37
- // Not all origins have usable timing data, we'll default to using no additional latency.
38
- const rttForOrigin = rttByOrigin.get(origin) || minimumRtt;
39
- additionalRttByOrigin.set(origin, rttForOrigin - minimumRtt);
40
- serverResponseTimeByOrigin.set(origin, summary.median);
41
- }
42
-
43
- return {
44
- rtt: minimumRtt,
45
- additionalRttByOrigin,
46
- serverResponseTimeByOrigin,
47
- };
48
- }
49
-
50
12
  /**
51
13
  * @param {LH.DevtoolsLog} devtoolsLog
52
14
  * @param {LH.Artifacts.ComputedContext} context
@@ -54,9 +16,7 @@ class NetworkAnalysis {
54
16
  */
55
17
  static async compute_(devtoolsLog, context) {
56
18
  const records = await NetworkRecords.request(devtoolsLog, context);
57
- const throughput = NetworkAnalyzer.estimateThroughput(records);
58
- const rttAndServerResponseTime = NetworkAnalysis.computeRTTAndServerResponseTime(records);
59
- return {throughput, ...rttAndServerResponseTime};
19
+ return NetworkAnalyzer.analyze(records);
60
20
  }
61
21
  }
62
22
 
@@ -164,10 +164,23 @@ export class NetworkAnalyzer {
164
164
  * Excludes data URI, failed or otherwise incomplete, and cached requests.
165
165
  * Returns Infinity if there were no analyzable network records.
166
166
  *
167
- * @param {Array<Lantern.NetworkRequest>} networkRecords
167
+ * @param {Lantern.NetworkRequest[]} records
168
168
  * @return {number}
169
169
  */
170
- static estimateThroughput(networkRecords: Array<Lantern.NetworkRequest>): number;
170
+ static estimateThroughput(records: Lantern.NetworkRequest[]): number;
171
+ /**
172
+ * @param {Lantern.NetworkRequest[]} records
173
+ */
174
+ static computeRTTAndServerResponseTime(records: Lantern.NetworkRequest[]): {
175
+ rtt: number;
176
+ additionalRttByOrigin: Map<string, number>;
177
+ serverResponseTimeByOrigin: Map<string, number>;
178
+ };
179
+ /**
180
+ * @param {Lantern.NetworkRequest[]} records
181
+ * @return {Lantern.Simulation.Settings['networkAnalysis']}
182
+ */
183
+ static analyze(records: Lantern.NetworkRequest[]): Lantern.Simulation.Settings['networkAnalysis'];
171
184
  /**
172
185
  * @template {Lantern.NetworkRequest} T
173
186
  * @param {Array<T>} records
@@ -431,16 +431,16 @@ class NetworkAnalyzer {
431
431
  * Excludes data URI, failed or otherwise incomplete, and cached requests.
432
432
  * Returns Infinity if there were no analyzable network records.
433
433
  *
434
- * @param {Array<Lantern.NetworkRequest>} networkRecords
434
+ * @param {Lantern.NetworkRequest[]} records
435
435
  * @return {number}
436
436
  */
437
- static estimateThroughput(networkRecords) {
437
+ static estimateThroughput(records) {
438
438
  let totalBytes = 0;
439
439
 
440
440
  // We will measure throughput by summing the total bytes downloaded by the total time spent
441
441
  // downloading those bytes. We slice up all the network records into start/end boundaries, so
442
442
  // it's easier to deal with the gaps in downloading.
443
- const timeBoundaries = networkRecords.reduce((boundaries, record) => {
443
+ const timeBoundaries = records.reduce((boundaries, record) => {
444
444
  const scheme = record.parsedURL?.scheme;
445
445
  // Requests whose bodies didn't come over the network or didn't completely finish will mess
446
446
  // with the computation, just skip over them.
@@ -483,6 +483,55 @@ class NetworkAnalyzer {
483
483
  return totalBytes * 8 / totalDuration;
484
484
  }
485
485
 
486
+ /**
487
+ * @param {Lantern.NetworkRequest[]} records
488
+ */
489
+ static computeRTTAndServerResponseTime(records) {
490
+ // First pass compute the estimated observed RTT to each origin's servers.
491
+ /** @type {Map<string, number>} */
492
+ const rttByOrigin = new Map();
493
+ for (const [origin, summary] of NetworkAnalyzer.estimateRTTByOrigin(records).entries()) {
494
+ rttByOrigin.set(origin, summary.min);
495
+ }
496
+
497
+ // We'll use the minimum RTT as the assumed connection latency since we care about how much addt'l
498
+ // latency each origin introduces as Lantern will be simulating with its own connection latency.
499
+ const minimumRtt = Math.min(...Array.from(rttByOrigin.values()));
500
+ // We'll use the observed RTT information to help estimate the server response time
501
+ const responseTimeSummaries = NetworkAnalyzer.estimateServerResponseTimeByOrigin(records, {
502
+ rttByOrigin,
503
+ });
504
+
505
+ /** @type {Map<string, number>} */
506
+ const additionalRttByOrigin = new Map();
507
+ /** @type {Map<string, number>} */
508
+ const serverResponseTimeByOrigin = new Map();
509
+ for (const [origin, summary] of responseTimeSummaries.entries()) {
510
+ // Not all origins have usable timing data, we'll default to using no additional latency.
511
+ const rttForOrigin = rttByOrigin.get(origin) || minimumRtt;
512
+ additionalRttByOrigin.set(origin, rttForOrigin - minimumRtt);
513
+ serverResponseTimeByOrigin.set(origin, summary.median);
514
+ }
515
+
516
+ return {
517
+ rtt: minimumRtt,
518
+ additionalRttByOrigin,
519
+ serverResponseTimeByOrigin,
520
+ };
521
+ }
522
+
523
+ /**
524
+ * @param {Lantern.NetworkRequest[]} records
525
+ * @return {Lantern.Simulation.Settings['networkAnalysis']}
526
+ */
527
+ static analyze(records) {
528
+ const throughput = NetworkAnalyzer.estimateThroughput(records);
529
+ return {
530
+ throughput,
531
+ ...NetworkAnalyzer.computeRTTAndServerResponseTime(records),
532
+ };
533
+ }
534
+
486
535
  /**
487
536
  * @template {Lantern.NetworkRequest} T
488
537
  * @param {Array<T>} records
@@ -7,6 +7,10 @@ export type ConnectionTiming = import('./simulator-timing-map.js').ConnectionTim
7
7
  * @template [T=any]
8
8
  */
9
9
  export class Simulator<T = any> {
10
+ /**
11
+ * @param {Lantern.Simulation.Settings} settings
12
+ */
13
+ static createSimulator(settings: Lantern.Simulation.Settings): Promise<Simulator<any>>;
10
14
  /** @return {Map<string, Map<Node, CompleteNodeTiming>>} */
11
15
  static get ALL_NODE_TIMINGS(): Map<string, Map<Node, CompleteNodeTiming>>;
12
16
  /**
@@ -50,6 +50,62 @@ const ALL_SIMULATION_NODE_TIMINGS = new Map();
50
50
  * @template [T=any]
51
51
  */
52
52
  class Simulator {
53
+ /**
54
+ * @param {Lantern.Simulation.Settings} settings
55
+ */
56
+ static async createSimulator(settings) {
57
+ const {throttlingMethod, throttling, precomputedLanternData, networkAnalysis} = settings;
58
+
59
+ /** @type {Lantern.Simulation.Options} */
60
+ const options = {
61
+ additionalRttByOrigin: networkAnalysis.additionalRttByOrigin,
62
+ serverResponseTimeByOrigin: networkAnalysis.serverResponseTimeByOrigin,
63
+ observedThroughput: networkAnalysis.throughput,
64
+ };
65
+
66
+ // If we have precomputed lantern data, overwrite our observed estimates and use precomputed instead
67
+ // for increased stability.
68
+ if (precomputedLanternData) {
69
+ options.additionalRttByOrigin = new Map(Object.entries(
70
+ precomputedLanternData.additionalRttByOrigin));
71
+ options.serverResponseTimeByOrigin = new Map(Object.entries(
72
+ precomputedLanternData.serverResponseTimeByOrigin));
73
+ }
74
+
75
+ switch (throttlingMethod) {
76
+ case 'provided':
77
+ options.rtt = networkAnalysis.rtt;
78
+ options.throughput = networkAnalysis.throughput;
79
+ options.cpuSlowdownMultiplier = 1;
80
+ options.layoutTaskMultiplier = 1;
81
+ break;
82
+ case 'devtools':
83
+ if (throttling) {
84
+ options.rtt =
85
+ throttling.requestLatencyMs / constants.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR;
86
+ options.throughput =
87
+ throttling.downloadThroughputKbps * 1024 /
88
+ constants.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR;
89
+ }
90
+
91
+ options.cpuSlowdownMultiplier = 1;
92
+ options.layoutTaskMultiplier = 1;
93
+ break;
94
+ case 'simulate':
95
+ if (throttling) {
96
+ options.rtt = throttling.rttMs;
97
+ options.throughput = throttling.throughputKbps * 1024;
98
+ options.cpuSlowdownMultiplier = throttling.cpuSlowdownMultiplier;
99
+ }
100
+ break;
101
+ default:
102
+ // intentionally fallback to simulator defaults
103
+ break;
104
+ }
105
+
106
+ return new Simulator(options);
107
+ }
108
+
53
109
  /**
54
110
  * @param {Lantern.Simulation.Options} [options]
55
111
  */
@@ -101,6 +101,44 @@ export namespace Simulation {
101
101
  pessimistic: number;
102
102
  }
103
103
 
104
+ /** Simulation settings that control the amount of network & cpu throttling in the run. */
105
+ interface ThrottlingSettings {
106
+ /** The round trip time in milliseconds. */
107
+ rttMs?: number;
108
+ /** The network throughput in kilobits per second. */
109
+ throughputKbps?: number;
110
+ // devtools settings
111
+ /** The network request latency in milliseconds. */
112
+ requestLatencyMs?: number;
113
+ /** The network download throughput in kilobits per second. */
114
+ downloadThroughputKbps?: number;
115
+ /** The network upload throughput in kilobits per second. */
116
+ uploadThroughputKbps?: number;
117
+ // used by both
118
+ /** The amount of slowdown applied to the cpu (1/<cpuSlowdownMultiplier>). */
119
+ cpuSlowdownMultiplier?: number
120
+ }
121
+
122
+ interface PrecomputedLanternData {
123
+ additionalRttByOrigin: {[origin: string]: number};
124
+ serverResponseTimeByOrigin: {[origin: string]: number};
125
+ }
126
+
127
+ interface Settings {
128
+ networkAnalysis: {
129
+ rtt: number;
130
+ additionalRttByOrigin: Map<string, number>;
131
+ serverResponseTimeByOrigin: Map<string, number>;
132
+ throughput: number;
133
+ };
134
+ /** The method used to throttle the network. */
135
+ throttlingMethod: 'devtools'|'simulate'|'provided';
136
+ /** The throttling config settings. */
137
+ throttling: Required<ThrottlingSettings>;
138
+ /** Precomputed lantern estimates to use instead of observed analysis. */
139
+ precomputedLanternData?: PrecomputedLanternData | null;
140
+ }
141
+
104
142
  interface Options {
105
143
  rtt?: number;
106
144
  throughput?: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "12.0.0-dev.20240514",
4
+ "version": "12.0.0-dev.20240515",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {