Skip to content

Commit c8662e3

Browse files
authored
Introduce computedArtifacts (#583)
The difference between classic gatherers and computed artifacts: * gatherers all speak directly to the browser and collect data. All of them talk to the protocol * computed artifacts do not talk to the protocol. They take either a trace or networkRecords and generate another artifact that's of value to more than 1 audits. #583
1 parent 543b422 commit c8662e3

29 files changed

+484
-375
lines changed

lighthouse-core/audits/critical-request-chains.js

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CriticalRequestChains extends Audit {
3030
name: 'critical-request-chains',
3131
description: 'Critical Request Chains',
3232
optimalValue: 0,
33-
requiredArtifacts: ['CriticalRequestChains']
33+
requiredArtifacts: ['networkRecords']
3434
};
3535
}
3636

@@ -40,31 +40,33 @@ class CriticalRequestChains extends Audit {
4040
* @return {!AuditResult} The score from the audit, ranging from 0-100.
4141
*/
4242
static audit(artifacts) {
43-
let chainCount = 0;
44-
function walk(node, depth) {
45-
const children = Object.keys(node);
43+
return artifacts.requestCriticalRequestChains(artifacts.networkRecords).then(chains => {
44+
let chainCount = 0;
45+
function walk(node, depth) {
46+
const children = Object.keys(node);
4647

47-
// Since a leaf node indicates the end of a chain, we can inspect the number
48-
// of child nodes, and, if the count is zero, increment the count.
49-
if (children.length === 0) {
50-
chainCount++;
51-
}
48+
// Since a leaf node indicates the end of a chain, we can inspect the number
49+
// of child nodes, and, if the count is zero, increment the count.
50+
if (children.length === 0) {
51+
chainCount++;
52+
}
5253

53-
children.forEach(id => {
54-
const child = node[id];
55-
walk(child.children, depth + 1);
56-
}, '');
57-
}
54+
children.forEach(id => {
55+
const child = node[id];
56+
walk(child.children, depth + 1);
57+
}, '');
58+
}
5859

59-
walk(artifacts.CriticalRequestChains, 0);
60+
walk(chains, 0);
6061

61-
return CriticalRequestChains.generateAuditResult({
62-
rawValue: chainCount,
63-
optimalValue: this.meta.optimalValue,
64-
extendedInfo: {
65-
formatter: Formatter.SUPPORTED_FORMATS.CRITICAL_REQUEST_CHAINS,
66-
value: artifacts.CriticalRequestChains
67-
}
62+
return CriticalRequestChains.generateAuditResult({
63+
rawValue: chainCount,
64+
optimalValue: this.meta.optimalValue,
65+
extendedInfo: {
66+
formatter: Formatter.SUPPORTED_FORMATS.CRITICAL_REQUEST_CHAINS,
67+
value: chains
68+
}
69+
});
6870
});
6971
}
7072
}

lighthouse-core/audits/estimated-input-latency.js

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,56 +35,60 @@ class EstimatedInputLatency extends Audit {
3535
name: 'estimated-input-latency',
3636
description: 'Estimated Input Latency',
3737
optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString() + 'ms',
38-
requiredArtifacts: ['traceContents', 'Speedline']
38+
requiredArtifacts: ['traceContents']
3939
};
4040
}
4141

42+
static calculate(speedline, trace) {
43+
// Use speedline's first paint as start of range for input latency check.
44+
const startTime = speedline.first;
45+
46+
const tracingProcessor = new TracingProcessor();
47+
const model = tracingProcessor.init(trace);
48+
const latencyPercentiles = TracingProcessor.getRiskToResponsiveness(model, trace, startTime);
49+
50+
const ninetieth = latencyPercentiles.find(result => result.percentile === 0.9);
51+
const rawValue = parseFloat(ninetieth.time.toFixed(1));
52+
53+
// Use the CDF of a log-normal distribution for scoring.
54+
// 10th Percentile ≈ 58ms
55+
// 25th Percentile ≈ 75ms
56+
// Median = 100ms
57+
// 75th Percentile ≈ 133ms
58+
// 95th Percentile ≈ 199ms
59+
const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
60+
SCORING_POINT_OF_DIMINISHING_RETURNS);
61+
let score = 100 * distribution.computeComplementaryPercentile(ninetieth.time);
62+
63+
return EstimatedInputLatency.generateAuditResult({
64+
score: Math.round(score),
65+
optimalValue: this.meta.optimalValue,
66+
rawValue,
67+
displayValue: `${rawValue}ms`,
68+
extendedInfo: {
69+
value: latencyPercentiles,
70+
formatter: Formatter.SUPPORTED_FORMATS.ESTIMATED_INPUT_LATENCY
71+
}
72+
});
73+
}
74+
4275
/**
4376
* Audits the page to estimate input latency.
4477
* @see https://github.com/GoogleChrome/lighthouse/issues/28
4578
* @param {!Artifacts} artifacts The artifacts from the gather phase.
46-
* @return {!AuditResult} The score from the audit, ranging from 0-100.
79+
* @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
4780
*/
4881
static audit(artifacts) {
49-
try {
50-
// Use speedline's first paint as start of range for input latency check.
51-
const startTime = artifacts.Speedline.first;
52-
53-
const trace = artifacts.traces[this.DEFAULT_TRACE] &&
54-
artifacts.traces[this.DEFAULT_TRACE].traceEvents;
55-
const tracingProcessor = new TracingProcessor();
56-
const model = tracingProcessor.init(trace);
57-
const latencyPercentiles = TracingProcessor.getRiskToResponsiveness(model, trace, startTime);
58-
59-
const ninetieth = latencyPercentiles.find(result => result.percentile === 0.9);
60-
const rawValue = parseFloat(ninetieth.time.toFixed(1));
82+
const trace = artifacts.traces[this.DEFAULT_TRACE];
6183

62-
// Use the CDF of a log-normal distribution for scoring.
63-
// 10th Percentile ≈ 58ms
64-
// 25th Percentile ≈ 75ms
65-
// Median = 100ms
66-
// 75th Percentile ≈ 133ms
67-
// 95th Percentile ≈ 199ms
68-
const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
69-
SCORING_POINT_OF_DIMINISHING_RETURNS);
70-
let score = 100 * distribution.computeComplementaryPercentile(ninetieth.time);
71-
72-
return EstimatedInputLatency.generateAuditResult({
73-
score: Math.round(score),
74-
optimalValue: this.meta.optimalValue,
75-
rawValue,
76-
displayValue: `${rawValue}ms`,
77-
extendedInfo: {
78-
value: latencyPercentiles,
79-
formatter: Formatter.SUPPORTED_FORMATS.ESTIMATED_INPUT_LATENCY
80-
}
81-
});
82-
} catch (err) {
83-
return EstimatedInputLatency.generateAuditResult({
84-
rawValue: -1,
85-
debugString: 'Unable to parse trace contents: ' + err.message
84+
return artifacts.requestSpeedline(trace)
85+
.then(speedline => EstimatedInputLatency.calculate(speedline, trace))
86+
.catch(err => {
87+
return EstimatedInputLatency.generateAuditResult({
88+
rawValue: -1,
89+
debugString: 'Speedline unable to parse trace contents: ' + err.message
90+
});
8691
});
87-
}
8892
}
8993
}
9094

