@best/console-stream
Version:
Best stdout stream wrapper
267 lines • 10.6 kB
JavaScript
"use strict";
/*
* Copyright (c) 2019, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const utils_1 = require("@best/utils");
const chalk_1 = __importDefault(require("chalk"));
const trim_path_1 = __importDefault(require("./utils/trim-path"));
const count_eod_1 = __importDefault(require("./utils/count-eod"));
const proxy_stream_1 = require("./utils/proxy-stream");
var State;
(function (State) {
State["QUEUED"] = "QUEUED";
State["RUNNING"] = "RUNNING";
State["DONE"] = "DONE";
State["ERROR"] = "ERROR";
})(State || (State = {}));
const STATE_ANSI = {
RUNNING: chalk_1.default.reset.inverse.yellow.bold(` ${State.RUNNING} `),
QUEUED: chalk_1.default.reset.inverse.gray.bold(` ${State.QUEUED} `),
ERROR: chalk_1.default.reset.inverse.redBright.bold(` ${State.ERROR} `),
DONE: chalk_1.default.reset.inverse.green.bold(` ${State.DONE} `),
};
const INIT_MSG = '\n Running benchmarks... \n\n';
const PROGRESS_TEXT = chalk_1.default.dim('Progress running: ');
const PROGRESS_BAR_WIDTH = 40;
const DEFAULT_TIMEOUT = 60;
function printState(state) {
return STATE_ANSI[state];
}
function printDisplayName(displayPath, overflow) {
const dirname = overflow ? (0, trim_path_1.default)(path_1.default.dirname(displayPath), overflow) : path_1.default.dirname(displayPath);
const basename = path_1.default.basename(displayPath);
return chalk_1.default.dim(dirname + path_1.default.sep) + chalk_1.default.bold(basename);
}
function printProjectName(projectName) {
return ' ' + chalk_1.default.reset.cyan.dim(`(${projectName})`);
}
function calculateBenchmarkProgress(progress, { iterations, maxDuration, minSampleCount }) {
const { executedIterations, executedTime } = progress;
const avgIteration = executedTime / executedIterations;
const runtime = parseInt(executedTime / 1000 + '', 10);
let estimated;
if (iterations) {
estimated = Math.round((iterations * avgIteration) / 1000) + 1;
}
else if (avgIteration * minSampleCount > maxDuration) {
estimated = Math.round((minSampleCount * avgIteration) / 1000) + 1;
}
else {
estimated = maxDuration / 1000;
}
return {
executedIterations,
estimated,
runtime,
avgIteration,
};
}
function printProgressBar(runTime, estimatedTime, width) {
// If we are more than one second over the estimated time, highlight it.
const renderedTime = estimatedTime && runTime >= estimatedTime + 1 ? chalk_1.default.bold.yellow(runTime + 's') : runTime + 's';
let time = chalk_1.default.bold(`Time:`) + ` ${renderedTime}`;
if (runTime < estimatedTime) {
time += `, estimated ${estimatedTime}s`;
}
// Only show a progress bar if the test run is actually going to take some time
if (estimatedTime > 2 && runTime < estimatedTime && width) {
const availableWidth = Math.min(PROGRESS_BAR_WIDTH, width);
const length = Math.min(Math.floor((runTime / estimatedTime) * availableWidth), availableWidth);
if (availableWidth >= 2) {
time += '\n' + chalk_1.default.green('█').repeat(length) + chalk_1.default.white('█').repeat(availableWidth - length);
}
}
return time;
}
class RunnerOutputStream {
stdoutColumns;
stdoutWrite;
isInteractive;
_streamBuffer = '';
_state;
_innerLog = '';
_scheduled = null;
_proxyStream;
constructor(buildConfig, stream, isInteractive) {
this.stdoutColumns = stream.columns || 80;
this.stdoutWrite = stream.write.bind(stream);
this.isInteractive = isInteractive !== undefined ? isInteractive : utils_1.isInteractive;
this._state = this.initState(buildConfig);
this._proxyStream = (0, proxy_stream_1.proxyStream)(stream, this.isInteractive);
}
initState(buildConfigs) {
return buildConfigs.reduce((state, benchmarkBundle) => {
benchmarkBundle.benchmarkBuilds.forEach(({ benchmarkEntry, benchmarkSignature, projectConfig: { projectName } }) => {
state.set(benchmarkSignature, {
projectName,
state: State.QUEUED,
displayPath: benchmarkEntry,
});
});
return state;
}, new Map());
}
clearBufferStream() {
let buffer = this._streamBuffer;
const lines = (0, count_eod_1.default)(buffer);
if (lines) {
buffer = '\r\x1B[K\r\x1B[1A'.repeat(lines);
}
if (this.isInteractive) {
// clear last line
this.stdoutWrite('\x1b[999D\x1b[K');
}
this.stdoutWrite(buffer);
this._streamBuffer = '';
}
writeBufferStream(str) {
this._streamBuffer += str;
this.stdoutWrite(str);
}
updateRunnerState(benchmarkSignature, state) {
const stateConfig = this._state.get(benchmarkSignature);
if (!stateConfig) {
throw new Error(`Unknown benchmark build started (${benchmarkSignature})`);
}
if (stateConfig.state !== State.ERROR) {
stateConfig.state = state;
}
}
scheduleUpdate(time, fn) {
if (!this._scheduled) {
this._scheduled = setTimeout(() => {
fn ? fn() : this.updateStream();
this._scheduled = null;
}, time || DEFAULT_TIMEOUT);
}
}
printBenchmarkState({ state, projectName, displayPath, }) {
const columns = this.stdoutColumns;
const overflow = columns - (state.length + projectName.length + displayPath.length + /* for padding */ 14);
const hasOverflow = overflow < 0;
const ansiState = printState(state);
const ansiProjectName = printProjectName(projectName);
const ansiDisplayname = printDisplayName(displayPath, hasOverflow ? Math.abs(overflow) : 0);
return `${ansiState} ${ansiProjectName} ${ansiDisplayname}\n`;
}
printProgress(progress, { displayPath }) {
const benchmarkName = chalk_1.default.bold.black(path_1.default.basename(displayPath));
return ([
`\n${PROGRESS_TEXT} ${benchmarkName}`,
chalk_1.default.bold.black('Avg iteration: ') + progress.avgIteration.toFixed(2) + 'ms',
chalk_1.default.bold.black('Completed iterations: ') + progress.executedIterations,
printProgressBar(progress.runtime, progress.estimated, 40),
].join('\n') + '\n\n');
}
updateStream() {
let buffer = INIT_MSG;
let progressStr = '';
for (const benchmarkState of this._state.values()) {
const { state, displayPath, projectName, progress } = benchmarkState;
buffer += this.printBenchmarkState({ state, displayPath, projectName });
if (state === State.RUNNING && progress) {
progressStr += this.printProgress(progress, benchmarkState);
}
}
const streamProxyBuffer = this._proxyStream.readBuffer();
streamProxyBuffer ? `Buffered console logs:\n ${streamProxyBuffer}` : '';
this.clearBufferStream();
this.writeBufferStream(buffer + progressStr + streamProxyBuffer);
}
log(message) {
this._innerLog = message;
if (this.isInteractive) {
this.scheduleUpdate();
}
else {
this.stdoutWrite(` :: ${message}\n`);
}
}
_clearTimeout() {
if (this._scheduled) {
clearTimeout(this._scheduled);
this._scheduled = null;
}
}
// -- Lifecycle
onBenchmarkStart(benchmarkSignature) {
this.updateRunnerState(benchmarkSignature, State.RUNNING);
if (this.isInteractive) {
this.scheduleUpdate();
}
else {
const benchmarkState = this._state.get(benchmarkSignature);
if (benchmarkState) {
this.stdoutWrite(this.printBenchmarkState(benchmarkState));
}
}
}
onBenchmarkEnd(benchmarkSignature) {
this.updateRunnerState(benchmarkSignature, State.DONE);
this._innerLog = '';
const benchmarkState = this._state.get(benchmarkSignature);
if (benchmarkState) {
if (this.isInteractive) {
if (benchmarkState.state === State.ERROR) {
this.updateStream();
this.stdoutWrite('\n');
}
else {
this.scheduleUpdate();
}
}
else {
this._clearTimeout();
this.stdoutWrite(this.printBenchmarkState(benchmarkState) + '\n');
}
}
}
onBenchmarkError(benchmarkSignature) {
this.updateRunnerState(benchmarkSignature, State.ERROR);
}
updateBenchmarkProgress(benchmarkSignature, updatedBenchmarkState, runtimeOpts) {
const progress = calculateBenchmarkProgress(updatedBenchmarkState, runtimeOpts);
const benchmarkState = this._state.get(benchmarkSignature);
benchmarkState.progress = progress;
const { executedIterations, avgIteration, estimated, runtime } = progress;
const runIter = executedIterations.toString().padEnd(5, ' ');
const avgIter = `${avgIteration.toFixed(2)}ms`.padEnd(10, ' ');
const remaining = estimated - runtime;
if (this.isInteractive) {
this.scheduleUpdate();
}
else {
this.scheduleUpdate(2500, () => {
this.stdoutWrite(` :: ${benchmarkState.displayPath} > ran: ${runIter} | avg: ${avgIter} | remainingTime: ${remaining}s \n`);
});
}
}
init() {
if (this.isInteractive) {
this.updateStream();
}
else {
this.stdoutWrite(INIT_MSG);
}
}
finish() {
this._clearTimeout();
this._proxyStream.unproxyStream();
if (this.isInteractive) {
this.updateStream();
}
else {
this.stdoutWrite('\n');
}
}
}
exports.default = RunnerOutputStream;
//# sourceMappingURL=runner-stream.js.map