From d3a3097d1c7c45f7622940bf1fed9e0080b46561 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:10:29 +0000 Subject: [PATCH] feat: implement load and stress test script with relay monitoring Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- artifacts/load-report.json | 97 ------------------------------------- scripts/agent/load-test.mjs | 50 +++++++++++++++---- 2 files changed, 40 insertions(+), 107 deletions(-) delete mode 100644 artifacts/load-report.json diff --git a/artifacts/load-report.json b/artifacts/load-report.json deleted file mode 100644 index 7fcefddb..00000000 --- a/artifacts/load-report.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "timestamp": "2026-01-26T19:39:32.887Z", - "config": { - "duration": 60, - "clients": 1000, - "rate": 0.1, - "relay": "ws://localhost:8889", - "force": false - }, - "metrics": { - "total_sent": 6467, - "total_received": 6467, - "errors": 0, - "throughput_sent": 107.78333333333333, - "throughput_recv": 107.78333333333333, - "latency_ms": { - "p50": 0.9700979999979609, - "p95": 14.598196999999345, - "p99": 27.9113440000001 - }, - "operation_times_ms": { - "build_avg": 0.10325683753031617, - "sign_avg": 6.474848927014066 - }, - "resource_usage": [ - { - "timestamp": 1769456317885, - "cpuPercent": 74.54, - "rss": 129236992, - "heapUsed": 26275344 - }, - { - "timestamp": 1769456322898, - "cpuPercent": 71.77, - "rss": 130256896, - "heapUsed": 30151424 - }, - { - "timestamp": 1769456327916, - "cpuPercent": 70.52, - "rss": 131235840, - "heapUsed": 30829208 - }, - { - "timestamp": 1769456332917, - "cpuPercent": 72.98, - "rss": 131637248, - "heapUsed": 38053280 - }, - { - "timestamp": 1769456337917, - "cpuPercent": 78.13, - "rss": 132292608, - "heapUsed": 25903752 - }, - { - "timestamp": 1769456342918, - "cpuPercent": 92.71, - "rss": 133566464, - "heapUsed": 28111320 - }, - { - "timestamp": 1769456347922, - "cpuPercent": 94.4, - "rss": 133959680, - "heapUsed": 33235048 - }, - { - "timestamp": 1769456352924, - "cpuPercent": 77.78, - "rss": 134717440, - "heapUsed": 27477160 - }, - { - "timestamp": 1769456357925, - "cpuPercent": 86.22, - "rss": 135241728, - "heapUsed": 36644992 - }, - { - "timestamp": 1769456362926, - "cpuPercent": 79.21, - "rss": 136028160, - "heapUsed": 28313856 - }, - { - "timestamp": 1769456367927, - "cpuPercent": 71.57, - "rss": 136814592, - "heapUsed": 39710304 - } - ], - "error_breakdown": {} - }, - "bottlenecks": [], - "remediation": [] -} \ No newline at end of file diff --git a/scripts/agent/load-test.mjs b/scripts/agent/load-test.mjs index 86b17196..87fa9450 100644 --- a/scripts/agent/load-test.mjs +++ b/scripts/agent/load-test.mjs @@ -1,12 +1,15 @@ import './setup-test-env.js'; import { parseArgs } from 'node:util'; -import { spawn } from 'node:child_process'; +import { spawn, exec } from 'node:child_process'; import { fileURLToPath } from 'url'; import NodeWebSocket from 'ws'; import * as NostrTools from 'nostr-tools'; import fs from 'node:fs'; import path from 'node:path'; import { performance } from 'node:perf_hooks'; +import util from 'node:util'; + +const execAsync = util.promisify(exec); // Polyfill global NostrTools for app modules global.NostrTools = NostrTools; @@ -76,6 +79,7 @@ const metrics = { sign: [], }, resourceUsage: [], + relayResourceUsage: [], // { timestamp, cpu, mem } }; // --- Helpers --- @@ -96,7 +100,7 @@ async function startRelay() { // Wait for port await waitForPort(parseInt(port)); - console.log('[Setup] Relay started.'); + console.log('[Setup] Relay started (PID: ' + relayProcess.pid + ').'); } async function waitForPort(port) { @@ -121,6 +125,20 @@ async function waitForPort(port) { throw new Error(`Relay did not start on port ${port}`); } +async function getProcessStats(pid) { + try { + // Output: %CPU %MEM + const { stdout } = await execAsync(`ps -p ${pid} -o %cpu,%mem --no-headers`); + const [cpu, mem] = stdout.trim().split(/\s+/); + return { + cpu: parseFloat(cpu) || 0, + mem: parseFloat(mem) || 0 + }; + } catch (e) { + return null; + } +} + function createClient() { const sk = NostrTools.generateSecretKey(); const pk = NostrTools.getPublicKey(sk); @@ -177,7 +195,6 @@ async function run() { // If user provided a custom URL, we assume they manage the relay unless it matches our default logic. // For simplicity, we always try to start the relay if it's on localhost and port matches default or we can detect it's not running. // But per instructions "Use a local relay", let's always try to start it if it's the default port, or fail if port is taken. - // Actually, let's just try to start it. await startRelay().catch(e => { console.log('[Setup] Could not start relay (maybe already running?):', e.message); }); @@ -195,6 +212,7 @@ async function run() { // 3. Load Loop console.log('[Load] Starting load generation...'); + console.log('[Load] Requirements: 10% Video Post (multipart), 90% View Event'); const startTime = performance.now(); const endTime = startTime + (config.duration * 1000); @@ -209,7 +227,7 @@ async function run() { let lastTime = performance.now(); // Monitoring Loop - const monitorInterval = setInterval(() => { + const monitorInterval = setInterval(async () => { const now = performance.now(); const cpu = process.cpuUsage(); const elapsedMs = now - lastTime; @@ -228,11 +246,22 @@ async function run() { heapUsed: mem.heapUsed, }); + if (relayProcess) { + const stats = await getProcessStats(relayProcess.pid); + if (stats) { + metrics.relayResourceUsage.push({ + timestamp: Date.now(), + cpuPercent: stats.cpu, + memPercent: stats.mem + }); + } + } + lastCpu = cpu; lastTime = now; const p50 = calculatePercentile(metrics.latencies, 50); - console.log(`[Monitor] Sent: ${metrics.sent}, Recv: ${metrics.received}, Errors: ${metrics.errors}, Latency p50: ${p50.toFixed(2)}ms, CPU: ${cpuPercent.toFixed(1)}%`); + console.log(`[Monitor] Sent: ${metrics.sent}, Recv: ${metrics.received}, Errors: ${metrics.errors}, Latency p50: ${p50.toFixed(2)}ms, Runner CPU: ${cpuPercent.toFixed(1)}%`); } }, 5000); @@ -249,7 +278,6 @@ async function run() { const isHeavy = Math.random() < 0.1; try { - let event; const buildStart = performance.now(); const now = Math.floor(Date.now() / 1000); const hexPk = client.pk; @@ -315,7 +343,7 @@ async function run() { } else { // View Event - event = buildViewEvent({ + const event = buildViewEvent({ pubkey: hexPk, created_at: now, pointerValue: `load-video-${Math.random()}`, @@ -388,6 +416,8 @@ function calculatePercentile(data, percentile) { } function generateReport() { + const signAvg = metrics.operationTimes.sign.reduce((a, b) => a + b, 0) / metrics.operationTimes.sign.length || 0; + const report = { timestamp: new Date().toISOString(), config, @@ -405,9 +435,10 @@ function generateReport() { }, operation_times_ms: { build_avg: metrics.operationTimes.build.reduce((a, b) => a + b, 0) / metrics.operationTimes.build.length || 0, - sign_avg: metrics.operationTimes.sign.reduce((a, b) => a + b, 0) / metrics.operationTimes.sign.length || 0, + sign_avg: signAvg, }, resource_usage: metrics.resourceUsage, + relay_resource_usage: metrics.relayResourceUsage, error_breakdown: metrics.errorCounts, }, bottlenecks: [], @@ -425,7 +456,6 @@ function generateReport() { } // Hot functions proxy / Crypto bottleneck - const signAvg = report.metrics.operation_times_ms.sign_avg; if (signAvg > 10) { report.bottlenecks.push(`Signing is slow (${signAvg.toFixed(2)}ms)`); report.remediation.push('Optimize signing or offload to worker.'); @@ -442,7 +472,7 @@ function generateReport() { const reportPath = path.join(ARTIFACTS_DIR, filename); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); - // Also save as load-report.json + // Also save as load-report.json for convenience const latestPath = path.join(ARTIFACTS_DIR, 'load-report.json'); fs.copyFileSync(reportPath, latestPath);