From dd961d21332b921be07eb8ffa9018a3a9ecbc5f6 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 19 Apr 2026 12:12:59 -0400 Subject: [PATCH] lumbda-www: asm-gc static file server for lumbda.com + caddy race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Makefile | 5 ++ asm/lumbda-gc | Bin 59944 -> 60024 bytes asm/lumbda-gc.o | Bin 62568 -> 62688 bytes asm/lumbda.s | 24 ++++++- examples/http-static-server.lsp | 124 ++++++++++++++++++++++++++++++++ tests/bench-lumbda-www.sh | 105 +++++++++++++++++++++++++++ tests/bench-www-race.sh | 108 ++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 examples/http-static-server.lsp create mode 100755 tests/bench-lumbda-www.sh create mode 100755 tests/bench-www-race.sh diff --git a/Makefile b/Makefile index 306235f..c7fc847 100644 --- a/Makefile +++ b/Makefile @@ -138,6 +138,11 @@ bench-gc-adaptive: asm-build bench-gc-http: asm-build @bash tests/bench-gc-http.sh +bench-lumbda-www: asm-build + @bash tests/bench-lumbda-www.sh + +bench-www-race: asm-build + @bash tests/bench-www-race.sh bench-all: bench c-bench bench-3way bench-portal bench-portal-cross bench-web bench-rpc-chain bench-proof @echo "═══════════════════════════════════════════════════════════" @echo "All benchmarks complete. Numbers in the whitepaper §6.4," diff --git a/asm/lumbda-gc b/asm/lumbda-gc index cfaf410422cdc36d5547b589e62e6c80de5b7568..8bdd07024cb2e229f4572cd99b40b2b1619db369 100755 GIT binary patch delta 15042 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 delta 14891 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

