1'use strict'; 2 3const { fork } = require('child_process'); 4const { inspect } = require('util'); 5const path = require('path'); 6const CLI = require('./_cli.js'); 7const BenchmarkProgress = require('./_benchmark_progress.js'); 8 9// 10// Parse arguments 11// 12const cli = new CLI(`usage: ./node compare.js [options] [--] <category> ... 13 Run each benchmark in the <category> directory many times using two different 14 node versions. More than one <category> directory can be specified. 15 The output is formatted as csv, which can be processed using for 16 example 'compare.R'. 17 18 --new ./new-node-binary new node binary (required) 19 --old ./old-node-binary old node binary (required) 20 --runs 30 number of samples 21 --filter pattern includes only benchmark scripts matching 22 <pattern> (can be repeated) 23 --exclude pattern excludes scripts matching <pattern> (can be 24 repeated) 25 --set variable=value set benchmark variable (can be repeated) 26 --no-progress don't show benchmark progress indicator 27`, { arrayArgs: ['set', 'filter', 'exclude'], boolArgs: ['no-progress'] }); 28 29if (!cli.optional.new || !cli.optional.old) { 30 cli.abort(cli.usage); 31} 32 33const binaries = ['old', 'new']; 34const runs = cli.optional.runs ? parseInt(cli.optional.runs, 10) : 30; 35const benchmarks = cli.benchmarks(); 36 37if (benchmarks.length === 0) { 38 console.error('No benchmarks found'); 39 process.exitCode = 1; 40 return; 41} 42 43// Create queue from the benchmarks list such both node versions are tested 44// `runs` amount of times each. 45// Note: BenchmarkProgress relies on this order to estimate 46// how much runs remaining for a file. All benchmarks generated from 47// the same file must be run consecutively. 48const queue = []; 49for (const filename of benchmarks) { 50 for (let iter = 0; iter < runs; iter++) { 51 for (const binary of binaries) { 52 queue.push({ binary, filename, iter }); 53 } 54 } 55} 56// queue.length = binary.length * runs * benchmarks.length 57 58// Print csv header 59console.log('"binary","filename","configuration","rate","time"'); 60 61const kStartOfQueue = 0; 62 63const showProgress = !cli.optional['no-progress']; 64let progress; 65if (showProgress) { 66 progress = new BenchmarkProgress(queue, benchmarks); 67 progress.startQueue(kStartOfQueue); 68} 69 70(function recursive(i) { 71 const job = queue[i]; 72 73 const child = fork(path.resolve(__dirname, job.filename), cli.optional.set, { 74 execPath: cli.optional[job.binary], 75 }); 76 77 child.on('message', (data) => { 78 if (data.type === 'report') { 79 // Construct configuration string, " A=a, B=b, ..." 80 let conf = ''; 81 for (const key of Object.keys(data.conf)) { 82 conf += ` ${key}=${inspect(data.conf[key])}`; 83 } 84 conf = conf.slice(1); 85 // Escape quotes (") for correct csv formatting 86 conf = conf.replace(/"/g, '""'); 87 88 console.log(`"${job.binary}","${job.filename}","${conf}",` + 89 `${data.rate},${data.time}`); 90 if (showProgress) { 91 // One item in the subqueue has been completed. 92 progress.completeConfig(data); 93 } 94 } else if (showProgress && data.type === 'config') { 95 // The child has computed the configurations, ready to run subqueue. 96 progress.startSubqueue(data, i); 97 } 98 }); 99 100 child.once('close', (code) => { 101 if (code) { 102 process.exit(code); 103 } 104 if (showProgress) { 105 progress.completeRun(job); 106 } 107 108 // If there are more benchmarks execute the next 109 if (i + 1 < queue.length) { 110 recursive(i + 1); 111 } 112 }); 113})(kStartOfQueue); 114