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.
Files changed (111) hide show
  1. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +2 -2
  2. package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
  3. package/core/audits/byte-efficiency/render-blocking-resources.d.ts +3 -3
  4. package/core/audits/byte-efficiency/render-blocking-resources.js +4 -4
  5. package/core/audits/dobetterweb/uses-http2.d.ts +2 -2
  6. package/core/audits/dobetterweb/uses-http2.js +2 -2
  7. package/core/audits/long-tasks.d.ts +1 -1
  8. package/core/audits/long-tasks.js +1 -1
  9. package/core/audits/prioritize-lcp-image.d.ts +1 -1
  10. package/core/computed/document-urls.js +3 -2
  11. package/core/computed/load-simulator.d.ts +4 -4
  12. package/core/computed/load-simulator.js +3 -3
  13. package/core/computed/main-resource.js +3 -2
  14. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +4 -4
  15. package/core/computed/metrics/lantern-first-contentful-paint.js +3 -3
  16. package/core/computed/metrics/lantern-interactive.d.ts +4 -4
  17. package/core/computed/metrics/lantern-interactive.js +3 -3
  18. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +4 -4
  19. package/core/computed/metrics/lantern-largest-contentful-paint.js +3 -3
  20. package/core/computed/metrics/lantern-max-potential-fid.d.ts +4 -4
  21. package/core/computed/metrics/lantern-max-potential-fid.js +3 -3
  22. package/core/computed/metrics/lantern-metric.d.ts +4 -3
  23. package/core/computed/metrics/lantern-metric.js +4 -4
  24. package/core/computed/metrics/lantern-speed-index.d.ts +4 -4
  25. package/core/computed/metrics/lantern-speed-index.js +3 -3
  26. package/core/computed/metrics/lantern-total-blocking-time.d.ts +4 -4
  27. package/core/computed/metrics/lantern-total-blocking-time.js +3 -3
  28. package/core/computed/metrics/total-blocking-time.js +3 -1
  29. package/core/computed/network-analysis.js +2 -2
  30. package/core/computed/page-dependency-graph.d.ts +2 -2
  31. package/core/computed/page-dependency-graph.js +6 -6
  32. package/core/computed/tbt-impact-tasks.js +3 -2
  33. package/core/config/constants.js +1 -1
  34. package/core/lib/asset-saver.js +2 -2
  35. package/core/lib/lantern/{base-node.d.ts → BaseNode.d.ts} +7 -9
  36. package/core/lib/lantern/{base-node.js → BaseNode.js} +6 -6
  37. package/core/lib/lantern/BaseNode.test.js +385 -0
  38. package/core/lib/lantern/{cpu-node.d.ts → CpuNode.d.ts} +8 -9
  39. package/core/lib/lantern/{cpu-node.js → CpuNode.js} +4 -5
  40. package/core/lib/lantern/{lantern-error.d.ts → LanternError.d.ts} +1 -1
  41. package/core/lib/lantern/{metric.d.ts → Metric.d.ts} +17 -17
  42. package/core/lib/lantern/{metric.js → Metric.js} +10 -12
  43. package/core/lib/lantern/{network-node.d.ts → NetworkNode.d.ts} +4 -5
  44. package/core/lib/lantern/{network-node.js → NetworkNode.js} +4 -5
  45. package/core/lib/lantern/{page-dependency-graph.d.ts → PageDependencyGraph.d.ts} +5 -5
  46. package/core/lib/lantern/{page-dependency-graph.js → PageDependencyGraph.js} +11 -10
  47. package/core/lib/lantern/PageDependencyGraph.test.js +654 -0
  48. package/core/lib/lantern/{tbt-utils.d.ts → TBTUtils.d.ts} +1 -1
  49. package/core/lib/lantern/TBTUtils.test.d.ts +2 -0
  50. package/core/lib/lantern/TBTUtils.test.js +130 -0
  51. package/core/lib/lantern/{trace-engine-computation-data.d.ts → TraceEngineComputationData.d.ts} +3 -3
  52. package/core/lib/lantern/{trace-engine-computation-data.js → TraceEngineComputationData.js} +5 -7
  53. package/core/lib/lantern/lantern.d.ts +16 -46
  54. package/core/lib/lantern/lantern.js +16 -40
  55. package/core/lib/lantern/metrics/{first-contentful-paint.d.ts → FirstContentfulPaint.d.ts} +11 -11
  56. package/core/lib/lantern/metrics/{first-contentful-paint.js → FirstContentfulPaint.js} +8 -10
  57. package/core/lib/lantern/metrics/FirstContentfulPaint.test.js +54 -0
  58. package/core/lib/lantern/metrics/{interactive.d.ts → Interactive.d.ts} +4 -5
  59. package/core/lib/lantern/metrics/{interactive.js → Interactive.js} +10 -13
  60. package/core/lib/lantern/metrics/Interactive.test.js +56 -0
  61. package/core/lib/lantern/metrics/{largest-contentful-paint.d.ts → LargestContentfulPaint.d.ts} +5 -6
  62. package/core/lib/lantern/metrics/{largest-contentful-paint.js → LargestContentfulPaint.js} +9 -12
  63. package/core/lib/lantern/metrics/LargestContentfulPaint.test.js +42 -0
  64. package/core/lib/lantern/metrics/{max-potential-fid.d.ts → MaxPotentialFID.d.ts} +5 -6
  65. package/core/lib/lantern/metrics/{max-potential-fid.js → MaxPotentialFID.js} +8 -9
  66. package/core/lib/lantern/metrics/MetricTestUtils.d.ts +19 -0
  67. package/core/lib/lantern/metrics/MetricTestUtils.js +48 -0
  68. package/core/lib/lantern/metrics/{speed-index.d.ts → SpeedIndex.d.ts} +5 -6
  69. package/core/lib/lantern/metrics/{speed-index.js → SpeedIndex.js} +7 -9
  70. package/core/lib/lantern/metrics/SpeedIndex.test.js +83 -0
  71. package/core/lib/lantern/metrics/{total-blocking-time.d.ts → TotalBlockingTime.d.ts} +5 -6
  72. package/core/lib/lantern/metrics/{total-blocking-time.js → TotalBlockingTime.js} +9 -12
  73. package/core/lib/lantern/metrics/__snapshots__/first-contentful-paint-test.js.snap +11 -0
  74. package/core/lib/lantern/metrics/__snapshots__/interactive-test.js.snap +17 -0
  75. package/core/lib/lantern/metrics/metrics.d.ts +9 -0
  76. package/core/lib/lantern/metrics/metrics.js +16 -0
  77. package/core/lib/lantern/{simulator/connection-pool.d.ts → simulation/ConnectionPool.d.ts} +5 -5
  78. package/core/lib/lantern/{simulator/connection-pool.js → simulation/ConnectionPool.js} +3 -3
  79. package/core/lib/lantern/simulation/ConnectionPool.test.js +195 -0
  80. package/core/lib/lantern/simulation/Constants.d.ts +52 -0
  81. package/core/lib/lantern/simulation/Constants.js +48 -0
  82. package/core/lib/lantern/{simulator/dns-cache.d.ts → simulation/DNSCache.d.ts} +2 -2
  83. package/core/lib/lantern/{simulator/dns-cache.js → simulation/DNSCache.js} +1 -1
  84. package/core/lib/lantern/simulation/DNSCache.test.js +72 -0
  85. package/core/lib/lantern/{simulator/network-analyzer.d.ts → simulation/NetworkAnalyzer.d.ts} +2 -2
  86. package/core/lib/lantern/{simulator/network-analyzer.js → simulation/NetworkAnalyzer.js} +1 -1
  87. package/core/lib/lantern/simulation/NetworkAnalyzer.test.js +475 -0
  88. package/core/lib/lantern/{simulator/simulator-timing-map.d.ts → simulation/SimulationTimingMap.d.ts} +7 -7
  89. package/core/lib/lantern/{simulator/simulator-timing-map.js → simulation/SimulationTimingMap.js} +5 -6
  90. package/core/lib/lantern/{simulator/simulator.d.ts → simulation/Simulator.d.ts} +13 -15
  91. package/core/lib/lantern/{simulator/simulator.js → simulation/Simulator.js} +25 -26
  92. package/core/lib/lantern/simulation/Simulator.test.js +434 -0
  93. package/core/lib/lantern/simulation/TCPConnection.test.d.ts +2 -0
  94. package/core/lib/lantern/simulation/TCPConnection.test.js +374 -0
  95. package/core/lib/lantern/{simulator/tcp-connection.d.ts → simulation/TcpConnection.d.ts} +2 -2
  96. package/core/lib/lantern/{simulator/tcp-connection.js → simulation/TcpConnection.js} +1 -1
  97. package/core/lib/lantern/simulation/simulation.d.ts +21 -0
  98. package/core/lib/lantern/simulation/simulation.js +28 -0
  99. package/core/lib/lantern/types/lantern.d.ts +5 -5
  100. package/core/lib/lantern-trace-saver.d.ts +4 -4
  101. package/core/lib/lantern-trace-saver.js +2 -2
  102. package/core/lib/navigation-error.js +3 -3
  103. package/core/lib/network-recorder.js +2 -2
  104. package/core/lib/network-request.d.ts +1 -1
  105. package/core/lib/network-request.js +1 -1
  106. package/package.json +2 -2
  107. package/tsconfig.json +10 -8
  108. package/types/artifacts.d.ts +3 -4
  109. package/types/gatherer.d.ts +1 -1
  110. /package/core/lib/lantern/{lantern-error.js → LanternError.js} +0 -0
  111. /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 '../types/lantern.js';
8
- import {BaseNode} from '../base-node.js';
9
- import {TcpConnection} from './tcp-connection.js';
10
- import {ConnectionPool} from './connection-pool.js';
11
- import {DNSCache} from './dns-cache.js';
12
- import {SimulatorTimingMap} from './simulator-timing-map.js';
13
- import {constants} from '../lantern.js';
14
-
15
- const defaultThrottling = constants.throttling.mobileSlow4G;
16
-
17
- /** @typedef {import('../base-node.js').Node} Node */
18
- /** @typedef {import('../network-node.js').NetworkNode} NetworkNode */
19
- /** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
20
- /** @typedef {import('./simulator-timing-map.js').CpuNodeTimingComplete | import('./simulator-timing-map.js').NetworkNodeTimingComplete} CompleteNodeTiming */
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 / constants.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR;
84
+ throttling.requestLatencyMs / Constants.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR;
86
85
  options.throughput =
87
86
  throttling.downloadThroughputKbps * 1024 /
88
- constants.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR;
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
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TCPConnection.test.d.ts.map