Files
wikideck/scripts/rpc-load.ts
T
eric 636f5b9c77 V2.1 (#21)
Working version Reviewed-on: #21
2026-11-01 22:07:51 +00:00

34 lines
2.5 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export {};
// Opt-in loopback-only, read-only smoke load. Not a production capacity certification.
const args=process.argv.slice(2);
const argument=(key:string,fallback:string)=>{const i=args.indexOf(key);return i<0?fallback:args[i+1];};
if(!args.includes('--consent'))throw new Error('Explicit --consent required; use only an isolated disposable local node.');
const endpoint=new URL(argument('--node','http://127.0.0.1:3001'));
if(!['127.0.0.1','[::1]'].includes(endpoint.hostname)||!['http:','https:'].includes(endpoint.protocol)||
endpoint.username||endpoint.password||endpoint.pathname!=='/'||endpoint.search||endpoint.hash)throw new Error('Only literal loopback HTTP(S) origins are allowed. No remote load targets.');
const concurrency=Number(argument('--concurrency','8')),count=Number(argument('--requests','100'));
if(!Number.isInteger(concurrency)||concurrency<1||concurrency>32||!Number.isInteger(count)||count<1||count>1000)throw new Error('Concurrency 1–32; requests 1–1000.');
let next=0,totalBytes=0;const statuses:Record<string,number>={};const latencies:number[]=[];
const started=performance.now();
await Promise.all(Array.from({length:concurrency},async()=>{
while(next<count){const i=next++,start=performance.now();
try{
const path=i%3===0?'/api/peers':'/api/summary';
const response=await fetch(new URL(path,endpoint),{redirect:'error',signal:AbortSignal.timeout(5000)});
const reader=response.body?.getReader();let bytes=0;
if(reader)try{for(;;){const part=await reader.read();if(part.done)break;bytes+=part.value.byteLength;if(bytes>2*1024*1024)throw new Error('Response too large');}}finally{await reader.cancel();reader.releaseLock();}
totalBytes+=bytes;statuses[response.status]=(statuses[response.status]??0)+1;
}catch{statuses.error=(statuses.error??0)+1;}
latencies.push(performance.now()-start);
}
}));
latencies.sort((a,b)=>a-b);
const health=await fetch(new URL('/healthz',endpoint),{redirect:'error',signal:AbortSignal.timeout(5000)});
console.log(JSON.stringify({scope:'loopback read-only smoke, not production benchmark',requests:count,concurrency,
elapsedMs:Math.round(performance.now()-started),statuses,totalBytes,
p50Ms:Math.round(latencies[Math.floor(latencies.length*.5)]??0),p95Ms:Math.round(latencies[Math.min(latencies.length-1,Math.floor(latencies.length*.95))]??0),
healthStatus:health.status},null,2));
await health.body?.cancel();
if(statuses.error||health.status!==200)process.exitCode=1;