diff --git a/frameworks/zix-grpc/.gitignore b/frameworks/zix-grpc/.gitignore new file mode 100644 index 000000000..595d20d1a --- /dev/null +++ b/frameworks/zix-grpc/.gitignore @@ -0,0 +1,4 @@ +.zig-cache +zig-out +zig-package +vendor diff --git a/frameworks/zix-grpc/Dockerfile b/frameworks/zix-grpc/Dockerfile index 0a095f82d..722310e21 100644 --- a/frameworks/zix-grpc/Dockerfile +++ b/frameworks/zix-grpc/Dockerfile @@ -4,9 +4,10 @@ FROM alpine:3.20 AS build ARG RETRY=6 ARG TARGETARCH ARG RETRY_DELAY=3 +ARG TIMEOUT_SEC=180 ARG ZIG_VERSION=0.16.0 -ARG ZIX_VERSION=0.4.x-rc3 -RUN apk add --no-cache ca-certificates curl git tar xz +ARG ZIX_VERSION=0.5.x-rc1 +RUN apk add --no-cache ca-certificates curl git tar xz openssl RUN set -eu; \ case "${TARGETARCH:-amd64}" in \ @@ -14,31 +15,29 @@ RUN set -eu; \ arm64) ZIG_ARCH=aarch64 ;; \ *) echo "unsupported arch: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ - curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \ + curl -fSL -m ${TIMEOUT_SEC} "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \ | tar -xJ -C /opt; \ mv "/opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}" /opt/zig ENV PATH="/opt/zig:${PATH}" -# Vendor zix X.Y.Z, separate layer so source-only rebuilds skip the fetch. -# The Http1 raw engine work this image needs (large-body drain plus the per-worker -# response cache used by the /json endpoint) must be present on the X.Y.Z branch. -# Four ordered attempts before giving up: curl the archive tarball from github then -# codeberg, then a shallow git clone from github then codeberg. The github archive -# redirects to codeload.github.com (which the benchmark runner may not resolve), so -# curl can fall through to the codeberg tarball, and git clone talks to github.com -# and codeberg.org directly as the deeper fallback. RETRY and RETRY_DELAY bound -# every attempt. +WORKDIR /server +COPY build.zig build.zig.zon ./ +COPY src ./src + +# Vendor zix ${ZIX_VERSION} in its own layer so source-only rebuilds skip the fetch. +# Tries curl tarball then git clone, github before codeberg. +# RETRY / RETRY_DELAY bound every attempt. RUN set -eu; \ fetch() { \ - rm -rf /src/vendor/zix; mkdir -p /src/vendor/zix; \ - curl -fsSL --retry ${RETRY} --retry-delay ${RETRY_DELAY} --retry-all-errors "$1" -o /tmp/zix.tar.gz \ - && tar -xz --strip-components=1 -C /src/vendor/zix -f /tmp/zix.tar.gz; \ + rm -rf /server/vendor/zix; mkdir -p /server/vendor/zix; \ + curl -fSL --retry ${RETRY} --retry-delay ${RETRY_DELAY} --retry-all-errors "$1" -o /tmp/zix.tar.gz \ + && tar -xz --strip-components=1 -C /server/vendor/zix -f /tmp/zix.tar.gz; \ }; \ clone() { \ attempt=0; \ while [ "${attempt}" -lt "${RETRY}" ]; do \ - rm -rf /src/vendor/zix; \ - git clone --depth 1 --branch "${ZIX_VERSION}" "$1" /src/vendor/zix && return 0; \ + rm -rf /server/vendor/zix; \ + git clone --depth 1 --branch "${ZIX_VERSION}" "$1" /server/vendor/zix && return 0; \ attempt=$((attempt + 1)); \ sleep "${RETRY_DELAY}"; \ done; \ @@ -53,17 +52,25 @@ RUN set -eu; \ clone "https://codeberg.org/prothegee/zix.git" \ || { echo "FAILED: git clone ${RETRY} times from codeberg" >&2; exit 1; }; }; }; } -WORKDIR /src -COPY build.zig build.zig.zon ./ -COPY src ./src +# Add +aes+pclmul: x86_64_v3 omits AES-NI / PCLMUL, +# so zix TLS would compile the ~40x slower software AES-GCM. +# Every x86_64_v3 CPU has them, so it is safe. RUN set -eu; \ case "${TARGETARCH:-amd64}" in \ amd64) ZIG_TARGET=x86_64-linux-musl; ZIG_CPU=x86_64_v3 ;; \ arm64) ZIG_TARGET=aarch64-linux-musl; ZIG_CPU=baseline ;; \ esac; \ - zig build -Dtarget="${ZIG_TARGET}" -Dcpu="${ZIG_CPU}" --release=fast + zig build -Dtarget="${ZIG_TARGET}" -Dcpu="${ZIG_CPU}+aes+pclmul+adx" --release=fast + +# Self-signed Ed25519 cert generated at image build, baked at /etc/zix-tls. +RUN set -eu; \ + mkdir -p /etc/zix-tls; \ + openssl genpkey -algorithm ED25519 -out /etc/zix-tls/server.key; \ + openssl req -new -x509 -key /etc/zix-tls/server.key -out /etc/zix-tls/server.crt \ + -days 3650 -subj "/CN=localhost" FROM alpine:3.20 -COPY --from=build /src/zig-out/bin/zix-grpc /zix-grpc -EXPOSE 8080 +COPY --from=build /server/zig-out/bin/zix-grpc /zix-grpc +COPY --from=build /etc/zix-tls /etc/zix-tls +EXPOSE 8080 8443/tcp ENTRYPOINT ["/zix-grpc"] diff --git a/frameworks/zix-grpc/meta.json b/frameworks/zix-grpc/meta.json index 4aac2379e..d29e960a7 100644 --- a/frameworks/zix-grpc/meta.json +++ b/frameworks/zix-grpc/meta.json @@ -1,14 +1,16 @@ { - "display_name": "zix-grpc", + "display_name": "zix", "language": "Zig", "type": "engine", - "engine": "zix", - "description": "Zig gRPC server (h2c) on the zix.Grpc engine built on zix.Http2 (no std.http). Shared-nothing by design: each worker owns its own SO_REUSEPORT listener, io_uring completion ring, and connections, with no shared state or locking across cores, multiplexing HTTP/2 streams per connection. Replies use comptime-cached HPACK blocks. Implements GetSum (unary) and StreamSum (server-streaming).", - "repo": "https://codeberg.org/prothegee/zix", + "engine": "zix.Grpc URING Dispatch Model", + "description": "Zig gRPC server on the zix.Grpc engine (native h2 framing), .URING dispatch: shared-nothing per-core io_uring with SO_REUSEPORT, unary plus server-streaming with DATA-frame coalescing. Dual listener (config.tls_port): h2c 8080 plus gRPC over TLS 1.3 on 8443, one process.", + "repo": "https://github.com/prothegee/zix", "enabled": true, "tests": [ "unary-grpc", - "stream-grpc" + "stream-grpc", + "unary-grpc-tls", + "stream-grpc-tls" ], "maintainers": ["prothegee"] } diff --git a/frameworks/zix-grpc/src/main.zig b/frameworks/zix-grpc/src/main.zig index 3c8305303..fb04e943f 100644 --- a/frameworks/zix-grpc/src/main.zig +++ b/frameworks/zix-grpc/src/main.zig @@ -1,9 +1,16 @@ //! HttpArena: zix-grpc //! -//! zix HttpArena gRPC (h2c) entry point. +//! zix HttpArena gRPC entry point. //! -//! Intent: demonstrate zix.Grpc (EPOLL dispatch model) against the HttpArena -//! gRPC benchmark suite (unary, server-streaming). +//! Intent: demonstrate zix.Grpc (URING dispatch model) against the HttpArena +//! gRPC benchmark suite (unary, server-streaming), cleartext h2c and over TLS. +//! +//! ONE server, two listeners through config.tls_port (dual listener): +//! - h2c cleartext on PORT (8080). Serves unary-grpc and stream-grpc. +//! - gRPC over TLS 1.3 on TLS_PORT (8443), ALPN h2, self-signed Ed25519 cert +//! baked at /etc/zix-tls, terminated on the same per-core .URING workers +//! (no second launch, no doubled workers or fd tables). +//! Serves unary-grpc-tls and stream-grpc-tls. //! //! Design choices: //! - GetSum: unary SumRequest{a, b} -> SumReply{a + b}. The compute is a single @@ -12,32 +19,21 @@ //! - StreamSum: server-streaming, count replies of a + b + i. //! - max_streams is wide enough that a client opening many parallel streams is //! never refused at startup. + const std = @import("std"); const zix = @import("zix"); // --------------------------------------------------------- // +const IP: []const u8 = "::"; const PORT: u16 = 8080; -/// Required for ipv4 and ipv6 -const LISTEN_IP: []const u8 = "::"; const DISPATCH_MODEL: zix.Grpc.DispatchModel = .URING; -const KERNEL_BACKLOG: u31 = 1024 * 16; -const WORKERS: usize = 0; -/// 0 selects the engine default EPOLL pool (max(10, cpu*2)). Each worker owns one connection -/// while it is active. After the Phase 1 syscall cuts the unary path is CPU-bound, not -/// connection-bound: a modest cpu-relative pool tops out throughput, while an oversized pool -/// (thread-per-connection) thrashes the scheduler and collapses it. So keep the default. -const POOL_SIZE: usize = 0; - -/// Advertise enough concurrent streams that a client opening many in parallel (h2load uses -/// -m 100) is never refused at startup. Must be >= the load generator's stream count or those -/// streams get REFUSED_STREAM. Per-stream buffers are tiny (below), so a wide table is cheap. -const MAX_STREAMS: usize = 128; +const WORKERS: usize = 0; -/// gRPC sum messages are a few bytes. A small per-stream body buffer keeps the wide stream -/// table affordable in memory (MAX_STREAMS * MAX_BODY per connection). -const MAX_BODY: usize = 4 * 1024; +const TLS_PORT: u16 = 8443; +const TLS_CERT_DEFAULT: []const u8 = "/etc/zix-tls/server.crt"; +const TLS_KEY_DEFAULT: []const u8 = "/etc/zix-tls/server.key"; // --------------------------------------------------------- // @@ -108,20 +104,39 @@ fn streamSumHandler(headers: []const zix.Http2.Header, ctx: *zix.Grpc.Context) v // --------------------------------------------------------- // +const Routes = &[_]zix.Grpc.Route{ + .{ .path = "/benchmark.BenchmarkService/GetSum", .handler = getSumHandler }, + .{ .path = "/benchmark.BenchmarkService/StreamSum", .handler = streamSumHandler, .is_server_streaming = true }, +}; + pub fn main(process: std.process.Init) !void { - var server = try zix.Grpc.Server.init(&[_]zix.Grpc.Route{ - .{ .path = "/benchmark.BenchmarkService/GetSum", .handler = getSumHandler }, - .{ .path = "/benchmark.BenchmarkService/StreamSum", .handler = streamSumHandler, .is_server_streaming = true }, - }, .{ + var allocator_tls = std.heap.ArenaAllocator.init(std.heap.smp_allocator); + defer allocator_tls.deinit(); + + var tls = zix.Tls.Context.init(allocator_tls.allocator(), process.io, .{ + .cert_path = TLS_CERT_DEFAULT, + .key_path = TLS_KEY_DEFAULT, + .alpn = &.{.H2}, + .min_version = .TLS_1_3, + }) catch |e| { + std.debug.print("Error tls context: {}\n", .{e}); + return; + }; + defer tls.deinit(); + + var server = zix.Grpc.Server.init(Routes, .{ .io = process.io, - .ip = LISTEN_IP, + .ip = IP, .port = PORT, + .tls = &tls, + .tls_port = TLS_PORT, .dispatch_model = DISPATCH_MODEL, - .kernel_backlog = KERNEL_BACKLOG, - .workers = WORKERS, - .pool_size = POOL_SIZE, - .max_streams = MAX_STREAMS, - .max_body = MAX_BODY, + .kernel_backlog = 24 * 1024, + .max_streams = 1024, + .max_frame_size = 24 * 1024, + .max_recv_buf = 64 * 1024, + .max_body = 32 * 1024, + .tls_write_buf_initial_bytes = 32 * 1024, }); defer server.deinit(); diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 3852d9bfe..149cae5be 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -926,6 +926,34 @@ "type": "engine", "engine": "veb" }, + "WebFramework C": { + "dir": "web-framework-cc", + "description": "Web framework in C++ with different language APIs(Python, C, C++, C#)", + "repo": "https://github.com/LazyPanda07/WebFramework", + "type": "production", + "engine": "WebFramework" + }, + "WebFramework C++": { + "dir": "web-framework-cpp", + "description": "Web framework in C++ with different language APIs(Python, C, C++, C#)", + "repo": "https://github.com/LazyPanda07/WebFramework", + "type": "production", + "engine": "WebFramework" + }, + "WebFramework C#": { + "dir": "web-framework-csharp", + "description": "Web framework in C++ with different language APIs(Python, C, C++, C#)", + "repo": "https://github.com/LazyPanda07/WebFramework", + "type": "production", + "engine": "WebFramework" + }, + "WebFramework Python": { + "dir": "web-framework-python", + "description": "Web framework in C++ with different language APIs(Python, C, C++, C#)", + "repo": "https://github.com/LazyPanda07/WebFramework", + "type": "production", + "engine": "WebFramework" + }, "workerman-websocket": { "dir": "workerman-websocket", "description": "An asynchronous event driven PHP socket framework. Supports HTTP, Websocket, SSL and other custom protocols.", @@ -982,13 +1010,6 @@ "type": "engine", "engine": "io_uring" }, - "zix-grpc": { - "dir": "zix-grpc", - "description": "Zig gRPC server (h2c) on the zix.Grpc engine built on zix.Http2 (no std.http). Shared-nothing by design: each worker owns its own SO_REUSEPORT listener, io_uring completion ring, and connections, with no shared state or locking across cores, multiplexing HTTP/2 streams per connection. Replies use comptime-cached HPACK blocks. Implements GetSum (unary) and StreamSum (server-streaming).", - "repo": "https://codeberg.org/prothegee/zix", - "type": "engine", - "engine": "zix" - }, "zix": { "dir": "zix", "description": "Zig HTTP/1.1 server on the zix.Http1 raw engine (no std.http). Shared-nothing: each worker runs its own SO_REUSEPORT multishot accept plus io_uring completion loop and owns its connections. The /json endpoint serves from the per-worker response cache, and request bodies larger than the read buffer are drained rather than buffered.", @@ -996,6 +1017,13 @@ "type": "engine", "engine": "zix", "variants": [ + { + "dir": "zix-grpc", + "description": "Zig gRPC server on the zix.Grpc engine (native h2 framing), .URING dispatch: shared-nothing per-core io_uring with SO_REUSEPORT, unary plus server-streaming with DATA-frame coalescing. Dual listener (config.tls_port): h2c 8080 plus gRPC over TLS 1.3 on 8443, one process.", + "repo": "https://github.com/prothegee/zix", + "type": "engine", + "engine": "zix.Grpc URING Dispatch Model" + }, { "dir": "zix-http2", "description": "Zig HTTP/2 server on the zix.Http2 raw engine, .URING dispatch: shared-nothing per-core io_uring with SO_REUSEPORT, per-worker stream-slot pool. Dual listener (config.tls_port): h2c 8082 plus h2 over TLS 1.3 (ALPN h2) on 8443, one process.", diff --git a/site/data/results/zix.json b/site/data/results/zix.json index 67822789b..4110064cc 100644 --- a/site/data/results/zix.json +++ b/site/data/results/zix.json @@ -234,6 +234,120 @@ "status_4xx": 0, "status_5xx": 0 }, + "stream-grpc-64": { + "framework": "zix", + "language": "Zig", + "rps": 9622000, + "avg_latency": "126.83ms", + "p99_latency": "480.49ms", + "cpu": "29.1%", + "memory": "100MiB", + "connections": 64, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "0", + "reconnects": 0, + "status_2xx": 9622, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "stream-grpc-tls-64": { + "framework": "zix", + "language": "Zig", + "rps": 9665000, + "avg_latency": "126.17ms", + "p99_latency": "461.50ms", + "cpu": "40.0%", + "memory": "109MiB", + "connections": 64, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "0", + "reconnects": 0, + "status_2xx": 9665, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "unary-grpc-1024": { + "framework": "zix", + "language": "Zig", + "rps": 7115742, + "avg_latency": "7.43ms", + "p99_latency": "27.84ms", + "cpu": "3940.5%", + "memory": "205MiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "452.37MB/s", + "reconnects": 0, + "status_2xx": 35934500, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "unary-grpc-256": { + "framework": "zix", + "language": "Zig", + "rps": 7211013, + "avg_latency": "2.58ms", + "p99_latency": "79.99ms", + "cpu": "3887.7%", + "memory": "174MiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "456.61MB/s", + "reconnects": 0, + "status_2xx": 36271400, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "unary-grpc-tls-1024": { + "framework": "zix", + "language": "Zig", + "rps": 6911600, + "avg_latency": "7.42ms", + "p99_latency": "55.69ms", + "cpu": "3997.0%", + "memory": "281MiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "440.26MB/s", + "reconnects": 0, + "status_2xx": 34972700, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "unary-grpc-tls-256": { + "framework": "zix", + "language": "Zig", + "rps": 7007733, + "avg_latency": "2.38ms", + "p99_latency": "11.09ms", + "cpu": "3914.4%", + "memory": "199MiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "443.73MB/s", + "reconnects": 0, + "status_2xx": 35248900, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, "upload-256": { "framework": "zix", "language": "Zig", diff --git a/site/static/logs/stream-grpc-tls/64/zix-grpc.log b/site/static/logs/stream-grpc-tls/64/zix-grpc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/unary-grpc-tls/1024/zix-grpc.log b/site/static/logs/unary-grpc-tls/1024/zix-grpc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/unary-grpc-tls/256/zix-grpc.log b/site/static/logs/unary-grpc-tls/256/zix-grpc.log new file mode 100644 index 000000000..e69de29bb