initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/build/
/h3blast
*.o
+42
View File
@@ -0,0 +1,42 @@
# h3blast — links against Bun's already-built vendored deps so we don't
# rebuild lsquic/BoringSSL/HdrHistogram. Run `bun run build:release` once
# in the repo root first so the .o files exist.
ROOT := ../..
PROFILE ?= release
OBJ := $(ROOT)/build/$(PROFILE)/obj/vendor
VENDOR := $(ROOT)/vendor
CC ?= clang
CFLAGS := -std=c11 -O3 -g -Wall -Wextra -Wno-unused-parameter \
-I$(VENDOR)/lsquic/include \
-I$(VENDOR)/hdrhistogram/include \
-I$(VENDOR)/boringssl/include
LDFLAGS := -lstdc++ -lm -lpthread
DEPS := lsquic lsqpack lshpack boringssl hdrhistogram zlib
DEP_DIRS := $(addprefix $(OBJ)/,$(DEPS))
LIBDEPS := build/libdeps.a
BIN := h3blast
all: $(BIN)
$(LIBDEPS):
@if [ ! -d "$(OBJ)/lsquic" ]; then \
echo "error: no dep objects found under $(OBJ)"; \
echo " run 'bun run build:$(PROFILE)' in the repo root first"; \
exit 1; \
fi
@mkdir -p build
@echo " AR $@"
@find $(DEP_DIRS) -name '*.o' | xargs ar rcs $@
$(BIN): src/h3blast.c $(LIBDEPS)
@echo " CC $@"
@$(CC) $(CFLAGS) -o $@ $< $(LIBDEPS) $(LDFLAGS)
clean:
rm -f $(BIN) $(LIBDEPS)
.PHONY: all clean
+90
View File
@@ -0,0 +1,90 @@
# h3blast
A small, very fast HTTP/3 load generator built directly on **lsquic**. It exists
to stress `Bun.serve({ http3: true })` without going through curl or a Node QUIC
shim — same lsquic, same BoringSSL, same packet path as the server side.
```
h3blast · HTTP/3 8 threads × 8 connections × 32 streams
────────────────────────────────────────────────────────────────
static 406,114 req/s
GET localhost:3001/hi
4,061,140 requests in 10.00s 66.12 B/req · p99 1.10ms
js 172,600 req/s
GET localhost:3001/
1,726,000 requests in 10.00s 46.06 B/req · p99 2.44ms
```
The bytes/req figure is wire bytes received (QUIC payload — headers + body +
framing). Pass `-v` for per-target request totals, body-only bytes, total
transfer, and the full latency-percentile / status-code charts.
## Design
One process, **N worker threads**. Each worker owns one non-blocking UDP
socket, one lsquic client engine, and `connections / threads` QUIC connections.
Each connection keeps `--streams` HTTP/3 request streams in flight at all
times. UDP I/O is batched with `sendmmsg`/`recvmmsg`; the lsquic timer wheel
is driven via `epoll` + `timerfd`. The main thread samples per-worker atomic
counters at ~10 Hz to render the live TUI, then aggregates per-worker
HdrHistograms for the final percentile report.
No TLS verification by default (it's a load tester).
## Build
h3blast links the **already-compiled** lsquic / BoringSSL / HdrHistogram / zlib
object files from Bun's build tree, so you need a Bun release build first:
```sh
cd ../.. # repo root
bun run build:release # populates build/release/obj/vendor/**
cd packages/h3blast
make # → ./h3blast
```
`PROFILE=debug make` links against the debug objects instead.
## Usage
```sh
./h3blast -t 4 -c 8 -m 64 -d 30 https://127.0.0.1:3000/
./h3blast -X POST -H 'content-type: application/json' -b '{"a":1}' https://host/api
./h3blast --json -d 5 https://host/ | jq '.[0].req_per_sec'
# compare two servers side-by-side — each positional is [label=]url
./h3blast -c 4 -m 32 -d 10 bun=https://127.0.0.1:3443/ node=https://127.0.0.1:3444/
```
When multiple URLs are given they are benchmarked **sequentially** (same
`-t`/`-c`/`-m`/`-d` each) so they don't compete for client CPU. The live TUI
shows finished targets above the active one; the final report has one labelled
block per target with average response-body size. The full latency-percentile
and status-code charts are only printed with `-v/--verbose`; the default report
shows just `body B/req` and `p50/p99`.
| flag | meaning |
| -------------------------- | --------------------------------------------- |
| `-t, --threads N` | worker threads |
| `-c, --connections N` | total QUIC connections (split across threads) |
| `-m, --streams N` | concurrent request streams per connection |
| `-d, --duration SEC` | run length (default 10) |
| `-n, --requests N` | stop after N total responses |
| `-X, --method M` | HTTP method |
| `-H 'k: v'` | extra request header (repeatable) |
| `-b STR` / `--body-file P` | request body |
| `--warmup SEC` | ignore stats from the first SEC seconds |
| `--json` | machine-readable summary (array of targets) |
| `-v, --verbose` | full latency/status charts in final report |
| `--no-color`, `-q` | disable color / live UI |
| `H3BLAST_DEBUG=debug` | turn on lsquic's internal logger |
## Against a local Bun H3 server
```sh
bun test-server.js 3443 &
./h3blast -t 2 -c 4 -m 32 -d 10 https://127.0.0.1:3443/
```
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
// Minimal Bun HTTP/3 hello-world target for h3blast.
// Usage: bun test-server.js [port]
import { spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const dir = mkdtempSync(join(tmpdir(), "h3blast-srv-"));
const certPath = join(dir, "cert.pem");
const keyPath = join(dir, "key.pem");
spawnSync(
"openssl",
[
"req",
"-x509",
"-nodes",
"-newkey",
"rsa:2048",
"-days",
"365",
"-subj",
"/CN=localhost",
"-keyout",
keyPath,
"-out",
certPath,
],
{ stdio: "ignore" },
);
const server = Bun.serve({
port: Number(process.argv[2] ?? process.env.PORT ?? 3443),
http3: true,
http1: false,
tls: { cert: readFileSync(certPath, "utf8"), key: readFileSync(keyPath, "utf8") },
fetch() {
return new Response("Hello, World!");
},
});
console.error("listening", String(server.url));
setInterval(() => {}, 1 << 30);