react/scripts/bench/benchmark.js

131 lines
3.0 KiB
JavaScript

'use strict';
const Lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const stats = require('stats-analysis');
const config = require('lighthouse/lighthouse-core/config/perf-config');
const spawn = require('child_process').spawn;
const os = require('os');
const timesToRun = 10;
function wait(val) {
return new Promise(resolve => setTimeout(resolve, val));
}
async function runScenario(benchmark, chrome) {
const port = chrome.port;
const results = await Lighthouse(
`http://localhost:8080/${benchmark}/`,
{
output: 'json',
port,
},
config
);
const perfMarkings = results.lhr.audits['user-timings'].details.items;
const entries = perfMarkings
.filter(({timingType}) => timingType !== 'Mark')
.map(({duration, name}) => ({
entry: name,
time: duration,
}));
entries.push({
entry: 'First Meaningful Paint',
time: results.lhr.audits['first-meaningful-paint'].rawValue,
});
return entries;
}
function bootstrap(data) {
const len = data.length;
const arr = Array(len);
for (let j = 0; j < len; j++) {
arr[j] = data[(Math.random() * len) | 0];
}
return arr;
}
function calculateStandardErrorOfMean(data) {
const means = [];
for (let i = 0; i < 10000; i++) {
means.push(stats.mean(bootstrap(data)));
}
return stats.stdev(means);
}
function calculateAverages(runs) {
const data = [];
const averages = [];
runs.forEach((entries, x) => {
entries.forEach(({entry, time}, i) => {
if (i >= averages.length) {
data.push([time]);
averages.push({
entry,
mean: 0,
sem: 0,
});
} else {
data[i].push(time);
if (x === runs.length - 1) {
const dataWithoutOutliers = stats.filterMADoutliers(data[i]);
averages[i].mean = stats.mean(dataWithoutOutliers);
averages[i].sem = calculateStandardErrorOfMean(data[i]);
}
}
});
});
return averages;
}
async function initChrome() {
const platform = os.platform();
if (platform === 'linux') {
process.env.XVFBARGS = '-screen 0, 1024x768x16';
process.env.LIGHTHOUSE_CHROMIUM_PATH = 'chromium-browser';
const child = spawn('xvfb start', [{detached: true, stdio: ['ignore']}]);
child.unref();
// wait for chrome to load then continue
await wait(3000);
return child;
}
}
async function launchChrome(headless) {
return await chromeLauncher.launch({
chromeFlags: [headless ? '--headless' : ''],
});
}
async function runBenchmark(benchmark, headless) {
const results = {
runs: [],
averages: [],
};
await initChrome();
for (let i = 0; i < timesToRun; i++) {
let chrome = await launchChrome(headless);
results.runs.push(await runScenario(benchmark, chrome));
// add a delay or sometimes it confuses lighthouse and it hangs
await wait(500);
try {
await chrome.kill();
} catch (e) {}
}
results.averages = calculateAverages(results.runs);
return results;
}
module.exports = runBenchmark;