Skip to content

echo-100k: replace upload with a 100 KB TLS echo - #1382

Merged
MDA2AV merged 32 commits into
mainfrom
profiles/in-out-echo
Aug 30, 2026
Merged

echo-100k: replace upload with a 100 KB TLS echo#1382
MDA2AV merged 32 commits into
mainfrom
profiles/in-out-echo

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Aug 29, 2026

Copy link
Copy Markdown
Owner

[skip-maintainer-ping]

Replaces the upload profile with echo-100k: a 100 KB body posted over TLS and returned verbatim, so ingest and egress load at once.

Why upload had to go

It measured ingest alone with bodies up to 20 MB, and had stopped discriminating:

entry rps @256c ingest
humming-bird 3,156 25.0 GB/s
vibe.d 3,104 24.6 GB/s
actix 3,062 24.3 GB/s
go-stdlib 2,936 23.3 GB/s

A 7% spread across 99 entries spanning D, Rust, Go, Ruby and C++ — what it looks like when a benchmark measures memcpy and the loopback rather than the server.

The profile

POST /echo · TLS :8081 · 100 KB · Content-Length · conns 32/256 · wrk · reference-only

100 KB in + 100 KB out is 200 KB per request, which leaves the bandwidth ceiling far enough away that per-request framework overhead is still visible, while being ~7 TLS records and more than one socket buffer — so partial reads, multi-record handling and partial writes all happen every request.

The endpoint is /echo, not /echo-100k, so a later profile can drive it at another size without a second route.

Content-Length is the generator's constraint, not a preference. wrk frames the body itself and always emits Content-Length; adding Transfer-Encoding produces a request carrying both, which RFC 9112 §6.1 makes an error — wrk rejects it. Verified, not assumed. Chunked moved into validation, where it is mandatory.

Validation is byte-exact, and closes a hole the old profile had

Every upload check sent a body with an accurate Content-Length and compared the returned count — so a handler that echoed that header without reading a byte passed the entire suite. echo-100k compares bytes: 1 B, 1 KB and 100 KB random bodies, a chunked 100 KB body that cannot be answered from a header, and an empty body.

Every probe is pinned to --http1.1, and that is load-bearing: :8081 advertises ALPN h2, and an echo that is broken under HTTP/1.1 can pass under h2 (see below). wrk speaks only HTTP/1.1.

The generator rotates eight distinct 100 KB bodies so a canned response of the right size is wrong seven times in eight.

106 entries implemented

Every flagship and emerging entry that can serve TLS. Seventeen are unsubscribed rather than given a listener — robyn has no TLS support at all, the WebFramework family opens a single listener so it cannot serve 8080 and 8081 together, and rage, sanic, veb and the rest simply have no second listener. Five more (araara ×2, hical, iris, typev) build from sources this repo does not contain, or are hand-written servers whose buffers are smaller than the payload.

Five review agents, and they found real defects

Not polish — bugs that would have shipped:

  • 4 Go entries were broken. I streamed the body back using the request's Content-Length; net/http drains and closes the request body once response headers flush with unread body left (maxPostHandlerReadBytes = 256 KB), so the response went short under a promised length and tore the connection. The old 20 MB body was over that threshold, which is why the same shape worked before.
  • 2 entries did not parse — stray }); in hono-bun and hono-node.
  • nestjs corrupted the body — Nest's express adapter JSON-serialises a returned Buffer: 102,400 bytes out as 365,887 bytes of {"type":"Buffer","data":[…]}.
  • bottle returned an empty 200 on chunked — gunicorn de-chunks but leaves HTTP_TRANSFER_ENCODING set, so bottle re-parsed the framing, raised 400, and a bare except swallowed it.
  • genhttp-kestrel did not build — pinned to GenHTTP 10.5.1, where the Method enum does not exist.
  • 4 entries had /echo on the wrong listener (fulmine ×2, elysia, swoole, workerman) — 404 on the TLS port the profile drives.
  • 2 had a duplicate Content-Type (roda, rage) that byte comparison would never catch.

Isolation

No regressions to other endpoints. ioxide is the only entry where the change reached shared code — its chunked decoder is also what /baseline11 parses its integer body from — so every ioxide endpoint was re-run after: baseline GET, POST with Content-Length, POST chunked, pipeline, json, json+br, static, static+gzip, delay, async-db, 404, Connection: close, and a pipelined batch. All correct.

I also swept all changed sources for cross-endpoint edits, which caught 8 files that had silently lost their CRLF line endings (rewriting three genhttp Project.cs files and both servicestack files whole). Restored.

Smoke tested

15 entries started and probed live, 5/5 checks each, zero failures — all five probes over HTTP/1.1, byte-compared:

  • Go — chi, echo, fiber, gin, go-fasthttp, go-stdlib
  • C# — aspnet-minimal, carter, sisk, simplew, genhttp-11, fastendpoints
  • Rust — axum, ntex, actix
  • ioxide — plus the full endpoint regression above

The audit agent separately live-tested express, fastify, koa, node, node-h3, bun, elysia, hono-bun, hono-node, aiohttp, blackbull, django, fastapi, flask, litestar, sanic, starlette, robyn, uvicorn and fastpysgi-asgi.

Not exercised by anyone — worth watching in this run

PHP, Ruby, JVM and the exotic tail (V, Zig, D, Lua, Luau, Perl, Julia, Swift, Elixir, Erlang, F#, C++) rest on source review plus this PR's validation. Two I would watch hardest:

  • slimeweb — its response object exposes only plain/html/json plus set_header; there is no binary responder and none take a content type. If that build coerces to str, binary will not round-trip.
  • lute — could not confirm the framework de-chunks at all, so the chunked probe is the one likely to fail there.

Verification

validate_profiles 26/26, bash -n clean on every touched script, node scripts/check_badge_parity.jsbadge parity ok — 637 ranks match. Changing scripts/validate.sh means this PR's validation covers every enabled entry, not only the ones touched.

MDA2AV added 14 commits August 29, 2026 17:21
upload measured ingest alone, with bodies up to 20 MB, and had stopped
discriminating: 3,156 rps for humming-bird against 2,936 for go-stdlib,
a 7% spread across 99 entries spanning D, Rust, Go, Ruby and C++. At 8 MB
average bodies it was measuring memcpy and the loopback, not servers.

in-out loads both directions at once. 100 KB up over TLS, the same 100 KB
back, so every request moves 200 KB through the read path, the write path
and the TLS record layer in both directions. 100 KB rather than more
because in+out is already 200 KB per request, which leaves the box's
bandwidth ceiling far enough away that per-request framework overhead is
still visible - and it is ~7 TLS records and more than one socket buffer,
so partial reads, multi-record handling and partial writes all happen on
every request.

The endpoint is POST /echo rather than /in-out, so a later profile can
drive it at a different size or framing without a second route.

Content-Length, and that is the generator's constraint rather than a
preference: wrk frames the body itself and always emits Content-Length,
so adding Transfer-Encoding produces a request carrying both, which
RFC 9112 6.1 makes an error - wrk rejects it outright. Verified rather
than assumed. Chunked moves into validation, where it is mandatory.

Validation is byte-exact throughout and closes a hole the old profile
had. Every upload check sent a body with an accurate Content-Length and
compared the returned count, so a handler that echoed that header without
reading a byte passed all of them. in-out compares the bytes: 1B, 1KB and
100KB random bodies, a chunked 100KB body that cannot be answered from a
header, and an empty body.

The generator rotates eight distinct 100 KB bodies rather than repeating
one, so a canned response of the right size is wrong seven times in eight.
Verified end to end against an echo server: 24,965 rps and all eight
bodies observed in even proportion.

wrk reports only the bytes it read, so its Transfer/sec is the download
half of the echo. benchmark.sh reconstructs the ingest half from
rps x 102400; without it the profile would publish half the I/O it moves.

The 198 published upload rows are removed rather than carried over - they
measure a different workload - along with the log directory and the four
upload fixtures, which had no other user. The 128 entries subscribed to
upload are switched to in-out.

Entries do not implement POST /echo yet; that follows in this branch.
Reference-only, as upload was.

validate_profiles passes at 26/26 and badge parity matches at 637.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
chi, echo, fiber, gin, go-fasthttp and go-stdlib. The /upload route and
its byte-count handler are replaced rather than kept beside the new one -
no profile drives /upload any more.

Where the framework exposes the request as a stream (net/http, echo, gin)
the body is streamed straight back with Content-Length taken from the
request, so nothing buffers the whole 100 KB. Where it has already been
read into a buffer by the server (fiber, fasthttp) the echo is that
buffer.

Every one handles the chunked case explicitly: with no Content-Length the
response cannot be framed until the body has been read, so those paths
read first and then write. validate.sh sends a chunked 100 KB body and
compares byte for byte, so this is exercised rather than assumed.

All six compile.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
Rust: actix, axum, mq-bridge, ntex, rocket, salvo, trillium,
trillium-tuned. JS: express, fastify, fulmine, fulmine-tuned,
hyper-express, koa, node, node-h3, ultimate-express. TS: bananabread,
bun, deno, elysia, hono-bun, hono-node, nestjs.

Where the framework has already collected the body (axum's Bytes, ntex's
Bytes, fasthttp's buffer) the echo is that buffer handed straight back at
no extra copy. Where the body arrives as a stream it is collected before
the response is written, which is deliberate rather than lazy: the
response cannot carry a Content-Length until the length is known, and a
chunked request has none to forward. validate.sh sends a chunked 100 KB
body, so the streaming-through shortcut would fail it.

The Web-standard runtimes (bun, deno, elysia, hono) use arrayBuffer(),
which reads to end regardless of framing and gives the Response its
Content-Length for free.

/upload and its byte-count handler are replaced rather than left beside
the new route - no profile drives it any more - and the three stale
comments that explained why a JSON body parser was mounted per-route
rather than globally now say /echo.

Verified: axum, actix, ntex and rocket type-check clean. salvo,
mq-bridge and both trilliums fail on this box for reasons unrelated to
the change - a dependency wanting a different rustc, and trillium-http
using unstable features - so they rest on the harness build.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
aiohttp, bjoern, blackbull, bottle, django, fastapi, fastpysgi-asgi,
fastpysgi-wsgi, flask, litestar, mq-bridge-py, pyronova, robyn, sanic,
slimeweb, socketify, starlette, uvicorn.

Every one collects the body before replying rather than piping it
through. That is the deliberate choice: the response cannot carry a
Content-Length until the length is known, and a chunked request has none
to forward. validate.sh posts a chunked 100 KB body and compares byte for
byte, so streaming-through would fail it.

Two entries needed more than the mechanical change. pyronova's upload
handler used stream.drain_count(), which counts in Rust with the GIL
released once and never materialises the bytes - fast for a byte count
and useless for an echo, so it collects the chunks instead. bjoern's
text_resp() hardcodes text/plain and takes no content type, so the echo
calls make_resp() directly rather than gaining a parameter no other
caller wants.

slimeweb was checked rather than guessed: its response object has no
raw() method. The native module exports body/bytes/file/header/html/
json/plain/send_bytes/send_text/status, and content_type is a recognised
keyword, so the echo uses resp.bytes(body, content_type=...).

All eighteen files compile.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
The litestar handler was renamed but its route_handlers list still named
upload, which would not have imported. pyronova's max_body_size comment
still explained itself in terms of a 20 MiB upload template that no
longer exists.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
aspnet-minimal, aspnet-minimal-ioxide, carter, effinitive, fastendpoints,
the four genhttp variants, servicestack, simplew, simplew-tuned and sisk.

Three of these needed the framework's API checked rather than guessed,
and two of the guesses would have been wrong:

  - SimpleW has no raw/binary response helper by that name. Reflecting
    over the package shows HttpResponse.Body(Byte[], String) and that
    HttpRequest.Body is a ReadOnlySequence<byte>, so the echo is
    Response.Body(Request.Body.ToArray(), ...) - no text conversion,
    which would have corrupted a binary payload.
  - Effinitive's HttpRequest exposes ReadBodyAsync(ct) alongside the
    CountBodyBytesAsync the old handler used; the endpoint is now
    NoRequestEndpointBase<byte[]>.
  - sisk's ByteArrayContent has no four-argument constructor. The build
    caught it; the content type is set on the header instead.

genhttp returns a Stream rather than a byte[] so GenHTTP stays on its raw
response path instead of serializing, and the new type is named Echo to
avoid colliding with the existing EchoHandler, which is the WebSocket one.

Every one of these builds.

Two C# entries are not in this commit. ioxide counts upload bytes inside
its hand-written parser as they arrive and never buffers them, so an echo
is a real change to a hot path rather than a handler swap.
web-framework-csharp depends on WebFrameworkCSharpAPI, which is not in
the local package cache, and its SetBody is only ever called with a
string - echoing binary through one would corrupt it, so it needs the
package checked first.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
PHP: frankenphp-trueasync, hyperf, laravel, php, slim, swoole,
symfony-spawn-franken, symfony-spawn-tas, true-async-server, workerman.
Ruby: h2o-mruby, hanami, rage, rails, roda, sinatra.

Most were a one-line swap from a length to the bytes, because these
frameworks had already read the body to measure it. Two were not:

  - true-async-server used a streaming fast path that deliberately never
    materialises the body, counting chunks as they arrive. An echo needs
    the bytes, so the chunks are collected instead of discarded.
  - the plain php entry read $_SERVER['CONTENT_LENGTH'] and never touched
    the body at all - the exact shortcut the old profile could not catch.
    It now reads php://input, which reads to end regardless of framing.

hanami's action moves from Upload to Echo as its own file, since actions
there are one class per file and routed by name.

No php or ruby toolchain on this box, so these rest on the harness build
and on validate.sh's byte-exact checks.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
Java: helidon-production, helidon-tuned, jooby, micronaut, quarkus-jvm,
spring-boot, vertx. Kotlin: fishcake, http4k, ktor, ktor-ghost.
Scala: http4s, zio-http. Clojure: aleph, http-kit, pedestal, reitit,
ring-http-exchange, ring-jetty-adapter, ring-jetty9-adapter.

Almost all of these counted the body by transferring it to a null sink -
transferTo(OutputStream.nullOutputStream()) in Java and Clojure, a fold
over the chunk stream in http4s and zio-http, a Publisher subscriber
accumulating a length in micronaut. None of them kept the bytes, so an
echo is a real rewrite of the handler rather than a return-value change.
Each now collects and returns the body, which is also what makes a
chunked request work: the response cannot be framed until the length is
known.

Renames that follow the frameworks' conventions: helidon's UploadHandler
becomes EchoHandler, spring-boot's UploadController becomes
EchoController, fishcake's Upload service becomes Echo, and the two
mapped ring adapters gain an echo-response beside the count-stream-bytes
they no longer call.

vertx needed an io.vertx.core.buffer.Buffer import that the counting
version did not; micronaut swaps AtomicLong for ByteArrayOutputStream and
drops the now-dead import.

No maven, kotlinc, scala or clojure toolchain on this box - only gradle
and javac, and these are maven projects - so these rest on the harness
build and on validate.sh's byte-exact checks.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
…rue-async-server

Route renames the earlier batches missed: the ktor and ktor-ghost tests
still POSTed /upload and asserted a byte count (they now assert the bytes
come back), rails' MarkUploadAsBinary middleware keyed on PATH_INFO
'/upload', ring-jetty-adapter's 405 test named the old path, and
true-async-server's own validate.sh checked for a count.

web-framework-python and web-framework-csharp are the mirrored
executor-per-file entries: the Upload executor is replaced by an Echo one
and both registrations (executors/web.json and server/config.json) are
repointed.

Note on web-framework-csharp: the C++ sibling's getBody() returns a
std::string, which is binary-safe, so its echo is exact. The C# API is
not in the local package cache and GetHttpBody() is almost certainly a
UTF-16 string, which is NOT binary-safe - a random-byte body would not
survive it. The entry is disabled and in the orphan production tier, so
nothing is published from it, but this needs the package checked before
it is enabled.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
ioxide parses HTTP by hand, and its upload path was built for the profile
it is replacing: a POST /upload was detected before the body was read,
then counted as it streamed and dropped, so memory stayed bounded across
20 MB bodies. Nothing kept the bytes, so an echo could not be bolted on.

The streaming counter is gone - PendingUploadRemaining, the drain branch
in Feed(), the Pump() guard and FinishUpload() with it. At 100 KB there
is nothing to stream around, and the body has to be buffered to be
echoed anyway.

The chunked decoder needed the larger change. It kept only a 256-byte
peek, because its one caller wanted an integer for /baseline11 and a
length for the byte count. It now also de-chunks into a per-connection
buffer, so /echo has the bytes; the peek stays for the integer parse. It
stops being static to reach that buffer.

Verified end to end against a running server: 1 B, 1 KB and 100 KB
Content-Length bodies and a chunked 100 KB body all come back
byte-for-byte identical to random input, an empty body answers 200, and
both the plain and chunked /baseline11 paths still return the right sum -
that last one being the check that the decoder change did not break the
caller it was written for.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
sark's upload endpoint bound its body as #[stream_body] BodyLen, a type
that carries only a length - deliberately, for a profile that wanted a
byte count off a 20 MB body. The echo needs the bytes, so it binds
#[raw_body] LocalFrameBytes the way baseline_post and crud_create already
do, and copies into an Owned the way the crud cache-hit path does. The
BodyLen import goes with it.

araara and araara-standard are unsubscribed from in-out rather than
converted, because they cannot be converted from this repository: both
build the server from the upstream `hcs` opam package with
`opam source`, so the /upload handler is upstream code with no local
copy. araara-standard applies a patch to that source, but araara has no
patch mechanism at all, and writing a route rename blind against a file
this repo does not contain would be a patch that fails to apply.

Both are experimental and disabled, so nothing is published from either
and no flagship or emerging entry is affected. They can resubscribe once
upstream serves /echo.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
…ngines

warp (flagship, Haskell) and fletch (emerging, Dart) are the two enabled
entries the earlier sweeps missed - warp because its routes are pattern
matches on a path list rather than a string literal, fletch because its
handler lives under bin/ rather than src/.

warp's countBody streamed and summed chunk lengths, so it kept nothing;
it becomes readBody, which concatenates, and a new `octets` response
builder frames the result with its own Content-Length beside the existing
`plain` one. fletch collects the chunks and answers with res.bytes.

hical, iris and typev are unsubscribed rather than converted.

hical and iris have no source in this repository at all - their
Dockerfiles `git clone` the server at build time - so there is nothing
here to change, exactly like araara.

typev does have source, and this is a judgement call rather than an
impossibility: it is a hand-written epoll server whose upload path
stream-drains the body without buffering, and whose BUFCAP and OBUFCAP
are both 65536 - smaller than the 100 KB this profile echoes. Making it
work means growing the buffers and reworking the drain into a
read-then-write across both the Content-Length and the chunked paths, in
Type-C, with no compiler on this box to catch a mistake. It is an engine
entry and disabled, so the cost of guessing wrong outweighs the value.

All three are disabled and none is flagship or emerging, so no scored
entry is affected. Subscriptions: 126 -> 123.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
Renamed from in-out to echo-100k across the profile tables, the wrk
adapter and its Lua script, the readiness probe, the CATALOG and doc map,
validate.sh, the docs directory, the README and 123 meta.json files. The
endpoint stays /echo, so a later profile can drive it at another size.

FOUR GO ENTRIES WERE BROKEN and are fixed here: chi, echo, gin and
go-stdlib streamed the body back using the request's Content-Length. That
is not viable in net/http - the server drains and closes the request body
as soon as response headers flush while unread body remains
(chunkWriter.writeHeader, maxPostHandlerReadBytes = 256 KB), so io.Copy
fails mid-way and the response is short under a Content-Length already
promised, which tears the connection. The old profile's 20 MB body was
over that threshold, so the body was left open and the same code worked;
100 KB is not. All four now read the body before writing anything.
Verified against the running go-stdlib binary: 1 B, 1 KB, 100 KB and
chunked 100 KB all byte-exact over HTTP/1.1, and twenty keep-alive
requests all return exactly 102400 bytes.

validate.sh now pins --http1.1 on every echo probe, and that is
load-bearing rather than tidiness. Port 8081 advertises ALPN h2, so curl
was negotiating HTTP/2 - where Go's h2 server does no such drain and a
broken echo passes. wrk speaks only HTTP/1.1, so the profile would have
benchmarked torn responses on a green validation.

Isolation, checked rather than assumed. ioxide is the only entry where
the change reached shared code: its chunked decoder is also what
/baseline11 parses its integer body from. Every ioxide endpoint was
re-run afterwards - baseline GET, POST with Content-Length, POST chunked,
pipeline, json, json+br, static, static+gzip, delay, async-db, 404,
Connection: close and a pipelined batch - and all still answer correctly.
Helpers left orphaned by the swap are removed rather than left dangling:
nestjs's countBody and count-stream-bytes in both ring adapters.

Also restores CRLF on eight files that Python's universal newlines had
silently converted to LF, which had rewritten three genhttp Project.cs
files and servicestack whole. Those diffs are now one line each.

Minor fixes from the audit: rocket drops the now-unused sink import,
salvo stops copying 100 KB per request (Bytes clone is a refcount bump)
and answers 400 rather than 200-with-empty-body on a read failure, and 76
framework READMEs stop documenting a /upload endpoint that returns a byte
count.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
…e TLS

Five review agents went over the branch. They found real defects, not
polish, and this commit is those fixes.

Two entries did not parse at all: hono-bun and hono-node each carried a
stray `});` left by my edit, so `bun build` failed outright.

Three corrupted the body. nestjs returned a Buffer, and Nest's express
adapter does `isObject(body) ? res.json(body) : ...` - a Buffer is an
object, so 102400 bytes went out as 365887 bytes of
{"type":"Buffer","data":[...]} under an octet-stream content type. It now
writes through @res. bottle failed the chunked probe: gunicorn de-chunks
but leaves HTTP_TRANSFER_ENCODING set with no CONTENT_LENGTH, so
request.body re-parsed chunk framing and raised 400, which a bare except
turned into a 200 with an empty body; it reads wsgi.input directly now
and no longer swallows failures. genhttp-kestrel did not build - it is
pinned to GenHTTP 10.5.1, where the Method enum does not exist - so it
uses RequestMethod.Post like its siblings in the same directory. My
earlier claim that every C# entry built was wrong: twelve of thirteen did.

Two had a wrong Content-Type that byte comparison would never catch. roda
loads plain_hash_response_headers, which makes headers a case-SENSITIVE
hash, so a capitalised key did not suppress roda's lowercase text/html
default and both went out; rage's plain Hash had the same problem plus
`render plain:` overwriting it. Both now use the file's own lowercase
idiom.

Four had /echo on the wrong listener. fulmine, fulmine-tuned and elysia
registered it only on the plaintext app while :8081 got json and static;
swoole and workerman have a separate callback for :8081 entirely. The
profile drives TLS, so every one of those was a 404. The handlers are now
shared or duplicated onto the TLS side.

Scope. Seventeen entries have no way to answer on TLS :8081 - robyn has
no TLS support at all, the WebFramework family opens a single listener so
it cannot serve 8080 and 8081 together, and rage, sanic, veb and the rest
simply have no second listener. They are unsubscribed rather than given
one: adding a TLS listener to each is a different change, and several
cannot have one. 123 -> 106 subscribed.

slimeweb is implemented but flagged: its response object exposes only
plain/html/json plus set_header, with no binary responder and no content
type parameter. The header is set explicitly and the body handed to
plain(). If that build coerces to str it will not round-trip binary, and
that is the entry to watch.

Also: rocket drops an unused import, salvo stops copying 100 KB per
request and answers 400 rather than 200-with-empty-body on a read
failure, and the last /upload references in comments and READMEs are
gone from every subscribed entry.

validate_profiles passes and badge parity matches at 637.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
MDA2AV added 2 commits August 29, 2026 19:04
Fallout from #1375. When the `json` profile was removed its body check was
kept and re-gated to `json-comp || json-h2c`, but that block probes
plaintext :8080 - and an h2c-only entry has no HTTP/1.1 listener there.
actix-h2c, quarkus-jvm-h2c, vanilla-h2c, wtx-http2, zix-http2 and nginx
were all being failed for not answering on a port they never open.

Each json profile now validates on its own port and nowhere else:
json-comp on :8080, json-tls on :8081, json-h2c on :8082. json-h2c
already had a complete body check of its own, so nothing is lost by
dropping it from the plaintext gate - it was only ever being checked
twice, once impossibly.

Also closes the same anti-cheat hole in the h2c check that json-tls had:
it verified total == price * quantity * m using the response's OWN price
and quantity, so a fabricated item passed. It now diffs every field
against data/dataset.json, matching json-comp and json-tls.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
Validation on #1382 ran 149 entries: 134 passed, 14 failed. This is those
failures, and two of them were build breaks I introduced.

  http4k  Main.kt:101 "Unresolved reference 'Status'" - the file imports
          Status.Companion.OK and uses the bare OK, which is what text()
          at line 65 already does. Docker build failed outright.
  jooby   App.java:82 "cannot find symbol" - MediaType.octetstream does
          not exist in jooby. valueOf("application/octet-stream") does.

Three were wrong at runtime:

  helidon-production, helidon-tuned  404 on :8081. The default listener's
          routing does not apply to a named socket, and the h1-tls socket
          registered only the json routes, so /echo was never bound on the
          port the profile drives. Registered there too.
  hyper-express  Empty body hung the connection - uWebSockets emits
          neither 'data' nor 'end' for a zero-length body, so an
          event-driven handler never replies. Uses request.buffer().
  slimeweb  Unsubscribed. This is the risk flagged when it was written,
          and it was real: plain() rejects bytes with "argument
          'resp_obj': 'bytes' object cannot be cast as 'str'", and
          slimeweb 0.2.6 has no other responder. The framework cannot
          return a binary body unchanged. The route stays as a text echo
          so it is ready if a release adds one.

Disabled as requested: web-framework-cc, web-framework-cpp,
web-framework-python, warp, fastpysgi-asgi. ktor-ghost drops fortunes,
which is what it was failing on.

Two failures are NOT from this branch and are left alone: humming-bird
fails the json-tls TLS-quality probe by completing handshakes over
tls1/tls1_1, and beskar-websocket fails its post-test health check after
passing all six WebSocket assertions. Both are pre-existing and unrelated
to the echo endpoint.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
@MDA2AV

MDA2AV commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f ioxide -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: ioxide | Test: echo-100k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 32 0 0% 0MiB NEW NEW
echo-100k 256 74,489 6216.9% 598MiB NEW NEW
Full log
[info] available CPUs: 128
[info] framework: ioxide (ioxide, C#)
[info] subscribed tests: baseline,async,latency-1m,latency-10k,pipelined,limited-conn,json-comp,json-tls,static-tls,echo-100k,async-db,baseline-h2,static-h2,baseline-h3,static-h3
[info] building image: httparena-ioxide
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.06kB done
#1 DONE 0.0s

#2 [internal] load metadata for mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7
#2 DONE 0.5s

#3 [internal] load metadata for mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7
#3 DONE 0.5s

#4 [internal] load .dockerignore
#4 transferring context: 50B done
#4 DONE 0.0s

#5 [internal] load build context
#5 DONE 0.0s

#6 [stage-1 1/3] FROM mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7@sha256:43b356dd973fcfde5b328225386a5dc0c6e47712827889208c77fb40ebf5410a
#6 resolve mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7@sha256:43b356dd973fcfde5b328225386a5dc0c6e47712827889208c77fb40ebf5410a 0.1s done
#6 DONE 0.1s

#7 [build 1/6] FROM mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7@sha256:4ef612e0abfd775a882c404f8a7ba7acd4a7de5e90ec5c0818dc09096a09c864
#7 resolve mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7@sha256:4ef612e0abfd775a882c404f8a7ba7acd4a7de5e90ec5c0818dc09096a09c864 0.1s done
#7 DONE 0.1s

#5 [internal] load build context
#5 transferring context: 92.92kB done
#5 DONE 0.0s

#8 [build 2/6] WORKDIR /source
#8 CACHED

#9 [build 3/6] COPY ioxide-arena.csproj ./
#9 CACHED

#10 [build 4/6] RUN dotnet restore
#10 CACHED

#11 [build 5/6] COPY . .
#11 DONE 0.1s

#12 [build 6/6] RUN dotnet publish -c Release --no-self-contained -o /app/out
#12 1.384   Determining projects to restore...
#12 1.673   All projects are up-to-date for restore.
#12 1.781 /usr/share/dotnet/sdk/11.0.100-preview.7.26381.103/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.RuntimeIdentifierInference.targets(385,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [/source/ioxide-arena.csproj]
#12 3.420   ioxide-arena -> /source/bin/Release/net11.0/ioxide-arena.dll
#12 3.420   ioxide-arena -> /app/out/
#12 DONE 3.5s

#13 [stage-1 2/3] WORKDIR /app
#13 CACHED

#14 [stage-1 3/3] COPY --from=build /app/out ./
#14 DONE 0.1s

#15 exporting to image
#15 exporting layers
#15 exporting layers 0.2s done
#15 exporting manifest sha256:cffdfc86627b7b0d99959644d92dece6af9faa56b7381076f5acfd4a6ce28050 0.0s done
#15 exporting config sha256:b267efd106c1b2490917504fb3ebe8adc336076906103ecec8a9b40d9f33a49c 0.0s done
#15 exporting attestation manifest sha256:0e635815141b80bec9ba79141f43b238a29ca1b3b0a59979b7088c52265a88c0 0.0s done
#15 exporting manifest list sha256:791a9fe79ab3be4f50cc8f482944d063827a1991d5aae9d516478ceb983ff37b
#15 exporting manifest list sha256:791a9fe79ab3be4f50cc8f482944d063827a1991d5aae9d516478ceb983ff37b 0.0s done
#15 naming to docker.io/library/httparena-ioxide:latest done
#15 unpacking to docker.io/library/httparena-ioxide:latest 0.1s done
#15 DONE 0.5s
[info] zrk: docker mode (zrk:local)
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] starting postgres sidecar
[info] postgres ready (seeded)

==============================================
=== ioxide / echo-100k / 32c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

[run 2/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

[run 3/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

=== Best: 0 req/s (CPU: 0%, Mem: 0MiB) ===
[info] saved results/echo-100k/32/ioxide.json
httparena-bench-ioxide
httparena-bench-ioxide

==============================================
=== ioxide / echo-100k / 256c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.39ms    3.88ms 185.81ms   95.24%
    Req/Sec     1.14k   346.80     2.84k    66.24%
  371125 requests in 5.10s, 35.43GB read
Requests/sec:  72780.91
Transfer/sec:      6.95GB
[info] CPU 5628.2% | Mem 528MiB

[run 2/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.46ms    1.88ms  17.61ms   72.52%
    Req/Sec     1.14k   279.66     3.22k    73.70%
  373970 requests in 5.10s, 35.70GB read
Requests/sec:  73343.76
Transfer/sec:      7.00GB
[info] CPU 6476.7% | Mem 576MiB

[run 3/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.44ms    2.03ms  39.21ms   72.88%
    Req/Sec     1.15k   324.73     3.20k    70.47%
  379879 requests in 5.10s, 36.26GB read
Requests/sec:  74489.32
Transfer/sec:      7.11GB
[info] CPU 6216.9% | Mem 598MiB

=== Best: 74489 req/s (CPU: 6216.9%, Mem: 598MiB) ===
[info] input BW: 7.10GB/s (100 KB body x 74489 rps)
[info] saved results/echo-100k/256/ioxide.json
httparena-bench-ioxide
httparena-bench-ioxide
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/ioxide.json - 2 new, 24 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

@MDA2AV

MDA2AV commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f actix -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: actix | Test: echo-100k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 32 0 0% 0MiB NEW NEW
echo-100k 256 93,124 5435.6% 213MiB NEW NEW
Full log
#12 6.532    Compiling tokio v1.53.1
#12 6.766    Compiling postgres-protocol v0.6.12
#12 7.079    Compiling ring v0.17.14
#12 7.079    Compiling zstd-sys v2.0.16+zstd.1.5.7
#12 7.284    Compiling actix-router v0.5.4 (https://github.com/actix/actix-web?rev=67490f9e75d3262446449481c510e5ab36c9bcfa#67490f9e)
#12 7.503    Compiling regex v1.13.1
#12 7.652    Compiling postgres-types v0.2.14
#12 7.826    Compiling displaydoc v0.2.7
#12 7.826    Compiling futures-macro v0.3.34
#12 7.826    Compiling serde_derive v1.0.229
#12 7.827    Compiling async-trait v0.1.92
#12 8.141    Compiling futures-util v0.3.34
#12 8.271    Compiling synstructure v0.13.2
#12 8.572    Compiling zerofrom-derive v0.1.7
#12 8.572    Compiling yoke-derive v0.8.2
#12 8.572    Compiling zerovec-derive v0.11.3
#12 8.573    Compiling tracing-attributes v0.1.31
#12 8.573    Compiling actix-macros v0.2.4
#12 8.576    Compiling derive_more-impl v2.1.1
#12 8.579    Compiling actix-web-codegen v4.3.0 (https://github.com/actix/actix-web?rev=67490f9e75d3262446449481c510e5ab36c9bcfa#67490f9e)
#12 8.979    Compiling zerofrom v0.1.8
#12 9.033    Compiling yoke v0.8.3
#12 9.106    Compiling tokio-util v0.7.19
#12 9.106    Compiling actix-rt v2.11.0
#12 9.107    Compiling deadpool-runtime v0.1.4
#12 9.133    Compiling zerovec v0.11.6
#12 9.133    Compiling zerotrie v0.2.4
#12 9.165    Compiling deadpool v0.12.3
#12 9.454    Compiling serde_urlencoded v0.7.1
#12 9.458    Compiling actix-codec v0.5.2
#12 9.538    Compiling tinystr v0.8.3
#12 9.538    Compiling potential_utf v0.1.5
#12 9.594    Compiling icu_collections v2.2.0
#12 9.614    Compiling h2 v0.3.27
#12 9.615    Compiling actix-server v2.7.0
#12 9.615    Compiling tokio-postgres v0.7.18
#12 9.635    Compiling icu_locale_core v2.2.0
#12 9.652    Compiling derive_more v2.1.1
#12 10.25    Compiling icu_provider v2.2.0
#12 10.40    Compiling icu_normalizer v2.2.0
#12 10.40    Compiling icu_properties v2.2.0
#12 11.03    Compiling deadpool-postgres v0.14.1
#12 11.07    Compiling idna_adapter v1.2.2
#12 11.11    Compiling idna v1.1.0
#12 11.33    Compiling url v2.5.8
#12 12.12    Compiling rustls-webpki v0.103.14
#12 14.82    Compiling tokio-rustls v0.26.4
#12 14.96    Compiling actix-tls v3.5.0
#12 15.19    Compiling zstd v0.13.3
#12 15.34    Compiling actix-http v3.13.1 (https://github.com/actix/actix-web?rev=67490f9e75d3262446449481c510e5ab36c9bcfa#67490f9e)
#12 17.09    Compiling actix-web v4.14.0 (https://github.com/actix/actix-web?rev=67490f9e75d3262446449481c510e5ab36c9bcfa#67490f9e)
#12 19.15    Compiling actix-files v0.6.10 (https://github.com/actix/actix-web?rev=67490f9e75d3262446449481c510e5ab36c9bcfa#67490f9e)
#12 21.83    Compiling httparena-actix v0.1.0 (/app)
#12 40.68     Finished `release` profile [optimized] target(s) in 39.41s
#12 DONE 40.9s

#6 [stage-1 1/2] FROM docker.io/library/debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d
#6 CACHED

#13 [stage-1 2/2] COPY --from=build /app/target/release/httparena-actix /server
#13 DONE 0.1s

#14 exporting to image
#14 exporting layers
#14 exporting layers 0.5s done
#14 exporting manifest sha256:c74901f07c23173779ff98f421dcee2186c16be7f3faec17ef7060f7dc295b6e 0.0s done
#14 exporting config sha256:ba9a78a27712f5a0914e8b28359ad586a744a32269b1283cdf96fca71f15fefe 0.0s done
#14 exporting attestation manifest sha256:23ca15d44a395c22ee7c1e4ae5891cf8a8d754053a2c886c077c631e7979172f 0.1s done
#14 exporting manifest list sha256:91bdc3cdb47a0037962f292d7f96ecaae46289de57fdf27e9d75cd83180a5844
#14 exporting manifest list sha256:91bdc3cdb47a0037962f292d7f96ecaae46289de57fdf27e9d75cd83180a5844 0.0s done
#14 naming to docker.io/library/httparena-actix:latest done
#14 unpacking to docker.io/library/httparena-actix:latest 0.1s done
#14 DONE 0.7s
[info] zrk: docker mode (zrk:local)
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] starting postgres sidecar
[info] postgres ready (seeded)

==============================================
=== actix / echo-100k / 32c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

[run 2/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

[run 3/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

=== Best: 0 req/s (CPU: 0%, Mem: 0MiB) ===
[info] saved results/echo-100k/32/actix.json
httparena-bench-actix
httparena-bench-actix

==============================================
=== actix / echo-100k / 256c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.55ms  630.28us  10.70ms   74.57%
    Req/Sec     1.42k   164.45     3.38k    96.47%
  472265 requests in 5.10s, 45.10GB read
Requests/sec:  92646.76
Transfer/sec:      8.85GB
[info] CPU 5287.3% | Mem 199MiB

[run 2/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.53ms  612.98us   8.86ms   74.19%
    Req/Sec     1.43k   166.77     3.57k    96.47%
  474845 requests in 5.10s, 45.35GB read
Requests/sec:  93124.54
Transfer/sec:      8.89GB
[info] CPU 5435.6% | Mem 213MiB

[run 3/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.55ms  618.62us   8.83ms   74.37%
    Req/Sec     1.42k   161.09     3.34k    96.43%
  472445 requests in 5.10s, 45.12GB read
Requests/sec:  92633.84
Transfer/sec:      8.85GB
[info] CPU 5377.5% | Mem 216MiB

=== Best: 93124 req/s (CPU: 5435.6%, Mem: 213MiB) ===
[info] input BW: 8.88GB/s (100 KB body x 93124 rps)
[info] saved results/echo-100k/256/actix.json
httparena-bench-actix
httparena-bench-actix
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/actix.json - 2 new, 32 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

@MDA2AV

MDA2AV commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f genhttp-11 -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: genhttp-11 | Test: echo-100k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 32 0 0% 0MiB NEW NEW
echo-100k 256 26,921 3721.7% 904MiB NEW NEW
Full log
[info] subscribed tests: baseline,latency-1m,latency-10k,pipelined,limited-conn,json-comp,json-tls,static-tls,echo-100k,async-db,echo-ws,echo-ws-pipeline,echo-ws-limited
[info] building image: httparena-genhttp-11
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 356B done
#1 DONE 0.0s

#2 [internal] load metadata for mcr.microsoft.com/dotnet/sdk:10.0
#2 DONE 0.1s

#3 [internal] load metadata for mcr.microsoft.com/dotnet/runtime:10.0
#3 DONE 0.1s

#4 [internal] load .dockerignore
#4 transferring context: 2B done
#4 DONE 0.0s

#5 [stage-1 1/3] FROM mcr.microsoft.com/dotnet/runtime:10.0@sha256:8fb7ff015fcf0ebc6e90105bd6db06875954e6dc3d374b9dbb34c732867d13e4
#5 resolve mcr.microsoft.com/dotnet/runtime:10.0@sha256:8fb7ff015fcf0ebc6e90105bd6db06875954e6dc3d374b9dbb34c732867d13e4 0.1s done
#5 DONE 0.1s

#6 [build 1/6] FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:8a90a473da5205a16979de99d2fc20975e922c68304f5c79d564e666dc3982fc
#6 resolve mcr.microsoft.com/dotnet/sdk:10.0@sha256:8a90a473da5205a16979de99d2fc20975e922c68304f5c79d564e666dc3982fc 0.1s done
#6 DONE 0.1s

#7 [internal] load build context
#7 transferring context: 20.42kB done
#7 DONE 0.0s

#8 [build 2/6] WORKDIR /source
#8 CACHED

#9 [build 3/6] COPY genhttp.csproj ./
#9 CACHED

#10 [build 4/6] RUN dotnet restore
#10 CACHED

#11 [build 5/6] COPY . .
#11 DONE 0.1s

#12 [build 6/6] RUN dotnet publish -c Release --no-self-contained -o /app
#12 1.359   Determining projects to restore...
#12 1.800   All projects are up-to-date for restore.
#12 4.772 /source/Tests/Crud.cs(32,31): warning CS8602: Dereference of a possibly null reference. [/source/genhttp.csproj]
#12 4.772 /source/Tests/Crud.cs(76,43): warning CS8600: Converting null literal or possible null value to non-nullable type. [/source/genhttp.csproj]
#12 4.772 /source/Tests/Crud.cs(79,54): warning CS8604: Possible null reference argument for parameter 'content' in 'StringContent.StringContent(string content, ContentType? contentType = null)'. [/source/genhttp.csproj]
#12 4.772 /source/Tests/Crud.cs(104,31): warning CS8602: Dereference of a possibly null reference. [/source/genhttp.csproj]
#12 4.772 /source/Tests/Crud.cs(110,37): warning CS8604: Possible null reference argument for parameter 'value' in 'NpgsqlParameter NpgsqlParameterCollection.AddWithValue(object value)'. [/source/genhttp.csproj]
#12 4.773 /source/Tests/Crud.cs(125,31): warning CS8602: Dereference of a possibly null reference. [/source/genhttp.csproj]
#12 4.865   genhttp -> /source/bin/Release/net10.0/genhttp.dll
#12 4.923   genhttp -> /app/
#12 DONE 5.0s

#13 [stage-1 2/3] WORKDIR /app
#13 CACHED

#14 [stage-1 3/3] COPY --from=build /app .
#14 DONE 0.1s

#15 exporting to image
#15 exporting layers
#15 exporting layers 0.8s done
#15 exporting manifest sha256:8c52545298a0b7e9d66b87faa6dbac18dd6845b5cf29034900d319482ebd006a 0.0s done
#15 exporting config sha256:bc3c93f708db75e4874725cf535ec4a0e950e8138f99b1bc3e4571ebba6c0576 0.0s done
#15 exporting attestation manifest sha256:6ea95fdbab6483f6404cdd07691292555711afd4cc1cc88613a5231631653404 0.1s done
#15 exporting manifest list sha256:e1106d5e4e18c5820ec69eec8bd459899ca61b7a5c1b00bf33145be49e8580de
#15 exporting manifest list sha256:e1106d5e4e18c5820ec69eec8bd459899ca61b7a5c1b00bf33145be49e8580de 0.0s done
#15 naming to docker.io/library/httparena-genhttp-11:latest done
#15 unpacking to docker.io/library/httparena-genhttp-11:latest
#15 unpacking to docker.io/library/httparena-genhttp-11:latest 0.2s done
#15 DONE 1.2s
[info] zrk: docker mode (zrk:local)
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] starting postgres sidecar
[info] postgres ready (seeded)

==============================================
=== genhttp-11 / echo-100k / 32c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

[run 2/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

[run 3/3]
number of connections must be >= threads
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   
                                                      
    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
        --latency          Print latency statistics   
        --timeout     <T>  Socket/request timeout     
    -v, --version          Print version details      
                                                      
  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
[info] CPU 0% | Mem 0MiB

=== Best: 0 req/s (CPU: 0%, Mem: 0MiB) ===
[info] saved results/echo-100k/32/genhttp-11.json
httparena-bench-genhttp-11
httparena-bench-genhttp-11

==============================================
=== genhttp-11 / echo-100k / 256c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    12.43ms   12.51ms 105.73ms   85.18%
    Req/Sec   417.28    120.68     1.23k    70.04%
  136199 requests in 5.10s, 13.01GB read
Requests/sec:  26709.87
Transfer/sec:      2.55GB
[info] CPU 3658.5% | Mem 1.0GiB

[run 2/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    13.13ms   13.74ms 113.16ms   84.52%
    Req/Sec   413.49    142.88     1.96k    73.66%
  135569 requests in 5.10s, 12.95GB read
Requests/sec:  26581.07
Transfer/sec:      2.54GB
[info] CPU 3841.0% | Mem 911MiB

[run 3/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    13.20ms   14.37ms 121.75ms   85.12%
    Req/Sec   421.22    160.95     2.24k    73.98%
  137280 requests in 5.10s, 13.11GB read
Requests/sec:  26921.51
Transfer/sec:      2.57GB
[info] CPU 3721.7% | Mem 904MiB

=== Best: 26921 req/s (CPU: 3721.7%, Mem: 904MiB) ===
[info] input BW: 2.57GB/s (100 KB body x 26921 rps)
[info] saved results/echo-100k/256/genhttp-11.json
httparena-bench-genhttp-11
httparena-bench-genhttp-11
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/genhttp-11.json - 2 new, 25 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

MDA2AV added 4 commits August 29, 2026 23:15
wrk cannot drive this profile at 32 connections: every entry benchmarked so
far reported 0 rps there (genhttp-11, actix, ioxide), while 256 produced real
numbers. At a 100 KB request body the per-connection write is large enough
that 32 connections never fill the pipe, and the profile is measuring
throughput under load rather than a connection ramp, so the low point carried
no signal even when it did run.

Conns 32,256 -> 256 in profiles.sh, in the CATALOG row that drives both the
explorer and the scored set, and in the profile's implementation doc.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
/echo copied the 100 KB body twice: once from where it was received into the
per-connection Out buffer, then again from Out into the write slab. This
leaves it where it landed and has the handler write it into the slab directly
behind the response header, so it is copied once - the same trick
PendingStaticFd already uses for static files, with a memory span instead of a
file descriptor. Header and body still leave in one flush.

Measured on the real profile path (h1 over TLS, kTLS TX on, 256 connections):
575% -> ~545% CPU at equal-or-better throughput, about +8% requests per CPU
percent. That matters because on the bench box ioxide runs at 6217% of its
6400% CPU allocation - it is CPU-saturated there, so CPU freed converts to
throughput.

The body is only left in place when nothing in the batch can disturb it
first: it must be the last complete request in the buffer, so no pipelined
remainder is compacted over it and no later recv slice appends onto it, and no
static file may already be queued to write at the same point behind Out.
Anything else falls back to the original copy. When the fast path is taken the
carry buffer is retired to the pending echo and a fresh one swapped in, so a
later recv cannot overwrite a body that has not been written yet; the retired
buffer is recycled. Bodies under 4 KB keep copying - the bookkeeping costs
more than the memcpy saves.

Verified byte-exact on plaintext and TLS at 0/1/4095/4096/100K/300K bytes,
chunked, pipelined, and interleaved with /pipeline, /static and the baseline
route on one connection; /json, /static content negotiation, /delay and the
a+b baseline are unchanged.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
128 KB x 32 instead of 16 KB x 256. That is the same 4 MB of provided-buffer
ring per reactor, just carved differently: a 100 KB echo body now arrives in
one recv rather than seven, which is worth about 7% requests-per-CPU on
echo-100k on top of the zero-copy change.

Measured neutral everywhere else it could have mattered - baseline at 512 and
at 4096 connections, and json-tls at 512 - all inside run-to-run noise, and 32
buffers per reactor showed no starvation at 4096 connections. An earlier
reading of mine rejected this on a 128 KB x 64 variant that did dip; x32 does
not, and unlike x64 it keeps the ring size unchanged.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
The earlier sizing was picked against a load generator that was itself the
bottleneck - wrk capped the box at ~50k rps while ioxide sat at 5.3 of 8 cores,
so every knob looked flat and the differences between them were noise. Pinning
the server to two logical CPUs makes it the constraint and the numbers
separate properly.

Re-measured that way (2 cores, wrk on the remaining 30, 256 connections):

  copy + 16 KB x 256 (the original)   13,979 rps
  zero-copy only                      14,858 rps   +6.3%
  recv reshape only                   16,028 rps  +14.7%
  both, at 128 KB x 32                17,117 rps  +22.4%
  both, at 256 KB x 16                17,521 rps  +25.3%

So the ring reshape is the larger of the two effects, not the smaller one as
the capped runs suggested. 256 KB x 16 beat 128 KB x 32 on three consecutive
pairs and is still 4 MB per reactor; baseline at 4096 connections and json-tls
are unchanged, and 16 buffers per reactor does not starve.

Two knobs are worth recording as measured-and-rejected, because both looked
harmless when the generator was capping and are not: incremental recv mode
costs 15% here, and turning kTLS RX off costs 18%. Both keep their current
defaults (off and on respectively).

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
@MDA2AV

MDA2AV commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f ioxide -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: ioxide | Test: echo-100k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 256 946,538 5248.0% 283MiB NEW NEW
Full log
[info] available CPUs: 128
[info] framework: ioxide (ioxide, C#)
[info] subscribed tests: baseline,async,latency-1m,latency-10k,pipelined,limited-conn,json-comp,json-tls,static-tls,echo-100k,async-db,baseline-h2,static-h2,baseline-h3,static-h3
[info] building image: httparena-ioxide
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.06kB done
#1 DONE 0.0s

#2 [internal] load metadata for mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7
#2 DONE 0.4s

#3 [internal] load metadata for mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7
#3 DONE 0.4s

#4 [internal] load .dockerignore
#4 transferring context: 50B done
#4 DONE 0.0s

#5 [internal] load build context
#5 DONE 0.0s

#6 [build 1/6] FROM mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7@sha256:4ef612e0abfd775a882c404f8a7ba7acd4a7de5e90ec5c0818dc09096a09c864
#6 resolve mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7@sha256:4ef612e0abfd775a882c404f8a7ba7acd4a7de5e90ec5c0818dc09096a09c864 0.1s done
#6 DONE 0.1s

#7 [stage-1 1/3] FROM mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7@sha256:43b356dd973fcfde5b328225386a5dc0c6e47712827889208c77fb40ebf5410a
#7 resolve mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7@sha256:43b356dd973fcfde5b328225386a5dc0c6e47712827889208c77fb40ebf5410a 0.0s done
#7 DONE 0.1s

#5 [internal] load build context
#5 transferring context: 18.98kB done
#5 DONE 0.0s

#8 [build 2/6] WORKDIR /source
#8 CACHED

#9 [build 3/6] COPY ioxide-arena.csproj ./
#9 CACHED

#10 [build 4/6] RUN dotnet restore
#10 CACHED

#11 [build 5/6] COPY . .
#11 DONE 0.1s

#12 [build 6/6] RUN dotnet publish -c Release --no-self-contained -o /app/out
#12 1.417   Determining projects to restore...
#12 1.655   All projects are up-to-date for restore.
#12 1.775 /usr/share/dotnet/sdk/11.0.100-preview.7.26381.103/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.RuntimeIdentifierInference.targets(385,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [/source/ioxide-arena.csproj]
#12 3.375   ioxide-arena -> /source/bin/Release/net11.0/ioxide-arena.dll
#12 3.400   ioxide-arena -> /app/out/
#12 DONE 3.5s

#13 [stage-1 2/3] WORKDIR /app
#13 CACHED

#14 [stage-1 3/3] COPY --from=build /app/out ./
#14 DONE 0.1s

#15 exporting to image
#15 exporting layers
#15 exporting layers 0.2s done
#15 exporting manifest sha256:981fe30ae2ab9e1d5d5ff3af920d9818b13cc06ec2949b42dfb60545eecf5b2a 0.0s done
#15 exporting config sha256:705b5e0ada9cdb542d9ef5cd68595a53d980f57320368bd0069daa5b538d60f0 0.0s done
#15 exporting attestation manifest sha256:371bcfa071fc413e0423ea6a6d6d27a05f6d76022a71099e89ee811bee5cacb1 0.0s done
#15 exporting manifest list sha256:062580133f500b923189ef4a61c4f4a84f7ef2fd180de99dc4d216a331dc565b
#15 exporting manifest list sha256:062580133f500b923189ef4a61c4f4a84f7ef2fd180de99dc4d216a331dc565b 0.0s done
#15 naming to docker.io/library/httparena-ioxide:latest done
#15 unpacking to docker.io/library/httparena-ioxide:latest 0.1s done
#15 DONE 0.5s
[info] zrk: docker mode (zrk:local)
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] starting postgres sidecar
[info] postgres ready (seeded)

==============================================
=== ioxide / echo-100k / 256c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   278.13us    0.90ms  55.83ms   99.45%
    Req/Sec    13.94k     2.17k   18.56k    88.76%
  4527903 requests in 5.10s, 43.53GB read
Requests/sec: 887833.17
Transfer/sec:      8.53GB
[info] CPU 4822.6% | Mem 263MiB

[run 2/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   241.52us  101.26us  11.54ms   84.52%
    Req/Sec    14.86k   771.23    18.94k    88.45%
  4827233 requests in 5.10s, 46.40GB read
Requests/sec: 946538.36
Transfer/sec:      9.10GB
[info] CPU 5248.0% | Mem 283MiB

[run 3/3]
Running 5s test @ https://localhost:8081
  64 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   254.47us  261.02us  28.76ms   98.19%
    Req/Sec    14.71k   483.34    18.41k    83.00%
  4776561 requests in 5.10s, 45.92GB read
Requests/sec: 936674.44
Transfer/sec:      9.00GB
[info] CPU 5165.2% | Mem 304MiB

=== Best: 946538 req/s (CPU: 5248.0%, Mem: 283MiB) ===
[info] input BW: 9.03GB/s (100 KB body x 946538 rps)
[info] saved results/echo-100k/256/ioxide.json
httparena-bench-ioxide
httparena-bench-ioxide
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/ioxide.json - 1 new, 23 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

Profile spec, the CATALOG row that drives both the explorer and the scored
conn set, and the profile doc.

Verified wrk holds 4096 here with no socket errors, which is worth checking
given the same generator could not drive this profile at 32 connections at all
(every entry reported 0 rps there).

4096 also puts the axis the profile claims to measure under real pressure. Per
connection memory is flat for a streaming implementation and proportional to
body size for a buffering one, so at a 100 KB body the difference between the
two designs stops being a few hundred megabytes and becomes several gigabytes.
Local check at 4096 connections: ioxide's RSS went 158 -> 725 MiB at a 10 KB
body, and it buffers.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
@MDA2AV

MDA2AV commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/benchmark-multiple -f ioxide,actix,ntex,genhttp-11-ioxide -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Frameworks: 4 | Test: echo-100k

ioxide

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 4096 495,664 4633.6% 1.1GiB NEW NEW

actix

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 4096 475,206 6211.1% 339MiB NEW NEW

ntex

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 4096 520,945 5157.4% 243MiB NEW NEW

genhttp-11-ioxide

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 4096 254,375 5959.4% 1.3GiB NEW NEW

Swaps the generator from wrk (open-loop, "how fast can this go") to zrk
(paced, "what did serving exactly this cost"). Connections 4096 -> 512, offered
rate pinned at 50,000 req/s via ZRK_RATE_ECHO_100K.

Verified end to end against ioxide before committing: 512 connections at a
50,000 target held rate_ratio 0.9889 (49,446 achieved), 395,675 requests all
2xx, zero connect/read/write/timeout errors, over TLS on 8081.

Plumbing this needed three things beyond the profile spec and the CATALOG row:

  - endpoint_tool() maps echo-100k to zrk, and zrk_build_args grew an
    echo-100k case (-m POST, -b @file, -k for the self-signed bench cert).
  - zrk's body comes from a file, so REQUESTS_DIR is now mounted into the two
    fallback ZRK_CMD paths that lacked it; without that -b @file resolves to
    nothing inside the container. The fixture is generated on the host,
    deterministically, so no two entries are measured against different bytes.
  - the zrk image build was gated on the two latency profiles alone, so an
    entry subscribing to echo-100k but not to those would have reached the run
    with no generator.

One real regression, recorded in the profile doc rather than glossed: zrk takes
a single body, so the eight-body rotation that made a canned response wrong
seven times out of eight is gone. The anti-cheat now rests entirely on
validation, which posts random bodies and compares byte for byte, plus a
chunked body that cannot be sized from Content-Length at all. The benchmark run
no longer proves the bytes came back; validation does.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
@MDA2AV

MDA2AV commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

/benchmark-multiple -f ioxide,actix,ntex,genhttp-11-ioxide -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Frameworks: 4 | Test: echo-100k

ioxide

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 512 49,396 638.4% 314MiB NEW NEW

actix

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 512 49,427 199.1% 92MiB NEW NEW

ntex

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 512 49,382 304.1% 63MiB NEW NEW

genhttp-11-ioxide

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 512 49,355 304.2% 709MiB NEW NEW

@MDA2AV

MDA2AV commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f ioxide -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: ioxide | Test: echo-100k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 512 49,378 578.0% 320MiB NEW NEW
Full log
[info] available CPUs: 128
[info] framework: ioxide (ioxide, C#)
[info] subscribed tests: baseline,async,latency-1m,latency-10k,pipelined,limited-conn,json-comp,json-tls,static-tls,echo-100k,async-db,baseline-h2,static-h2,baseline-h3,static-h3
[info] building image: httparena-ioxide
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.06kB done
#1 DONE 0.0s

#2 [internal] load metadata for mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7
#2 DONE 0.4s

#3 [internal] load metadata for mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7
#3 DONE 0.4s

#4 [internal] load .dockerignore
#4 transferring context: 50B done
#4 DONE 0.0s

#5 [stage-1 1/3] FROM mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7@sha256:43b356dd973fcfde5b328225386a5dc0c6e47712827889208c77fb40ebf5410a
#5 resolve mcr.microsoft.com/dotnet/runtime:11.0.0-preview.7@sha256:43b356dd973fcfde5b328225386a5dc0c6e47712827889208c77fb40ebf5410a 0.1s done
#5 DONE 0.1s

#6 [build 1/6] FROM mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7@sha256:4ef612e0abfd775a882c404f8a7ba7acd4a7de5e90ec5c0818dc09096a09c864
#6 resolve mcr.microsoft.com/dotnet/sdk:11.0.100-preview.7@sha256:4ef612e0abfd775a882c404f8a7ba7acd4a7de5e90ec5c0818dc09096a09c864 0.1s done
#6 DONE 0.1s

#7 [internal] load build context
#7 transferring context: 418B done
#7 DONE 0.0s

#8 [stage-1 2/3] WORKDIR /app
#8 CACHED

#9 [build 3/6] COPY ioxide-arena.csproj ./
#9 CACHED

#10 [build 5/6] COPY . .
#10 CACHED

#11 [build 2/6] WORKDIR /source
#11 CACHED

#12 [build 4/6] RUN dotnet restore
#12 CACHED

#13 [build 6/6] RUN dotnet publish -c Release --no-self-contained -o /app/out
#13 CACHED

#14 [stage-1 3/3] COPY --from=build /app/out ./
#14 CACHED

#15 exporting to image
#15 exporting layers done
#15 exporting manifest sha256:981fe30ae2ab9e1d5d5ff3af920d9818b13cc06ec2949b42dfb60545eecf5b2a done
#15 exporting config sha256:705b5e0ada9cdb542d9ef5cd68595a53d980f57320368bd0069daa5b538d60f0 done
#15 exporting attestation manifest sha256:d106ce4cf37367b3b451cc3db6421043275f3e133855e08849b2aba0404fca0e 0.1s done
#15 exporting manifest list sha256:f3bf503e60f3c8967084eb2a9256bcc95b25aaf37fcfa6b06a2803fe73c39f6e
#15 exporting manifest list sha256:f3bf503e60f3c8967084eb2a9256bcc95b25aaf37fcfa6b06a2803fe73c39f6e 0.0s done
#15 naming to docker.io/library/httparena-ioxide:latest done
#15 unpacking to docker.io/library/httparena-ioxide:latest done
#15 DONE 0.2s
[info] zrk: docker mode (zrk:local)
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] starting postgres sidecar
[info] postgres ready (seeded)

==============================================
=== ioxide / echo-100k / 512c (tool=zrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
{
  "zrk_version": "2.3.0",
  "target": { "url": "https://localhost:8081/echo", "method": "POST" },
  "config": { "connections": 512, "launched": 512, "duration_s": 5.000, "closed": false, "target_rate": 50000, "timeout_ms": 2000, "deadline_ms": 0, "deadline_abort": false, "record_timeouts": true },
  "duration_s": 5.002,
  "requests": 246146,
  "bytes": 2540719012,
  "achieved_rate": 49210.22,
  "target_rate": 50000,
  "target_rate_end": 50000,
  "rate_ratio": 0.9842,
  "bytes_per_sec": 507947847.70,
  "error_rate": 0.000032,
  "max_schedule_lag_us": 1991898,
  "latency_us": {
    "min": 52, "mean": 6677.5, "stdev": 92806.0, "max": 2000895,
    "p50": 180, "p75": 189, "p90": 200, "p99": 457, "p99_9": 1697280, "p99_99": 1971712
  },
  "status_codes": { "1xx": 0, "2xx": 246146, "3xx": 0, "4xx": 0, "5xx": 0 },
  "errors": { "connect": 0, "read": 0, "write": 0, "timeout": 8, "deadline": 0, "non_2xx_3xx": 0 },
  "latency_histogram": "HISTFAAAB4x4nH1XfYhdxRW/78y5cz/m3r378nzZ7G5etruva7ouQZKgUYOJIY1oS78ssU1Rgoj4UVCQFhX8Y1lkDRJCUFkkhCBB0vwhqYRUWgghiKTSliL7R1iWIhKClGUpoYQQghR/55y7uy9Jde/emTkzvzlzvu+89W+804qi6o7I/lzdN5R4581o5xWbeJYiIr/ujg0PPPzEG42TjX82ZugwzbolOuved/PuIF9xi3yej8RT8Vx83l+Nr8eX/bHkKJ7TyWJyKplO55P59FK6kH6aLqYz+VJ6ODuRXcqOZAvZgfxadjxfyKbypex/2VJ2JbuQHQ0XswPhcH44fzs/Hb7IP8y/zs/k/86PhLn8QD4bzoTF/HI4UZwLn4VTxdkwHxbCbHEy/DccLGaL2XAhfJ1Ph2Phg2IKiKthKSwVX5Vvl8eL08Wh4hCwl4ujfX+rrjevtj9ad6Nzrnty8l/3zOy8+Oi1vQv7zzz38Us3fjf3yluvffLa9KvHf3/h5esvzOxf2Hftl/M/ntlz46Evtr9/79SWP286etfn46fH5jon1k8Pvrv2P6255oHm+eqr4kucfgw6XkoOJp/5D/wn8cH4Yz7L7/Epd9y95S7SNbpMx+gkfUSH6B+NKVpsfNo4j/dc4yzsO43xTOP1F5/7+W8e/vUTjz3+yCOP7/jplh0/+uGO+3bc9/2xH4wNbdnwvTvXjg2t2TC2dqh/aKjoX5/mRd+avrX5mr68L1+zrujnokiZOGefp55yZhAEAi/GzkvLMRE5jIgiZjg5lgE56TFIKSEn854lAogjjrwMHBYjTMk+kjammGVXLCDMOaIMmyO8CUZOdpBwowpCBEw1MVGhjwUbg3SsoEpXiEZIl+VRdiVmKrSxERkF2oQ3AnwTaBXZARNhMiEToEv7iLaRsrCzulibAOIXxqWkQRqnFhZKGgYops0KgzByaEYDBO5bBboRgGHaDj6O7qFnqK0cW3iepF3YUoH3KN2NfpD/0IAgv6Kn6S8NnLdX9cpULtFCsC2gd4OaBHNRBA1WAvaLSJWeQr9VgzoIMenL2hojeJoqmEgdVHPl2FTfgRF3MLUVw45YULTaBlSTS1I37ANuUhTaDg5tVYoSHhA2kT4V3gkIIi7qQhinxqQ94iJlh1Ph6wALccy71RctqmMG7oZ3S6zhODFjAn6U8bCyDubKiHepc0YxMa5TTkMjUQM1IYyGWKbh48iLDVrAthniKF7UrdT3aMSibUWPqwsTtbRDbE8o/SAOSvA6EW8Sr/B7swExRLgM7VYN1sCTWADLLt1PauyYLbAsSlusboTaHT0FhyaAM/bCHqwrZoVRYon9OhFkU5qp+mQSQziLagl0MUokrNGU6NoEZZ1yUtPAyLV91MGSeBbgTqVKZIPpBGUZ/KHUiIlnsuuJCUuKZmrW2tMsO8QEtjdS5cFMQ7XSkgCbJCokZEgkziKNNct0szdkhh24K9uUV4zYHMBpHcl/rmo91TpaYESBth/AaBDnTayskjJ3aaTyWvWQsAlciyzhLiIOe61Owk5rhkO8qUEiy9tah0hLU+zNZFFdloRrqVhRn63iyEh8zOYM9W6k8R6p4VQi0Uzxsl0kGZcwDSyxKPZnaTaSlZ9EpY0seURn9V2i5q/UDUJpwuN4DTMR3WplYgXOJMuk6mm4DAivptaBNngOowwlOjModuNxzYgBixLLgSbIYYWIOZ9FcZNMEXOP4xlRKTfXlpViSpIym1XTn0GEoNBK0+BRlMwRZTwBlhs1PZ6kPzbo7w2kj82UKC5/atBfZaaj2d2mnUDt15ImOpY4dj9QnbocS9F8CvQgQt50GUVp3Yv1lkZlBfp5yDKqwSz7u6iKP4Ekg2aMEXoJApdabaR4bANU7KXS3G+fDPFqUEKKKPcQknAJCqHB7MvxIA6XpUi/NTtr0WmVlEWnrtkF8woVa97u1k9CadVYyJaRIhaqp1GaP7xHLW+OQuZIuNnnE9IbIcnaVe/XC12NC10QQj+sbIk7qm6K65Jja7GI4ZSs15TQWpVoXI5asf7WBStQoyxFWRIwtvHyPFkJTFSjEQt4jVml4npTIoQGtBSSmxY6io31y9dL4YDhurjdtMRlvaIXgCBcDCcfcji1JTmKPGMJZklvKcqWzqxXg9ALkNIg9pN06x2p3TJbXh26+iLj9HZjVdXp3adOWa4hmV2XXI3Wqq65VyN6hr38bOzd6tBuHz1DscD/5yzXr9vAy4hQj1X/nqGajVbHrFW3hkRqOakpZENDR5oTbhlhNYtsGNWI3rFdJZZ5r8zfQqyConrMhqmsgi8TJnB0O365tq9g3O1Drq+QuhOW/A6oDfHRES2l9LNV7+Ue3ru1W8Xeusc79fW39LgJW8/6vZdtzlTx8Xf1LBevyPbZlcBLldBO7HfLLq7Rq7us94mc19N6XmllV93ZTwGufxL4RITWtge+vMlTT8c+W2k939R6wVibiM18ttJi1VogU1ppKQ3y6wWNZ2s8Fm5q8hIv7qaAalPjU3GmNnnJnOrrCa/n0qdkb17pf0mpL9njTVFvisr+sZSmVe6rPqr6K+rDf9n/DYCP/ks="
}
[info] CPU 559.2% | Mem 294MiB

[run 2/3]
{
  "zrk_version": "2.3.0",
  "target": { "url": "https://localhost:8081/echo", "method": "POST" },
  "config": { "connections": 512, "launched": 512, "duration_s": 5.000, "closed": false, "target_rate": 50000, "timeout_ms": 2000, "deadline_ms": 0, "deadline_abort": false, "record_timeouts": true },
  "duration_s": 5.001,
  "requests": 246608,
  "bytes": 2545487776,
  "achieved_rate": 49308.12,
  "target_rate": 50000,
  "target_rate_end": 50000,
  "rate_ratio": 0.9862,
  "bytes_per_sec": 508958452.99,
  "error_rate": 0.000000,
  "max_schedule_lag_us": 26945,
  "latency_us": {
    "min": 52, "mean": 191.1, "stdev": 563.1, "max": 37215,
    "p50": 181, "p75": 189, "p90": 199, "p99": 230, "p99_9": 6070, "p99_99": 24200
  },
  "status_codes": { "1xx": 0, "2xx": 246608, "3xx": 0, "4xx": 0, "5xx": 0 },
  "errors": { "connect": 0, "read": 0, "write": 0, "timeout": 0, "deadline": 0, "non_2xx_3xx": 0 },
  "latency_histogram": "HISTFAAAA/N4nD1Tb2gcRRTffTO7t7uze5tz73K9JJc0Ta7VRIyx6UlpaywNBpWCX6yEgoIilX6pIgWJ0sYS6hElhKaEI0iwh5RS21okwlnKEYKEww+hHCVIkBKLHySUo4QgQcLh702COzczb97/93vv2sanI8OwPjV2PrF7m/ox/ZUx+HSHcYZI2E48nuzOD73/2k3zuvnEnKAyXRZrtEUFsSrmxV3sh/KBHLMWZE1uykmrYj2RM9Yle8IuW3N20Z6y79l/2GtYS/ZkbCY26/yKXXNWnftOBfeWs+Dcca96N9yiV/TuelPefW/DrXpXvUmv5l3HXVBjqqwmVM2b8/7y5tWSmlSbuBfBXVKzqq5u+WtqWd3zH/sNta5u+hW/oO6of/1b8Y3gH7/oL/tb/oo/FVTCibCU+C01lqm0P9232vPg0OZg7WR1pHBm41z1s0efP7xYvnDli8bo+ujj89sfNz7afrc0MvVW6Y3y0E+DNw6vD5T6F3rrB8a7vun4Pbvcst38KHUp+XdiPlwJ6n4VuW453zoNe8VuWLPWFasui7Igi+I7MSOW6Xuao7q5aFaxFs0S1ox5YXTcvGyePXX21FD+1aH80Z5XevJ7X3j26MDAQH7vS/u7n0++2NK+v7utfd+ePW1xv6X9maam5qak35yMN3m+Iz3H9jxH2p6U5BA+x5AkySZpS8cmkjFsA3whcVjgGKTAIkGWhEA4FljSogTU8I5BICiAVoJC3AFuOghdFlAnWcTOYvon6DheOUrhhFBrx+A9RYEUlIauJUO8BH1ALlZEGdqJH8B3gj6BRg60oGsmCDoE3jCIPuwj9CaHS8AcPkdgg4BdMDhOvTpJdpCmk5ruhGuugLLwHoAMwWZTlw7AskOe1mpv430OOjHovMeODagGYHbpFHvhxUDYTpwR1HIaig44yCIl+tKEoQvX/cAjgiTCC1CGcMJxQ20Ygh3A9zu67gxkh7WTvl2pgLkGYJha8RhEoRGdgPggnLioSNdOx5B4GivQPINc+3XoRdKS/O6HW85kWHIWnHWM0U/A8UWQHGcYrjtAvYz49Bydpj9NFJilH7iKaRNY020TGkgxw0eku43eupDnwBBcW0zjyA3VbeOoLMsxEpJR6YOeyxi4ulYeMd3QHdxwy4RGN0UhT2FGo2XxMApNpLV2l8YyrfHsZ11Yp4APUwytq7V0u3I4DSjlUJQepiPcSAuGrZwrxpRzEaAzpCQPup5NjqWYxndCpnWGke4ni1nv/+nnKJyilrCpzIDkocsCzUB3N9ot0dVwhUi9dbe7IAJtm8D5IfH492K2FP1ios2YoZ9N+tqkViRq6eg/mnRew8v/GHT3P4oyyno="
}
[info] CPU 619.0% | Mem 314MiB

[run 3/3]
{
  "zrk_version": "2.3.0",
  "target": { "url": "https://localhost:8081/echo", "method": "POST" },
  "config": { "connections": 512, "launched": 512, "duration_s": 5.000, "closed": false, "target_rate": 50000, "timeout_ms": 2000, "deadline_ms": 0, "deadline_abort": false, "record_timeouts": true },
  "duration_s": 5.001,
  "requests": 246962,
  "bytes": 2549141764,
  "achieved_rate": 49378.45,
  "target_rate": 50000,
  "target_rate_end": 50000,
  "rate_ratio": 0.9876,
  "bytes_per_sec": 509684327.43,
  "error_rate": 0.000000,
  "max_schedule_lag_us": 17665,
  "latency_us": {
    "min": 52, "mean": 185.7, "stdev": 397.3, "max": 27919,
    "p50": 182, "p75": 190, "p90": 199, "p99": 232, "p99_9": 2377, "p99_99": 18664
  },
  "status_codes": { "1xx": 0, "2xx": 246962, "3xx": 0, "4xx": 0, "5xx": 0 },
  "errors": { "connect": 0, "read": 0, "write": 0, "timeout": 0, "deadline": 0, "non_2xx_3xx": 0 },
  "latency_histogram": "HISTFAAAA/94nC1TUUhcVxC9b9697717n+tmXdd1q2YbRU1aiIlVNE3RliSGfuRDP1JSAk37k49SKLT5qjZJu22lBCkbyUcIaRApIYSSLhJKkbIUCaFIKGEJNhSRIFJkKX6E4Ee79Mzs+nzv3jsz98yZM7OdhatppcxPqv7nN1ZPDle/UeM7dcN5HRAeFbmW7qHTH3y+6P3h1bxLtEI3qUT3sT6l57Tor/rP/GX9SF8zC2bXFIOqWQy2sa4GD4PF4LewHG6FxXAjqkXFaDOqRCW7HS3YdbttH7qbbgu7ql2y190j+9Tu2EtY1+xju2vnXRW+227BbYqlbCv2hi27mptz827VzcY/upW45H5wG/A8cGuu4GrRv/YOUB7YTSAU3RfxslsF0rfAKMbrTTtNd5L/pGbbdtvX87/03Rr468jyxJWpuXPXP3zx6bPPlmaeXCxc9q5d9ram56dLFyofPz//97mdM/emam/fPb721upoZej+4ZVXKn2z3V/nv+ustt/IVlrvttxKbTSXE7/GS3Etuh2VokJ4JVg0W9Dknn7hV/2y/zM9gWYlKlDFq3oV77E36/3uFb15b86bnj579t3jE6dH3xgbnDr05tD+sbHXJ0YPHRwcOrh/9OW+wc6+wb2dL7V0t7ft7Wxv7W7b41wQNe9pbo0CFzQ1BVEQNGtSzU0UaKVhVGib9nEKNDlyWuvABIZ8rUgR/n1s0wSTNoRtSCG+ZGBRlOQQC5uCNSCK+QLiFFnYORJXqIMaZp/XFJaEuLJA4atK/LSPrxDAehr+DCLYm8He0GsIMPDgwMaEGHHDaLzCE7dD0jEycFAab0ic1AgCn8Ccl36dwoUB0M1TQsPbRcfEn4Z5mMZxzlIO7hDcfamSvwCK8ShUcoAJ95AkyDCLpJDy4WbuXZJVaT7va9CUUvMcLeoMs4qh4Pp0GO8xfYTrQ29AgwvNS6VSlGGJdRas0mxgCXMNYRhIcjN8VhLFIn0oElkEs54JuaDwTeJhbw/jmEZgqJlwDq9FPw1z7MWjuKVJ6QsnETUV15Pn/QidoElYTkDKo4QiAPmnR/00Q+/QVx5dhOkCvUf/eQSqOXof/LgsA+RhJEnRJ+hUgupy5uEVRXuRLi9sEth3wGhpQKbxpNTdQ0d1fSByEJelBxLXKcphBLPIFNMpvD2wdtTHeBIh/UAw0taMSJ8j0crnvKeQrR6cxJoBddZFJusjTFhOtEuLyiM8qtzsXozKAUD0cqQMMTMYgG1GWpAWeCN7w7zScMlwphgwYHDDPz4lYY0Z4RGjEVgHIGMHQvPyi1Ey0eOS2Gifm5RAoRZkOgCbBueT9KUH+uMg30Wvck2TSJoB/e89dvH0+nwtRWfof678yoY="
}
[info] CPU 578.0% | Mem 320MiB

=== Best: 49378 req/s (CPU: 578.0%, Mem: 320MiB) ===
[info] input BW: 482.21MB/s (100 KB body x 49378 rps)
[info] saved results/echo-100k/512/ioxide.json
httparena-bench-ioxide
httparena-bench-ioxide
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/ioxide.json - 1 new, 23 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

MDA2AV added 3 commits August 30, 2026 01:26
…uffers"

Back to 16 KB x 256, the value that was measured for this entry. Config.cs is
now byte-identical to what it was before any of my recv changes.

The earlier revert only took it as far as 128 KB x 32, which was not what was
asked for.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
Its other fourteen tests are unchanged. The /echo route stays in the entry, so
resubscribing later is a one-line meta.json edit and needs no implementation
work.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
@MDA2AV

MDA2AV commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f fulmine -t echo-100k

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: fulmine | Test: echo-100k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-100k 512 49,355 355.5% 3.1GiB NEW NEW
Full log
[info] available CPUs: 128
[info] framework: fulmine (fulmine, JS)
[info] subscribed tests: baseline,latency-1m,latency-10k,pipelined,limited-conn,async,json-comp,echo-100k,async-db,static-tls,echo-ws,echo-ws-pipeline,echo-ws-limited,json-tls,gateway-64,gateway-h3,production-stack,fortunes
[info] building image: httparena-fulmine
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 782B 0.0s done
#1 DONE 0.1s

#2 [internal] load metadata for docker.io/library/node:26-trixie-slim
#2 DONE 0.8s

#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.1s

#4 [internal] load build context
#4 transferring context: 124B done
#4 DONE 0.1s

#5 [build 1/6] FROM docker.io/library/node:26-trixie-slim@sha256:c0753125a3789977aefe869cbebccf70e3cfd7ea84ca48547458f02e4f1d7146
#5 resolve docker.io/library/node:26-trixie-slim@sha256:c0753125a3789977aefe869cbebccf70e3cfd7ea84ca48547458f02e4f1d7146 0.1s done
#5 DONE 0.1s

#6 [stage-1 2/6] RUN apt-get update &&     apt-get install -y --no-install-recommends ca-certificates curl libpq5 &&     rm -rf /var/lib/apt/lists/*
#6 CACHED

#7 [build 3/6] RUN git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"
#7 CACHED

#8 [stage-1 3/6] WORKDIR /app
#8 CACHED

#9 [stage-1 4/6] COPY --from=build /app/node_modules ./node_modules
#9 CACHED

#10 [build 2/6] RUN apt-get update &&     apt-get install -y --no-install-recommends ca-certificates git python3 make g++ libpq-dev &&     rm -rf /var/lib/apt/lists/*
#10 CACHED

#11 [build 4/6] WORKDIR /app
#11 CACHED

#12 [build 6/6] RUN npm install --omit=dev
#12 CACHED

#13 [stage-1 5/6] COPY app.js .
#13 CACHED

#14 [build 5/6] COPY package.json .
#14 CACHED

#15 [stage-1 6/6] COPY views ./views
#15 CACHED

#16 exporting to image
#16 exporting layers done
#16 exporting manifest sha256:afdc028e3237c9107fdccaced94221ca35b6da0ec6989b787fbddf6f83cc4145 done
#16 exporting config sha256:86734d19ad990ea2b6dd74e6a0a293feb5bb59abddcd4f336a34db747a639075 done
#16 exporting attestation manifest sha256:7f609f430fbfbbe3cdb3116da5939b11d338fd56fdf27bfef9dd32f04a57e725 0.1s done
#16 exporting manifest list sha256:da756117030ea4773cb7eb51883e3b2dbc450ce14e2d5149160ec5918367d852
#16 exporting manifest list sha256:da756117030ea4773cb7eb51883e3b2dbc450ce14e2d5149160ec5918367d852 0.1s done
#16 naming to docker.io/library/httparena-fulmine:latest done
#16 unpacking to docker.io/library/httparena-fulmine:latest 0.0s done
#16 DONE 0.3s
[info] zrk: docker mode (zrk:local)
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] starting postgres sidecar
[info] postgres ready (seeded)

==============================================
=== fulmine / echo-100k / 512c (tool=zrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
{
  "zrk_version": "2.3.0",
  "target": { "url": "https://localhost:8081/echo", "method": "POST" },
  "config": { "connections": 512, "launched": 512, "duration_s": 5.000, "closed": false, "target_rate": 50000, "timeout_ms": 2000, "deadline_ms": 0, "deadline_abort": false, "record_timeouts": true },
  "duration_s": 5.002,
  "requests": 245813,
  "bytes": 2558175891,
  "achieved_rate": 49147.32,
  "target_rate": 50000,
  "target_rate_end": 50000,
  "rate_ratio": 0.9829,
  "bytes_per_sec": 511476162.10,
  "error_rate": 0.000000,
  "max_schedule_lag_us": 19926,
  "latency_us": {
    "min": 63, "mean": 148.4, "stdev": 509.2, "max": 30175,
    "p50": 119, "p75": 146, "p90": 182, "p99": 361, "p99_9": 3911, "p99_99": 24168
  },
  "status_codes": { "1xx": 0, "2xx": 245813, "3xx": 0, "4xx": 0, "5xx": 0 },
  "errors": { "connect": 0, "read": 0, "write": 0, "timeout": 0, "deadline": 0, "non_2xx_3xx": 0 },
  "latency_histogram": "HISTFAAABW54nDVUW2hUVxS9Z9997z1z586dyWQymXGSTB5jjGPMO/GRGhPTB/QB0o/QFyL98EuqtFD6YVtJQxSxIlFSKUEkSBAZQhGxIR9FgogfYoNICFJEikiQINJCkCCha59JZ+bes8/e+6y19j7nTM3YhUrL8v61yh97c1RmcuGUtf9V2fE9WWRxItXa13FiQj1St+ml/dQZd2/pF/7J8FViLnU3+yy/Vpgsvt45117qGGt/0H5651jbdNu5ttHO8933u1/2Pd011X+3//r+xcHVwZtDSweWh6eH77w9M/TXwJP+9f5ze87s+qdnued5x6O28R3Xize3n92+0Dyz9UbTVONv9VP1V+pn8mu1z2pu1F6qu13zuuZxzb3cWm5ty0p2IXs1M1c9n1lLr6dfYDxTfS1dSi2l1ismK17Hn4dX4hvhbLgaPgzPx5Zjl4P70ZXoun/bn/WfR1b0k8jfeioyFinpUT2Bp+Q98Oa9RXfKnXevu/fdl86yc9pd5UXnljPpXHSW+CqeUWeWl/kan+USj/OG/djesN/YZ+1Zu2Qv2Bs0bf9qX7QX6A1N2Ku0Qg/pHi3SBP1Bj2iebtAMXaQNdQWecVpT5+kkPVMP1Ip6rJ6qWTx30OMFtYTnJuw5dfQu3FfVZZin1fFpVVIn1Rl14ofLmI6qSTWmvr6kRkbVuDr+3ci3Rw4dOTRy+KvDXxz98sjgx58NjnxyaOTDt0be72ncPXCwp3XvwMEDPR0tu9/t6ujra84M7G3t2t1c7OortHYV61uat7Q0FguNxXRdTaHY0tKSqS82Frqa082pQiaRTBQyqWRjXaG5LlMXJpET+pmadLpeh77fmEpnUoktQSoIw/p0IpnOhJm0DsJkMp1MpwJOJoJUGGh2dTKRcn03oTUntdaB1q4f+D5ZfhBwyC6HQUhiWpqCkGWFDvwkDHx9S3NACe2S65LWfsAWuQkmZgo1MWkY7Lr4kQ2btRVgGYfa8rUFbNau61o+s4/RFQcZpwUEC0uJgApIi3wECRJAZfm+xQ5imikg5DHiNhJBIGsYZLgitkAz2wJFIAAsO6KsnGURuXKRAAspJIxgw0oxxWLDCDTP5NqGCWERJhiuZJoQMFiuJJVfTI7IkwokgbVjUFmSpDILL0JBlklAuqgtK9LCLFGPxemIDPIMHcUkIj6mKKSVS8Rah6JoTERg2WZPSoiSLdQ26vXMIk9KimyaAmEbUinHERypN2pcFseNkggSJVQts0rTGKOITbpjMh0j1im3zRLRMrF5U7tT5shiiAC9Ek/MUOVZghRHNIo38jgOqywuatK2UT9KiZZp46YlMaCQ4fbMV+ZGMd5YV4Gh1jg8k2T/Xyt+VVQthgWkCuiopSaB2y+wMYGMUYNZQztgNnG5ujjw90GIQznTizxivYCKAKObRHDU9MGRZmHMY5C6sowkMYQKPlldCQKQOdzAArWN4K3CijjMKujJb6Jl8fSaAjzqNDuSA+tHyBsydduGA1AVsg9ZxLYiZnZWSoEfToH1QPiLgqPXSNmFvF5CF2zag1jOuHOmuTEBMFXk6R0YVZxHe3gHgMrdcgBfaZTkGKZ0Ko9JFtL30U6Eh81WSA894G5HQVn6XYFMHPQ5y/npB3IOjg8Q/4YrkO0BvhoRUS671QAyxzS6F/wRoMBsMMGfFRreTagiZjZTRPyk0LYhIPypzAmxAfwp/YiuxTc7CHWdQDKnV8gjZs8cYETpGEunbVOZOYs5VxTJMa0yG1W+yhhYjle71IydO6WMqwkMW80hj0F0zvxpRE0zyzcjVr6ccnyjZm8qNgGrYEVMoMosdQx9lt4j6UgE3ZFdifGw2eY8uobj2UnH6D/RgfTu"
}
[info] CPU 626.3% | Mem 3.2GiB

[run 2/3]
{
  "zrk_version": "2.3.0",
  "target": { "url": "https://localhost:8081/echo", "method": "POST" },
  "config": { "connections": 512, "launched": 512, "duration_s": 5.000, "closed": false, "target_rate": 50000, "timeout_ms": 2000, "deadline_ms": 0, "deadline_abort": false, "record_timeouts": true },
  "duration_s": 5.001,
  "requests": 246814,
  "bytes": 2568593298,
  "achieved_rate": 49348.79,
  "target_rate": 50000,
  "target_rate_end": 50000,
  "rate_ratio": 0.9870,
  "bytes_per_sec": 513572816.63,
  "error_rate": 0.000000,
  "max_schedule_lag_us": 26231,
  "latency_us": {
    "min": 59, "mean": 125.4, "stdev": 523.4, "max": 36479,
    "p50": 102, "p75": 116, "p90": 135, "p99": 196, "p99_9": 7918, "p99_99": 22040
  },
  "status_codes": { "1xx": 0, "2xx": 246814, "3xx": 0, "4xx": 0, "5xx": 0 },
  "errors": { "connect": 0, "read": 0, "write": 0, "timeout": 0, "deadline": 0, "non_2xx_3xx": 0 },
  "latency_histogram": "HISTFAAABEZ4nDVUX2hbZRS/93zf/XKT3Jubv02TNo1ttm6pa2ucLK5NqTrLdLVzuipTtglTFBF9EHwT/1EykZmHbQ9DivgwyhAZow9SiojuIYwyikgZow9lFBlD9hBkSJU9+DvnxqS593znz++c3znna//ChYxlmfes8KO6b1sOF85a051Q8ZHRZCXTPfsm3rpsr9vf0rL601lxNxL3c2vlzb1Xa38cWJpYaaw3fpq419hq3JtanW7NtJ+/OLd0rHW8M//bic5rt08+OPnD6Yenmm+0Tv/4+r8nVl757MUbs60j6zPtQ5tPf9FoT547sLn/m9rN8cXRW9XF6ubw9cra0MLgark5cLfvXN9y4VrvWs/NbCe7mvkr/X3yTup+ciOxk7ju3/C+9Drx27Gd6PnYjrvqbrhN97K7GFmKtCNb5qrTdH7W2/qu+kpvq2XdUbfUNdVUC+oBLdHf9At9Rx37jv3QbtGWvY13Gwx/ty/hu2w37Sv2Ar4fnLc//OTtj8/MHzkzf+zVoy/PP3P0yeeeemFo7LFnRx4fazwxMrZvaGRk767iIwO7hocHir3FYm9/tn8gmcz2JBKxdDqZyHo9JpmOxdyk8TzteUXjUiztesZoL2Zci1yjDf+5MSg94xqytLb4Qa42RC7haEi7cLSI8IsQ3pYcMCBNokcEacdYmhSRI3bDWkWOJnwcrQQWCrzgSAykNIdZsCGYMgCjiGY9WzgQSAq+DiwQI4yjo2yM8AEJTBSpoGUsgdaSjOKcHMHdjECnqJzgEaUU0CwdNawKy3DYEQjiw0ZfpDisDifimhwyqovIHlyyRZyf31QQA1cehxL5kSca4pKPUzx0xikAU0vSsNv/kFbYW4lIUdhmS1on1WAM7FMFFnPPU4iXg9wnhQZoHccGsCnuKeQ30YpJAPgCAnYVDtJ5KiEQEMi4H4YKgPI0Q/BLAS8ediVD78O7jJQBDFMAdaQsaUkcWVPgPEuTNEqDtKfLrUDMmhtUAnQeaRwd4MT9yyEmTtMg8y4kB4ABcWqFVBHxSQlnJSMuiBxB6gx+zJdLPiwahJTpFEJ9AFUQmqM6jBmSXh+mT1ER46EcH+Ich9RpN/B9kEFXEDqJUB91z8Gfu1almvRyGtYUwCoIPk5ce11mG5cqAvCq4VeCNEXvQDuIUmZx4o58jnEdhFShlyBxm7+2aVyKr0vwDJ71Llkf2jKhSzxNXrcUaJQwkpzsQMAjwMxKzDxceiXll3gO+qAsQBXOPHAHT1kpuWOyGYCDQUT0SdbB4usm5kCI5bvrCJhDsofhlqM8bGu4owpOu2V2FdEwtxo4MoWMfHVK1kH+I3CUEmLMgjvmcz7Oz2NlB06HWozsf0mYYsr6Ud4HWfZBvjgZvhp8+bniLmRKhkqmT64GGMhthMhzyTMwX4AcjWs8LNRZ4rE70jPMpiCXOUAxo2C7Bz4+/WrTPw6dtek/rEmpzw=="
}
[info] CPU 482.6% | Mem 3.3GiB

[run 3/3]
{
  "zrk_version": "2.3.0",
  "target": { "url": "https://localhost:8081/echo", "method": "POST" },
  "config": { "connections": 512, "launched": 512, "duration_s": 5.000, "closed": false, "target_rate": 50000, "timeout_ms": 2000, "deadline_ms": 0, "deadline_abort": false, "record_timeouts": true },
  "duration_s": 5.001,
  "requests": 246839,
  "bytes": 2568853473,
  "achieved_rate": 49355.25,
  "target_rate": 50000,
  "target_rate_end": 50000,
  "rate_ratio": 0.9871,
  "bytes_per_sec": 513640091.13,
  "error_rate": 0.000000,
  "max_schedule_lag_us": 16439,
  "latency_us": {
    "min": 60, "mean": 109.6, "stdev": 313.7, "max": 26687,
    "p50": 99, "p75": 109, "p90": 124, "p99": 166, "p99_9": 2065, "p99_99": 19016
  },
  "status_codes": { "1xx": 0, "2xx": 246839, "3xx": 0, "4xx": 0, "5xx": 0 },
  "errors": { "connect": 0, "read": 0, "write": 0, "timeout": 0, "deadline": 0, "non_2xx_3xx": 0 },
  "latency_histogram": "HISTFAAAAz14nD2SS2hTQRSG5/537iO3N22TmyaNaU1jjO/6qlWKFmlrRcUn4hN1oYKKj/pYuBKk+KJEKW2VouLCRZHioouiRaS4FhFBEZEuRETEhRRXRbrwP1Mxw83M+c8535wzM3XX+iOl9AE187P/zZYx+m+q9ZMzwmW44ex0+9bjo9ZTq4z79lt935+omqr9U+pe/nPdWEdP573N3ZtGOns6frW9aZ/Y+HPL5M7Xe8eOXDtePjF9auLs43OTXb8v9F4c7xo5/ePkn6NDBwd3j279smmkY6D1e8vn1b1N5WXTi4YXfC0NFl8VftR/rvuQe5btyZZrH6WH0+OpqehhcjDRVz1ROR3/En8X3gg/BUPBy9id2ID/yx/zvrpP3FFnSnc7Q/qOnrJ/24/tIfu1/Q2T+IZhPMRV9PG7io/WC2ucXYxYN6yy1Wd1W+dv8e/M+ZNHD+3dvn/nhiWr1q2ct3zOoqUNC5bMndMwq2FeMlndkJqVTlWHqdqkDuuqg1Q6DJJ+pVupfe36bhgErquh/TB0Ax1qVwehD61d+D58F67v+0oD2oEOtPZBCTQBm0PHAMVZUVEUdQUcKG2DujPjhPd/KBl0RkCebhsMU/wSTEqgyLWHHGqQRStWkJoAKoQQcfJg0us5EloE2T7PZIfrGq4LiGua6xlQwQFSFDM9nKLqQXZtRRNn8eRw20Iz8axaOllMJTKYNmxmXo7YDBrNBiysnvQ8oVmCiG7EGk1bqpiPFmKb6XEktNlwItkl0iVUUVvINnZR3kF0HCXacjpSJKGKIQk6BZ2Z6VChqDM0Wk2cx25bJLaLyQ6j4+DRSHExqnEWLjESYP875Lg51AxDl8F03WTo0lEzt1UsvMBiYrSqOErs+gK2sZGYqV38OebFiXAE4JAptIwptAS5uzxBkbkbh1oWnYyRw1uDDmJWSD1FShFvYxfZSmcpF2gLhhmSp69b5ubyFB1TeZE7xORxUPG4EtI+XGJUFX01nNmzNodbAEGK8Da5zQomOXQ0MqFo7rKJO2cM9DB7lSM5xn4jLe/Cw10bZxnayNQHFjE12MPoxXhvmRtLUCDPwVpcwXOLuJx0lICrpLQ29MubwF8d75Qz"
}
[info] CPU 355.5% | Mem 3.1GiB

=== Best: 49355 req/s (CPU: 355.5%, Mem: 3.1GiB) ===
[info] input BW: 481.98MB/s (100 KB body x 49355 rps)
[info] saved results/echo-100k/512/fulmine.json
httparena-bench-fulmine
httparena-bench-fulmine
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/fulmine.json - 1 new, 32 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

MDA2AV added 3 commits August 30, 2026 01:32
The load body has been 10 KB since the LOH experiment; the name, the docs and
the parameter tables still said 100 KB. This makes the name true rather than
moving the body back.

Renamed throughout: the profile key and PROFILE_ORDER, endpoint_tool, the
CATALOG row and its description, ZRK_RATE_ECHO_10K, the doc directory, the wrk
fixture, and the tests array in all 104 subscribing meta.json files (each
re-parsed as JSON afterwards, since rebuild_site_data.py parses every one and a
broken quote there breaks the whole build).

Three claims in the profile doc were written for a 100 KB body and are false at
10 KB, so they are rewritten rather than renumbered: it is no longer "around
seven TLS records", it does not span "more than one socket buffer", and 10 KB
does emphatically leave in one write. What the profile measures at this size is
per-request overhead paid twice - once in, once out - which is what the section
now says.

Validation gains the benchmark's own size instead of only bracketing it: byte
-exact probes at 1 B, 1 KB, 10 KB and 100 KB, and the chunked probe now runs at
both 10 KB and 100 KB. Keeping 100 KB matters because it is larger than
anything the benchmark sends, so a handler that only works at the size it was
tuned for is still caught. That check carries more weight than it used to - the
paced generator sends one constant body, so validation is the only thing left
that makes answering without reading impossible.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
The rate is pinned, so every entry that holds it returns the same rps and the
composite cannot rank it on throughput the way it ranks the open-loop profiles.
It now contributes a 0-100 score built the same way latency-1m and latency-10k
build theirs: 0.60 CPU-per-request + 0.25 p99 + 0.15 p99.9, all multiplied by
the fraction of the offered rate actually held, with full credit at 47,500 -
95% of the 50,000 target, since the generator never quite reaches its own
number. CATALOG flags go True,True,False; infraScored stays False because
scoredForType() reads it ahead of `scored` and no infrastructure entry has run
this profile.

Both copies of the scoring are updated, because they are two copies:
LAT_FULL in site/leaderboard/index.html and FULL_RATE in
scripts/latency_score.py. check_badge_parity.js compares them and fails the
deploy when they drift; it passes here (637 ranks match).

The part that would have silently produced a board of zeros is in benchmark.sh
rather than in either scorer. Three gates named the two latency profiles
explicitly, and the score's own inputs come out of them: the mktemp cgroup
sampling directory that best-of-N selection reads, the fixed-rate validity
check, and the block that emits cpu_usec, cpu_per_req_us, target_rate,
rate_ratio and p99_9_latency onto the row. Without echo-10k in all three the
rows would carry no cpu_per_req_us and every entry would have scored 0 while
looking perfectly healthy. All three now include it.

`latency_score.py --table --profile echo-10k` is accepted and reports no
results yet, which is correct - nothing has been run and saved under the new
name.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
Merged rather than rebased on purpose: rebasing 30 commits means a force-push,
and a force-push at the wrong moment is what silently auto-closed PR #1363
earlier on this same work.

Only two files actually conflicted. The 99 result files did not - our side
deletes the dead upload-32/upload-256 rows and main's #1380 adds
latency-10k-1024 rows, and git resolved those as the non-overlapping edits they
are. Verified after the fact rather than assumed: 111 result files now carry
latency-10k rows, and 0 still carry upload rows.

README.md: main scored latency-10k in #1380 and dropped its reference-only
asterisk, while our side still marked it reference-only; main's line is
correct and is what survives. The Workload row takes our echo-10k line, but
without the asterisk our side had on it - that mark means "excluded from the
composite", and echo-10k is scored as of the previous commit.

search.js is generated, so it was regenerated rather than hand-merged.

Checked after merging: both CATALOG rows are scored True,True,False, LAT_FULL
and FULL_RATE agree on all three fixed-rate profiles, badge parity passes at
641 ranks, and the shell scripts still parse.

Claude-Session: https://claude.ai/code/session_01NsAGTadPkBtwXaYEngJomx
@MDA2AV

MDA2AV commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

/benchmark-test -t echo-10k --save

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Frameworks: all subscribed to echo-10k — 91 total

actix

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,401 195.7% 93MiB NEW NEW

aiohttp

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,381 770.4% 625MiB NEW NEW

aleph

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,343 431.0% 11.6GiB NEW NEW

aspnet-minimal

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,340 935.2% 314MiB NEW NEW

axum

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,058 239.4% 87MiB NEW NEW

blackbull

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,504 1022.6% 2.1GiB NEW NEW

bottle

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 17,958 4913.3% 2.6GiB NEW NEW

bun

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,357 401.5% 693MiB NEW NEW

carter

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,315 878.7% 240MiB NEW NEW

chi

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,107 1124.1% 88MiB NEW NEW

deno

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 39,296 5064.7% 5.0GiB NEW NEW

django

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 27,527 5152.2% 5.3GiB NEW NEW

drogon

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,402 260.2% 160MiB NEW NEW

echo

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,084 1147.2% 90MiB NEW NEW

elysia

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,362 1372.0% 1.6GiB NEW NEW

express

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,373 1147.8% 4.7GiB NEW NEW

fastapi

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,117 1310.5% 4.9GiB NEW NEW

fastendpoints

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,364 1053.0% 344MiB NEW NEW

fastify

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,313 1108.5% 3.9GiB NEW NEW

fastpysgi-wsgi

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,351 323.0% 448MiB NEW NEW

fiber

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,320 336.8% 83MiB NEW NEW

flask

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 16,688 4977.3% 3.3GiB NEW NEW

fletch

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,289 4149.5% 3.6GiB NEW NEW

fulmine

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,345 353.4% 3.1GiB NEW NEW

fulmine-tuned

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,413 354.2% 3.1GiB NEW NEW

genhttp-11

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,308 542.7% 351MiB NEW NEW

genhttp-11-ioxide

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,348 319.6% 701MiB NEW NEW

genhttp-11-kestrel

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,396 1025.9% 392MiB NEW NEW

genhttp-kestrel

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,352 1038.5% 322MiB NEW NEW

gin

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,060 1064.4% 86MiB NEW NEW

go-fasthttp

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,346 291.5% 99MiB NEW NEW

go-stdlib

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,986 1104.3% 87MiB NEW NEW

h2o-mruby

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,204 260.9% 305MiB NEW NEW

hanami

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,365 2937.4% 5.0GiB NEW NEW

helidon-production

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,263 583.7% 3.8GiB NEW NEW

helidon-tuned

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,313 554.8% 2.6GiB NEW NEW

hono-bun

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,101 1093.8% 1.7GiB NEW NEW

hono-node

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,251 2119.8% 10.0GiB NEW NEW

http4k

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,325 546.8% 3.4GiB NEW NEW

http4s

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 41,178 1073.5% 6.2GiB NEW NEW

httpjl

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,291 3871.9% 581MiB NEW NEW

humming-bird

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,072 701.2% 78MiB NEW NEW

hyper-express

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,347 439.7% 6.3GiB NEW NEW

hyperf

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,988 674.3% 1.9GiB NEW NEW

jooby

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 47,453 512.8% 8.8GiB NEW NEW

koa

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,364 922.2% 2.8GiB NEW NEW

ktor

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,202 750.0% 2.7GiB NEW NEW

ktor-ghost

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,249 694.6% 2.9GiB NEW NEW

lapis

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,403 582.4% 653MiB NEW NEW

litestar

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,407 1336.0% 4.4GiB NEW NEW

lute

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,262 196.0% 113MiB NEW NEW

micronaut

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,446 339.9% 2.0GiB NEW NEW

mojolicious

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 46,385 4204.9% 1016MiB NEW NEW

mq-bridge

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,161 259.4% 88MiB NEW NEW

mq-bridge-py

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,369 883.0% 2.9GiB NEW NEW

node

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,337 576.8% 2.0GiB NEW NEW

node-h3

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,366 1354.7% 4.3GiB NEW NEW

ntex

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,058 260.1% 63MiB NEW NEW

oxpecker

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,351 1013.8% 289MiB NEW NEW

phoenix-bandit

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,037 1936.7% 510MiB NEW NEW

php

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,355 1474.2% 3.8GiB NEW NEW

plug-cowboy

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,918 2253.0% 592MiB NEW NEW

pyronova

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 28,618 366.6% 902MiB NEW NEW

quarkus-jvm

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,395 764.1% 2.3GiB NEW NEW

rails

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,433 2905.6% 5.9GiB NEW NEW

reitit

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 47,243 531.7% 1.3GiB NEW NEW

ring-http-exchange

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,304 636.1% 941MiB NEW NEW

ring-jetty-adapter

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 47,726 576.5% 1.4GiB NEW NEW

ring-jetty9-adapter

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 47,391 547.2% 1.4GiB NEW NEW

roadrunner

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 12,881 582.8% 307MiB NEW NEW

rocket

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,705 489.0% 142MiB NEW NEW

roda

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,246 1421.8% 7.5GiB NEW NEW

salvo

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,852 252.0% 103MiB NEW NEW

servicestack

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 22,864 781.3% 419MiB NEW NEW

simplew

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,381 480.7% 324MiB NEW NEW

simplew-tuned

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,324 460.0% 3.5GiB NEW NEW

sinatra

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,275 1500.1% 7.1GiB NEW NEW

sisk

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,356 717.7% 285MiB NEW NEW

spring-boot

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,598 610.5% 1.2GiB NEW NEW

starlette

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,387 1080.0% 3.6GiB NEW NEW

swerver

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,423 221.8% 2.1GiB NEW NEW

swoole

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,356 282.9% 439MiB NEW NEW

symfony-spawn-tas

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 48,149 1569.4% 481MiB NEW NEW

trillium

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,058 244.1% 303MiB NEW NEW

trillium-tuned

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,418 269.9% 288MiB NEW NEW

true-async-server

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,363 324.1% 260MiB NEW NEW

ultimate-express

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,358 554.5% 7.4GiB NEW NEW

userver

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,423 679.2% 250MiB NEW NEW

uvicorn

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,239 750.1% 3.3GiB NEW NEW

vertx

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,247 305.4% 3.3GiB NEW NEW

vibed

Test Conn RPS CPU Mem Δ RPS Δ Mem
echo-10k 512 49,372 405.0% 225MiB NEW NEW

@MDA2AV
MDA2AV merged commit f7ea1c0 into main Aug 30, 2026
@MDA2AV
MDA2AV deleted the profiles/in-out-echo branch August 30, 2026 13:14
gquintard added a commit to gquintard/HttpArena that referenced this pull request Sep 1, 2026
upload was unscored and replaced by 8gbit upstream (MDA2AV#1373, MDA2AV#1382) before
this branch's meta.json change landed, so CI correctly rejected it as an
unknown profile. Removes the vmod's CountingWriter/upload_count and the
/upload VCL route along with it.

latency-10k already exists in the shared profile registry and drives the
same GET /baseline11 baseline already validates, so subscribing costs no
new code, same as latency-1m.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant