Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

benchmark: add simple https benchmark #36612

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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