Mercurial
view benchmark/bun-http-framework-benchmark/bench.ts @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| 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)