all files / lighthouse-core/audits/ speed-index-metric.js

100% Statements 24/24
100% Branches 8/8
100% Functions 2/2
100% Lines 24/24
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122                                                        18×                                                                                                                                            
/**
 * @license
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
'use strict';
 
const Audit = require('./audit');
const TracingProcessor = require('../lib/traces/tracing-processor');
const Formatter = require('../formatters/formatter');
 
// Parameters (in ms) for log-normal CDF scoring. To see the curve:
// https://www.desmos.com/calculator/mdgjzchijg
const SCORING_POINT_OF_DIMINISHING_RETURNS = 1250;
const SCORING_MEDIAN = 5500;
 
class SpeedIndexMetric extends Audit {
  /**
   * @return {!AuditMeta}
   */
  static get meta() {
    return {
      category: 'Performance',
      name: 'speed-index-metric',
      description: 'Speed Index',
      optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString(),
      requiredArtifacts: ['Speedline']
    };
  }
 
  /**
   * Audits the page to give a score for the Speed Index.
   * @see  https://github.com/GoogleChrome/lighthouse/issues/197
   * @param {!Artifacts} artifacts The artifacts from the gather phase.
   * @return {!Promise<!AuditResult>} The score from the audit, ranging from 0-100.
   */
  static audit(artifacts) {
    return new Promise((resolve, reject) => {
      const speedline = artifacts.Speedline;
 
      // Speedline gather failed; pass on error condition.
      if (speedline.debugString) {
        return resolve(SpeedIndexMetric.generateAuditResult({
          value: -1,
          debugString: speedline.debugString
        }));
      }
 
      if (speedline.frames.length === 0) {
        return resolve(SpeedIndexMetric.generateAuditResult({
          value: -1,
          debugString: 'Trace unable to find visual progress frames.'
        }));
      }
 
      if (speedline.frames.length < 3) {
        return resolve(SpeedIndexMetric.generateAuditResult({
          value: -1,
          debugString: 'Trace unable to find sufficient frames to evaluate Speed Index.'
        }));
      }
 
      if (speedline.speedIndex === 0) {
        return resolve(SpeedIndexMetric.generateAuditResult({
          value: -1,
          debugString: 'Error in Speedline calculating Speed Index (speedIndex of 0).'
        }));
      }
 
      // Use the CDF of a log-normal distribution for scoring.
      //  10th Percentile = 2,240
      //  25th Percentile = 3,430
      //  Median = 5,500
      //  75th Percentile = 8,820
      //  95th Percentile = 17,400
      const distribution = TracingProcessor.getLogNormalDistribution(SCORING_MEDIAN,
          SCORING_POINT_OF_DIMINISHING_RETURNS);
      let score = 100 * distribution.computeComplementaryPercentile(speedline.speedIndex);
 
      // Clamp the score to 0 <= x <= 100.
      score = Math.min(100, score);
      score = Math.max(0, score);
 
      const extendedInfo = {
        first: speedline.first,
        complete: speedline.complete,
        duration: speedline.duration,
        frames: speedline.frames.map(frame => {
          return {
            timestamp: frame.getTimeStamp(),
            progress: frame.getProgress()
          };
        })
      };
 
      resolve(SpeedIndexMetric.generateAuditResult({
        value: Math.round(score),
        rawValue: Math.round(speedline.speedIndex),
        optimalValue: this.meta.optimalValue,
        extendedInfo: {
          formatter: Formatter.SUPPORTED_FORMATS.SPEEDLINE,
          value: extendedInfo
        }
      }));
    });
  }
}
 
module.exports = SpeedIndexMetric;