-
-
Save ivanminutillo/892b5eea4f4c5cda5c256141f5c811dd to your computer and use it in GitHub Desktop.
k6_liveview.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Bonfire LiveView Load Test for k6 | |
| // | |
| // Tests: "Can the server handle N concurrent LiveView connections to feed/explore?" | |
| // | |
| // Usage: | |
| // 1. Login to Bonfire in browser, copy session cookie from DevTools | |
| // 2. Run: k6 run -e COOKIE="your_cookie" -e HOST="localhost:4000" benchmarks/load_test/k6_liveview.js | |
| // | |
| // Options: | |
| // HOST - Target host (default: localhost:4000) | |
| // COOKIE - Session cookie value (required) | |
| // HTTPS - Set to "1" for https/wss (default: http/ws) | |
| // VUS - Number of virtual users (default: 50) | |
| // DURATION - Hold duration in seconds (default: 120) | |
| import http from "k6/http"; | |
| import ws from "k6/ws"; | |
| import { check, sleep } from "k6"; | |
| import { Counter, Trend } from "k6/metrics"; | |
| // Custom metrics | |
| const wsConnections = new Counter("ws_connections"); | |
| const wsErrors = new Counter("ws_errors"); | |
| const feedLoadTime = new Trend("feed_load_time", true); | |
| const wsConnectTime = new Trend("ws_connect_time", true); | |
| // Config from environment | |
| const host = __ENV.HOST || "localhost:4000"; | |
| const cookie = __ENV.COOKIE; | |
| const https = __ENV.HTTPS === "1"; | |
| const protocol = https ? "https" : "http"; | |
| const wsProtocol = https ? "wss" : "ws"; | |
| const vus = parseInt(__ENV.VUS || "500"); | |
| const duration = parseInt(__ENV.DURATION || "120"); | |
| export const options = { | |
| scenarios: { | |
| liveview_load: { | |
| executor: "constant-vus", | |
| vus: vus, | |
| duration: `${duration}s`, | |
| }, | |
| }, | |
| thresholds: { | |
| http_req_duration: ["p(95)<5000"], // 95% of requests under 5s | |
| ws_connections: ["count>0"], | |
| }, | |
| }; | |
| export default function () { | |
| if (!cookie) { | |
| console.error("COOKIE env var required. Get it from browser DevTools after logging in."); | |
| return; | |
| } | |
| // Step 1: Load feed/explore page | |
| const startFeed = Date.now(); | |
| const feedRes = http.get(`${protocol}://${host}/feed/explore`, { | |
| cookies: { bonfire: cookie }, | |
| }); | |
| feedLoadTime.add(Date.now() - startFeed); | |
| const feedOk = check(feedRes, { | |
| "feed loaded": (r) => r.status === 200, | |
| "has LiveView": (r) => r.body.includes("data-phx-session"), | |
| }); | |
| if (!feedOk) { | |
| console.log(`Feed failed: ${feedRes.status}`); | |
| wsErrors.add(1); | |
| return; | |
| } | |
| // Step 2: Extract LiveView tokens | |
| const body = feedRes.body; | |
| const csrfMatch = body.match(/name="csrf-token"[^>]*content="([^"]+)"/); | |
| const sessionMatch = body.match(/data-phx-session="([^"]+)"/); | |
| const staticMatch = body.match(/data-phx-static="([^"]+)"/); | |
| const idMatch = body.match(/data-phx-main[^>]*id="([^"]+)"/); | |
| const csrfToken = csrfMatch ? csrfMatch[1] : null; | |
| const phxSession = sessionMatch ? sessionMatch[1] : null; | |
| const phxStatic = staticMatch ? staticMatch[1] : null; | |
| const phxId = idMatch ? idMatch[1] : "phx-main"; | |
| if (!phxSession) { | |
| console.log("No LiveView session found"); | |
| wsErrors.add(1); | |
| return; | |
| } | |
| // Step 3: Connect WebSocket | |
| const wsUrl = `${wsProtocol}://${host}/live/websocket?vsn=2.0.0`; | |
| const startWs = Date.now(); | |
| const res = ws.connect( | |
| wsUrl, | |
| { headers: { Cookie: `bonfire=${cookie}` } }, | |
| function (socket) { | |
| socket.on("open", function () { | |
| wsConnectTime.add(Date.now() - startWs); | |
| wsConnections.add(1); | |
| // Join LiveView | |
| socket.send( | |
| JSON.stringify([ | |
| "1", | |
| "1", | |
| `lv:${phxId}`, | |
| "phx_join", | |
| { | |
| url: `${protocol}://${host}/feed/explore`, | |
| params: { _csrf_token: csrfToken, _mounts: 0 }, | |
| session: phxSession, | |
| static: phxStatic, | |
| }, | |
| ]) | |
| ); | |
| }); | |
| socket.on("message", function (msg) { | |
| // Handle messages silently, just keep connection alive | |
| }); | |
| socket.on("error", function (e) { | |
| console.log(`WebSocket error: ${e}`); | |
| wsErrors.add(1); | |
| }); | |
| // Send heartbeats every 30s | |
| socket.setInterval(function () { | |
| socket.send( | |
| JSON.stringify([null, `${Date.now()}`, "phoenix", "heartbeat", {}]) | |
| ); | |
| }, 30000); | |
| // Hold connection for test duration minus buffer | |
| socket.setTimeout(function () { | |
| socket.close(); | |
| }, (duration - 5) * 1000); | |
| } | |
| ); | |
| check(res, { "ws connected": (r) => r && r.status === 101 }); | |
| } | |
| export function handleSummary(data) { | |
| const summary = { | |
| vus: vus, | |
| duration_s: duration, | |
| feed_requests: data.metrics.http_reqs ? data.metrics.http_reqs.values.count : 0, | |
| feed_p95_ms: data.metrics.feed_load_time ? data.metrics.feed_load_time.values["p(95)"] : null, | |
| ws_connections: data.metrics.ws_connections ? data.metrics.ws_connections.values.count : 0, | |
| ws_errors: data.metrics.ws_errors ? data.metrics.ws_errors.values.count : 0, | |
| ws_connect_p95_ms: data.metrics.ws_connect_time ? data.metrics.ws_connect_time.values["p(95)"] : null, | |
| }; | |
| return { | |
| stdout: ` | |
| ===================================== | |
| Bonfire LiveView Load Test Results | |
| ===================================== | |
| VUs: ${summary.vus} | |
| Duration: ${summary.duration_s}s | |
| Feed Requests: ${summary.feed_requests} | |
| Feed p95: ${summary.feed_p95_ms ? summary.feed_p95_ms.toFixed(0) + "ms" : "N/A"} | |
| WS Connections: ${summary.ws_connections} | |
| WS Errors: ${summary.ws_errors} | |
| WS Connect p95: ${summary.ws_connect_p95_ms ? summary.ws_connect_p95_ms.toFixed(0) + "ms" : "N/A"} | |
| ===================================== | |
| `, | |
| "benchmarks/output/k6_results.json": JSON.stringify(summary, null, 2), | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment