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(image-elements): set 5s time budget, add _privateCssSizing #12065

Merged
merged 25 commits into from Feb 11, 2021

Conversation

paulirish
Copy link
Member

as explored in #11289, we can spend a lot of time in image-elements gathering on some pages. https://www.kohls.com is one such repro.

this PR adds a time budget the expensive parts of gathering that data.

3s is currently the ~85th percentile on LR staging.

fix #11289
also related: #12054

@paulirish paulirish requested a review from a team as a code owner February 8, 2021 21:57
@paulirish paulirish requested review from connorjclark and removed request for a team February 8, 2021 21:57
@google-cla google-cla bot added the cla: yes label Feb 8, 2021
@@ -249,6 +249,10 @@ class ImageElements extends Gatherer {
}

/**
* Images might be sized via CSS. In order to compute unsized-images failures, we need to collect
* matched CSS rules to see if this is the case.
* Perf warning: Running this for 700 elements takes 1s to 5s.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just checking: did you mean 50 elements here (the previous limit)?

3s is currently the ~85th percentile on LR staging.

I presume that is 3s for the top 50, so it is surprising that 700 would only take 1-5s.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah thx for bringing attention to these numbers.

Just checking: did you mean 50 elements here (the previous limit)?

nah i was just trying to provide some general context. though TBH the count of total imageElements doesn't correlate too well. kohls has 700 and takes 1s on my machine and 5s on LR... however like 500 of them are data uris and 495 of those are reusing network resources.
but the gatherer cost correlates with # of network-resource-associated non-css non-shadowdom images that we do these protocol calls for. (something like ~160 for kohls i think? eh.)

I'll remove this particular sentence and replace it with something more useful.

3s is currently the ~85th percentile on LR staging.

I presume that is 3s for the top 50, so it is surprising that 700 would only take 1-5s.

this 3s 85th percentile figure is accurate for staging LR, which has the top50 filter for fetchElementWithSizeInformation but not for fetchSourceRules.

700 does sound like a lot, but language selectors w/ flag sprites and other lazyload image stuff, the number can get high pretty quickly. shrug. i couldn't tell you what the distribution of ImageElements.length is for LR, but this investigation does make me curious...

lighthouse-core/gather/gatherers/image-elements.js Outdated Show resolved Hide resolved
for (let element of elements) {
// Pull some of our information directly off the network record.
const networkRecord = indexedNetworkRecords[element.src] || {};
element.mimeType = networkRecord.mimeType;

if (!element.isInShadowDOM && !element.isCss) {
// Need source rules to determine if sized via CSS (for unsized-images).
if (!element.isInShadowDOM && !element.isCss && hasBudget) {
Copy link
Member

Choose a reason for hiding this comment

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

Adding this bailout is the big change. Won't this lead to false failures? My read of

static isSizedImage(image) {
const attrWidth = image.attributeWidth;
const attrHeight = image.attributeHeight;
const cssWidth = image.cssWidth;
const cssHeight = image.cssHeight;
const widthIsValidAttribute = UnsizedImages.isValidAttr(attrWidth);
const widthIsValidCss = UnsizedImages.isValidCss(cssWidth);
const heightIsValidAttribute = UnsizedImages.isValidAttr(attrHeight);
const heightIsValidCss = UnsizedImages.isValidCss(cssHeight);
const validWidth = widthIsValidAttribute || widthIsValidCss;
const validHeight = heightIsValidAttribute || heightIsValidCss;
return validWidth && validHeight;
}

is that if fetchSourceRules isn't run, then cssWidth and cssHeight will be undefined, and so images could be falsely marked as unsized. We should probably fail the other way, not making a claim if we don't have the info?

Copy link
Member

Choose a reason for hiding this comment

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

(also it looks like we don't have any unsized-image tests with cssWidth: undefined, cssHeight: undefined. Not a huge deal because we rely on falsy behavior so the tests using the empty string are equivalent, but we should probably add a few at some point since that's an allowed state)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, I did miss this change. The previous PR was just focused on saving time for the second request for the source rules, and that's all I was looking at.

So the long tail is not that the 50 largest elements take a long time in fetchElementWithSizeInformation , but that all the elements take a long time to go thru fetchSourceRules

We should probably fail the other way, not making a claim if we don't have the info?

Should we mark these fields null to signify we don't know, and undefined if the element size is not specified (aka when getEffectiveSizingRule returns undefined)? Or put cssWidth/cssHeight into an optional property cssSizing?: {width?: string, height?: string}?

Copy link
Member Author

@paulirish paulirish Feb 10, 2021

Choose a reason for hiding this comment

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

we discussed in chat.. highlights from that:

I tend to think of undefined as equivalent to "not set" and null as equivalent to "set and has no value".

👍 👍 👍 👍 👍 👍

idea for imageElement.cssSizing obj width width/height props.. obj is appropriately null/undefined if skipped etc.
obj could include the naturalWidth bits as well and be fetchedDimensions or we keep 'em separate.

it would be nice on the next major to do a minor rethink of ImageElements to make it easier on audits using them, since naturalWidth/naturalHeight is in a similar but not quite the same situation as cssWidth/cssHeight after this change
(to add to the null/undefined confusion, don't forget the return value of el.getAttribute('notASetAttribute') :)

edit: filed in #12077

Co-authored-by: Connor Clark <cjamcl@google.com>
Copy link
Collaborator

@connorjclark connorjclark left a comment

Choose a reason for hiding this comment

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

comments etc.

@connorjclark connorjclark changed the title core(image-elements): set a time budget so we don't spent >3s gathering core(image-elements): set 3s time budget Feb 10, 2021
…ndefined. but anyway now its explicitly null. yay
@brendankenny
Copy link
Member

I don't love what feels like rushing in to a new cssSizing property in a public artifact that was just refactored, but I do love the concept of these now optional properties being contained in an optional nested object to make consumption easier. WDYT about some sort of psuedoprivate __experimentalFetchedCssRules that gives us more time to sit with it and discuss #12077 ? I'm fine with it as-is if the rest of the team wants to move forward with cssSizing though

+1. Seems like a good eng sync topic given that we all seem to be on ever so slightly different pages :)

Not smoke testing this doesn't feel good, but I have no suggestions for a good way to test it :/

Co-authored-by: Connor Clark <cjamcl@google.com>
types/artifacts.d.ts Outdated Show resolved Hide resolved
@paulirish
Copy link
Member Author

I don't love what feels like rushing in to a new cssSizing property in a public artifact that was just refactored, but I do love the concept of these now optional properties being contained in an optional nested object to make consumption easier. WDYT about some sort of psuedoprivate __experimentalFetchedCssRules that gives us more time to sit with it and discuss #12077 ? I'm fine with it as-is if the rest of the team wants to move forward with cssSizing though

+1. Seems like a good eng sync topic given that we all seem to be on ever so slightly different pages :)

works for me. renamed to _privateCssSizing for now.

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.

LGTM

/** The CSS width property of the image element. */
/**
* The CSS width property of the image element.
* @deprecated Use `element?._privateCssSizing.width` instead.
Copy link
Member

Choose a reason for hiding this comment

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

should these deprecations be TODO since we don't actually want anyone using _privateCssSizing yet?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes definitely. done.

cssHeight?: string;
/**
* The width/height of the element as defined by matching CSS rules. Set to `undefined` if the data was not collected.
* @private
Copy link
Member

Choose a reason for hiding this comment

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

seems not worth it to ts-expect-error all uses of this to keep it @private? We could actually mark this @deprecated which is kind of backwards but would discourage use and strike it out in vscode but still leave it visible for type checking. Maybe there are other options?

Copy link
Member Author

Choose a reason for hiding this comment

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

i dig it. done. and added some careful wording around it.

lighthouse-core/gather/gatherers/image-elements.js Outdated Show resolved Hide resolved
Co-authored-by: Brendan Kenny <bckenny@gmail.com>
@paulirish paulirish changed the title core(image-elements): set 5s time budget core(image-elements): set 5s time budget, add _privateCssSizing Feb 11, 2021
@devtools-bot devtools-bot merged commit 4202f19 into master Feb 11, 2021
@devtools-bot devtools-bot deleted the fasterimageelments branch February 11, 2021 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OOM in ImageElements gatherer
5 participants