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

core(lhr): strictly numeric scores, add scoreDisplayMode #4690

Merged
merged 8 commits into from
Mar 14, 2018

Conversation

paulirish
Copy link
Member

@paulirish paulirish commented Mar 5, 2018

  1. Ensure all audits return a numeric score (0-1), no boolean nonsense.
  2. scoreDisplayMode forces how it's displayed (renamed from scoringMode)
  3. Remove displayValue fallback stuff that was introduced in Resolve audit result #487

This is a chain of PRs. Landing order is... 1st: newdetails (#4616). 2nd: shallowCategories (#4711). 3rd: scoring2.0 (#4690)

ref #4614

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.

quick, let's merge before there are conflicts 😆

@@ -57,7 +57,7 @@ An object containing the results of the audits, keyed by their name.
| rawValue | <code>boolean&#124;number</code> | The unscored value determined by the audit. Typically this will match the score if there's no additional information to impart. For performance audits, this value is typically a number indicating the metric value. |
| displayValue | `string` | The string to display in the report alongside audit results. If empty, nothing additional is shown. This is typically used to explain additional information such as the number and nature of failing items. |
| score | <code>boolean&#124;number</code> | The scored value determined by the audit as either boolean or a number `0-100`. If the audit is a boolean, the implication is `score ? 100 : 0`. |
| scoringMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail or are there shades of gray 0-100. *NOTE: This does not necessarily mean `typeof audit.score === audit.scoringMode`, an audit can have a score of 40 with a scoringMode of `"binary"` meant to indicate display should be failure.* |
| scoreDisplayMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail or are there shades of gray 0-100. *NOTE: This does not necessarily mean `typeof audit.score === audit.scoreDisplayMode`, an audit can have a score of 40 with a scoreDisplayMode of `"binary"` meant to indicate display should be failure.* |
Copy link
Collaborator

Choose a reason for hiding this comment

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

score should always be a number now, so entire caveat is unnecessary :)

score = Math.max(0, score);
return Math.round(score);
return Math.round(score * 100) / 100;
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍


result.score = auditScore;
if (!Number.isFinite(result.score)) {
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 to check in both places?

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'd be fine with also doing this in generateAuditResult, however IMO there's not a big diff.

  • Many of our tests assert the result returned by audit()
  • Audit.generateAuditResult is called via Runner once each audit is done
  • Basically right after that, we call this scoreAllCategories method.

So ideally we'd have something in Audit class that wraps each audit's audit() method, but we don't.

Copy link
Member Author

Choose a reason for hiding this comment

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

lol I just made your point for you. 😊

* @param {number} val
* @return {number}
*/
const clamp2decimals = val => Math.round(val * 100) / 100;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not the biggest fan of this name, clampTo2Decimals?

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

@paulirish paulirish changed the title core: refactor scoring to be numeric only. introduce scoreDisplayMode core(LHR): refactor scoring to be numeric only. introduce scoreDisplayMode Mar 7, 2018
@paulirish paulirish changed the title core(LHR): refactor scoring to be numeric only. introduce scoreDisplayMode core(LHR): refactor scoring to be strictly numeric. introduce scoreDisplayMode Mar 7, 2018
@paulirish paulirish changed the title core(LHR): refactor scoring to be strictly numeric. introduce scoreDisplayMode core(LHR): refactor scores to be strictly numeric, introduce scoreDisplayMode Mar 7, 2018
throw new Error(`Audit score for ${audit.meta.name} is > 1`);
}

const scoreDisplayMode = result.scoreDisplayMode || audit.meta.scoreDisplayMode ||
Copy link
Member Author

Choose a reason for hiding this comment

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

remove result.scoreDisplayMode and update the byteefficiency audit metas.

@paulirish paulirish changed the title core(LHR): refactor scores to be strictly numeric, introduce scoreDisplayMode core(lhr): refactor scores to be strictly numeric, introduce scoreDisplayMode Mar 7, 2018
@paulirish paulirish changed the base branch from newdetails to shallowCategories March 7, 2018 23:39
@paulirish paulirish force-pushed the scoring2.0 branch 3 times, most recently from 1584258 to bb099d3 Compare March 9, 2018 23:33
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.

couple nits, but we are so close! :D

@@ -56,8 +55,8 @@ An object containing the results of the audits, keyed by their name.
| error | `boolean` | Set to true if there was an an exception thrown within the audit. The error message will be in `debugString`.
| rawValue | <code>boolean&#124;number</code> | The unscored value determined by the audit. Typically this will match the score if there's no additional information to impart. For performance audits, this value is typically a number indicating the metric value. |
| displayValue | `string` | The string to display in the report alongside audit results. If empty, nothing additional is shown. This is typically used to explain additional information such as the number and nature of failing items. |
| score | <code>boolean&#124;number</code> | The scored value determined by the audit as either boolean or a number `0-100`. If the audit is a boolean, the implication is `score ? 100 : 0`. |
| scoringMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail or are there shades of gray 0-100. *NOTE: This does not necessarily mean `typeof audit.score === audit.scoringMode`, an audit can have a score of 40 with a scoringMode of `"binary"` meant to indicate display should be failure.* |
| score | <code>boolean&#124;number</code> | The scored value determined by the audit as a number `0-1`, representing displayed scores of 0-100. |
Copy link
Collaborator

Choose a reason for hiding this comment

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

no longer boolean|number just number 🎉 :D

| score | <code>boolean&#124;number</code> | The scored value determined by the audit as either boolean or a number `0-100`. If the audit is a boolean, the implication is `score ? 100 : 0`. |
| scoringMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail or are there shades of gray 0-100. *NOTE: This does not necessarily mean `typeof audit.score === audit.scoringMode`, an audit can have a score of 40 with a scoringMode of `"binary"` meant to indicate display should be failure.* |
| score | <code>boolean&#124;number</code> | The scored value determined by the audit as a number `0-1`, representing displayed scores of 0-100. |
| scoreDisplayMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail (score of 1 or 0), or are there shades of gray (scores between 0-1 exclusive). |
Copy link
Collaborator

Choose a reason for hiding this comment

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

why exclusive? we can still assign scores of 1 to numeric audits :)

it('throws if an audit returns a score that\'s not a number', () => {
const re = /Invalid score/;
assert.throws(_ => Audit._normalizeAuditScore(B, {rawValue: true, score: NaN}), re);
assert.throws(_ => Audit._normalizeAuditScore(B, {rawValue: true, score: '50'}), /is > 1/);
Copy link
Collaborator

Choose a reason for hiding this comment

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

wait why isn't this /Invalid score/?

Copy link
Member Author

Choose a reason for hiding this comment

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

this pair of assertions:
https://github.com/GoogleChrome/lighthouse/blob/scoring2.0/lighthouse-core/audits/audit.js#L127-L128

we assert its finite first, throw invalid score. otherwise we doublecheck we're finite but <= 1

Copy link
Collaborator

@patrickhulce patrickhulce Mar 13, 2018

Choose a reason for hiding this comment

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

yeah exactly so shouldn't we throw invalid score... I'm so confused haha

image

EDIT: OH wait, it's always clampTo2Decimals which casts to number, is that what we want? seems ok if it doesn't turn out to NaN

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah exactly. :D clamp casts.
i fixed the tests.

so right now one could pass in a score of '0.7' and it'd get casted and be just fine. we could hypothetically throw but yeah i don't think its a big deal.

i fixed the tests.

@paulirish paulirish changed the base branch from shallowCategories to master March 13, 2018 18:39
@paulirish paulirish changed the title core(lhr): refactor scores to be strictly numeric, introduce scoreDisplayMode core(lhr): strictly numeric scores, add scoreDisplayMode Mar 13, 2018
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.

💥 🎉

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.

review!

@@ -9,9 +9,9 @@ _Some incomplete notes_
* **Driver** - Interfaces with [Chrome Debugging Protocol](https://developer.chrome.com/devtools/docs/debugger-protocol) ([API viewer](https://chromedevtools.github.io/debugger-protocol-viewer/))
* **Gatherers** - Uses Driver to collect information about the page. Minimal post-processing.
* **Artifacts** - output of a gatherer
* **Audit** - Tests for a single feature/optimization/metric. Using the Artifacts as input, an audit evaluates a test and resolves to a score which may be pass/fail/numeric. Formatting note: The meta description may contain markdown links and meta title may contain markdown code.
* **Audit** - Tests for a single feature/optimization/metric. Using the Artifacts as input, an audit evaluates a test and resolves to a numeric score, described by scoreDisplayMode to be pass/fail or numeric. Formatting note: The meta description may contain markdown links and meta title may contain markdown code.
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 the scoreDisplayMode comment to the "formatting note"? It's pretty tangential to the rest of the sentence.

docs/scoring.md Outdated
@@ -36,30 +36,30 @@ The metric results are not weighted equally. Currently the weights are:
* 1X - perceptual speed index
* 1X - estimated input latency

These weights were determined based on heuristics, and the Lighthouse team is working on formalizing this approach through more field data.
These weights were determined based on heuristics, and the Lighthouse team is working on formalizing this approach through more field data.
Copy link
Member

Choose a reason for hiding this comment

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

these weights are heuristics...

| score | <code>boolean&#124;number</code> | The scored value determined by the audit as either boolean or a number `0-100`. If the audit is a boolean, the implication is `score ? 100 : 0`. |
| scoringMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail or are there shades of gray 0-100. *NOTE: This does not necessarily mean `typeof audit.score === audit.scoringMode`, an audit can have a score of 40 with a scoringMode of `"binary"` meant to indicate display should be failure.* |
| score | <code>number</code> | The scored value determined by the audit as a number `0-1`, representing displayed scores of 0-100. |
| scoreDisplayMode | <code>"binary"&#124;"numeric"</code> | A string identifying how granular the score is meant to be indicating, i.e. is the audit pass/fail (score of 1 or 0), or are there shades of gray (scores between 0-1 inclusive). |
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 string identifying the score granularity, i.e. is the ...."

},
'link-blocking-first-paint': {
score: 100,
score: 1.00,
Copy link
Member

Choose a reason for hiding this comment

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

mixed 1.00s and 1s. I'd personally rather just embrace it and go with all 1 all the time, but should at least be consistent in a file and/or context

Copy link
Member

Choose a reason for hiding this comment

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

I personally feel the same about the trailing 0s on e.g. 0.80, too. Why hold on to the digit if we're not going to be doing 0-100 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

very well! :)
fixed up all these. also standardized on a leading 0, so we don't have a mix of .25 and 0.25

score = Math.max(0, score);
return Math.round(score);
return Math.round(score * 100) / 100;
Copy link
Member

Choose a reason for hiding this comment

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

clampTo2Decimals?

score,
scoreDisplayMode,
informative,
rawValue,
Copy link
Member

Choose a reason for hiding this comment

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

should rawValue be copying over the rawValue from the audit result? Should it have a different name if not?

Copy link
Member

Choose a reason for hiding this comment

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

looking at usage below, maybe these could have a name that indicates they're overrides

@@ -52,7 +52,7 @@ function AuditFullResult() {}
AuditFullResult.prototype.score;
Copy link
Member

Choose a reason for hiding this comment

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

should be only {number} now?

@@ -207,9 +207,9 @@ if (typeof module !== 'undefined' && module.exports) {
* manual: (boolean|undefined),
* notApplicable: (boolean|undefined),
* debugString: (string|undefined),
* displayValue: string,
* displayValue: (string|undefined),
Copy link
Member

Choose a reason for hiding this comment

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

what happened here?

}

const score = Number(itemScore) || 0;
const score = Number(item.score) || 0;
Copy link
Member

Choose a reason for hiding this comment

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

no more need for Number coercion?

}

if (!Number.isFinite(score)) throw new Error(`Invalid score: ${score}`);
if (score > 1) throw new Error(`Audit score for ${audit.meta.name} is > 1`);
Copy link
Member

Choose a reason for hiding this comment

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

worth checking < 0 as well?

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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants