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

Compute overall score #4

Closed
staabm opened this issue Jun 12, 2020 · 2 comments
Closed

Compute overall score #4

staabm opened this issue Jun 12, 2020 · 2 comments

Comments

@staabm
Copy link

staabm commented Jun 12, 2020

Would it be possible for this lib to compute a overall score (like the lighthouse perf score)?

I want to integrate the low level metrics into a cms, but it would help for a top level view if there was a single score at first. The user would then „drill down“ into the separate metrics

@staabm
Copy link
Author

staabm commented Jun 14, 2020

Maybe we could calculate a number similar to https://googlechrome.github.io/lighthouse/scorecalc/

@alekseykulikov
Copy link
Member

alekseykulikov commented Jun 15, 2020

web-vitals-reporter@0.2.0 adds beforeSend option, which you can use to customize final output. Use logic from lighthouse-plugin-field-performance and Lighthouse's computeLogNormal to get the final score.

Working example:

import { getCLS, getFID, getLCP } from 'web-vitals'
import { createApiReporter } from 'web-vitals-reporter'

const report = createApiReporter('/analytics', {
  beforeSend: (result) => {
    const lcpScore = computeLogNormalScore({ p10: 2500, median: 4500 }, result.LCP)
    const fidScore = computeLogNormalScore({ p10: 100, median: 300 }, result.FID || 0)
    const clsScore = computeLogNormalScore({ p10: 0.1, median: 0.25 }, result.CLS)
    const score = Math.min(lcpScore, fidScore, clsScore)
    return { lcpScore, fidScore, clsScore, score }
  },
})

getLCP(report)
getFID(report)
getCLS(report)

// helpers extracted from Lighthouse
// https://github.com/GoogleChrome/lighthouse/blob/b36b694f12b668cc64d232b8fe7c0507011bfdb7/lighthouse-core/audits/audit.js#L79

/** @param {{median: number, p10: number}} controlPoints @param {number} value */
function computeLogNormalScore(controlPoints, value) {
  const percentile = getLogNormalScore(controlPoints, value)
  return clampTo2Decimals(percentile)
}

/** @param {{median: number, p10: number}} parameters @param {number} value */
function getLogNormalScore({ median, p10 }, value) {
  // Required for the log-normal distribution.
  if (median <= 0) throw new Error('median must be greater than zero')
  if (p10 <= 0) throw new Error('p10 must be greater than zero')
  // Not required, but if p10 > median, it flips around and becomes the p90 point.
  if (p10 >= median) throw new Error('p10 must be less than the median')

  // Non-positive values aren't in the distribution, so always 1.
  if (value <= 0) return 1

  // Closest double to `erfc-1(2 * 1/10)`.
  const INVERSE_ERFC_ONE_FIFTH = 0.9061938024368232

  // Shape (σ) is `log(p10/median) / (sqrt(2)*erfc^-1(2 * 1/10))` and
  // standardizedX is `1/2 erfc(log(value/median) / (sqrt(2)*σ))`, so simplify a bit.
  const xLogRatio = Math.log(value / median)
  const p10LogRatio = -Math.log(p10 / median) // negate to keep σ positive.
  const standardizedX = (xLogRatio * INVERSE_ERFC_ONE_FIFTH) / p10LogRatio
  const complementaryPercentile = (1 - erf(standardizedX)) / 2

  // Clamp to [0, 1] to avoid any floating-point out-of-bounds issues.
  return Math.min(1, Math.max(0, complementaryPercentile))
}

/** @param {number} x */
function erf(x) {
  // erf(-x) = -erf(x);
  const sign = Math.sign(x)
  x = Math.abs(x)

  const a1 = 0.254829592
  const a2 = -0.284496736
  const a3 = 1.421413741
  const a4 = -1.453152027
  const a5 = 1.061405429
  const p = 0.3275911
  const t = 1 / (1 + p * x)
  const y = t * (a1 + t * (a2 + t * (a3 + t * (a4 + t * a5))))
  return sign * (1 - y * Math.exp(-x * x))
}

/** @param {number} val */
function clampTo2Decimals(val) {
  return Math.round(val * 100) / 100
}

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

No branches or pull requests

2 participants