1'use strict'; 2 3const readline = require('readline'); 4 5function pad(input, minLength, fill) { 6 const result = String(input); 7 const padding = fill.repeat(Math.max(0, minLength - result.length)); 8 return `${padding}${result}`; 9} 10 11function fraction(numerator, denominator) { 12 const fdenominator = String(denominator); 13 const fnumerator = pad(numerator, fdenominator.length, ' '); 14 return `${fnumerator}/${fdenominator}`; 15} 16 17function getTime(diff) { 18 const time = Math.ceil(diff[0] + diff[1] / 1e9); 19 const hours = pad(Math.floor(time / 3600), 2, '0'); 20 const minutes = pad(Math.floor((time % 3600) / 60), 2, '0'); 21 const seconds = pad((time % 3600) % 60, 2, '0'); 22 return `${hours}:${minutes}:${seconds}`; 23} 24 25// A run is an item in the job queue: { binary, filename, iter } 26// A config is an item in the subqueue: { binary, filename, iter, configs } 27class BenchmarkProgress { 28 constructor(queue, benchmarks) { 29 this.queue = queue; // Scheduled runs. 30 this.benchmarks = benchmarks; // Filenames of scheduled benchmarks. 31 this.completedRuns = 0; // Number of completed runs. 32 this.scheduledRuns = queue.length; // Number of scheduled runs. 33 // Time when starting to run benchmarks. 34 this.startTime = process.hrtime(); 35 // Number of times each file will be run (roughly). 36 this.runsPerFile = queue.length / benchmarks.length; 37 this.currentFile = ''; // Filename of current benchmark. 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; // Updates 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() { 68 this.completedConfig++; 69 this.updateProgress(); 70 } 71 72 completeRun() { 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 = 90 finished ? runsPerFile : 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() { 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 120module.exports = BenchmarkProgress; 121