core(simulator): clearer intermediate timing types (#11744)
This commit is contained in:
Родитель
c051eebe4f
Коммит
9aad172567
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* @license Copyright 2020 The Lighthouse Authors. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const BaseNode = require('../base-node.js');
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
*
|
||||
* This class encapsulates the type-related validation logic for moving timing information for nodes
|
||||
* through the different simulation phases. Methods here ensure that the invariants of simulation hold
|
||||
* as nodes are queued, partially simulated, and completed.
|
||||
*/
|
||||
|
||||
|
||||
/** @typedef {BaseNode.Node} Node */
|
||||
/** @typedef {import('../network-node')} NetworkNode */
|
||||
/** @typedef {import('../cpu-node')} CpuNode */
|
||||
|
||||
/**
|
||||
* @typedef NodeTimingComplete
|
||||
* @property {number} startTime
|
||||
* @property {number} endTime
|
||||
* @property {number} queuedTime Helpful for debugging.
|
||||
* @property {number} estimatedTimeElapsed
|
||||
* @property {number} timeElapsed
|
||||
* @property {number} timeElapsedOvershoot
|
||||
* @property {number} bytesDownloaded
|
||||
*/
|
||||
|
||||
/** @typedef {Pick<NodeTimingComplete, 'queuedTime'>} NodeTimingQueued */
|
||||
|
||||
/** @typedef {NodeTimingQueued & Pick<NodeTimingComplete, 'startTime'|'timeElapsed'>} CpuNodeTimingStarted */
|
||||
/** @typedef {CpuNodeTimingStarted & Pick<NodeTimingComplete, 'timeElapsedOvershoot'|'bytesDownloaded'>} NetworkNodeTimingStarted */
|
||||
|
||||
/** @typedef {CpuNodeTimingStarted & Pick<NodeTimingComplete, 'estimatedTimeElapsed'>} CpuNodeTimingInProgress */
|
||||
/** @typedef {NetworkNodeTimingStarted & Pick<NodeTimingComplete, 'estimatedTimeElapsed'>} NetworkNodeTimingInProgress */
|
||||
|
||||
/** @typedef {CpuNodeTimingInProgress & Pick<NodeTimingComplete, 'endTime'>} CpuNodeTimingComplete */
|
||||
/** @typedef {NetworkNodeTimingInProgress & Pick<NodeTimingComplete, 'endTime'>} NetworkNodeTimingComplete */
|
||||
|
||||
/** @typedef {NodeTimingQueued | CpuNodeTimingStarted | NetworkNodeTimingStarted | CpuNodeTimingInProgress | NetworkNodeTimingInProgress | CpuNodeTimingComplete | NetworkNodeTimingComplete} NodeTimingData */
|
||||
|
||||
class SimulatorTimingMap {
|
||||
constructor() {
|
||||
/** @type {Map<Node, NodeTimingData>} */
|
||||
this._nodeTimings = new Map();
|
||||
}
|
||||
|
||||
/** @return {Array<Node>} */
|
||||
getNodes() {
|
||||
return Array.from(this._nodeTimings.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {{queuedTime: number}} values
|
||||
*/
|
||||
setReadyToStart(node, values) {
|
||||
this._nodeTimings.set(node, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {{startTime: number}} values
|
||||
*/
|
||||
setInProgress(node, values) {
|
||||
const nodeTiming = {
|
||||
...this.getQueued(node),
|
||||
startTime: values.startTime,
|
||||
timeElapsed: 0,
|
||||
};
|
||||
|
||||
this._nodeTimings.set(
|
||||
node,
|
||||
node.type === BaseNode.TYPES.NETWORK
|
||||
? {...nodeTiming, timeElapsedOvershoot: 0, bytesDownloaded: 0}
|
||||
: nodeTiming
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {{endTime: number}} values
|
||||
*/
|
||||
setCompleted(node, values) {
|
||||
const nodeTiming = {
|
||||
...this.getInProgress(node),
|
||||
endTime: values.endTime,
|
||||
};
|
||||
|
||||
this._nodeTimings.set(node, nodeTiming);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CpuNode} node
|
||||
* @param {{timeElapsed: number}} values
|
||||
*/
|
||||
setCpu(node, values) {
|
||||
const nodeTiming = {
|
||||
...this.getCpuStarted(node),
|
||||
timeElapsed: values.timeElapsed,
|
||||
};
|
||||
|
||||
this._nodeTimings.set(node, nodeTiming);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CpuNode} node
|
||||
* @param {{estimatedTimeElapsed: number}} values
|
||||
*/
|
||||
setCpuEstimated(node, values) {
|
||||
const nodeTiming = {
|
||||
...this.getCpuStarted(node),
|
||||
estimatedTimeElapsed: values.estimatedTimeElapsed,
|
||||
};
|
||||
|
||||
this._nodeTimings.set(node, nodeTiming);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NetworkNode} node
|
||||
* @param {{timeElapsed: number, timeElapsedOvershoot: number, bytesDownloaded: number}} values
|
||||
*/
|
||||
setNetwork(node, values) {
|
||||
const nodeTiming = {
|
||||
...this.getNetworkStarted(node),
|
||||
timeElapsed: values.timeElapsed,
|
||||
timeElapsedOvershoot: values.timeElapsedOvershoot,
|
||||
bytesDownloaded: values.bytesDownloaded,
|
||||
};
|
||||
|
||||
this._nodeTimings.set(node, nodeTiming);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NetworkNode} node
|
||||
* @param {{estimatedTimeElapsed: number}} values
|
||||
*/
|
||||
setNetworkEstimated(node, values) {
|
||||
const nodeTiming = {
|
||||
...this.getNetworkStarted(node),
|
||||
estimatedTimeElapsed: values.estimatedTimeElapsed,
|
||||
};
|
||||
|
||||
this._nodeTimings.set(node, nodeTiming);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @return {NodeTimingQueued}
|
||||
*/
|
||||
getQueued(node) {
|
||||
const timing = this._nodeTimings.get(node);
|
||||
if (!timing) throw new Error(`Node ${node.id} not yet queued`);
|
||||
return timing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CpuNode} node
|
||||
* @return {CpuNodeTimingStarted}
|
||||
*/
|
||||
getCpuStarted(node) {
|
||||
const timing = this._nodeTimings.get(node);
|
||||
if (!timing) throw new Error(`Node ${node.id} not yet queued`);
|
||||
if (!('startTime' in timing)) throw new Error(`Node ${node.id} not yet started`);
|
||||
if ('bytesDownloaded' in timing) throw new Error(`Node ${node.id} timing not valid`);
|
||||
return timing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NetworkNode} node
|
||||
* @return {NetworkNodeTimingStarted}
|
||||
*/
|
||||
getNetworkStarted(node) {
|
||||
const timing = this._nodeTimings.get(node);
|
||||
if (!timing) throw new Error(`Node ${node.id} not yet queued`);
|
||||
if (!('startTime' in timing)) throw new Error(`Node ${node.id} not yet started`);
|
||||
if (!('bytesDownloaded' in timing)) throw new Error(`Node ${node.id} timing not valid`);
|
||||
return timing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @return {CpuNodeTimingInProgress | NetworkNodeTimingInProgress}
|
||||
*/
|
||||
getInProgress(node) {
|
||||
const timing = this._nodeTimings.get(node);
|
||||
if (!timing) throw new Error(`Node ${node.id} not yet queued`);
|
||||
if (!('startTime' in timing)) throw new Error(`Node ${node.id} not yet started`);
|
||||
if (!('estimatedTimeElapsed' in timing)) throw new Error(`Node ${node.id} not yet in progress`);
|
||||
return timing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @return {CpuNodeTimingComplete | NetworkNodeTimingComplete}
|
||||
*/
|
||||
getCompleted(node) {
|
||||
const timing = this._nodeTimings.get(node);
|
||||
if (!timing) throw new Error(`Node ${node.id} not yet queued`);
|
||||
if (!('startTime' in timing)) throw new Error(`Node ${node.id} not yet started`);
|
||||
if (!('estimatedTimeElapsed' in timing)) throw new Error(`Node ${node.id} not yet in progress`);
|
||||
if (!('endTime' in timing)) throw new Error(`Node ${node.id} not yet completed`);
|
||||
return timing;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimulatorTimingMap;
|
|
@ -10,6 +10,7 @@ const TcpConnection = require('./tcp-connection.js');
|
|||
const ConnectionPool = require('./connection-pool.js');
|
||||
const DNSCache = require('./dns-cache.js');
|
||||
const mobileSlow4G = require('../../../config/constants.js').throttling.mobileSlow4G;
|
||||
const SimulatorTimingMap = require('./simulator-timing-map.js');
|
||||
|
||||
/** @typedef {BaseNode.Node} Node */
|
||||
/** @typedef {import('../network-node')} NetworkNode */
|
||||
|
@ -73,8 +74,7 @@ class Simulator {
|
|||
|
||||
// Properties reset on every `.simulate` call but duplicated here for type checking
|
||||
this._flexibleOrdering = false;
|
||||
/** @type {Map<Node, NodeTimingIntermediate>} */
|
||||
this._nodeTimings = new Map();
|
||||
this._nodeTimings = new SimulatorTimingMap();
|
||||
/** @type {Map<string, number>} */
|
||||
this._numberInProgressByType = new Map();
|
||||
/** @type {Record<number, Set<Node>>} */
|
||||
|
@ -112,7 +112,7 @@ class Simulator {
|
|||
* Initializes the various state data structures such _nodeTimings and the _node Sets by state.
|
||||
*/
|
||||
_initializeAuxiliaryData() {
|
||||
this._nodeTimings = new Map();
|
||||
this._nodeTimings = new SimulatorTimingMap();
|
||||
this._numberInProgressByType = new Map();
|
||||
|
||||
this._nodes = {};
|
||||
|
@ -132,27 +132,6 @@ class Simulator {
|
|||
return this._numberInProgressByType.get(type) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {NodeTimingIntermediate} values
|
||||
*/
|
||||
_setTimingData(node, values) {
|
||||
const timingData = this._nodeTimings.get(node) || {};
|
||||
Object.assign(timingData, values);
|
||||
this._nodeTimings.set(node, timingData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @return {Required<NodeTimingIntermediate>}
|
||||
*/
|
||||
_getTimingData(node) {
|
||||
const timingData = this._nodeTimings.get(node);
|
||||
if (!timingData) throw new Error(`Unable to get timing data for node ${node.id}`);
|
||||
// @ts-expect-error - Allow consumers to assume all values are defined.
|
||||
return timingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {number} queuedTime
|
||||
|
@ -167,7 +146,7 @@ class Simulator {
|
|||
|
||||
this._nodes[NodeState.ReadyToStart].add(node);
|
||||
this._nodes[NodeState.NotReadyToStart].delete(node);
|
||||
this._setTimingData(node, {queuedTime});
|
||||
this._nodeTimings.setReadyToStart(node, {queuedTime});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,7 +160,7 @@ class Simulator {
|
|||
this._nodes[NodeState.InProgress].add(node);
|
||||
this._nodes[NodeState.ReadyToStart].delete(node);
|
||||
this._numberInProgressByType.set(node.type, this._numberInProgress(node.type) + 1);
|
||||
this._setTimingData(node, {startTime});
|
||||
this._nodeTimings.setInProgress(node, {startTime});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,7 +171,7 @@ class Simulator {
|
|||
this._nodes[NodeState.Complete].add(node);
|
||||
this._nodes[NodeState.InProgress].delete(node);
|
||||
this._numberInProgressByType.set(node.type, this._numberInProgress(node.type) - 1);
|
||||
this._setTimingData(node, {endTime});
|
||||
this._nodeTimings.setCompleted(node, {endTime});
|
||||
|
||||
// Try to add all its dependents to the queue
|
||||
for (const dependent of node.getDependents()) {
|
||||
|
@ -232,7 +211,6 @@ class Simulator {
|
|||
// Start a CPU task if there's no other CPU task in process
|
||||
if (this._numberInProgress(node.type) === 0) {
|
||||
this._markNodeAsInProgress(node, totalElapsedTime);
|
||||
this._setTimingData(node, {timeElapsed: 0});
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -250,11 +228,6 @@ class Simulator {
|
|||
}
|
||||
|
||||
this._markNodeAsInProgress(node, totalElapsedTime);
|
||||
this._setTimingData(node, {
|
||||
timeElapsed: 0,
|
||||
timeElapsedOvershoot: 0,
|
||||
bytesDownloaded: 0,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -287,7 +260,7 @@ class Simulator {
|
|||
* @return {number}
|
||||
*/
|
||||
_estimateCPUTimeRemaining(cpuNode) {
|
||||
const timingData = this._getTimingData(cpuNode);
|
||||
const timingData = this._nodeTimings.getCpuStarted(cpuNode);
|
||||
const multiplier = cpuNode.didPerformLayout()
|
||||
? this._layoutTaskMultiplier
|
||||
: this._cpuSlowdownMultiplier;
|
||||
|
@ -296,7 +269,7 @@ class Simulator {
|
|||
DEFAULT_MAXIMUM_CPU_TASK_DURATION
|
||||
);
|
||||
const estimatedTimeElapsed = totalDuration - timingData.timeElapsed;
|
||||
this._setTimingData(cpuNode, {estimatedTimeElapsed});
|
||||
this._nodeTimings.setCpuEstimated(cpuNode, {estimatedTimeElapsed});
|
||||
return estimatedTimeElapsed;
|
||||
}
|
||||
|
||||
|
@ -306,7 +279,7 @@ class Simulator {
|
|||
*/
|
||||
_estimateNetworkTimeRemaining(networkNode) {
|
||||
const record = networkNode.record;
|
||||
const timingData = this._getTimingData(networkNode);
|
||||
const timingData = this._nodeTimings.getNetworkStarted(networkNode);
|
||||
|
||||
let timeElapsed = 0;
|
||||
if (networkNode.fromDiskCache) {
|
||||
|
@ -337,7 +310,7 @@ class Simulator {
|
|||
}
|
||||
|
||||
const estimatedTimeElapsed = timeElapsed + timingData.timeElapsedOvershoot;
|
||||
this._setTimingData(networkNode, {estimatedTimeElapsed});
|
||||
this._nodeTimings.setNetworkEstimated(networkNode, {estimatedTimeElapsed});
|
||||
return estimatedTimeElapsed;
|
||||
}
|
||||
|
||||
|
@ -361,7 +334,7 @@ class Simulator {
|
|||
* @param {number} totalElapsedTime
|
||||
*/
|
||||
_updateProgressMadeInTimePeriod(node, timePeriodLength, totalElapsedTime) {
|
||||
const timingData = this._getTimingData(node);
|
||||
const timingData = this._nodeTimings.getInProgress(node);
|
||||
const isFinished = timingData.estimatedTimeElapsed === timePeriodLength;
|
||||
|
||||
if (node.type === BaseNode.TYPES.CPU || node.isConnectionless) {
|
||||
|
@ -371,6 +344,7 @@ class Simulator {
|
|||
}
|
||||
|
||||
if (node.type !== BaseNode.TYPES.NETWORK) throw new Error('Unsupported');
|
||||
if (!('bytesDownloaded' in timingData)) throw new Error('Invalid timing data');
|
||||
|
||||
const record = node.record;
|
||||
const connection = this._connectionPool.acquireActiveConnectionFromRecord(record);
|
||||
|
@ -407,8 +381,8 @@ class Simulator {
|
|||
_computeFinalNodeTimings() {
|
||||
/** @type {Array<[Node, LH.Gatherer.Simulation.NodeTiming]>} */
|
||||
const nodeTimingEntries = [];
|
||||
for (const node of this._nodeTimings.keys()) {
|
||||
const timing = this._getTimingData(node);
|
||||
for (const node of this._nodeTimings.getNodes()) {
|
||||
const timing = this._nodeTimings.getCompleted(node);
|
||||
nodeTimingEntries.push([node, {
|
||||
startTime: timing.startTime,
|
||||
endTime: timing.endTime,
|
||||
|
@ -530,14 +504,3 @@ class Simulator {
|
|||
}
|
||||
|
||||
module.exports = Simulator;
|
||||
|
||||
/**
|
||||
* @typedef NodeTimingIntermediate
|
||||
* @property {number} [startTime]
|
||||
* @property {number} [endTime]
|
||||
* @property {number} [queuedTime] Helpful for debugging.
|
||||
* @property {number} [estimatedTimeElapsed]
|
||||
* @property {number} [timeElapsed]
|
||||
* @property {number} [timeElapsedOvershoot]
|
||||
* @property {number} [bytesDownloaded]
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче