Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core(lantern): move LanternMetric in lib/lantern #15857

Merged
merged 1 commit into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/computed/metrics/lantern-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class LanternInteractive extends LanternMetric {

/**
* @param {LH.Gatherer.Simulation.Result} simulationResult
* @param {import('./lantern-metric.js').Extras} extras
* @param {import('../../lib/lantern/metric.js').Extras} extras
* @return {LH.Gatherer.Simulation.Result}
*/
static getEstimateFromSimulation(simulationResult, extras) {
Expand Down
2 changes: 1 addition & 1 deletion core/computed/metrics/lantern-max-potential-fid.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class LanternMaxPotentialFID extends LanternMetric {

/**
* @param {LH.Gatherer.Simulation.Result} simulation
* @param {import('./lantern-metric.js').Extras} extras
* @param {import('../../lib/lantern/metric.js').Extras} extras
* @return {LH.Gatherer.Simulation.Result}
*/
static getEstimateFromSimulation(simulation, extras) {
Expand Down
130 changes: 6 additions & 124 deletions core/computed/metrics/lantern-metric.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,93 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {BaseNode} from '../../lib/lantern/base-node.js';
import {NetworkRequest} from '../../lib/network-request.js';
import {ProcessedNavigation} from '../processed-navigation.js';
import {PageDependencyGraph} from '../page-dependency-graph.js';
import {Metric} from '../../lib/lantern/metric.js';
import {LoadSimulator} from '../load-simulator.js';
import {PageDependencyGraph} from '../page-dependency-graph.js';
import {ProcessedNavigation} from '../processed-navigation.js';

/** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
/** @typedef {import('../../lib/lantern/network-node.js').NetworkNode<LH.Artifacts.NetworkRequest>} NetworkNode */
/** @typedef {import('../../lib/lantern/simulator/simulator.js').Simulator} Simulator */

/**
* @typedef Extras
* @property {boolean} optimistic
* @property {LH.Artifacts.LanternMetric=} fcpResult
* @property {LH.Artifacts.LanternMetric=} fmpResult
* @property {LH.Artifacts.LanternMetric=} interactiveResult
* @property {{speedIndex: number}=} speedline
*/

class LanternMetric {
/**
* @param {Node} dependencyGraph
* @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
* @return {Set<string>}
*/
static getScriptUrls(dependencyGraph, treatNodeAsRenderBlocking) {
/** @type {Set<string>} */
const scriptUrls = new Set();

dependencyGraph.traverse(node => {
if (node.type !== BaseNode.TYPES.NETWORK) return;
if (node.record.resourceType !== NetworkRequest.TYPES.Script) return;
if (treatNodeAsRenderBlocking?.(node)) {
scriptUrls.add(node.record.url);
}
});

return scriptUrls;
}

/**
* @return {LH.Gatherer.Simulation.MetricCoefficients}
*/
static get COEFFICIENTS() {
throw new Error('COEFFICIENTS unimplemented!');
}

/**
* Returns the coefficients, scaled by the throttling settings if needed by the metric.
* Some lantern metrics (speed-index) use components in their estimate that are not
* from the simulator. In this case, we need to adjust the coefficients as the target throttling
* settings change.
*
* @param {number} rttMs
* @return {LH.Gatherer.Simulation.MetricCoefficients}
*/
static getScaledCoefficients(rttMs) { // eslint-disable-line no-unused-vars
return this.COEFFICIENTS;
}

/**
* @param {Node} dependencyGraph
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
* @return {Node}
*/
static getOptimisticGraph(dependencyGraph, processedNavigation) { // eslint-disable-line no-unused-vars
throw new Error('Optimistic graph unimplemented!');
}

/**
* @param {Node} dependencyGraph
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
* @return {Node}
*/
static getPessimisticGraph(dependencyGraph, processedNavigation) { // eslint-disable-line no-unused-vars
throw new Error('Pessmistic graph unimplemented!');
}

/**
* @param {LH.Gatherer.Simulation.Result} simulationResult
* @param {Extras} extras
* @return {LH.Gatherer.Simulation.Result}
*/
static getEstimateFromSimulation(simulationResult, extras) { // eslint-disable-line no-unused-vars
return simulationResult;
}
/** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */

class LanternMetric extends Metric {
/**
* @param {LH.Artifacts.MetricComputationDataInput} data
* @param {LH.Artifacts.ComputedContext} context
Expand All @@ -104,50 +25,11 @@ class LanternMetric {
throw new Error(`Lantern metrics can only be computed on navigations`);
}

const metricName = this.name.replace('Lantern', '');
const graph = await PageDependencyGraph.request(data, context);
const processedNavigation = await ProcessedNavigation.request(data.trace, context);
const simulator = data.simulator || (await LoadSimulator.request(data, context));

const optimisticGraph = this.getOptimisticGraph(graph, processedNavigation);
const pessimisticGraph = this.getPessimisticGraph(graph, processedNavigation);

/** @type {{flexibleOrdering?: boolean, label?: string}} */
let simulateOptions = {label: `optimistic${metricName}`};
const optimisticSimulation = simulator.simulate(optimisticGraph, simulateOptions);

simulateOptions = {label: `optimisticFlex${metricName}`, flexibleOrdering: true};
const optimisticFlexSimulation = simulator.simulate(optimisticGraph, simulateOptions);

simulateOptions = {label: `pessimistic${metricName}`};
const pessimisticSimulation = simulator.simulate(pessimisticGraph, simulateOptions);

const optimisticEstimate = this.getEstimateFromSimulation(
optimisticSimulation.timeInMs < optimisticFlexSimulation.timeInMs ?
optimisticSimulation : optimisticFlexSimulation, {...extras, optimistic: true}
);

const pessimisticEstimate = this.getEstimateFromSimulation(
pessimisticSimulation,
{...extras, optimistic: false}
);

const coefficients = this.getScaledCoefficients(simulator.rtt);
// Estimates under 1s don't really follow the normal curve fit, minimize the impact of the intercept
const interceptMultiplier = coefficients.intercept > 0 ?
Math.min(1, optimisticEstimate.timeInMs / 1000) : 1;
const timing =
coefficients.intercept * interceptMultiplier +
coefficients.optimistic * optimisticEstimate.timeInMs +
coefficients.pessimistic * pessimisticEstimate.timeInMs;

return {
timing,
optimisticEstimate,
pessimisticEstimate,
optimisticGraph,
pessimisticGraph,
};
return this.compute({simulator, graph, processedNavigation}, extras);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion core/computed/metrics/lantern-speed-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class LanternSpeedIndex extends LanternMetric {

/**
* @param {LH.Gatherer.Simulation.Result} simulationResult
* @param {import('./lantern-metric.js').Extras} extras
* @param {import('../../lib/lantern/metric.js').Extras} extras
* @return {LH.Gatherer.Simulation.Result}
*/
static getEstimateFromSimulation(simulationResult, extras) {
Expand Down
2 changes: 1 addition & 1 deletion core/computed/metrics/lantern-total-blocking-time.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class LanternTotalBlockingTime extends LanternMetric {

/**
* @param {LH.Gatherer.Simulation.Result} simulation
* @param {import('./lantern-metric.js').Extras} extras
* @param {import('../../lib/lantern/metric.js').Extras} extras
* @return {LH.Gatherer.Simulation.Result}
*/
static getEstimateFromSimulation(simulation, extras) {
Expand Down
143 changes: 143 additions & 0 deletions core/lib/lantern/metric.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {BaseNode} from '../../lib/lantern/base-node.js';
import {NetworkRequest} from '../../lib/network-request.js';

/** @typedef {import('./base-node.js').Node} Node */
/** @typedef {import('./network-node.js').NetworkNode} NetworkNode */
/** @typedef {import('./simulator/simulator.js').Simulator} Simulator */
/** @typedef {import('../../../types/internal/lantern.js').Lantern.Simulation.MetricComputationDataInput} MetricComputationDataInput */

/**
* @typedef Extras
* @property {boolean} optimistic
* @property {LH.Artifacts.LanternMetric=} fcpResult
* @property {LH.Artifacts.LanternMetric=} fmpResult
* @property {LH.Artifacts.LanternMetric=} interactiveResult
* @property {{speedIndex: number}=} speedline
*/

class Metric {
/**
* @param {Node} dependencyGraph
* @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
* @return {Set<string>}
*/
static getScriptUrls(dependencyGraph, treatNodeAsRenderBlocking) {
/** @type {Set<string>} */
const scriptUrls = new Set();

dependencyGraph.traverse(node => {
if (node.type !== BaseNode.TYPES.NETWORK) return;
if (node.record.resourceType !== NetworkRequest.TYPES.Script) return;
if (treatNodeAsRenderBlocking?.(node)) {
scriptUrls.add(node.record.url);
}
});

return scriptUrls;
}

/**
* @return {LH.Gatherer.Simulation.MetricCoefficients}
*/
static get COEFFICIENTS() {
throw new Error('COEFFICIENTS unimplemented!');
}

Check warning on line 50 in core/lib/lantern/metric.js

View check run for this annotation

Codecov / codecov/patch

core/lib/lantern/metric.js#L49-L50

Added lines #L49 - L50 were not covered by tests

/**
* Returns the coefficients, scaled by the throttling settings if needed by the metric.
* Some lantern metrics (speed-index) use components in their estimate that are not
* from the simulator. In this case, we need to adjust the coefficients as the target throttling
* settings change.
*
* @param {number} rttMs
* @return {LH.Gatherer.Simulation.MetricCoefficients}
*/
static getScaledCoefficients(rttMs) { // eslint-disable-line no-unused-vars
return this.COEFFICIENTS;
}

/**
* @param {Node} dependencyGraph
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
* @return {Node}
*/
static getOptimisticGraph(dependencyGraph, processedNavigation) { // eslint-disable-line no-unused-vars
throw new Error('Optimistic graph unimplemented!');
}

Check warning on line 72 in core/lib/lantern/metric.js

View check run for this annotation

Codecov / codecov/patch

core/lib/lantern/metric.js#L71-L72

Added lines #L71 - L72 were not covered by tests

/**
* @param {Node} dependencyGraph
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
* @return {Node}
*/
static getPessimisticGraph(dependencyGraph, processedNavigation) { // eslint-disable-line no-unused-vars
throw new Error('Pessmistic graph unimplemented!');
}

Check warning on line 81 in core/lib/lantern/metric.js

View check run for this annotation

Codecov / codecov/patch

core/lib/lantern/metric.js#L80-L81

Added lines #L80 - L81 were not covered by tests

/**
* @param {LH.Gatherer.Simulation.Result} simulationResult
* @param {Extras} extras
* @return {LH.Gatherer.Simulation.Result}
*/
static getEstimateFromSimulation(simulationResult, extras) { // eslint-disable-line no-unused-vars
return simulationResult;
}

/**
* @param {MetricComputationDataInput} data
* @param {Omit<Extras, 'optimistic'>=} extras
* @return {Promise<LH.Artifacts.LanternMetric>}
*/
static async compute(data, extras) {
const {simulator, graph, processedNavigation} = data;

const metricName = this.name.replace('Lantern', '');
const optimisticGraph = this.getOptimisticGraph(graph, processedNavigation);
const pessimisticGraph = this.getPessimisticGraph(graph, processedNavigation);

/** @type {{flexibleOrdering?: boolean, label?: string}} */
let simulateOptions = {label: `optimistic${metricName}`};
const optimisticSimulation = simulator.simulate(optimisticGraph, simulateOptions);

simulateOptions = {label: `optimisticFlex${metricName}`, flexibleOrdering: true};
const optimisticFlexSimulation = simulator.simulate(optimisticGraph, simulateOptions);

simulateOptions = {label: `pessimistic${metricName}`};
const pessimisticSimulation = simulator.simulate(pessimisticGraph, simulateOptions);

const optimisticEstimate = this.getEstimateFromSimulation(
optimisticSimulation.timeInMs < optimisticFlexSimulation.timeInMs ?
optimisticSimulation : optimisticFlexSimulation, {...extras, optimistic: true}
);

const pessimisticEstimate = this.getEstimateFromSimulation(
pessimisticSimulation,
{...extras, optimistic: false}
);

const coefficients = this.getScaledCoefficients(simulator.rtt);
// Estimates under 1s don't really follow the normal curve fit, minimize the impact of the intercept
const interceptMultiplier = coefficients.intercept > 0 ?
Math.min(1, optimisticEstimate.timeInMs / 1000) : 1;
const timing =
coefficients.intercept * interceptMultiplier +
coefficients.optimistic * optimisticEstimate.timeInMs +
coefficients.pessimistic * pessimisticEstimate.timeInMs;

return {
timing,
optimisticEstimate,
pessimisticEstimate,
optimisticGraph,
pessimisticGraph,
};
}
}

export {Metric};
6 changes: 6 additions & 0 deletions types/internal/lantern.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,11 @@ declare namespace Lantern {
timeInMs: number;
nodeTimings: Map<GraphNode<T>, NodeTiming>;
}

interface MetricComputationDataInput {
simulator: Simulator<any>;
graph: GraphNode<any>;
processedNavigation: LH.Artifacts.ProcessedNavigation;
}
connorjclark marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading