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

plots: dashboard - identify variance over lighthouse versions #2520

Merged
merged 24 commits into from
Jul 6, 2017

Conversation

wwwillchen
Copy link
Contributor

@wwwillchen wwwillchen commented Jun 16, 2017

Updated code: please review the code :)

Example:
screenshot from 2017-06-29 14 40 29

If you need some dummy data, you can copy this file into plots/out/dashboard-results.js and then open dashboard/index.html or I can show you the demo in person.

addresses part of: #2413

@patrickhulce
Copy link
Collaborator

image

@wwwillchen wwwillchen changed the title plots: dashboard [prototype] plots: dashboard - identify variance over lighthouse versions [prototype] Jun 16, 2017
@codecov
Copy link

codecov bot commented Jun 21, 2017

Codecov Report

Merging #2520 into master will decrease coverage by 0.83%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2520      +/-   ##
==========================================
- Coverage   84.52%   83.68%   -0.84%     
==========================================
  Files         169      177       +8     
  Lines        5614     5780     +166     
  Branches      774      801      +27     
==========================================
+ Hits         4745     4837      +92     
- Misses        855      923      +68     
- Partials       14       20       +6
Impacted Files Coverage Δ
lighthouse-core/lib/network-recorder.js 100% <0%> (ø) ⬆️
lighthouse-cli/commands/list-audits.ts 100% <0%> (ø)
lighthouse-cli/cli-flags.ts 90% <0%> (ø)
lighthouse-cli/bin.ts 29.03% <0%> (ø)
lighthouse-cli/commands/commands.ts 100% <0%> (ø)
lighthouse-cli/shim-modules.ts 50% <0%> (ø)
lighthouse-cli/index.js 100% <0%> (ø)
lighthouse-cli/printer.ts 94.28% <0%> (ø)
lighthouse-cli/commands/list-trace-categories.ts 100% <0%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bfcbc30...4c18ec9. Read the comment docs.

@wwwillchen wwwillchen changed the title plots: dashboard - identify variance over lighthouse versions [prototype] plots: dashboard - identify variance over lighthouse versions Jun 21, 2017
@wwwillchen
Copy link
Contributor Author

@patrickhulce @paulirish - please take a look.

color: white;
padding: 15px;
font-size: 16px;
background-color: #3367d6;
Copy link
Collaborator

Choose a reason for hiding this comment

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

wanna put any of these colors in same vars? :)

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.

let currentMetric = metrics[0];
let numberOfBatchesToShow = 0;

function main() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this file is kinda hard to follow and grok, wdyt about grouping some of these into classes/objects/sections by usage? i.e. event handlers, rendering, init, 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.

done.

<html>

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

Choose a reason for hiding this comment

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

blocking script in head! 😱 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

bumped down to body.

{
"main": "index.js",
"scripts": {
"measure": "node measure.js",
Copy link
Collaborator

Choose a reason for hiding this comment

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

aw I liked yarn measure

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, the reason why I removed it is because passing flags through yarn or npm can be unintuitive (e.g. yarn measure -- --flag) and I'm guessing most of the time we're using these scripts with flags. In a f/u PR, I can look into using npm link and then we could have something like (plots measure --out out-hi) but I think we should cross that bridge later (e.g. when plots becomes useful outside of the core team).

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah sgtm


const renderingScheduler = new RenderingScheduler();
const charts = new Charts(renderingScheduler);
const dashboard = new Dashboard(metrics, charts);
Copy link
Collaborator

Choose a reason for hiding this comment

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

niiiiice muuuuch easier to follow!

minor nit, personally I like to separate the rendering logic from a constructor to an explicit render() method so it's clear what attaches to the DOM and what does basic setup and whatnot (if there's no need to keep instance variables at all some could even be static and the classes just used for logical grouping but totally up to you)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, done. I'll keep the instance variables (otherwise I think I'd need to use singletons or global variables).

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh yeah wasn't implying that you should make them static, just that if it shook out without the need for instance variables didn't have to keep em that way

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.

Thanks, please take another look.

color: white;
padding: 15px;
font-size: 16px;
background-color: #3367d6;
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.

<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.

bumped down to body.

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

Yeah, the reason why I removed it is because passing flags through yarn or npm can be unintuitive (e.g. yarn measure -- --flag) and I'm guessing most of the time we're using these scripts with flags. In a f/u PR, I can look into using npm link and then we could have something like (plots measure --out out-hi) but I think we should cross that bridge later (e.g. when plots becomes useful outside of the core team).

let currentMetric = metrics[0];
let numberOfBatchesToShow = 0;

function 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.


const renderingScheduler = new RenderingScheduler();
const charts = new Charts(renderingScheduler);
const dashboard = new Dashboard(metrics, charts);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, done. I'll keep the instance variables (otherwise I think I'd need to use singletons or global variables).

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.

looking good.

I haven't looked carefully enough to answer this for myself: That Note: null results are treated as 0 note, does that include when calculating percentiles?

@@ -0,0 +1,41 @@
<!doctype html>
Copy link
Member

Choose a reason for hiding this comment

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

license header

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

<script src="./dashboard.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

@@ -0,0 +1,119 @@
* {
Copy link
Member

Choose a reason for hiding this comment

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

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

plots/README.md Outdated
$ node generate-dashboard.js --input-root dir-with-subdirectories

# Or you can specify each input directory explicitly
$ node generated-dashboard.js --inputs out-1 out-2 out-3
Copy link
Member

Choose a reason for hiding this comment

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

generated => generate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

.example('node $0 --input-root dir-with-subdirectories')
.example('node $0 --inputs out-1 out-2 out-3')
.describe({
'inputs': 'paths to out directory generated by measure.js',
Copy link
Member

Choose a reason for hiding this comment

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

could we instead have a single --input? (And tbh, at that point, you might as well skip the arg).

It'd look like this:

node generate-dashboard out-a out-b out-x

node generate-dashboard outParentFolder

Take the directory or its children, depending on the contents

for (const inputPath of inputPaths) {
childProcess.execSync(`node analyze.js ${inputPath}`, {
env: Object.assign({}, process.env, {
CI: '1'
Copy link
Member

Choose a reason for hiding this comment

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

add a comment that this is to prevent opn() from running.
and collapse this Obj.assign into one 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

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 - yeah, null is treated as 0 in when calculating the percentiles.

@paulirish - ptal. thanks!

plots/README.md Outdated
$ node generate-dashboard.js --input-root dir-with-subdirectories

# Or you can specify each input directory explicitly
$ node generated-dashboard.js --inputs out-1 out-2 out-3
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

@@ -0,0 +1,119 @@
* {
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

@@ -0,0 +1,41 @@
<!doctype html>
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

<script src="./dashboard.js"></script>
</body>

</html>
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 (const inputPath of inputPaths) {
childProcess.execSync(`node analyze.js ${inputPath}`, {
env: Object.assign({}, process.env, {
CI: '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

@brendankenny
Copy link
Member

null is treated as 0 in when calculating the percentiles.

can they be filtered out before calculating? Adding 0s to the sample will bring the median downward when usually (but not always) missing results come from what would be much higher values.

For the time being we could filter nulls out and then in the future add something like a warning that there were too many missing values in a plots/ run and either it needs to be rerun or there's a serious regression we let slip in that needs to be fixed :)

@wwwillchen
Copy link
Contributor Author

@brendankenny I tried out your suggestion and ran into an issue when all the runs for a particular batch of results for a site have a null value. Essentially plotly gets confused about where to plot. I think we can go with treating null as 0 for now and then figuring out a better solution in a f/u PR.

Before (treating null as 0)

before

After (filtering null)

after

@brendankenny
Copy link
Member

hmm, if nulls are filtered out of the array before calling calculatePercentile on it and the array ends empty, shouldn't calculatePercentile already return 0 for the median, 20%, and 80%? I'm ok with having those runs drop to 0 on the graph (it'll be really clear that something was wrong), I just meant nulls should be removed from the array before calculating a percentile.

(unless that's what you did and I'm missing the step that's messing up the graph)

@wwwillchen
Copy link
Contributor Author

@brendankenny updated it so it retuns 0 if the array is empty (before if the array was empty it was returning undefined).

if (array.length === 0) {
return 0;
}
if (percentile <= 0) {
Copy link
Member

Choose a reason for hiding this comment

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

doesn't it need to be sorted before 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.

yep - fixed.

if (percentile >= 1) {
return array[array.length - 1];
}
const sorted = array.slice().sort((a, b) => a - b).filter(x => x !== null);
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 does filter() first it can ditch the slice()

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

return sorted[lower] || 0;
}
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
}
Copy link
Member

Choose a reason for hiding this comment

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

how do you feel about

calculatePercentile(array, percentile) {
  const sorted = array.filter(x => x !== null).sort((a, b) => a - b);

  if (sorted.length === 0) {
    return 0;
  }
  if (sorted.length === 1 || percentile <= 0) {
    return sorted[0];
  }
  if (percentile >= 1) {
    return sorted[sorted.length - 1];
  }

  const index = (sorted.length - 1) * percentile;
  const lower = Math.floor(index);
  const upper = lower + 1;
  const weight = index % 1;

  return sorted[lower] * (1 - weight) + sorted[upper] * weight;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

looks good.

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 thanks - updated per your comments.

if (array.length === 0) {
return 0;
}
if (percentile <= 0) {
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 - fixed.

if (percentile >= 1) {
return array[array.length - 1];
}
const sorted = array.slice().sort((a, b) => a - b).filter(x => x !== null);
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

return sorted[lower] || 0;
}
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

looks good.

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!

@wwwillchen wwwillchen merged commit 9561330 into GoogleChrome:master Jul 6, 2017
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