#!/usr/bin/env node /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { logging, tags, terminal } from '@angular-devkit/core'; import { appendFileSync, writeFileSync } from 'fs'; import * as minimist from 'minimist'; import { filter, map, toArray } from 'rxjs/operators'; import { Command } from '../src/command'; import { defaultReporter } from '../src/default-reporter'; import { defaultStatsCapture } from '../src/default-stats-capture'; import { runBenchmark } from '../src/run-benchmark'; export interface MainOptions { args: string[]; stdout?: { write(buffer: string | Buffer): boolean }; stderr?: { write(buffer: string | Buffer): boolean }; } export async function main({ args, stdout = process.stdout, stderr = process.stderr, }: MainOptions): Promise<0 | 1> { // Show usage of the CLI tool, and exit the process. function usage(logger: logging.Logger) { logger.info(tags.stripIndent` benchmark [options] -- [command to benchmark] Collects process stats from running the command. Options: --help Show this message. --verbose Show more information while running. --exit-code Expected exit code for the command. Default is 0. --iterations Number of iterations to run the benchmark over. Default is 5. --retries Number of times to retry when process fails. Default is 5. --cwd Current working directory to run the process in. --output-file File to output benchmark log to. --overwrite-output-file If the output file should be overwritten rather than appended to. --prefix Logging prefix. Example: benchmark --iterations=3 -- node my-script.js `); } interface BenchmarkCliArgv { help: boolean; verbose: boolean; 'overwrite-output-file': boolean; 'exit-code': number; iterations: number; retries: number; 'output-file': string | null; cwd: string; prefix: string; '--': string[] | null; } // Parse the command line. const argv = minimist(args, { boolean: ['help', 'verbose', 'overwrite-output-file'], default: { 'exit-code': 0, 'iterations': 5, 'retries': 5, 'output-file': null, 'cwd': process.cwd(), 'prefix': '[benchmark]', }, '--': true, }) as {} as BenchmarkCliArgv; // Create the DevKit Logger used through the CLI. const logger = new logging.TransformLogger( 'benchmark-prefix-logger', stream => stream.pipe(map(entry => { if (argv['prefix']) { entry.message = `${argv['prefix']} ${entry.message}`; } return entry; })), ); // Log to console. logger .pipe(filter(entry => (entry.level != 'debug' || argv['verbose']))) .subscribe(entry => { let color: (s: string) => string = x => terminal.dim(terminal.white(x)); let output = stdout; switch (entry.level) { case 'info': color = terminal.white; break; case 'warn': color = terminal.yellow; break; case 'error': color = terminal.red; output = stderr; break; case 'fatal': color = (x: string) => terminal.bold(terminal.red(x)); output = stderr; break; } output.write(color(entry.message) + '\n'); }); // Print help. if (argv['help']) { usage(logger); return 0; } const commandArgv = argv['--']; // Exit early if we can't find the command to benchmark. if (!commandArgv || !Array.isArray(argv['--']) || (argv['--'] as Array<string>).length < 1) { logger.fatal(`Missing command, see benchmark --help for help.`); return 1; } // Setup file logging. if (argv['output-file'] !== null) { if (argv['overwrite-output-file']) { writeFileSync(argv['output-file'] as string, ''); } logger.pipe(filter(entry => (entry.level != 'debug' || argv['verbose']))) .subscribe(entry => appendFileSync(argv['output-file'] as string, `${entry.message}\n`)); } // Run benchmark on given command, capturing stats and reporting them. const exitCode = argv['exit-code']; const cmd = commandArgv[0]; const cmdArgs = commandArgv.slice(1); const command = new Command(cmd, cmdArgs, argv['cwd'], exitCode); const captures = [defaultStatsCapture]; const reporters = [defaultReporter(logger)]; const iterations = argv['iterations']; const retries = argv['retries']; logger.info(`Benchmarking process over ${iterations} iterations, with up to ${retries} retries.`); logger.info(` ${command.toString()}`); let res; try { res = await runBenchmark( { command, captures, reporters, iterations, retries, logger }, ).pipe(toArray()).toPromise(); } catch (error) { if (error.message) { logger.fatal(error.message); } else { logger.fatal(error); } return 1; } if (res.length === 0) { return 1; } return 0; } if (require.main === module) { const args = process.argv.slice(2); main({ args }) .then(exitCode => process.exitCode = exitCode) .catch(e => { throw (e); }); }