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

DBW: add console.time -> performance.mark() suggestion #712

Merged
merged 10 commits into from
Sep 28, 2016
Merged

Conversation

ebidel
Copy link
Contributor

@ebidel ebidel commented Sep 28, 2016

R: @brendankenny @paulirish @GoogleChrome/lighthouse

  • Makes article naming consistent across similar audits
  • Also refactors some common code for recording function calls into driver.

screen shot 2016-09-28 at 11 24 21 am

Usage is much simpler now to record API calls:

class DateNowUse extends Gatherer {
  beforePass(options) {
    this.collectUsage = options.driver.captureJSCalls(
        'Date.now', 'window.__dateNowStackTraces');
  }
  afterPass() {
    return this.collectUsage().then(dateNowUses => {
      this.artifact.usage = dateNowUses;
    }, _ => {
      this.artifact = -1;
      return;
    });
  }
}

- Also refactors common code into driver
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.

a couple of initial thoughts :)

prev.push(err);
}
return prev;
}, []);
Copy link
Member

@brendankenny brendankenny Sep 28, 2016

Choose a reason for hiding this comment

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

could also do something like

// Filter usage from other hosts.
const results = artifacts.ConsoleTimeUsage.usage.filter(err => {
  return url.parse(err.url).host === pageHost;
}).map(err => `(line: ${err.line}, col: ${err.col})`);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

err.misc = `(line: ${err.line}, col: ${err.col})`;
return err;
});
}, []);
Copy link
Member

Choose a reason for hiding this comment

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

sames

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

beforePass(options) {
const driver = options.driver;
return driver.evaluateScriptOnLoad(`
window.__consoleTimeStackTraces = new Set();
Copy link
Member

@brendankenny brendankenny Sep 28, 2016

Choose a reason for hiding this comment

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

this is still a bit fiddly on the caller's side.

Could more of this be pulled into driver? Maybe captureAPIUsage could set up the collecting as it does here but also handles the evaluateScriptOnLoad part and returns a collectUsage function that could be called whenever (probably usually in afterPass) that takes care of fetching the array of callsites for you? all without having to know that you're evaluating a script, that the callsites are on __consoleTimeStackTraces in the host page, etc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But doesn't the evaluateScriptOnLoad stuff need to be setup in beforePass? Or can that be pushed off until afterPass time?

Copy link
Member

@brendankenny brendankenny Sep 28, 2016

Choose a reason for hiding this comment

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

yeah, I meant like, in beforePass, have driver.captureAPIUsage().then(collectUsage => this.collectUsage = collectUsage) and then in afterPass have something like return this.collectUsage.then(uses => this.artifact = uses) or whatever

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, exactly what I ended up doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@ebidel
Copy link
Contributor Author

ebidel commented Sep 28, 2016

PTAL

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.

reviewed!

return url.parse(err.url).host === pageHost;
}).map(err => {
err.misc = `(line: ${err.line}, col: ${err.col})`;
return err;
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't mutate the errors in the artifact. Maybe

return Object.assign({
  misc: `(line: ${err.line}, col: ${err.col})`
}, err);

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// Filter usage from other hosts.
const results = artifacts.DateNowUse.usage.filter(err => {
return url.parse(err.url).host === pageHost;
}).map(err => {
Copy link
Member

Choose a reason for hiding this comment

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

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* on the global object.
* @return {Function} A wrapper around the original function.
*/
static captureUsage(func, set) {
Copy link
Member

@brendankenny brendankenny Sep 28, 2016

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 should be in the Driver class since it's not a method for it. One option would be to define it outside of the class body to make that clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


/**
* Tracks function call usage. Used by captureJSCalls to inject code into the page.
* @param {Function} func The function call to track.
Copy link
Member

@brendankenny brendankenny Sep 28, 2016

Choose a reason for hiding this comment

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

could do {function(...*): *}, I think

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

/**
* Tracks function call usage. Used by captureJSCalls to inject code into the page.
* @param {Function} func The function call to track.
* @param {Set} set An empty set to populate with stack traces. Should be
Copy link
Member

Choose a reason for hiding this comment

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

{!Set}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* Keeps track of calls to a JS function and returns a list of {url, line, col}
* of the usage.
* @param {string} funcVar The function name to track ('Date.now', 'console.time').
* @param {string} variableToPopulate The variable name to populate with stack traces.
Copy link
Member

@brendankenny brendankenny Sep 28, 2016

Choose a reason for hiding this comment

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

couldn't captureJSCalls just generate the variable name? That prevents the gatherers from having to care the variables each other are using

Copy link
Member

Choose a reason for hiding this comment

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

aye. left a comment along those lines, too.

captureJSCalls(funcVar, variableToPopulate) {
const collectUsage = () => {
return this.evaluateAsync(
`(__returnResults(Array.from(${variableToPopulate}).map(item => JSON.parse(item))))`);
Copy link
Member

Choose a reason for hiding this comment

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

do you need surrounding parens?


/**
* Keeps track of calls to a JS function and returns a list of {url, line, col}
* of the usage.
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 comment on usage? (must be called before page load, mostly)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* @param {string} funcVar The function name to track ('Date.now', 'console.time').
* @param {string} variableToPopulate The variable name to populate with stack traces.
* This should unique and on the global object ('window.__dateNowStackTraces').
* @return {Function} Call this method when you want results.
Copy link
Member

Choose a reason for hiding this comment

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

@return {function(): !Promise<!Array<{url: string, line: number, col: number}>>} :)

Copy link
Contributor

Choose a reason for hiding this comment

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

@return {function(): !Promise<!Array<!{url: string, line: number, col: number}>>}

unless the contents of the array can be null also ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Member

Choose a reason for hiding this comment

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

unless the contents of the array can be null also ;)

record types are non-nullable by default :P


afterPass() {
return this.collectUsage().then(consoleTimeUsage => {
this.artifact.usage = consoleTimeUsage;
Copy link
Member

Choose a reason for hiding this comment

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

do these have to saved on this.artifact.usage? Could also just save on this.artifact itself

Copy link
Contributor Author

@ebidel ebidel Sep 28, 2016

Choose a reason for hiding this comment

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

I like the consistency between audits of this natural. Always accessing the same .usage array.


/**
* Tracks function call usage. Used by captureJSCalls to inject code into the page.
* @param {Function} func The function call to track.
Copy link
Member

Choose a reason for hiding this comment

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

func to funcRef, just to be clearer it's not the string name of it anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@brendankenny
Copy link
Member

what about captureFunctionCallSites instead of captureJSCalls just to be more precise?

`(__returnResults(Array.from(${variableToPopulate}).map(item => JSON.parse(item))))`);
};

this.evaluateScriptOnLoad(`
Copy link
Member

@paulirish paulirish Sep 28, 2016

Choose a reason for hiding this comment

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

I'm having a hard time parsing this magic. can we split it out a tad more?

I think this is basically what we're doing, eh?

 this.evaluateScriptOnLoad(`
        ${variableToPopulate} = new Set();
        function ${Driver.captureUsage.toString()};
        ${funcVar} = ${Driver.captureUsage.name}(${funcVar}, ${variableToPopulate});
`);

Copy link
Member

Choose a reason for hiding this comment

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

Slightly more important is renaming funcVar to funcName since it's in string form until the template literal is evaluated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* This should unique and on the global object ('window.__dateNowStackTraces').
* @return {Function} Call this method when you want results.
*/
captureJSCalls(funcVar, variableToPopulate) {
Copy link
Member

@paulirish paulirish Sep 28, 2016

Choose a reason for hiding this comment

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

Kind of prefer an options object for API here.. but what do you guys think?

driver.captureJSCalls({
  funcName: 'console.time',
  globalVarName: 'window.__consoleTimeStackTraces'
})

alternatively we can leave it alone because i can imagine we can later refactor out this second argument from the exposed API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

given there's only two args right now, I think it's fine.

@paulirish
Copy link
Member

this is definitely lgtm after this round.

* @return {function(): !Promise<!Array<{url: string, line: number, col: number}>>}
* Call this method when you want results.
*/
captureFunctionCallSites(funcName, globalVarToPopulate) {
Copy link
Member

@brendankenny brendankenny Sep 28, 2016

Choose a reason for hiding this comment

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

why not just, like, const globalVarToPopulate = window['__${funcName}StackTraces']`` and then you'll get it for free and for most funcNames you'll be sure no one else will be overwriting it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea that works

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@ebidel
Copy link
Contributor Author

ebidel commented Sep 28, 2016

PTAL

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.

Looks good. Great addition to driver

@brendankenny brendankenny merged commit 307a296 into master Sep 28, 2016
@paulirish
Copy link
Member

Boom.

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