UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

270 lines (231 loc) 8.18 kB
/** * Test 2: k6 Load Test for WebSocket Message Bus * * Tests message routing throughput and latency under various connection loads. * * Usage: * k6 run load-test.js # Default: 100 agents, Node.js server * k6 run load-test.js -e TARGET=ws://localhost:8081/ws # Test Rust implementation * k6 run load-test.js -e AGENTS=500 # Test with 500 concurrent agents * k6 run load-test.js -e DURATION=5m # Run for 5 minutes * * Note: Rust server expects route /ws/:agent_id, so TARGET must include /ws base path */ import ws from 'k6/ws'; import { check, sleep } from 'k6'; import { Counter, Trend } from 'k6/metrics'; // Custom metrics const messagesRouted = new Counter('messages_routed'); const messagesFailed = new Counter('messages_failed'); const messageLatency = new Trend('message_latency_ms'); const ackLatency = new Trend('ack_latency_ms'); // Configuration from environment variables const TARGET = __ENV.TARGET || 'ws://localhost:8080'; const MAX_AGENTS = (() => { const parsed = parseInt(__ENV.AGENTS || '100', 10); // Validate and enforce minimum of 1 if (isNaN(parsed) || parsed < 1) { console.warn(`Invalid AGENTS value '${__ENV.AGENTS}', using default: 100`); return 100; } return Math.floor(parsed); })(); const TEST_DURATION = __ENV.DURATION || '2m'; // Test stages: ramp up, steady state, ramp down export const options = { stages: [ { duration: '30s', target: Math.floor(MAX_AGENTS * 0.2) }, // Ramp to 20% { duration: '30s', target: Math.floor(MAX_AGENTS * 0.5) }, // Ramp to 50% { duration: '30s', target: MAX_AGENTS }, // Ramp to 100% { duration: TEST_DURATION, target: MAX_AGENTS }, // Hold at 100% { duration: '30s', target: Math.floor(MAX_AGENTS * 0.5) }, // Ramp down to 50% { duration: '30s', target: 0 }, // Ramp down to 0 ], thresholds: { 'message_latency_ms': ['p(95)<500'], // 95% of messages routed in <500ms 'ack_latency_ms': ['p(95)<100'], // 95% of acks received in <100ms 'ws_connecting': ['p(95)<1000'], // 95% of connections established in <1s }, }; export default function () { const agentId = `agent-${__VU}`; const url = `${TARGET}/${agentId}`; const response = ws.connect(url, {}, function (socket) { // Track connection success const connectSuccess = check(socket, { 'Connected successfully': (s) => s !== null, }); if (!connectSuccess) { messagesFailed.add(1); return; } let welcomeReceived = false; let messagesReceived = 0; let acksReceived = 0; // Handle incoming messages socket.on('message', (data) => { try { const msg = JSON.parse(data); // Welcome message if (msg.type === 'welcome') { welcomeReceived = true; check(msg, { 'Welcome contains agent_id': (m) => m.agent_id === agentId, 'Welcome contains timestamp': (m) => m.timestamp > 0, }); } // Acknowledgment message else if (msg.ack) { acksReceived++; const now = Date.now(); if (msg.routed_at) { const latency = now - msg.routed_at; ackLatency.add(latency); } } // Routed message (from another agent) else if (msg.routed_by === 'message-bus') { messagesReceived++; const now = Date.now(); // Calculate end-to-end latency if sent_at is present if (msg.sent_at) { const latency = now - msg.sent_at; messageLatency.add(latency); } messagesRouted.add(1); } // Error message else if (msg.error) { messagesFailed.add(1); check(msg, { 'Error message contains description': (m) => m.error.length > 0, }); } } catch (e) { messagesFailed.add(1); } }); socket.on('error', (e) => { messagesFailed.add(1); }); // Wait for welcome message let waited = 0; while (!welcomeReceived && waited < 5000) { sleep(0.1); waited += 100; } if (!welcomeReceived) { socket.close(); return; } // Simulate agent-to-agent messaging patterns const patterns = [ sendBroadcastPattern, sendTargetedPattern, sendRoundRobinPattern, ]; // Randomly select a messaging pattern const pattern = patterns[Math.floor(Math.random() * patterns.length)]; pattern(socket, agentId); // Keep connection open for some time sleep(Math.random() * 5 + 5); // 5-10 seconds socket.close(); }); check(response, { 'WebSocket connection established': (r) => r && r.status === 101, }); sleep(1); } /** * Broadcast pattern: Send messages to multiple random agents */ function sendBroadcastPattern(socket, agentId) { const messageCount = Math.floor(Math.random() * 5) + 5; // 5-10 messages for (let i = 0; i < messageCount; i++) { // Pick random target agent const targetVU = Math.floor(Math.random() * MAX_AGENTS) + 1; const targetAgent = `agent-${targetVU}`; sendMessage(socket, agentId, targetAgent, { pattern: 'broadcast', index: i, total: messageCount, }); sleep(Math.random() * 0.5 + 0.1); // 100-600ms between messages } } /** * Targeted pattern: Send multiple messages to same agent (conversation) */ function sendTargetedPattern(socket, agentId) { // Pick one target agent for conversation const targetVU = Math.floor(Math.random() * MAX_AGENTS) + 1; const targetAgent = `agent-${targetVU}`; const messageCount = Math.floor(Math.random() * 10) + 10; // 10-20 messages for (let i = 0; i < messageCount; i++) { sendMessage(socket, agentId, targetAgent, { pattern: 'targeted', index: i, total: messageCount, conversation_id: `conv-${agentId}-${targetAgent}`, }); sleep(Math.random() * 0.3 + 0.05); // 50-350ms between messages (rapid conversation) } } /** * Round-robin pattern: Send messages to sequential agents */ function sendRoundRobinPattern(socket, agentId) { const messageCount = Math.floor(Math.random() * 8) + 8; // 8-16 messages for (let i = 0; i < messageCount; i++) { // Round-robin through agents const targetVU = ((__VU + i) % MAX_AGENTS) + 1; const targetAgent = `agent-${targetVU}`; sendMessage(socket, agentId, targetAgent, { pattern: 'round-robin', index: i, total: messageCount, }); sleep(Math.random() * 0.4 + 0.1); // 100-500ms between messages } } /** * Send a message through the WebSocket */ function sendMessage(socket, from, to, payload) { const message = { from: from, to: to, payload: payload, sent_at: Date.now(), id: `msg-${from}-${Date.now()}-${Math.random()}`, }; try { socket.send(JSON.stringify(message)); } catch (e) { messagesFailed.add(1); } } /** * Setup: Print test configuration */ export function setup() { console.log('========================================'); console.log('WebSocket Message Bus Load Test'); console.log('========================================'); console.log(`Target: ${TARGET}`); console.log(`Max Agents: ${MAX_AGENTS}`); console.log(`Test Duration: ${TEST_DURATION}`); console.log('========================================'); } /** * Teardown: Fetch and print final metrics */ export function teardown(data) { console.log('========================================'); console.log('Test Complete - Fetching Metrics...'); console.log('========================================'); // Note: k6 doesn't support HTTP requests in teardown // Metrics must be fetched manually: // curl http://localhost:8080/metrics (Node.js) // curl http://localhost:8081/metrics (Rust) }