57KL(#l{0I2(Jaa$i;YGx+ z3HKMTJ@eP=CyGrljw^cL&Bjbr>qtNUsk_(z^9tvPeW(4MnI|@f^4jW1@n1`t|GLlj g;!vZpMVyxK%fCL_MBQh|-8ddr(_QJwvuk4i44*#H0l diff --git a/asm/lumbda-gc.o b/asm/lumbda-gc.o index e07506e902dc7d960c1991e7c56920355588f0e3..94f751684f7a626b122a91f83b5f17970820a7df 100644 GIT binary patch delta 15700 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 delta 15591 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 diff --git a/asm/lumbda.s b/asm/lumbda.s index c9b9761..777d306 100644 --- a/asm/lumbda.s +++ b/asm/lumbda.s @@ -788,9 +788,27 @@ heap_alloc: movq %r15, gc_free_list(%rip) addq %rax, %r15 # %r15 now == %r13 .ha_grow_no_pad: + # Chunk size: at least HEAP_SIZE, but large enough for this one + # block. A single big allocation (e.g. file->string on a ~3 MB + # PDF) would otherwise loop forever because each mmap'd 1 MB + # chunk still doesn't fit. Round up (8+%rbx) to HEAP_SIZE + # multiples so small allocs still land in standard-sized chunks. + leaq 8(%rbx), %rcx # required bytes + cmpq $HEAP_SIZE, %rcx + jbe .ha_grow_std + # Oversize: pad up to HEAP_SIZE alignment and use that. + addq $(HEAP_SIZE - 1), %rcx + movq $HEAP_SIZE, %rdx + negq %rdx # rdx = -HEAP_SIZE (low bits = 0-HEAP_SIZE mask) + andq %rdx, %rcx + jmp .ha_grow_do_mmap +.ha_grow_std: + movq $HEAP_SIZE, %rcx +.ha_grow_do_mmap: + movq %rcx, %r12 # stash size across the syscall movq $SYS_MMAP, %rax xorq %rdi, %rdi - movq $HEAP_SIZE, %rsi + movq %r12, %rsi # chunk size movq $3, %rdx movq $0x22, %r10 movq $-1, %r8 @@ -799,7 +817,9 @@ heap_alloc: cmpq $-1, %rax je die_oom movq %rax, %r15 - leaq HEAP_SIZE(%rax), %r13 + addq %r12, %rax # chunk end + movq %rax, %r13 + movq %r15, %rax # restore ptr (we'll use below) movq gc_chunk_count(%rip), %rdx cmpq $GC_MAX_CHUNKS, %rdx jae die_oom diff --git a/examples/http-static-server.lsp b/examples/http-static-server.lsp new file mode 100644 index 0000000..4b4d186 --- /dev/null +++ b/examples/http-static-server.lsp @@ -0,0 +1,124 @@ +;;; http-static-server.lsp — serve lumbda.com's docroot from pure Scheme. +;;; +;;; Runs in any tier that has file->string + the tcp-* family: Python, +;;; C, and both asm builds. The asm-gc build is the target deployment +;;; (~27 KB binary, bounded memory via heap-snapshot per request). +;;; +;;; ./asm/lumbda-gc < examples/http-static-server.lsp +;;; +;;; Serves GET requests under *docroot*. Default docroot is "www" — +;;; run from the repo root so the relative path resolves. + +(define *port* 8080) +(define *docroot* "www") +(define *max-requests* 100000) + +(define *crlf* "\r\n") +(define *crlf-crlf* "\r\n\r\n") + +;;; ── HTTP helpers ──────────────────────────────────────────── + +(define (http-response status ctype body) + (string-append + "HTTP/1.0 " status *crlf* + "Content-Type: " ctype *crlf* + "Content-Length: " (number->string (string-length body)) *crlf* + "Connection: close" *crlf-crlf* + body)) + +(define SPACE 32) +(define DOT 46) +(define (char-at s i) (char->integer (string-ref s i))) + +;;; Pull the URL path out of the request line "GET /path HTTP/1.0\r\n..." +(define (second-token s) + (let ((len (string-length s))) + (let loop1 ((i 0)) + (cond + ((= i len) "/") + ((= (char-at s i) SPACE) + (let loop2 ((j (+ i 1))) + (cond + ((= j len) (substring s (+ i 1) len)) + ((= (char-at s j) SPACE) (substring s (+ i 1) j)) + (else (loop2 (+ j 1)))))) + (else (loop1 (+ i 1))))))) + +;;; ── Path safety ───────────────────────────────────────────── + +;;; Reject any path containing ".." — keeps requests under docroot. +(define (has-dotdot? s) + (let ((n (string-length s))) + (let loop ((i 0)) + (cond + ((>= (+ i 1) n) #f) + ((and (= (char-at s i) DOT) (= (char-at s (+ i 1)) DOT)) #t) + (else (loop (+ i 1))))))) + +;;; ── MIME dispatch ─────────────────────────────────────────── + +(define (ends-with? s suffix) + (let ((ns (string-length s)) (nf (string-length suffix))) + (if (< ns nf) #f + (string=? suffix (substring s (- ns nf) ns))))) + +(define (mime-of path) + (cond + ((ends-with? path ".html") "text/html; charset=utf-8") + ((ends-with? path ".css") "text/css; charset=utf-8") + ((ends-with? path ".pdf") "application/pdf") + ((ends-with? path ".txt") "text/plain; charset=utf-8") + ((ends-with? path ".js") "application/javascript") + ((ends-with? path ".png") "image/png") + ((ends-with? path ".jpg") "image/jpeg") + ((ends-with? path ".svg") "image/svg+xml") + ((ends-with? path ".ico") "image/x-icon") + (else "application/octet-stream"))) + +;;; ── File resolution ───────────────────────────────────────── + +(define (resolve-fs-path url-path) + (cond + ((string=? url-path "/") (string-append *docroot* "/index.html")) + (else (string-append *docroot* url-path)))) + +(define (serve-file url-path) + (cond + ((has-dotdot? url-path) + (http-response "403 Forbidden" "text/plain" "forbidden\n")) + (else + (let ((fs-path (resolve-fs-path url-path))) + (let ((body (file->string fs-path))) + (if body + (http-response "200 OK" (mime-of fs-path) body) + (let ((nf (file->string (string-append *docroot* "/404.html")))) + (http-response "404 Not Found" "text/html; charset=utf-8" + (if nf nf "not found\n"))))))))) + +(define (handle-request req) + (let ((path (second-token req))) + (serve-file path))) + +;;; ── Main loop ─────────────────────────────────────────────── + +(define server (tcp-listen *port*)) + +(define (server-loop n snap) + (if (>= n *max-requests*) + (begin + (display "request cap reached, exiting\n") + (tcp-close server)) + (begin + (let ((client (tcp-accept server))) + (let ((req (tcp-recv client 4096))) + (if (and req (> (string-length req) 0)) + (tcp-send client (handle-request req)) + #f)) + (tcp-close client)) + ;; Rewind per-request allocations on asm; no-op on Python/C. + (heap-restore snap) + (server-loop (+ n 1) snap)))) + +(display "lumbda-www on :") (display *port*) +(display " serving ") (display *docroot*) (newline) +(server-loop 0 (heap-snapshot)) diff --git a/tests/bench-lumbda-www.sh b/tests/bench-lumbda-www.sh new file mode 100755 index 0000000..86f03c8 --- /dev/null +++ b/tests/bench-lumbda-www.sh @@ -0,0 +1,105 @@ +#!/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 diff --git a/tests/bench-www-race.sh b/tests/bench-www-race.sh new file mode 100755 index 0000000..c1a1104 --- /dev/null +++ b/tests/bench-www-race.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# bench-www-race.sh — asm-gc lumbda-www vs caddy file-server on the +# same lumbda.com docroot. Small-request (index.html) and large- +# request (2.67 MB PDF) workloads, identical concurrency, adjacent +# runs. Reports req/s, MiB/s, and peak RSS for each server. +set -u +cd "$(dirname "$0")/.." + +SMALL_N=${SMALL_N:-2000} +LARGE_N=${LARGE_N:-200} +CONCURRENCY=${CONCURRENCY:-8} +LUMBDA_PORT=${LUMBDA_PORT:-8080} +CADDY_PORT=${CADDY_PORT:-8081} +CADDY_BIN=${CADDY_BIN:-/home/fox/git/make_post_sell/caddy} + +declare -a SPAWNED=() +cleanup() { + for pid in "${SPAWNED[@]}"; do kill -9 "$pid" 2>/dev/null; done + sleep 0.2 + pkill -9 -u "$USER" -f 'http-static-server' 2>/dev/null || true + pkill -9 -u "$USER" -f 'caddy file-server' 2>/dev/null || true +} +trap cleanup EXIT INT TERM + +make -s -C asm all + +printf "\n═══════════════════════════════════════════════════════\n" +printf "asm-gc lumbda-www vs caddy — same docroot, same workload\n" +printf " %s small (/), %s large (/whitepaper.pdf 2.67 MiB)\n" "$SMALL_N" "$LARGE_N" +printf " concurrency %s\n" "$CONCURRENCY" +printf "═══════════════════════════════════════════════════════\n" + +# ── start both servers ── +./asm/lumbda-gc < examples/http-static-server.lsp >/dev/null 2>&1 & +LUMBDA_PID=$! +SPAWNED+=("$LUMBDA_PID") + +"$CADDY_BIN" file-server --root www --listen ":$CADDY_PORT" >/dev/null 2>&1 & +CADDY_PID=$! +SPAWNED+=("$CADDY_PID") + +# Wait for both to be ready +for _ in $(seq 1 30); do + a=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$LUMBDA_PORT/" 2>/dev/null || echo 0) + b=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$CADDY_PORT/" 2>/dev/null || echo 0) + [ "$a" = "200" ] && [ "$b" = "200" ] && break + sleep 0.1 +done +kill -0 "$LUMBDA_PID" 2>/dev/null || { echo "lumbda-www failed to start"; exit 1; } +kill -0 "$CADDY_PID" 2>/dev/null || { echo "caddy failed to start"; exit 1; } + +# ── Byte-integrity check (both ways) ── +curl -s "http://localhost:$LUMBDA_PORT/whitepaper.pdf" -o /tmp/a-lumbda.pdf +curl -s "http://localhost:$CADDY_PORT/whitepaper.pdf" -o /tmp/a-caddy.pdf +echo -n " pdf-integrity (lumbda): " +cmp /tmp/a-lumbda.pdf whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1 && echo "byte-identical" || echo "DIFFERS" +echo -n " pdf-integrity (caddy): " +cmp /tmp/a-caddy.pdf whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1 && echo "byte-identical" || echo "DIFFERS" +rm -f /tmp/a-lumbda.pdf /tmp/a-caddy.pdf + +# ── benchmark one (pid, port, label, N, path) ── +bench_one() { + local pid="$1" port="$2" label="$3" n="$4" path="$5" + local peak=0 rss t0 t1 + t0=$(date +%s.%N) + ( seq 1 "$n" | xargs -P "$CONCURRENCY" -I_ \ + curl -s -o /dev/null "http://localhost:$port$path" ) & + local 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) + local rps=$(python3 -c " +t = float('$t1') - float('$t0') +print(f'{$n / max(t, 1e-6):.0f}') +") + local mibs=0 + if [ "$path" = "/whitepaper.pdf" ]; then + mibs=$(python3 -c " +t = float('$t1') - float('$t0') +print(f'{$n * 2669058 / (1024*1024) / max(t, 1e-6):.0f}') +") + printf " %-20s %6s req/s %4s MiB/s peak_rss_kb=%s\n" "$label" "$rps" "$mibs" "$peak" + else + printf " %-20s %6s req/s peak_rss_kb=%s\n" "$label" "$rps" "$peak" + fi +} + +echo +echo "── small (/) ──" +bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www (asm-gc)" "$SMALL_N" "/" +bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$SMALL_N" "/" + +echo +echo "── large (/whitepaper.pdf, 2.67 MiB) ──" +bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www (asm-gc)" "$LARGE_N" "/whitepaper.pdf" +bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$LARGE_N" "/whitepaper.pdf" + +echo +echo "── binary sizes ──" +printf " lumbda-gc %10s bytes stripped\n" "$(du -b asm/lumbda-gc | cut -f1)" +printf " caddy %10s bytes (Go, v2.5.1, static)\n" "$(du -b "$CADDY_BIN" | cut -f1)" + +kill -9 "$LUMBDA_PID" "$CADDY_PID" 2>/dev/null +wait "$LUMBDA_PID" "$CADDY_PID" 2>/dev/null || true