lighthouse 12.0.0-dev.20240611 → 12.0.0-dev.20240613
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/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +2 -2
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/byte-efficiency/render-blocking-resources.d.ts +3 -3
- package/core/audits/byte-efficiency/render-blocking-resources.js +4 -4
- package/core/audits/dobetterweb/uses-http2.d.ts +2 -2
- package/core/audits/dobetterweb/uses-http2.js +2 -2
- package/core/audits/long-tasks.d.ts +1 -1
- package/core/audits/long-tasks.js +1 -1
- package/core/audits/prioritize-lcp-image.d.ts +1 -1
- package/core/computed/document-urls.js +3 -2
- package/core/computed/load-simulator.d.ts +4 -4
- package/core/computed/load-simulator.js +3 -3
- package/core/computed/main-resource.js +3 -2
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +4 -4
- package/core/computed/metrics/lantern-first-contentful-paint.js +3 -3
- package/core/computed/metrics/lantern-interactive.d.ts +4 -4
- package/core/computed/metrics/lantern-interactive.js +3 -3
- package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +4 -4
- package/core/computed/metrics/lantern-largest-contentful-paint.js +3 -3
- package/core/computed/metrics/lantern-max-potential-fid.d.ts +4 -4
- package/core/computed/metrics/lantern-max-potential-fid.js +3 -3
- package/core/computed/metrics/lantern-metric.d.ts +4 -3
- package/core/computed/metrics/lantern-metric.js +4 -4
- package/core/computed/metrics/lantern-speed-index.d.ts +4 -4
- package/core/computed/metrics/lantern-speed-index.js +3 -3
- package/core/computed/metrics/lantern-total-blocking-time.d.ts +4 -4
- package/core/computed/metrics/lantern-total-blocking-time.js +3 -3
- package/core/computed/metrics/total-blocking-time.js +3 -1
- package/core/computed/network-analysis.js +2 -2
- package/core/computed/page-dependency-graph.d.ts +2 -2
- package/core/computed/page-dependency-graph.js +6 -6
- package/core/computed/tbt-impact-tasks.js +3 -2
- package/core/config/constants.js +1 -1
- package/core/lib/asset-saver.js +2 -2
- package/core/lib/lantern/{base-node.d.ts → BaseNode.d.ts} +7 -9
- package/core/lib/lantern/{base-node.js → BaseNode.js} +6 -6
- package/core/lib/lantern/BaseNode.test.js +385 -0
- package/core/lib/lantern/{cpu-node.d.ts → CpuNode.d.ts} +8 -9
- package/core/lib/lantern/{cpu-node.js → CpuNode.js} +4 -5
- package/core/lib/lantern/{lantern-error.d.ts → LanternError.d.ts} +1 -1
- package/core/lib/lantern/{metric.d.ts → Metric.d.ts} +17 -17
- package/core/lib/lantern/{metric.js → Metric.js} +10 -12
- package/core/lib/lantern/{network-node.d.ts → NetworkNode.d.ts} +4 -5
- package/core/lib/lantern/{network-node.js → NetworkNode.js} +4 -5
- package/core/lib/lantern/{page-dependency-graph.d.ts → PageDependencyGraph.d.ts} +5 -5
- package/core/lib/lantern/{page-dependency-graph.js → PageDependencyGraph.js} +11 -10
- package/core/lib/lantern/PageDependencyGraph.test.js +654 -0
- package/core/lib/lantern/{tbt-utils.d.ts → TBTUtils.d.ts} +1 -1
- package/core/lib/lantern/TBTUtils.test.d.ts +2 -0
- package/core/lib/lantern/TBTUtils.test.js +130 -0
- package/core/lib/lantern/{trace-engine-computation-data.d.ts → TraceEngineComputationData.d.ts} +3 -3
- package/core/lib/lantern/{trace-engine-computation-data.js → TraceEngineComputationData.js} +5 -7
- package/core/lib/lantern/lantern.d.ts +16 -46
- package/core/lib/lantern/lantern.js +16 -40
- package/core/lib/lantern/metrics/{first-contentful-paint.d.ts → FirstContentfulPaint.d.ts} +11 -11
- package/core/lib/lantern/metrics/{first-contentful-paint.js → FirstContentfulPaint.js} +8 -10
- package/core/lib/lantern/metrics/FirstContentfulPaint.test.js +54 -0
- package/core/lib/lantern/metrics/{interactive.d.ts → Interactive.d.ts} +4 -5
- package/core/lib/lantern/metrics/{interactive.js → Interactive.js} +10 -13
- package/core/lib/lantern/metrics/Interactive.test.js +56 -0
- package/core/lib/lantern/metrics/{largest-contentful-paint.d.ts → LargestContentfulPaint.d.ts} +5 -6
- package/core/lib/lantern/metrics/{largest-contentful-paint.js → LargestContentfulPaint.js} +9 -12
- package/core/lib/lantern/metrics/LargestContentfulPaint.test.js +42 -0
- package/core/lib/lantern/metrics/{max-potential-fid.d.ts → MaxPotentialFID.d.ts} +5 -6
- package/core/lib/lantern/metrics/{max-potential-fid.js → MaxPotentialFID.js} +8 -9
- package/core/lib/lantern/metrics/MetricTestUtils.d.ts +19 -0
- package/core/lib/lantern/metrics/MetricTestUtils.js +48 -0
- package/core/lib/lantern/metrics/{speed-index.d.ts → SpeedIndex.d.ts} +5 -6
- package/core/lib/lantern/metrics/{speed-index.js → SpeedIndex.js} +7 -9
- package/core/lib/lantern/metrics/SpeedIndex.test.js +83 -0
- package/core/lib/lantern/metrics/{total-blocking-time.d.ts → TotalBlockingTime.d.ts} +5 -6
- package/core/lib/lantern/metrics/{total-blocking-time.js → TotalBlockingTime.js} +9 -12
- package/core/lib/lantern/metrics/__snapshots__/first-contentful-paint-test.js.snap +11 -0
- package/core/lib/lantern/metrics/__snapshots__/interactive-test.js.snap +17 -0
- package/core/lib/lantern/metrics/metrics.d.ts +9 -0
- package/core/lib/lantern/metrics/metrics.js +16 -0
- package/core/lib/lantern/{simulator/connection-pool.d.ts → simulation/ConnectionPool.d.ts} +5 -5
- package/core/lib/lantern/{simulator/connection-pool.js → simulation/ConnectionPool.js} +3 -3
- package/core/lib/lantern/simulation/ConnectionPool.test.js +195 -0
- package/core/lib/lantern/simulation/Constants.d.ts +52 -0
- package/core/lib/lantern/simulation/Constants.js +48 -0
- package/core/lib/lantern/{simulator/dns-cache.d.ts → simulation/DNSCache.d.ts} +2 -2
- package/core/lib/lantern/{simulator/dns-cache.js → simulation/DNSCache.js} +1 -1
- package/core/lib/lantern/simulation/DNSCache.test.js +72 -0
- package/core/lib/lantern/{simulator/network-analyzer.d.ts → simulation/NetworkAnalyzer.d.ts} +2 -2
- package/core/lib/lantern/{simulator/network-analyzer.js → simulation/NetworkAnalyzer.js} +1 -1
- package/core/lib/lantern/simulation/NetworkAnalyzer.test.js +475 -0
- package/core/lib/lantern/{simulator/simulator-timing-map.d.ts → simulation/SimulationTimingMap.d.ts} +7 -7
- package/core/lib/lantern/{simulator/simulator-timing-map.js → simulation/SimulationTimingMap.js} +5 -6
- package/core/lib/lantern/{simulator/simulator.d.ts → simulation/Simulator.d.ts} +13 -15
- package/core/lib/lantern/{simulator/simulator.js → simulation/Simulator.js} +25 -26
- package/core/lib/lantern/simulation/Simulator.test.js +434 -0
- package/core/lib/lantern/simulation/TCPConnection.test.d.ts +2 -0
- package/core/lib/lantern/simulation/TCPConnection.test.js +374 -0
- package/core/lib/lantern/{simulator/tcp-connection.d.ts → simulation/TcpConnection.d.ts} +2 -2
- package/core/lib/lantern/{simulator/tcp-connection.js → simulation/TcpConnection.js} +1 -1
- package/core/lib/lantern/simulation/simulation.d.ts +21 -0
- package/core/lib/lantern/simulation/simulation.js +28 -0
- package/core/lib/lantern/types/lantern.d.ts +5 -5
- package/core/lib/lantern-trace-saver.d.ts +4 -4
- package/core/lib/lantern-trace-saver.js +2 -2
- package/core/lib/navigation-error.js +3 -3
- package/core/lib/network-recorder.js +2 -2
- package/core/lib/network-request.d.ts +1 -1
- package/core/lib/network-request.js +1 -1
- package/package.json +2 -2
- package/tsconfig.json +10 -8
- package/types/artifacts.d.ts +3 -4
- package/types/gatherer.d.ts +1 -1
- /package/core/lib/lantern/{lantern-error.js → LanternError.js} +0 -0
- /package/core/lib/lantern/{tbt-utils.js → TBTUtils.js} +0 -0
|
@@ -4,21 +4,20 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import * as Lantern from '../
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/** @typedef {import('../
|
|
18
|
-
/** @typedef {import('../
|
|
19
|
-
/** @typedef {import('
|
|
20
|
-
/** @typedef {import('./
|
|
21
|
-
/** @typedef {import('./simulator-timing-map.js').ConnectionTiming} ConnectionTiming */
|
|
7
|
+
import * as Lantern from '../lantern.js';
|
|
8
|
+
import {TcpConnection} from './TcpConnection.js';
|
|
9
|
+
import {ConnectionPool} from './ConnectionPool.js';
|
|
10
|
+
import {DNSCache} from './DNSCache.js';
|
|
11
|
+
import {SimulatorTimingMap} from './SimulationTimingMap.js';
|
|
12
|
+
|
|
13
|
+
const Constants = Lantern.Simulation.Constants;
|
|
14
|
+
const defaultThrottling = Constants.throttling.mobileSlow4G;
|
|
15
|
+
|
|
16
|
+
/** @typedef {import('../BaseNode.js').Node} Node */
|
|
17
|
+
/** @typedef {import('../NetworkNode.js').NetworkNode} NetworkNode */
|
|
18
|
+
/** @typedef {import('../CpuNode.js').CPUNode} CpuNode */
|
|
19
|
+
/** @typedef {import('./SimulationTimingMap.js').CpuNodeTimingComplete | import('./SimulationTimingMap.js').NetworkNodeTimingComplete} CompleteNodeTiming */
|
|
20
|
+
/** @typedef {import('./SimulationTimingMap.js').ConnectionTiming} ConnectionTiming */
|
|
22
21
|
|
|
23
22
|
// see https://cs.chromium.org/search/?q=kDefaultMaxNumDelayableRequestsPerClient&sq=package:chromium&type=cs
|
|
24
23
|
const DEFAULT_MAXIMUM_CONCURRENT_REQUESTS = 10;
|
|
@@ -82,10 +81,10 @@ class Simulator {
|
|
|
82
81
|
case 'devtools':
|
|
83
82
|
if (throttling) {
|
|
84
83
|
options.rtt =
|
|
85
|
-
throttling.requestLatencyMs /
|
|
84
|
+
throttling.requestLatencyMs / Constants.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR;
|
|
86
85
|
options.throughput =
|
|
87
86
|
throttling.downloadThroughputKbps * 1024 /
|
|
88
|
-
|
|
87
|
+
Constants.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR;
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
options.cpuSlowdownMultiplier = 1;
|
|
@@ -162,7 +161,7 @@ class Simulator {
|
|
|
162
161
|
/** @type {Lantern.NetworkRequest[]} */
|
|
163
162
|
const records = [];
|
|
164
163
|
graph.getRootNode().traverse(node => {
|
|
165
|
-
if (node.type === BaseNode.TYPES.NETWORK) {
|
|
164
|
+
if (node.type === Lantern.BaseNode.TYPES.NETWORK) {
|
|
166
165
|
records.push(node.request);
|
|
167
166
|
}
|
|
168
167
|
});
|
|
@@ -268,7 +267,7 @@ class Simulator {
|
|
|
268
267
|
* @param {number} totalElapsedTime
|
|
269
268
|
*/
|
|
270
269
|
_startNodeIfPossible(node, totalElapsedTime) {
|
|
271
|
-
if (node.type === BaseNode.TYPES.CPU) {
|
|
270
|
+
if (node.type === Lantern.BaseNode.TYPES.CPU) {
|
|
272
271
|
// Start a CPU task if there's no other CPU task in process
|
|
273
272
|
if (this._numberInProgress(node.type) === 0) {
|
|
274
273
|
this._markNodeAsInProgress(node, totalElapsedTime);
|
|
@@ -277,7 +276,7 @@ class Simulator {
|
|
|
277
276
|
return;
|
|
278
277
|
}
|
|
279
278
|
|
|
280
|
-
if (node.type !== BaseNode.TYPES.NETWORK) throw new Error('Unsupported');
|
|
279
|
+
if (node.type !== Lantern.BaseNode.TYPES.NETWORK) throw new Error('Unsupported');
|
|
281
280
|
|
|
282
281
|
// If a network request is connectionless, we can always start it, so skip the connection checks
|
|
283
282
|
if (!node.isConnectionless) {
|
|
@@ -296,7 +295,7 @@ class Simulator {
|
|
|
296
295
|
* currently in flight.
|
|
297
296
|
*/
|
|
298
297
|
_updateNetworkCapacity() {
|
|
299
|
-
const inFlight = this._numberInProgress(BaseNode.TYPES.NETWORK);
|
|
298
|
+
const inFlight = this._numberInProgress(Lantern.BaseNode.TYPES.NETWORK);
|
|
300
299
|
if (inFlight === 0) return;
|
|
301
300
|
|
|
302
301
|
for (const connection of this._connectionPool.connectionsInUse()) {
|
|
@@ -310,9 +309,9 @@ class Simulator {
|
|
|
310
309
|
* @return {number}
|
|
311
310
|
*/
|
|
312
311
|
_estimateTimeRemaining(node) {
|
|
313
|
-
if (node.type === BaseNode.TYPES.CPU) {
|
|
312
|
+
if (node.type === Lantern.BaseNode.TYPES.CPU) {
|
|
314
313
|
return this._estimateCPUTimeRemaining(node);
|
|
315
|
-
} else if (node.type === BaseNode.TYPES.NETWORK) {
|
|
314
|
+
} else if (node.type === Lantern.BaseNode.TYPES.NETWORK) {
|
|
316
315
|
return this._estimateNetworkTimeRemaining(node);
|
|
317
316
|
} else {
|
|
318
317
|
throw new Error('Unsupported');
|
|
@@ -401,13 +400,13 @@ class Simulator {
|
|
|
401
400
|
const timingData = this._nodeTimings.getInProgress(node);
|
|
402
401
|
const isFinished = timingData.estimatedTimeElapsed === timePeriodLength;
|
|
403
402
|
|
|
404
|
-
if (node.type === BaseNode.TYPES.CPU || node.isConnectionless) {
|
|
403
|
+
if (node.type === Lantern.BaseNode.TYPES.CPU || node.isConnectionless) {
|
|
405
404
|
return isFinished
|
|
406
405
|
? this._markNodeAsComplete(node, totalElapsedTime)
|
|
407
406
|
: (timingData.timeElapsed += timePeriodLength);
|
|
408
407
|
}
|
|
409
408
|
|
|
410
|
-
if (node.type !== BaseNode.TYPES.NETWORK) throw new Error('Unsupported');
|
|
409
|
+
if (node.type !== Lantern.BaseNode.TYPES.NETWORK) throw new Error('Unsupported');
|
|
411
410
|
if (!('bytesDownloaded' in timingData)) throw new Error('Invalid timing data');
|
|
412
411
|
|
|
413
412
|
const request = node.request;
|
|
@@ -488,7 +487,7 @@ class Simulator {
|
|
|
488
487
|
* @return {Lantern.Simulation.Result<T>}
|
|
489
488
|
*/
|
|
490
489
|
simulate(graph, options) {
|
|
491
|
-
if (BaseNode.hasCycle(graph)) {
|
|
490
|
+
if (Lantern.BaseNode.hasCycle(graph)) {
|
|
492
491
|
throw new Error('Cannot simulate graph with cycle');
|
|
493
492
|
}
|
|
494
493
|
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2017 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import assert from 'assert/strict';
|
|
8
|
+
|
|
9
|
+
import * as Lantern from '../lantern.js';
|
|
10
|
+
import {readJson} from '../../../test/test-utils.js';
|
|
11
|
+
import {runTraceEngine} from '../../../test/lib/lantern/metrics/MetricTestUtils.js';
|
|
12
|
+
|
|
13
|
+
const {NetworkNode, CPUNode} = Lantern;
|
|
14
|
+
const {Simulator, DNSCache} = Lantern.Simulation;
|
|
15
|
+
|
|
16
|
+
const pwaTrace = readJson('../../../fixtures/artifacts/progressive-app/trace.json', import.meta);
|
|
17
|
+
|
|
18
|
+
let nextRequestId = 1;
|
|
19
|
+
let nextTid = 1;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Lantern.Trace} trace
|
|
23
|
+
*/
|
|
24
|
+
async function createGraph(trace) {
|
|
25
|
+
const traceEngineData = await runTraceEngine(trace.traceEvents);
|
|
26
|
+
const requests =
|
|
27
|
+
Lantern.TraceEngineComputationData.createNetworkRequests(trace, traceEngineData);
|
|
28
|
+
return Lantern.TraceEngineComputationData.createGraph(requests, trace, traceEngineData);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function request(opts) {
|
|
32
|
+
const scheme = opts.scheme || 'http';
|
|
33
|
+
const url = `${scheme}://example.com`;
|
|
34
|
+
const rendererStartTime = opts.startTime;
|
|
35
|
+
const networkEndTime = opts.endTime;
|
|
36
|
+
delete opts.startTime;
|
|
37
|
+
delete opts.endTime;
|
|
38
|
+
|
|
39
|
+
return Object.assign({
|
|
40
|
+
requestId: opts.requestId || nextRequestId++,
|
|
41
|
+
url,
|
|
42
|
+
transferSize: opts.transferSize || 1000,
|
|
43
|
+
protocol: scheme,
|
|
44
|
+
parsedURL: {scheme, host: 'example.com', securityOrigin: url},
|
|
45
|
+
timing: opts.timing,
|
|
46
|
+
rendererStartTime,
|
|
47
|
+
networkEndTime,
|
|
48
|
+
}, opts);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cpuTask({tid, ts, duration}) {
|
|
52
|
+
tid = tid || nextTid++;
|
|
53
|
+
ts = ts || 0;
|
|
54
|
+
const dur = ((duration || 0) * 1000) / 5;
|
|
55
|
+
return {tid, ts, dur};
|
|
56
|
+
}
|
|
57
|
+
describe('DependencyGraph/Simulator', () => {
|
|
58
|
+
// Insulate the simulator tests from DNS multiplier changes
|
|
59
|
+
let originalDNSMultiplier;
|
|
60
|
+
|
|
61
|
+
before(() => {
|
|
62
|
+
originalDNSMultiplier = DNSCache.RTT_MULTIPLIER;
|
|
63
|
+
DNSCache.RTT_MULTIPLIER = 1;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
after(() => {
|
|
67
|
+
DNSCache.RTT_MULTIPLIER = originalDNSMultiplier;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('.simulate', () => {
|
|
71
|
+
const serverResponseTimeByOrigin = new Map([['http://example.com', 500]]);
|
|
72
|
+
|
|
73
|
+
function assertNodeTiming(result, node, assertions) {
|
|
74
|
+
const timing = result.nodeTimings.get(node);
|
|
75
|
+
assert.ok(timing, 'missing node timing information');
|
|
76
|
+
Object.keys(assertions).forEach(key => {
|
|
77
|
+
assert.equal(timing[key], assertions[key]);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
it('should simulate basic network graphs', () => {
|
|
82
|
+
const rootNode = new NetworkNode(request({}));
|
|
83
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
84
|
+
const result = simulator.simulate(rootNode);
|
|
85
|
+
// should be 3 RTTs and 500ms for the server response time
|
|
86
|
+
assert.equal(result.timeInMs, 450 + 500);
|
|
87
|
+
assertNodeTiming(result, rootNode, {startTime: 0, endTime: 950});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should simulate basic mixed graphs', () => {
|
|
91
|
+
const rootNode = new NetworkNode(request({}));
|
|
92
|
+
const cpuNode = new CPUNode(cpuTask({duration: 200}));
|
|
93
|
+
cpuNode.addDependency(rootNode);
|
|
94
|
+
|
|
95
|
+
const simulator = new Simulator({
|
|
96
|
+
serverResponseTimeByOrigin,
|
|
97
|
+
cpuSlowdownMultiplier: 5,
|
|
98
|
+
});
|
|
99
|
+
const result = simulator.simulate(rootNode);
|
|
100
|
+
// should be 3 RTTs and 500ms for the server response time + 200 CPU
|
|
101
|
+
assert.equal(result.timeInMs, 450 + 500 + 200);
|
|
102
|
+
assertNodeTiming(result, rootNode, {startTime: 0, endTime: 950});
|
|
103
|
+
assertNodeTiming(result, cpuNode, {startTime: 950, endTime: 1150});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should simulate basic network waterfall graphs', () => {
|
|
107
|
+
const nodeA = new NetworkNode(request({startTime: 0, endTime: 1}));
|
|
108
|
+
const nodeB = new NetworkNode(request({startTime: 0, endTime: 3}));
|
|
109
|
+
const nodeC = new NetworkNode(request({startTime: 0, endTime: 5}));
|
|
110
|
+
const nodeD = new NetworkNode(request({startTime: 0, endTime: 7}));
|
|
111
|
+
|
|
112
|
+
nodeA.addDependent(nodeB);
|
|
113
|
+
nodeB.addDependent(nodeC);
|
|
114
|
+
nodeC.addDependent(nodeD);
|
|
115
|
+
|
|
116
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
117
|
+
const result = simulator.simulate(nodeA);
|
|
118
|
+
// should be 950ms for A, 650ms each for B, C, D (no DNS and one-way connection)
|
|
119
|
+
assert.equal(result.timeInMs, 2900);
|
|
120
|
+
assertNodeTiming(result, nodeA, {startTime: 0, endTime: 950});
|
|
121
|
+
assertNodeTiming(result, nodeB, {startTime: 950, endTime: 1600});
|
|
122
|
+
assertNodeTiming(result, nodeC, {startTime: 1600, endTime: 2250});
|
|
123
|
+
assertNodeTiming(result, nodeD, {startTime: 2250, endTime: 2900});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should simulate cached network graphs', () => {
|
|
127
|
+
const nodeA = new NetworkNode(request({startTime: 0, endTime: 1,
|
|
128
|
+
fromDiskCache: true}));
|
|
129
|
+
const nodeB = new NetworkNode(request({startTime: 0, endTime: 3,
|
|
130
|
+
fromDiskCache: true}));
|
|
131
|
+
nodeA.addDependent(nodeB);
|
|
132
|
+
|
|
133
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
134
|
+
const result = simulator.simulate(nodeA);
|
|
135
|
+
// should be ~8ms each for A, B
|
|
136
|
+
assert.equal(result.timeInMs, 16);
|
|
137
|
+
assertNodeTiming(result, nodeA, {startTime: 0, endTime: 8});
|
|
138
|
+
assertNodeTiming(result, nodeB, {startTime: 8, endTime: 16});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should simulate data URL network graphs', () => {
|
|
142
|
+
const url = 'data:image/jpeg;base64,foobar';
|
|
143
|
+
const protocol = 'data';
|
|
144
|
+
const parsedURL = {scheme: 'data', host: '', securityOrigin: 'null'};
|
|
145
|
+
const nodeA = new NetworkNode(request({startTime: 0, endTime: 1, url,
|
|
146
|
+
parsedURL, protocol}));
|
|
147
|
+
const nodeB = new NetworkNode(request({startTime: 0, endTime: 3, url,
|
|
148
|
+
parsedURL, protocol, resourceSize: 1024 * 1024}));
|
|
149
|
+
nodeA.addDependent(nodeB);
|
|
150
|
+
|
|
151
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
152
|
+
const result = simulator.simulate(nodeA);
|
|
153
|
+
|
|
154
|
+
// should be ~2ms for A (resourceSize 0), ~12ms for B (resourceSize 1MB)
|
|
155
|
+
assert.equal(result.timeInMs, 14);
|
|
156
|
+
assertNodeTiming(result, nodeA, {startTime: 0, endTime: 2});
|
|
157
|
+
assertNodeTiming(result, nodeB, {startTime: 2, endTime: 14});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should simulate basic CPU queue graphs', () => {
|
|
161
|
+
const nodeA = new NetworkNode(request({}));
|
|
162
|
+
const nodeB = new CPUNode(cpuTask({duration: 100}));
|
|
163
|
+
const nodeC = new CPUNode(cpuTask({duration: 600}));
|
|
164
|
+
const nodeD = new CPUNode(cpuTask({duration: 300}));
|
|
165
|
+
|
|
166
|
+
nodeA.addDependent(nodeB);
|
|
167
|
+
nodeA.addDependent(nodeC);
|
|
168
|
+
nodeA.addDependent(nodeD);
|
|
169
|
+
|
|
170
|
+
const simulator = new Simulator({
|
|
171
|
+
serverResponseTimeByOrigin,
|
|
172
|
+
cpuSlowdownMultiplier: 5,
|
|
173
|
+
});
|
|
174
|
+
const result = simulator.simulate(nodeA);
|
|
175
|
+
// should be 800ms A, then 1000 ms total for B, C, D in serial
|
|
176
|
+
assert.equal(result.timeInMs, 1950);
|
|
177
|
+
assertNodeTiming(result, nodeA, {startTime: 0, endTime: 950});
|
|
178
|
+
assertNodeTiming(result, nodeB, {startTime: 950, endTime: 1050});
|
|
179
|
+
assertNodeTiming(result, nodeC, {startTime: 1050, endTime: 1650});
|
|
180
|
+
assertNodeTiming(result, nodeD, {startTime: 1650, endTime: 1950});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should simulate basic network waterfall graphs with CPU', () => {
|
|
184
|
+
const nodeA = new NetworkNode(request({}));
|
|
185
|
+
const nodeB = new NetworkNode(request({}));
|
|
186
|
+
const nodeC = new NetworkNode(request({}));
|
|
187
|
+
const nodeD = new NetworkNode(request({}));
|
|
188
|
+
const nodeE = new CPUNode(cpuTask({duration: 1000}));
|
|
189
|
+
const nodeF = new CPUNode(cpuTask({duration: 1000}));
|
|
190
|
+
|
|
191
|
+
nodeA.addDependent(nodeB);
|
|
192
|
+
nodeB.addDependent(nodeC);
|
|
193
|
+
nodeB.addDependent(nodeE); // finishes 350 ms after C
|
|
194
|
+
nodeC.addDependent(nodeD);
|
|
195
|
+
nodeC.addDependent(nodeF); // finishes 700 ms after D
|
|
196
|
+
|
|
197
|
+
const simulator = new Simulator({
|
|
198
|
+
serverResponseTimeByOrigin,
|
|
199
|
+
cpuSlowdownMultiplier: 5,
|
|
200
|
+
});
|
|
201
|
+
const result = simulator.simulate(nodeA);
|
|
202
|
+
// should be 950ms for A, 650ms each for B, C, D, with F finishing 700 ms after D
|
|
203
|
+
assert.equal(result.timeInMs, 3600);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should simulate basic parallel requests', () => {
|
|
207
|
+
const nodeA = new NetworkNode(request({}));
|
|
208
|
+
const nodeB = new NetworkNode(request({}));
|
|
209
|
+
const nodeC = new NetworkNode(request({transferSize: 15000}));
|
|
210
|
+
const nodeD = new NetworkNode(request({}));
|
|
211
|
+
|
|
212
|
+
nodeA.addDependent(nodeB);
|
|
213
|
+
nodeA.addDependent(nodeC);
|
|
214
|
+
nodeA.addDependent(nodeD);
|
|
215
|
+
|
|
216
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
217
|
+
const result = simulator.simulate(nodeA);
|
|
218
|
+
// should be 950ms for A and 950ms for C (2 round trips of downloading, but no DNS)
|
|
219
|
+
assert.equal(result.timeInMs, 950 + 950);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should make connections in parallel', () => {
|
|
223
|
+
const nodeA = new NetworkNode(request({startTime: 0, networkRequestTime: 0, endTime: 1}));
|
|
224
|
+
const nodeB = new NetworkNode(request({startTime: 2, networkRequestTime: 2, endTime: 3}));
|
|
225
|
+
const nodeC = new NetworkNode(request({startTime: 2, networkRequestTime: 2, endTime: 5}));
|
|
226
|
+
const nodeD = new NetworkNode(request({startTime: 2, networkRequestTime: 2, endTime: 7}));
|
|
227
|
+
|
|
228
|
+
nodeA.addDependent(nodeB);
|
|
229
|
+
nodeA.addDependent(nodeC);
|
|
230
|
+
nodeA.addDependent(nodeD);
|
|
231
|
+
|
|
232
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
233
|
+
const result = simulator.simulate(nodeA);
|
|
234
|
+
// should be 950ms for A, 650ms for B reusing connection, 800ms for C and D in parallel.
|
|
235
|
+
assert.equal(result.timeInMs, 950 + 800);
|
|
236
|
+
assertNodeTiming(result, nodeA, {startTime: 0, endTime: 950});
|
|
237
|
+
assertNodeTiming(result, nodeB, {startTime: 950, endTime: 1600});
|
|
238
|
+
assertNodeTiming(result, nodeC, {startTime: 950, endTime: 1750});
|
|
239
|
+
assertNodeTiming(result, nodeD, {startTime: 950, endTime: 1750});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should adjust throughput based on number of requests', () => {
|
|
243
|
+
const nodeA = new NetworkNode(request({}));
|
|
244
|
+
const nodeB = new NetworkNode(request({}));
|
|
245
|
+
const nodeC = new NetworkNode(request({transferSize: 14000}));
|
|
246
|
+
const nodeD = new NetworkNode(request({}));
|
|
247
|
+
|
|
248
|
+
nodeA.addDependent(nodeB);
|
|
249
|
+
nodeA.addDependent(nodeC);
|
|
250
|
+
nodeA.addDependent(nodeD);
|
|
251
|
+
|
|
252
|
+
// 80 kbps while all 3 download at 150ms/RT = ~1460 bytes/RT
|
|
253
|
+
// 240 kbps while the last one finishes at 150ms/RT = ~4380 bytes/RT
|
|
254
|
+
// ~14000 bytes = 5 RTs
|
|
255
|
+
// 1 RT 80 kbps b/c its shared
|
|
256
|
+
// 1 RT 80 kbps b/c it needs to grow congestion window from being shared
|
|
257
|
+
// 1 RT 160 kbps b/c TCP
|
|
258
|
+
// 2 RT 240 kbps b/c throughput cap
|
|
259
|
+
const simulator = new Simulator({serverResponseTimeByOrigin, throughput: 240000});
|
|
260
|
+
const result = simulator.simulate(nodeA);
|
|
261
|
+
// should be 950ms for A and 1400ms for C (5 round trips of downloading)
|
|
262
|
+
assert.equal(result.timeInMs, 950 + (150 + 750 + 500));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should start network requests in startTime order', () => {
|
|
266
|
+
const rootNode = new NetworkNode(request({startTime: 0, endTime: 0.05,
|
|
267
|
+
connectionId: 1}));
|
|
268
|
+
const imageNodes = [
|
|
269
|
+
new NetworkNode(request({startTime: 5})),
|
|
270
|
+
new NetworkNode(request({startTime: 4})),
|
|
271
|
+
new NetworkNode(request({startTime: 3})),
|
|
272
|
+
new NetworkNode(request({startTime: 2})),
|
|
273
|
+
new NetworkNode(request({startTime: 1})),
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
for (const imageNode of imageNodes) {
|
|
277
|
+
imageNode.request.connectionReused = true;
|
|
278
|
+
imageNode.request.connectionId = 1;
|
|
279
|
+
rootNode.addDependent(imageNode);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const simulator = new Simulator({serverResponseTimeByOrigin, maximumConcurrentRequests: 1});
|
|
283
|
+
const result = simulator.simulate(rootNode);
|
|
284
|
+
|
|
285
|
+
// should be 3 RTs + SRT for rootNode (950ms)
|
|
286
|
+
// should be 1 RT + SRT for image nodes in observed order (650ms)
|
|
287
|
+
assertNodeTiming(result, rootNode, {startTime: 0, endTime: 950});
|
|
288
|
+
assertNodeTiming(result, imageNodes[4], {startTime: 950, endTime: 1600});
|
|
289
|
+
assertNodeTiming(result, imageNodes[3], {startTime: 1600, endTime: 2250});
|
|
290
|
+
assertNodeTiming(result, imageNodes[2], {startTime: 2250, endTime: 2900});
|
|
291
|
+
assertNodeTiming(result, imageNodes[1], {startTime: 2900, endTime: 3550});
|
|
292
|
+
assertNodeTiming(result, imageNodes[0], {startTime: 3550, endTime: 4200});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should start network requests in priority order to break startTime ties', () => {
|
|
296
|
+
const rootNode = new NetworkNode(request({startTime: 0, endTime: 0.05,
|
|
297
|
+
connectionId: 1}));
|
|
298
|
+
const imageNodes = [
|
|
299
|
+
new NetworkNode(request({startTime: 0.1, priority: 'VeryLow'})),
|
|
300
|
+
new NetworkNode(request({startTime: 0.2, priority: 'Low'})),
|
|
301
|
+
new NetworkNode(request({startTime: 0.3, priority: 'Medium'})),
|
|
302
|
+
new NetworkNode(request({startTime: 0.4, priority: 'High'})),
|
|
303
|
+
new NetworkNode(request({startTime: 0.5, priority: 'VeryHigh'})),
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
for (const imageNode of imageNodes) {
|
|
307
|
+
imageNode.request.connectionReused = true;
|
|
308
|
+
imageNode.request.connectionId = 1;
|
|
309
|
+
rootNode.addDependent(imageNode);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const simulator = new Simulator({serverResponseTimeByOrigin, maximumConcurrentRequests: 1});
|
|
313
|
+
const result = simulator.simulate(rootNode);
|
|
314
|
+
|
|
315
|
+
// should be 3 RTs + SRT for rootNode (950ms)
|
|
316
|
+
// should be 1 RT + SRT for image nodes in priority order (650ms)
|
|
317
|
+
assertNodeTiming(result, rootNode, {startTime: 0, endTime: 950});
|
|
318
|
+
assertNodeTiming(result, imageNodes[4], {startTime: 950, endTime: 1600});
|
|
319
|
+
assertNodeTiming(result, imageNodes[3], {startTime: 1600, endTime: 2250});
|
|
320
|
+
assertNodeTiming(result, imageNodes[2], {startTime: 2250, endTime: 2900});
|
|
321
|
+
assertNodeTiming(result, imageNodes[1], {startTime: 2900, endTime: 3550});
|
|
322
|
+
assertNodeTiming(result, imageNodes[0], {startTime: 3550, endTime: 4200});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should simulate two graphs in a row', () => {
|
|
326
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
327
|
+
|
|
328
|
+
const nodeA = new NetworkNode(request({}));
|
|
329
|
+
const nodeB = new NetworkNode(request({}));
|
|
330
|
+
const nodeC = new NetworkNode(request({transferSize: 15000}));
|
|
331
|
+
const nodeD = new NetworkNode(request({}));
|
|
332
|
+
|
|
333
|
+
nodeA.addDependent(nodeB);
|
|
334
|
+
nodeA.addDependent(nodeC);
|
|
335
|
+
nodeA.addDependent(nodeD);
|
|
336
|
+
|
|
337
|
+
const resultA = simulator.simulate(nodeA);
|
|
338
|
+
// should be 950ms for A and 950ms for C (2 round trips of downloading, no DNS)
|
|
339
|
+
assert.equal(resultA.timeInMs, 950 + 950);
|
|
340
|
+
|
|
341
|
+
const nodeE = new NetworkNode(request({}));
|
|
342
|
+
const nodeF = new NetworkNode(request({}));
|
|
343
|
+
const nodeG = new NetworkNode(request({}));
|
|
344
|
+
|
|
345
|
+
nodeE.addDependent(nodeF);
|
|
346
|
+
nodeE.addDependent(nodeG);
|
|
347
|
+
|
|
348
|
+
const resultB = simulator.simulate(nodeE);
|
|
349
|
+
// should be 950ms for E and 800ms for F/G
|
|
350
|
+
assert.equal(resultB.timeInMs, 950 + 800);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should maximize throughput with H2', () => {
|
|
354
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
355
|
+
const connectionDefaults = {protocol: 'h2', connectionId: 1};
|
|
356
|
+
const nodeA = new NetworkNode(request({startTime: 0, endTime: 1,
|
|
357
|
+
...connectionDefaults}));
|
|
358
|
+
const nodeB = new NetworkNode(request({startTime: 1, endTime: 2,
|
|
359
|
+
...connectionDefaults}));
|
|
360
|
+
const nodeC = new NetworkNode(request({startTime: 2, endTime: 3,
|
|
361
|
+
...connectionDefaults}));
|
|
362
|
+
const nodeD = new NetworkNode(request({startTime: 3, endTime: 4,
|
|
363
|
+
...connectionDefaults}));
|
|
364
|
+
|
|
365
|
+
nodeA.addDependent(nodeB);
|
|
366
|
+
nodeB.addDependent(nodeC);
|
|
367
|
+
nodeB.addDependent(nodeD);
|
|
368
|
+
|
|
369
|
+
// Run two simulations:
|
|
370
|
+
// - The first with C & D in parallel.
|
|
371
|
+
// - The second with C & D in series.
|
|
372
|
+
// Under HTTP/2 simulation these should be equivalent, but definitely parallel
|
|
373
|
+
// shouldn't be slower.
|
|
374
|
+
const resultA = simulator.simulate(nodeA);
|
|
375
|
+
nodeC.addDependent(nodeD);
|
|
376
|
+
const resultB = simulator.simulate(nodeA);
|
|
377
|
+
expect(resultA.timeInMs).toBeLessThanOrEqual(resultB.timeInMs);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should throw (not hang) on graphs with cycles', () => {
|
|
381
|
+
const rootNode = new NetworkNode(request({}));
|
|
382
|
+
const depNode = new NetworkNode(request({}));
|
|
383
|
+
rootNode.addDependency(depNode);
|
|
384
|
+
depNode.addDependency(rootNode);
|
|
385
|
+
|
|
386
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
387
|
+
assert.throws(() => simulator.simulate(rootNode), /cycle/);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('on a real trace', () => {
|
|
391
|
+
const trace = pwaTrace;
|
|
392
|
+
|
|
393
|
+
it('should compute a timeInMs', async () => {
|
|
394
|
+
const graph = await createGraph(trace);
|
|
395
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
396
|
+
const result = simulator.simulate(graph);
|
|
397
|
+
expect(result.timeInMs).toBeGreaterThan(100);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should sort the task event times', async () => {
|
|
401
|
+
const graph = await createGraph(trace);
|
|
402
|
+
const simulator = new Simulator({serverResponseTimeByOrigin});
|
|
403
|
+
const result = simulator.simulate(graph);
|
|
404
|
+
const nodeTimings = Array.from(result.nodeTimings.entries());
|
|
405
|
+
|
|
406
|
+
for (let i = 1; i < nodeTimings.length; i++) {
|
|
407
|
+
const startTime = nodeTimings[i][1].startTime;
|
|
408
|
+
const previousStartTime = nodeTimings[i - 1][1].startTime;
|
|
409
|
+
expect(startTime).toBeGreaterThanOrEqual(previousStartTime);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('.simulateTimespan', () => {
|
|
416
|
+
it('calculates savings using throughput', () => {
|
|
417
|
+
const simulator = new Simulator({throughput: 1000, observedThroughput: 2000});
|
|
418
|
+
const wastedMs = simulator.computeWastedMsFromWastedBytes(500);
|
|
419
|
+
expect(wastedMs).toBeCloseTo(4000);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('falls back to observed throughput if throughput is 0', () => {
|
|
423
|
+
const simulator = new Simulator({throughput: 0, observedThroughput: 2000});
|
|
424
|
+
const wastedMs = simulator.computeWastedMsFromWastedBytes(500);
|
|
425
|
+
expect(wastedMs).toBeCloseTo(2000);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('returns 0 if throughput and observed throughput are 0', () => {
|
|
429
|
+
const simulator = new Simulator({throughput: 0, observedThroughput: 0});
|
|
430
|
+
const wastedMs = simulator.computeWastedMsFromWastedBytes(500);
|
|
431
|
+
expect(wastedMs).toEqual(0);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
});
|