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 17, 2017
1 parent 45bb40f commit 27fbbf1
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 0 deletions.
95 changes: 95 additions & 0 deletions benchmark/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict';

const jstp = require('..');

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

let connections;

process.on('message', ([type, ...payload]) => {
if (type === 'connect') {
connections = new Array(payload[1]);
connect(payload[0]);
} 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.true(() => {

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];
}
119 changes: 119 additions & 0 deletions benchmark/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
'use strict';

const { fork } = require('child_process');
const path = require('path');
const fs = require('fs');
const statistics = require('./statistics');

const [
clientsAmount,
connectionsPerClient,
requestsPerConnection
] = process.argv.splice(2).map(arg => +arg);

const server = fork(path.join(__dirname, './server'), [], { stdin: 'pipe' });
let serverExited = false;

const clients = new Array(clientsAmount);
const clientsExited = new Array(clientsAmount);

const results = new Array(clientsAmount);
let clientsConnected = 0;
let clientsFinished = 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 onClientExitFactory = index => (exitCode) => {
clientsExited[index] = true;
if (exitCode !== 0) {
terminate();
}
};

for (let i = 0; i < clientsAmount; i++) {
clients[i] = fork(path.join(__dirname, 'client.js'), [], { stdin: 'pipe' });

clients[i].on('exit', onClientExitFactory(i));
clients[i].on('message', clientListener);
clients[i].send(['connect', payload, connectionsPerClient]);
}
});

function clientListener([type, payload]) {
if (type === 'connected') {
clientsConnected++;

if (clientsConnected === clientsAmount) {
becnhStartedHR = process.hrtime();
for (let i = 0; i < clientsAmount; i++) {
clients[i].send(['start', requestsPerConnection]);
}
}
} else if (type === 'finished') {
results[clientsFinished] = payload;
clientsFinished++;

if (clientsFinished === clientsAmount) {
outputResults(process.hrtime(becnhStartedHR));
}
}
}

function outputResults(benchTimeHR) {
const count = clientsAmount * connectionsPerClient * 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 / clientsAmount);

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(`
Benchmark is being terminated due to an error or signal termination
`);
clients.filter((_, index) => !clientsExited[index])
.forEach((client) => {
client.kill('SIGKILL');
});

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

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

const jstp = require('..');

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

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

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 };
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"test-todo": "tap test/todo",
"test-coverage": "nyc npm run test-node",
"lint": "eslint . && remark .",
"benchmark": "node benchmark/run.js 10 10 10000",
"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 27fbbf1

Please sign in to comment.