Files
anonymous_github/backend/scripts/render-http-benchmark.cjs
T

28 lines
5.8 KiB
JavaScript
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.
'use strict';
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
const input=process.argv[2]||'backend/reports/http-archive-benchmark.json';
const report=JSON.parse(fs.readFileSync(input,'utf8'));
const median=values=>[...values].sort((a,b)=>a-b)[Math.floor(values.length/2)];
const fixtures=[...new Set(report.results.map(r=>r.fixture))];
const rows=fixtures.map(fixture=>({fixture,...Object.fromEntries(['node','go'].map(runtime=>{
const samples=report.results.filter(r=>r.fixture===fixture&&r.runtime===runtime);
assert.ok(samples.length);
if(samples.some(r=>r.status==='failed'))return [runtime,{failure:samples.find(r=>r.status==='failed').failure}];
return [runtime,Object.fromEntries([1,8].map(concurrency=>{
const batches=samples.map(r=>r.warm.find(w=>w.concurrency===concurrency));assert.ok(batches.every(Boolean));
return [concurrency,Object.fromEntries(['p95Ms','requestsPerSecond','peakProcessTreeRssBytes','cpuMsPerRequest'].map(metric=>[metric,median(batches.map(w=>metric==='cpuMsPerRequest'?w.cpuMs/w.requests:w[metric]))]))];
}))];
}))}));
const data=JSON.stringify(rows).replaceAll('<','\\u003c');
const title='Node and Go: archive file requests';
const html=`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${title}</title><style>
body{margin:0;background:transparent;color:var(--foreground,#242424);font:14px/1.5 var(--font-sans,system-ui,sans-serif)}h2{font-size:20px;margin:0 0 8px;font-weight:650}p{margin:8px 0;color:var(--muted-foreground,#68717d)}.controls{display:flex;gap:12px;flex-wrap:wrap;margin:18px 0}label{display:flex;flex-direction:column;gap:4px;font-size:12px}select{font:inherit;font-size:14px;padding:7px 9px;border:1px solid var(--border,#b8bdc6);border-radius:6px;background:var(--card,#fff);color:inherit}table{border-collapse:collapse;width:100%;table-layout:fixed}th,td{padding:12px 5px;border-bottom:1px solid var(--border,#d0d5dd);text-align:right;vertical-align:top;overflow-wrap:anywhere}th{font-size:12px;font-weight:600;color:var(--muted-foreground,#68717d)}th:first-child,td:first-child{text-align:left;width:35%;padding-left:0}th:last-child,td:last-child{padding-right:0}td{font-variant-numeric:tabular-nums}.failure{color:var(--destructive,#b4413b);font-size:12px}.note{font-size:12px;margin-top:14px}.limit{border-left:3px solid var(--border,#9aa2ad);padding-left:12px;margin-top:18px}footer{font-size:11px;color:var(--muted-foreground,#68717d);margin:16px 0 0}@media(max-width:440px){th,td{padding:10px 3px;font-size:12px}h2{font-size:18px}}
</style></head><body><h2>${title}</h2><p>Real server entrypoints, disposable MongoDB and Redis, synthetic archive originals. Every successful response was checked by SHA-256.</p><div class="controls"><label>Concurrent requests<select id="concurrency"><option value="8">8 warm requests</option><option value="1">1 warm request</option></select></label><label>Metric<select id="metric"><option value="p95Ms">p95 response time</option><option value="requestsPerSecond">Throughput</option><option value="peakProcessTreeRssBytes">Sampled peak RSS</option><option value="cpuMsPerRequest">CPU per request</option></select></label></div><table><thead><tr><th scope="col">Fixture</th><th scope="col">Node</th><th scope="col">Go</th><th scope="col">Go / Node</th></tr></thead><tbody id="rows"></tbody></table><p class="note" id="explanation"></p><p class="limit">Node returned HTTP 500 for the 2 MiB text fixture: its persisted term array could not be cloned into the worker. No speedup is assigned to that failed baseline.</p><p class="note">${report.method.quick?'Smoke run; 16 warm requests per batch.':'Three alternating-order rounds; 100 warm requests per batch.'} CPU includes worker processes and post-response view writes. RSS is sampled every 10 ms and excludes MongoDB, Redis and the load generator. Shared gzip caching, remote providers, S3 and startup are outside this workload.</p><footer>${report.node} · ${report.go} · ${report.hardware.cpu} · ${report.recordedAt}</footer><script>
const data=${data};const descriptions={p95Ms:'Lower is better. Values are medians of the per-round p95 latencies, not a pooled p95.',requestsPerSecond:'Higher is better. Values are medians of three per-round throughputs.',peakProcessTreeRssBytes:'Lower is better. Median of the sampled per-round process-tree peaks. Short-lived peaks can be missed.',cpuMsPerRequest:'Lower is better. Approximate server process-tree CPU per completed request, then median across rounds.'};
function render(){const metric=document.querySelector('#metric').value,c=document.querySelector('#concurrency').value,body=document.querySelector('#rows');body.replaceChildren();for(const row of data){const tr=document.createElement('tr');const name=document.createElement('td');name.textContent=row.fixture;tr.append(name);for(const runtime of ['node','go']){const td=document.createElement('td');if(row[runtime].failure){td.textContent='HTTP 500';td.className='failure'}else{let value=row[runtime][c][metric],unit=metric==='requestsPerSecond'?'req/s':'ms';if(metric==='peakProcessTreeRssBytes'){value/=1048576;unit='MiB'}td.textContent=value.toFixed(1)+' '+unit}tr.append(td)}const ratio=document.createElement('td');ratio.textContent=row.node.failure||row.go.failure?'—':(row.go[c][metric]/row.node[c][metric]).toFixed(2)+'×';tr.append(ratio);body.append(tr)}document.querySelector('#explanation').textContent=descriptions[metric]+' The last column divides Go by Node.'}
document.querySelectorAll('select').forEach(select=>select.addEventListener('change',render));render();console.log('Benchmark rows:',data.length);
</script></body></html>`;
const target=input.replace(/\.json$/,'.html');assert.notEqual(target,input);fs.writeFileSync(target,html);
console.log(target);
for(const row of rows)console.log(JSON.stringify(row));