Phase 4 verification harness in loadtest/: - bench-pool.js: standalone dep-free model showing pool of 4 = 4x and pool of 8 = 8x throughput vs a single serialized connection. - run.js: autocannon harness for the read path (req/s + p50/p90/p99). - docker-compose.loadtest.yml + init.sql: seeded Postgres + app read-path stack (no Prosody) for local load testing. - README.md: usage and staging validation checklist. Part-of: <http://gitlab.vnc.biz/uxf/prosody-muc-rest/-/merge_requests/3>
97 lines
2.9 KiB
JavaScript
97 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* bench-pool.js — standalone throughput model for the telnet pool.
|
|
*
|
|
* No external dependencies, no Prosody/Postgres. Simulates a Prosody console
|
|
* command that takes `cmdLatency` ms and compares:
|
|
* - a single shared connection (serialized process-wide) [pre-fix model]
|
|
* - a pool of N connections with a wait-queue [post-fix model]
|
|
*
|
|
* Usage:
|
|
* node loadtest/bench-pool.js [totalCommands] [cmdLatencyMs] [poolSize]
|
|
*
|
|
* Defaults: 200 commands, 50 ms latency, pool size 4.
|
|
*/
|
|
'use strict';
|
|
|
|
var totalCommands = parseInt(process.argv[2], 10) || 200;
|
|
var cmdLatency = parseInt(process.argv[3], 10) || 50;
|
|
var poolSize = parseInt(process.argv[4], 10) || 4;
|
|
|
|
function sleep(ms) {
|
|
return new Promise(function (resolve) { setTimeout(resolve, ms); });
|
|
}
|
|
|
|
// Single shared connection: every command serializes on one socket.
|
|
function runSingle(commands, latency) {
|
|
var start = Date.now();
|
|
var i = 0;
|
|
function next() {
|
|
if (i >= commands) return Promise.resolve();
|
|
i++;
|
|
return sleep(latency).then(next);
|
|
}
|
|
return next().then(function () { return Date.now() - start; });
|
|
}
|
|
|
|
// Pool of `size` connections with a simple wait-queue (mirrors app/telnet-pool.js).
|
|
function runPool(commands, latency, size) {
|
|
var start = Date.now();
|
|
var queue = [];
|
|
var inflight = 0;
|
|
var done = 0;
|
|
|
|
function dispatch() {
|
|
while (inflight < size && (done + inflight) < commands) {
|
|
inflight++;
|
|
sleep(latency).then(function () {
|
|
inflight--;
|
|
done++;
|
|
if (done >= commands) {
|
|
return;
|
|
}
|
|
dispatch();
|
|
while (inflight < size && queue.length > 0) {
|
|
var w = queue.shift();
|
|
inflight++;
|
|
w();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return new Promise(function (resolve) {
|
|
dispatch();
|
|
var timer = setInterval(function () {
|
|
if (done >= commands) {
|
|
clearInterval(timer);
|
|
resolve(Date.now() - start);
|
|
}
|
|
}, 5);
|
|
});
|
|
}
|
|
|
|
function fmt(ms, commands) {
|
|
var sec = ms / 1000;
|
|
var rps = commands / sec;
|
|
return ms + ' ms (' + rps.toFixed(1) + ' req/s)';
|
|
}
|
|
|
|
(async function main() {
|
|
console.log('bench-pool: ' + totalCommands + ' commands, ' + cmdLatency + ' ms latency each, pool size ' + poolSize);
|
|
console.log('-----------------------------------------------------------');
|
|
|
|
var singleMs = await runSingle(totalCommands, cmdLatency);
|
|
console.log('single connection (serialized): ' + fmt(singleMs, totalCommands));
|
|
|
|
var poolMs = await runPool(totalCommands, cmdLatency, poolSize);
|
|
console.log('pool of ' + poolSize + ' (concurrent): ' + fmt(poolMs, totalCommands));
|
|
|
|
var speedup = singleMs / poolMs;
|
|
console.log('-----------------------------------------------------------');
|
|
console.log('speedup: ' + speedup.toFixed(2) + 'x (theoretical max ' + poolSize + 'x)');
|
|
})().catch(function (e) {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|