| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c6af766 commit f61c71b
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,120 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const readline = require('readline'); | ||
| 4 | + | ||
| 5 | + function pad(input, minLength, fill) { | ||
| 6 | + var result = input + ''; | ||
| 7 | + return fill.repeat(Math.max(0, minLength - result.length)) + result; | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + function fraction(numerator, denominator) { | ||
| 11 | + const fdenominator = denominator + ''; | ||
| 12 | + const fnumerator = pad(numerator, fdenominator.length, ' '); | ||
| 13 | + return `${fnumerator}/${fdenominator}`; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + function getTime(diff) { | ||
| 17 | + const time = Math.ceil(diff[0] + diff[1] / 1e9); | ||
| 18 | + const seconds = pad(time % 60, 2, '0'); | ||
| 19 | + const minutes = pad(Math.floor(time / 60) % (60 * 60), 2, '0'); | ||
| 20 | + const hours = pad(Math.floor(time / (60 * 60)), 2, '0'); | ||
| 21 | + return `${hours}:${minutes}:${seconds}`; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + // A run is an item in the job queue: { binary, filename, iter } | ||
| 25 | + // A config is an item in the subqueue: { binary, filename, iter, configs } | ||
| 26 | + class BenchmarkProgress { | ||
| 27 | + constructor(queue, benchmarks) { | ||
| 28 | + this.queue = queue; // Scheduled runs. | ||
| 29 | + this.benchmarks = benchmarks; // Filenames of scheduled benchmarks. | ||
| 30 | + this.completedRuns = 0; // Number of completed runs. | ||
| 31 | + this.scheduledRuns = queue.length; // Number of scheduled runs. | ||
| 32 | + // Time when starting to run benchmarks. | ||
| 33 | + this.startTime = process.hrtime(); | ||
| 34 | + // Number of times each file will be run (roughly). | ||
| 35 | + this.runsPerFile = queue.length / benchmarks.length; | ||
| 36 | + this.currentFile = ''; // Filename of current benchmark. | ||
| 37 | + this.currentFileConfig; // Configurations for current file | ||
| 38 | + // Number of configurations already run for the current file. | ||
| 39 | + this.completedConfig = 0; | ||
| 40 | + // Total number of configurations for the current file | ||
| 41 | + this.scheduledConfig = 0; | ||
| 42 | + this.interval = 0; // result of setInterval for updating the elapsed time | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + startQueue(index) { | ||
| 46 | + this.kStartOfQueue = index; | ||
| 47 | + this.currentFile = this.queue[index].filename; | ||
| 48 | + this.interval = setInterval(() => { | ||
| 49 | + if (this.completedRuns === this.scheduledRuns) { | ||
| 50 | + clearInterval(this.interval); | ||
| 51 | + } else { | ||
| 52 | + this.updateProgress(); | ||
| 53 | + } | ||
| 54 | + }, 1000); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + startSubqueue(data, index) { | ||
| 58 | + // This subqueue is generated by a new benchmark | ||
| 59 | + if (data.name !== this.currentFile || index === this.kStartOfQueue) { | ||
| 60 | + this.currentFile = data.name; | ||
| 61 | + this.scheduledConfig = data.queueLength; | ||
| 62 | + } | ||
| 63 | + this.completedConfig = 0; | ||
| 64 | + this.updateProgress(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + completeConfig(data) { | ||
| 68 | + this.completedConfig++; | ||
| 69 | + this.updateProgress(); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + completeRun(job) { | ||
| 73 | + this.completedRuns++; | ||
| 74 | + this.updateProgress(); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + getProgress() { | ||
| 78 | + // Get time as soon as possible. | ||
| 79 | + const diff = process.hrtime(this.startTime); | ||
| 80 | + | ||
| 81 | + const completedRuns = this.completedRuns; | ||
| 82 | + const scheduledRuns = this.scheduledRuns; | ||
| 83 | + const finished = completedRuns === scheduledRuns; | ||
| 84 | + | ||
| 85 | + // Calculate numbers for fractions. | ||
| 86 | + const runsPerFile = this.runsPerFile; | ||
| 87 | + const completedFiles = Math.floor(completedRuns / runsPerFile); | ||
| 88 | + const scheduledFiles = this.benchmarks.length; | ||
| 89 | + const completedRunsForFile = finished ? runsPerFile : | ||
| 90 | + completedRuns % runsPerFile; | ||
| 91 | + const completedConfig = this.completedConfig; | ||
| 92 | + const scheduledConfig = this.scheduledConfig; | ||
| 93 | + | ||
| 94 | + // Calculate the percentage. | ||
| 95 | + let runRate = 0; // Rate of current incomplete run. | ||
| 96 | + if (completedConfig !== scheduledConfig) { | ||
| 97 | + runRate = completedConfig / scheduledConfig; | ||
| 98 | + } | ||
| 99 | + const completedRate = ((completedRuns + runRate) / scheduledRuns); | ||
| 100 | + const percent = pad(Math.floor(completedRate * 100), 3, ' '); | ||
| 101 | + | ||
| 102 | + const caption = finished ? 'Done\n' : this.currentFile; | ||
| 103 | + return `[${getTime(diff)}|% ${percent}` + | ||
| 104 | + `| ${fraction(completedFiles, scheduledFiles)} files ` + | ||
| 105 | + `| ${fraction(completedRunsForFile, runsPerFile)} runs ` + | ||
| 106 | + `| ${fraction(completedConfig, scheduledConfig)} configs]` + | ||
| 107 | + `: ${caption}`; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + updateProgress(finished) { | ||
| 111 | + if (!process.stderr.isTTY || process.stdout.isTTY) { | ||
| 112 | + return; | ||
| 113 | + } | ||
| 114 | + readline.clearLine(process.stderr); | ||
| 115 | + readline.cursorTo(process.stderr, 0); | ||
| 116 | + process.stderr.write(this.getProgress()); | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + module.exports = BenchmarkProgress; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,13 +45,13 @@ function CLI(usage, settings) { | |||
| 45 | 45 | currentOptional = arg.slice(1); | |
| 46 | 46 | } | |
| 47 | 47 | ||
| 48 | - // Default the value to true | ||
| 49 | - if (!settings.arrayArgs.includes(currentOptional)) { | ||
| 48 | + if (settings.boolArgs && settings.boolArgs.includes(currentOptional)) { | ||
| 50 | 49 | this.optional[currentOptional] = true; | |
| 50 | + mode = 'both'; | ||
| 51 | + } else { | ||
| 52 | + // expect the next value to be option related (either -- or the value) | ||
| 53 | + mode = 'option'; | ||
| 51 | 54 | } | |
| 52 | - | ||
| 53 | - // expect the next value to be option related (either -- or the value) | ||
| 54 | - mode = 'option'; | ||
| 55 | 55 | } else if (mode === 'option') { | |
| 56 | 56 | // Optional arguments value | |
| 57 | 57 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -128,6 +128,14 @@ Benchmark.prototype.http = function(options, cb) { | |||
| 128 | 128 | ||
| 129 | 129 | Benchmark.prototype._run = function() { | |
| 130 | 130 | const self = this; | |
| 131 | + // If forked, report to the parent. | ||
| 132 | + if (process.send) { | ||
| 133 | + process.send({ | ||
| 134 | + type: 'config', | ||
| 135 | + name: this.name, | ||
| 136 | + queueLength: this.queue.length | ||
| 137 | + }); | ||
| 138 | + } | ||
| 131 | 139 | ||
| 132 | 140 | (function recursive(queueIndex) { | |
| 133 | 141 | const config = self.queue[queueIndex]; | |
@@ -217,7 +225,8 @@ Benchmark.prototype.report = function(rate, elapsed) { | |||
| 217 | 225 | name: this.name, | |
| 218 | 226 | conf: this.config, | |
| 219 | 227 | rate: rate, | |
| 220 | - time: elapsed[0] + elapsed[1] / 1e9 | ||
| 228 | + time: elapsed[0] + elapsed[1] / 1e9, | ||
| 229 | + type: 'report' | ||
| 221 | 230 | }); | |
| 222 | 231 | }; | |
| 223 | 232 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,6 +3,7 @@ | |||
| 3 | 3 | const fork = require('child_process').fork; | |
| 4 | 4 | const path = require('path'); | |
| 5 | 5 | const CLI = require('./_cli.js'); | |
| 6 | + const BenchmarkProgress = require('./_benchmark_progress.js'); | ||
| 6 | 7 | ||
| 7 | 8 | // | |
| 8 | 9 | // Parse arguments | |
@@ -13,13 +14,15 @@ const cli = CLI(`usage: ./node compare.js [options] [--] <category> ... | |||
| 13 | 14 | The output is formatted as csv, which can be processed using for | |
| 14 | 15 | example 'compare.R'. | |
| 15 | 16 | ||
| 16 | - --new ./new-node-binary new node binary (required) | ||
| 17 | - --old ./old-node-binary old node binary (required) | ||
| 18 | - --runs 30 number of samples | ||
| 19 | - --filter pattern string to filter benchmark scripts | ||
| 20 | - --set variable=value set benchmark variable (can be repeated) | ||
| 17 | + --new ./new-node-binary new node binary (required) | ||
| 18 | + --old ./old-node-binary old node binary (required) | ||
| 19 | + --runs 30 number of samples | ||
| 20 | + --filter pattern string to filter benchmark scripts | ||
| 21 | + --set variable=value set benchmark variable (can be repeated) | ||
| 22 | + --no-progress don't show benchmark progress indicator | ||
| 21 | 23 | `, { | |
| 22 | - arrayArgs: ['set'] | ||
| 24 | + arrayArgs: ['set'], | ||
| 25 | + boolArgs: ['no-progress'] | ||
| 23 | 26 | }); | |
| 24 | 27 | ||
| 25 | 28 | if (!cli.optional.new || !cli.optional.old) { | |
@@ -39,6 +42,9 @@ if (benchmarks.length === 0) { | |||
| 39 | 42 | ||
| 40 | 43 | // Create queue from the benchmarks list such both node versions are tested | |
| 41 | 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. | ||
| 42 | 48 | const queue = []; | |
| 43 | 49 | for (const filename of benchmarks) { | |
| 44 | 50 | for (let iter = 0; iter < runs; iter++) { | |
@@ -47,10 +53,20 @@ for (const filename of benchmarks) { | |||
| 47 | 53 | } | |
| 48 | 54 | } | |
| 49 | 55 | } | |
| 56 | + // queue.length = binary.length * runs * benchmarks.length | ||
| 50 | 57 | ||
| 51 | 58 | // Print csv header | |
| 52 | 59 | console.log('"binary", "filename", "configuration", "rate", "time"'); | |
| 53 | 60 | ||
| 61 | + const kStartOfQueue = 0; | ||
| 62 | + | ||
| 63 | + const showProgress = !cli.optional['no-progress']; | ||
| 64 | + let progress; | ||
| 65 | + if (showProgress) { | ||
| 66 | + progress = new BenchmarkProgress(queue, benchmarks); | ||
| 67 | + progress.startQueue(kStartOfQueue); | ||
| 68 | + } | ||
| 69 | + | ||
| 54 | 70 | (function recursive(i) { | |
| 55 | 71 | const job = queue[i]; | |
| 56 | 72 | ||
@@ -59,29 +75,40 @@ console.log('"binary", "filename", "configuration", "rate", "time"'); | |||
| 59 | 75 | }); | |
| 60 | 76 | ||
| 61 | 77 | child.on('message', function(data) { | |
| 62 | - // Construct configuration string, " A=a, B=b, ..." | ||
| 63 | - let conf = ''; | ||
| 64 | - for (const key of Object.keys(data.conf)) { | ||
| 65 | - conf += ' ' + key + '=' + JSON.stringify(data.conf[key]); | ||
| 66 | - } | ||
| 67 | - conf = conf.slice(1); | ||
| 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 + '=' + JSON.stringify(data.conf[key]); | ||
| 83 | + } | ||
| 84 | + conf = conf.slice(1); | ||
| 85 | + // Escape quotes (") for correct csv formatting | ||
| 86 | + conf = conf.replace(/"/g, '""'); | ||
| 68 | 87 | ||
| 69 | - // Escape quotes (") for correct csv formatting | ||
| 70 | - conf = conf.replace(/"/g, '""'); | ||
| 71 | - | ||
| 72 | - console.log(`"${job.binary}", "${job.filename}", "${conf}", ` + | ||
| 73 | - `${data.rate}, ${data.time}`); | ||
| 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 | + } | ||
| 74 | 98 | }); | |
| 75 | 99 | ||
| 76 | 100 | child.once('close', function(code) { | |
| 77 | 101 | if (code) { | |
| 78 | 102 | process.exit(code); | |
| 79 | 103 | return; | |
| 80 | 104 | } | |
| 105 | + if (showProgress) { | ||
| 106 | + progress.completeRun(job); | ||
| 107 | + } | ||
| 81 | 108 | ||
| 82 | 109 | // If there are more benchmarks execute the next | |
| 83 | 110 | if (i + 1 < queue.length) { | |
| 84 | 111 | recursive(i + 1); | |
| 85 | 112 | } | |
| 86 | 113 | }); | |
| 87 | - })(0); | ||
| 114 | + })(kStartOfQueue); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,6 +44,9 @@ if (format === 'csv') { | |||
| 44 | 44 | } | |
| 45 | 45 | ||
| 46 | 46 | child.on('message', function(data) { | |
| 47 | + if (data.type !== 'report') { | ||
| 48 | + return; | ||
| 49 | + } | ||
| 47 | 50 | // Construct configuration string, " A=a, B=b, ..." | |
| 48 | 51 | let conf = ''; | |
| 49 | 52 | for (const key of Object.keys(data.conf)) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -42,6 +42,10 @@ function csvEncodeValue(value) { | |||
| 42 | 42 | const child = fork(path.resolve(__dirname, filepath), cli.optional.set); | |
| 43 | 43 | ||
| 44 | 44 | child.on('message', function(data) { | |
| 45 | + if (data.type !== 'report') { | ||
| 46 | + return; | ||
| 47 | + } | ||
| 48 | + | ||
| 45 | 49 | // print csv header | |
| 46 | 50 | if (printHeader) { | |
| 47 | 51 | const confHeader = Object.keys(data.conf) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments