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>
65 lines
2.1 KiB
JavaScript
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 });
|