feat(logger): update logger configuration to set log level to Fatal to eliminate IO lock contention
fix(redis): silence Redis internal logging and optimize connection pool settings to reduce mutex contention feat(userlist): enhance user list component with avatar support and improved styling test(load): add production-style load test script for WebSocket connections and Redis PubSub stress testing chore(loadtest): create script to run load tests with pprof profiling for performance analysis
This commit is contained in:
173
loadtest/loadtest_prod.js
Normal file
173
loadtest/loadtest_prod.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import ws from 'k6/ws';
|
||||
import { check, sleep } from 'k6';
|
||||
import { Counter, Trend, Rate } from 'k6/metrics';
|
||||
|
||||
// =============================================================================
|
||||
// PRODUCTION-STYLE LOAD TEST (CONFIGURABLE)
|
||||
// =============================================================================
|
||||
// Usage examples:
|
||||
// k6 run loadtest/loadtest_prod.js
|
||||
// SCENARIOS=connect k6 run loadtest/loadtest_prod.js
|
||||
// SCENARIOS=connect,fanout ROOMS=10 FANOUT_VUS=1000 k6 run loadtest/loadtest_prod.js
|
||||
// BASE_URL=ws://localhost:8080/ws/loadtest k6 run loadtest/loadtest_prod.js
|
||||
//
|
||||
// Notes:
|
||||
// - Default uses /ws/loadtest to bypass auth + DB permission checks.
|
||||
// - RTT is not measured (server does not echo to sender).
|
||||
// - Use SCENARIOS to isolate connection-only vs fanout pressure.
|
||||
|
||||
const BASE_URL = __ENV.BASE_URL || 'ws://localhost:8080/ws/loadtest';
|
||||
const ROOMS = parseInt(__ENV.ROOMS || '10', 10);
|
||||
const SEND_INTERVAL_MS = parseInt(__ENV.SEND_INTERVAL_MS || '500', 10);
|
||||
const PAYLOAD_BYTES = parseInt(__ENV.PAYLOAD_BYTES || '200', 10);
|
||||
const CONNECT_HOLD_SEC = parseInt(__ENV.CONNECT_HOLD_SEC || '30', 10);
|
||||
const SCENARIOS = (__ENV.SCENARIOS || 'connect,fanout').split(',').map((s) => s.trim());
|
||||
|
||||
// =============================================================================
|
||||
// CUSTOM METRICS
|
||||
// =============================================================================
|
||||
const connectionTime = new Trend('ws_connection_time_ms');
|
||||
const connectionsFailed = new Counter('ws_connections_failed');
|
||||
const messagesReceived = new Counter('ws_msgs_received');
|
||||
const messagesSent = new Counter('ws_msgs_sent');
|
||||
const connectionSuccess = new Rate('ws_connection_success');
|
||||
|
||||
function roomForVU() {
|
||||
return `loadtest-room-${__VU % ROOMS}`;
|
||||
}
|
||||
|
||||
function buildUrl(roomId) {
|
||||
return `${BASE_URL}/${roomId}`;
|
||||
}
|
||||
|
||||
function connectAndHold(roomId, holdSec) {
|
||||
const url = buildUrl(roomId);
|
||||
const connectStart = Date.now();
|
||||
|
||||
const res = ws.connect(url, {}, function (socket) {
|
||||
connectionTime.add(Date.now() - connectStart);
|
||||
connectionSuccess.add(1);
|
||||
|
||||
socket.on('message', () => {
|
||||
messagesReceived.add(1);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
connectionsFailed.add(1);
|
||||
});
|
||||
|
||||
socket.setTimeout(() => {
|
||||
socket.close();
|
||||
}, holdSec * 1000);
|
||||
});
|
||||
|
||||
const connected = check(res, {
|
||||
'WebSocket connected': (r) => r && r.status === 101,
|
||||
});
|
||||
|
||||
if (!connected) {
|
||||
connectionsFailed.add(1);
|
||||
connectionSuccess.add(0);
|
||||
}
|
||||
}
|
||||
|
||||
function connectAndFanout(roomId) {
|
||||
const url = buildUrl(roomId);
|
||||
const connectStart = Date.now();
|
||||
const payload = new Uint8Array(PAYLOAD_BYTES);
|
||||
payload[0] = 1;
|
||||
for (let i = 1; i < PAYLOAD_BYTES; i++) {
|
||||
payload[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
|
||||
const res = ws.connect(url, {}, function (socket) {
|
||||
connectionTime.add(Date.now() - connectStart);
|
||||
connectionSuccess.add(1);
|
||||
|
||||
socket.on('message', () => {
|
||||
messagesReceived.add(1);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
connectionsFailed.add(1);
|
||||
});
|
||||
|
||||
socket.setInterval(() => {
|
||||
socket.sendBinary(payload.buffer);
|
||||
messagesSent.add(1);
|
||||
}, SEND_INTERVAL_MS);
|
||||
|
||||
socket.setTimeout(() => {
|
||||
socket.close();
|
||||
}, CONNECT_HOLD_SEC * 1000);
|
||||
});
|
||||
|
||||
const connected = check(res, {
|
||||
'WebSocket connected': (r) => r && r.status === 101,
|
||||
});
|
||||
|
||||
if (!connected) {
|
||||
connectionsFailed.add(1);
|
||||
connectionSuccess.add(0);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SCENARIOS (decided at init time from env)
|
||||
// =============================================================================
|
||||
const scenarios = {};
|
||||
|
||||
if (SCENARIOS.includes('connect')) {
|
||||
scenarios.connect_only = {
|
||||
executor: 'ramping-vus',
|
||||
startVUs: 0,
|
||||
stages: [
|
||||
{ duration: '10s', target: 200 },
|
||||
{ duration: '10s', target: 500 },
|
||||
{ duration: '10s', target: 1000 },
|
||||
{ duration: '60s', target: 1000 },
|
||||
{ duration: '10s', target: 0 },
|
||||
],
|
||||
exec: 'connectOnly',
|
||||
};
|
||||
}
|
||||
|
||||
if (SCENARIOS.includes('fanout')) {
|
||||
scenarios.fanout = {
|
||||
executor: 'constant-vus',
|
||||
vus: parseInt(__ENV.FANOUT_VUS || '1000', 10),
|
||||
duration: __ENV.FANOUT_DURATION || '90s',
|
||||
exec: 'fanout',
|
||||
};
|
||||
}
|
||||
|
||||
export const options = {
|
||||
scenarios,
|
||||
thresholds: {
|
||||
ws_connection_time_ms: ['p(95)<500'],
|
||||
ws_connection_success: ['rate>0.95'],
|
||||
},
|
||||
};
|
||||
|
||||
export function connectOnly() {
|
||||
connectAndHold(roomForVU(), CONNECT_HOLD_SEC);
|
||||
sleep(0.1);
|
||||
}
|
||||
|
||||
export function fanout() {
|
||||
connectAndFanout(roomForVU());
|
||||
sleep(0.1);
|
||||
}
|
||||
|
||||
export function setup() {
|
||||
console.log('========================================');
|
||||
console.log(' Production-Style Load Test');
|
||||
console.log('========================================');
|
||||
console.log(`BASE_URL: ${BASE_URL}`);
|
||||
console.log(`ROOMS: ${ROOMS}`);
|
||||
console.log(`SCENARIOS: ${SCENARIOS.join(',')}`);
|
||||
console.log(`SEND_INTERVAL_MS: ${SEND_INTERVAL_MS}`);
|
||||
console.log(`PAYLOAD_BYTES: ${PAYLOAD_BYTES}`);
|
||||
console.log(`CONNECT_HOLD_SEC: ${CONNECT_HOLD_SEC}`);
|
||||
console.log('========================================');
|
||||
}
|
||||
Reference in New Issue
Block a user