From e739ee42d08a1c8e2f5b8de58df8e57672d5af39 Mon Sep 17 00:00:00 2001 From: Diego Ferreiro Val Date: Mon, 28 Oct 2019 16:12:13 -0700 Subject: [PATCH] feat: add runInBand for better debugging (#212) * feat: add runInBand for better debugging * fix: add copyright --- packages/@best/builder/package.json | 2 +- packages/@best/builder/src/index.ts | 21 ++++++++- packages/@best/cli/src/cli/args.ts | 8 +++- packages/@best/config/src/index.ts | 1 + packages/@best/config/src/utils/defaults.ts | 1 + packages/@best/config/src/utils/normalize.ts | 3 ++ .../@best/console-stream/src/build-stream.ts | 43 ++++++++++------- .../@best/console-stream/src/runner-stream.ts | 47 ++++++++++++------- .../console-stream/src/utils/proxy-stream.ts | 42 +++++++++++++++++ packages/@best/frontend/package.json | 2 +- packages/@best/types/src/config.ts | 3 ++ yarn.lock | 8 ++-- 12 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 packages/@best/console-stream/src/utils/proxy-stream.ts diff --git a/packages/@best/builder/package.json b/packages/@best/builder/package.json index 368185c1..195f893f 100644 --- a/packages/@best/builder/package.json +++ b/packages/@best/builder/package.json @@ -18,7 +18,7 @@ "mkdirp": "~0.5.1", "ncp": "^2.0.0", "rimraf": "^2.6.2", - "rollup": "~1.25.2", + "rollup": "~1.26.0", "rollup-plugin-compat": "^0.21.7", "worker-farm": "~1.7.0" }, diff --git a/packages/@best/builder/src/index.ts b/packages/@best/builder/src/index.ts index b0403dc6..fa7f1222 100644 --- a/packages/@best/builder/src/index.ts +++ b/packages/@best/builder/src/index.ts @@ -9,6 +9,7 @@ import { FrozenGlobalConfig, FrozenProjectConfig, BuildConfig } from '@best/type import { BuildOutputStream } from "@best/console-stream"; import { isCI } from '@best/utils'; import workerFarm from "worker-farm"; +import { buildBenchmark } from "./build-benchmark" const DEFAULT_FARM_OPTS = { maxConcurrentWorkers: isCI ? 2 : require('os').cpus().length, @@ -17,7 +18,17 @@ const DEFAULT_FARM_OPTS = { interface ChildMessage { type: string, benchmarkPath: string, message: string } -export async function buildBenchmarks(benchmarks: string[], projectConfig: FrozenProjectConfig, globalConfig: FrozenGlobalConfig, buildLogStream: BuildOutputStream): Promise { +async function runInBand(benchmarks: string[], projectConfig: FrozenProjectConfig, globalConfig: FrozenGlobalConfig, buildLogStream: BuildOutputStream): Promise { + const benchmarkResults: BuildConfig[] = []; + for (const benchmark of benchmarks) { + const result = await buildBenchmark(benchmark,projectConfig, globalConfig, buildLogStream); + benchmarkResults.push(result); + } + + return benchmarkResults; +} + +function runInWorkers(benchmarks: string[], projectConfig: FrozenProjectConfig, globalConfig: FrozenGlobalConfig, buildLogStream: BuildOutputStream): Promise { const opts = { ...DEFAULT_FARM_OPTS, onChild: (child: NodeJS.Process) => { @@ -61,3 +72,11 @@ export async function buildBenchmarks(benchmarks: string[], projectConfig: Froze }); }); } + +export function buildBenchmarks(benchmarks: string[], projectConfig: FrozenProjectConfig, globalConfig: FrozenGlobalConfig, buildLogStream: BuildOutputStream): Promise { + if (globalConfig.runInBand) { + return runInBand(benchmarks, projectConfig, globalConfig, buildLogStream); + } else { + return runInWorkers(benchmarks, projectConfig, globalConfig, buildLogStream); + } +} diff --git a/packages/@best/cli/src/cli/args.ts b/packages/@best/cli/src/cli/args.ts index 62857f3f..f6775d18 100644 --- a/packages/@best/cli/src/cli/args.ts +++ b/packages/@best/cli/src/cli/args.ts @@ -63,6 +63,11 @@ export const options: { [key: string]: Options } = { description: 'Displays calculated globalConfig and project configs', type: 'boolean', }, + runInBand: { + default: undefined, + description: "Run in the main thread in one core (no paralellism)", + type: 'boolean' + }, externalStorage: { default: undefined, description: @@ -120,7 +125,7 @@ export const options: { [key: string]: Options } = { }; export function normalize(args: { [x: string]: any; _: string[]; $0: string }): CliConfig { - const { _, help, clearCache, clearResults, showConfigs, disableInteractive, gitIntegration, generateHTML, useHttp, externalStorage, runner, runnerConfig, config, projects, iterations, compareStats, dbAdapter, dbURI, runInBatch } = args; + const { _, help, clearCache, clearResults, showConfigs, disableInteractive, gitIntegration, generateHTML, useHttp, externalStorage, runner, runnerConfig, config, projects, iterations, compareStats, dbAdapter, dbURI, runInBatch, runInBand } = args; return { _, @@ -129,6 +134,7 @@ export function normalize(args: { [x: string]: any; _: string[]; $0: string }): clearResults: Boolean(clearResults), useHttp: Boolean(useHttp), showConfigs: Boolean(showConfigs), + runInBand: Boolean(runInBand), disableInteractive, runInBatch, gitIntegration, diff --git a/packages/@best/config/src/index.ts b/packages/@best/config/src/index.ts index 75f7272b..820a875d 100644 --- a/packages/@best/config/src/index.ts +++ b/packages/@best/config/src/index.ts @@ -28,6 +28,7 @@ function generateProjectConfigs(options: NormalizedConfig, isRoot: boolean, gitI projects: options.projects, rootDir: options.rootDir, rootProjectName: options.projectName, + runInBand: options.runInBand, nonFlagArgs: options.nonFlagArgs, gitInfo: gitInfo, isInteractive: options.isInteractive, diff --git a/packages/@best/config/src/utils/defaults.ts b/packages/@best/config/src/utils/defaults.ts index f3680518..bf100b56 100644 --- a/packages/@best/config/src/utils/defaults.ts +++ b/packages/@best/config/src/utils/defaults.ts @@ -32,6 +32,7 @@ const defaultOptions = { }], plugins: [], projects: [], + runInBand: false, runnerConfig: {}, benchmarkEnvironment: 'production', benchmarkMaxDuration: 1000 * 15, // 15s diff --git a/packages/@best/config/src/utils/normalize.ts b/packages/@best/config/src/utils/normalize.ts index fd6df575..21db7441 100644 --- a/packages/@best/config/src/utils/normalize.ts +++ b/packages/@best/config/src/utils/normalize.ts @@ -77,6 +77,9 @@ function setCliOptionOverrides(initialOptions: UserConfig, argsCLI: CliConfig): case 'runInBatch': options.runInBatch = !!argsCLI[key]; break; + case 'runInBand': + options.runInBand = !!argsCLI[key]; + break; case 'projects': if (argsCLI.projects && argsCLI.projects.length) { options.projects = argsCLI.projects; diff --git a/packages/@best/console-stream/src/build-stream.ts b/packages/@best/console-stream/src/build-stream.ts index ade2b999..b3a5a1c3 100644 --- a/packages/@best/console-stream/src/build-stream.ts +++ b/packages/@best/console-stream/src/build-stream.ts @@ -6,10 +6,11 @@ */ import path from "path"; -import { isInteractive as globaIsInteractive, clearLine } from "@best/utils"; +import { isInteractive as globaIsInteractive } from "@best/utils"; import chalk from "chalk"; import trimPath from "./utils/trim-path"; import countEOL from "./utils/count-eod"; +import { ProxiedStream, proxyStream } from "./utils/proxy-stream"; interface ProjectBenchmarkTests { config: { projectName: string; rootDir: string }, @@ -49,17 +50,21 @@ function printProjectName(projectName: string) { } export default class BuildOutputStream { - stdout: NodeJS.WriteStream; + stdoutColumns: number; + stdoutWrite: Function; isInteractive: boolean; _streamBuffer: string = ''; _state: AllBencharkState; _innerLog: string = ''; _scheduled: NodeJS.Timeout | null = null; + _proxiedStream: ProxiedStream; constructor(buildConfig: ListProjectBenchmarkTests, stream: NodeJS.WriteStream, isInteractive?: boolean) { - this.stdout = stream; + this.stdoutColumns = stream.columns || 80; + this.stdoutWrite = stream.write.bind(stream); this.isInteractive = isInteractive !== undefined ? isInteractive : globaIsInteractive; this._state = this.initState(buildConfig); + this._proxiedStream = proxyStream(stream, this.isInteractive); } initState(buildConfig: ListProjectBenchmarkTests) { @@ -82,14 +87,19 @@ export default class BuildOutputStream { if (lines) { buffer = '\r\x1B[K\r\x1B[1A'.repeat(lines); } - clearLine(this.stdout); - this.stdout.write(buffer); + + if (this.isInteractive) { + // clear last line + this.stdoutWrite('\x1b[999D\x1b[K'); + } + + this.stdoutWrite(buffer); this._streamBuffer = ''; } writeBufferStream(str: string) { this._streamBuffer += str; - this.stdout.write(str); + this.stdoutWrite(str); } updateBenchmarkState(benchmarkPath: string, state: State) { @@ -107,7 +117,7 @@ export default class BuildOutputStream { } else { const benchmarkState = this._state.get(benchmarkPath); if (benchmarkState) { - this.stdout.write(this.printBenchmark(benchmarkState)); + this.stdoutWrite(this.printBenchmark(benchmarkState)); } } } @@ -121,23 +131,23 @@ export default class BuildOutputStream { } } - printBenchmark({ state, projectName, displayPath }:{ state: State, projectName: string, displayPath: string }) { - const columns = this.stdout.columns || 80; + printBenchmark({ state, projectName, displayPath }:{ state: State, projectName: string, displayPath: string }, streamProxyBuffer?: string) { + 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`; + const proxiedBuffer = streamProxyBuffer ? `Buffered console logs:\n ${streamProxyBuffer}` : ''; + return `${ansiState} ${ansiProjectName} ${ansiDisplayname}\n${proxiedBuffer}`; } updateStream() { const innerState = this._innerLog; let buffer = INIT_MSG; for (const { state, displayPath, projectName } of this._state.values()) { - buffer += this.printBenchmark({ state, displayPath, projectName }); + buffer += this.printBenchmark({ state, displayPath, projectName }, this._proxiedStream.readBuffer()); } if (innerState) { @@ -157,7 +167,7 @@ export default class BuildOutputStream { } else { const benchmarkState = this._state.get(benchmarkPath); if (benchmarkState) { - this.stdout.write(this.printBenchmark(benchmarkState) + '\n'); + this.stdoutWrite(this.printBenchmark(benchmarkState, this._proxiedStream.readBuffer()) + '\n'); } } } @@ -167,7 +177,7 @@ export default class BuildOutputStream { if (this.isInteractive) { this.scheduleUpdate(); } else { - this.stdout.write(` :: ${message}\n`); + this.stdoutWrite(` :: ${message}\n`); } } @@ -175,11 +185,12 @@ export default class BuildOutputStream { if (this.isInteractive) { this.updateStream(); } else { - this.stdout.write(INIT_MSG); + this.stdoutWrite(INIT_MSG); } } finish() { + this._proxiedStream.unproxyStream(); if (this._scheduled) { clearTimeout(this._scheduled); this._scheduled = null; @@ -188,7 +199,7 @@ export default class BuildOutputStream { if (this.isInteractive) { this.updateStream(); } else { - this.stdout.write('\n'); + this.stdoutWrite('\n'); } } } diff --git a/packages/@best/console-stream/src/runner-stream.ts b/packages/@best/console-stream/src/runner-stream.ts index af016594..00154fa6 100644 --- a/packages/@best/console-stream/src/runner-stream.ts +++ b/packages/@best/console-stream/src/runner-stream.ts @@ -6,10 +6,11 @@ */ import path from "path"; -import { isInteractive as globaIsInteractive, clearLine } from "@best/utils"; +import { isInteractive as globaIsInteractive } from "@best/utils"; import chalk from "chalk"; import trimPath from "./utils/trim-path"; import countEOL from "./utils/count-eod"; +import { ProxiedStream, proxyStream } from "./utils/proxy-stream"; import { BenchmarkResultsState, BenchmarkRuntimeConfig, @@ -101,19 +102,22 @@ function printProgressBar(runTime: number, estimatedTime: number, width: number) } export default class RunnerOutputStream { - stdout: NodeJS.WriteStream; + stdoutColumns: number; + stdoutWrite: Function; isInteractive: boolean; - _streamBuffer: string = ''; _state: AllBencharkRunnerState; _innerLog: string = ''; _progress: BenchmarkProgress | null = null; _scheduled: NodeJS.Timeout | null = null; + _proxyStream: ProxiedStream; constructor(buildConfig: BuildConfig[], stream: NodeJS.WriteStream, isInteractive?: boolean) { - this.stdout = stream; + this.stdoutColumns = stream.columns || 80; + this.stdoutWrite = stream.write.bind(stream); this.isInteractive = isInteractive !== undefined ? isInteractive : globaIsInteractive; this._state = this.initState(buildConfig); + this._proxyStream = proxyStream(stream, this.isInteractive); } initState(buildConfigs: BuildConfig[]): AllBencharkRunnerState { @@ -136,14 +140,19 @@ export default class RunnerOutputStream { if (lines) { buffer = '\r\x1B[K\r\x1B[1A'.repeat(lines); } - clearLine(this.stdout); - this.stdout.write(buffer); + + if (this.isInteractive) { + // clear last line + this.stdoutWrite('\x1b[999D\x1b[K'); + } + + this.stdoutWrite(buffer); this._streamBuffer = ''; } writeBufferStream(str: string) { this._streamBuffer += str; - this.stdout.write(str); + this.stdoutWrite(str); } updateRunnerState(benchmarkPath: string, state: State) { @@ -166,7 +175,7 @@ export default class RunnerOutputStream { } printBenchmarkState({ state, projectName, displayPath }: { state: State, projectName: string, displayPath: string }) { - const columns = this.stdout.columns || 80; + const columns = this.stdoutColumns; const overflow = columns - (state.length + projectName.length + displayPath.length + /* for padding */ 14); const hasOverflow = overflow < 0; @@ -177,13 +186,14 @@ export default class RunnerOutputStream { return `${ansiState} ${ansiProjectName} ${ansiDisplayname}\n`; } - printProgress(progress: BenchmarkProgress, { displayPath }: BenchmarkStatus): string { + printProgress(progress: BenchmarkProgress, { displayPath }: BenchmarkStatus, streamProxyBuffer?: string): string { const benchmarkName = chalk.bold.black(path.basename(displayPath)); return [ `\n${PROGRESS_TEXT} ${benchmarkName}`, chalk.bold.black('Avg iteration: ') + progress.avgIteration.toFixed(2) + 'ms', chalk.bold.black('Completed iterations: ') + progress.executedIterations, - printProgressBar(progress.runtime, progress.estimated, 40) + printProgressBar(progress.runtime, progress.estimated, 40), + streamProxyBuffer ? `Buffered console logs:\n ${streamProxyBuffer}` : '' ].join('\n') + '\n\n'; } @@ -200,7 +210,7 @@ export default class RunnerOutputStream { } if (current && progress) { - buffer += this.printProgress(progress, current); + buffer += this.printProgress(progress, current, this._proxyStream.readBuffer()); } this.clearBufferStream(); @@ -212,7 +222,7 @@ export default class RunnerOutputStream { if (this.isInteractive) { this.scheduleUpdate(); } else { - this.stdout.write(` :: ${message}\n`); + this.stdoutWrite(` :: ${message}\n`); } } @@ -231,7 +241,7 @@ export default class RunnerOutputStream { } else { const benchmarkState = this._state.get(benchmarkPath); if (benchmarkState) { - this.stdout.write(this.printBenchmarkState(benchmarkState)); + this.stdoutWrite(this.printBenchmarkState(benchmarkState)); } } } @@ -245,13 +255,13 @@ export default class RunnerOutputStream { if (this.isInteractive) { if (benchmarkState.state === State.ERROR) { this.updateStream(); - this.stdout.write('\n'); + this.stdoutWrite('\n'); } else { this.scheduleUpdate(); } } else { this._clearTimeout(); - this.stdout.write(this.printBenchmarkState(benchmarkState) + '\n'); + this.stdoutWrite(this.printBenchmarkState(benchmarkState) + '\n'); } } } @@ -271,7 +281,7 @@ export default class RunnerOutputStream { this.scheduleUpdate(); } else { this.scheduleUpdate(2500, () => { - this.stdout.write( + this.stdoutWrite( ` :: ran: ${runIter} | avg: ${avgIter} | remainingTime: ${remaining}s \n` ); }); @@ -282,16 +292,17 @@ export default class RunnerOutputStream { if (this.isInteractive) { this.updateStream(); } else { - this.stdout.write(INIT_MSG); + this.stdoutWrite(INIT_MSG); } } finish() { this._clearTimeout(); + this._proxyStream.unproxyStream(); if (this.isInteractive) { this.updateStream(); } else { - this.stdout.write('\n'); + this.stdoutWrite('\n'); } } } diff --git a/packages/@best/console-stream/src/utils/proxy-stream.ts b/packages/@best/console-stream/src/utils/proxy-stream.ts new file mode 100644 index 00000000..6a5f81e0 --- /dev/null +++ b/packages/@best/console-stream/src/utils/proxy-stream.ts @@ -0,0 +1,42 @@ +/* + * 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 +*/ + +export interface ProxiedStream { + unproxyStream(): void; + readBuffer(): string; + clearBuffer(): void; + writeBuffer(msg?: string): void; +} + +export function proxyStream(stream: any, isInteractive: boolean): ProxiedStream { + const _originalWrite = stream.write; + let proxyBuffer = ''; + if (isInteractive) { + stream.write = (msg: string) => { + proxyBuffer += msg; + } + } + + return { + unproxyStream() { + proxyBuffer = ''; + stream.write = _originalWrite; + }, + readBuffer() { + return proxyBuffer; + }, + clearBuffer() { + proxyBuffer = ''; + }, + writeBuffer(msg: string) { + if (msg) { + proxyBuffer += msg; + } + } + }; + +} diff --git a/packages/@best/frontend/package.json b/packages/@best/frontend/package.json index 4faea3f2..075c68f5 100644 --- a/packages/@best/frontend/package.json +++ b/packages/@best/frontend/package.json @@ -23,7 +23,7 @@ "query-string": "^6.6.0", "redux": "^4.0.1", "redux-thunk": "^2.3.0", - "rollup": "~1.25.2", + "rollup": "~1.26.0", "rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-node-resolve": "^5.0.3", "rollup-plugin-replace": "^2.2.0", diff --git a/packages/@best/types/src/config.ts b/packages/@best/types/src/config.ts index a96dc494..4ba25c6e 100644 --- a/packages/@best/types/src/config.ts +++ b/packages/@best/types/src/config.ts @@ -54,6 +54,7 @@ export interface CliConfig { externalStorage?: string, runner: string, runnerConfig: { [x:string]: any }, + runInBand: boolean, config: string | undefined, projects: string[], iterations?: number, @@ -85,6 +86,7 @@ export interface NormalizedConfig { projectName: string, projects: string[], plugins: ProjectConfigPlugin[], + runInBand: boolean, runner: string, runners: RunnerConfig[], runnerConfig: any, @@ -106,6 +108,7 @@ export interface GlobalConfig { gitIntegration: boolean; projects: string[]; rootDir: string; + runInBand: boolean; compareStats?: string[]; nonFlagArgs: string[]; isInteractive?: boolean; diff --git a/yarn.lock b/yarn.lock index 65e744dd..5fd8256e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12393,10 +12393,10 @@ rollup@^1.7.4: "@types/node" "^12.0.8" acorn "^6.1.1" -rollup@~1.25.2: - version "1.25.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.25.2.tgz#739f508bd8f7ece52bb6c1fcda83466af82b7f6d" - integrity sha512-+7z6Wab/L45QCPcfpuTZKwKiB0tynj05s/+s2U3F2Bi7rOLPr9UcjUwO7/xpjlPNXA/hwnth6jBExFRGyf3tMg== +rollup@~1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.26.0.tgz#cf40fd5e1edc4d7f3d4235a0a43f1c2be1cf294b" + integrity sha512-5HljNYn9icFvXX+Oe97qY5TWvnWhKqgGT0HGeWWqFPx7w7+Anzg7dfHMtUif7YYy6QxAgynDSwK6uxbgcrVUxw== dependencies: "@types/estree" "*" "@types/node" "*"