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 } 43 44 startQueue(index) { 45 this.kStartOfQueue = index; 46 this.currentFile = this.queue[index].filename; 47 this.interval = setInterval(() => { 48 if (this.completedRuns === this.scheduledRuns) { 49 clearInterval(this.interval); 50 } else { 51 this.updateProgress(); 52 } 53 }, 1000); 54 } 55 56 startSubqueue(data, index) { 57 // This subqueue is generated by a new benchmark 58 if (data.name !== this.currentFile || index === this.kStartOfQueue) { 59 this.currentFile = data.name; 60 this.scheduledConfig = data.queueLength; 61 } 62 this.completedConfig = 0; 63 this.updateProgress(); 64 } 65 66 completeConfig() { 67 this.completedConfig++; 68 this.updateProgress(); 69 } 70 71 completeRun() { 72 this.completedRuns++; 73 this.updateProgress(); 74 } 75 76 getProgress() { 77 // Get time as soon as possible. 78 const diff = process.hrtime(this.startTime); 79 80 const completedRuns = this.completedRuns; 81 const scheduledRuns = this.scheduledRuns; 82 const finished = completedRuns === scheduledRuns; 83 84 // Calculate numbers for fractions. 85 const runsPerFile = this.runsPerFile; 86 const completedFiles = Math.floor(completedRuns / runsPerFile); 87 const scheduledFiles = this.benchmarks.length; 88 const completedRunsForFile = 89 finished ? runsPerFile : completedRuns % runsPerFile; 90 const completedConfig = this.completedConfig; 91 const scheduledConfig = this.scheduledConfig; 92 93 // Calculate the percentage. 94 let runRate = 0; // Rate of current incomplete run. 95 if (completedConfig !== scheduledConfig) { 96 runRate = completedConfig / scheduledConfig; 97 } 98 const completedRate = ((completedRuns + runRate) / scheduledRuns); 99 const percent = pad(Math.floor(completedRate * 100), 3, ' '); 100 101 const caption = finished ? 'Done\n' : this.currentFile; 102 return `[${getTime(diff)}|% ${percent}| ` + 103 `${fraction(completedFiles, scheduledFiles)} files | ` + 104 `${fraction(completedRunsForFile, runsPerFile)} runs | ` + 105 `${fraction(completedConfig, scheduledConfig)} configs]: ` + 106 `${caption} `; 107 } 108 109 updateProgress() { 110 if (!process.stderr.isTTY || process.stdout.isTTY) { 111 return; 112 } 113 readline.clearLine(process.stderr); 114 readline.cursorTo(process.stderr, 0); 115 process.stderr.write(this.getProgress()); 116 } 117} 118 119module.exports = BenchmarkProgress; 120