Last active
November 8, 2019 17:08
-
-
Save Restuta/ea94198c1258f5e94f96c365c0bc5d0e to your computer and use it in GitHub Desktop.
Ora based progress bar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-return-assign */ | |
const chalk = require('chalk'); | |
const ora = require('ora'); | |
const prettyMs = require('pretty-ms'); | |
const throttle = require('lodash/throttle'); | |
const getHeapUsed = throttle( | |
() => { | |
const heapUsed = process.memoryUsage().heapUsed / 1024 / 1024; | |
return Math.round(heapUsed, 2); | |
}, | |
200, // ms | |
{ leading: true }, | |
); | |
module.exports = getHeapUsed; | |
const getTimeInMs = ([seconds, nanoSeconds]) => seconds * 1000 + nanoSeconds / 1e6; | |
/** | |
* Creates progress bar with appendable statuses. | |
* @param {String} [title='']Top title of the bar. | |
* @param {String} [fillChar=chalk.cyan('-')] Filling character. | |
* @param {String} [bgChar=' '] Background character | |
* @param {String} [startingChar=chalk.gray('[')] [description] | |
* @param {String} [endingChar=chalk.gray(']')] [description] | |
* @param {Number} [total=10] Aka length of the bar. | |
* @returns {Object} With tick and append functions. | |
*/ | |
const createProgressBar = ({ | |
title = '', | |
fillChar = chalk.cyan('-'), | |
bgChar = ' ', | |
startingChar = chalk.gray('['), | |
endingChar = chalk.gray(']'), | |
total = 10, | |
}) => { | |
const MAX_SCREEN_LENGTH = 80; | |
const barLength = total > MAX_SCREEN_LENGTH ? MAX_SCREEN_LENGTH : total; | |
const getCompleteness = progress => Math.round((barLength * progress) / total); | |
let currentProgress = 0; | |
const fillProgress = () => fillChar.repeat(getCompleteness(currentProgress)); | |
const fillSpace = () => bgChar.repeat(barLength - getCompleteness(currentProgress)); | |
const spinner = ora(); | |
// mutation of spinner text causes it to update | |
const updateProgress = text => (spinner.text = text); | |
let itIsFirstTick = true; | |
let startTime; | |
const appendedLines = []; | |
const heapUsedStart = getHeapUsed(); | |
let throughput; | |
return { | |
tick(count = 1) { | |
// start measuring progress after first tick is set. Progress bar can be created | |
// before first tick arrives, who knows what else is executed before that. | |
if (itIsFirstTick) { | |
startTime = process.hrtime(); | |
itIsFirstTick = false; | |
} | |
let prependedHeader = ''; | |
// const normalizedProgress = getNormalizedProgress(currentProgress); | |
// start the spinner on first tick | |
if (currentProgress === 0) { | |
spinner.start(); | |
} else { | |
const timeSinceStartInMs = getTimeInMs(process.hrtime(startTime)); | |
const averageTimePerCount = timeSinceStartInMs / currentProgress; | |
const roundMeanStr = `${Math.round(averageTimePerCount * 1000) / 1000}ms`; | |
const estTimeLeftStr = prettyMs( | |
Math.round(averageTimePerCount * (total - currentProgress)), | |
{ keepDecimalsOnWholeSeconds: true }, | |
); | |
const avgTicksPerSec = (currentProgress / timeSinceStartInMs) * 1000; | |
const roundAvgTicksStr = `${Math.round(avgTicksPerSec)} ticks/sec`; | |
throughput = roundAvgTicksStr; | |
prependedHeader = chalk.grey( | |
`Throughput: ${roundAvgTicksStr}\n` + | |
`Avg exec time: ${chalk.white(roundMeanStr)}, est. time left: ` + | |
`${chalk.white(estTimeLeftStr)}, ` + | |
`elapsed time: ${prettyMs(timeSinceStartInMs, { | |
keepDecimalsOnWholeSeconds: true, | |
})}` + | |
`, heapUsed: ${getHeapUsed()}mb`, | |
); | |
} | |
currentProgress += count; | |
if (currentProgress >= total) { | |
const heapUsedEnd = getHeapUsed(); | |
const heapUsedDelta = heapUsedEnd - heapUsedStart; | |
if (spinner.isSpinning) { | |
const totalMs = getTimeInMs(process.hrtime(startTime)); | |
// prettier-ignore | |
spinner.succeed( | |
`${title} 🎉 \nTotal run time: ${chalk.cyan( | |
prettyMs(totalMs, { millisecondsDecimalDigits: 3 }), | |
)}${chalk.grey(`, total ticks ${currentProgress},throughput: ${throughput}`)}\n` + | |
`Memory: heap used start: ${heapUsedStart}mb, heap used end: ${heapUsedEnd}mb, ` + | |
`delta: ${heapUsedDelta}mb`, | |
); | |
} | |
return; | |
} | |
const currentProgressFrame = | |
`${title}\n` + | |
`${currentProgress}/${total} ${startingChar}` + | |
`${fillProgress()}${fillSpace()}${endingChar}\n` + | |
`${prependedHeader ? `\n${prependedHeader}\n` : ''}` + | |
`\n${appendedLines.join('\n')}`; | |
updateProgress(currentProgressFrame); | |
}, | |
append(text) { | |
appendedLines.push(text); | |
}, | |
}; | |
}; | |
module.exports = createProgressBar; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment