Skip to content

Commit c4a00ba

Browse files
committed
add h3
1 parent 08b5301 commit c4a00ba

74 files changed

Lines changed: 1666 additions & 47 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frameworks/aspnet-minimal/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ COPY . .
44
RUN dotnet publish -c Release -o out
55

66
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview-alpine
7+
RUN apk add --no-cache libmsquic
78
WORKDIR /app
89
COPY --from=build /app/out .
9-
EXPOSE 8080 8443
10+
EXPOSE 8080 8443/tcp 8443/udp
1011
ENTRYPOINT ["dotnet", "aspnet-minimal.dll"]

frameworks/aspnet-minimal/Program.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@
1717
lo.Protocols = HttpProtocols.Http1;
1818
});
1919

20-
// HTTPS + HTTP/2 on port 8443
20+
// HTTPS + HTTP/2 + HTTP/3 on port 8443
2121
if (hasCert)
2222
{
2323
options.ListenAnyIP(8443, lo =>
2424
{
25-
lo.Protocols = HttpProtocols.Http2;
25+
lo.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
2626
lo.UseHttps(X509Certificate2.CreateFromPemFile(certPath, keyPath));
2727
});
28-
29-
}
28+
}
3029
});
3130

3231
var app = builder.Build();

frameworks/aspnet-minimal/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"description": "Minimal ASP.NET Core server using .NET 10 preview with Kestrel and minimal API routing.",
77
"repo": "https://github.com/dotnet/aspnetcore",
88
"enabled": true,
9-
"tests": ["baseline", "pipelined", "limited-conn", "json", "baseline-h2", "static-h2"]
9+
"tests": ["baseline", "pipelined", "limited-conn", "json", "baseline-h2", "static-h2", "baseline-h3", "static-h3"]
1010
}

frameworks/caddy/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ RUN xcaddy build v2.9.1 --with httparena-handler=/app
77
FROM debian:bookworm-slim
88
COPY --from=build /app/caddy /usr/local/bin/caddy
99
COPY Caddyfile /etc/caddy/Caddyfile
10-
EXPOSE 8080 8443
10+
EXPOSE 8080 8443/tcp 8443/udp
1111
ENV GOGC=400
1212
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]

frameworks/caddy/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"description": "Caddy web server with a custom handler module for benchmark endpoints, native HTTP/2 support.",
77
"repo": "https://github.com/caddyserver/caddy",
88
"enabled": true,
9-
"tests": ["baseline", "limited-conn", "json", "baseline-h2", "static-h2"]
9+
"tests": ["baseline", "limited-conn", "json", "baseline-h2", "static-h2", "baseline-h3", "static-h3"]
1010
}

frameworks/h2o-h3/Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM buildpack-deps:bookworm AS build
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends \
4+
cmake clang libssl-dev zlib1g-dev libbrotli-dev git bison ruby
5+
6+
# Build h2o from source with HTTP/3 (quicly + picotls submodules) and mruby
7+
WORKDIR /tmp/h2o
8+
RUN git clone --recurse-submodules --depth 1 https://github.com/h2o/h2o.git . && \
9+
cmake -B build \
10+
-DCMAKE_BUILD_TYPE=Release \
11+
-DCMAKE_C_COMPILER=clang \
12+
-DCMAKE_C_FLAGS="-flto=auto -march=native -mtune=native" \
13+
-DWITH_MRUBY=on \
14+
-S . && \
15+
cmake --build build -j && \
16+
cmake --install build
17+
18+
FROM debian:bookworm-slim
19+
RUN apt-get update && apt-get install -y --no-install-recommends \
20+
libssl3 jq && \
21+
rm -rf /var/lib/apt/lists/*
22+
COPY --from=build /usr/local/bin/h2o /usr/local/bin/h2o
23+
COPY --from=build /usr/local/share/h2o /usr/local/share/h2o
24+
COPY entrypoint.sh /entrypoint.sh
25+
RUN chmod +x /entrypoint.sh
26+
EXPOSE 8080 8443/tcp 8443/udp
27+
CMD ["/entrypoint.sh"]

frameworks/h2o-h3/entrypoint.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/sh
2+
set -e
3+
4+
NPROC=$(nproc)
5+
6+
# Preprocess dataset.json → response.json (add total field, wrap in {items,count})
7+
if [ -f /data/dataset.json ]; then
8+
jq '{ items: [.[] | . + { total: ((.price * .quantity * 100 | round) / 100) }], count: length }' \
9+
/data/dataset.json > /tmp/response.json
10+
fi
11+
12+
# Generate h2o.conf
13+
cat > /tmp/h2o.conf << EOF
14+
num-threads: ${NPROC}
15+
16+
listen:
17+
port: 8080
18+
19+
listen: &ssl_listen
20+
port: 8443
21+
ssl:
22+
certificate-file: /certs/server.crt
23+
key-file: /certs/server.key
24+
25+
listen:
26+
<<: *ssl_listen
27+
type: quic
28+
29+
hosts:
30+
default:
31+
paths:
32+
"/pipeline":
33+
mruby.handler: |
34+
Proc.new { [200, {"content-type" => "text/plain"}, ["ok"]] }
35+
36+
"/baseline11":
37+
mruby.handler: |
38+
Proc.new do |env|
39+
sum = 0
40+
qs = env["QUERY_STRING"]
41+
if qs
42+
qs.split("&").each do |pair|
43+
_k, v = pair.split("=", 2)
44+
sum += v.to_i if v
45+
end
46+
end
47+
if env["REQUEST_METHOD"] == "POST"
48+
body = env["rack.input"] ? env["rack.input"].read : ""
49+
body = body.strip
50+
sum += body.to_i if body.length > 0
51+
end
52+
[200, {"content-type" => "text/plain"}, [sum.to_s]]
53+
end
54+
55+
"/baseline2":
56+
mruby.handler: |
57+
Proc.new do |env|
58+
sum = 0
59+
qs = env["QUERY_STRING"]
60+
if qs
61+
qs.split("&").each do |pair|
62+
_k, v = pair.split("=", 2)
63+
sum += v.to_i if v
64+
end
65+
end
66+
[200, {"content-type" => "text/plain"}, [sum.to_s]]
67+
end
68+
69+
"/json":
70+
file.file: /tmp/response.json
71+
header.add: "content-type: application/json"
72+
73+
"/static":
74+
file.dir: /data/static
75+
EOF
76+
77+
exec h2o -c /tmp/h2o.conf

frameworks/h2o-h3/meta.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"display_name": "h2o-h3",
3+
"language": "C",
4+
"type": "realistic",
5+
"engine": "h2o",
6+
"description": "h2o config-based server with mruby handlers and native HTTP/3 (QUIC) support.",
7+
"repo": "https://github.com/h2o/h2o",
8+
"enabled": true,
9+
"tests": ["baseline", "pipelined", "limited-conn", "json", "baseline-h2", "static-h2", "baseline-h3", "static-h3"]
10+
}

scripts/benchmark.sh

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ cd "$ROOT_DIR"
77

88
GCANNON="${GCANNON:-/home/diogo/Desktop/Socket/gcannon/gcannon}"
99
H2LOAD="${H2LOAD:-h2load}"
10+
OHA="${OHA:-oha}"
1011
HARD_NOFILE=$(ulimit -Hn)
1112
ulimit -n "$HARD_NOFILE"
1213
THREADS=12
@@ -19,16 +20,18 @@ RESULTS_DIR="$ROOT_DIR/results"
1920
CERTS_DIR="$ROOT_DIR/certs"
2021

2122
# Profile definitions: pipeline|req_per_conn|cpu_limit|connections|endpoint
22-
# endpoint: empty = /baseline11 (raw), "json" = /json (GET), "pipeline" = /pipeline, "h2" = /baseline2 (h2load), "static-h2" = multi-URI h2load
23+
# endpoint: empty = /baseline11 (raw), "json" = /json (GET), "pipeline" = /pipeline, "h2" = /baseline2 (h2load), "static-h2" = multi-URI h2load, "h3" = /baseline2 (oha HTTP/3)
2324
declare -A PROFILES=(
2425
[baseline]="1|0||512,4096,16384|"
2526
[pipelined]="16|0||512,4096,16384|pipeline"
2627
[limited-conn]="1|10||512,4096|"
2728
[json]="1|0||4096,16384,32768|json"
2829
[baseline-h2]="1|0||64,256,1024|h2"
2930
[static-h2]="1|0||64,256,1024|static-h2"
31+
[baseline-h3]="128|0||64,512|h3"
32+
[static-h3]="128|0||64,512|static-h3"
3033
)
31-
PROFILE_ORDER=(baseline pipelined limited-conn json baseline-h2 static-h2)
34+
PROFILE_ORDER=(baseline pipelined limited-conn json baseline-h2 static-h2 baseline-h3 static-h3)
3235

3336
# Parse flags
3437
SAVE_RESULTS=false
@@ -267,7 +270,9 @@ for profile in "${profiles_to_run[@]}"; do
267270

268271
# Wait for server
269272
echo "[wait] Waiting for server..."
270-
if [ "$endpoint" = "h2" ] || [ "$endpoint" = "static-h2" ]; then
273+
if [ "$endpoint" = "h3" ] || [ "$endpoint" = "static-h3" ]; then
274+
local_check_url="https://localhost:$H2PORT/baseline2?a=1&b=1"
275+
elif [ "$endpoint" = "h2" ] || [ "$endpoint" = "static-h2" ]; then
271276
local_check_url="https://localhost:$H2PORT/static/reset.css"
272277
[ "$endpoint" = "h2" ] && local_check_url="https://localhost:$H2PORT/baseline2?a=1&b=1"
273278
elif [ "$endpoint" = "json" ]; then
@@ -289,7 +294,25 @@ for profile in "${profiles_to_run[@]}"; do
289294

290295
# Build load generator args based on profile endpoint
291296
USE_H2LOAD=false
292-
if [ "$endpoint" = "static-h2" ]; then
297+
USE_OHA=false
298+
if [ "$endpoint" = "static-h3" ]; then
299+
USE_OHA=true
300+
oha_out=$(mktemp)
301+
gc_args=("$OHA"
302+
"$REQUESTS_DIR/static-h2-uris.txt"
303+
--urls-from-file
304+
--http-version 3 --insecure
305+
-o "$oha_out" --output-format json
306+
-c "$CONNS" -p "$pipeline" -z "$DURATION")
307+
elif [ "$endpoint" = "h3" ]; then
308+
USE_OHA=true
309+
oha_out=$(mktemp)
310+
gc_args=("$OHA"
311+
"https://localhost:$H2PORT/baseline2?a=1&b=1"
312+
--http-version 3 --insecure
313+
-o "$oha_out" --output-format json
314+
-c "$CONNS" -p "$pipeline" -z "$DURATION")
315+
elif [ "$endpoint" = "static-h2" ]; then
293316
USE_H2LOAD=true
294317
gc_args=("$H2LOAD"
295318
-i "$REQUESTS_DIR/static-h2-uris.txt"
@@ -330,7 +353,11 @@ for profile in "${profiles_to_run[@]}"; do
330353
done &
331354
stats_pid=$!
332355

333-
if [ "$USE_H2LOAD" = "true" ]; then
356+
if [ "$USE_OHA" = "true" ]; then
357+
"${gc_args[@]}" || true
358+
output=$(cat "$oha_out" 2>/dev/null)
359+
rm -f "$oha_out"
360+
elif [ "$USE_H2LOAD" = "true" ]; then
334361
output=$(timeout 45 "${gc_args[@]}" 2>&1) || true
335362
else
336363
output=$(timeout 45 "$GCANNON" "${gc_args[@]}" 2>&1) || true
@@ -345,7 +372,11 @@ for profile in "${profiles_to_run[@]}"; do
345372
echo "$output"
346373
echo " CPU: $avg_cpu | Mem: $peak_mem"
347374

348-
if [ "$USE_H2LOAD" = "true" ]; then
375+
if [ "$USE_OHA" = "true" ]; then
376+
# oha JSON: .summary.requestsPerSec
377+
rps_int=$(echo "$output" | python3 -c "import sys,json; d=json.load(sys.stdin); print(int(d['summary']['requestsPerSec']))" 2>/dev/null || echo "0")
378+
rps_int=${rps_int:-0}
379+
elif [ "$USE_H2LOAD" = "true" ]; then
349380
# h2load: "finished in 5.00s, 123456.78 req/s, 45.67MB/s"
350381
rps_int=$(echo "$output" | grep -oP 'finished in [\d.]+s, \K[\d.]+' | cut -d. -f1 || echo "0")
351382
rps_int=${rps_int:-0}
@@ -370,7 +401,12 @@ for profile in "${profiles_to_run[@]}"; do
370401
echo "=== Best: ${best_rps} req/s (CPU: $best_cpu, Mem: $best_mem) ==="
371402

372403
# Extract metrics
373-
if [ "$USE_H2LOAD" = "true" ]; then
404+
if [ "$USE_OHA" = "true" ]; then
405+
# oha JSON: .summary.average (seconds), .latencyPercentiles.p99 (seconds)
406+
avg_lat=$(echo "$best_output" | python3 -c "import sys,json; d=json.load(sys.stdin); v=d['summary']['average']; print(f'{v*1e6:.0f}us' if v<0.001 else f'{v*1000:.2f}ms')" 2>/dev/null || echo "")
407+
p99_lat=$(echo "$best_output" | python3 -c "import sys,json; d=json.load(sys.stdin); v=d['latencyPercentiles']['p99']; print(f'{v*1e6:.0f}us' if v<0.001 else f'{v*1000:.2f}ms')" 2>/dev/null || echo "")
408+
reconnects="0"
409+
elif [ "$USE_H2LOAD" = "true" ]; then
374410
# h2load: "time for request: min max mean sd +/-sd" all on one line
375411
avg_lat=$(echo "$best_output" | awk '/time for request:/{print $6}')
376412
p99_lat="$avg_lat" # h2load doesn't report p99; use mean as placeholder

site/content/docs/scoring/composite-score.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Not all profiles count toward the composite score. Profiles marked as **scored**
4242
| JSON | HTTP/1.1 | Yes | Dataset processing and serialization |
4343
| Baseline | HTTP/2 | Yes | Query parsing over TLS with multiplexed streams |
4444
| Static | HTTP/2 | Yes | 20 static files served over TLS with multiplexed streams |
45+
| Baseline | HTTP/3 | Yes | Query parsing over QUIC (UDP) with TLS 1.3 |
46+
| Static | HTTP/3 | Yes | 20 static files served over QUIC (UDP) with TLS 1.3 |
4547

4648
Pipelined is reference-only because not all frameworks support HTTP pipelining.
4749

0 commit comments

Comments
 (0)