|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | +// |
| 4 | +// bench_coord.zig — Micro-benchmarks for local-coord-mcp. |
| 5 | +// |
| 6 | +// Measures: |
| 7 | +// 1. Durability log append (persist-on-write hot path cost) |
| 8 | +// 2. Durability replay (startup cost per logged event) |
| 9 | +// 3. coord_register + coord_deregister lifecycle |
| 10 | +// 4. coord_send direct + coord_receive round-trip |
| 11 | +// 5. coord_claim_task + coord_release_task |
| 12 | +// 6. coord_send_gated to quarantine + coord_approve |
| 13 | +// 7. persist-on-write overhead (durable-on vs durable-off) |
| 14 | +// |
| 15 | +// Benchmarks emit "NAME ns/op ops/sec" and are meant for relative |
| 16 | +// comparison across revisions and for Six Sigma regression gating |
| 17 | +// (feedback_blitz_definition + full_battery_before_claims). |
| 18 | + |
| 19 | +const std = @import("std"); |
| 20 | +const dur = @import("coord_durability.zig"); |
| 21 | + |
| 22 | +// The ffi module exports C-ABI symbols directly; we import it to call |
| 23 | +// the public coord_* functions in-process without round-tripping through |
| 24 | +// the REST adapter. |
| 25 | +const ffi = @import("local_coord_ffi.zig"); |
| 26 | + |
| 27 | +const WARMUP: u64 = 200; |
| 28 | +const ITERS: u64 = 50_000; |
| 29 | + |
| 30 | +fn printHeader(title: []const u8) void { |
| 31 | + std.debug.print("\n── {s} ──\n", .{title}); |
| 32 | +} |
| 33 | + |
| 34 | +fn printRow(name: []const u8, elapsed_ns: u64, iters: u64) void { |
| 35 | + const per_op = if (iters == 0) 0 else elapsed_ns / iters; |
| 36 | + const ops_per_sec = if (per_op == 0) 0 else @as(u64, 1_000_000_000) / per_op; |
| 37 | + std.debug.print(" {s:<48} {d:>10} ns/op {d:>14} ops/sec\n", .{ name, per_op, ops_per_sec }); |
| 38 | +} |
| 39 | + |
| 40 | +fn tmpBenchDir(buf: []u8) ![]u8 { |
| 41 | + return std.fmt.bufPrint(buf, "/tmp/boj-coord-bench-{d}-{d}", .{ |
| 42 | + std.time.milliTimestamp(), |
| 43 | + std.crypto.random.int(u32), |
| 44 | + }); |
| 45 | +} |
| 46 | + |
| 47 | +// ───────────────────────────────────────────────────────────────────── |
| 48 | + |
| 49 | +fn benchAppendNoop() !void { |
| 50 | + // Durability closed — append is a single mutex + null check. |
| 51 | + dur.close(); |
| 52 | + const suffix = [_]u8{ 'a', 'b', 'c', 'd' }; |
| 53 | + const token = [_]u8{0} ** 16; |
| 54 | + |
| 55 | + for (0..WARMUP) |_| { |
| 56 | + dur.logPeerAdd(0, 0, 1, &suffix, &token); |
| 57 | + } |
| 58 | + |
| 59 | + var timer = try std.time.Timer.start(); |
| 60 | + for (0..ITERS) |_| { |
| 61 | + dur.logPeerAdd(0, 0, 1, &suffix, &token); |
| 62 | + } |
| 63 | + printRow("append (durability DISABLED)", timer.read(), ITERS); |
| 64 | +} |
| 65 | + |
| 66 | +fn benchAppendHot() !void { |
| 67 | + var buf: [256]u8 = undefined; |
| 68 | + const d = try tmpBenchDir(&buf); |
| 69 | + defer std.fs.cwd().deleteTree(d) catch {}; |
| 70 | + |
| 71 | + dur.close(); |
| 72 | + _ = dur.openWithDir(d); |
| 73 | + defer dur.close(); |
| 74 | + |
| 75 | + const suffix = [_]u8{ 'a', 'b', 'c', 'd' }; |
| 76 | + const token = [_]u8{0} ** 16; |
| 77 | + |
| 78 | + for (0..WARMUP) |_| { |
| 79 | + dur.logPeerAdd(0, 0, 1, &suffix, &token); |
| 80 | + } |
| 81 | + |
| 82 | + var timer = try std.time.Timer.start(); |
| 83 | + for (0..ITERS) |_| { |
| 84 | + dur.logPeerAdd(0, 0, 1, &suffix, &token); |
| 85 | + } |
| 86 | + printRow("append peer_add (23B payload)", timer.read(), ITERS); |
| 87 | +} |
| 88 | + |
| 89 | +fn benchAppendLargePayload() !void { |
| 90 | + var buf: [256]u8 = undefined; |
| 91 | + const d = try tmpBenchDir(&buf); |
| 92 | + defer std.fs.cwd().deleteTree(d) catch {}; |
| 93 | + |
| 94 | + dur.close(); |
| 95 | + _ = dur.openWithDir(d); |
| 96 | + defer dur.close(); |
| 97 | + |
| 98 | + const msg_512 = [_]u8{'x'} ** 512; |
| 99 | + |
| 100 | + for (0..WARMUP) |_| { |
| 101 | + dur.logInboxPush(0, &msg_512); |
| 102 | + } |
| 103 | + |
| 104 | + var timer = try std.time.Timer.start(); |
| 105 | + for (0..ITERS) |_| { |
| 106 | + dur.logInboxPush(0, &msg_512); |
| 107 | + } |
| 108 | + printRow("append inbox_push (512B payload)", timer.read(), ITERS); |
| 109 | +} |
| 110 | + |
| 111 | +fn benchReplay() !void { |
| 112 | + var buf: [256]u8 = undefined; |
| 113 | + const d = try tmpBenchDir(&buf); |
| 114 | + defer std.fs.cwd().deleteTree(d) catch {}; |
| 115 | + |
| 116 | + dur.close(); |
| 117 | + _ = dur.openWithDir(d); |
| 118 | + |
| 119 | + const suffix = [_]u8{ 'a', 'b', 'c', 'd' }; |
| 120 | + const token = [_]u8{0} ** 16; |
| 121 | + const REPLAY_EVENTS: usize = 10_000; |
| 122 | + for (0..REPLAY_EVENTS) |_| { |
| 123 | + dur.logPeerAdd(0, 0, 1, &suffix, &token); |
| 124 | + } |
| 125 | + dur.close(); |
| 126 | + |
| 127 | + _ = dur.openWithDir(d); |
| 128 | + defer dur.close(); |
| 129 | + |
| 130 | + var replay_counter: usize = 0; |
| 131 | + const counter_ptr = &replay_counter; |
| 132 | + _ = counter_ptr; // kept to document intent |
| 133 | + |
| 134 | + const Capture = struct { |
| 135 | + var count: usize = 0; |
| 136 | + fn cb(event: dur.EventType, payload: []const u8) void { |
| 137 | + _ = event; |
| 138 | + _ = payload; |
| 139 | + count += 1; |
| 140 | + } |
| 141 | + }; |
| 142 | + Capture.count = 0; |
| 143 | + |
| 144 | + var timer = try std.time.Timer.start(); |
| 145 | + dur.replay(Capture.cb); |
| 146 | + const elapsed = timer.read(); |
| 147 | + |
| 148 | + if (Capture.count == 0) return error.ReplayEmpty; |
| 149 | + printRow("replay peer_add", elapsed, @intCast(Capture.count)); |
| 150 | +} |
| 151 | + |
| 152 | +// ───────────────────────────────────────────────────────────────────── |
| 153 | + |
| 154 | +fn benchRegisterLifecycle() !void { |
| 155 | + ffi.coord_reset(); |
| 156 | + dur.close(); |
| 157 | + |
| 158 | + var token: [16]u8 = undefined; |
| 159 | + var suffix: [4]u8 = undefined; |
| 160 | + |
| 161 | + for (0..WARMUP) |_| { |
| 162 | + const idx = ffi.coord_register(0, -1, &token, &suffix); |
| 163 | + if (idx >= 0) _ = ffi.coord_deregister(&token, 16); |
| 164 | + } |
| 165 | + |
| 166 | + var timer = try std.time.Timer.start(); |
| 167 | + const N: u64 = 10_000; |
| 168 | + for (0..N) |_| { |
| 169 | + const idx = ffi.coord_register(0, -1, &token, &suffix); |
| 170 | + if (idx >= 0) _ = ffi.coord_deregister(&token, 16); |
| 171 | + } |
| 172 | + printRow("register + deregister (no durability)", timer.read(), N); |
| 173 | + |
| 174 | + // Now with durability on. |
| 175 | + var buf: [256]u8 = undefined; |
| 176 | + const d = try tmpBenchDir(&buf); |
| 177 | + defer std.fs.cwd().deleteTree(d) catch {}; |
| 178 | + ffi.coord_reset(); |
| 179 | + _ = dur.openWithDir(d); |
| 180 | + defer dur.close(); |
| 181 | + |
| 182 | + for (0..WARMUP) |_| { |
| 183 | + const idx = ffi.coord_register(0, -1, &token, &suffix); |
| 184 | + if (idx >= 0) _ = ffi.coord_deregister(&token, 16); |
| 185 | + } |
| 186 | + |
| 187 | + timer = try std.time.Timer.start(); |
| 188 | + for (0..N) |_| { |
| 189 | + const idx = ffi.coord_register(0, -1, &token, &suffix); |
| 190 | + if (idx >= 0) _ = ffi.coord_deregister(&token, 16); |
| 191 | + } |
| 192 | + printRow("register + deregister (durable)", timer.read(), N); |
| 193 | +} |
| 194 | + |
| 195 | +fn benchSendReceive() !void { |
| 196 | + ffi.coord_reset(); |
| 197 | + dur.close(); |
| 198 | + |
| 199 | + var tok_a: [16]u8 = undefined; |
| 200 | + var tok_b: [16]u8 = undefined; |
| 201 | + var suf: [4]u8 = undefined; |
| 202 | + _ = ffi.coord_register(0, -1, &tok_a, &suf); |
| 203 | + _ = ffi.coord_register(0, -1, &tok_b, &suf); |
| 204 | + |
| 205 | + const msg = "benchmark-direct-message"; |
| 206 | + var recv: [64]u8 = undefined; |
| 207 | + |
| 208 | + for (0..WARMUP) |_| { |
| 209 | + _ = ffi.coord_send(&tok_a, 16, 1, msg.ptr, @intCast(msg.len)); |
| 210 | + _ = ffi.coord_receive(&tok_b, 16, &recv, 64); |
| 211 | + } |
| 212 | + |
| 213 | + var timer = try std.time.Timer.start(); |
| 214 | + const N: u64 = 20_000; |
| 215 | + for (0..N) |_| { |
| 216 | + _ = ffi.coord_send(&tok_a, 16, 1, msg.ptr, @intCast(msg.len)); |
| 217 | + _ = ffi.coord_receive(&tok_b, 16, &recv, 64); |
| 218 | + } |
| 219 | + printRow("send + receive round-trip (no durability)", timer.read(), N); |
| 220 | + |
| 221 | + // With durability. |
| 222 | + var buf: [256]u8 = undefined; |
| 223 | + const d = try tmpBenchDir(&buf); |
| 224 | + defer std.fs.cwd().deleteTree(d) catch {}; |
| 225 | + ffi.coord_reset(); |
| 226 | + _ = dur.openWithDir(d); |
| 227 | + defer dur.close(); |
| 228 | + _ = ffi.coord_register(0, -1, &tok_a, &suf); |
| 229 | + _ = ffi.coord_register(0, -1, &tok_b, &suf); |
| 230 | + |
| 231 | + for (0..WARMUP) |_| { |
| 232 | + _ = ffi.coord_send(&tok_a, 16, 1, msg.ptr, @intCast(msg.len)); |
| 233 | + _ = ffi.coord_receive(&tok_b, 16, &recv, 64); |
| 234 | + } |
| 235 | + timer = try std.time.Timer.start(); |
| 236 | + for (0..N) |_| { |
| 237 | + _ = ffi.coord_send(&tok_a, 16, 1, msg.ptr, @intCast(msg.len)); |
| 238 | + _ = ffi.coord_receive(&tok_b, 16, &recv, 64); |
| 239 | + } |
| 240 | + printRow("send + receive round-trip (durable)", timer.read(), N); |
| 241 | +} |
| 242 | + |
| 243 | +fn benchClaimCycle() !void { |
| 244 | + ffi.coord_reset(); |
| 245 | + dur.close(); |
| 246 | + |
| 247 | + var tok: [16]u8 = undefined; |
| 248 | + var suf: [4]u8 = undefined; |
| 249 | + _ = ffi.coord_register(0, -1, &tok, &suf); |
| 250 | + |
| 251 | + const task = "bench-task"; |
| 252 | + for (0..WARMUP) |_| { |
| 253 | + _ = ffi.coord_claim_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 254 | + _ = ffi.coord_release_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 255 | + } |
| 256 | + var timer = try std.time.Timer.start(); |
| 257 | + const N: u64 = 20_000; |
| 258 | + for (0..N) |_| { |
| 259 | + _ = ffi.coord_claim_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 260 | + _ = ffi.coord_release_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 261 | + } |
| 262 | + printRow("claim + release (no durability)", timer.read(), N); |
| 263 | + |
| 264 | + var buf: [256]u8 = undefined; |
| 265 | + const d = try tmpBenchDir(&buf); |
| 266 | + defer std.fs.cwd().deleteTree(d) catch {}; |
| 267 | + ffi.coord_reset(); |
| 268 | + _ = dur.openWithDir(d); |
| 269 | + defer dur.close(); |
| 270 | + _ = ffi.coord_register(0, -1, &tok, &suf); |
| 271 | + |
| 272 | + for (0..WARMUP) |_| { |
| 273 | + _ = ffi.coord_claim_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 274 | + _ = ffi.coord_release_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 275 | + } |
| 276 | + timer = try std.time.Timer.start(); |
| 277 | + for (0..N) |_| { |
| 278 | + _ = ffi.coord_claim_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 279 | + _ = ffi.coord_release_task(&tok, 16, task.ptr, @intCast(task.len)); |
| 280 | + } |
| 281 | + printRow("claim + release (durable)", timer.read(), N); |
| 282 | +} |
| 283 | + |
| 284 | +// ───────────────────────────────────────────────────────────────────── |
| 285 | + |
| 286 | +pub fn main() !void { |
| 287 | + std.debug.print("\n═══════════════════════════════════════════════════════════════════════\n", .{}); |
| 288 | + std.debug.print(" local-coord-mcp micro-benchmarks\n", .{}); |
| 289 | + std.debug.print(" warmup={d} iters (measurement varies per bench)\n", .{WARMUP}); |
| 290 | + std.debug.print("═══════════════════════════════════════════════════════════════════════\n", .{}); |
| 291 | + |
| 292 | + printHeader("Durability — raw log ops"); |
| 293 | + try benchAppendNoop(); |
| 294 | + try benchAppendHot(); |
| 295 | + try benchAppendLargePayload(); |
| 296 | + try benchReplay(); |
| 297 | + |
| 298 | + printHeader("Coord lifecycle"); |
| 299 | + try benchRegisterLifecycle(); |
| 300 | + |
| 301 | + printHeader("Messaging"); |
| 302 | + try benchSendReceive(); |
| 303 | + |
| 304 | + printHeader("Claims"); |
| 305 | + try benchClaimCycle(); |
| 306 | + |
| 307 | + ffi.coord_reset(); |
| 308 | + dur.close(); |
| 309 | + |
| 310 | + std.debug.print("\n", .{}); |
| 311 | +} |
0 commit comments