Conversation
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
|
/benchmark -f libioxd --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
|
CI failed only the two 100 KB echoes (0 bytes back), and it was a library bug rather than an entry one. The streamed echo sends each piece while the rest of the body is still arriving; on the hosted runner the body came in as small completions, enough to fill libioxd's 64-slot per-connection receive queue while the handler was parked on its first send, and the library's policy for a full queue was to end the input and reset the peer. Locally the same body arrives in 2 KB completions and never got near the mark, which is why validation passed here twice. Reproduced with a 4-deep queue: a 100 KB echo died after 16 KB. Fixed in libioxd (85b61bd): at the mark the multishot recv is paused, the socket's own window holds the peer, and the reader re-arms it as it drains; the burst the kernel had already posted is kept in a grown ring, so nothing delivered is dropped. The library's suites gained a 1 MiB streamed echo (also through a 4-deep queue under buffer starvation). The entry is unchanged; only the pinned commit moves. |
Benchmark ResultsFramework:
Full log |
|
/benchmark -f libioxd --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Full log |
|
/benchmark -f libioxd --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Full log |
|
/benchmark -f libioxd -t json-comp --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Full log |
|
potential ENOBUF buffer starvation in some tests, investigate. |
…ine-scored H1 profile plus static-tls
The library was renamed libioxd (github.com/MDA2AV/libioxd) and its API changed
(ioxd_bind + ioxd_run, reserve/advance into the reply slab, ioxd_delay on the
ring, TLS 1.3 with kTLS records, a JSON writer), so the entry follows: the
directory, meta.json, the Dockerfile's pinned commit and the handlers.
Subscribed to the profiles an engine is scored on - baseline, limited-conn,
async, latency-1m, latency-10k, json-tls, 8gbit - plus static-tls, and the
latency-500k-8cpu reference the old entry already ran. New handlers:
GET /delay/{ms} ioxd_delay: the kernel's timeout on the ring, per request
GET /json/{count}?m= the dataset (parsed once with cJSON) serialized per
request by the library's writer, total = price x quantity x m
POST /echo the body read from its framing straight into the reply
slab and sent as it is read; Content-Length kept, chunked streams
GET /static/{file} opened, sized and read from disk on every request, the
.br/.gz twin when Accept-Encoding takes it; nothing cached
TLS on :8081 from the mounted /certs pair: libioxd loads a <dir>/<host>/cert.pem
store, so the entry links a `default` host to /certs/server.crt and server.key.
scripts/validate.sh libioxd: 63 passed, 0 failed (baseline incl. the exhaustive
fragmentation sweep, async, json-tls, 8gbit, static-tls, TLS posture and quality).
The entry's rows in site/data (results, frameworks.json) carry the new name.
Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
… connection CI failed the two 100 KB echoes (Content-Length and chunked, 0 bytes back): the streamed echo sends each piece while the rest of the body is still arriving, and on the hosted runner the body came in as small completions - enough to fill the library's 64-slot per-connection queue while the handler was parked on its first send, which ended the input and reset the peer. libioxd now pauses the multishot recv at the queue's mark and re-arms it as the handler drains (the burst already posted is kept in a grown ring), so a slow consumer is held by the socket's window rather than dropped. The entry is unchanged; only the pinned commit moves. Validated locally again: 63/63. Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
The library change the previous pin pointed at was reverted upstream. Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
…SON writer 16 KB receive buffers, a 16 KB slab and a ring of 8192 (the fastest of what was measured against ioxide and edixoi), and a JSON writer that stores at the slab's tail instead of copying through the engine per fragment. Same box, same cores: json-tls 336k -> 607k rps, static-tls 353k -> 462k, the 10 KB TLS echo 359k -> 449k. The entry is unchanged; only the pin moves. Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
The static-tls handler is one call, ioxd_static_serve: the library keeps what a worker served in memory, checks the file on disk (inode, size, modification time) before serving it again, picks the .br or .gz twin by Accept-Encoding's q-values, and sends a body larger than the reply slab from where it is, in one message behind the head. The hand-rolled type table, the Accept-Encoding parser and the open/fstat/read loop are gone with it; the query parameter is ioxd_req_param. libioxd pinned at 2e7b12b, which brings the module, the slice search and the gather send. Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
ioxd_compress listed after the /json handler: brotli (quality 0, the one-pass coder) or gzip by the request's Accept-Encoding, applied by the engine at the reply's first flush - the serialized body is one encoder call and one message with the exact coded length - and nothing when the header is absent. The Dockerfile brings libbrotli and zlib, meta.json subscribes to json-comp, and libioxd is pinned at the commit that carries the middleware. Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
The entry never called ioxd_configure, so every worker ran the library default of 1024 x 16 KB provided buffers: 16 MB a worker, and 1 GB across the 64 workers the 8gbit profile spawns, which is where the 960 MiB that profile reports comes from. 512 halves it. A 10 KB echo body still arrives in a single delivery - measured, 1.00 recv completion a request with every one in the 8-12 KB bucket - and 512 still covers a worker's share of the 16384-connection profiles, 256, so no recv parks on -ENOBUFS. This is a memory change, not a CPU one. Against the arena's own 8gbit load (512 connections paced at 50k/s, the 10 KB POST /echo, CPU from the container's cgroup) it costs 12.26 us a request, inside the 11.2-12.5 band repeated runs of the unchanged entry produce, and a sweep of 1024/512/256/128 buffers is flat within noise on this hardware. Claude-Session: https://claude.ai/code/session_01Sbu8Z2J7wjoCZEjACmY9qj
|
/benchmark -f libioxd -t 8gbit --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Full log |
On this host libioxd costs 131.5 us of CPU a request on 8gbit against edixoi's 41.8, with average latency 203.7 us against 97.0 - while both deliver the same rate, 49308 against 49339 rps. Locally the same two arena images under the same paced load invert it: 12.26 us a request for libioxd against 15.77 for edixoi over TLS, and 6.62 against 6.78 plain. Excluded by measurement on that box: TLS itself, loopback MTU 1500, record fragmentation, the receive ring size, buffer starvation, socket buffer limits, io_uring setup, CPU spread, worker pinning, and unpinning as a remedy. What the two machines do not share is shape. The profile hands the entry 64 CPUs for a load paced at 50k rps, so it starts 64 workers that each serve ~770 rps and sleep in between, and nearly every request has to wake a thread on an idle core - here, one that may sit on another chiplet. Locally that regime is expensive and measurable: at 780 rps a worker a request costs 18.85 us against 13.67 at 3100 rps a worker, 28%. The script runs the profile unchanged - same generator arguments, connection count, cpusets, host tuning and cgroup CPU window as a scored run, all of it through the harness's own functions - varying only the worker count, which the entry already accepts as its first argument. If CPU a request falls sharply as workers drop, the server is sized wrong for a paced profile and the fix belongs in the library; if it stays flat, worker count is a red herring and the cost is per request inside the server on this hardware. framework_start gains one opt-in hook, FRAMEWORK_CMD_ARGS, so the diagnostic starts containers through the real path instead of reimplementing its docker flags and drifting from them. Unset - every existing caller - it runs the image's own CMD exactly as before. Claude-Session: https://claude.ai/code/session_01Sbu8Z2J7wjoCZEjACmY9qj
Summary
The engine behind the
libiomaentry was renamed libioxd (github.com/MDA2AV/libioxd, manual) and its API changed, so the entry follows: directory,meta.json, the Dockerfile's pinned commit (LIBIOXD_VERSION) and the handlers.The entry now subscribes to every H1 profile an engine is scored on -
baseline,limited-conn,async,latency-1m,latency-10k,json-tls,8gbit- plusstatic-tls, and keeps thelatency-500k-8cpureference it already ran.GET/POST /baseline11GET /delay/{ms}ioxd_delay: the kernel's timeout on the ring, one entry per request; the coroutine parksGET /json/{count}?m=total = price × quantity × mPOST /echoGET /static/{file}.br/.gztwin whenAccept-Encodingtakes itTLS on
:8081is TLS 1.3 only: OpenSSL runs the handshake, the kernel frames the records (kTLS transmit and receive), so the bench host needs thetlsmodule as it does for the ioxide entry. libioxd loads a<dir>/<host>/cert.pemstore, so the entry links adefaulthost to the mounted/certs/server.crtandserver.key.The entry's rows in
site/data(results,frameworks.json) carry the new name so the board does not keep alibiomaghost; the numbers are the old ones until the next benchmark run.Validation
./scripts/validate.sh libioxdon the final build: 63 passed, 0 failed - baseline (including the exhaustive fragmentation sweep, 9 shapes × 1047 offsets), async (32 overlapping delays), json-tls (items matched against the dataset), 8gbit (byte-exact at 1 B to 100 KB, Content-Length and chunked, empty body), static-tls (20 sizes, 15 compressed twins, both follow-the-disk probes), and the TLS posture and quality probes on:8081.🤖 Generated with Claude Code
https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P