lighthouse 12.0.0-dev.20240513 → 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.
package/cli/run.js CHANGED
@@ -9,7 +9,6 @@
9
9
  import path from 'path';
10
10
  import os from 'os';
11
11
 
12
- import psList from 'ps-list';
13
12
  import * as ChromeLauncher from 'chrome-launcher';
14
13
  import yargsParser from 'yargs-parser';
15
14
  import log from 'lighthouse-logger';
@@ -176,36 +175,6 @@ async function saveResults(runnerResult, flags) {
176
175
  }
177
176
  }
178
177
 
179
- /**
180
- * Attempt to kill the launched Chrome, if defined.
181
- * @param {ChromeLauncher.LaunchedChrome=} launchedChrome
182
- * @return {Promise<void>}
183
- */
184
- async function potentiallyKillChrome(launchedChrome) {
185
- if (!launchedChrome) return;
186
-
187
- /** @type {NodeJS.Timeout} */
188
- let timeout;
189
- const timeoutPromise = new Promise((_, reject) => {
190
- timeout = setTimeout(reject, 5000, new Error('Timed out waiting to kill Chrome'));
191
- });
192
-
193
- return Promise.race([
194
- launchedChrome.kill(),
195
- timeoutPromise,
196
- ]).catch(async err => {
197
- const runningProcesses = await psList();
198
- if (!runningProcesses.some(proc => proc.pid === launchedChrome.pid)) {
199
- log.warn('CLI', 'Warning: Chrome process could not be killed because it already exited.');
200
- return;
201
- }
202
-
203
- throw new Error(`Couldn't quit Chrome process. ${err}`);
204
- }).finally(() => {
205
- clearTimeout(timeout);
206
- });
207
- }
208
-
209
178
  /**
210
179
  * @param {string} url
211
180
  * @param {LH.CliFlags} flags
@@ -214,12 +183,10 @@ async function potentiallyKillChrome(launchedChrome) {
214
183
  */
215
184
  async function runLighthouse(url, flags, config) {
216
185
  /** @param {any} reason */
217
- async function handleTheUnhandled(reason) {
186
+ function handleTheUnhandled(reason) {
218
187
  process.stderr.write(`Unhandled Rejection. Reason: ${reason}\n`);
219
- await potentiallyKillChrome(launchedChrome).catch(() => {});
220
- setTimeout(_ => {
221
- process.exit(1);
222
- }, 100);
188
+ launchedChrome?.kill();
189
+ process.exit(1);
223
190
  }
224
191
  process.on('unhandledRejection', handleTheUnhandled);
225
192
 
@@ -247,7 +214,7 @@ async function runLighthouse(url, flags, config) {
247
214
  await saveResults(runnerResult, flags);
248
215
  }
249
216
 
250
- await potentiallyKillChrome(launchedChrome);
217
+ launchedChrome?.kill();
251
218
  process.removeListener('unhandledRejection', handleTheUnhandled);
252
219
 
253
220
  // Runtime errors indicate something was *very* wrong with the page result.
@@ -265,7 +232,7 @@ async function runLighthouse(url, flags, config) {
265
232
 
266
233
  return runnerResult;
267
234
  } catch (err) {
268
- await potentiallyKillChrome(launchedChrome).catch(() => {});
235
+ launchedChrome?.kill();
269
236
  return printErrorAndExit(err);
270
237
  }
271
238
  }
@@ -97,7 +97,7 @@ async function runBundledLighthouse(url, config, testRunnerOptions) {
97
97
  };
98
98
  } finally {
99
99
  // Clean up and return results.
100
- await launchedChrome.kill();
100
+ launchedChrome.kill();
101
101
  }
102
102
  }
103
103
 
@@ -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.20240513",
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": {
@@ -198,7 +198,6 @@
198
198
  "metaviewport-parser": "0.3.0",
199
199
  "open": "^8.4.0",
200
200
  "parse-cache-control": "1.0.1",
201
- "ps-list": "^8.0.0",
202
201
  "puppeteer-core": "^22.6.5",
203
202
  "robots-parser": "^3.0.1",
204
203
  "semver": "^5.3.0",