-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
PROPOSAL: new structure for metrics + lantern #4676
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/** | ||
* @license Copyright 2016 Google Inc. 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 Audit = require('./audit'); | ||
|
||
class FirstContentfulPaint extends Audit { | ||
/** | ||
* @return {!AuditMeta} | ||
*/ | ||
static get meta() { | ||
return { | ||
name: 'first-contentful-paint', | ||
description: 'First Contentful Paint', | ||
failureDescription: 'First Contentful Paint', | ||
helpText: 'Foo', | ||
requiredArtifacts: ['traces', 'devtoolsLogs'], | ||
}; | ||
} | ||
|
||
/** | ||
* @param {!Artifacts} artifacts | ||
* @return {!AuditResult} | ||
*/ | ||
static async audit(artifacts, context) { | ||
const result = await artifacts.requestFirstContentfulPaint({ | ||
trace: artifacts.traces.defaultPass, | ||
devtoolsLog: artifacts.devtoolsLogs.defaultPass, | ||
throttling: context.config.settings.throttling, | ||
}) | ||
|
||
return { | ||
rawValue: result.timing, | ||
score: 100 - 100 * (result.timing / 10000), | ||
}; | ||
} | ||
} | ||
|
||
module.exports = FirstContentfulPaint; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/** | ||
* @license Copyright 2017 Google Inc. 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'; | ||
|
||
module.exports = { | ||
extends: 'lighthouse:default', | ||
settings: { | ||
throttling: { | ||
method: 'lantern', | ||
rtt: emulation.settings.TYPICAL_MOBILE_THROTTLING_METRICS.targetLatency, | ||
throughput: emulation.settings.TYPICAL_MOBILE_THROTTLING_METRICS.targetDownloadThroughput, | ||
cpuSlowdownMultiplier: emulation.settings.CPU_THROTTLE_METRICS.rate, | ||
}, | ||
skipAudits: [ | ||
// disabled for now because their results are not meaningful/cannot be computed anymore | ||
'first-meaningful-paint', | ||
'first-interactive', | ||
'consistently-interactive', | ||
'estimated-input-latency', | ||
'speed-index-metric', | ||
'offscreen-images', | ||
'load-fast-enough-for-pwa', | ||
], | ||
}, | ||
passes: [ | ||
{ | ||
passName: 'defaultPass', | ||
// overwrite the throttling and load wait parameters | ||
useThrottling: false, | ||
pauseAfterLoadMs: 0, | ||
networkQuietThresholdMs: 500, | ||
cpuQuietThresholdMs: 500, | ||
// no need to add any gatherers yet, but this property is required | ||
gatherers: [], | ||
}, | ||
], | ||
audits: [ | ||
'predictive-perf', | ||
], | ||
categories: { | ||
performance: { | ||
audits: [ | ||
{id: 'predictive-perf', weight: 5, group: 'perf-metric'}, | ||
], | ||
}, | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/** | ||
* @license Copyright 2017 Google Inc. 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 ComputedArtifact = require('./computed-artifact'); | ||
const Node = require('../../lib/dependency-graph/node'); | ||
const Simulator = require('../../lib/dependency-graph/simulator/simulator'); | ||
|
||
const COEFFICIENTS = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment on where this come from plx |
||
intercept: 1440, | ||
optimistic: -1.75, | ||
pessimistic: 2.73, | ||
}; | ||
|
||
class FirstContentfulPaint extends ComputedArtifact { | ||
get name() { | ||
return 'FirstContentfulPaint'; | ||
} | ||
|
||
/** | ||
* @param {!Node} dependencyGraph | ||
* @param {function()=} condition | ||
* @return {!Set<string>} | ||
*/ | ||
static getScriptUrls(dependencyGraph, condition) { | ||
const scriptUrls = new Set(); | ||
|
||
dependencyGraph.traverse(node => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it feels like it'd make sense for a common |
||
if (node.type === Node.TYPES.CPU) return; | ||
if (node.record._resourceType !== WebInspector.resourceTypes.Script) return; | ||
if (condition && !condition(node)) return; | ||
scriptUrls.add(node.record.url); | ||
}); | ||
|
||
return scriptUrls; | ||
} | ||
|
||
/** | ||
* @param {!Node} dependencyGraph | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not relevant to the architecture, but we should document how we compute this estimated FCP. |
||
* @param {!TraceOfTabArtifact} traceOfTab | ||
* @return {!Node} | ||
*/ | ||
static getOptimisticGraph(dependencyGraph, traceOfTab) { | ||
const fcp = traceOfTab.timestamps.firstContentfulPaint; | ||
const blockingScriptUrls = FirstContentfulPaint.getScriptUrls(dependencyGraph, node => { | ||
return ( | ||
node.endTime <= fcp && node.hasRenderBlockingPriority() && node.initiatorType !== 'script' | ||
); | ||
}); | ||
|
||
return dependencyGraph.cloneWithRelationships(node => { | ||
if (node.endTime > fcp) return false; | ||
// Include EvaluateScript tasks for blocking scripts | ||
if (node.type === Node.TYPES.CPU) return node.isEvaluateScriptFor(blockingScriptUrls); | ||
// Include non-script-initiated network requests with a render-blocking priority | ||
return node.hasRenderBlockingPriority() && node.initiatorType !== 'script'; | ||
}); | ||
} | ||
|
||
/** | ||
* @param {!Node} dependencyGraph | ||
* @param {!TraceOfTabArtifact} traceOfTab | ||
* @return {!Node} | ||
*/ | ||
static getPessimisticGraph(dependencyGraph, traceOfTab) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. theres so much similarity between these two fns (optimistic & pessimistic). could we DRY it up? dunno if adding |
||
const fcp = traceOfTab.timestamps.firstContentfulPaint; | ||
const blockingScriptUrls = FirstContentfulPaint.getScriptUrls(dependencyGraph, node => { | ||
return node.endTime <= fcp && node.hasRenderBlockingPriority(); | ||
}); | ||
|
||
return dependencyGraph.cloneWithRelationships(node => { | ||
if (node.endTime > fcp) return false; | ||
// Include EvaluateScript tasks for blocking scripts | ||
if (node.type === Node.TYPES.CPU) return node.isEvaluateScriptFor(blockingScriptUrls); | ||
// Include all network requests that had render-blocking priority (even script-initiated) | ||
return node.hasRenderBlockingPriority(); | ||
}); | ||
} | ||
|
||
static async computeLantern(data, artifacts) { | ||
const {trace, devtoolsLog} = data; | ||
const graph = await artifacts.requestPageDependencyGraph({trace, devtoolsLog}); | ||
const traceOfTab = await artifacts.requestTraceOfTab(trace); | ||
const networkAnalysis = await artifacts.requestNetworkAnalysis(devtoolsLog); | ||
|
||
const optimisticGraph = FirstContentfulPaint.getOptimisticGraph(graph, traceOfTab); | ||
const pessimisticGraph = FirstContentfulPaint.getPessimisticGraph(graph, traceOfTab); | ||
|
||
const options = {...networkAnalysis, ...data.throttling}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like i wonder if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, and not that I can think of. Network analysis artifact is meant to provide you a glimpse into what was observed while throttling is what you want it to be. In lantern's case we don't really care about what was observed, other than the additional latency observed per origin |
||
const optimisticEstimate = new Simulator(optimisticGraph, options).simulate().timeInMs; | ||
const pessimisticEstimate = new Simulator(pessimisticGraph, options).simulate().timeInMs; | ||
|
||
const timing = | ||
COEFFICIENTS.intercept + | ||
COEFFICIENTS.optimistic * optimisticEstimate + | ||
COEFFICIENTS.pessimistic * pessimisticEstimate; | ||
|
||
return { | ||
timing, | ||
optimisticEstimate, | ||
pessimisticEstimate, | ||
optimisticGraph, | ||
pessimisticGraph, | ||
}; | ||
} | ||
|
||
/** | ||
* @param {{trace: Object, devtoolsLog: Object, throttling: Object}} data | ||
* @return {Object} | ||
*/ | ||
async compute_(data, artifacts) { | ||
if (data.throttling.method !== 'lantern') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps we should move this to the gatherer runner? this way we don't need to explicitly do if statements inside the compute_.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm I feel like that introduces a decent amount of extra complexity to where the computed artifacts are coming from when they are already pretty hard to follow. what's the primary concern with having the ifs here in the compute? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no real reason :) just thought it was better to not think about putting ifs everywhere but maybe it's better to make it explicit 😄 |
||
const traceOfTab = await artifacts.requestTraceOfTab(data.trace); | ||
return { | ||
timing: traceOfTab.timings.firstContentfulPaint, | ||
timestamp: traceOfTab.timestamps.firstContentfulPaint, | ||
}; | ||
} | ||
|
||
return FirstContentfulPaint.computeLantern(data, artifacts); | ||
} | ||
} | ||
|
||
/** | ||
* @typedef MetricResult | ||
* @property {number} timing | ||
* @property {number|undefined} timestamp | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i know it's just an example but shouldn't this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nah it should be more like |
||
* @property {number|undefined} optimisticEstimate | ||
* @property {number|undefined} pessimisticEstimate | ||
* @property {!Node|undefined} optimisticGraph | ||
* @property {!Node|undefined} pessimisticGraph | ||
*/ | ||
|
||
module.exports = FirstContentfulPaint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd like these key names to be more in-line with the ones used for lantern, but i think you already know that. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I named them differently specifically because they operate at different levels and the amount you'd want to apply to one should be very different from the amount you'd apply to the other to get the same experience :)