Skip to content

Commit

Permalink
Merge branch 'master' of github.com:mcollina/autocannon
Browse files Browse the repository at this point in the history
  • Loading branch information
mcollina committed Oct 14, 2024
2 parents 87ac52e + 3167fab commit 2854067
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [14, 16, 18, 20]
node-version: [20, 22]

steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Available options:
Start the command listed after -- on the command line. When it starts listening on a port,
start sending requests to that port. A URL is still required to send requests to
the correct path. The hostname can be omitted, `localhost` will be used by default.
If the command after -- is `node <script>`, this flag is optional and assumed to be `true`.
-m/--method METHOD
The HTTP method to use. default: 'GET'.
-t/--timeout NUM
Expand Down Expand Up @@ -113,7 +114,7 @@ Available options:
-l/--latency
Print all the latency data. default: false.
-I/--idReplacement
Enable replacement of [<id>] with a randomly generated ID within the request body. default: false.
Enable replacement of `[<id>]` with a randomly generated ID within the request body. e.g. `/items/[<id>]`. default: false.
-j/--json
Print the output as newline delimited JSON. This will cause the progress bar and results not to be rendered. default: false.
-f/--forever
Expand Down
7 changes: 6 additions & 1 deletion autocannon.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const URL = require('url').URL
const spawn = require('child_process').spawn
const managePath = require('manage-path')
const hasAsyncHooks = require('has-async-hooks')
const subarg = require('subarg')
const subarg = require('@minimistjs/subarg')
const printResult = require('./lib/printResult')
const initJob = require('./lib/init')
const track = require('./lib/progressTracker')
Expand Down Expand Up @@ -115,6 +115,11 @@ function parseArguments (argvs) {

argv.url = argv._.length > 1 ? argv._ : argv._[0]

// Assume onPort if `-- node` is provided
if (argv['--'][0] === 'node') {
argv.onPort = true
}

if (argv.onPort) {
argv.spawn = argv['--']
}
Expand Down
17 changes: 12 additions & 5 deletions lib/printResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,27 @@ const defaults = {
verbose: true
}

class TableWithoutColor extends Table {
constructor (opts = {}) {
super({ ...opts, style: { head: [], border: [] } })
}
}

const printResult = (result, opts) => {
opts = Object.assign({}, defaults, opts)
let strResult = ''

if (opts.verbose) {
const chalk = new Chalk.Instance(testColorSupport({ stream: opts.outputStream, alwaysReturn: true }))
const ColorSafeTable = chalk.level === 0 ? TableWithoutColor : Table

const shortLatency = new Table({
const shortLatency = new ColorSafeTable({
head: asColor(chalk.cyan, ['Stat', '2.5%', '50%', '97.5%', '99%', 'Avg', 'Stdev', 'Max'])
})
shortLatency.push(asLowRow(chalk.bold('Latency'), asMs(result.latency)))
logToLocalStr('\n' + shortLatency.toString())

const requests = new Table({
const requests = new ColorSafeTable({
head: asColor(chalk.cyan, ['Stat', '1%', '2.5%', '50%', '97.5%', 'Avg', 'Stdev', 'Min'])
})

Expand All @@ -37,7 +44,7 @@ const printResult = (result, opts) => {
logToLocalStr(requests.toString())

if (opts.renderStatusCodes === true) {
const statusCodeStats = new Table({
const statusCodeStats = new ColorSafeTable({
head: asColor(chalk.cyan, ['Code', 'Count'])
})
Object.keys(result.statusCodeStats).forEach(statusCode => {
Expand All @@ -57,7 +64,7 @@ const printResult = (result, opts) => {
logToLocalStr('')

if (opts.renderLatencyTable) {
const latencies = new Table({
const latencies = new ColorSafeTable({
head: asColor(chalk.cyan, ['Percentile', 'Latency (ms)'])
})
percentiles.map((perc) => {
Expand Down Expand Up @@ -85,7 +92,7 @@ const printResult = (result, opts) => {
logToLocalStr(`${format(result.mismatches)} requests with mismatched body`)
}
if (result.resets) {
logToLocalStr(`request pipeline was resetted ${format(result.resets)} ${result.resets === 1 ? 'time' : 'times'}`)
logToLocalStr(`request pipeline was reset ${format(result.resets)} ${result.resets === 1 ? 'time' : 'times'}`)
}

function logToLocalStr (msg) {
Expand Down
9 changes: 7 additions & 2 deletions lib/requestIterator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const hyperid = require('hyperid')(true)
const hyperid = require('hyperid')
const inherits = require('util').inherits
const requestBuilder = require('./httpRequestBuilder')
const clone = require('lodash.clonedeep')
Expand All @@ -26,6 +26,7 @@ function RequestIterator (opts) {
return new RequestIterator(opts)
}

this.hyperid = hyperid({ urlSafe: true })
this.resetted = false
this.headers = {}
this.initialContext = opts.initialContext || {}
Expand Down Expand Up @@ -107,8 +108,12 @@ RequestIterator.prototype.rebuildRequest = function () {
this.currentRequest.headers = this.currentRequest.headers || this.headers
data = this.requestBuilder(this.currentRequest, this.context)
if (data) {
const hyperid = this.hyperid()
this.currentRequest.requestBuffer = this.reqDefaults.idReplacement
? Buffer.from(data.toString().replace(/\[<id>\]/g, hyperid()))
? Buffer.from(data.toString()
.replace(/\[<id>\]/g, hyperid)
// in the first line only (the url), replace encoded id placeholders
.replace(/^.+/, m => m.replace(/\[%3Cid%3E]/g, hyperid)))
: data
} else if (this.currentRequestIndex === 0) {
// when first request fails to build, we can not reset pipeline, or it'll never end
Expand Down
4 changes: 4 additions & 0 deletions lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ module.exports = function validateOpts (opts, cbPassedIn) {
// fill in defaults after
opts = defaultOpts(opts)

if (opts.json === true) {
opts.renderProgressBar = opts.renderResultsTable = opts.renderLatencyTable = false
}

if (opts.requests) {
if (opts.requests.some(r => !isValidFn(r.setupRequest))) {
return new Error('Invalid option setupRequest, please provide a function (or file path when in workers mode)')
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,27 @@
"contributors": [
"Glen Keane <glenkeane.94@gmail.com>",
"Donald Robertson <donaldarobertson89@gmail.com",
"Salman Mitha <SalmanMitha@gmail.com"
"Salman Mitha <SalmanMitha@gmail.com>"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/mcollina/autocannon/issues"
},
"homepage": "https://github.com/mcollina/autocannon#readme",
"devDependencies": {
"ansi-regex": "^5.0.1",
"bl": "^6.0.0",
"busboy": "^0.3.1",
"pre-commit": "^1.1.2",
"proxyquire": "^2.1.3",
"sinon": "^15.0.0",
"split2": "^4.0.0",
"standard": "^17.0.0",
"tap": "^16.0.0"
"tap": "^16.0.0",
"why-is-node-running": "^2.3.0"
},
"dependencies": {
"@minimistjs/subarg": "^1.0.0",
"chalk": "^4.1.0",
"char-spinner": "^1.0.1",
"cli-table3": "^0.6.0",
Expand All @@ -69,7 +72,6 @@
"reinterval": "^1.1.0",
"retimer": "^3.0.0",
"semver": "^7.3.2",
"subarg": "^1.0.0",
"timestring": "^6.0.0"
}
}
32 changes: 24 additions & 8 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ test('should run benchmark against server', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

child
Expand Down Expand Up @@ -102,7 +104,9 @@ test('should parse HAR file and run requests', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

child
Expand All @@ -126,7 +130,9 @@ test('should throw on unknown HAR file', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

const lines = []
Expand Down Expand Up @@ -155,7 +161,9 @@ test('should throw on invalid HAR file', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

const lines = []
Expand Down Expand Up @@ -187,7 +195,9 @@ test('should write warning about unused HAR requests', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

const lines = []
Expand Down Expand Up @@ -241,7 +251,9 @@ test('run with workers', { skip: !hasWorkerSupport }, (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

child
Expand Down Expand Up @@ -278,7 +290,9 @@ test('should run handle PUT bodies', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

const outputLines = []
Expand Down Expand Up @@ -323,7 +337,9 @@ test('should run handle PUT bodies', (t) => {
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

const outputLines = []
Expand Down
9 changes: 8 additions & 1 deletion test/envPort.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
'use strict'

const why = require('why-is-node-running')
const t = require('tap')
const split = require('split2')
const path = require('path')
const childProcess = require('child_process')
const helper = require('./helper')

setInterval(function () {
console.log(why())
}, 30000).unref()

const lines = [
/Running 1s test @ .*$/,
/10 connections.*$/,
Expand Down Expand Up @@ -46,7 +51,9 @@ const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'),
})

t.teardown(() => {
child.kill()
try {
child.kill()
} catch {}
})

child
Expand Down
54 changes: 54 additions & 0 deletions test/onPort.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,57 @@ test('--on-port flag', { skip: !hasAsyncHooks() }, (t) => {
t.ok(regexp.test(line), 'line matches ' + regexp)
})
})

test('assume --on-port flag if -- node is set', { skip: !hasAsyncHooks() }, (t) => {
const lines = [
/Running 1s test @ .*$/,
/10 connections.*$/,
/$/,
/.*/,
/$/,
/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.*$/,
/.*/,
/$/,
/Req\/Bytes counts sampled once per second.*$/,
/# of samples: 10*$/,
/$/,
/.* requests in ([0-9]|\.)+s, .* read/
]

t.plan(lines.length * 2)

const child = spawn(process.execPath, [
path.join(__dirname, '..'),
'-c', '10',
'-d', '1',
'/',
'--', 'node', path.join(__dirname, './targetProcess')
], {
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 + `actual: ${line} expected: ${regexp}`)
})
})
Loading

0 comments on commit 2854067

Please sign in to comment.