Created
February 8, 2026 22:37
-
-
Save jkoppel/d26732574dfcdcc6bfc4958596054d2e to your computer and use it in GitHub Desktop.
Test showing Bun using 20x the CPU of Node
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bun | |
| /** | |
| * Minimal reproduction: Bun TLS CPU overhead with concurrent HTTPS requests. | |
| * | |
| * This test sends N concurrent HTTPS POST requests with varying payload sizes | |
| * to a public HTTPS endpoint and measures CPU consumption. | |
| * | |
| * Run with: bun run src/test-bun-tls-cpu.ts | |
| * Compare with: node --experimental-strip-types src/test-bun-tls-cpu.ts | |
| */ | |
| const pid = process.pid; | |
| const runtime = typeof Bun !== "undefined" ? `Bun ${Bun.version}` : `Node ${process.version}`; | |
| console.log(`Runtime: ${runtime}`); | |
| console.log(`PID: ${pid}\n`); | |
| // Use a simple HTTPS echo endpoint | |
| const URL = "https://httpbin.org/post"; | |
| async function measureConcurrentRequests(concurrency: number, payloadSizeKB: number): Promise<{ | |
| durationSec: number; | |
| peakCpu: number; | |
| avgCpu: number; | |
| successCount: number; | |
| failCount: number; | |
| nonOkCount: number; | |
| errors: Record<string, number>; | |
| cpuTimeline: Array<{ second: number; percent: number; userMs: number; systemMs: number }>; | |
| }> { | |
| const payload = JSON.stringify({ data: "x".repeat(payloadSizeKB * 1024) }); | |
| let lastCpu = process.cpuUsage(); | |
| let lastTs = Date.now(); | |
| const cpuTimeline: Array<{ second: number; percent: number; userMs: number; systemMs: number }> = []; | |
| const cpuInterval = setInterval(() => { | |
| const now = Date.now(); | |
| const delta = now - lastTs; | |
| if (delta <= 0) return; | |
| const usage = process.cpuUsage(lastCpu); | |
| const userMs = usage.user / 1000; | |
| const systemMs = usage.system / 1000; | |
| cpuTimeline.push({ | |
| second: cpuTimeline.length + 1, | |
| percent: ((userMs + systemMs) / delta) * 100, | |
| userMs, | |
| systemMs, | |
| }); | |
| lastCpu = process.cpuUsage(); | |
| lastTs = now; | |
| }, 1000); | |
| const startTime = Date.now(); | |
| let successCount = 0; | |
| let failCount = 0; | |
| let nonOkCount = 0; | |
| const errors: Record<string, number> = {}; | |
| const promises = Array.from({ length: concurrency }, (_, i) => | |
| fetch(URL, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", "X-Request-Index": String(i) }, | |
| body: payload, | |
| }).then(async (r) => { | |
| await r.text(); // consume body | |
| if (r.ok) { | |
| successCount++; | |
| } else { | |
| nonOkCount++; | |
| const key = `HTTP ${r.status}`; | |
| errors[key] = (errors[key] ?? 0) + 1; | |
| } | |
| }).catch((e: unknown) => { | |
| failCount++; | |
| const key = e instanceof Error ? e.message.slice(0, 80) : String(e).slice(0, 80); | |
| errors[key] = (errors[key] ?? 0) + 1; | |
| }) | |
| ); | |
| await Promise.all(promises); | |
| clearInterval(cpuInterval); | |
| const durationSec = (Date.now() - startTime) / 1000; | |
| const peakCpu = cpuTimeline.length > 0 ? Math.max(...cpuTimeline.map(s => s.percent)) : 0; | |
| const avgCpu = cpuTimeline.length > 0 ? cpuTimeline.reduce((a, b) => a + b.percent, 0) / cpuTimeline.length : 0; | |
| return { durationSec, peakCpu, avgCpu, successCount, failCount, nonOkCount, errors, cpuTimeline }; | |
| } | |
| console.log("Test: Concurrent HTTPS POST requests with varying payload sizes"); | |
| console.log("================================================================\n"); | |
| const tests = [ | |
| { concurrency: 50, payloadKB: 1 }, | |
| { concurrency: 50, payloadKB: 5 }, | |
| { concurrency: 50, payloadKB: 10 }, | |
| { concurrency: 100, payloadKB: 1 }, | |
| { concurrency: 100, payloadKB: 5 }, | |
| { concurrency: 100, payloadKB: 10 }, | |
| ]; | |
| for (const { concurrency, payloadKB } of tests) { | |
| const result = await measureConcurrentRequests(concurrency, payloadKB); | |
| // Show user/system breakdown from peak second | |
| const peakSecond = result.cpuTimeline.reduce((max, s) => s.percent > max.percent ? s : max, { percent: 0, userMs: 0, systemMs: 0, second: 0 }); | |
| const total = result.successCount + result.failCount + result.nonOkCount; | |
| console.log( | |
| `\n--- ${concurrency} concurrent, ${payloadKB}KB payload ---\n` + | |
| ` Duration: ${result.durationSec.toFixed(1)}s\n` + | |
| ` Results: ${result.successCount}/${total} OK, ${result.nonOkCount} non-200, ${result.failCount} failed\n` + | |
| ` Peak CPU: ${result.peakCpu.toFixed(1)}% (user: ${peakSecond.userMs.toFixed(0)}ms, sys: ${peakSecond.systemMs.toFixed(0)}ms)\n` + | |
| ` Avg CPU: ${result.avgCpu.toFixed(1)}%` | |
| ); | |
| if (Object.keys(result.errors).length > 0) { | |
| console.log(` Errors:`); | |
| for (const [msg, count] of Object.entries(result.errors)) { | |
| console.log(` ${count}x ${msg}`); | |
| } | |
| } | |
| } | |
| console.log("\nDone."); | |
| process.exit(0); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment