import ws from "k6/ws"; import { sleep, check } from "k6"; // Env-tunable ramp and endpoints so you can reuse this locally and on a VPS. const HOST = __ENV.HOST || "127.0.0.1"; const PORT = __ENV.PORT || "5173"; const SCHEME = __ENV.SCHEME || "wss"; const AGENT_TOKEN = __ENV.AGENT_TOKEN || ""; const TLS_INSECURE = __ENV.TLS_INSECURE === "1" || __ENV.TLS_INSECURE === "true"; const ROLE = __ENV.ROLE || "viewer"; const CLIENT_PREFIX = __ENV.CLIENT_PREFIX || "soak"; const HEARTBEAT_MS = Number(__ENV.HEARTBEAT_MS || 15000); const INCLUDE_HELLO = __ENV.HELLO !== "0"; // send hello frame once to populate DB/state // Ramp shape: default 500 VU every 60s until VUS max. Override via env. const MAX_VUS = Number(__ENV.VUS || 2000); const STEP = Number(__ENV.STEP || 500); const STAGE_SEC = Number(__ENV.STAGE_SEC || 60); const stages = []; for (let target = STEP; target <= MAX_VUS; target += STEP) { stages.push({ duration: `${STAGE_SEC}s`, target }); } stages.push({ duration: `${STAGE_SEC}s`, target: MAX_VUS }); export const options = { scenarios: { ws_capacity: { executor: "ramping-vus", startVUs: 0, stages, gracefulRampDown: "30s", }, }, thresholds: { ws_connecting: ["p(95)<1000"], // connect latency ws_session_duration: ["p(95)>300000"], // keep sessions alive ws_sessions: ["count>0"], ws_msgs_sent: ["count>0"], checks: ["rate>0.99"], }, insecureSkipTLSVerify: TLS_INSECURE, }; function buildUrl(clientId) { return `${SCHEME}://${HOST}:${PORT}/api/clients/${clientId}/stream/ws?role=${ROLE}`; } export default function () { const clientId = `${CLIENT_PREFIX}-${__VU}-${__ITER}`; const url = buildUrl(clientId); const params = {}; if (AGENT_TOKEN) { params.headers = { "X-Agent-Token": AGENT_TOKEN }; } const res = ws.connect(url, params, (socket) => { socket.on("open", () => { if (INCLUDE_HELLO) { socket.send( JSON.stringify({ type: "hello", id: clientId, host: "soak", os: "test", arch: "x64", version: "k6", user: "k6", monitors: 1, country: "ZZ", }), ); } socket.send(JSON.stringify({ type: "ping", ts: Date.now() })); socket.setInterval(() => { socket.send(JSON.stringify({ type: "ping", ts: Date.now() })); }, HEARTBEAT_MS); }); socket.on("message", () => { // consume messages to avoid backpressure }); socket.on("error", (e) => { console.error(`ws error: ${e.error()}`); }); socket.on("close", () => { // closed }); sleep(3600); // hold connection for the scenario duration }); check(res, { "status 101": (r) => r && r.status === 101, }); }