1. 'use strict';
    
  2. 
    
  3. const Lighthouse = require('lighthouse');
    
  4. const chromeLauncher = require('chrome-launcher');
    
  5. 
    
  6. const stats = require('stats-analysis');
    
  7. const config = require('lighthouse/lighthouse-core/config/perf-config');
    
  8. const spawn = require('child_process').spawn;
    
  9. const os = require('os');
    
  10. 
    
  11. const timesToRun = 10;
    
  12. 
    
  13. function wait(val) {
    
  14.   return new Promise(resolve => setTimeout(resolve, val));
    
  15. }
    
  16. 
    
  17. async function runScenario(benchmark, chrome) {
    
  18.   const port = chrome.port;
    
  19.   const results = await Lighthouse(
    
  20.     `http://localhost:8080/${benchmark}/`,
    
  21.     {
    
  22.       output: 'json',
    
  23.       port,
    
  24.     },
    
  25.     config
    
  26.   );
    
  27. 
    
  28.   const perfMarkings = results.lhr.audits['user-timings'].details.items;
    
  29.   const entries = perfMarkings
    
  30.     .filter(({timingType}) => timingType !== 'Mark')
    
  31.     .map(({duration, name}) => ({
    
  32.       entry: name,
    
  33.       time: duration,
    
  34.     }));
    
  35.   entries.push({
    
  36.     entry: 'First Meaningful Paint',
    
  37.     time: results.lhr.audits['first-meaningful-paint'].rawValue,
    
  38.   });
    
  39. 
    
  40.   return entries;
    
  41. }
    
  42. 
    
  43. function bootstrap(data) {
    
  44.   const len = data.length;
    
  45.   const arr = Array(len);
    
  46.   for (let j = 0; j < len; j++) {
    
  47.     arr[j] = data[(Math.random() * len) | 0];
    
  48.   }
    
  49.   return arr;
    
  50. }
    
  51. 
    
  52. function calculateStandardErrorOfMean(data) {
    
  53.   const means = [];
    
  54.   for (let i = 0; i < 10000; i++) {
    
  55.     means.push(stats.mean(bootstrap(data)));
    
  56.   }
    
  57.   return stats.stdev(means);
    
  58. }
    
  59. 
    
  60. function calculateAverages(runs) {
    
  61.   const data = [];
    
  62.   const averages = [];
    
  63. 
    
  64.   runs.forEach((entries, x) => {
    
  65.     entries.forEach(({entry, time}, i) => {
    
  66.       if (i >= averages.length) {
    
  67.         data.push([time]);
    
  68.         averages.push({
    
  69.           entry,
    
  70.           mean: 0,
    
  71.           sem: 0,
    
  72.         });
    
  73.       } else {
    
  74.         data[i].push(time);
    
  75.         if (x === runs.length - 1) {
    
  76.           const dataWithoutOutliers = stats.filterMADoutliers(data[i]);
    
  77.           averages[i].mean = stats.mean(dataWithoutOutliers);
    
  78.           averages[i].sem = calculateStandardErrorOfMean(data[i]);
    
  79.         }
    
  80.       }
    
  81.     });
    
  82.   });
    
  83. 
    
  84.   return averages;
    
  85. }
    
  86. 
    
  87. async function initChrome() {
    
  88.   const platform = os.platform();
    
  89. 
    
  90.   if (platform === 'linux') {
    
  91.     process.env.XVFBARGS = '-screen 0, 1024x768x16';
    
  92.     process.env.LIGHTHOUSE_CHROMIUM_PATH = 'chromium-browser';
    
  93.     const child = spawn('xvfb start', [{detached: true, stdio: ['ignore']}]);
    
  94.     child.unref();
    
  95.     // wait for chrome to load then continue
    
  96.     await wait(3000);
    
  97.     return child;
    
  98.   }
    
  99. }
    
  100. 
    
  101. async function launchChrome(headless) {
    
  102.   return await chromeLauncher.launch({
    
  103.     chromeFlags: [headless ? '--headless' : ''],
    
  104.   });
    
  105. }
    
  106. 
    
  107. async function runBenchmark(benchmark, headless) {
    
  108.   const results = {
    
  109.     runs: [],
    
  110.     averages: [],
    
  111.   };
    
  112. 
    
  113.   await initChrome();
    
  114. 
    
  115.   for (let i = 0; i < timesToRun; i++) {
    
  116.     let chrome = await launchChrome(headless);
    
  117. 
    
  118.     results.runs.push(await runScenario(benchmark, chrome));
    
  119.     // add a delay or sometimes it confuses lighthouse and it hangs
    
  120.     await wait(500);
    
  121.     try {
    
  122.       await chrome.kill();
    
  123.     } catch (e) {}
    
  124.   }
    
  125. 
    
  126.   results.averages = calculateAverages(results.runs);
    
  127.   return results;
    
  128. }
    
  129. 
    
  130. module.exports = runBenchmark;