Mercurial
view benchmark/bun-http-framework-benchmark/bench.ts @ 196:83f16548ba41
[AI] Adding s3 bucket uploader code using Seobeo.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 14 Feb 2026 16:08:15 -0800 |
| parents | a8976a008a9d |
| children |
line wrap: on
line source
import { readdirSync, mkdirSync, existsSync, lstatSync, readFileSync, writeFileSync } from 'fs' import killPort from 'kill-port' import { $, pathToFileURL } from 'bun' const whitelists = <string[]>[] // ? Not working const blacklists = [ // Not booting up in test 'node/adonis/index', // Not setting content-type header for some reason 'node/nest/index', // 'Not booting up in test' 'node/hapi', // Body: Result not match 'bun/xirelta', // Crash 'bun/bagel', // Crash 'bun/bunrest', // Doesn't work properly 'bun/colston', // Crash on 0.6.2 'bun/zarf', // Crash due to invalid npm version requirement of uWebSockets 'deno/byte', // Crash 'bun/fastify', // failed to parse body in benchmark 'bun/byte', // doesn't run 'bun/vixeny', 'node/elysia', 'c/BUILD/index', 'deno/acorn', 'deno/deno', 'deno/deno-web-standard', 'deno/hono', 'deno/oak' ] as const const time = 10 const commands = [ `/home/june/zenbu/benchmark/bun-http-framework-benchmark/bombardier-linux-386 --fasthttp -c 500 -d ${time}s http://127.0.0.1:3000/`, `/home/june/zenbu/benchmark/bun-http-framework-benchmark/bombardier-linux-386 --fasthttp -c 500 -d ${time}s http://127.0.0.1:3000/id/1?name=bun`, `/home/june/zenbu/benchmark/bun-http-framework-benchmark/bombardier-linux-386 --fasthttp -c 500 -d ${time}s -m POST -H 'Content-Type:application/json' -f ./scripts/body.json http://127.0.0.1:3000/json` ] as const const runtimeCommand = { node: 'node', deno: 'deno run --allow-net --allow-env', bun: 'bun', c: '' // C binaries are run directly after bazel build } as const // Path to the bazel-bin directory relative to project root const getBazelBinaryPath = (framework: string) => `../../bazel-bin/benchmark/bun-http-framework-benchmark/src/c/${framework}` const catchNumber = /Reqs\/sec\s+(\d+[.|,]\d+)/m const format = (value: string | number) => Intl.NumberFormat('en-US').format(+value) const sleep = (s = 1) => new Promise((resolve) => setTimeout(resolve, s * 1000)) const secToMin = (seconds: number) => Math.floor(seconds / 60) + ':' + (seconds % 60 < 10 ? '0' : '') + (seconds % 60) // Fetch with retry const retryFetch = ( url: string, options?: RequestInit, time = 0, resolveEnd?: Function, rejectEnd?: Function ) => { return new Promise<Response>((resolve, reject) => { fetch(url, options) .then((a) => { if (resolveEnd) resolveEnd(a) resolve(a) }) .catch((e) => { if (time > 7) { if (rejectEnd) rejectEnd(e) return reject(e) } setTimeout( () => retryFetch(url, options, time + 1, resolve, reject), 200 ) }) }) } const test = async () => { try { const index = await retryFetch('http://127.0.0.1:3000/') if ((await index.text()) !== 'Hi') throw new Error('Index: Result not match') if (!index.headers.get('Content-Type')?.includes('text/plain')) throw new Error('Index: Content-Type not match') const query = await retryFetch('http://127.0.0.1:3000/id/1?name=bun') if ((await query.text()) !== '1 bun') throw new Error('Query: Result not match') if (!query.headers.get('Content-Type')?.includes('text/plain')) throw new Error('Query: Content-Type not match') if (!query.headers.get('X-Powered-By')?.includes('benchmark')) throw new Error('Query: X-Powered-By not match') const body = await retryFetch('http://127.0.0.1:3000/json', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) }) if ((await body.text()) !== JSON.stringify({ hello: 'world' })) throw new Error('Body: Result not match') if (!body.headers.get('Content-Type')?.includes('application/json')) throw new Error('Body: Content-Type not match') } catch (error) { throw error } } const spawn = (target: string, title = true) => { let [runtime, framework, index] = target.split('/') as [ keyof typeof runtimeCommand, string, string ] if (index) framework += '/index' const name = framework.replace('/index', '') if (title) { console.log('\n', name) console.log(' >', runtime, framework, '\n') } let cmd: string[] if (runtime === 'c') { // For C, run the pre-built bazel binary directly const binaryPath = getBazelBinaryPath(framework) cmd = [binaryPath] } else { const file = existsSync(`./src/${runtime}/${framework}.ts`) ? `src/${runtime}/${framework}.ts` : `src/${runtime}/${framework}.js` cmd = [...runtimeCommand[runtime].split(" "), file] } const server = Bun.spawn({ cmd, env: { ...Bun.env, NODE_ENV: 'production' } }) return async () => { await server.kill() await sleep(0.3) try { await fetch('http://127.0.0.1:3000') await sleep(0.6) await fetch('http://127.0.0.1:3000') await killPort(3000) } catch { // Empty } } } try { if (lstatSync('results').isDirectory()) rimraf.sync('results') } catch {} await Bun.$`rm -rf ./results` mkdirSync('results') writeFileSync('results/results.md', '') const resultFile = Bun.file('results/results.md') const result = resultFile.writer() const main = async () => { try { await fetch('http://127.0.0.1:3000') await killPort(3000) } catch { // Empty } const runtimes = <string[]>[] let frameworks = readdirSync('src') .flatMap((runtime) => { if (!lstatSync(`src/${runtime}`).isDirectory()) return if (!existsSync(`results/${runtime}`)) mkdirSync(`results/${runtime}`) return readdirSync(`src/${runtime}`) .filter( (a) => a.endsWith('.ts') || a.endsWith('.js') || a.endsWith('.c') || !a.includes('.') ) .map((a) => a.includes('.') ? `${runtime}/` + a.replace(/\.(j|t|c)s?$/, '') : `${runtime}/${a}/index` ) .filter( (a) => !blacklists.includes(a as (typeof blacklists)[number]) ) }) .filter((x) => x) .sort() // Overwrite test here frameworks = whitelists?.length ? whitelists : frameworks console.log(`${frameworks.length} frameworks`) for (const framework of frameworks) console.log(`- ${framework}`) console.log('\nTest:') for (const target of frameworks) { const kill = spawn(target!, false) let [runtime, framework] = target!.split('/') await sleep(0.1) if (runtimes.includes(runtime)) { const folder = `results/${runtime}` if (!lstatSync(folder).isDirectory()) rimraf(folder) } try { const kill = await test() console.log(`✅ ${framework} (${runtime})`) } catch (error) { console.log(`❌ ${framework} (${runtime})`) console.log(' ', (error as Error)?.message || error) frameworks.splice(frameworks.indexOf(target!), 1) } finally { await kill() } } const estimateTime = frameworks.length * (commands.length * time + 1) console.log() console.log(`${frameworks.length} frameworks`) for (const framework of frameworks) console.log(`- ${framework}`) console.log(`\nEstimate time: ${secToMin(estimateTime)} min`) // process.exit() result.write( ` | Framework | Runtime | Average | Ping | Query | Body | | ---------------- | ------- | ------- | ---------- | ---------- | ---------- | ` ) for (const target of frameworks) { const kill = spawn(target!) let [runtime, framework, index] = target!.split('/') as [ keyof typeof runtimeCommand, string, string ] const name = framework.replace('/index', '') const frameworkResultFile = Bun.file(`results/${runtime}/${name}.txt`) const frameworkResult = frameworkResultFile.writer() result.write(`| ${name} | ${runtime} `) // Wait .3 second for server to bootup await sleep(0.4) let content = '' const total = [] for (const command of commands) { frameworkResult.write(`${command}\n`) console.log(command) const res = await Bun.spawn({ cmd: command.split(' '), env: Bun.env }) const stdout = await new Response(res.stdout).text() console.log(stdout) const results = catchNumber.exec(stdout) if (!results?.[1]) continue content += `| ${format(results[1])} ` total.push(toNumber(results[1])) frameworkResult.write(results + '\n') } content = `| ${format( total.reduce((a, b) => +a + +b, 0) / commands.length )} ` + content + '|\n' result.write(content) await result.flush() await kill() } } const toNumber = (a: string) => +a.replaceAll(',', '') const arrange = () => { const table = readFileSync('results/results.md', { encoding: 'utf-8' }) const orders = [] const [title, divider, ...rows] = table.split('\n') for (const row of rows) { const data = row .replace(/\ /g, '') .split('|') .filter((a) => a) if (data.length !== commands.length + 3) continue const [name, runtime, total] = data orders.push({ name, runtime, total: toNumber(total), row }) } const content = [ title, divider, ...orders.sort((a, b) => b.total - a.total).map((a) => a.row) ].join('\n') console.log(content) writeFileSync('results/results.md', content) process.exit(0) } process.on('beforeExit', async () => { await killPort(3000) }) main().then(arrange)