Skip to content

Commit edb7a78

Browse files
hyperpolymathclaude
andcommitted
bench(local-coord-mcp): micro-benchmarks for durability + coord ops
cartridges/local-coord-mcp/ffi/bench_coord.zig + build.zig `bench` step. Measures: - append no-op (durability closed) ~19 ns/op (52M ops/sec) - append peer_add (23B payload) ~4.7 µs/op (213k ops/sec) - append inbox_push (512B payload) ~6.4 µs/op (155k ops/sec) - replay peer_add ~4.1 µs/event (243k events/sec) - register + deregister, no durability ~134 ns (7.5M ops/sec) - register + deregister, durable ~9.0 µs (111k ops/sec) - send + receive round-trip, no dur ~111 ns (9.0M ops/sec) - send + receive round-trip, durable ~9.1 µs (110k ops/sec) - claim + release, no durability ~108 ns (9.3M ops/sec) - claim + release, durable ~9.0 µs (111k ops/sec) Durable-mode overhead is kernel sync cost — each mutation triggers a syscall to flush the append. ~110k ops/sec per peer is well inside the envelope for a localhost coord bus (few ops/sec per peer expected). Future optimisation note: batched writes could close most of the gap if sustained rate ever approaches the limit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bafcd25 commit edb7a78

2 files changed

Lines changed: 328 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
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+
}

cartridges/local-coord-mcp/ffi/build.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,21 @@ pub fn build(b: *std.Build) void {
5656

5757
const lib_step = b.step("lib", "Build shared library");
5858
lib_step.dependOn(&lib.step);
59+
60+
// ── Benchmarks ──────────────────────────────────────────────────
61+
const bench_mod = b.createModule(.{
62+
.root_source_file = b.path("bench_coord.zig"),
63+
.target = target,
64+
.optimize = .ReleaseFast,
65+
});
66+
bench_mod.addImport("cartridge_shim", shim_mod);
67+
68+
const bench_exe = b.addExecutable(.{
69+
.name = "bench_coord",
70+
.root_module = bench_mod,
71+
});
72+
73+
const run_bench = b.addRunArtifact(bench_exe);
74+
const bench_step = b.step("bench", "Run local-coord-mcp benchmarks");
75+
bench_step.dependOn(&run_bench.step);
5976
}

0 commit comments

Comments
 (0)