UNPKG

artillery

Version:

Flexible and powerful toolkit for load and functional testing

207 lines (177 loc) 6.19 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; const ora = require('ora'); const _ = require('lodash'); const moment = require('moment'); const chalk = require('chalk'); const util = require('./util'); module.exports = createConsoleReporter; function createConsoleReporter(events, opts) { const reporter = new ConsoleReporter(opts); events.on('phaseStarted', reporter.phaseStarted.bind(reporter)); events.on('stats', reporter.stats.bind(reporter)); events.on('highcpu', reporter.highcpu.bind(reporter)); events.on('done', reporter.done.bind(reporter)); reporter.start(); return reporter; } function ConsoleReporter(opts) { opts = opts || {}; this.quiet = opts.quiet; this.spinner = ora({ spinner: 'simpleDotsScrolling' }); this.spinnerOn = false; this.reportScenarioLatency = !!opts.reportScenarioLatency; this.startTime = Date.now(); // High CPU usage warning state: this.lastWarningAt = Date.now(); this.alreadyWarned = false; return this; } ConsoleReporter.prototype.cleanup = function(done) { return done(null); }; ConsoleReporter.prototype.start = function start() { if (this.quiet) { return this; } return this.toggleSpinner(); }; ConsoleReporter.prototype.phaseStarted = function phaseStarted(phase) { if (this.quiet) { return this; } this.toggleSpinner(); console.log( 'Started phase %s%s, duration: %ss @ %s', phase.index, phase.name ? ' (' + phase.name + ')' : '', phase.duration || phase.think, formatTimestamp(new Date()) ); this.toggleSpinner(); }; ConsoleReporter.prototype.stats = function stats(data) { if (this.quiet) { return this; } const report = data.report(); this.toggleSpinner(); console.log('Report @ %s', formatTimestamp(report.timestamp)); console.log(`Elapsed time: ${util.formatDuration(Date.now() - this.startTime)}`); this.printReport(report); this.toggleSpinner(); }; ConsoleReporter.prototype.highcpu = function(busyPids) { if (!process.env.ARTILLERY_DISABLE_CPU_MONITORING) { if (Date.now() - this.lastWarningAt > 10 * 1000) { this.toggleSpinner(); if (!this.alreadyWarned) { console.log( chalk.black.bgYellow.bold('Warning:'), chalk.yellow( `\nCPU usage of Artillery seems to be very high (pids: ${busyPids.join(',')})\nwhich may severely affect its performance.\nSee https://artillery.io/docs/faq/#high-cpu-warnings for details.\n` ) ); this.alreadyWarned = true; } else { console.log( chalk.black.bgYellow.bold('Warning:'), chalk.yellow( `High CPU usage warning (pids: ${busyPids.join(',')}).\nSee https://artillery.io/docs/faq/#high-cpu-warnings for details.\n` ) ); } this.lastWarningAt = Date.now(); this.toggleSpinner(); } } }; ConsoleReporter.prototype.done = function done(data) { if (this.quiet) { return this; } const report = data.report(); this.toggleSpinner(); console.log('All virtual users finished'); console.log('Summary report @ %s', formatTimestamp(report.timestamp)); delete report.concurrency; this.printReport(report, { showScenarioCounts: true }); }; ConsoleReporter.prototype.toggleSpinner = function toggleSpinner() { if (this.spinnerOn) { this.spinner.stop(); } else { this.spinner.start(); } this.spinnerOn = !this.spinnerOn; return this; }; ConsoleReporter.prototype.printReport = function printReport(report, opts) { opts = opts || {}; console.log(' Scenarios launched: %s', report.scenariosCreated); console.log(' Scenarios completed: %s', report.scenariosCompleted); console.log(' Requests completed: %s', report.requestsCompleted); // Final report does not have concurrency // if (report.concurrency) { // console.log(' Concurrent users: %s', report.concurrency); // } console.log(' Mean response/sec: %s', report.rps.mean); console.log(' Response time (msec):'); console.log(' min: %s', report.latency.min); console.log(' max: %s', report.latency.max); console.log(' median: %s', report.latency.median); console.log(' p95: %s', report.latency.p95); console.log(' p99: %s', report.latency.p99); if (this.reportScenarioLatency) { console.log(' Scenario duration:'); console.log(' min: %s', report.scenarioDuration.min); console.log(' max: %s', report.scenarioDuration.max); console.log(' median: %s', report.scenarioDuration.median); console.log(' p95: %s', report.scenarioDuration.p95); console.log(' p99: %s', report.scenarioDuration.p99); } // We only want to show this for the aggregate report if (opts.showScenarioCounts && report.scenarioCounts) { console.log(' Scenario counts:'); _.each(report.scenarioCounts, function(count, name) { let percentage = Math.round(count / report.scenariosCreated * 100 * 1000) / 1000; console.log(' %s: %s (%s%)', name, count, percentage); }); } if (_.keys(report.codes).length !== 0) { console.log(' Codes:'); _.each(report.codes, function(count, code) { console.log(' %s: %s', code, count); }); } if (_.keys(report.errors).length !== 0) { console.log(' Errors:'); _.each(report.errors, function(count, code) { console.log(' %s: %s', code, count); }); } if (_.size(report.customStats) > 0 || _.size(report.counters) > 0) { _.each(report.customStats, function(r, n) { console.log(' %s:', n); console.log(' min: %s', r.min); console.log(' max: %s', r.max); console.log(' median: %s', r.median); console.log(' p95: %s', r.p95); console.log(' p99: %s', r.p99); }); _.each(report.counters, function(value, name) { console.log(' %s: %s', name, value); }); } console.log(); }; function formatTimestamp(timestamp) { return moment(new Date(timestamp)).format('HH:mm:ss(ZZ) YYYY-MM-DD'); }