1. 'use strict';
    
  2. 
    
  3. const chalk = require('chalk');
    
  4. const Table = require('cli-table');
    
  5. 
    
  6. function percentChange(prev, current, prevSem, currentSem) {
    
  7.   const [mean, sd] = calculateMeanAndSdOfRatioFromDeltaMethod(
    
  8.     prev,
    
  9.     current,
    
  10.     prevSem,
    
  11.     currentSem
    
  12.   );
    
  13.   const pctChange = +(mean * 100).toFixed(1);
    
  14.   const ci95 = +(100 * 1.96 * sd).toFixed(1);
    
  15. 
    
  16.   const ciInfo = ci95 > 0 ? ` +- ${ci95} %` : '';
    
  17.   const text = `${pctChange > 0 ? '+' : ''}${pctChange} %${ciInfo}`;
    
  18.   if (pctChange + ci95 < 0) {
    
  19.     return chalk.green(text);
    
  20.   } else if (pctChange - ci95 > 0) {
    
  21.     return chalk.red(text);
    
  22.   } else {
    
  23.     // Statistically insignificant.
    
  24.     return text;
    
  25.   }
    
  26. }
    
  27. 
    
  28. function calculateMeanAndSdOfRatioFromDeltaMethod(
    
  29.   meanControl,
    
  30.   meanTest,
    
  31.   semControl,
    
  32.   semTest
    
  33. ) {
    
  34.   const mean =
    
  35.     (meanTest - meanControl) / meanControl -
    
  36.     (Math.pow(semControl, 2) * meanTest) / Math.pow(meanControl, 3);
    
  37.   const variance =
    
  38.     Math.pow(semTest / meanControl, 2) +
    
  39.     Math.pow(semControl * meanTest, 2) / Math.pow(meanControl, 4);
    
  40.   return [mean, Math.sqrt(variance)];
    
  41. }
    
  42. 
    
  43. function addBenchmarkResults(table, localResults, remoteMasterResults) {
    
  44.   const benchmarks = Object.keys(
    
  45.     (localResults && localResults.benchmarks) ||
    
  46.       (remoteMasterResults && remoteMasterResults.benchmarks)
    
  47.   );
    
  48.   benchmarks.forEach(benchmark => {
    
  49.     const rowHeader = [chalk.white.bold(benchmark)];
    
  50.     if (remoteMasterResults) {
    
  51.       rowHeader.push(chalk.white.bold('Time'));
    
  52.     }
    
  53.     if (localResults) {
    
  54.       rowHeader.push(chalk.white.bold('Time'));
    
  55.     }
    
  56.     if (localResults && remoteMasterResults) {
    
  57.       rowHeader.push(chalk.white.bold('Diff'));
    
  58.     }
    
  59.     table.push(rowHeader);
    
  60. 
    
  61.     const measurements =
    
  62.       (localResults && localResults.benchmarks[benchmark].averages) ||
    
  63.       (remoteMasterResults &&
    
  64.         remoteMasterResults.benchmarks[benchmark].averages);
    
  65.     measurements.forEach((measurement, i) => {
    
  66.       const row = [chalk.gray(measurement.entry)];
    
  67.       let remoteMean;
    
  68.       let remoteSem;
    
  69.       if (remoteMasterResults) {
    
  70.         remoteMean = remoteMasterResults.benchmarks[benchmark].averages[i].mean;
    
  71.         remoteSem = remoteMasterResults.benchmarks[benchmark].averages[i].sem;
    
  72.         // https://en.wikipedia.org/wiki/1.96 gives a 99% confidence interval.
    
  73.         const ci95 = remoteSem * 1.96;
    
  74.         row.push(
    
  75.           chalk.white(+remoteMean.toFixed(2) + ' ms +- ' + ci95.toFixed(2))
    
  76.         );
    
  77.       }
    
  78.       let localMean;
    
  79.       let localSem;
    
  80.       if (localResults) {
    
  81.         localMean = localResults.benchmarks[benchmark].averages[i].mean;
    
  82.         localSem = localResults.benchmarks[benchmark].averages[i].sem;
    
  83.         const ci95 = localSem * 1.96;
    
  84.         row.push(
    
  85.           chalk.white(+localMean.toFixed(2) + ' ms +- ' + ci95.toFixed(2))
    
  86.         );
    
  87.       }
    
  88.       if (localResults && remoteMasterResults) {
    
  89.         row.push(percentChange(remoteMean, localMean, remoteSem, localSem));
    
  90.       }
    
  91.       table.push(row);
    
  92.     });
    
  93.   });
    
  94. }
    
  95. 
    
  96. function printResults(localResults, remoteMasterResults) {
    
  97.   const head = [''];
    
  98.   if (remoteMasterResults) {
    
  99.     head.push(chalk.yellow.bold('Remote (Merge Base)'));
    
  100.   }
    
  101.   if (localResults) {
    
  102.     head.push(chalk.green.bold('Local (Current Branch)'));
    
  103.   }
    
  104.   if (localResults && remoteMasterResults) {
    
  105.     head.push('');
    
  106.   }
    
  107.   const table = new Table({head});
    
  108.   addBenchmarkResults(table, localResults, remoteMasterResults);
    
  109.   console.log(table.toString());
    
  110. }
    
  111. 
    
  112. module.exports = printResults;