Skip to content

Commit 7ca4f08

Browse files
authored
Merge pull request #11 from MDA2AV/bun
bun
2 parents a6c522e + 3ef331c commit 7ca4f08

5 files changed

Lines changed: 217 additions & 6 deletions

File tree

frameworks/bun/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM oven/bun:latest
2+
WORKDIR /app
3+
COPY server.ts entrypoint.sh ./
4+
RUN chmod +x entrypoint.sh
5+
EXPOSE 8080 8443
6+
CMD ["/app/entrypoint.sh"]

frameworks/bun/entrypoint.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
CPUS=$(nproc)
3+
for i in $(seq 1 "$CPUS"); do
4+
bun run /app/server.ts &
5+
done
6+
wait

frameworks/bun/meta.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"display_name": "bun",
3+
"language": "TS",
4+
"type": "realistic",
5+
"engine": "JavaScriptCore",
6+
"description": "Bun's built-in HTTP server using JavaScriptCore engine with multi-core clustering.",
7+
"repo": "https://github.com/oven-sh/bun",
8+
"enabled": true,
9+
"tests": [
10+
"baseline",
11+
"pipelined",
12+
"limited-conn",
13+
"json",
14+
"upload",
15+
"compression",
16+
"noisy",
17+
"baseline-h2",
18+
"static-h2"
19+
]
20+
}

frameworks/bun/server.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
const zlib = require("zlib");
2+
const fs = require("fs");
3+
4+
const MIME_TYPES: Record<string, string> = {
5+
".css": "text/css", ".js": "application/javascript", ".html": "text/html",
6+
".woff2": "font/woff2", ".svg": "image/svg+xml", ".webp": "image/webp", ".json": "application/json",
7+
};
8+
9+
// Pre-load dataset
10+
const data = JSON.parse(fs.readFileSync("/data/dataset.json", "utf8"));
11+
const items = data.map((d: any) => ({
12+
id: d.id, name: d.name, category: d.category,
13+
price: d.price, quantity: d.quantity, active: d.active,
14+
tags: d.tags, rating: d.rating,
15+
total: Math.round(d.price * d.quantity * 100) / 100,
16+
}));
17+
const jsonResponseBuf = Buffer.from(JSON.stringify({ items, count: items.length }));
18+
19+
// Pre-load large dataset for /compression endpoint (compressed per-request)
20+
const largeData = JSON.parse(fs.readFileSync("/data/dataset-large.json", "utf8"));
21+
const largeItems = largeData.map((d: any) => ({
22+
id: d.id, name: d.name, category: d.category,
23+
price: d.price, quantity: d.quantity, active: d.active,
24+
tags: d.tags, rating: d.rating,
25+
total: Math.round(d.price * d.quantity * 100) / 100,
26+
}));
27+
const largeJsonBuf = Buffer.from(JSON.stringify({ items: largeItems, count: largeItems.length }));
28+
29+
// Pre-load static files
30+
const staticFiles: Record<string, { buf: Buffer; ct: string }> = {};
31+
try {
32+
for (const name of fs.readdirSync("/data/static")) {
33+
const buf = fs.readFileSync(`/data/static/${name}`);
34+
const ext = name.slice(name.lastIndexOf("."));
35+
staticFiles[name] = { buf: Buffer.from(buf), ct: MIME_TYPES[ext] || "application/octet-stream" };
36+
}
37+
} catch (_) {}
38+
39+
function sumQuery(url: string): number {
40+
const q = url.indexOf("?");
41+
if (q === -1) return 0;
42+
let sum = 0;
43+
const qs = url.slice(q + 1);
44+
let i = 0;
45+
while (i < qs.length) {
46+
const eq = qs.indexOf("=", i);
47+
if (eq === -1) break;
48+
let amp = qs.indexOf("&", eq);
49+
if (amp === -1) amp = qs.length;
50+
const n = parseInt(qs.slice(eq + 1, amp), 10);
51+
if (!isNaN(n)) sum += n;
52+
i = amp + 1;
53+
}
54+
return sum;
55+
}
56+
57+
function handleRequest(req: Request): Response | Promise<Response> {
58+
const url = req.url;
59+
const protoEnd = url.indexOf("//") + 2;
60+
const pathStart = url.indexOf("/", protoEnd);
61+
const qIdx = url.indexOf("?", pathStart);
62+
const path = qIdx === -1 ? url.slice(pathStart) : url.slice(pathStart, qIdx);
63+
64+
if (path === "/pipeline") {
65+
return new Response("ok", { headers: { "content-type": "text/plain" } });
66+
}
67+
68+
if (path === "/json") {
69+
return new Response(jsonResponseBuf, {
70+
headers: { "content-type": "application/json", "content-length": String(jsonResponseBuf.length) },
71+
});
72+
}
73+
74+
if (path === "/compression") {
75+
const compressed = Bun.gzipSync(largeJsonBuf, { level: 1 });
76+
return new Response(compressed, {
77+
headers: {
78+
"content-type": "application/json",
79+
"content-encoding": "gzip",
80+
"content-length": String(compressed.length),
81+
},
82+
});
83+
}
84+
85+
if (path === "/baseline2") {
86+
const body = String(sumQuery(url));
87+
return new Response(body, { headers: { "content-type": "text/plain" } });
88+
}
89+
90+
if (path.startsWith("/static/")) {
91+
const name = path.slice(8);
92+
const sf = staticFiles[name];
93+
if (sf) {
94+
return new Response(sf.buf, {
95+
headers: { "content-type": sf.ct, "content-length": String(sf.buf.length) },
96+
});
97+
}
98+
return new Response("Not found", { status: 404 });
99+
}
100+
101+
if (path === "/upload" && req.method === "POST") {
102+
return req.arrayBuffer().then((ab) => {
103+
const buf = Buffer.from(ab);
104+
const c = zlib.crc32(buf);
105+
return new Response((c >>> 0).toString(16).padStart(8, "0"), {
106+
headers: { "content-type": "text/plain" },
107+
});
108+
});
109+
}
110+
111+
// /baseline11 — GET or POST
112+
const querySum = sumQuery(url);
113+
if (req.method === "POST") {
114+
return req.text().then((body) => {
115+
let total = querySum;
116+
const n = parseInt(body.trim(), 10);
117+
if (!isNaN(n)) total += n;
118+
return new Response(String(total), { headers: { "content-type": "text/plain" } });
119+
});
120+
}
121+
122+
return new Response(String(querySum), { headers: { "content-type": "text/plain" } });
123+
}
124+
125+
// Read TLS certs
126+
let tlsOptions: { cert: string; key: string } | undefined;
127+
try {
128+
tlsOptions = {
129+
cert: fs.readFileSync("/certs/server.crt", "utf8"),
130+
key: fs.readFileSync("/certs/server.key", "utf8"),
131+
};
132+
} catch (_) {}
133+
134+
// HTTP server on port 8080
135+
Bun.serve({
136+
port: 8080,
137+
fetch: handleRequest,
138+
reusePort: true,
139+
});
140+
141+
// HTTPS/H2 server on port 8443
142+
if (tlsOptions) {
143+
Bun.serve({
144+
port: 8443,
145+
tls: tlsOptions,
146+
fetch: handleRequest,
147+
reusePort: true,
148+
});
149+
}