lighthouse-core/audits/first-meaningful-paint.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ class FirstMeaningfulPaint extends Audit {
5757
if (!traceContents || !Array.isArray(traceContents)) {
5858
throw new Error(FAILURE_MESSAGE);
5959
}
60-
6160
const evts = this.collectEvents(traceContents);
6261

6362
const navStart = evts.navigationStart;

lighthouse-core/audits/screenshots.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,38 @@ class Screenshots extends Audit {
2929
category: 'Performance',
3030
name: 'screenshots',
3131
description: 'Screenshots of all captured frames',
32-
requiredArtifacts: ['ScreenshotFilmstrip']
32+
requiredArtifacts: ['traceContents']
3333
};
3434
}
3535

3636
/**
3737
* @param {!Artifacts} artifacts
38-
* @return {!AuditResultInput}
38+
* @return {!Promise<!AuditResult>}
3939
*/
4040
static audit(artifacts) {
41-
const screenshots = artifacts.ScreenshotFilmstrip;
42-
43-
if (typeof screenshots === 'undefined') {
44-
return Screenshots.generateAuditResult({
41+
const trace = artifacts.traces[this.DEFAULT_TRACE];
42+
if (typeof trace === 'undefined') {
43+
return Promise.resolve(Screenshots.generateAuditResult({
4544
rawValue: -1,
46-
debugString: 'No screenshot artifact'
47-
});
45+
debugString: 'No trace found to generate screenshots'
46+
}));
4847
}
4948

50-
return Screenshots.generateAuditResult({
51-
rawValue: screenshots.length || 0,
52-
extendedInfo: {
53-
formatter: Formatter.SUPPORTED_FORMATS.NULL,
54-
value: screenshots
49+
return artifacts.requestScreenshots(trace).then(screenshots => {
50+
if (typeof screenshots === 'undefined') {
51+
return Screenshots.generateAuditResult({
52+
rawValue: -1,
53+
debugString: 'No screenshot artifact'
54+
});
5555
}
56+
57+
return Screenshots.generateAuditResult({
58+
rawValue: screenshots.length || 0,
59+
extendedInfo: {
60+
formatter: Formatter.SUPPORTED_FORMATS.NULL,
61+
value: screenshots
62+
}
63+
});
5664
});
5765
}
5866
}

lighthouse-core/audits/speed-index-metric.js

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class SpeedIndexMetric extends Audit {
3636
name: 'speed-index-metric',
3737
description: 'Speed Index',
3838
optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString(),
39-
requiredArtifacts: ['Speedline']
39+
requiredArtifacts: ['traceContents']
4040
};
4141
}
4242

@@ -47,36 +47,35 @@ class SpeedIndexMetric extends Audit {
4747
* @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
4848
*/
4949
static audit(artifacts) {
50-
return new Promise((resolve, reject) => {
51-
const speedline = artifacts.Speedline;
52-
53-
// Speedline gather failed; pass on error condition.
54-
if (speedline.debugString) {
55-
return resolve(SpeedIndexMetric.generateAuditResult({
56-
rawValue: -1,
57-
debugString: speedline.debugString
58-
}));
59-
}
50+
const trace = artifacts.traces[this.DEFAULT_TRACE];
51+
if (typeof trace === 'undefined') {
52+
return SpeedIndexMetric.generateAuditResult({
53+
rawValue: -1,
54+
debugString: 'No trace found to generate screenshots'
55+
});
56+
}
6057

58+
// run speedline
59+
return artifacts.requestSpeedline(trace).then(speedline => {
6160
if (speedline.frames.length === 0) {
62-
return resolve(SpeedIndexMetric.generateAuditResult({
61+
return SpeedIndexMetric.generateAuditResult({
6362
rawValue: -1,
6463
debugString: 'Trace unable to find visual progress frames.'
65-
}));
64+
});
6665
}
6766

6867
if (speedline.frames.length < 3) {
69-
return resolve(SpeedIndexMetric.generateAuditResult({
68+
return SpeedIndexMetric.generateAuditResult({
7069
rawValue: -1,
7170
debugString: 'Trace unable to find sufficient frames to evaluate Speed Index.'
72-
}));
71+
});
7372
}
7473

7574
if (speedline.speedIndex === 0) {
76-
return resolve(SpeedIndexMetric.generateAuditResult({
75+
return SpeedIndexMetric.generateAuditResult({
7776
rawValue: -1,
7877
debugString: 'Error in Speedline calculating Speed Index (speedIndex of 0).'
79-
}));
78+
});
8079
}
8180

8281
// Use the CDF of a log-normal distribution for scoring.
@@ -86,7 +85,7 @@ class SpeedIndexMetric extends Audit {
8685
// 75th Percentile = 8,820
8786
// 95th Percentile = 17,400
8887
const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
89-
SCORING_POINT_OF_DIMINISHING_RETURNS);
88+
SCORING_POINT_OF_DIMINISHING_RETURNS);
9089
let score = 100 * distribution.computeComplementaryPercentile(speedline.speedIndex);
9190

9291
// Clamp the score to 0 <= x <= 100.
@@ -105,15 +104,20 @@ class SpeedIndexMetric extends Audit {
105104
})
106105
};
107106

108-
resolve(SpeedIndexMetric.generateAuditResult({
107+
return SpeedIndexMetric.generateAuditResult({
109108
score: Math.round(score),
110109
rawValue: Math.round(speedline.speedIndex),
111110
optimalValue: this.meta.optimalValue,
112111
extendedInfo: {
113112
formatter: Formatter.SUPPORTED_FORMATS.SPEEDLINE,
114113
value: extendedInfo
115114
}
116-
}));
115+
});
116+
}).catch(err => {
117+
return SpeedIndexMetric.generateAuditResult({
118+
rawValue: -1,
119+
debugString: err.message
120+
});
117121
});
118122
}
119123
}

lighthouse-core/audits/time-to-interactive.js

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class TTIMetric extends Audit {
2828
name: 'time-to-interactive',
2929
description: 'Time To Interactive (alpha)',
3030
optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString(),
31-
requiredArtifacts: ['traceContents', 'speedline']
31+
requiredArtifacts: ['traceContents']
3232
};
3333
}
3434

@@ -54,11 +54,17 @@ class TTIMetric extends Audit {
5454
* will be changing in the future to a more accurate number.
5555
*
5656
* @param {!Artifacts} artifacts The artifacts from the gather phase.
57-
* @return {!AuditResult} The score from the audit, ranging from 0-100.
57+
* @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
5858
*/
5959
static audit(artifacts) {
60+
const trace = artifacts.traces[Audit.DEFAULT_TRACE];
61+
const pendingSpeedline = artifacts.requestSpeedline(trace);
62+
const pendingFMP = FMPMetric.audit(artifacts);
63+
6064
// We start looking at Math.Max(FMPMetric, visProgress[0.85])
61-
return FMPMetric.audit(artifacts).then(fmpResult => {
65+
return Promise.all([pendingSpeedline, pendingFMP]).then(results => {
66+
const speedline = results[0];
67+
const fmpResult = results[1];
6268
if (fmpResult.rawValue === -1) {
6369
return generateError(fmpResult.debugString);
6470
}
@@ -68,8 +74,8 @@ class TTIMetric extends Audit {
6874

6975
// Process the trace
7076
const tracingProcessor = new TracingProcessor();
71-
const traceContents = artifacts.traces[Audit.DEFAULT_TRACE].traceEvents;
72-
const model = tracingProcessor.init(traceContents);
77+
const trace = artifacts.traces[Audit.DEFAULT_TRACE];
78+
const model = tracingProcessor.init(trace);
7379
const endOfTraceTime = model.bounds.max;
7480

7581
// TODO: Wait for DOMContentLoadedEndEvent
@@ -81,8 +87,8 @@ class TTIMetric extends Audit {
8187
// look at speedline results for 85% starting at FMP
8288
let visuallyReadyTiming = 0;
8389

84-
if (artifacts.Speedline.frames) {
85-
const eightyFivePctVC = artifacts.Speedline.frames.find(frame => {
90+
if (speedline.frames) {
91+
const eightyFivePctVC = speedline.frames.find(frame => {
8692
return frame.getTimeStamp() >= fMPts && frame.getProgress() >= 85;
8793
});
8894

@@ -111,7 +117,7 @@ class TTIMetric extends Audit {
111117
}
112118
// Get our expected latency for the time window
113119
const latencies = TracingProcessor.getRiskToResponsiveness(
114-
model, traceContents, startTime, endTime, percentiles);
120+
model, trace, startTime, endTime, percentiles);
115121
const estLatency = latencies[0].time.toFixed(2);
116122
foundLatencies.push({
117123
estLatency: estLatency,
@@ -151,7 +157,7 @@ class TTIMetric extends Audit {
151157
rawValue: timeToInteractive,
152158
displayValue: `${timeToInteractive}ms`,
153159
optimalValue: this.meta.optimalValue,
154-
debugString: artifacts.Speedline.debugString,
160+
debugString: speedline.debugString,
155161
extendedInfo: {
156162
value: extendedInfo,
157163
formatter: Formatter.SUPPORTED_FORMATS.NULL
@@ -170,6 +176,6 @@ function generateError(err) {
170176
value: -1,
171177
rawValue: -1,
172178
optimalValue: TTIMetric.meta.optimalValue,
173-
debugString: err
179+
debugString: err.message || err
174180
});
175181
}

0 commit comments

Comments
 (0)