//! Render macro-benchmark: scripted, deterministic scenarios through the //! REAL engine pipeline — UiApp + Runtime + the null platform with //! binary packet presents enabled, so the retained/patch protocol, the //! frame planner, and the wire encoders all run exactly as they do under //! a live host. Measures end-to-end latency per interaction (input //! dispatch through present) and per-stage attribution via the runtime's //! frame profile (`rebuild`/`layout`/`reconcile`/`emit`/`plan`/`patch`/ //! `encode`/`present`). //! //! What it deliberately does NOT measure: the macOS host's CoreText //! rasterization and Metal upload (the `host_decode`/`host_draw` stages) //! — those need a live window; use `native automate profile on` against //! a running app (or the gpu smokes) for that half. The engine-side //! `present` stage here is the null platform's packet recorder, so its //! cost is the wire handoff floor, not a paint. //! //! Run: //! //! zig build bench-render -Doptimize=ReleaseFast //! //! Deterministic inputs (fixed fixtures, synthetic timestamps, estimator //! text metrics); wall-clock durations are the measurement. Medians and //! p90s over N warm iterations after warmup; rows where p90 > 2.5x p50 //! are flagged `noisy` so one descheduled iteration cannot pass as a //! regression (or an improvement). //! //! Ratchet mode: //! //! zig build bench-render -Doptimize=ReleaseFast -- --check tools/bench-render-budgets.txt //! //! Runs the whole suite `check_passes` times, takes the MEDIAN e2e p50 //! per scenario across passes (one descheduled pass cannot fail the //! gate), and compares against the committed per-scenario budgets. //! Budgets carry ~30%+ headroom over healthy numbers: this mode exists //! to catch order-of-magnitude regressions and accidental O(n^2) //! reintroductions, not machine noise — see the budgets file for the //! per-scenario rationale. Refuses to run outside ReleaseFast (budgets //! are calibrated for it). Every scenario must have a budget and every //! budget must name a scenario, so renames cannot silently un-gate. const std = @import("std"); const builtin = @import("builtin"); const native_sdk = @import("native_sdk"); const canvas = native_sdk.canvas; const geometry = native_sdk.geometry; const runtime_mod = native_sdk.runtime; const Harness = native_sdk.TestHarness(); const FrameProfileStage = runtime_mod.FrameProfileStage; const stage_values = std.enums.values(FrameProfileStage); const canvas_label = "bench-canvas"; const surface_width: f32 = 1200; const surface_height: f32 = 800; const frame_interval_ns: u64 = 16_666_667; const warmup_iterations: usize = 8; const measured_iterations: usize = 40; const first_frame_iterations: usize = 12; const gpa = std.heap.page_allocator; // --------------------------------------------------------------- series const max_series_samples = 256; const Series = struct { samples: [max_series_samples]u64 = undefined, len: usize = 0, fn push(self: *Series, value: u64) void { if (self.len >= self.samples.len) return; self.samples[self.len] = value; self.len += 1; } /// Nearest-rank percentile in microseconds. fn percentileUs(self: *const Series, percentile: usize) u64 { if (self.len == 0) return 0; var sorted: [max_series_samples]u64 = undefined; @memcpy(sorted[0..self.len], self.samples[0..self.len]); std.sort.pdq(u64, sorted[0..self.len], {}, std.sort.asc(u64)); const rank = (self.len * percentile + 99) / 100; return sorted[@max(rank, 1) - 1] / std.time.ns_per_us; } }; const StageStats = struct { p50_us: u64 = 0, p90_us: u64 = 0, count: u64 = 0, window: usize = 0, }; const ScenarioReport = struct { name: []const u8, detail: []const u8, iterations: usize, e2e_p50_us: u64, e2e_p90_us: u64, stages: [stage_values.len]StageStats, fn noisy(self: *const ScenarioReport) bool { return self.e2e_p90_us > (self.e2e_p50_us * 5) / 2; } }; fn captureStages(runtime: *native_sdk.Runtime) [stage_values.len]StageStats { var stages: [stage_values.len]StageStats = undefined; inline for (stage_values, 0..) |stage, index| { const stats = runtime.frame_profile.stats(stage); stages[index] = .{ .p50_us = stats.p50_us, .p90_us = stats.p90_us, .count = stats.total, .window = stats.window_len }; } return stages; } // ------------------------------------------------------------- scaffold const bench_shell_views = [_]native_sdk.ShellView{.{ .label = canvas_label, .kind = .gpu_surface, .fill = true, }}; const bench_shell_windows = [_]native_sdk.ShellWindow{.{ .label = "main", .title = "bench", .width = surface_width, .height = surface_height, .restore_state = false, .views = &bench_shell_views, }}; const bench_scene: native_sdk.ShellConfig = .{ .windows = &bench_shell_windows }; /// Harness + app pair driving one fixture through the real event loop. fn Bench(comptime AppT: type) type { return struct { const Self = @This(); harness: *Harness, app: *AppT, frame_index: u64 = 0, timestamp_ns: u64 = 1_000_000_000, fn create(options: AppT.Options) !Self { const harness = try Harness.create(gpa, .{ .size = geometry.SizeF.init(surface_width, surface_height) }); harness.null_platform.gpu_surfaces = true; // The production macOS transport: compact binary packets over // the retained/patch protocol. harness.null_platform.gpu_surface_packet_binary = true; // Production runners disable the per-frame diagnostics // preview (a second, unrecorded frame plan per present); // mirror them so the baseline measures the shipped path. harness.runtime.options.gpu_surface_frame_diagnostics = false; const app = try gpa.create(AppT); app.* = AppT.init(gpa, .{}, options); var self = Self{ .harness = harness, .app = app }; try harness.start(app.app()); // Install frame: first present builds the retained baseline. try self.frame(); return self; } fn destroy(self: *Self) void { self.app.deinit(); gpa.destroy(self.app); self.harness.destroy(gpa); } fn runtime(self: *Self) *native_sdk.Runtime { return &self.harness.runtime; } /// One presented frame with advancing synthetic clocks. fn frame(self: *Self) !void { self.frame_index += 1; self.timestamp_ns += frame_interval_ns; try self.harness.runtime.dispatchPlatformEvent(self.app.app(), .{ .gpu_surface_frame = .{ .label = canvas_label, .size = geometry.SizeF.init(surface_width, surface_height), .scale_factor = 2, .frame_index = self.frame_index, .timestamp_ns = self.timestamp_ns, .nonblank = true, } }); } fn automation(self: *Self, comptime format: []const u8, args: anytype) !void { var buffer: [192]u8 = undefined; const line = try std.fmt.bufPrint(&buffer, format, args); try self.harness.runtime.dispatchAutomationCommand(self.app.app(), line); } fn timer(self: *Self, id: u64) !void { try self.harness.runtime.dispatchPlatformEvent(self.app.app(), .{ .timer = .{ .id = id, .timestamp_ns = self.timestamp_ns, } }); } }; } fn findWidgetByKind(widget: canvas.Widget, kind: canvas.WidgetKind) ?canvas.Widget { if (widget.kind == kind) return widget; for (widget.children) |child| { if (findWidgetByKind(child, kind)) |found| return found; } return null; } // ------------------------------------ fixture: big form (~930 widgets) const big_form_rows = 28; const big_form_columns = 32; const big_form_items = big_form_rows * big_form_columns; const BigItem = struct { title: []const u8, }; var big_item_title_storage: [big_form_items][12]u8 = undefined; var big_items: [big_form_items]BigItem = undefined; var big_rows: [big_form_rows][]const BigItem = undefined; fn initBigFormFixture() void { for (0..big_form_items) |index| { const title = std.fmt.bufPrint(&big_item_title_storage[index], "it {d}", .{index}) catch unreachable; big_items[index] = .{ .title = title }; } for (0..big_form_rows) |row| { big_rows[row] = big_items[row * big_form_columns .. (row + 1) * big_form_columns]; } } const BigFormMsg = union(enum) { toggle_flag, submit, }; const BigFormModel = struct { flag: bool = false, }; fn bigFormUpdate(model: *BigFormModel, msg: BigFormMsg) void { switch (msg) { .toggle_flag => model.flag = !model.flag, .submit => {}, } } const BigFormApp = native_sdk.UiApp(BigFormModel, BigFormMsg); const BigFormUi = BigFormApp.Ui; fn bigItemKey(item: *const BigItem) canvas.UiKey { return canvas.uiKey(item.title); } fn bigItemView(ui: *BigFormUi, flag: bool, item: *const BigItem) BigFormUi.Node { _ = flag; // Definite width: 32 items x (34 + 2 gap) fits the 1200pt surface, // so layout never logs overflow (stderr writes would distort the // measurement). return ui.listItem(.{ .width = 34, .height = 22 }, item.title); } fn bigRowKey(row: *const []const BigItem) canvas.UiKey { return canvas.uiKey(row.*[0].title); } fn bigRowView(ui: *BigFormUi, flag: bool, row: *const []const BigItem) BigFormUi.Node { return ui.row(.{ .height = 24, .gap = 2 }, ui.eachCtx(flag, row.*, bigItemKey, bigItemView)); } fn bigFormTextLeaf(ui: *BigFormUi, kind: canvas.WidgetKind, options: BigFormUi.ElementOptions, content: []const u8) BigFormUi.Node { var node = ui.el(kind, options, .{}); node.widget.text = content; return node; } fn bigFormView(ui: *BigFormUi, model: *const BigFormModel) BigFormUi.Node { return ui.column(.{ .padding = 8, .gap = 4 }, .{ ui.row(.{ .height = 32, .gap = 12, .cross = .center }, .{ bigFormTextLeaf(ui, .text_field, .{ .width = 260, .semantics = .{ .label = "Bench field" }, .on_submit = BigFormMsg.submit, }, "seed"), bigFormTextLeaf(ui, .switch_control, .{ .checked = model.flag, .value = if (model.flag) 1 else 0, .semantics = .{ .label = "Bench toggle" }, .on_toggle = BigFormMsg.toggle_flag, }, "Flag"), ui.spacer(1), }), ui.column(.{ .grow = 1, .gap = 2 }, ui.eachCtx(model.flag, big_rows[0..], bigRowKey, bigRowView)), }); } fn bigFormOptions() BigFormApp.Options { return .{ .name = "bench-big-form", .scene = bench_scene, .canvas_label = canvas_label, .update = bigFormUpdate, .view = bigFormView, }; } // -------------------------------------- fixture: markdown transcript const transcript_messages = 200; var transcript_storage: [transcript_messages][192]u8 = undefined; var transcript_sources: [transcript_messages][]const u8 = undefined; fn initTranscriptFixture() void { for (0..transcript_messages) |index| { // Two inline spans per message (bold speaker + prose) keeps 200 // messages inside `max_canvas_widget_spans_per_view` (1024). transcript_sources[index] = std.fmt.bufPrint( &transcript_storage[index], "**speaker-{d}**: reply {d} lands the retained-canvas fix for pass {d}, wrapping to a second line on narrow panes.", .{ index % 7, index, index % 13 }, ) catch unreachable; } } const TranscriptMsg = union(enum) { scrolled: canvas.ScrollState, }; const TranscriptModel = struct { offset: f32 = 0, }; fn transcriptUpdate(model: *TranscriptModel, msg: TranscriptMsg) void { switch (msg) { .scrolled => |scroll| model.offset = scroll.offset, } } const TranscriptApp = native_sdk.UiApp(TranscriptModel, TranscriptMsg); const TranscriptUi = TranscriptApp.Ui; const TranscriptMarkdown = canvas.markdown.Markdown(TranscriptMsg); fn transcriptMessageKey(source: *const []const u8) canvas.UiKey { return canvas.uiKey(source.*); } fn transcriptMessageView(ui: *TranscriptUi, context: void, source: *const []const u8) TranscriptUi.Node { _ = context; return TranscriptMarkdown.view(ui, source.*, .{}); } fn transcriptView(ui: *TranscriptUi, model: *const TranscriptModel) TranscriptUi.Node { return ui.scroll(.{ .grow = 1, .value = model.offset, .on_scroll = TranscriptUi.scrollMsg(.scrolled), .semantics = .{ .label = "Transcript" }, }, ui.column(.{ .gap = 10, .padding = 12 }, ui.eachCtx({}, transcript_sources[0..], transcriptMessageKey, transcriptMessageView))); } fn transcriptOptions() TranscriptApp.Options { return .{ .name = "bench-transcript", .scene = bench_scene, .canvas_label = canvas_label, .update = transcriptUpdate, .view = transcriptView, }; } // -------------------- fixture: measured-text chat (provider path) // The provider-path regression class: a live text measure provider // (CoreText on macOS) turns every text measurement into a host call, so // the interesting number is measured CALLS per interaction, not just // wall time. This fixture mirrors the profiled hot case — a focused // input above a couple dozen wrapped chat messages, full TEA rebuild // per keystroke — against a counting synthetic provider with // kerning-ish per-cluster advances (additive, like the class the // batched-seam parity law covers). The scenario asserts a hard cap on // provider calls per keystroke: the pre-batching seam measured every // growing line prefix once per cluster (tens of thousands of calls per // keystroke at this fixture size), the batched seam plus the retained // caches keep steady-state typing to a handful. const measured_chat_messages = 24; var measured_chat_storage: [measured_chat_messages][192]u8 = undefined; var measured_chat_sources: [measured_chat_messages][]const u8 = undefined; fn initMeasuredChatFixture() void { for (0..measured_chat_messages) |index| { measured_chat_sources[index] = std.fmt.bufPrint( &measured_chat_storage[index], "**voice-{d}**: message {d} in the measured transcript wraps across several lines at pane width, with `inline code` and *emphasis* mixed in for span variety.", .{ index % 5, index }, ) catch unreachable; } } const MeasuredCounters = struct { unit_calls: u64 = 0, /// Bytes measured through the per-prefix seam. THE ratchet metric: /// the convicted regression class measures the growing line prefix /// once per cluster, which is quadratic in BYTES while staying /// modest in calls — the healthy steady state is a handful of /// whole-slice widths per frame (line bounds and label widths), so /// bytes separate the two regimes by orders of magnitude where raw /// call counts blur them. unit_bytes: u64 = 0, batch_calls: u64 = 0, fn total(self: MeasuredCounters) u64 { return self.unit_calls + self.batch_calls; } }; var measured_counters: MeasuredCounters = .{}; /// Kerning-ish synthetic advance: varies per cluster lead byte and byte /// length so cumulative widths are irregular like shaped text, while /// staying additive — the class the batched seam contract covers. fn measuredClusterAdvance(font_id: u64, size: f32, cluster: []const u8) f32 { const lead: f32 = @floatFromInt(cluster[0] % 13); const len: f32 = @floatFromInt(cluster.len); const font: f32 = @floatFromInt(font_id % 5); return size * (0.31 + lead * 0.037 + len * 0.041 + font * 0.011); } fn measuredMeasureText(context: ?*anyopaque, font_id: u64, size: f32, text: []const u8) f32 { _ = context; measured_counters.unit_calls += 1; measured_counters.unit_bytes += text.len; var width: f32 = 0; var index: usize = 0; while (index < text.len) { const next = @min(text.len, index + canvas.utf8SequenceLength(text[index])); width += measuredClusterAdvance(font_id, size, text[index..next]); index = next; } return width; } fn measuredMeasureTextAdvances(context: ?*anyopaque, font_id: u64, size: f32, text: []const u8, advances: []f32) bool { _ = context; measured_counters.batch_calls += 1; var index: usize = 0; while (index < text.len) { const next = @min(text.len, index + canvas.utf8SequenceLength(text[index])); advances[index] = measuredClusterAdvance(font_id, size, text[index..next]); @memset(advances[index + 1 .. next], 0); index = next; } return true; } const MeasuredChatMsg = union(enum) { typed, scrolled: canvas.ScrollState, }; const measured_chat_draft_capacity = 96; const MeasuredChatModel = struct { offset: f32 = 0, draft: [measured_chat_draft_capacity]u8 = @splat('m'), draft_len: usize = 4, typed: u32 = 0, }; /// Each keystroke is a MODEL edit (a bound composer), so the whole view /// rebuilds — the convicted path: every mounted wrapped paragraph gets /// its height re-asked and its runs re-emitted per keystroke, changed /// or not. The draft cycles inside its capacity so every iteration is /// an identical steady-state edit. fn measuredChatUpdate(model: *MeasuredChatModel, msg: MeasuredChatMsg) void { switch (msg) { .typed => { model.typed += 1; model.draft[model.draft_len % measured_chat_draft_capacity] = 'a' + @as(u8, @intCast(model.typed % 26)); model.draft_len = (model.draft_len % measured_chat_draft_capacity) + 1; }, .scrolled => |scroll| model.offset = scroll.offset, } } const MeasuredChatApp = native_sdk.UiApp(MeasuredChatModel, MeasuredChatMsg); const MeasuredChatUi = MeasuredChatApp.Ui; const MeasuredChatMarkdown = canvas.markdown.Markdown(MeasuredChatMsg); fn measuredChatMessageKey(source: *const []const u8) canvas.UiKey { return canvas.uiKey(source.*); } fn measuredChatMessageView(ui: *MeasuredChatUi, context: void, source: *const []const u8) MeasuredChatUi.Node { _ = context; return MeasuredChatMarkdown.view(ui, source.*, .{}); } fn measuredChatTextLeaf(ui: *MeasuredChatUi, kind: canvas.WidgetKind, options: MeasuredChatUi.ElementOptions, content: []const u8) MeasuredChatUi.Node { var node = ui.el(kind, options, .{}); node.widget.text = content; return node; } fn measuredChatView(ui: *MeasuredChatUi, model: *const MeasuredChatModel) MeasuredChatUi.Node { return ui.column(.{ .padding = 12, .gap = 8 }, .{ ui.scroll(.{ .grow = 1, .value = model.offset, .on_scroll = MeasuredChatUi.scrollMsg(.scrolled), .semantics = .{ .label = "Measured transcript" }, }, ui.column(.{ .gap = 10 }, ui.eachCtx({}, measured_chat_sources[0..], measuredChatMessageKey, measuredChatMessageView))), measuredChatTextLeaf(ui, .text_field, .{ .height = 32, .semantics = .{ .label = "Composer" }, }, ui.fmt("{s}", .{model.draft[0..model.draft_len]})), }); } fn measuredChatOptions() MeasuredChatApp.Options { return .{ .name = "bench-measured-chat", .scene = bench_scene, .canvas_label = canvas_label, .update = measuredChatUpdate, .view = measuredChatView, }; } // ------------------------------------------ fixture: chart dashboard const chart_points = 120; const chart_timer_id: u64 = 7; const ChartMsg = union(enum) { tick, }; const ChartModel = struct { values: [chart_points]f32 = @splat(0), ticks: u32 = 0, }; fn chartUpdate(model: *ChartModel, msg: ChartMsg) void { switch (msg) { .tick => { model.ticks += 1; std.mem.copyForwards(f32, model.values[0 .. chart_points - 1], model.values[1..chart_points]); const phase: f32 = @floatFromInt(model.ticks % 97); model.values[chart_points - 1] = 40 + 30 * @sin(phase * 0.13) + 5 * @cos(phase * 0.41); }, } } const ChartApp = native_sdk.UiApp(ChartModel, ChartMsg); const ChartUi = ChartApp.Ui; fn chartView(ui: *ChartUi, model: *const ChartModel) ChartUi.Node { const series = [_]canvas.ChartSeries{.{ .kind = .line, .fill = true, .label = "throughput", .values = &model.values, }}; return ui.column(.{ .padding = 24, .gap = 16 }, .{ ui.text(.{}, "Bench dashboard"), ui.chart(.{ .width = 640, .height = 220, .grid_lines = 4, .baseline = true }, ui.arena.dupe(canvas.ChartSeries, &series) catch &.{}), ui.text(.{ .size = .sm }, ui.fmt("ticks {d}", .{model.ticks})), }); } fn chartOnTimer(id: u64, timestamp_ns: u64) ?ChartMsg { _ = timestamp_ns; if (id == chart_timer_id) return .tick; return null; } fn chartOptions() ChartApp.Options { return .{ .name = "bench-chart", .scene = bench_scene, .canvas_label = canvas_label, .update = chartUpdate, .view = chartView, .on_timer = chartOnTimer, }; } // -------------------------------------- fixture: large markdown doc const doc_blocks = 56; var doc_storage: [32 * 1024]u8 = undefined; var doc_len: usize = 0; /// Mutable tail the edit Msg appends to, so every re-render has a real /// content change to diff/patch. var doc_tail_len: usize = 0; fn initDocFixture() void { var writer = std.Io.Writer.fixed(&doc_storage); writer.writeAll("# Bench document\n\nA README-sized fixture: headings, prose, lists, code.\n\n") catch unreachable; for (0..doc_blocks / 4) |section| { writer.print("## Section {d}\n\n", .{section}) catch unreachable; writer.print("Paragraph {d} covers the retained canvas pipeline: display lists diff into patches, *unchanged* text runs skip `layoutTextRun`, and the host retains the keyed command dictionary between frames so steady-state cost tracks what changed.\n\n", .{section}) catch unreachable; writer.print("- item one for section {d} with `code` span\n- item two with **bold** emphasis\n- item three linking #12{d}\n\n", .{ section, section }) catch unreachable; writer.writeAll("```zig\nconst frame = try planCanvasFrame(options, storage);\n```\n\n") catch unreachable; } writer.writeAll("Tail: ") catch unreachable; doc_len = writer.buffered().len; } const DocMsg = union(enum) { edit, }; const DocModel = struct { revision: u32 = 0, }; fn docUpdate(model: *DocModel, msg: DocMsg) void { switch (msg) { .edit => { model.revision += 1; if (doc_len + doc_tail_len < doc_storage.len) { doc_storage[doc_len + doc_tail_len] = 'a' + @as(u8, @intCast(model.revision % 26)); doc_tail_len += 1; } }, } } const DocApp = native_sdk.UiApp(DocModel, DocMsg); const DocUi = DocApp.Ui; const DocMarkdown = canvas.markdown.Markdown(DocMsg); fn docView(ui: *DocUi, model: *const DocModel) DocUi.Node { _ = model; return ui.scroll(.{ .grow = 1 }, ui.column(.{ .padding = 16 }, DocMarkdown.view(ui, doc_storage[0 .. doc_len + doc_tail_len], .{}))); } fn docOptions() DocApp.Options { return .{ .name = "bench-doc", .scene = bench_scene, .canvas_label = canvas_label, .update = docUpdate, .view = docView, }; } // ------------------------------------------------------------ scenarios /// Generic measured loop: warmup, profile reset, N timed iterations. fn measure( comptime name: []const u8, comptime detail: []const u8, bench: anytype, iterations: usize, step: anytype, ) !ScenarioReport { bench.runtime().frame_profile.enabled = true; for (0..warmup_iterations) |_| try step.run(bench); bench.runtime().frame_profile.reset(); var e2e = Series{}; for (0..iterations) |_| { const begin = native_sdk.monotonicNanoseconds(); try step.run(bench); e2e.push(native_sdk.monotonicNanoseconds() -| begin); } return .{ .name = name, .detail = detail, .iterations = iterations, .e2e_p50_us = e2e.percentileUs(50), .e2e_p90_us = e2e.percentileUs(90), .stages = captureStages(bench.runtime()), }; } fn widgetIdByKind(bench: anytype, kind: canvas.WidgetKind) !canvas.ObjectId { const tree = bench.app.tree orelse return error.FixtureNotInstalled; const widget = findWidgetByKind(tree.root, kind) orelse return error.FixtureWidgetMissing; return widget.id; } fn scenarioKeystroke() !ScenarioReport { var bench = try Bench(BigFormApp).create(bigFormOptions()); defer bench.destroy(); const field_id = try widgetIdByKind(&bench, .text_field); try bench.automation("widget-action {s} {d} focus", .{ canvas_label, field_id }); try bench.frame(); const step = struct { fn run(b: *Bench(BigFormApp)) !void { try b.automation("widget-key {s} a a", .{canvas_label}); try b.frame(); } }; return measure( "keystroke-big-view", "typed char into focused field, ~930-widget view", &bench, measured_iterations, step, ); } fn scenarioToggle() !ScenarioReport { var bench = try Bench(BigFormApp).create(bigFormOptions()); defer bench.destroy(); const toggle_id = try widgetIdByKind(&bench, .switch_control); var buffer: [96]u8 = undefined; const line = try std.fmt.bufPrint(&buffer, "widget-click {s} {d}", .{ canvas_label, toggle_id }); toggle_click_line = line; const step = struct { fn run(b: *Bench(BigFormApp)) !void { try b.harness.runtime.dispatchAutomationCommand(b.app.app(), toggle_click_line); try b.frame(); } }; return measure( "toggle-big-view", "switch click -> Msg -> full TEA rebuild, ~930 widgets", &bench, measured_iterations, step, ); } var toggle_click_line: []const u8 = ""; var wheel_line: []const u8 = ""; fn scenarioTranscriptScroll() !ScenarioReport { var bench = try Bench(TranscriptApp).create(transcriptOptions()); defer bench.destroy(); const scroll_id = try widgetIdByKind(&bench, .scroll_view); var buffer: [96]u8 = undefined; wheel_line = try std.fmt.bufPrint(&buffer, "widget-wheel {s} {d} 96", .{ canvas_label, scroll_id }); const step = struct { fn run(b: *Bench(TranscriptApp)) !void { try b.harness.runtime.dispatchAutomationCommand(b.app.app(), wheel_line); try b.frame(); } }; return measure( "scroll-transcript", "wheel step through 200-message markdown transcript", &bench, measured_iterations, step, ); } /// The two provider-call caps guarding the batched seam and its caches. /// Both counters are deterministic (synthetic provider, scripted /// interactions), so the caps sit close to the measured signatures: /// /// - Per-prefix BYTES per keystroke — healthy 3355 (whole-slice widths /// only: per-frame line bounds and label widths over the retained /// runs); with the batched seam disabled 17465 (prefix re-measures /// come back for the composer and elided labels even though the wrap /// cache still absorbs the paragraphs); pre-batching, hundreds of /// thousands. Bytes rather than calls: a whole-line bounds measure /// and a one-cluster prefix step are one call each, so the quadratic /// class multiplies bytes by orders of magnitude while call counts /// blur. /// - Batched CALLS per keystroke — healthy 1 (the composer's changed /// text; everything else hits the retained advance and wrap caches); /// a broken cache (always-miss keying, generation stuck bumping) /// refetches every mounted paragraph every rebuild (tens per /// keystroke) while keeping per-prefix bytes low, which is why the /// byte cap alone cannot see it. const measured_chat_unit_byte_cap_per_keystroke: u64 = 8_000; const measured_chat_batch_call_cap_per_keystroke: u64 = 20; fn scenarioMeasuredKeystroke() !ScenarioReport { var bench = try Bench(MeasuredChatApp).create(measuredChatOptions()); defer bench.destroy(); // Install the counting provider the way platforms install CoreText: // on the runtime, before the measured interactions (tokens re-stamp // it on the next rebuild). The provider value lives on the runtime, // so its pointer identity is stable across frames like a real host's. bench.runtime().text_measure_provider = .{ .measure_fn = measuredMeasureText, .measure_advances_fn = measuredMeasureTextAdvances, }; // One rebuild with the provider installed so the first measured // iteration is steady-state, not the cold token flip. try bench.app.dispatch(&bench.harness.runtime, 1, .typed); try bench.frame(); const step = struct { fn run(b: *Bench(MeasuredChatApp)) !void { // The composer edit dispatches the way UiApp dispatches // command Msgs: model change -> full TEA rebuild -> present. try b.app.dispatch(&b.harness.runtime, 1, .typed); try b.frame(); } }; const before = measured_counters; const report = try measure( "keystroke-measured-text", "typed char over 24 wrapped messages, live measure provider", &bench, measured_iterations, step, ); // The snapshot above precedes `measure`, whose window is warmup plus // measured iterations — every one an identical steady-state // keystroke, so the per-iteration average over the whole window is // the honest per-keystroke number (the first-ever keystroke's cold // fetches amortize into it and still fit the cap with room). const iterations: u64 = @intCast(warmup_iterations + measured_iterations); const calls_per_keystroke = (measured_counters.total() - before.total()) / iterations; const unit_bytes_per_keystroke = (measured_counters.unit_bytes - before.unit_bytes) / iterations; std.debug.print( "bench-render: keystroke-measured-text per keystroke: {d} provider calls ({d} batched + {d} per-prefix), {d} per-prefix bytes (cap {d})\n", .{ calls_per_keystroke, measured_counters.batch_calls - before.batch_calls, measured_counters.unit_calls - before.unit_calls, unit_bytes_per_keystroke, measured_chat_unit_byte_cap_per_keystroke, }, ); if (unit_bytes_per_keystroke > measured_chat_unit_byte_cap_per_keystroke) { std.debug.print( "bench-render: keystroke-measured-text measured {d} per-prefix bytes per keystroke (cap {d}) — the batched measurement seam regressed\n", .{ unit_bytes_per_keystroke, measured_chat_unit_byte_cap_per_keystroke }, ); return error.MeasuredTextByteCapExceeded; } const batch_calls_per_keystroke = (measured_counters.batch_calls - before.batch_calls) / iterations; if (batch_calls_per_keystroke > measured_chat_batch_call_cap_per_keystroke) { std.debug.print( "bench-render: keystroke-measured-text made {d} batched provider calls per keystroke (cap {d}) — the advance or wrap cache regressed\n", .{ batch_calls_per_keystroke, measured_chat_batch_call_cap_per_keystroke }, ); return error.MeasuredTextBatchCapExceeded; } return report; } fn scenarioChartTick() !ScenarioReport { var bench = try Bench(ChartApp).create(chartOptions()); defer bench.destroy(); const step = struct { fn run(b: *Bench(ChartApp)) !void { try b.timer(chart_timer_id); try b.frame(); } }; return measure( "chart-tick", "timer Msg shifts 120-pt series, chart re-render", &bench, measured_iterations, step, ); } fn scenarioDocEdit() !ScenarioReport { var bench = try Bench(DocApp).create(docOptions()); defer bench.destroy(); const step = struct { fn run(b: *Bench(DocApp)) !void { // Drive the edit Msg through the real automation channel: // shortcut -> on_command has no mapping here, so dispatch the // Msg directly the way UiApp does for commands. try b.app.dispatch(&b.harness.runtime, 1, .edit); try b.frame(); } }; return measure( "markdown-doc-edit", "append char to README-sized doc, full markdown re-render", &bench, measured_iterations, step, ); } fn scenarioFirstFrame() !ScenarioReport { var e2e = Series{}; var stages: [stage_values.len]StageStats = undefined; for (&stages) |*entry| entry.* = .{}; var stage_series: [stage_values.len]Series = undefined; for (&stage_series) |*series| series.* = .{}; for (0..first_frame_iterations) |_| { const begin = native_sdk.monotonicNanoseconds(); var bench = try BenchFirstFrame.create(); e2e.push(native_sdk.monotonicNanoseconds() -| begin); // One install per harness: each stage's single-sample p50 IS the // sample; accumulate across iterations. inline for (stage_values, 0..) |stage, index| { const stats = bench.runtime().frame_profile.stats(stage); if (stats.window_len > 0) stage_series[index].push(stats.p50_us * std.time.ns_per_us); } bench.destroy(); } inline for (0..stage_values.len) |index| { stages[index] = .{ .p50_us = stage_series[index].percentileUs(50), .p90_us = stage_series[index].percentileUs(90), .count = stage_series[index].len, .window = stage_series[index].len, }; } return .{ .name = "first-frame", .detail = "create app+runtime -> install -> first present (~930 widgets)", .iterations = first_frame_iterations, .e2e_p50_us = e2e.percentileUs(50), .e2e_p90_us = e2e.percentileUs(90), .stages = stages, }; } /// First-frame variant of `Bench`: profiling is enabled BEFORE the /// install frame so the startup path is attributed. const BenchFirstFrame = struct { harness: *Harness, app: *BigFormApp, fn create() !BenchFirstFrame { const harness = try Harness.create(gpa, .{ .size = geometry.SizeF.init(surface_width, surface_height) }); harness.null_platform.gpu_surfaces = true; harness.null_platform.gpu_surface_packet_binary = true; harness.runtime.options.gpu_surface_frame_diagnostics = false; harness.runtime.frame_profile.enabled = true; const app = try gpa.create(BigFormApp); app.* = BigFormApp.init(gpa, .{}, bigFormOptions()); try harness.start(app.app()); try harness.runtime.dispatchPlatformEvent(app.app(), .{ .gpu_surface_frame = .{ .label = canvas_label, .size = geometry.SizeF.init(surface_width, surface_height), .scale_factor = 2, .frame_index = 1, .timestamp_ns = 1_000_000_000, .nonblank = true, } }); if (!app.installed) return error.FixtureNotInstalled; return .{ .harness = harness, .app = app }; } fn runtime(self: *BenchFirstFrame) *native_sdk.Runtime { return &self.harness.runtime; } fn destroy(self: *BenchFirstFrame) void { self.app.deinit(); gpa.destroy(self.app); self.harness.destroy(gpa); } }; // --------------------------------------------------------------- report fn printReports(reports: []const ScenarioReport) void { std.debug.print("\nbench-render: end-to-end (us per interaction; {d} iterations after {d} warmup; first-frame {d} iterations)\n\n", .{ measured_iterations, warmup_iterations, first_frame_iterations, }); std.debug.print("{s:<22} {s:>10} {s:>10} {s}\n", .{ "scenario", "p50_us", "p90_us", "notes" }); for (reports) |report| { std.debug.print("{s:<22} {s:>10} {s:>10} {s}{s}\n", .{ report.name, fmtUs(report.e2e_p50_us), fmtUs(report.e2e_p90_us), report.detail, if (report.noisy()) " [noisy]" else "", }); } std.debug.print("\nper-stage breakdown (p50/p90 us per invocation, xN = samples in the measured window; '-' = stage did not run)\n\n", .{}); std.debug.print("{s:<22}", .{"scenario"}); inline for (stage_values) |stage| { std.debug.print(" {s:>17}", .{@tagName(stage)}); } std.debug.print("\n", .{}); for (reports) |report| { std.debug.print("{s:<22}", .{report.name}); for (report.stages) |stage| { if (stage.count == 0) { std.debug.print(" {s:>17}", .{"-"}); } else { var cell: [32]u8 = undefined; const text = std.fmt.bufPrint(&cell, "{d}/{d} x{d}", .{ stage.p50_us, stage.p90_us, stage.window }) catch "?"; std.debug.print(" {s:>17}", .{text}); } } std.debug.print("\n", .{}); } std.debug.print("\n", .{}); } var fmt_us_storage: [8][24]u8 = undefined; var fmt_us_index: usize = 0; fn fmtUs(value: u64) []const u8 { fmt_us_index = (fmt_us_index + 1) % fmt_us_storage.len; return std.fmt.bufPrint(&fmt_us_storage[fmt_us_index], "{d}", .{value}) catch "?"; } // ---------------------------------------------------------- check mode const scenario_count = 7; const check_passes = 3; fn runAllScenarios() ![scenario_count]ScenarioReport { var reports: [scenario_count]ScenarioReport = undefined; reports[0] = try scenarioFirstFrame(); reports[1] = try scenarioKeystroke(); reports[2] = try scenarioToggle(); reports[3] = try scenarioTranscriptScroll(); reports[4] = try scenarioChartTick(); reports[5] = try scenarioDocEdit(); reports[6] = try scenarioMeasuredKeystroke(); return reports; } const Budget = struct { name: []const u8, p50_budget_us: u64, matched: bool = false, }; const max_budgets = 16; /// Budgets file: ` ` per line; blank lines /// and `#` comments ignored. fn parseBudgets(content: []const u8, storage: *[max_budgets]Budget) ![]Budget { var count: usize = 0; var lines = std.mem.splitScalar(u8, content, '\n'); while (lines.next()) |raw_line| { const line = std.mem.trim(u8, raw_line, " \t\r"); if (line.len == 0 or line[0] == '#') continue; var fields = std.mem.tokenizeAny(u8, line, " \t"); const name = fields.next() orelse continue; const value_text = fields.next() orelse { std.debug.print("bench-render --check: budget line missing a value: '{s}'\n", .{line}); return error.InvalidBudgetsFile; }; if (fields.next() != null) { std.debug.print("bench-render --check: budget line has trailing fields: '{s}'\n", .{line}); return error.InvalidBudgetsFile; } const value = std.fmt.parseInt(u64, value_text, 10) catch { std.debug.print("bench-render --check: budget value is not an integer of microseconds: '{s}'\n", .{line}); return error.InvalidBudgetsFile; }; if (count >= storage.len) return error.TooManyBudgets; storage[count] = .{ .name = name, .p50_budget_us = value }; count += 1; } if (count == 0) { std.debug.print("bench-render --check: budgets file declared no budgets\n", .{}); return error.InvalidBudgetsFile; } return storage[0..count]; } fn medianOf(values: []u64) u64 { std.sort.pdq(u64, values, {}, std.sort.asc(u64)); return values[(values.len - 1) / 2]; } fn runCheck(init: std.process.Init, budgets_path: []const u8) !void { if (builtin.mode != .ReleaseFast) { std.debug.print("bench-render --check: budgets are calibrated for ReleaseFast; rebuild with -Doptimize=ReleaseFast (got {s})\n", .{@tagName(builtin.mode)}); return error.WrongOptimizeMode; } var arena_state = std.heap.ArenaAllocator.init(init.gpa); defer arena_state.deinit(); const content = std.Io.Dir.cwd().readFileAlloc(init.io, budgets_path, arena_state.allocator(), .limited(64 * 1024)) catch |err| { std.debug.print("bench-render --check: cannot read budgets file '{s}': {s}\n", .{ budgets_path, @errorName(err) }); return err; }; var budget_storage: [max_budgets]Budget = undefined; const budgets = try parseBudgets(content, &budget_storage); // Median across passes: one descheduled pass (or one lucky one) // cannot decide the verdict on a loaded box. var passes: [check_passes][scenario_count]ScenarioReport = undefined; for (&passes, 0..) |*pass, pass_index| { pass.* = try runAllScenarios(); std.debug.print("bench-render --check: pass {d}/{d}:", .{ pass_index + 1, check_passes }); for (pass.*) |report| std.debug.print(" {s}={d}us", .{ report.name, report.e2e_p50_us }); std.debug.print("\n", .{}); } var failures: usize = 0; std.debug.print("\nbench-render --check: median e2e p50 of {d} passes vs budgets ({s})\n\n", .{ check_passes, budgets_path }); std.debug.print("{s:<22} {s:>10} {s:>10} {s}\n", .{ "scenario", "p50_us", "budget_us", "verdict" }); for (0..scenario_count) |scenario_index| { const name = passes[0][scenario_index].name; var samples: [check_passes]u64 = undefined; for (passes, 0..) |pass, pass_index| samples[pass_index] = pass[scenario_index].e2e_p50_us; const median = medianOf(&samples); const budget: ?*Budget = for (budgets) |*entry| { if (std.mem.eql(u8, entry.name, name)) break entry; } else null; if (budget) |entry| { entry.matched = true; const over = median > entry.p50_budget_us; if (over) failures += 1; std.debug.print("{s:<22} {d:>10} {d:>10} {s}\n", .{ name, median, entry.p50_budget_us, if (over) "FAIL" else "ok" }); } else { failures += 1; std.debug.print("{s:<22} {d:>10} {s:>10} {s}\n", .{ name, median, "-", "FAIL (no budget declared)" }); } } for (budgets) |entry| { if (!entry.matched) { failures += 1; std.debug.print("{s:<22} {s:>10} {d:>10} FAIL (budget names no scenario — renamed?)\n", .{ entry.name, "-", entry.p50_budget_us }); } } std.debug.print("\n", .{}); if (failures > 0) { std.debug.print("bench-render --check: {d} budget check(s) failed\n", .{failures}); return error.BudgetExceeded; } std.debug.print("bench-render --check: all scenarios within budget\n", .{}); } pub fn main(init: std.process.Init) !void { var args_arena = std.heap.ArenaAllocator.init(init.gpa); defer args_arena.deinit(); const args = try init.minimal.args.toSlice(args_arena.allocator()); var budgets_path: ?[]const u8 = null; var arg_index: usize = 1; while (arg_index < args.len) : (arg_index += 1) { if (std.mem.eql(u8, args[arg_index], "--check")) { arg_index += 1; if (arg_index >= args.len) { std.debug.print("bench-render: --check requires a budgets file path\n", .{}); return error.InvalidArguments; } budgets_path = args[arg_index]; } else { std.debug.print("bench-render: unknown argument '{s}' (usage: bench-render [--check ])\n", .{args[arg_index]}); return error.InvalidArguments; } } initBigFormFixture(); initTranscriptFixture(); initDocFixture(); initMeasuredChatFixture(); if (budgets_path) |path| return runCheck(init, path); const reports = try runAllScenarios(); printReports(&reports); }