From 8f83cff04efbe44f67b037acd9d7090e1dfaa028 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:38:01 +0000 Subject: [PATCH] Implement load test script with rate configuration and multipart event simulation. - Update `scripts/agent/load-test.mjs` to support `--rate` arg. - Add multipart video event simulation (Post + Mirror). - Generate performance report with bottleneck analysis. - Include `artifacts/load-report.json`. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- artifacts/load-report.json | 126 +++++++++++++++++++++--------------- scripts/agent/load-test.mjs | 61 +++++++++++------ 2 files changed, 117 insertions(+), 70 deletions(-) diff --git a/artifacts/load-report.json b/artifacts/load-report.json index 6415cde4..c737e0ef 100644 --- a/artifacts/load-report.json +++ b/artifacts/load-report.json @@ -1,77 +1,99 @@ { "meta": { - "clients": 1000, - "duration": 60, - "targetRatePerClient": 0.5 + "clients": 50, + "duration": 10, + "targetTotalRate": 50, + "ratePerClient": 1 }, "results": { - "totalSent": 9880, - "totalReceivedByObserver": 9212, + "totalSent": 545, + "totalReceivedByObserver": 554, "totalErrors": 0, "errorRate": 0, - "avgLatencyMs": 633.5714285714286, - "maxLatencyMs": 4352, - "avgSignTimeMs": 5.312883614738433 + "avgLatencyMs": 5.888888888888889, + "maxLatencyMs": 8, + "avgSignTimeMs": 5.534525739479302, + "hot_functions": [ + { + "name": "Event Signing (Client)", + "avgTimeMs": 5.534525739479302, + "calls": 545 + } + ] }, "bottlenecks": [ - "High Average Latency (>500ms)", - "Cryptographic Bottleneck (Avg Sign Time 5.31ms > 5ms)" + "Cryptographic Bottleneck (Avg Sign Time 5.53ms > 5ms)" ], "metrics": { "timestamps": [ - "2026-02-02T19:20:53.413Z", - "2026-02-02T19:21:05.252Z", - "2026-02-02T19:21:09.391Z", - "2026-02-02T19:21:17.727Z", - "2026-02-02T19:21:25.657Z", - "2026-02-02T19:21:37.832Z", - "2026-02-02T19:21:46.152Z" + "2026-02-05T19:34:52.639Z", + "2026-02-05T19:34:53.638Z", + "2026-02-05T19:34:54.641Z", + "2026-02-05T19:34:55.649Z", + "2026-02-05T19:34:56.649Z", + "2026-02-05T19:34:57.649Z", + "2026-02-05T19:34:58.672Z", + "2026-02-05T19:34:59.671Z", + "2026-02-05T19:35:00.672Z" ], "cpu": [ - 1.805, - 1131.911, - 420.9089, - 851.5216, - 798.2084, - 1222.0326, - 835.8484 + 47.1817, + 40.502, + 37.7894, + 36.4607, + 36.4638, + 36.3083, + 41.62, + 35.6474, + 35.4054 ], "memory": [ - 17.17798614501953, - 27.51868438720703, - 19.160232543945312, - 23.5911865234375, - 19.544578552246094, - 33.026039123535156, - 20.853515625 + 12.619827270507812, + 12.327117919921875, + 16.86083221435547, + 13.562667846679688, + 16.10260009765625, + 11.007034301757812, + 14.259078979492188, + 14.051017761230469, + 15.620246887207031 ], "latency": [ - 14, - 4352, - 13, - 12, - 15, - 15, - 14 + 8, + 6, + 6, + 5, + 6, + 6, + 6, + 5, + 5 ], "throughput": [ - 1, - 1119, - 1257, - 1356, - 1330, - 1448, - 1345 + 60, + 59, + 64, + 63, + 62, + 62, + 63, + 60, + 61 ], "avgSignTime": [ - 0, - 6.01051988318584, - 5.731465363770286, - 5.82640474538752, - 5.684893507148126, - 8.042285998617778, - 5.894615805059483 + 6.518746254237273, + 5.50673901724142, + 5.487499650793643, + 5.261303790322589, + 5.267506754098338, + 5.199484229508195, + 6.068795209677216, + 5.2998529661016915, + 5.2008037833333525 ] }, + "remediation": [ + "Client-side event signing is slow. Review cryptographic library (nostr-tools) usage or client hardware capabilities." + ], "requiresSecurityReview": true } \ No newline at end of file diff --git a/scripts/agent/load-test.mjs b/scripts/agent/load-test.mjs index 85bc4394..14637ff7 100644 --- a/scripts/agent/load-test.mjs +++ b/scripts/agent/load-test.mjs @@ -1,7 +1,7 @@ import './setup-test-env.js'; import { WebSocket } from 'ws'; import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools'; -import { buildVideoPostEvent, buildViewEvent } from '../../js/nostrEventSchemas.js'; +import { buildVideoPostEvent, buildViewEvent, buildVideoMirrorEvent } from '../../js/nostrEventSchemas.js'; import fs from 'fs'; import path from 'path'; import { spawn } from 'child_process'; @@ -73,41 +73,62 @@ class LoadBot { publish() { if (!this.connected || this.ws.readyState !== WebSocket.OPEN) return; - // Mix: 80% View Events (Small), 20% Video Post (Large) + // Mix: 80% View Events (Small), 20% Multi-part Video (Large) const isLarge = Math.random() < 0.2; - let eventTemplate; + const eventsToSend = []; if (isLarge) { - eventTemplate = buildVideoPostEvent({ + const videoRootId = `root-${this.id}-${Date.now()}`; + // 1. Video Metadata (Kind 30078) + const videoEvent = buildVideoPostEvent({ pubkey: this.pk, created_at: Math.floor(Date.now() / 1000), - dTagValue: `load-${this.id}-${Date.now()}`, + dTagValue: videoRootId, content: { version: 3, title: `Load Test Video ${Date.now()}`, - description: 'A' .repeat(500), // Filler content - videoRootId: `root-${this.id}-${Date.now()}`, + description: 'A'.repeat(5000), // Filler content ~5KB + videoRootId: videoRootId, magnet: `magnet:?xt=urn:btih:${'a'.repeat(40)}&dn=test` } }); + eventsToSend.push(videoEvent); + + // 2. Video Mirror (Kind 1063) - Simulated "multi-part" + const mirrorEvent = buildVideoMirrorEvent({ + pubkey: this.pk, + created_at: Math.floor(Date.now() / 1000), + tags: [ + ['a', `30078:${this.pk}:${videoRootId}`] + ], + content: { + description: 'Mirror filler ' + 'B'.repeat(100) + } + }); + eventsToSend.push(mirrorEvent); + } else { - eventTemplate = buildViewEvent({ + // View Event + const viewEvent = buildViewEvent({ pubkey: this.pk, created_at: Math.floor(Date.now() / 1000), content: 'view-ping', dedupeTag: `view-${this.id}-${Date.now()}` }); + eventsToSend.push(viewEvent); } try { - const t0 = performance.now(); - const signedEvent = finalizeEvent(eventTemplate, this.sk); - const t1 = performance.now(); - this.signTimes.push(t1 - t0); + for (const ev of eventsToSend) { + const t0 = performance.now(); + const signedEvent = finalizeEvent(ev, this.sk); + const t1 = performance.now(); + this.signTimes.push(t1 - t0); - const msg = JSON.stringify(['EVENT', signedEvent]); - this.ws.send(msg); - this.sentCount++; + const msg = JSON.stringify(['EVENT', signedEvent]); + this.ws.send(msg); + this.sentCount++; + } } catch (e) { this.errors++; } @@ -195,12 +216,15 @@ async function runLoadTest() { const args = process.argv.slice(2); const clientsArg = args.find(a => a.startsWith('--clients=')); const durationArg = args.find(a => a.startsWith('--duration=')); + const rateArg = args.find(a => a.startsWith('--rate=')); const NUM_CLIENTS = clientsArg ? parseInt(clientsArg.split('=')[1]) : 1000; const DURATION_SEC = durationArg ? parseInt(durationArg.split('=')[1]) : 60; // Default 1 min - const TARGET_RATE = 0.5; // Events per second per bot (0.5 * 1000 = 500 events/sec total) + const TOTAL_TARGET_RATE = rateArg ? parseInt(rateArg.split('=')[1]) : 500; // Events/sec total + const RATE_PER_CLIENT = TOTAL_TARGET_RATE / NUM_CLIENTS; console.log(`[Load Test] Starting with ${NUM_CLIENTS} clients for ${DURATION_SEC}s...`); + console.log(`[Load Test] Target Rate: ${TOTAL_TARGET_RATE} events/s (${RATE_PER_CLIENT.toFixed(2)}/client)`); // Ensure artifacts dir if (!fs.existsSync(ARTIFACTS_DIR)) fs.mkdirSync(ARTIFACTS_DIR); @@ -222,7 +246,7 @@ async function runLoadTest() { const bots = []; console.log('[Load Test] Spawning bots...'); for (let i = 0; i < NUM_CLIENTS; i++) { - const bot = new LoadBot(i, RELAY_URL, TARGET_RATE); + const bot = new LoadBot(i, RELAY_URL, RATE_PER_CLIENT); bots.push(bot); } @@ -313,7 +337,8 @@ async function runLoadTest() { meta: { clients: NUM_CLIENTS, duration: DURATION_SEC, - targetRatePerClient: TARGET_RATE + targetTotalRate: TOTAL_TARGET_RATE, + ratePerClient: RATE_PER_CLIENT }, results: { totalSent,