Skip to content

Commit

Permalink
Adding individual status code statistics table (#347)
Browse files Browse the repository at this point in the history
* Adding individual status code statistics table

* Adding unit test for status code statistics table

* Adding unit test for status code statistics table

* Adding unit test for status code statistics table
  • Loading branch information
maxfortun committed Mar 10, 2021
1 parent 4d15643 commit 0ea08b7
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 4 deletions.
5 changes: 4 additions & 1 deletion autocannon.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports.parseArguments = parseArguments

function parseArguments (argvs) {
const argv = minimist(argvs, {
boolean: ['json', 'n', 'help', 'renderLatencyTable', 'renderProgressBar', 'forever', 'idReplacement', 'excludeErrorStats', 'onPort', 'debug', 'ignoreCoordinatedOmission'],
boolean: ['json', 'n', 'help', 'renderLatencyTable', 'renderProgressBar', 'renderStatusCodes', 'forever', 'idReplacement', 'excludeErrorStats', 'onPort', 'debug', 'ignoreCoordinatedOmission'],
alias: {
connections: 'c',
pipelining: 'p',
Expand All @@ -57,6 +57,7 @@ function parseArguments (argvs) {
ignoreCoordinatedOmission: 'C',
reconnectRate: 'D',
renderProgressBar: 'progress',
renderStatusCodes: 'statusCodes',
title: 'T',
version: 'v',
forever: 'f',
Expand All @@ -75,6 +76,7 @@ function parseArguments (argvs) {
reconnectRate: 0,
renderLatencyTable: false,
renderProgressBar: true,
renderStatusCodes: false,
json: false,
forever: false,
method: 'GET',
Expand All @@ -96,6 +98,7 @@ function parseArguments (argvs) {
if (argv.n) {
argv.renderProgressBar = false
argv.renderResultsTable = false
argv.renderStatusCodes = false
}

if (argv.version) {
Expand Down
2 changes: 2 additions & 0 deletions help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ Available options:
-E/--expectBody EXPECTED
Ensure the body matches this value. If enabled, mismatches count towards bailout.
Enabling this option will slow down the load testing.
--renderStatusCodes
Print status codes and their respective statistics.
--debug
Print connection errors to stderr.
-v/--version
Expand Down
9 changes: 9 additions & 0 deletions lib/aggregateResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ function aggregateResult (results, opts, histograms) {
acc['4xx'] += r['4xx']
acc['5xx'] += r['5xx']

Object.keys(r.statusCodeStats).forEach(statusCode => {
if (!acc.statusCodeStats[statusCode]) {
acc.statusCodeStats[statusCode] = r.statusCodeStats[statusCode]
} else {
acc.statusCodeStats[statusCode].count += r.statusCodeStats[statusCode].count
}
})

return acc
})

Expand All @@ -52,6 +60,7 @@ function aggregateResult (results, opts, histograms) {
'3xx': aggregated['3xx'],
'4xx': aggregated['4xx'],
'5xx': aggregated['5xx'],
statusCodeStats: aggregated.statusCodeStats,

latency: addPercentiles(aggregated.latencies, histAsObj(aggregated.latencies)),
requests: addPercentiles(histograms.requests, histAsObj(histograms.requests, aggregated.totalCompletedRequests)),
Expand Down
18 changes: 18 additions & 0 deletions lib/printResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ const printResult = (result, opts) => {
requests.push(asHighRow(chalk.bold('Req/Sec'), result.requests))
requests.push(asHighRow(chalk.bold('Bytes/Sec'), asBytes(result.throughput)))
logToLocalStr(requests.toString())

if (opts.renderStatusCodes === true) {
const statusCodeStats = new Table({
head: asColor(chalk.cyan, ['Code', 'Count'])
})
Object.keys(result.statusCodeStats).forEach(statusCode => {
const stats = result.statusCodeStats[statusCode]
const colorize = colorizeByStatusCode(chalk, statusCode)
statusCodeStats.push([colorize(statusCode), stats.count])
})
logToLocalStr(statusCodeStats.toString())
}

logToLocalStr('')
logToLocalStr('Req/Bytes counts sampled once per second.\n')

Expand Down Expand Up @@ -131,4 +144,9 @@ function asBytes (stat) {
return result
}

function colorizeByStatusCode (chalk, statusCode) {
const codeClass = Math.floor(parseInt(statusCode) / 100) - 1
return [chalk.cyan, chalk.cyan, chalk.cyan, chalk.redBright, chalk.redBright][codeClass]
}

module.exports = printResult
10 changes: 10 additions & 0 deletions lib/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ function run (opts, tracker, cb) {
0 // 5xx
]

const statusCodeStats = {}

if (opts.overallRate && (opts.overallRate < opts.connections)) opts.connections = opts.overallRate

let counter = 0
Expand Down Expand Up @@ -116,6 +118,7 @@ function run (opts, tracker, cb) {
timeouts: timeouts,
mismatches: mismatches,
non2xx: statusCodes[0] + statusCodes[2] + statusCodes[3] + statusCodes[4],
statusCodeStats,
resets: resets,
duration: Math.round((Date.now() - startTime) / 10) / 100,
start: new Date(startTime),
Expand Down Expand Up @@ -216,6 +219,13 @@ function run (opts, tracker, cb) {
tracker.emit('response', this, statusCode, resBytes, responseTime)
const codeIndex = Math.floor(parseInt(statusCode) / 100) - 1
statusCodes[codeIndex] += 1

if (!statusCodeStats[statusCode]) {
statusCodeStats[statusCode] = { count: 1 }
} else {
statusCodeStats[statusCode].count++
}

// only recordValue 2xx latencies
if (codeIndex === 1 || includeErrorStats) {
if (rate && !opts.ignoreCoordinatedOmission) {
Expand Down
10 changes: 8 additions & 2 deletions test/fixtures/example-result.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@
"2xx": 500,
"3xx": 0,
"4xx": 0,
"5xx": 0
"5xx": 0,
"statusCodeStats": {
"200": { "count": "500" },
"302": { "count": "0" },
"401": { "count": "0" },
"403": { "count": "0" }
}
}


10 changes: 9 additions & 1 deletion test/printResult-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

const autocannon = require('../autocannon')
const exampleResult = require('./fixtures/example-result.json')
const crossArgv = require('cross-argv')

const resultStr = autocannon.printResult(exampleResult)
let opts = null

if (process.argv.length > 2) {
const args = crossArgv(process.argv.slice(2))
opts = autocannon.parseArguments(args)
}

const resultStr = autocannon.printResult(exampleResult, opts)
process.stderr.write(resultStr)
61 changes: 61 additions & 0 deletions test/printResult-renderStatusCodes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict'

const test = require('tap').test
const split = require('split2')
const path = require('path')
const childProcess = require('child_process')

test('should stdout (print) the result', (t) => {
const lines = [
/.*/,
/Stat.*2\.5%.*50%.*97\.5%.*99%.*Avg.*Stdev.*Max.*$/,
/.*/,
/Latency.*$/,
/$/,
/.*/,
/Stat.*1%.*2\.5%.*50%.*97\.5%.*Avg.*Stdev.*Min.*$/,
/.*/,
/Req\/Sec.*$/,
/.*/,
/Bytes\/Sec.*$/,
/.*/,
/.*/,
/Code.*Count.*$/,
/.*/,
/200.*500.*$/,
/.*/,
/302.*0.*$/,
/.*/,
/401.*0.*$/,
/.*/,
/403.*0.*$/,
/.*/,
/$/,
/Req\/Bytes counts sampled once per second.*$/,
/$/,
/.* requests in ([0-9]|\.)+s, .* read/
]

t.plan(lines.length * 2)

const child = childProcess.spawn(process.execPath, [path.join(__dirname, 'printResult-process.js'), '--renderStatusCodes', 'http://127.0.0.1'], {
cwd: __dirname,
env: process.env,
stdio: ['ignore', 'pipe', 'pipe'],
detached: false
})

t.tearDown(() => {
child.kill()
})

child
.stderr
.pipe(split())
.on('data', (line) => {
const regexp = lines.shift()
t.ok(regexp, 'we are expecting this line')
t.ok(regexp.test(line), 'line matches ' + regexp)
})
.on('end', t.end)
})

0 comments on commit 0ea08b7

Please sign in to comment.