Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ora based progress bar
/* 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