/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2019 AcceleratXR, Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// import JobCoordinator from "./JobCoordinator"; import * as process from "process"; import { CharmColor } from "charm"; import { Job } from "./Job"; import * as ms from "ms"; const charm = require("charm")(); // The length of time that the progress bar animation will take, in milliseconds const ANIM_LENGTH: number = 10000; /** * The `CLIVisualizer` is responsible for displaying the current state of all active jobs to `stdout` and the provided * logger. * * @author Jean-Philippe Steinmetz */ class CLIVisualizer { private animStart: number; private logger: any; private interval: NodeJS.Timeout; private wave: string = ""; private waveInterval: NodeJS.Timeout; constructor() { this.run = this.run.bind(this); } /** * Initializes the instance with the given defaults. * @param logger */ public init(logger: any) { charm.pipe(process.stdout); this.logger = logger; } /** * Starts execution of the visualizer. */ public start(): void { this.interval = setInterval(() => this.run(), 33); this.animStart = Date.now(); // Randomly generate the initial wave for (let i = 0; i < 10; i++) { this.wave += Math.random() > 0.5 ? 1 : 0; } // Set a timer that will animate the wave this.waveInterval = setInterval(() => { // Move the wave forward one char this.wave = String(Math.random() > 0.5 ? 1 : 0) + this.wave; this.wave = this.wave.substr(0, 10); }, 250); } /** * Stops execution of the visualizer. */ public stop(): void { if (this.interval) { clearInterval(this.interval); } if (this.waveInterval) { clearInterval(this.waveInterval); } } private renderProgressBar(): void { let dashes: string = ""; for (let i = 0; i < process.stdout.columns; i++) { dashes += "-"; } let spaces: string = ""; let waves: string = ""; const progress: number = (Date.now() - this.animStart) / ANIM_LENGTH; const maxChars: number = Math.min(progress * process.stdout.columns, process.stdout.columns); for (let i = 0; i < maxChars; i++) { spaces += " "; waves += "~"; } charm.foreground("white").write(`${dashes}\n`); let line: string = `${spaces} /${this.wave.substr(5, 4)}\\\n`; charm.foreground("cyan").write(line.substr(0, process.stdout.columns)); line = `${spaces} /${this.wave.substr(3, 2)}/ \\${this.wave.substr(9, 1)}/\n`; charm.foreground("cyan").write(line.substr(0, process.stdout.columns)); line = `${waves}/${this.wave.substr(0, 3)}\\~~\n`; charm.foreground("cyan").write(line.substr(0, process.stdout.columns)); charm.foreground("white").write(`${dashes}\n`); if (progress >= 1.0) { this.animStart = Date.now(); } } /** * Executes once per interval. */ private run(): void { charm.cursor(false); charm.reset(); charm.up(process.stdout.rows); charm.foreground("cyan").write("Tsunami\n"); this.renderProgressBar(); charm.write("\n\n"); let errors: string = ""; for (const status of JobCoordinator.jobStatus.values()) { const job: Job = JobCoordinator.jobs.get(status.uid); const elapsedTime: number = job && job.startTime ? Date.now() - job.startTime : 0; const color: CharmColor = status.state === "COMPLETE" ? "green" : status.state === "FAILED" ? "red" : "cyan"; charm.foreground("white").write(`Test: ${status.name}\tVUS: ${status.vus}\tElapsed: ${ms(elapsedTime)}`); charm.foreground("green").write(`\tSuccesses: ${status.successes}`); charm.foreground("red").write(`\tFailures: ${status.failures}`); charm.foreground(color).write(`\tState: ${status.state}\n`); if (status.error) { errors += JSON.stringify(status.error) + "\n"; } } if (errors.length > 0) { charm.foreground("red").write("\nErrors: " + errors); } charm.foreground("white").write("\nPress Ctrl+C to stop the test.\n"); } } const instance: CLIVisualizer = new CLIVisualizer(); export default instance;