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

Add TTI < 10s audit for PWA #1840

Merged
merged 7 commits into from
Mar 22, 2017
Merged

Add TTI < 10s audit for PWA #1840

merged 7 commits into from
Mar 22, 2017

Conversation

paulirish
Copy link
Member

Basic audit that just checks TTI value against a threshold.

Current UI is
image

Note: I have this audit included in default.json here, but I will remove it before landing this PR. It's just there to make testing a bit easier. :)

Once landed, I will move the other perf metrics from PWA to Perf, leaving only this one in PWA.

Fixes #1831

if (!isFast) {
return LoadFastEnough4Pwa.generateAuditResult({
rawValue: false,
debugString: `Under mobile conditions, the TTI was at ${ttiResult.displayValue}. ` +
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need a debug string here?
also feels a little weird we're asserting here that everything was under mobile conditions when we don't really know it was :) thoughts on adding some fancy check for network throughput AND TTI matching certain conditions?

Copy link
Contributor

Choose a reason for hiding this comment

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

Very good point. I'd hate for people to setup a CI, leave off emulation, and always pass this audit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a unit after ${ttiResult.displayValue}?

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 like this idea. SG.


return FastPWAAudit.audit(generateArtifactsWithTrace(traceEvents)).then(result => {
assert.equal(result.rawValue, false, 'not failing a long TTI value');
TTIAudit.audit = origTTI;
Copy link
Collaborator

Choose a reason for hiding this comment

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

will this mess anything else up if it fails? should we restore original method in catch too?

Copy link
Member Author

Choose a reason for hiding this comment

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

Was thinking about that! Maybe move to before each etc..

Copy link
Member

Choose a reason for hiding this comment

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

another reason why we need randomized test order :)

if (!isFast) {
return LoadFastEnough4Pwa.generateAuditResult({
rawValue: false,
debugString: `Under mobile conditions, the TTI was at ${ttiResult.displayValue}. ` +
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a unit after ${ttiResult.displayValue}?

return LoadFastEnough4Pwa.generateAuditResult({
rawValue: false,
debugString: `Under mobile conditions, the TTI was at ${ttiResult.displayValue}. ` +
'More details in "Performance" section.',
Copy link
Contributor

Choose a reason for hiding this comment

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

fancy link? More details in ["Performance" section](#performance).

Copy link
Member Author

Choose a reason for hiding this comment

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

can't do markdown in debugText. can only do it in helpText. :/

Copy link
Member Author

Choose a reason for hiding this comment

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

and displayValue already has a unit in it. :)

@@ -0,0 +1,62 @@
/**
* @license Copyright 2017 Google Inc. All Rights Reserved.
Copy link
Member

Choose a reason for hiding this comment

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

this is hideous

Copy link
Member

Choose a reason for hiding this comment

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

but for reals, I think we should have one style or the other, and not mix them based on who wrote the file :)

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 am very happy to change the codebase in one full sweep.

Copy link
Member

Choose a reason for hiding this comment

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

i am very happy to change the codebase in one full sweep.

sounds perfect for another PR :P


return FastPWAAudit.audit(generateArtifactsWithTrace(traceEvents)).then(result => {
assert.equal(result.rawValue, false, 'not failing a long TTI value');
TTIAudit.audit = origTTI;
Copy link
Member

Choose a reason for hiding this comment

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

another reason why we need randomized test order :)

// https://developers.google.com/web/progressive-web-apps/checklist
const MAXIMUM_TTI = 10 * 1000;

class LoadFastEnough4Pwa extends Audit {
Copy link
Member

Choose a reason for hiding this comment

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

also hideous :P

'use strict';

const Audit = require('./audit');
const TTIMetric = require('./time-to-interactive');
Copy link
Member

Choose a reason for hiding this comment

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

we should really just make a "page timing" computed artifact and move both FMP and TTI in there

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I have need for it now too in OffscreenImages thanks to @brendankenny obnoxiously useful suggestion :P

Copy link
Member Author

Choose a reason for hiding this comment

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

TBH i think audits depending on audits is almost the same as computed artifacts depending on eachother.

and i'm kinda less interested in making one big "page timing" one, as it'd have to run TTI tracing model stuff even if you only want FMP.

Copy link
Collaborator

Choose a reason for hiding this comment

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

but now don't we have to run the TTI tracing model stuff 3x instead of just once?

Copy link
Member Author

Choose a reason for hiding this comment

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

gotta rerun parts of it, but the tracing model is a comptued artifact.

i profiled a run and reruning the TTI.audit() probably is like 3ms max, but we have like 6000ms of main thread time total.

const mockTTIResult = {extendedInfo: {value: {timings: {timeToInteractive: 15000}}}};
TTIAudit.audit = _ => Promise.resolve(mockTTIResult);

return FastPWAAudit.audit(generateArtifactsWithTrace(traceEvents)).then(result => {
Copy link
Member

Choose a reason for hiding this comment

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

why not just pass a trace in? If there was a trace with one really really long top level event it should fail the test :)

@paulirish
Copy link
Member Author

Okay! Added checks to make sure network traffic was sufficiently slow.

Screenshots of the two failure cases below:
image
image

Test changed to monkeypatch TTI audit, as creating fake traces involves screenshots, etc.

ptal

});
});

it('fails a bad TTI value', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

add a quick test for the network case?

Copy link
Member Author

Choose a reason for hiding this comment

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

quick you say..

Copy link
Member Author

Choose a reason for hiding this comment

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

added!

category: 'PWA',
name: 'load-fast-enough-for-pwa',
description: 'Page load is fast enough on 3G',
helpText: 'Satisfied if the _Time To Interactive_ duration is shorter than _10 seconds_, as defined by the [PWA Baseline Checklist](https://developers.google.com/web/progressive-web-apps/checklist).',
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe add a note here too that emulation must be enabled?

@@ -0,0 +1,62 @@
/**
* @license Copyright 2017 Google Inc. All Rights Reserved.
Copy link
Member

Choose a reason for hiding this comment

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

but for reals, I think we should have one style or the other, and not mix them based on who wrote the file :)

static audit(artifacts) {
const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
const allRequestLatencies = networkRecords.map(record => {
if (!record._timing) return Infinity;
Copy link
Member

Choose a reason for hiding this comment

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

why default with Infinity and not e.g. 0?

Copy link
Member Author

Choose a reason for hiding this comment

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

because if there is no timing data on a request then we don't care about it. we don't want to consider it for this analysis.

i'm also happy to filter netRecords to only the ones with timing first.

Copy link
Member Author

Choose a reason for hiding this comment

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

or alternatively the allRequestLatencies.every(val => val > latency3gMin) thing will return true if val is undefined.

Copy link
Member

Choose a reason for hiding this comment

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

definitely don't want to depend on implicit NaN behavior. I was just wondering, but filtering does sound like it might be clearer (plus Infinity doesn't make it through JSON.stringify(), so it makes sense if including the latencies in the extendedInfo)


const extendedInfo = {
formatter: Formatter.SUPPORTED_FORMATS.NULL,
value: {allRequestLatencies, timeToInteractive}
Copy link
Member

Choose a reason for hiding this comment

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

do we care about having either one of these in the extended info? timeToInteractive may make sense, I guess (e.g. if you run the PWA checks but not the perf ones), but probably not the latencies

Copy link
Member Author

Choose a reason for hiding this comment

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

shrugs.
i am just thinking that people will probably want to debug failures here ( in case throttling messed up or they are trying to use diff throttling or real cellular )...

and if we know that A BUNCH of their reqs were slow enough but 1 had a latency that barely missed things, then we can change our policy here.

Copy link
Member

Choose a reason for hiding this comment

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

i am just thinking that people will probably want to debug failures here ( in case throttling messed up or they are trying to use diff throttling or real cellular )...

and if we know that A BUNCH of their reqs were slow enough but 1 had a latency that barely missed things, then we can change our policy here.

OTOH things only get added to the results object, no one really gets around to trimming unused data out of extendedInfo, but fair enough :P

});

const latency3gMin = Emulation.settings.TYPICAL_MOBILE_THROTTLING_METRICS.latency - 10;
const areLatenciesAll3G = allRequestLatencies.every(val => val > latency3gMin);
Copy link
Member

Choose a reason for hiding this comment

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

if you don't include areLatenciesAll3G in the extendedInfo, maybe pull this all into a areLatenciesAll3G(networkRecords) function?

Copy link
Member

Choose a reason for hiding this comment

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

incidentally, this is a really cool way of checking that it's a sufficiently slow connection :)

@paulirish
Copy link
Member Author

PTAL. updated.

@paulirish
Copy link
Member Author

@ebidel @brendankenny can i get an amen?

@@ -0,0 +1,62 @@
/**
* @license Copyright 2017 Google Inc. All Rights Reserved.
Copy link
Member

Choose a reason for hiding this comment

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

i am very happy to change the codebase in one full sweep.

sounds perfect for another PR :P

*/

const Audit = require('./audit');
const TTIMetric = require('./time-to-interactive');
Copy link
Member

Choose a reason for hiding this comment

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

calling into another audit really is an architecture smell, but we can fix that in a follow up

static audit(artifacts) {
const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS];
const allRequestLatencies = networkRecords.map(record => {
if (!record._timing) return Infinity;
Copy link
Member

Choose a reason for hiding this comment

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

definitely don't want to depend on implicit NaN behavior. I was just wondering, but filtering does sound like it might be clearer (plus Infinity doesn't make it through JSON.stringify(), so it makes sense if including the latencies in the extendedInfo)

TTIAudit.audit = generateTTIResults(5000);
return FastPWAAudit.audit(generateArtifacts()).then(result => {
assert.equal(result.rawValue, true, 'fixture trace is not passing audit');
}).catch(err => {
Copy link
Member

Choose a reason for hiding this comment

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

don't need catch block (here and below), test will fail if a rejected promise is returned


const extendedInfo = {
formatter: Formatter.SUPPORTED_FORMATS.NULL,
value: {allRequestLatencies, timeToInteractive}
Copy link
Member

Choose a reason for hiding this comment

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

i am just thinking that people will probably want to debug failures here ( in case throttling messed up or they are trying to use diff throttling or real cellular )...

and if we know that A BUNCH of their reqs were slow enough but 1 had a latency that barely missed things, then we can change our policy here.

OTOH things only get added to the results object, no one really gets around to trimming unused data out of extendedInfo, but fair enough :P

Copy link
Contributor

@ebidel ebidel left a comment

Choose a reason for hiding this comment

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

Amen from me.

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

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.

None yet

4 participants