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: merge render blocking audits to lantern #4995

Merged
merged 7 commits into from
Apr 25, 2018

Conversation

patrickhulce
Copy link
Collaborator

@patrickhulce patrickhulce commented Apr 19, 2018

So this turned out to be quite a doozy 😩

Changes in this PR:

  • Fixes a bug in byte-efficiency-audit base where the default lantern settings were always used regardless of settings
  • Adds HTML imports to Lantern's render blocking definition
  • Merges render blocking links/scripts into single audit
  • Changes the savings calculation to be based on lantern
    • What's the same? We still pass our smoketests and identify all the same things as render blocking! 🎉

What's different about the savings calculation?

  • Previously we would say that you save Math.max(...downloadTimes) but this wasn't really accurate since you pretty much have to remove all render blocking resource to see this benefit.
  • Additionally, this assumed that the code is just deferred and you don't need it at all. The new approach is computing "how much faster would this page reach FCP if we inlined all the used CSS from render blocking stylesheets and deferred all scripts" as a result the savings we report are slightly more conservative but much more accurate.
  • This also means that H2 sites will basically never be flagged since the simulation treats H2 in essentially this way. Doesn't seem so bad though considering the only savings you're seeing with H2 is ~1 RT which in our settings is just 150ms.
  • Our fancy amp-style detection is less relevant since we don't actually identify savings from addressing a single resource, just all in aggregate and this should be much more conservative. It does mean though that we might slightly overstate savings if AMP-style mitigation didn't kick in on unthrottled but would on throttled.

ref #4333

@paulirish
Copy link
Member

Merges render blocking links/scripts into single audit

I think we should keep them separate. :)

Handling RB stylesheets is done via reducing, inlining and the critical CSS dance.Handling RB scripts involved a bunch of different techniques. I think they're quite distinct in how you have to reason about them, even though at a browser level they are similar. So i feel its still valuable to report on them individually. Also getting to avoid the super generic "resources" is great..

@paulirish
Copy link
Member

Discussed this more. Here are the options that we could do:

  1. Start with what's in master. Use lantern for the download timing calculation.
  2. Use this PR, mostly as is. single RB audit instead of the current 2.
  3. Use this PR, but show it as two (CSS & JS). Savings will be the same number. Docs optionally explain that you need to fix both to see the savings.
  4. Take it to the next level. This audit becomes a "Inline critical render-blocking stuff and defer the rest". We use code coverage to calculate how much of the JS/CSS is needed and then calculate if that was inlined and then the rest are deferred. Most sophisticated, but accurate estimation and the user recommendation is very clear.

@patrickhulce
Copy link
Collaborator Author

@paulirish says

let's do 4

on it.

@patrickhulce
Copy link
Collaborator Author

gentle reminder this is awaitin' review once again :)

});

graphWithoutChildren.record._transferSize += totalChildNetworkBytes;
const estimateAfterInlineA = simulator.simulate(graphWithoutChildren);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A?
And why not just pull the property off this in one-line?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

estimateAfterInlining

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just leftover from debugging, fixed

* @param {Map<string, number>} wastedBytesMap
* @return {number}
*/
static estimateSavingsFromInlining(simulator, fcpGraph, deferredIds, wastedBytesMap) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some comments? it's taking me a long time to grok what's happening within here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Estimate savings from deferring render-blocking requests and inlining used CSS into the HTML response.

Copy link
Member

@paulirish paulirish Apr 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a lot of what you wrote in the awesome PR description can be moved into comments. 😀

like

the new approach is computing "how much faster would this page reach FCP if we inlined all the used CSS from render blocking stylesheets and deferred all scripts" as a result the savings we report are slightly more conservative but much more accurate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 done

// i.e. the referenced font asset won't become inlined just because you inline the CSS
node.traverse(node => deferredNodeIds.add(node.id));

const wastedMs = nodeTiming.endTime - nodeTiming.startTime;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wasted cost of each request is from requestSent to responseReceived

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw do you want to round this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const willDefer = deferredIds.has(node.id);
if (willDefer && node.type === Node.TYPES.NETWORK &&
node.record._resourceType === WebInspector.resourceTypes.Stylesheet) {
const wastedBytes = wastedBytesMap.get(node.record.url) || 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assume we defer the network request till later, and mark it non-blocking

right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya, comment added

return !willDefer;
});

graphWithoutChildren.record._transferSize += totalChildNetworkBytes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add inlined bytes back to the HTML response

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @param {Map<string, number>} wastedBytesMap
* @return {number}
*/
static estimateSavingsFromInlining(simulator, fcpGraph, deferredIds, wastedBytesMap) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also since the inlining only applies to CSS but this affects both JS and CSS it should be named something that's more balanced. estimateSavingsFromDeferring sg. wdyt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it's doing both, I just renamed to estimateSavingsWithGraphs and added better description 👍

const originalEstimate = simulator.simulate(fcpGraph).timeInMs;

let totalChildNetworkBytes = 0;
const graphWithoutChildren = fcpGraph.cloneWithRelationships(node => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it called this btw? can't really make sense of it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was leftover from when everything was inlined, changed to minimalFCPGraph

* @param {Map<string, number>} wastedBytesMap
* @return {number}
*/
static estimateSavingsFromInlining(simulator, fcpGraph, deferredIds, wastedBytesMap) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And can you also add a comment that we don't inline JS because we assume they can get a [defer] attribute

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const fcpTsInMs = traceOfTab.timestamps.firstContentfulPaint / 1000;

const nodeTimingMap = fcpSimulation.optimisticEstimate.nodeTiming;
const nodesByUrl = keyByUrl(Array.from(nodeTimingMap.keys()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we instead just have a helper function of like getNodeTimingForUrl(nodeTimingMap, url){ ..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the helper function loops through the keys to find the matching record?

const fcpSimulation = await artifacts.requestFirstContentfulPaint(metricComputationData);
const fcpTsInMs = traceOfTab.timestamps.firstContentfulPaint / 1000;

const nodeTimingMap = fcpSimulation.optimisticEstimate.nodeTiming;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side note i wish this was called nodeTimings or nodeTimingMap to differentiate from the singular case within it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we can rename in separate PR 👍

@patrickhulce
Copy link
Collaborator Author

@paulirish all done 'cept the helper method business, not 100% I understood what that wins us

@patrickhulce patrickhulce merged commit b1ecfac into master Apr 25, 2018
@patrickhulce patrickhulce deleted the combine_render_blocking branch April 25, 2018 19:44
Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently too slow


const {node, nodeTiming} = nodesByUrl[resource.tag.url];

// Mark this node and all it's dependents as deferrable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its

static async computeWastedCSSBytes(artifacts, context) {
const wastedBytesByUrl = new Map();
try {
const results = await UnusedCSS.audit(artifacts, context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a TODO that this should be pulled out into a computed artifact?

for (const item of results.details.items) {
wastedBytesByUrl.set(item.url, item.wastedBytes);
}
} catch (_) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats the failure mode here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assume all bytes were used and inline all CSS

const simulatorData = {devtoolsLog, settings: context.settings};
const traceOfTab = await artifacts.requestTraceOfTab(trace);
const simulator = await artifacts.requestLoadSimulator(simulatorData);
const wastedBytesMap = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wastedCssBytesMap? Or it's more like cssBytesByUrl or something in function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants