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

feat(redirects-audit): Adding Redirect audit (PSI Compat) #3308

Merged
merged 17 commits into from
Oct 5, 2017

Conversation

wardpeet
Copy link
Collaborator

@wardpeet wardpeet commented Sep 12, 2017

Fixes #3210
fixes #605

Looking at psi it allows 1 redirect (not sure if we should only consider subdomains as allowed redirects).
https://example.com -> https://m.example.com

Report:
image

Perhaps we should use start & end time to calculate wasted ms?

@patrickhulce
Copy link
Collaborator

nice! using the time taken for everything but the last request (minus TCP handshake if its the same domain) as wasted ms sounds good to me

@wardpeet
Copy link
Collaborator Author

do we need a smoke test?

@brendankenny
Copy link
Member

Ideally yes, though we'd have to add some kind of redirect support to the test server?

Alternatively, some of the PWAs for the smokehouse run on live sites were chosen because they redirect. We could expand pwa-config.js to include the redirect test to smoke test it

@brendankenny
Copy link
Member

if it ends up being complicated, we can do in a followup to keep the PR single-purpose

displayValue: pageRedirects.length,
rawValue: passed,
extendedInfo: {
value: pageRedirects
Copy link
Member

Choose a reason for hiding this comment

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

yah lets calculated the wasted time. we can then show this in opportunities rather than diagnostics.

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

a few more things noted. still thinking this should move to Opportunities with a wastedMs calculation.

const Audit = require('./audit');

// PSI allows one redirect (http://example.com => http://m.example.com)
const REDIRECT_TRESHOLD = 1;
Copy link
Member

Choose a reason for hiding this comment

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

THRESHOLD*

const passed = pageRedirects.length <= REDIRECT_TRESHOLD;
if (!passed) {
debugString = `Your page has ${pageRedirects.length} redirects.` +
' Redirects introduce additional delays before the page can be loaded.';
Copy link
Member

Choose a reason for hiding this comment

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

I guess we dont need this second sentence since its already in helptext

@wardpeet
Copy link
Collaborator Author

Local test page: (smokehouse)
image

Gmail looks like this:
image

Just a random website with 1 redirect looks like thise:
image

Zero redirects
image

}

static isRedirect(requestKey) {
return requestKey.includes('redirected');
Copy link
Collaborator

@kdzwinel kdzwinel Sep 21, 2017

Choose a reason for hiding this comment

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

it looks like we depend here on 'redirected' being used in the name of request ID. IMO it'd be better to use something more solid likeWebInspector.NetworkRequest.redirects or request status code.

Since I'm dealing with a similar thing here: #3311 . How about creating a MainResource computed artifact that exposes WebInspector.NetworkRequest.redirects that we both could use?

screen shot 2017-09-21 at 17 28 27

cc @brendankenny

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@kdzwinel Should we name it more like getMainResourceChain?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@kdzwinel I think we have a slightly different use case :) I need to get the redirects as well so I cannot use the computed artifact

Copy link
Collaborator

@kdzwinel kdzwinel Sep 26, 2017

Choose a reason for hiding this comment

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

@wardpeet maybe I'm missing something, but when you request a MainResource artifact you also get all NetworkRequest (that contain all timing info) representing all redirects that led to that resource. Check out this:

Isn't that all you need to compute the time wasted in redirects?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ah sorry I missed that! Great!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Awesome 🙌 I already got one 👍 on my PR, so I hope to merge soon.

Copy link
Collaborator

Choose a reason for hiding this comment

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

MainResource computed artifact sgtm, we'll prob want to move away from more "chain-like" computed artifacts though going forward and try to make use of the page-dependency-graph instead

@Metaway Metaway mentioned this pull request Sep 21, 2017
const cacheBuster = Number(new Date());
module.exports = [
{
initialUrl: `http://localhost:10200/online-only.html?cb=${cacheBuster}&delay=500&redirect=%2Foffline-only.html%3Fcb=${cacheBuster}%26delay=500%26redirect%3D%2Fredirects-final.html`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

ha this is quite the URL :)

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

sweet I like the look of this one!

url: 'http://localhost:10200/redirects-final.html',
audits: {
'redirects': {
score: 2,
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's assert on details length instead

url: 'http://localhost:10200/redirects-final.html',
audits: {
'redirects': {
score: 1,
Copy link
Collaborator

Choose a reason for hiding this comment

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

same


return {
debugString,
score: pageRedirects.length,
Copy link
Collaborator

Choose a reason for hiding this comment

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

score actually has meaning for the opportunities and is used to color the bar based on how bad the offending opportunity was, they can still get this information from the details length

@wardpeet
Copy link
Collaborator Author

@patrickhulce Updated the PR :) what do you think?

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

thanks for the quick turnaround @wardpeet! :)

@@ -0,0 +1,32 @@
/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

revert these? :)


return {
debugString,
score: passed,
Copy link
Collaborator

@patrickhulce patrickhulce Sep 29, 2017

Choose a reason for hiding this comment

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

oops, maybe I wasn't very clear last time my bad!

score is automatically determined for the performance opportunities, let's reuse that

score: UnusedBytes.scoreForWastedMs(wastedMs),

/**
* @param {number} wastedMs
* @return {number}
*/
static scoreForWastedMs(wastedMs) {
if (wastedMs === 0) return 100;
else if (wastedMs < WASTED_MS_FOR_AVERAGE) return 90;
else if (wastedMs < WASTED_MS_FOR_POOR) return 65;
else return 0;
}

url: 'http://localhost:10200/redirects-final.html',
audits: {
'redirects': {
score: false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

once the numeric score is brought back, let's assert that this score is in the lowest bracket

let debugString = null;

const pageRedirects = redirectRequests.map(request => {
const wastedMs = (request.endTime - request.startTime) * 1000;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is what we want here, from the screenshot of gmail it looks like request.endTime is the end time of the final mainResource, I think we want something more like requests[1].startTime - requests[0].startTime?

};
});

const passed = pageRedirects.length <= REDIRECT_THRESHOLD;
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's go ahead and nuke the passed and debugString now that passed isn't needed, hopefully its clear from the table how many redirects they had :)


const headings = [
{key: 'url', itemType: 'text', text: 'URL'},
{key: 'wastedMs', itemType: 'text', text: 'Wasted ms'},
Copy link
Collaborator

@patrickhulce patrickhulce Sep 30, 2017

Choose a reason for hiding this comment

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

nit: let's set the text to "Time for Redirect"

@wardpeet
Copy link
Collaborator Author

wardpeet commented Oct 1, 2017

should have fixed everything.

One issue I found is that the displayValue is not the same as when you make a sum of all separate wastedMs in the table and this is due the formatMilliseconds which rounds the numbers in the table
image

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

looks great! I'm ready to approve % open question about how to handle totalWastedMs :)

name: 'redirects',
description: 'Avoids page redirects.',
failureDescription: 'Has page redirects.',
helpText: ' Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/speed/docs/insights/AvoidRedirects).',
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: remove space at beginning

const request = redirectRequests[i - 1];
const nextRequest = redirectRequests[i];
const wastedMs = (nextRequest.startTime - request.startTime) * 1000;
totalWastedMs += wastedMs;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I liked what you had about allowing a single redirect before like PSI, wdyt about excluding the first redirect cost from totalWastedMs but still reporting how long it took?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like as we should report all redirects as a cost is a cost +1

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

nicely done! 🎉 💯

to you @paulirish if you still have lingering requested changes

category: 'Performance',
name: 'redirects',
description: 'Avoids page redirects.',
failureDescription: 'Has page redirects.',
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I'd prefer adding an 'excessive' or some qualifier here so it's clear you can have one

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

😱 I'm not that good with words in english so what about
Has more than one page redirects?

Copy link
Collaborator

Choose a reason for hiding this comment

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

heh sure Has more than one page redirect 👍

audits: {
'redirects': {
score: 100,
rawValue: '>=0',
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: we can assert 0 exactly here, right?

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.

sorry, drive-by nits on just the smoke testing part :)

@@ -51,7 +51,8 @@ function requestHandler(request, response) {
}

function sendResponse(statusCode, data) {
let headers;
let headers = {};
Copy link
Member

Choose a reason for hiding this comment

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

this change not needed anymore?

@@ -51,7 +51,8 @@ function requestHandler(request, response) {
}

function sendResponse(statusCode, data) {
let headers;
let headers = {};
let delay = 0;
Copy link
Member

Choose a reason for hiding this comment

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

maybe move delay declaration to below the filePath.endsWith set of conditionals since it's not used until inside if (queryString) {}?


// redirect url to new url if present
if (typeof queryString.redirect !== 'undefined') {
if (delay > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

since delay defaults to 0, for code readability this could just drop the delay check and do return setTimeout(sendRedirect, delay, queryString.redirect); for both

}

response.writeHead(statusCode, headers);

// Delay the response by the specified ms defaulting to 2000ms for non-numeric values
if (queryString && typeof queryString.delay !== 'undefined') {
response.write('');
Copy link
Member

Choose a reason for hiding this comment

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

was response.write(''); important here? I have no idea :) but it was here since the first version

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so as response.write(''); as response.end() is important to close the request.
maybe @paulirish still knows why he added it.

Copy link
Member

Choose a reason for hiding this comment

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

i barely remember it being important. is it fine if we keep it as is?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

keep it as is = add the response.write('')?

Copy link
Member

Choose a reason for hiding this comment

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

okay scratch what i said. response.write('') is a noop.

.write('.') does change things quite considerably, but in a bad direction. (the latency moves from TTFB to content download).

your change here is great. let's keep it. :)


module.exports = [
{
initialUrl: `http://localhost:10200/online-only.html?cb=${cacheBuster}&delay=500&redirect=%2Foffline-only.html%3Fcb=${cacheBuster}%26delay=500%26redirect%3D%2Fredirects-final.html`,
Copy link
Member

Choose a reason for hiding this comment

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

smokehouse should be opening a new instance of chrome for each expectations entry, so are the cache busters necessary?

Copy link
Collaborator Author

@wardpeet wardpeet Oct 4, 2017

Choose a reason for hiding this comment

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

Probably not 👍

@paulirish
Copy link
Member

I verified the numbers we're reporting:

image

(I updated the table below via contenteditable, dont freak out)
image

This list does leave off the last URL destination. (which is accounts.google.com/...), Which is kind of awkward. I'm going to try to tweak the table rendering to also include that.

Numbers are good. 👍

@wardpeet
Copy link
Collaborator Author

wardpeet commented Oct 5, 2017

@paulirish I can add it to the table :) give me a sec 😸

@GoogleChrome GoogleChrome deleted a comment from googlebot Oct 5, 2017
Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

I have some proposed superficial changes. I'd like to merge this PR as it is and i'll followup with a PR for ward to review.

}

response.writeHead(statusCode, headers);

// Delay the response by the specified ms defaulting to 2000ms for non-numeric values
if (queryString && typeof queryString.delay !== 'undefined') {
response.write('');
Copy link
Member

Choose a reason for hiding this comment

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

okay scratch what i said. response.write('') is a noop.

.write('.') does change things quite considerably, but in a bad direction. (the latency moves from TTFB to content download).

your change here is great. let's keep it. :)

@paulirish paulirish merged commit b074ac7 into GoogleChrome:master Oct 5, 2017
@wardpeet wardpeet deleted the feature/audit-redirects branch October 6, 2017 05:45
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.

Audit: Avoid landing page redirects Count and report on document redirects
5 participants