Skip to content

Commit

Permalink
benchmark: add simple https benchmark
Browse files Browse the repository at this point in the history
Adds a simple benchmark for https server based on the http simple
benchmark. Updates benchmarker integration for autocannon and wrk,
so that they support https scheme.

Also adds a new HTTPS section and improves HTTP/2 section in
the benchmark guide.

PR-URL: #36612
Reviewed-By: Rich Trott <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
  • Loading branch information
puzpuzpuz authored and targos committed May 1, 2021
1 parent d85ea61 commit 3a11ee8
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 9 deletions.
14 changes: 9 additions & 5 deletions benchmark/_http-benchmarkers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class AutocannonBenchmarker {
for (const field in options.headers) {
args.push('-H', `${field}=${options.headers[field]}`);
}
args.push(`http://127.0.0.1:${options.port}${options.path}`);
const scheme = options.scheme || 'http';
args.push(`${scheme}://127.0.0.1:${options.port}${options.path}`);
const child = child_process.spawn(this.executable, args);
return child;
}
Expand Down Expand Up @@ -60,11 +61,12 @@ class WrkBenchmarker {
const duration = typeof options.duration === 'number' ?
Math.max(options.duration, 1) :
options.duration;
const scheme = options.scheme || 'http';
const args = [
'-d', duration,
'-c', options.connections,
'-t', Math.min(options.connections, require('os').cpus().length || 8),
`http://127.0.0.1:${options.port}${options.path}`,
`${scheme}://127.0.0.1:${options.port}${options.path}`,
];
for (const field in options.headers) {
args.push('-H', `${field}: ${options.headers[field]}`);
Expand All @@ -90,8 +92,8 @@ class WrkBenchmarker {
*/
class TestDoubleBenchmarker {
constructor(type) {
// `type` is the type of benchmarker. Possible values are 'http' and
// 'http2'.
// `type` is the type of benchmarker. Possible values are 'http', 'https',
// and 'http2'.
this.name = `test-double-${type}`;
this.executable = path.resolve(__dirname, '_test-double-benchmarker.js');
this.present = fs.existsSync(this.executable);
Expand All @@ -101,8 +103,9 @@ class TestDoubleBenchmarker {
create(options) {
process.env.duration = process.env.duration || options.duration || 5;

const scheme = options.scheme || 'http';
const env = {
test_url: `http://127.0.0.1:${options.port}${options.path}`,
test_url: `${scheme}://127.0.0.1:${options.port}${options.path}`,
...process.env
};

Expand Down Expand Up @@ -179,6 +182,7 @@ const http_benchmarkers = [
new WrkBenchmarker(),
new AutocannonBenchmarker(),
new TestDoubleBenchmarker('http'),
new TestDoubleBenchmarker('https'),
new TestDoubleBenchmarker('http2'),
new H2LoadBenchmarker(),
];
Expand Down
15 changes: 12 additions & 3 deletions benchmark/_test-double-benchmarker.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
'use strict';

const myModule = process.argv[2];
if (!['http', 'http2'].includes(myModule)) {
if (!['http', 'https', 'http2'].includes(myModule)) {
throw new Error(`Invalid module for benchmark test double: ${myModule}`);
}

let options;
if (myModule === 'https') {
options = { rejectUnauthorized: false };
}

const http = require(myModule);

const duration = +process.env.duration;
Expand Down Expand Up @@ -33,8 +38,12 @@ function request(res, client) {
}

function run() {
if (http.get) { // HTTP
http.get(url, request);
if (http.get) { // HTTP or HTTPS
if (options) {
http.get(url, options, request);
} else {
http.get(url, request);
}
} else { // HTTP/2
const client = http.connect(url);
client.on('error', (e) => { throw e; });
Expand Down
72 changes: 72 additions & 0 deletions benchmark/fixtures/simple-https-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

const fixtures = require('../../test/common/fixtures');
const https = require('https');

const options = {
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt')
};

const storedBytes = Object.create(null);
const storedBuffer = Object.create(null);

module.exports = https.createServer(options, (req, res) => {
// URL format: /<type>/<length>/<chunks>/chunkedEnc
const params = req.url.split('/');
const command = params[1];
let body = '';
const arg = params[2];
const n_chunks = parseInt(params[3], 10);
const chunkedEnc = params.length >= 5 && params[4] === '0' ? false : true;
let status = 200;

let n, i;
if (command === 'bytes') {
n = ~~arg;
if (n <= 0)
throw new Error('bytes called with n <= 0');
if (storedBytes[n] === undefined) {
storedBytes[n] = 'C'.repeat(n);
}
body = storedBytes[n];
} else if (command === 'buffer') {
n = ~~arg;
if (n <= 0)
throw new Error('buffer called with n <= 0');
if (storedBuffer[n] === undefined) {
storedBuffer[n] = Buffer.allocUnsafe(n);
for (i = 0; i < n; i++) {
storedBuffer[n][i] = 'C'.charCodeAt(0);
}
}
body = storedBuffer[n];
} else {
status = 404;
body = 'not found\n';
}

// example: https://localhost:port/bytes/512/4
// sends a 512 byte body in 4 chunks of 128 bytes
const len = body.length;
if (chunkedEnc) {
res.writeHead(status, {
'Content-Type': 'text/plain',
'Transfer-Encoding': 'chunked'
});
} else {
res.writeHead(status, {
'Content-Type': 'text/plain',
'Content-Length': len.toString()
});
}
// send body in chunks
if (n_chunks > 1) {
const step = Math.floor(len / n_chunks) || 1;
for (i = 0, n = (n_chunks - 1); i < n; ++i)
res.write(body.slice(i * step, i * step + step));
res.end(body.slice((n_chunks - 1) * step));
} else {
res.end(body);
}
});
29 changes: 29 additions & 0 deletions benchmark/https/simple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');

const bench = common.createBenchmark(main, {
type: ['bytes', 'buffer'],
len: [4, 1024, 102400],
chunks: [1, 4],
c: [50, 500],
chunkedEnc: [1, 0],
benchmarker: ['test-double-https'],
duration: 5
});

function main({ type, len, chunks, c, chunkedEnc, duration }) {
const server = require('../fixtures/simple-https-server.js')
.listen(common.PORT)
.on('listening', () => {
const path = `/${type}/${len}/${chunks}/${chunkedEnc}`;

bench.http({
path,
connections: c,
scheme: 'https',
duration
}, () => {
server.close();
});
});
}
11 changes: 10 additions & 1 deletion doc/guides/writing-and-running-benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* [Prerequisites](#prerequisites)
* [HTTP Benchmark Requirements](#http-benchmark-requirements)
* [HTTPS Benchmark Requirements](#https-benchmark-requirements)
* [HTTP/2 Benchmark Requirements](#http2-benchmark-requirements)
* [Benchmark Analysis Requirements](#benchmark-analysis-requirements)
* [Running benchmarks](#running-benchmarks)
* [Running individual benchmarks](#running-individual-benchmarks)
Expand Down Expand Up @@ -43,13 +45,20 @@ benchmarker to be used should be specified by providing it as an argument:

`node benchmark/http/simple.js benchmarker=autocannon`

#### HTTPS Benchmark Requirements

To run the `https` benchmarks, one of `autocannon` or `wrk` benchmarkers must
be used.

`node benchmark/https/simple.js benchmarker=autocannon`

#### HTTP/2 Benchmark Requirements

To run the `http2` benchmarks, the `h2load` benchmarker must be used. The
`h2load` tool is a component of the `nghttp2` project and may be installed
from [nghttp2.org][] or built from source.

`node benchmark/http2/simple.js benchmarker=autocannon`
`node benchmark/http2/simple.js benchmarker=h2load`

### Benchmark Analysis Requirements

Expand Down

0 comments on commit 3a11ee8

Please sign in to comment.