Files
vnctalk-muc-rest/loadtest/bench-pool.js
T
Stefan-Sanger 683e224d67 test: add loadtest harness for telnet pool verification
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>
2026-07-10 14:54:25 +02:00

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);
});