#!/usr/bin/env node const base = process.argv[2]; if (!base) { console.error('usage: node alt/bench.mjs [--duration-ms=10000]'); process.exit(2); } const duration = Number(process.argv.find(x => x.startsWith('--duration-ms='))?.split('=')[1]) || 10000; const cachedId = process.env.TEST_MEDIA_ID || 'dQw4w9WgXcQ'; const query = encodeURIComponent(process.env.BENCH_QUERY || 'ytplayer-bench-seed'); const manifest = await (await fetch(new URL('/api/manifest', base))).json(); const cssHash = manifest.files?.['/styles.css']?.h; const targets = [ ['version', '/api/version'], ['static', `/styles.css${cssHash ? `?v=${cssHash}` : ''}`], ['cached-search', `/api/search?q=${query}`], ['media-range', `/api/media/${cachedId}?g=1`, { Range: 'bytes=0-1048575' }], ]; async function run(label, path, concurrency, headers = {}) { let count = 0, responseBytes = 0, errors = 0; const latencies = []; const until = Date.now() + duration; async function worker() { while (Date.now() < until) { const start = performance.now(); try { const response = await fetch(new URL(path, base), { headers }); const body = await response.arrayBuffer(); responseBytes += body.byteLength; if (!response.ok) errors++; } catch { errors++; } latencies.push(performance.now() - start); count++; } } await Promise.all(Array.from({ length: concurrency }, worker)); latencies.sort((a, b) => a - b); const pick = p => latencies[Math.min(latencies.length - 1, Math.floor(latencies.length * p))] || 0; return { route: label, concurrency, durationMs: duration, requests: count, rps: +(count / (duration / 1000)).toFixed(1), responseBytes, errors, p50Ms: +pick(.5).toFixed(1), p95Ms: +pick(.95).toFixed(1), p99Ms: +pick(.99).toFixed(1) }; } for (const concurrency of [1, 10, 50, 200]) { for (const [label, path, headers] of targets) console.log(JSON.stringify(await run(label, path, concurrency, headers))); }