Skip to content

Commit

Permalink
core(lantern): move LanternMetric in lib/lantern (#15857)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Mar 7, 2024
1 parent 7d80178 commit e72a9c9
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 128 deletions.
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!');
}

/**
* 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;
}

/**
* @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;
}
}
}

0 comments on commit e72a9c9

Please sign in to comment.