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

Measure and visualize perf metrics #1936

Merged
merged 3 commits into from
Apr 7, 2017

Conversation

wwwillchen
Copy link
Contributor

@wwwillchen wwwillchen commented Mar 30, 2017

Follow-up work:

  • Do a side-by-side perf metrics comparison with a previous version of Lighthouse (allows you to easily see how a commit will change the metrics on real-world sites)
  • Run this on a compute instance in Asia to test the hypothesis that the high variance for international sites is driven by network latency

Fixes part of #1924

@paulirish paulirish mentioned this pull request Mar 30, 2017
52 tasks
@@ -79,6 +79,10 @@ class TTIMetric extends Audit {

const fmpTiming = fMPtsInMS - navStartTsInMS;

const traceEndTsInMS = trace.traceEvents.reduce(
(acc, traceEvent) => Math.max(acc, traceEvent.ts), 0) / 1000;
Copy link
Member

Choose a reason for hiding this comment

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

lgtm. we have tabTrace.processEvents, which is already sorted, but it's missing other processes (like browser, in particular). After considering it, we definitely want the last last TS in the trace, regardless of process. So let's do it this way. :)

Also this Math.max reduce is crazy fast. On a 61MB trace, this completed in 15ms. :) Meanwhile a sort() took ~160ms.

```
# View all commands
$ cd lighthouse-plots
$ npm run
Copy link
Member

Choose a reason for hiding this comment

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

let's upgrade the hipster value, shall we @wwwillchen ? :)

yarn run
yarn measure
yarn analyze

Copy link
Member

Choose a reason for hiding this comment

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

let's upgrade the hipster value

people running yarn generally know how to translate npm commands to yarn. I'd wager there are many people who use yarn who don't know how to translate the other way (or at the very least, would have to look it up)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@brendankenny brings up a good point - I'll leave these alone for now.

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.

awesome stuff!!

*/
async function singleRunAnalysis(url, id, ignoreRun) {
const parsedURL = parseURL(url);
const host = `${parsedURL.host}-${parsedURL.pathname}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: maybe just string.replace(/[./]+/g, '-')

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.

can we call this filenameHost or something?

Also I wrote this bit for traceviewer in case you want to reuse:

      var illegalRe = /[\/\?<>\\:\*\|":]/g;
      var controlRe = /[\x00-\x1f\x80-\x9f]/g;
      var reservedRe = /^\.+$/;

return filename
          .replace(illegalRe, '.')
          .replace(controlRe, '\u2022')
          .replace(reservedRe, '')
          .replace(/\s+/g, '_');

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

const id = i.toString();
// Might not want to record the first run because it has different perf characteristics from
// subsequent runs (e.g. DNS cache which can't be easily reset between runs)
const isFirstRun = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

did you end up running with the first run disabled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had the first run enabled - but I'm going to disable it going forward.

@@ -0,0 +1,159 @@
'use strict';
Copy link
Collaborator

Choose a reason for hiding this comment

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

need license on all these

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

// TODO: annotate
function main() {
const allResults = [];
fs.readdirSync(OUT_PATH).map(siteDir => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

is out path the "out" of the previous step? so input to this script? I was kinda confused at first

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah - it's like how Chromium uses out/ to store all generated files and sometimes uses out/ as inputs to generate additional files.

const siteResults = [];
fs.readdirSync(sitePath).forEach(runDir => {
const runPath = path.resolve(sitePath, runDir);
const lighthouseReportPath = path.resolve(runPath, 'lighthouse.json');
Copy link
Collaborator

Choose a reason for hiding this comment

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

want to pass this in to readResult instead?

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

@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 great!

bikeshed territory: it would be good to drop the lighthouse- prefix on the directory (lighthouse-core et al. were a mistake we'll correct someday :)

But also plots may not be the best descriptor since it's just as much a test runner and and aggregator. Something to do with analysis or monitoring? plots might work too, though :)

```
# View all commands
$ cd lighthouse-plots
$ npm run
Copy link
Member

Choose a reason for hiding this comment

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

let's upgrade the hipster value

people running yarn generally know how to translate npm commands to yarn. I'd wager there are many people who use yarn who don't know how to translate the other way (or at the very least, would have to look it up)

@@ -0,0 +1,159 @@
'use strict';
Copy link
Member

Choose a reason for hiding this comment

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

need license headers on all of these

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.

'https://bbc.com'
];

async function main() {
Copy link
Member

Choose a reason for hiding this comment

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

we're supporting node 6, so no async/await yet, sadly

Copy link
Member

Choose a reason for hiding this comment

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

oh, just saw the node 7 note in the readme. I'd personally prefer if we had one node target for the whole codebase, but others can weigh in

Copy link
Member

Choose a reason for hiding this comment

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

For now the plots code is designed for our use and not targeting the use-cases of LH users. I think we should keep the scope pretty constrained. As such, the requirement of node 7 feels good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll rewrite with promises.

Copy link
Member

Choose a reason for hiding this comment

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

As such, the requirement of node 7 feels good

you just want to sneak in more async/await :P Node LTS will go to 8 in October, which isn't that long from now.

The biggest issue for me (besides consistency) is probably testing...ideally we'll have tests for this code to make sure it doesn't get out of sync with things, and that would require testing the node version in CI to only run plots tests on node 7, etc. Easier to just use promises for now


async function main() {
if (utils.isDir(OUT_PATH)) {
console.log('Please remove or move ./out/');
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if I'd know how to interpret this message if I saw it on the command line :) Maybe just mention results from a previous run were stored there or something to give context about why it's there (it didn't come with the repo) and that it's expected if you've run it before?

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

/**
* @typedef {!{site: string, results: !RunResults}}
*/
let SiteResults;
Copy link
Member

Choose a reason for hiding this comment

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

can add // eslint-disable-line no-unused-vars to these lines to make eslint happy

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} id
* @param {boolean} ignoreRun
*/
async function singleRunAnalysis(url, id, ignoreRun) {
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 definitely boolean-trapish (it's not obvious from singleRunAnalysis('https://example.com', 5, false) how that false is going to affect things), though I don't have a great suggestion around it. One option would be to separate analysis from saving and have runAnalysis call both singleRunAnalysis and then with the results make a conditional call to a saveAnalysis function. That keeps isFirstRun logic isolated there.

It is unfortunate you have to recreate so much of bin.ts to do all this yourself

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used an options object parameter so it's more readable, e.g. singleRunAnalysis('https://example.com', 5, {ignoreRun: false})

@@ -0,0 +1,43 @@
{
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 reuse the existing perf.json in the config directory? Would be nice to reduce work to keep configs updated to latest format (e.g. dropping aggregations soon)

Copy link
Member

Choose a reason for hiding this comment

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

ya seems like you can reuse perf.json

"estimated-input-latency",
"time-to-interactive"
],
"aggregations": [{
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 care at all about aggregations? Seems like you're only looking at audit results. I believe you can just drop the field completely (or at the very least do "aggregations": [])

// TODO: annotate
function main() {
const allResults = [];
fs.readdirSync(OUT_PATH).map(siteDir => {
Copy link
Member

Choose a reason for hiding this comment

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

s/map/forEach?

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.

"scripts": {
"measure": "node measure.js",
"analyze": "node analyze.js"
}
Copy link
Member

Choose a reason for hiding this comment

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

maybe add something like clean and have it rimraf out/?

Copy link
Member

Choose a reason for hiding this comment

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

+1

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.

@wwwillchen wwwillchen changed the title WIP: Plots Measure and visualize perf metrics Mar 30, 2017
@paulirish
Copy link
Member

But also plots may not be the best descriptor since it's just as much a test runner and and aggregator. Something to do with analysis or monitoring? plots might work too, though :)

I suggested plots because mostly because of completion. (The subset of the word "analysis" you type before hitting tab. ;) Plus its a single syllable and cute. However I do agree it doesn't describe things very completely.

);
const audits = data.audits;
return {
firstMeaningfulPaint: {
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking it'd be advantageous to reuse the definitions inside of pwmetrics-events.js. They are primarily used for adding the LH metrics back into the trace, but the external pwmetrics module also depends on it.

As a usecase, @patrickhulce is adding some new TTI values now and we'll want to have them both embedded in the trace and summarized here in plots.

I didn't capture score over there, but I'm okay adding it. I'm also OK with ignoring score completely for now.

const GENERATED_RESULTS_PATH = path.resolve(OUT_PATH, 'generatedResults.js');
const LIGHTHOUSE_RESULTS_FILENAME = 'lighthouse.json';

const METRICS = [
Copy link
Member

Choose a reason for hiding this comment

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

this might be addressed anyhow, but I didn't see why this was neccessary. Isn't it the same as object.keys on the readResult value?

@@ -0,0 +1,43 @@
{
Copy link
Member

Choose a reason for hiding this comment

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

ya seems like you can reuse perf.json

'https://bbc.com'
];

async function main() {
Copy link
Member

Choose a reason for hiding this comment

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

For now the plots code is designed for our use and not targeting the use-cases of LH users. I think we should keep the scope pretty constrained. As such, the requirement of node 7 feels good.

} catch (err) {
await launcher.run();
}
await runAnalysis();
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 something i'd actually like to A/B test with this framework. (To see how much of a difference it makes). DNS costs will definitely be in relaunched ones.

In this model, it favors quicker collection, but also aligns closer with how people run LH with the extension and in devtools. So I think it's slightly better to debug that scenario first.

*/
async function singleRunAnalysis(url, id, ignoreRun) {
const parsedURL = parseURL(url);
const host = `${parsedURL.host}-${parsedURL.pathname}`
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 call this filenameHost or something?

Also I wrote this bit for traceviewer in case you want to reuse:

      var illegalRe = /[\/\?<>\\:\*\|":]/g;
      var controlRe = /[\x00-\x1f\x80-\x9f]/g;
      var reservedRe = /^\.+$/;

return filename
          .replace(illegalRe, '.')
          .replace(controlRe, '\u2022')
          .replace(reservedRe, '')
          .replace(/\s+/g, '_');

"scripts": {
"measure": "node measure.js",
"analyze": "node analyze.js"
}
Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

@wwwillchen wwwillchen left a comment

Choose a reason for hiding this comment

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

I think I've addressed all of the comments in this PR. Please take another look. Thanks!

@paulirish - I think you mentioned over hangouts there was an onload event you wanted me to add. Can you point me to the trace event?

I noticed there's a weird issue with nvm install on travis (e.g. https://travis-ci.org/GoogleChrome/lighthouse/jobs/218671384#L263)

"scripts": {
"measure": "node measure.js",
"analyze": "node analyze.js"
}
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.


async function main() {
if (utils.isDir(OUT_PATH)) {
console.log('Please remove or move ./out/');
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

main();

async function runAnalysis() {
// Running it n + 1 times because the first run might not be recorded
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the wording.

* @param {string} id
* @param {boolean} ignoreRun
*/
async function singleRunAnalysis(url, id, ignoreRun) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used an options object parameter so it's more readable, e.g. singleRunAnalysis('https://example.com', 5, {ignoreRun: false})

*/
async function singleRunAnalysis(url, id, ignoreRun) {
const parsedURL = parseURL(url);
const host = `${parsedURL.host}-${parsedURL.pathname}`
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

for (let i = 0; i <= NUMBER_OF_RUNS; i++) {
const id = i.toString();
// Might not want to record the first run because it has different perf characteristics from
// subsequent runs (e.g. DNS cache which can't be easily reset between runs)
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

plots/measure.js Outdated

function createConfig() {
const config = require('../lighthouse-core/config/perf.json');
config.audits = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

feels fragile to hardcode this but don't know of a better way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, using your own config seemed fine to me. This specific case makes sense to have a metric only config without the cruft of the other gatherers and whatnot slowing things down. @paulirish @brendankenny why use perf.json?

Copy link
Contributor Author

@wwwillchen wwwillchen Apr 5, 2017

Choose a reason for hiding this comment

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

created a trimmed down config here - lighthouse-core/config/plots.json

@brendankenny
Copy link
Member

brendankenny commented Apr 4, 2017

I noticed there's a weird issue with nvm install on travis

yeah, I was just looking at that. Looks like there are nvm download failures and it's defaulting to node 4.

@brendankenny
Copy link
Member

looks like it's nodejs/nodejs.org#1191

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.

looking good! pulling the metrics to chart from pwmetrics should keep it easy to evaluate our incoming TTIs :D

* @param {string} path
* @return {*}
*/
function safeGet(object, path) {
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 usually put these at the top of the file

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


const URLS = [
// Flagship sites
'https://nytimes.com',
Copy link
Collaborator

Choose a reason for hiding this comment

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

might also be worth throwing in some of these

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

plots/measure.js Outdated

function createConfig() {
const config = require('../lighthouse-core/config/perf.json');
config.audits = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, using your own config seemed fine to me. This specific case makes sense to have a metric only config without the cruft of the other gatherers and whatnot slowing things down. @paulirish @brendankenny why use perf.json?

plots/analyze.js Outdated
* @param {!Array<!SiteResults>} results
* @return {!FormattedResults}
*/
function formatResults(results) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: maybe rename so it's clearer this basically groups by metric name

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

plots/chart.js Outdated
})
.reverse();

const layout = {title: title + ' ' + type};
Copy link
Member

Choose a reason for hiding this comment

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

i saw some plots where the X axis didnt' start at 0. can we make sure it always does?

plots/README.md Outdated
$ npm run analyze

# View visualization
# Open chart.html in browser
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 rename to index.html for ease of use

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

plots/chart.js Outdated
.reverse();

const layout = {title: title + ' ' + type};
Plotly.newPlot(createChartElement(1000), data, layout);
Copy link
Member

Choose a reason for hiding this comment

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

since we flood all these plots at once i see my machine stall for 5s while plotly goes to work.

here's an incremental raf-scheduler for them: d5405e7

Copy link
Contributor Author

@wwwillchen wwwillchen left a comment

Choose a reason for hiding this comment

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

Updated per second round of comments. Please take another look.

plots/README.md Outdated
$ npm run analyze

# View visualization
# Open chart.html in browser
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} path
* @return {*}
*/
function safeGet(object, path) {
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

plots/analyze.js Outdated
* @param {!Array<!SiteResults>} results
* @return {!FormattedResults}
*/
function formatResults(results) {
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


const URLS = [
// Flagship sites
'https://nytimes.com',
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

@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'm happy with this. @brendankenny you good?

@googlebot
Copy link

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.

@wwwillchen
Copy link
Contributor Author

@googlebot it's ok

@paulirish
Copy link
Member

paulirish commented Apr 6, 2017

@googlebot shh bby is ok

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 few more things, mostly style :)

@@ -21,3 +21,6 @@ lighthouse-cli/types/*.js
# Handlebar-templates
lighthouse-core/report/templates/report-templates.js
lighthouse-core/report/partials/templates/report-partials.js

# Generated files
plots/out/**
Copy link
Member

Choose a reason for hiding this comment

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

should add this to .npmignore as well. Also, do we want to ship plots with the npm module? Could be useful, but we might want to wait on that, so we could npmignore plots/ entirely for now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's npmignore plots entirely for now. Once it's been more thoroughly tested and documented, then it makes sense to ship it with the npm module.

Copy link
Member

Choose a reason for hiding this comment

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

Let's npmignore plots entirely for now

SGTM

* @param {string} path
* @return {*}
*/
function safeGet(object, path) {
Copy link
Member

Choose a reason for hiding this comment

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

not wild about this, mostly because it completely disables typing. Is the issue that the intermediate objects are sometimes undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right - this happens when the audit fails. An alternative is to always to have the intermediate objects be created even when the audit fails.

Copy link
Member

Choose a reason for hiding this comment

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

An alternative is to always to have the intermediate objects be created even when the audit fails.

I think it's reasonable not to have an extendedInfo if the audit failed, but if it didn't fail, all the extendedInfo paths are filled or explicitly null (or whatever) at the leaf properties.

I'm sighing via code review because I'm currently in the process of re-enabling closure compiler for the codebase and this will all have to be undone again :)

I don't want to block plots/ on having to hunt down all the current failure modes though, so we can go with this for now and file an issue about extendedInfo structure guarantees (which having a type check test will help with :)

plots/index.html Outdated
<script src="./chart.js"></script>
</body>

</html>
Copy link
Member

Choose a reason for hiding this comment

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

new line

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.

plots/analyze.js Outdated
let SiteResults; // eslint-disable-line no-unused-vars

/**
* @typedef {Array<{runId: string, metrics: !Array<!Metric>}>}
Copy link
Member

@brendankenny brendankenny Apr 6, 2017

Choose a reason for hiding this comment

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

!Array, 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.

I wasn't sure whether to put nullability operator for typedefs since I do !RunResults above, but I'll add the ! to be consistent w/ lighthouse

Copy link
Member

Choose a reason for hiding this comment

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

I wasn't sure whether to put nullability operator for typedefs since I do !RunResults above, but I'll add the ! to be consistent w/ lighthouse

yeah, my understanding (though I'd have to bring up old threads to be sure) is that without the ! the typedef would be for (null|!Array<...>), and so even if the typedef is used as non-nullable !RunResults, that would be treated as the union of the empty set and the typedef, so still nullable as (null|!Array).

plots/analyze.js Outdated
let Metric; // eslint-disable-line no-unused-vars

/**
* @typedef {Object<string, !Array<{site: string, metrics: !Array<!{timing: string}>}>}
Copy link
Member

Choose a reason for hiding this comment

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

!Object

Copy link
Member

@brendankenny brendankenny Apr 6, 2017

Choose a reason for hiding this comment

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

For metrics: !Array<!{timing: string}>}> you don't need the inner !. Also should timing really be a number?

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 - good catch

// (e.g. DNS cache which can't be easily reset between runs)
for (let i = 0; i <= NUMBER_OF_RUNS; i++) {
// Averages out any order-dependent effects such as memory pressure
utils.shuffle(URLS);
Copy link
Member

Choose a reason for hiding this comment

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

👍

plots/measure.js Outdated
}

/**
* @typedef {{ignoreRun: boolean}}
Copy link
Member

Choose a reason for hiding this comment

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

nit: if it's just the one property I'd say just inline in the uses below

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


main();

/**
Copy link
Member

Choose a reason for hiding this comment

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

function description doc strings on these (and the other files) would be 👍 😄

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

See the License for the specific language governing permissions and
limitations under the License.
-->

Copy link
Member

Choose a reason for hiding this comment

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

two space indent in this file

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.

plots/index.html Outdated
<html>

<head>
<script src="https://cdn.plot.ly/plotly-1.25.2.min.js"></script>
Copy link
Member

Choose a reason for hiding this comment

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

we don't have to do this in this PR, but it might be good to move plotly to a devDependency so this works locally

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made a note to do this in a follow-up.

wwwillchen and others added 2 commits April 6, 2017 18:07
a00ad41cb947ab4322437ee2e1079065c83d6b94
init

8f406f93a9f802b7887db21797c7f468a124d3d6
working

f88c01d2113f294ba3a26a42ee0ae2c1a84a3ae3
fixin

e1d52f007999f518e24ef110e31dcbb6cc6e133c
done

6415b7bf59c3b82e57cff967817e82492c9d1cfe
done

nit

fixes

update unit test

onLoad event

fix

pr fb
Copy link
Contributor Author

@wwwillchen wwwillchen left a comment

Choose a reason for hiding this comment

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

@brendankenny please take another look, thanks.

@@ -21,3 +21,6 @@ lighthouse-cli/types/*.js
# Handlebar-templates
lighthouse-core/report/templates/report-templates.js
lighthouse-core/report/partials/templates/report-partials.js

# Generated files
plots/out/**
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's npmignore plots entirely for now. Once it's been more thoroughly tested and documented, then it makes sense to ship it with the npm module.

* @param {string} path
* @return {*}
*/
function safeGet(object, path) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right - this happens when the audit fails. An alternative is to always to have the intermediate objects be created even when the audit fails.


main();

/**
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

plots/analyze.js Outdated
let SiteResults; // eslint-disable-line no-unused-vars

/**
* @typedef {Array<{runId: string, metrics: !Array<!Metric>}>}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure whether to put nullability operator for typedefs since I do !RunResults above, but I'll add the ! to be consistent w/ lighthouse

plots/analyze.js Outdated
let Metric; // eslint-disable-line no-unused-vars

/**
* @typedef {Object<string, !Array<{site: string, metrics: !Array<!{timing: string}>}>}
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 - good catch

plots/index.html Outdated
<html>

<head>
<script src="https://cdn.plot.ly/plotly-1.25.2.min.js"></script>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made a note to do this in a follow-up.

See the License for the specific language governing permissions and
limitations under the License.
-->

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.


const NUMBER_OF_RUNS = 20;

const URLS = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's about ~20 sec per run * 72 sites * 20 runs = 8 hours. We can create a narrower list (e.g. the most problematic sites) when we generate plots as part of the lighthouse's CI process.

plots/measure.js Outdated
return;
}

const launcher = new ChromeLauncher({port: 9222, autoSelectChrome: true});
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

plots/measure.js Outdated
}

/**
* @typedef {{ignoreRun: boolean}}
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

@@ -21,3 +21,6 @@ lighthouse-cli/types/*.js
# Handlebar-templates
lighthouse-core/report/templates/report-templates.js
lighthouse-core/report/partials/templates/report-partials.js

# Generated files
plots/out/**
Copy link
Member

Choose a reason for hiding this comment

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

Let's npmignore plots entirely for now

SGTM

* @param {string} path
* @return {*}
*/
function safeGet(object, path) {
Copy link
Member

Choose a reason for hiding this comment

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

An alternative is to always to have the intermediate objects be created even when the audit fails.

I think it's reasonable not to have an extendedInfo if the audit failed, but if it didn't fail, all the extendedInfo paths are filled or explicitly null (or whatever) at the leaf properties.

I'm sighing via code review because I'm currently in the process of re-enabling closure compiler for the codebase and this will all have to be undone again :)

I don't want to block plots/ on having to hunt down all the current failure modes though, so we can go with this for now and file an issue about extendedInfo structure guarantees (which having a type check test will help with :)


const NUMBER_OF_RUNS = 20;

const URLS = [
Copy link
Member

Choose a reason for hiding this comment

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

It's about ~20 sec per run * 72 sites * 20 runs = 8 hours. We can create a narrower list (e.g. the most problematic sites) when we generate plots as part of the lighthouse's CI process

yeah, I like that. Maybe we can have it be a few sets of URLs, the full run does all of them, and then flags for different subsets (and even a single URL)

/**
* Launches Chrome once at the beginning, runs all the analysis,
* and then kills Chrome.
* TODO(chenwilliam): measure the overhead of starting chrome, if it's minimal
Copy link
Member

Choose a reason for hiding this comment

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

@paulirish also wanted to test the opposite, does not opening a new chrome every time have a significant impact on metrics, since e.g. users don't (usually) open a new instance every time they want to test their site in devtools. Will you file an issue to track this?

plots/analyze.js Outdated
let SiteResults; // eslint-disable-line no-unused-vars

/**
* @typedef {Array<{runId: string, metrics: !Array<!Metric>}>}
Copy link
Member

Choose a reason for hiding this comment

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

I wasn't sure whether to put nullability operator for typedefs since I do !RunResults above, but I'll add the ! to be consistent w/ lighthouse

yeah, my understanding (though I'd have to bring up old threads to be sure) is that without the ! the typedef would be for (null|!Array<...>), and so even if the typedef is used as non-nullable !RunResults, that would be treated as the union of the empty set and the typedef, so still nullable as (null|!Array).

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.

whoops, commented too soon.

LGTM! 💹📉📈📊

do you want to use #1924 as a meta bug for remaining tasks or close that one and open something new? I think main thing is testing effect of launching (or not) a new Chrome for every test.

Also adding tests so we don't break plots/ :)

@brendankenny brendankenny merged commit c43eb09 into GoogleChrome:master Apr 7, 2017
@wwwillchen
Copy link
Contributor Author

Thanks. I created a meta-issue to track remaining work for plots: #1980

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

5 participants