Ships examples/http-static-server.lsp — ~65 lines of portable Scheme
that reads files from a docroot (default ./www) and serves them over
HTTP/1.0 with MIME dispatch, path-traversal rejection, heap-snapshot
per request. Runs in any tier; target deployment is asm-gc for the
27 KB stripped binary + bounded memory backstop.
Required one asm fix first: heap_grow was mmap'ing fixed HEAP_SIZE
chunks, so any single allocation larger than a chunk (notably the
2.67 MB whitepaper PDF read via file->string) loop-looped through
.ha_overflow forever. Now heap_grow rounds required bytes up to
HEAP_SIZE multiples on oversize alloc, so a big request carves its
own big chunk in one go. Small allocs still land in standard-sized
chunks.
Two new benches:
tests/bench-lumbda-www.sh — drive N small + M large requests against
asm-gc, verify PDF round-trip, sample peak RSS. At 1000/100: 331 req/s
small, 120 req/s large (304 MiB/s), peak 15.5 MB.
tests/bench-www-race.sh — adjacent A/B vs caddy v2.5.1 on the same
docroot. Numbers on this laptop, concurrency 8, 2000 small + 200 large:
small req/s PDF req/s PDF MiB/s peak RSS binary
lumbda-www (asm-gc) 375 137 349 7–16 MB 27 KB
caddy file-server 358 231 588 38 MB 38 MB
Reading: lumbda edges caddy on small files (less per-request overhead),
caddy wins 1.7x on large files (sendfile zero-copy; we allocate the
whole file into a string and write it with one syscall). Both byte-
identical on the PDF. Memory: lumbda 2.5-5x less at steady state.
Binary size: 1400x smaller (27 KB vs 38 MB).
Feature gap: caddy has HTTPS, HTTP/2, range, middleware, etc. lumbda
has none of that yet — but for the specific job of serving lumbda.com's
six-file docroot it is viable right now.
Makefile adds `bench-lumbda-www` and `bench-www-race` targets.
137 asm no-GC + 137 asm GC tests still pass.
105 lines
3.9 KiB
Bash
Executable file
105 lines
3.9 KiB
Bash
Executable file
#!/bin/bash
|
|
# bench-lumbda-www.sh — serve lumbda.com's docroot from asm-gc
|
|
# and bombard it with small-file + large-file requests. Verifies:
|
|
# (a) throughput is viable for real traffic,
|
|
# (b) peak RSS stays bounded under mixed payloads (2.67 MB PDF
|
|
# per request would otherwise blow up without heap-restore),
|
|
# (c) PDF bytes round-trip identically across thousands of
|
|
# requests (no truncation, no partial writes).
|
|
set -u
|
|
cd "$(dirname "$0")/.."
|
|
|
|
SMALL_N=${SMALL_N:-5000}
|
|
LARGE_N=${LARGE_N:-500}
|
|
CONCURRENCY=${CONCURRENCY:-8}
|
|
BIN=${BIN:-./asm/lumbda-gc}
|
|
|
|
declare -a SPAWNED=()
|
|
cleanup() {
|
|
for pid in "${SPAWNED[@]}"; do kill -9 "$pid" 2>/dev/null; done
|
|
sleep 0.2
|
|
pkill -9 -u "$USER" -f 'examples/http-static-server' 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
make -s -C asm all
|
|
|
|
ulimit -v 524288
|
|
|
|
printf "\n═══════════════════════════════════════════════════════\n"
|
|
printf "lumbda.com under asm-gc: %s small (/) + %s large (/whitepaper.pdf)\n" "$SMALL_N" "$LARGE_N"
|
|
printf " concurrency %s\n" "$CONCURRENCY"
|
|
printf "═══════════════════════════════════════════════════════\n"
|
|
|
|
"$BIN" < examples/http-static-server.lsp >/dev/null 2>&1 &
|
|
pid=$!
|
|
SPAWNED+=("$pid")
|
|
|
|
# Wait for port
|
|
for _ in $(seq 1 30); do
|
|
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/ 2>/dev/null | grep -q 200; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
if ! kill -0 "$pid" 2>/dev/null; then
|
|
echo "server failed to start"; exit 1
|
|
fi
|
|
|
|
# Verify once that the PDF round-trips byte-identically.
|
|
curl -s -o /tmp/bench-pdf-check http://localhost:8080/whitepaper.pdf
|
|
if cmp /tmp/bench-pdf-check whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1; then
|
|
echo " pdf-integrity: byte-identical ($(wc -c < /tmp/bench-pdf-check) bytes)"
|
|
else
|
|
echo " pdf-integrity: DIFFERS"; exit 1
|
|
fi
|
|
rm -f /tmp/bench-pdf-check
|
|
|
|
# Baseline RSS
|
|
rss0=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status)
|
|
echo " baseline_rss_kb=$rss0"
|
|
|
|
# Drive small-file traffic, sample RSS while it runs.
|
|
peak=0
|
|
t0=$(date +%s.%N)
|
|
( seq 1 "$SMALL_N" | xargs -P "$CONCURRENCY" -I_ \
|
|
curl -s -o /dev/null http://localhost:8080/ ) &
|
|
curl_pid=$!
|
|
while kill -0 "$curl_pid" 2>/dev/null; do
|
|
rss=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
|
[ "$rss" -gt "$peak" ] && peak=$rss
|
|
sleep 0.2
|
|
done
|
|
wait "$curl_pid" 2>/dev/null || true
|
|
t1=$(date +%s.%N)
|
|
small_rps=$(python3 -c "print(f'{$SMALL_N / ((float(\"$t1\") - float(\"$t0\")) if float(\"$t1\") > float(\"$t0\") else 1):.0f}')")
|
|
printf " small (/) %s req/s peak_rss_kb=%s\n" "$small_rps" "$peak"
|
|
|
|
# Reset peak for large-file phase
|
|
peak=0
|
|
t0=$(date +%s.%N)
|
|
( seq 1 "$LARGE_N" | xargs -P "$CONCURRENCY" -I_ \
|
|
curl -s -o /dev/null http://localhost:8080/whitepaper.pdf ) &
|
|
curl_pid=$!
|
|
while kill -0 "$curl_pid" 2>/dev/null; do
|
|
rss=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
|
[ "$rss" -gt "$peak" ] && peak=$rss
|
|
sleep 0.2
|
|
done
|
|
wait "$curl_pid" 2>/dev/null || true
|
|
t1=$(date +%s.%N)
|
|
large_rps=$(python3 -c "print(f'{$LARGE_N / ((float(\"$t1\") - float(\"$t0\")) if float(\"$t1\") > float(\"$t0\") else 1):.0f}')")
|
|
large_mib=$(python3 -c "print(f'{$LARGE_N * 2669058 / (1024 * 1024) / ((float(\"$t1\") - float(\"$t0\")) if float(\"$t1\") > float(\"$t0\") else 1):.0f}')")
|
|
printf " large (/pdf 2.67M) %s req/s %s MiB/s peak_rss_kb=%s\n" "$large_rps" "$large_mib" "$peak"
|
|
|
|
# Final RSS before kill
|
|
final=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
|
growth=$((final - rss0))
|
|
printf " final_rss_kb=%s growth_kb=%s\n" "$final" "$growth"
|
|
|
|
kill -9 "$pid" 2>/dev/null
|
|
wait "$pid" 2>/dev/null || true
|
|
|
|
if pgrep -u "$USER" -f 'examples/http-static-server' > /dev/null; then
|
|
echo "STRAGGLER" >&2; exit 1
|
|
fi
|