Skip to content

Commit

Permalink
core(metrics): move TTCI to computed artifact (#4943)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce authored Apr 10, 2018
1 parent 1de5667 commit fe07970
Show file tree
Hide file tree
Showing 16 changed files with 496 additions and 331 deletions.
190 changes: 22 additions & 168 deletions lighthouse-core/audits/consistently-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@

const Audit = require('./audit');
const Util = require('../report/v2/renderer/util');
const NetworkRecorder = require('../lib/network-recorder');
const TracingProcessor = require('../lib/traces/tracing-processor');
const LHError = require('../lib/errors');

const REQUIRED_QUIET_WINDOW = 5000;
const ALLOWED_CONCURRENT_REQUESTS = 2;

/**
* @fileoverview This audit identifies the time the page is "consistently interactive".
Expand Down Expand Up @@ -47,177 +41,37 @@ class ConsistentlyInteractiveMetric extends Audit {
};
}

/**
* Finds all time periods where the number of inflight requests is less than or equal to the
* number of allowed concurrent requests (2).
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
* @param {{timestamps: {traceEnd: number}}} traceOfTab
* @return {!Array<!TimePeriod>}
*/
static _findNetworkQuietPeriods(networkRecords, traceOfTab) {
const traceEndTsInMs = traceOfTab.timestamps.traceEnd / 1000;
return NetworkRecorder.findNetworkQuietPeriods(networkRecords,
ALLOWED_CONCURRENT_REQUESTS, traceEndTsInMs);
}

/**
* Finds all time periods where there are no long tasks.
* @param {!Array<!TimePeriod>} longTasks
* @param {{timestamps: {navigationStart: number, traceEnd: number}}} traceOfTab
* @return {!Array<!TimePeriod>}
*/
static _findCPUQuietPeriods(longTasks, traceOfTab) {
const navStartTsInMs = traceOfTab.timestamps.navigationStart / 1000;
const traceEndTsInMs = traceOfTab.timestamps.traceEnd / 1000;
if (longTasks.length === 0) {
return [{start: 0, end: traceEndTsInMs}];
}

const quietPeriods = [];
longTasks.forEach((task, index) => {
if (index === 0) {
quietPeriods.push({
start: 0,
end: task.start + navStartTsInMs,
});
}

if (index === longTasks.length - 1) {
quietPeriods.push({
start: task.end + navStartTsInMs,
end: traceEndTsInMs,
});
} else {
quietPeriods.push({
start: task.end + navStartTsInMs,
end: longTasks[index + 1].start + navStartTsInMs,
});
}
});

return quietPeriods;
}

/**
* Finds the first time period where a network quiet period and a CPU quiet period overlap.
* @param {!Array<!TimePeriod>} longTasks
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
* @param {{timestamps: {navigationStart: number, firstMeaningfulPaint: number,
* traceEnd: number}}} traceOfTab
* @return {{cpuQuietPeriod: !TimePeriod, networkQuietPeriod: !TimePeriod,
* cpuQuietPeriods: !Array<!TimePeriod>, networkQuietPeriods: !Array<!TimePeriod>}}
*/
static findOverlappingQuietPeriods(longTasks, networkRecords, traceOfTab) {
const FMPTsInMs = traceOfTab.timestamps.firstMeaningfulPaint / 1000;

const isLongEnoughQuietPeriod = period =>
period.end > FMPTsInMs + REQUIRED_QUIET_WINDOW &&
period.end - period.start >= REQUIRED_QUIET_WINDOW;
const networkQuietPeriods = this._findNetworkQuietPeriods(networkRecords, traceOfTab)
.filter(isLongEnoughQuietPeriod);
const cpuQuietPeriods = this._findCPUQuietPeriods(longTasks, traceOfTab)
.filter(isLongEnoughQuietPeriod);

const cpuQueue = cpuQuietPeriods.slice();
const networkQueue = networkQuietPeriods.slice();

// We will check for a CPU quiet period contained within a Network quiet period or vice-versa
let cpuCandidate = cpuQueue.shift();
let networkCandidate = networkQueue.shift();
while (cpuCandidate && networkCandidate) {
if (cpuCandidate.start >= networkCandidate.start) {
// CPU starts later than network, window must be contained by network or we check the next
if (networkCandidate.end >= cpuCandidate.start + REQUIRED_QUIET_WINDOW) {
return {
cpuQuietPeriod: cpuCandidate,
networkQuietPeriod: networkCandidate,
cpuQuietPeriods,
networkQuietPeriods,
};
} else {
networkCandidate = networkQueue.shift();
}
} else {
// Network starts later than CPU, window must be contained by CPU or we check the next
if (cpuCandidate.end >= networkCandidate.start + REQUIRED_QUIET_WINDOW) {
return {
cpuQuietPeriod: cpuCandidate,
networkQuietPeriod: networkCandidate,
cpuQuietPeriods,
networkQuietPeriods,
};
} else {
cpuCandidate = cpuQueue.shift();
}
}
}

throw new LHError(
cpuCandidate
? LHError.errors.NO_TTI_NETWORK_IDLE_PERIOD
: LHError.errors.NO_TTI_CPU_IDLE_PERIOD
);
}

/**
* @param {!Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {!Promise<!AuditResult>}
*/
static audit(artifacts, context) {
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const computedArtifacts = [
artifacts.requestNetworkRecords(devtoolsLog),
artifacts.requestTraceOfTab(trace),
];

return Promise.all(computedArtifacts)
.then(([networkRecords, traceOfTab]) => {
if (!traceOfTab.timestamps.firstMeaningfulPaint) {
throw new LHError(LHError.errors.NO_FMP);
}

if (!traceOfTab.timestamps.domContentLoaded) {
throw new LHError(LHError.errors.NO_DCL);
}

const longTasks = TracingProcessor.getMainThreadTopLevelEvents(traceOfTab)
.filter(event => event.duration >= 50);
const quietPeriodInfo = this.findOverlappingQuietPeriods(longTasks, networkRecords,
traceOfTab);
const cpuQuietPeriod = quietPeriodInfo.cpuQuietPeriod;

const timestamp = Math.max(
cpuQuietPeriod.start,
traceOfTab.timestamps.firstMeaningfulPaint / 1000,
traceOfTab.timestamps.domContentLoaded / 1000
) * 1000;
const timeInMs = (timestamp - traceOfTab.timestamps.navigationStart) / 1000;
const extendedInfo = Object.assign(quietPeriodInfo, {timestamp, timeInMs});
const metricComputationData = {trace, devtoolsLog, settings: context.settings};
const metricResult = await artifacts.requestConsistentlyInteractive(metricComputationData);
const timeInMs = metricResult.timing;
const extendedInfo = {
timeInMs,
timestamp: metricResult.timestamp,
optimistic: metricResult.optimisticEstimate && metricResult.optimisticEstimate.timeInMs,
pessimistic: metricResult.pessimisticEstimate && metricResult.pessimisticEstimate.timeInMs,
};

return {
score: Audit.computeLogNormalScore(
timeInMs,
context.options.scorePODR,
context.options.scoreMedian
),
rawValue: timeInMs,
displayValue: Util.formatMilliseconds(timeInMs),
extendedInfo: {
value: extendedInfo,
},
};
});
return {
score: Audit.computeLogNormalScore(
timeInMs,
context.options.scorePODR,
context.options.scoreMedian
),
rawValue: timeInMs,
displayValue: Util.formatMilliseconds(timeInMs),
extendedInfo: {
value: extendedInfo,
},
};
}
}

module.exports = ConsistentlyInteractiveMetric;

/**
* @typedef {{
* start: number,
* end: number,
* }}
*/
let TimePeriod; // eslint-disable-line no-unused-vars
2 changes: 1 addition & 1 deletion lighthouse-core/closure/typedefs/ComputedArtifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ ComputedArtifacts.prototype.requestNetworkThroughput;
/** @type {function(!Trace): !Promise<!SpeedlineArtifact>} */
ComputedArtifacts.prototype.requestSpeedline;

/** @type {function(!Trace): !Promise<!TraceOfTabArtifact>} */
/** @type {function(!Trace): !Promise<LH.Artifacts.TraceOfTab>} */
ComputedArtifacts.prototype.requestTraceOfTab;

/** @type {function(!Trace): !Promise<{timeInMs: number, timestamp: number}>} */
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/gather/computed/first-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class FirstInteractive extends ComputedArtifact {
}

/**
* @param {!TraceOfTabArtifact} traceOfTab
* @param {LH.Artifacts.TraceOfTab} traceOfTab
* @return {{timeInMs: number, timestamp: number}}
*/
computeWithArtifacts(traceOfTab) {
Expand Down
Loading

0 comments on commit fe07970

Please sign in to comment.