From 6025b5a2aac182f3facfa1e3d669edb25767783f Mon Sep 17 00:00:00 2001 From: Dragos Varovici Date: Sat, 21 Feb 2026 22:52:45 +0200 Subject: [PATCH 1/3] Adds zig dusty framework benchmark --- frameworks/Zig/dusty/.gitignore | 2 + frameworks/Zig/dusty/README.md | 25 ++++ frameworks/Zig/dusty/benchmark_config.json | 26 ++++ frameworks/Zig/dusty/build.zig | 58 +++++++++ frameworks/Zig/dusty/build.zig.zon | 24 ++++ frameworks/Zig/dusty/dusty.dockerfile | 30 +++++ frameworks/Zig/dusty/src/endpoints.zig | 136 +++++++++++++++++++++ frameworks/Zig/dusty/src/main.zig | 98 +++++++++++++++ frameworks/Zig/dusty/src/pool.zig | 84 +++++++++++++ 9 files changed, 483 insertions(+) create mode 100644 frameworks/Zig/dusty/.gitignore create mode 100644 frameworks/Zig/dusty/README.md create mode 100644 frameworks/Zig/dusty/benchmark_config.json create mode 100644 frameworks/Zig/dusty/build.zig create mode 100644 frameworks/Zig/dusty/build.zig.zon create mode 100644 frameworks/Zig/dusty/dusty.dockerfile create mode 100644 frameworks/Zig/dusty/src/endpoints.zig create mode 100644 frameworks/Zig/dusty/src/main.zig create mode 100644 frameworks/Zig/dusty/src/pool.zig diff --git a/frameworks/Zig/dusty/.gitignore b/frameworks/Zig/dusty/.gitignore new file mode 100644 index 00000000000..d8c8979f82c --- /dev/null +++ b/frameworks/Zig/dusty/.gitignore @@ -0,0 +1,2 @@ +.zig-cache +zig-out diff --git a/frameworks/Zig/dusty/README.md b/frameworks/Zig/dusty/README.md new file mode 100644 index 00000000000..df1b2b944ff --- /dev/null +++ b/frameworks/Zig/dusty/README.md @@ -0,0 +1,25 @@ + +# [Dusty](https://github.com/lalinsky/dusty) - Zig HTTP client/server library + +## Description + +Zig HTTP client/server library built on top of zio (coroutine/async engine) + +## Test URLs + +### Test 1: JSON Encoding + + http://localhost:3000/json + +### Test 2: Plaintext + + http://localhost:3000/plaintext + +### Test 2: Single Row Query + + http://localhost:3000/db + +### Test 4: Fortunes (Template rendering) + + http://localhost:3000/fortunes + diff --git a/frameworks/Zig/dusty/benchmark_config.json b/frameworks/Zig/dusty/benchmark_config.json new file mode 100644 index 00000000000..b20effb89c0 --- /dev/null +++ b/frameworks/Zig/dusty/benchmark_config.json @@ -0,0 +1,26 @@ +{ + "framework": "dusty", + "tests": [{ + "default": { + "json_url": "/json", + "plaintext_url": "/plaintext", + "db_url": "/db", + "fortune_url": "/fortunes", + "port": 3000, + "approach": "Realistic", + "classification": "Fullstack", + "database": "Postgres", + "framework": "dusty", + "language": "Zig", + "flavor": "None", + "orm": "raw", + "platform": "None", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "Dusty (Zig)", + "notes": "", + "versus": "" + } + }] +} diff --git a/frameworks/Zig/dusty/build.zig b/frameworks/Zig/dusty/build.zig new file mode 100644 index 00000000000..794a04d7b13 --- /dev/null +++ b/frameworks/Zig/dusty/build.zig @@ -0,0 +1,58 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const dep_opts = .{ .target = target, .optimize = optimize }; + + const root_module = b.addModule("root_mod", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/main.zig"), + }); + + const dusty_dep = b.dependency("dusty", dep_opts); + const dusty_module = dusty_dep.module("dusty"); + const zio_module = dusty_dep.builder.dependency("zio", dep_opts).module("zio"); + const pg_module = b.dependency("pg", dep_opts).module("pg"); + const datetimez_module = b.dependency("datetimez", dep_opts).module("datetime"); + + const exe = b.addExecutable(.{ + .name = "dusty", + .root_module = root_module, + }); + + exe.root_module.addImport("dusty", dusty_module); + exe.root_module.addImport("zio", zio_module); + exe.root_module.addImport("pg", pg_module); + exe.root_module.addImport("datetimez", datetimez_module); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/frameworks/Zig/dusty/build.zig.zon b/frameworks/Zig/dusty/build.zig.zon new file mode 100644 index 00000000000..cd25187bc91 --- /dev/null +++ b/frameworks/Zig/dusty/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .dusty_testing, + .fingerprint = 0x402c0022f1133ce0, + .version = "0.1.1", + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, + .dependencies = .{ + .pg = .{ + .url = "git+https://github.com/karlseguin/pg.zig.git#f8d4892387fbad2abdf775783e101e50a7114335", + .hash = "pg-0.0.0-Wp_7gag6BgD_QAZrPhNNEGpnUZR_LEkKT40Ura3p-4yX", + }, + .dusty = .{ + .url = "git+https://github.com/lalinsky/dusty.git#8aaedf71a069758bd88437d117ccd89069dc9bf8", + .hash = "dusty-0.0.0-Qdw7Rqh_CQDJNptlxOVIRgT4DxHnAKT9KohxfNhSH9bC", + }, + .datetimez = .{ + .url = "git+https://github.com/frmdstryr/zig-datetime.git#3a39a21e6e34dcb0ade0ff828d0914d40ba535f3", + .hash = "datetime-0.8.0-cJNXzP_YAQBxQ5hkNNP6ScnG5XsqciJmeP5RVV4xwCBA", + }, + }, +} diff --git a/frameworks/Zig/dusty/dusty.dockerfile b/frameworks/Zig/dusty/dusty.dockerfile new file mode 100644 index 00000000000..2be267c748e --- /dev/null +++ b/frameworks/Zig/dusty/dusty.dockerfile @@ -0,0 +1,30 @@ +FROM debian:12.9 + +ENV PG_USER=benchmarkdbuser +ENV PG_PASS=benchmarkdbpass +ENV PG_DB=hello_world +ENV PG_HOST=tfb-database +ENV PG_PORT=5432 + +WORKDIR /app + +COPY src src +COPY build.zig.zon build.zig.zon +COPY build.zig build.zig + +ARG ZIG_VER=0.15.2 + +RUN apt-get update && apt-get install -y wget xz-utils ca-certificates + +RUN wget https://ziglang.org/download/${ZIG_VER}/zig-$(uname -m)-linux-${ZIG_VER}.tar.xz + +RUN tar -xvf zig-$(uname -m)-linux-${ZIG_VER}.tar.xz + +RUN mv zig-$(uname -m)-linux-${ZIG_VER} /usr/local/zig + +ENV PATH="/usr/local/zig:$PATH" +RUN zig build -Doptimize=ReleaseFast + +EXPOSE 3000 + +CMD ["zig-out/bin/dusty"] diff --git a/frameworks/Zig/dusty/src/endpoints.zig b/frameworks/Zig/dusty/src/endpoints.zig new file mode 100644 index 00000000000..9c173d4ff11 --- /dev/null +++ b/frameworks/Zig/dusty/src/endpoints.zig @@ -0,0 +1,136 @@ +const std = @import("std"); +const dusty = @import("dusty"); +const pg = @import("pg"); +const datetimez = @import("datetimez"); + +pub var date_str: [29]u8 = undefined; + +pub const Global = struct { + pool: *pg.Pool, + rand: *std.Random, +}; + +const World = struct { + id: i32, + randomNumber: i32, +}; + +const Fortune = struct { + id: i32, + message: []const u8, +}; + +pub fn plaintext(_: *Global, _: *dusty.Request, res: *dusty.Response) !void { + try setHeaders(res); + + try res.header("Content-Type", "text/plain"); + res.body = "Hello, World!"; +} + +pub fn json(_: *Global, _: *dusty.Request, res: *dusty.Response) !void { + try setHeaders(res); + + try res.json(.{ .message = "Hello, World!" }, .{}); +} + +pub fn db(global: *Global, _: *dusty.Request, res: *dusty.Response) !void { + try setHeaders(res); + + const random_number = 1 + (global.rand.uintAtMostBiased(u32, 9999)); + + const world = getWorld(global.pool, random_number) catch |err| { + std.debug.print("Error querying database: {}\n", .{err}); + return; + }; + + try res.json(world, .{}); +} + +pub fn fortune(global: *Global, _: *dusty.Request, res: *dusty.Response) !void { + try setHeaders(res); + + const fortunes_html = try getFortunesHtml(res.arena, global.pool); + + try res.header("Content-Type", "text/html; charset=utf-8"); + res.body = fortunes_html; +} + +fn getWorld(pool: *pg.Pool, random_number: u32) !World { + var conn = try pool.acquire(); + defer conn.release(); + + const row_result = try conn.row("SELECT id, randomNumber FROM World WHERE id = $1", .{random_number}); + + var row = row_result.?; + defer row.deinit() catch {}; + + return World{ .id = row.get(i32, 0), .randomNumber = row.get(i32, 1) }; +} + +fn setHeaders(res: *dusty.Response) !void { + try res.header("Server", "Dusty"); + try res.header("Date", try res.arena.dupe(u8, &date_str)); +} + +fn getFortunesHtml(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const u8 { + const fortunes = try getFortunes(allocator, pool); + + var sb = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0); + + const writer = sb.writer(allocator); + try sb.appendSlice(allocator, "Fortunes"); + + for (fortunes) |ft| { + try writer.print("", .{ + ft.id, + try escape_html(allocator, ft.message), + }); + } + + try sb.appendSlice(allocator, "
idmessage
{d}{s}
"); + + return sb.toOwnedSlice(allocator); +} + +fn getFortunes(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const Fortune { + var conn = try pool.acquire(); + defer conn.release(); + + var rows = try conn.query("SELECT id, message FROM Fortune", .{}); + defer rows.deinit(); + + var fortunes = try std.ArrayListUnmanaged(Fortune).initCapacity(allocator, 0); + defer fortunes.deinit(allocator); + + while (try rows.next()) |row| { + const current_fortune = Fortune{ .id = row.get(i32, 0), .message = row.get([]const u8, 1) }; + try fortunes.append(allocator, current_fortune); + } + + const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." }; + try fortunes.append(allocator, zero_fortune); + + const fortunes_slice = try fortunes.toOwnedSlice(allocator); + std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage); + + return fortunes_slice; +} + +fn cmpFortuneByMessage(_: void, a: Fortune, b: Fortune) bool { + return std.mem.order(u8, a.message, b.message).compare(std.math.CompareOperator.lt); +} + +fn escape_html(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { + var output = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0); + defer output.deinit(allocator); + + for (input) |char| { + switch (char) { + '<' => try output.appendSlice(allocator, "<"), + '>' => try output.appendSlice(allocator, ">"), + else => try output.append(allocator, char), + } + } + + return output.toOwnedSlice(allocator); +} diff --git a/frameworks/Zig/dusty/src/main.zig b/frameworks/Zig/dusty/src/main.zig new file mode 100644 index 00000000000..780db1a19b1 --- /dev/null +++ b/frameworks/Zig/dusty/src/main.zig @@ -0,0 +1,98 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const dusty = @import("dusty"); +const zio = @import("zio"); +const pg = @import("pg"); +const datetimez = @import("datetimez"); +const pool = @import("pool.zig"); + +const endpoints = @import("endpoints.zig"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{ + .thread_safe = true, + }){}; + defer { + if (builtin.mode == .Debug) _ = gpa.deinit(); + } + + const allocator = if (builtin.mode == .Debug) gpa.allocator() else std.heap.smp_allocator; + + var io = try zio.Runtime.init(allocator, .{}); + defer io.deinit(); + + var task = try zio.spawn(runServer, .{ allocator, io }); + try task.join(); +} + +fn runServer(allocator: std.mem.Allocator, io: *zio.Runtime) !void { + _ = io; + + var pg_pool = try pool.initPool(allocator); + defer pg_pool.deinit(); + + const date_thread = try std.Thread.spawn(.{}, struct { + fn update() !void { + while (true) { + const now = datetimez.datetime.Date.now(); + const time = datetimez.datetime.Time.now(); + + const TB_DATE_FMT = "{s:0>3}, {d:0>2} {s:0>3} {d:0>4} {d:0>2}:{d:0>2}:{d:0>2} GMT"; + _ = try std.fmt.bufPrint(&endpoints.date_str, TB_DATE_FMT, .{ now.weekdayName()[0..3], now.day, now.monthName()[0..3], now.year, time.hour, time.minute, time.second }); + std.Thread.sleep(std.time.ns_per_ms * 980); + } + } + }.update, .{}); + + date_thread.detach(); + + var prng: std.Random.DefaultPrng = .init(@as(u64, @bitCast(std.time.milliTimestamp()))); + + var rand = prng.random(); + + var global = endpoints.Global{ + .pool = pg_pool, + .rand = &rand, + }; + + const port: u16 = 3000; + + const DustyServer = dusty.Server(endpoints.Global); + + const config: dusty.ServerConfig = .{ + .timeout = .{ + .request = 60 * std.time.ms_per_s, + .keepalive = 300 * std.time.ms_per_s, + } + }; + + var server = DustyServer.init(allocator, config, &global); + defer server.deinit(); + + server.router.get("/json", endpoints.json); + server.router.get("/plaintext", endpoints.plaintext); + server.router.get("/db", endpoints.db); + server.router.get("/fortunes", endpoints.fortune); + + std.debug.print("Dusty listening at 0.0.0.0:{d}\n", .{port}); + + const addr = try zio.net.IpAddress.parseIp("0.0.0.0", port); + + var listen_task = try zio.spawn(DustyServer.listen, .{ &server, addr }); + defer listen_task.cancel(); + + var sigint = try zio.Signal.init(.interrupt); + defer sigint.deinit(); + + var sigterm = try zio.Signal.init(.terminate); + defer sigterm.deinit(); + + const result = try zio.select(.{ .task = &listen_task, .sigint = &sigint, .sigterm = &sigterm }); + switch (result) { + .task => |r| return r, + .sigint, .sigterm => { + listen_task.cancel(); + return; + }, + } +} diff --git a/frameworks/Zig/dusty/src/pool.zig b/frameworks/Zig/dusty/src/pool.zig new file mode 100644 index 00000000000..b8944b547db --- /dev/null +++ b/frameworks/Zig/dusty/src/pool.zig @@ -0,0 +1,84 @@ +const std = @import("std"); +const pg = @import("pg"); + +const Allocator = std.mem.Allocator; +const Pool = pg.Pool; + +pub fn initPool(allocator: Allocator) !*pg.Pool { + const info = try parsePostgresConnStr(allocator); + + const pg_pool = try Pool.init(allocator, .{ + .size = 56, + .connect = .{ + .port = info.port, + .host = info.hostname, + }, + .auth = .{ + .username = info.username, + .database = info.database, + .password = info.password, + }, + .timeout = 10_000, + }); + + return pg_pool; +} + +pub const ConnectionInfo = struct { + username: []const u8, + password: []const u8, + hostname: []const u8, + port: u16, + database: []const u8, +}; + +fn addressAsString(address: std.net.Address) ![]const u8 { + const bytes = @as(*const [4]u8, @ptrCast(&address.in.sa.addr)); + + var buffer: [256]u8 = undefined; + var source = std.io.StreamSource{ .buffer = std.io.fixedBufferStream(&buffer) }; + var writer = source.writer(); + + //try writer.writeAll("Hello, World!"); + + try writer.print("{}.{}.{}.{}", .{ + bytes[0], + bytes[1], + bytes[2], + bytes[3], + }); + + const output = source.buffer.getWritten(); + + return output; +} + +fn parsePostgresConnStr(allocator: Allocator) !ConnectionInfo { + const pg_port = try getEnvVar(allocator, "PG_PORT", "5432"); + // std.debug.print("tfb port {s}\n", .{pg_port}); + var port = try std.fmt.parseInt(u16, pg_port, 0); + + if (port == 0) { + port = 5432; + } + + return ConnectionInfo{ + .username = try getEnvVar(allocator, "PG_USER", "benchmarkdbuser"), + .password = try getEnvVar(allocator, "PG_PASS", "benchmarkdbpass"), + .hostname = try getEnvVar(allocator, "PG_HOST", "localhost"), + .port = port, + .database = try getEnvVar(allocator, "PG_DB", "hello_world"), + }; +} + +fn getEnvVar(allocator: Allocator, name: []const u8, default: []const u8) ![]const u8 { + const env_var = std.process.getEnvVarOwned(allocator, name) catch |err| switch (err) { + error.EnvironmentVariableNotFound => return default, + error.OutOfMemory => return err, + error.InvalidWtf8 => return err, + }; + + if (env_var.len == 0) return default; + + return env_var; +} From a16fdf71bbd0387e94dda4e8d8509f8940636ac2 Mon Sep 17 00:00:00 2001 From: Dragos Varovici Date: Sun, 22 Feb 2026 07:57:16 +0200 Subject: [PATCH 2/3] Zig dusty dockerfile fixes --- frameworks/Zig/dusty/dusty.dockerfile | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/frameworks/Zig/dusty/dusty.dockerfile b/frameworks/Zig/dusty/dusty.dockerfile index 2be267c748e..a0b960494ec 100644 --- a/frameworks/Zig/dusty/dusty.dockerfile +++ b/frameworks/Zig/dusty/dusty.dockerfile @@ -1,10 +1,4 @@ -FROM debian:12.9 - -ENV PG_USER=benchmarkdbuser -ENV PG_PASS=benchmarkdbpass -ENV PG_DB=hello_world -ENV PG_HOST=tfb-database -ENV PG_PORT=5432 +FROM debian:12.9 AS build WORKDIR /app @@ -14,7 +8,7 @@ COPY build.zig build.zig ARG ZIG_VER=0.15.2 -RUN apt-get update && apt-get install -y wget xz-utils ca-certificates +RUN apt-get update && apt-get install -y wget xz-utils ca-certificates git RUN wget https://ziglang.org/download/${ZIG_VER}/zig-$(uname -m)-linux-${ZIG_VER}.tar.xz @@ -25,6 +19,20 @@ RUN mv zig-$(uname -m)-linux-${ZIG_VER} /usr/local/zig ENV PATH="/usr/local/zig:$PATH" RUN zig build -Doptimize=ReleaseFast + +FROM debian:12-slim + +ENV PG_USER=benchmarkdbuser +ENV PG_PASS=benchmarkdbpass +ENV PG_DB=hello_world +ENV PG_HOST=tfb-database +ENV PG_PORT=5432 + +RUN apt-get -qq update +RUN apt-get -qy install ca-certificates + +COPY --from=build /app/zig-out/bin/dusty /server + EXPOSE 3000 -CMD ["zig-out/bin/dusty"] +ENTRYPOINT ["/server"] From ac134db3d64cb9cc722174d7505e916a1d7c48a7 Mon Sep 17 00:00:00 2001 From: Dragos Varovici Date: Sun, 22 Feb 2026 14:36:05 +0200 Subject: [PATCH 3/3] Zig dusty maintainers --- frameworks/Zig/dusty/benchmark_config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frameworks/Zig/dusty/benchmark_config.json b/frameworks/Zig/dusty/benchmark_config.json index b20effb89c0..a34aa4c3d46 100644 --- a/frameworks/Zig/dusty/benchmark_config.json +++ b/frameworks/Zig/dusty/benchmark_config.json @@ -1,5 +1,6 @@ { "framework": "dusty", + "maintainers": ["dragosv"], "tests": [{ "default": { "json_url": "/json",