Skip to content

Commit

Permalink
bench: add simple benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
nechaido committed Jul 24, 2017
1 parent 2054024 commit edb28df
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 13 deletions.
139 changes: 139 additions & 0 deletions benchmark/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use strict';

const cp = require('child_process');
const path = require('path');
const fs = require('fs');
const statistics = require('./statistics');

const yargsParser = require('yargs-parser');

const args = yargsParser(
process.argv.slice(2),
{
alias: {
workersAmount: ['w', 'W', 'workers'],
connectionsPerWorker: ['c', 'C', 'connections'],
requestsPerConnection: ['r', 'R', 'requests'],
argumentSize: ['s', 'S', 'arg-size'],
},
}
);

const {
workersAmount,
connectionsPerWorker,
requestsPerConnection,
argumentSize,
} = args;

const server = cp.fork(
path.join(__dirname, 'server'),
[workersAmount * connectionsPerWorker],
{ stdin: 'pipe' }
);

let serverExited = false;

const workers = new Array(workersAmount);
const workersExited = new Array(workersAmount);

const results = new Array(workersAmount);
let workersConnected = 0;
let workersFinished = 0;

let becnhStartedHR;

server.on('exit', (exitCode) => {
serverExited = true;
if (exitCode !== 0) {
terminate();
}
});

server.on('error', terminate);

server.on('message', ([type, payload]) => {
if (type !== 'started') {
return;
}

const onWorkerExitFactory = index => (exitCode) => {
workersExited[index] = true;
if (exitCode !== 0) {
terminate();
}
};

for (let i = 0; i < workersAmount; i++) {
workers[i] = cp.fork(path.join(__dirname, 'worker'), [], { stdin: 'pipe' });

workers[i].on('exit', onWorkerExitFactory(i));
workers[i].on('message', workerListener);
workers[i].send(['connect', payload, connectionsPerWorker, argumentSize]);
}
});

function workerListener([type, payload]) {
if (type === 'connected') {
workersConnected++;

if (workersConnected === workersAmount) {
becnhStartedHR = process.hrtime();
for (let i = 0; i < workersAmount; i++) {
workers[i].send(['start', requestsPerConnection]);
}
}
} else if (type === 'finished') {
results[workersFinished] = payload;
workersFinished++;

if (workersFinished === workersAmount) {
outputResults(process.hrtime(becnhStartedHR));
}
}
}

function outputResults(benchTimeHR) {
const count = workersAmount * connectionsPerWorker * requestsPerConnection;
const mean = statistics.mean(results.map(result => result[0]));

const sum = results.reduce((previous, current) => (
previous + Math.pow(current[1], 2) + Math.pow(current[0] - mean, 2)
), 0);
const stdev = Math.sqrt(sum / workersAmount);

const benchTime = benchTimeHR[0] * 1e9 + benchTimeHR[1];
const erps = count * 1e9 / benchTime;

server.send(['stop']);
console.log(`
Requests sent: ${count}
Mean time of one request: ${mean * 1e-6} (ms)
Stdev of time of one request: ${stdev * 1e-6} (ms)
Estimated RPS: ${erps}
`);
process.exit(0);
}

function terminate() {
console.log(
'\nBenchmark is being terminated due to an error or signal termination\n'
);
workers.filter((_, index) => !workersExited[index])
.forEach((worker) => {
worker.kill('SIGKILL');
});

if (!serverExited) {
server.kill('SIGINT');
setTimeout(() => {
if (!serverExited) {
server.kill('SIGKILL');
console.log('Master process was not able to close server gracefully');
fs.unlinkSync('/tmp/jstp_benchmark_ipc');
}
}, 5000);
}

process.exit(0);
}
40 changes: 40 additions & 0 deletions benchmark/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const jstp = require('..');

const maxConnections = +process.argv[2];

const app = new jstp.Application('app', {
iface: {
method(connection, argument, callback) {
callback(null);
},
},
});

const server = jstp.net.createServer([app]);
server.maxConnections = maxConnections;

const socket = '/tmp/jstp_benchmark_ipc';

const terminate = () => {
server.close();
process.exit(0);
};

process.on('message', ([type]) => {
if (type === 'stop') {
terminate();
}
});

process.on('SIGINT', terminate);

server.listen(socket, (error) => {
if (error) {
console.error(error);
}

console.log(`Server listening on ${socket} 🚀`);
process.send(['started', socket]);
});
28 changes: 28 additions & 0 deletions benchmark/statistics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const mean = (sample) => {
const len = sample.length;
if (len === 0)
return;
let sum = 0;
for (let i = 0; i < len; i++) {
sum += sample[i];
}
return sum / len;
};

const stdev = (sample, meanValue) => {
const len = sample.length;
if (len === 0)
return;
if (len === 1)
return 0;
let sum = 0;
for (let i = 0; i < len; i++) {
sum += Math.pow(sample[i] - meanValue, 2);
}
const variance = sum / len;
return Math.sqrt(variance);
};

module.exports = { mean, stdev };
98 changes: 98 additions & 0 deletions benchmark/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict';

const jstp = require('..');

const statistics = require('./statistics');

let connections;

let argument;

process.on('message', ([type, ...payload]) => {
if (type === 'connect') {
connections = new Array(payload[1]);
connect(payload[0]);
argument = '0'.repeat(payload[2]);
} else if (type === 'start') {
start(payload[0]);
}
});

function connect(socket) {
let connected = 0;
const createConnection = (index) => {
jstp.net.connectAndInspect('app', null, ['iface'], socket,
(error, conn) => {
connected++;

if (error) {
console.error(`Could not connect to the server: ${error}`);
return;
}

connections[index] = conn;

if (connected === connections.length) {
process.send(['connected']);
}
}
);
};

for (let i = 0; i < connections.length; i++) {
createConnection(i);
}

}

function start(requests) {
const responseTimesHR = new Array(connections.length);
for (let i = 0; i < connections.length; i++) {
responseTimesHR[i] = new Array(requests);
}
let responses = 0;
let startTimeHR = new Array(2);

const sendRequest = (connectionIndex, requestIndex) => {
const timeOfStart = process.hrtime();
connections[connectionIndex].remoteProxies.iface.method(argument, () => {

responseTimesHR[connectionIndex][requestIndex] =
process.hrtime(timeOfStart);

responses++;
if (responses === requests * connections.length) {
process.send([
'finished',
prepareResults(responseTimesHR, process.hrtime(startTimeHR)),
]);
connections.forEach(connection => connection.close());
process.exit(0);
}
});
};

startTimeHR = process.hrtime();

for (let i = 0; i < connections.length; i++) {
for (let j = 0; j < requests; j++) {
sendRequest(i, j);
}
}
}

function prepareResults(responseTimesHR, timeSpentHR) {
const hrtimeToNSeconds = hrtime => hrtime[0] * 1e9 + hrtime[1];

responseTimesHR = responseTimesHR.reduce(
(previous, current) => previous.concat(current), []
);

const responseTimes = responseTimesHR.map(hrtimeToNSeconds);
const timeSpent = hrtimeToNSeconds(timeSpentHR);

const mean = statistics.mean(responseTimes);
const stdev = statistics.stdev(responseTimes, mean);

return [mean, stdev, timeSpent];
}
25 changes: 13 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"remark-lint": "^6.0.0",
"remark-validate-links": "^6.1.0",
"tap": "^10.7.0",
"webpack": "^3.1.0"
"webpack": "^3.1.0",
"yargs-parser": "^7.0.0"
},
"scripts": {
"test": "npm run lint && npm run test-node && npm run test-integration",
Expand All @@ -57,6 +58,7 @@
"test-todo": "tap test/todo",
"test-coverage": "nyc npm run test-node",
"lint": "eslint . && remark .",
"benchmark": "node benchmark/run.js --workers 10 --connections 10 --requests 10000 --arg-size 100",
"install": "npm run rebuild-node",
"build": "npm run build-node && npm run build-browser",
"build-node": "node tools/build-native",
Expand Down

0 comments on commit edb28df

Please sign in to comment.