scripts/benchmark.sh

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,45 @@ rebuild_site_data() {
8282
[ -d "$conn_dir" ] || continue
8383
local conns=$(basename "$conn_dir")
8484
local data_file="$site_data/${profile}-${conns}.json"
85-
echo '[' > "$data_file"
86-
local first=true
85+
86+
# Collect new framework names from results
87+
local new_fws=""
8788
for f in "$conn_dir"/*.json; do
8889
[ -f "$f" ] || continue
89-
$first || echo ',' >> "$data_file"
90-
cat "$f" >> "$data_file"
91-
first=false
90+
local fw_name=$(basename "$f" .json)
91+
new_fws="$new_fws $fw_name"
9292
done
93-
echo ']' >> "$data_file"
93+
94+
# Merge: keep existing entries for frameworks NOT in new results, then add new ones
95+
python3 -c "
96+
import json, sys, os, glob
97+
98+
data_file = sys.argv[1]
99+
conn_dir = sys.argv[2]
100+
new_fws = set(sys.argv[3].split())
101+
102+
# Load existing data
103+
existing = []
104+
if os.path.exists(data_file):
105+
try:
106+
existing = json.load(open(data_file))
107+
except:
108+
existing = []
109+
110+
# Remove entries for frameworks being updated
111+
merged = [e for e in existing if e.get('framework') not in new_fws]
112+
113+
# Add new results
114+
for f in sorted(glob.glob(os.path.join(conn_dir, '*.json'))):
115+
try:
116+
merged.append(json.load(open(f)))
117+
except:
118+
pass
119+
120+
with open(data_file, 'w') as out:
121+
json.dump(merged, out, indent=2)
122+
" "$data_file" "$conn_dir" "$new_fws"
123+
94124
echo "[updated] site/data/${profile}-${conns}.json"
95125
done
96126
done

0 commit comments

Comments
 (0)