Files
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

65 lines
2.1 KiB
JavaScript

#!/usr/bin/env node
/*
* run.js — autocannon harness against a live prosody-muc-rest instance.
*
* Reports req/s + p50/p90/p99 latency for the read path (GET /groupchats and
* GET /healthcheck). Mutating endpoints are intentionally excluded to avoid
* hammering Prosody's telnet console under load.
*
* Requires autocannon (not a runtime dependency of the app):
* yarn add --dev autocannon
* # or: npx autocannon
*
* Usage:
* node loadtest/run.js [url] [durationSec] [connections]
*
* Defaults: http://127.0.0.1:9511, 10 s, 50 connections.
* Targets the /api/ prefix mounted by app/app.js.
*/
'use strict';
var url = process.argv[2] || 'http://127.0.0.1:9511';
var duration = parseInt(process.argv[3], 10) || 10;
var connections = parseInt(process.argv[4], 10) || 50;
var autocannon;
try {
autocannon = require('autocannon');
} catch (e) {
console.error('autocannon is not installed. Install it with:');
console.error(' yarn add --dev autocannon (or: npx autocannon)');
process.exit(1);
}
var base = url.replace(/\/+$/, '');
var routes = ['/api/groupchats', '/api/healthcheck'];
var instance = autocannon({
url: base + routes[0],
connections: connections,
duration: duration,
headers: { 'accept': 'application/json' },
requests: [
{ method: 'GET', path: routes[0] },
{ method: 'GET', path: routes[1] }
]
}, function (err, result) {
if (err) {
console.error('autocannon error:', err);
process.exit(1);
}
console.log('\n=== ' + base + ' (conn=' + connections + ', dur=' + duration + 's) ===');
console.log('requests: ' + result.requests.total + ' (' + result.requests.sent + ' sent)');
console.log('req/s: ' + (result.requests.average).toFixed(1));
console.log('latency p50: ' + result.latency.p50 + ' ms');
console.log('latency p90: ' + result.latency.p90 + ' ms');
console.log('latency p99: ' + result.latency.p99 + ' ms');
console.log('errors: ' + result.errors);
console.log('timeouts: ' + result.timeouts);
if (result.non2xx > 0) {
console.log('non-2xx: ' + result.non2xx);
}
});
autocannon.track(instance, { renderProgressBar: true });