UNPKG

@best/console-stream

Version:

Best stdout stream wrapper

267 lines 10.6 kB
"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