const std = @import("std"); const h = @import("e2e_harness.zig"); const Harness = h.Harness; const linux = std.os.linux; const posix = std.posix; const payload_len = 64; const wheel_events = 240; const j_events = 240; const ctrl_d_events = 60; const delete_line_input = "xxxxxxxxxxxxxxxxd"; const delete_select_lines = delete_line_input.len - 1; const scroll_marker_line = wheel_events; const marker_wait_ms = 60_000; const scroll_quiet_ms = 1000; const large_stdlib_path = "/usr/lib/zig/std/Io/Threaded.zig"; const target_ratio_x100 = 150; const movement_ratio_floor_us = 20_000; const startup_first_frame_target_us = 15_000; const startup_samples = 5; fn nowNs() u64 { var ts: linux.timespec = undefined; _ = linux.clock_gettime(linux.CLOCK.MONOTONIC, &ts); return @intCast(ts.sec * 1_000_000_000 + ts.nsec); } fn fillPayload(buf: *[payload_len]u8, marker: []const u8) []const u8 { const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; for (buf, 0..) |*ch, i| ch.* = alphabet[(i * 7 + marker.len * 11) % alphabet.len]; @memcpy(buf[0..marker.len], marker); return buf[0..marker.len]; } fn waitRawGrowth(hs: *Harness, start_len: usize, ms: i64) !bool { const deadline = nowNs() + @as(u64, @intCast(ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, 5) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); hs.stream.nextSlice(buf[0..n]); if (hs.raw.items.len > start_len) return true; } } return hs.raw.items.len > start_len; } fn waitForOutputQuiet(hs: *Harness, quiet_ms: i64, max_ms: i64) !bool { const deadline = nowNs() + @as(u64, @intCast(max_ms)) * 1_000_000; var quiet_deadline = nowNs() + @as(u64, @intCast(quiet_ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { if (nowNs() >= quiet_deadline) return true; var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, 10) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); hs.stream.nextSlice(buf[0..n]); quiet_deadline = nowNs() + @as(u64, @intCast(quiet_ms)) * 1_000_000; } } return false; } fn leftPaneTopLine(hs: *Harness) !usize { const text = try hs.screenText(); defer hs.gpa.free(text); var rows = std.mem.splitScalar(u8, text, '\n'); var row_idx: usize = 0; while (rows.next()) |row| : (row_idx += 1) { if (row_idx < 2) continue; if (row_idx > 2) break; const trimmed = std.mem.trimStart(u8, row, " "); var end: usize = 0; while (end < trimmed.len and std.ascii.isDigit(trimmed[end])) : (end += 1) {} if (end > 0) return std.fmt.parseInt(usize, trimmed[0..end], 10) catch 0; } return 0; } fn waitForTopLineAbove(hs: *Harness, start_line: usize, ms: i64) !?usize { const deadline = nowNs() + @as(u64, @intCast(ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, 10) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); hs.stream.nextSlice(buf[0..n]); } const top_line = try leftPaneTopLine(hs); if (top_line > start_line) return top_line; } return null; } fn waitForScreenMissing(hs: *Harness, needle: []const u8, ms: i64) !bool { { const text = try hs.screenText(); defer hs.gpa.free(text); if (std.mem.indexOf(u8, text, needle) == null) return true; } const deadline = nowNs() + @as(u64, @intCast(ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, 10) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); hs.stream.nextSlice(buf[0..n]); } const text = try hs.screenText(); defer hs.gpa.free(text); if (std.mem.indexOf(u8, text, needle) == null) return true; } return false; } fn waitForScreenContainsPoll(hs: *Harness, needle: []const u8, ms: i64, poll_ms: i32) !bool { { const text = try hs.screenText(); defer hs.gpa.free(text); if (std.mem.indexOf(u8, text, needle) != null) return true; } const deadline = nowNs() + @as(u64, @intCast(ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, poll_ms) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); hs.stream.nextSlice(buf[0..n]); } const text = try hs.screenText(); defer hs.gpa.free(text); if (std.mem.indexOf(u8, text, needle) != null) return true; } return false; } fn waitForRawContainsPoll(hs: *Harness, needle: []const u8, ms: i64, poll_ms: i32) !bool { if (std.mem.indexOf(u8, hs.raw.items, needle) != null) return true; const deadline = nowNs() + @as(u64, @intCast(ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, poll_ms) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); hs.stream.nextSlice(buf[0..n]); if (std.mem.indexOf(u8, hs.raw.items, needle) != null) return true; } } return std.mem.indexOf(u8, hs.raw.items, needle) != null; } const startup_metric_prefix = "PARDES_STARTUP_US="; fn parseStartupMetric(raw: []const u8) ?u64 { const start = std.mem.indexOf(u8, raw, startup_metric_prefix) orelse return null; var i = start + startup_metric_prefix.len; const digit_start = i; while (i < raw.len and std.ascii.isDigit(raw[i])) : (i += 1) {} if (i == digit_start) return null; return std.fmt.parseInt(u64, raw[digit_start..i], 10) catch null; } fn waitForStartupMetric(hs: *Harness, ms: i64, poll_ms: i32) !?u64 { if (parseStartupMetric(hs.raw.items)) |value| return value; const deadline = nowNs() + @as(u64, @intCast(ms)) * 1_000_000; var buf: [4096]u8 = undefined; while (nowNs() < deadline) { var fds = [_]posix.pollfd{.{ .fd = hs.master, .events = posix.POLL.IN, .revents = 0 }}; _ = posix.poll(&fds, poll_ms) catch {}; if ((fds[0].revents & posix.POLL.IN) != 0) { const n = posix.read(hs.master, &buf) catch break; if (n == 0) break; try hs.raw.appendSlice(hs.gpa, buf[0..n]); if (parseStartupMetric(hs.raw.items)) |value| return value; } } return parseStartupMetric(hs.raw.items); } fn wheelDownBurst(gpa: std.mem.Allocator) ![]u8 { var out: std.ArrayList(u8) = .empty; errdefer out.deinit(gpa); var i: usize = 0; while (i < wheel_events) : (i += 1) try out.appendSlice(gpa, "\x1b[<65;10;6M"); return out.toOwnedSlice(gpa); } const Movement = enum { wheel, j, ctrl_d }; fn movementName(movement: Movement) []const u8 { return switch (movement) { .wheel => "wheel", .j => "j", .ctrl_d => "ctrl_d", }; } fn movementEventCount(movement: Movement) usize { return switch (movement) { .wheel => wheel_events, .j => j_events, .ctrl_d => ctrl_d_events, }; } fn movementInput(gpa: std.mem.Allocator, movement: Movement) ![]u8 { if (movement == .wheel) return wheelDownBurst(gpa); var out: std.ArrayList(u8) = .empty; errdefer out.deinit(gpa); var i: usize = 0; const bytes = switch (movement) { .wheel => unreachable, .j => "j", .ctrl_d => "\x04", }; while (i < movementEventCount(movement)) : (i += 1) try out.appendSlice(gpa, bytes); return out.toOwnedSlice(gpa); } fn readFileAlloc(gpa: std.mem.Allocator, path: [*:0]const u8) ![]u8 { const rc = linux.open(path, .{ .ACCMODE = .RDONLY }, 0); const sfd: isize = @bitCast(rc); if (sfd < 0) return error.OpenFailed; const fd: c_int = @intCast(sfd); defer _ = linux.close(fd); var out: std.ArrayList(u8) = .empty; errdefer out.deinit(gpa); var buf: [16 * 1024]u8 = undefined; while (true) { const n = posix.read(fd, &buf) catch return error.ReadFailed; if (n == 0) break; try out.appendSlice(gpa, buf[0..n]); } return out.toOwnedSlice(gpa); } fn appendZigMarker(gpa: std.mem.Allocator, out: *std.ArrayList(u8), marker: []const u8) !void { const line = try std.fmt.allocPrint(gpa, "const {s} = \"visible\";\n", .{marker}); defer gpa.free(line); try out.appendSlice(gpa, line); } fn appendTextMarker(gpa: std.mem.Allocator, out: *std.ArrayList(u8), marker: []const u8) !void { const line = try std.fmt.allocPrint(gpa, "{s} visible\n", .{marker}); defer gpa.free(line); try out.appendSlice(gpa, line); } fn appendMarker(gpa: std.mem.Allocator, out: *std.ArrayList(u8), marker: []const u8, highlighted: bool) !void { if (highlighted) { try appendZigMarker(gpa, out, marker); } else { try appendTextMarker(gpa, out, marker); } } fn makeSmallFixture(gpa: std.mem.Allocator, open_marker: []const u8, scroll_marker: []const u8, highlighted: bool) ![]u8 { var out: std.ArrayList(u8) = .empty; errdefer out.deinit(gpa); try appendMarker(gpa, &out, open_marker, highlighted); if (highlighted) { try out.appendSlice(gpa, "const std = @import(\"std\");\n"); try out.appendSlice(gpa, "pub fn perf_entry() usize {\n"); try out.appendSlice(gpa, " return perf_func_0() + perf_func_199();\n"); try out.appendSlice(gpa, "}\n"); } var line_no: usize = if (highlighted) 5 else 1; var i: usize = 0; while (line_no < scroll_marker_line) : ({ i += 1; line_no += 1; }) { const line = if (highlighted) try std.fmt.allocPrint(gpa, "fn perf_func_{d}() usize {{ return {d} + @as(usize, {d}); }}\n", .{ i, i, i % 17 }) else try std.fmt.allocPrint(gpa, "plain perf line {d}: words numbers strings comments\n", .{i}); defer gpa.free(line); try out.appendSlice(gpa, line); } try appendMarker(gpa, &out, scroll_marker, highlighted); line_no += 1; while (line_no < scroll_marker_line + 40) : ({ i += 1; line_no += 1; }) { const line = if (highlighted) try std.fmt.allocPrint(gpa, "fn perf_tail_{d}() usize {{ return {d}; }}\n", .{ i, i }) else try std.fmt.allocPrint(gpa, "plain perf tail {d}: words numbers strings comments\n", .{i}); defer gpa.free(line); try out.appendSlice(gpa, line); } return out.toOwnedSlice(gpa); } fn makeStdlibFixture(gpa: std.mem.Allocator, source: []const u8, open_marker: []const u8, scroll_marker: []const u8, highlighted: bool) ![]u8 { var out: std.ArrayList(u8) = .empty; errdefer out.deinit(gpa); try appendMarker(gpa, &out, open_marker, highlighted); var line_no: usize = 1; var lines = std.mem.splitScalar(u8, source, '\n'); while (lines.next()) |line| { if (line_no == scroll_marker_line) { try appendMarker(gpa, &out, scroll_marker, highlighted); line_no += 1; } try out.appendSlice(gpa, line); try out.append(gpa, '\n'); line_no += 1; } while (line_no <= scroll_marker_line) : (line_no += 1) { if (line_no == scroll_marker_line) { try appendMarker(gpa, &out, scroll_marker, highlighted); } else if (highlighted) { const line = try std.fmt.allocPrint(gpa, "fn perf_padding_{d}() void {{}}\n", .{line_no}); defer gpa.free(line); try out.appendSlice(gpa, line); } else { const line = try std.fmt.allocPrint(gpa, "plain stdlib padding {d}\n", .{line_no}); defer gpa.free(line); try out.appendSlice(gpa, line); } } return out.toOwnedSlice(gpa); } fn lineCount(content: []const u8) usize { return std.mem.count(u8, content, "\n") + 1; } const Fixture = struct { name: []const u8, path: []const u8, open_marker: []const u8, edit_marker: []const u8, scroll_marker: []const u8, highlighted: bool, bytes: usize, lines: usize, }; const BenchResult = struct { startup_app_first_frame_us: u64, startup_spawn_first_frame_us: u64, startup_shell_ready_us: u64, open_visible_us: u64, open_repaint_us: ?u64, edit_us: u64, delete_line_us: u64, undo_us: u64, move_first_us: u64, move_done_us: u64, move_active_us: u64, move_top_line: usize, }; const StartupResult = struct { internal_first_frame_us: u64, harness_first_frame_us: u64, spawn_first_frame_us: u64, }; const BestOps = struct { open_visible_us: u64 = std.math.maxInt(u64), edit_us: u64 = std.math.maxInt(u64), delete_line_us: u64 = std.math.maxInt(u64), undo_us: u64 = std.math.maxInt(u64), fn add(self: *BestOps, result: BenchResult) void { self.open_visible_us = @min(self.open_visible_us, result.open_visible_us); self.edit_us = @min(self.edit_us, result.edit_us); self.delete_line_us = @min(self.delete_line_us, result.delete_line_us); self.undo_us = @min(self.undo_us, result.undo_us); } }; const StartupStats = struct { best_us: u64 = std.math.maxInt(u64), worst_us: u64 = 0, total_us: u64 = 0, count: usize = 0, fn add(self: *StartupStats, us: u64) void { self.best_us = @min(self.best_us, us); self.worst_us = @max(self.worst_us, us); self.total_us += us; self.count += 1; } fn avg(self: StartupStats) u64 { if (self.count == 0) return 0; return self.total_us / self.count; } }; fn runFixture(gpa: std.mem.Allocator, exe: [*:0]const u8, fixture: Fixture, movement: Movement) !BenchResult { const startup_spawn_t0 = nowNs(); var hs = try Harness.init(gpa, exe, 30, 100); defer hs.deinit(); const startup_app_t0 = nowNs(); if (!try waitForRawContainsPoll(&hs, "Kill Newcol", 8000, 1)) { hs.dump("app first frame did not render before syntax perf"); return error.StartupFirstFrameTimeout; } const startup_app_first_frame_us = (nowNs() - startup_app_t0) / 1000; const startup_spawn_first_frame_us = (nowNs() - startup_spawn_t0) / 1000; try hs.expectWaitFor("tests.zig", 8000, "app never auto-ls'd before syntax perf"); const startup_shell_ready_us = (nowNs() - startup_spawn_t0) / 1000; try hs.send("\x02"); // normal -> tty try hs.pump(500); { var cmd_buf: [256]u8 = undefined; const cmd = try std.fmt.bufPrint(&cmd_buf, "clear; echo {s}\r", .{fixture.path}); try hs.send(cmd); } try hs.expectWaitFor(fixture.path, 8000, "perf path did not render in tty"); try hs.send("\x02"); // tty -> normal try hs.expectWaitFor("nm /", 6000, "failed to leave tty before syntax perf open"); const open_t0 = nowNs(); try hs.send("\x1b[<2;8;3M\x1b[<2;8;3m"); // right-click body row 0 path try hs.expectWaitFor(fixture.open_marker, marker_wait_ms, "syntax perf file did not open"); const open_visible_us = (nowNs() - open_t0) / 1000; var open_repaint_us: ?u64 = null; if (fixture.highlighted) { open_repaint_us = open_visible_us; } if (!try waitForOutputQuiet(&hs, 250, marker_wait_ms)) { hs.dump("syntax perf open did not go quiet"); return error.OpenQuietTimeout; } try hs.send("i"); try hs.expectWaitFor("in /tmp/pardes_perf_", 5000, "syntax perf did not enter file insert mode"); var payload: [payload_len]u8 = undefined; const edit_marker = fillPayload(&payload, fixture.edit_marker); const edit_t0 = nowNs(); try hs.send(&payload); try hs.expectWaitFor(edit_marker, marker_wait_ms, "syntax perf edit marker did not appear"); const edit_us = (nowNs() - edit_t0) / 1000; try hs.send("\x1b"); // insert -> normal try hs.expectWaitFor("nm /tmp/pardes_perf_", 5000, "syntax perf did not leave insert mode"); if (!try waitForOutputQuiet(&hs, 250, marker_wait_ms)) { hs.dump("syntax perf edit repaint did not go quiet"); return error.EditRepaintQuietTimeout; } const delete_t0 = nowNs(); try hs.send(delete_line_input); if (!try waitForScreenMissing(&hs, edit_marker, marker_wait_ms)) { hs.dump("syntax perf line delete marker did not disappear"); return error.DeleteLineTimeout; } const delete_line_us = (nowNs() - delete_t0) / 1000; if (!try waitForOutputQuiet(&hs, 250, marker_wait_ms)) { hs.dump("syntax perf line delete repaint did not go quiet"); return error.DeleteLineQuietTimeout; } const undo_t0 = nowNs(); try hs.send("u"); try hs.expectWaitFor(edit_marker, marker_wait_ms, "syntax perf undo did not restore edit marker"); const undo_us = (nowNs() - undo_t0) / 1000; if (!try waitForOutputQuiet(&hs, 250, marker_wait_ms)) { hs.dump("syntax perf undo repaint did not go quiet"); return error.UndoQuietTimeout; } const move_input = try movementInput(gpa, movement); defer gpa.free(move_input); const start_top_line = @max(try leftPaneTopLine(&hs), 1); const move_t0 = nowNs(); try hs.send(move_input); if (try waitForTopLineAbove(&hs, start_top_line, marker_wait_ms)) |_| { // Progress observed; keep draining below until the movement settles. } else { hs.dump("syntax perf movement did not move the visible file window"); return error.MovementDidNotMove; } const move_first_us = (nowNs() - move_t0) / 1000; if (!try waitForOutputQuiet(&hs, scroll_quiet_ms, marker_wait_ms)) { hs.dump("syntax perf movement did not go quiet"); return error.MovementQuietTimeout; } const move_done_us = (nowNs() - move_t0) / 1000; const move_top_line = try leftPaneTopLine(&hs); return .{ .startup_app_first_frame_us = startup_app_first_frame_us, .startup_spawn_first_frame_us = startup_spawn_first_frame_us, .startup_shell_ready_us = startup_shell_ready_us, .open_visible_us = open_visible_us, .open_repaint_us = open_repaint_us, .edit_us = edit_us, .delete_line_us = delete_line_us, .undo_us = undo_us, .move_first_us = move_first_us, .move_done_us = move_done_us, .move_active_us = move_done_us -| (@as(u64, scroll_quiet_ms) * 1000), .move_top_line = move_top_line, }; } fn runStartup(gpa: std.mem.Allocator, exe: [*:0]const u8) !StartupResult { const startup_spawn_t0 = nowNs(); var hs = try Harness.initArgs(gpa, exe, 30, 100, "--perf-startup"); defer hs.deinit(); const startup_app_t0 = nowNs(); const internal_first_frame_us = (try waitForStartupMetric(&hs, 8000, 1)) orelse { hs.dump("app first frame did not render before startup perf"); return error.StartupFirstFrameTimeout; }; if (!try waitForRawContainsPoll(&hs, "Kill Newcol", 8000, 1)) { hs.dump("app first frame marker rendered without topbar text"); return error.StartupFirstFrameTimeout; } return .{ .internal_first_frame_us = internal_first_frame_us, .harness_first_frame_us = (nowNs() - startup_app_t0) / 1000, .spawn_first_frame_us = (nowNs() - startup_spawn_t0) / 1000, }; } fn printResult(fixture: Fixture, movement: Movement, result: BenchResult) void { std.debug.print( "case={s} movement={s} highlight={d} bytes={d} lines={d} startup_app_first_frame_us={d} startup_spawn_first_frame_us={d} startup_shell_ready_us={d} open_visible_us={d} open_repaint_us={d} edit_us={d} edit_us_per_char={d} delete_line_us={d} undo_us={d} move_first_us={d} move_done_us={d} move_active_us={d} move_top_line={d}\n", .{ fixture.name, movementName(movement), @intFromBool(fixture.highlighted), fixture.bytes, fixture.lines, result.startup_app_first_frame_us, result.startup_spawn_first_frame_us, result.startup_shell_ready_us, result.open_visible_us, result.open_repaint_us orelse 0, result.edit_us, result.edit_us / payload_len, result.delete_line_us, result.undo_us, result.move_first_us, result.move_done_us, result.move_active_us, result.move_top_line, }, ); } fn ratioX100(highlight_us: u64, plain_us: u64) u64 { if (plain_us == 0) return 0; return (highlight_us * 100 + plain_us / 2) / plain_us; } fn movementRatioX100(highlight_us: u64, plain_us: u64) u64 { return ratioX100(highlight_us, @max(plain_us, movement_ratio_floor_us)); } fn printRatioSummary(movement: Movement, small_txt: BenchResult, small_zig: BenchResult, large_txt: BenchResult, large_zig: BenchResult) void { std.debug.print( "ratio movement={s} small_move_active_x100={d} large_move_active_x100={d} target_x100={d} small_open_visible_x100={d} large_open_visible_x100={d} small_edit_x100={d} large_edit_x100={d} small_delete_line_x100={d} large_delete_line_x100={d} small_undo_x100={d} large_undo_x100={d}\n", .{ movementName(movement), movementRatioX100(small_zig.move_active_us, small_txt.move_active_us), movementRatioX100(large_zig.move_active_us, large_txt.move_active_us), target_ratio_x100, ratioX100(small_zig.open_visible_us, small_txt.open_visible_us), ratioX100(large_zig.open_visible_us, large_txt.open_visible_us), ratioX100(small_zig.edit_us, small_txt.edit_us), ratioX100(large_zig.edit_us, large_txt.edit_us), ratioX100(small_zig.delete_line_us, small_txt.delete_line_us), ratioX100(large_zig.delete_line_us, large_txt.delete_line_us), ratioX100(small_zig.undo_us, small_txt.undo_us), ratioX100(large_zig.undo_us, large_txt.undo_us), }, ); } fn checkMovementRatio(movement: Movement, size_name: []const u8, highlighted_us: u64, plain_us: u64) !void { const ratio = movementRatioX100(highlighted_us, plain_us); if (ratio > target_ratio_x100) { std.debug.print( "FAIL movement={s} size={s} move_active_x100={d} target_x100={d} highlighted_us={d} plain_us={d}\n", .{ movementName(movement), size_name, ratio, target_ratio_x100, highlighted_us, plain_us }, ); return error.HighlightMovementPerfRegression; } } fn checkMovementRatios(movement: Movement, small_txt: BenchResult, small_zig: BenchResult, large_txt: BenchResult, large_zig: BenchResult) !void { try checkMovementRatio(movement, "small", small_zig.move_active_us, small_txt.move_active_us); try checkMovementRatio(movement, "large", large_zig.move_active_us, large_txt.move_active_us); } fn printBestOpsRatioSummary(best: [4]BestOps) void { std.debug.print( "ratio aggregate=best_of_movements target_x100={d} small_open_visible_x100={d} large_open_visible_x100={d} small_edit_x100={d} large_edit_x100={d} small_delete_line_x100={d} large_delete_line_x100={d} small_undo_x100={d} large_undo_x100={d}\n", .{ target_ratio_x100, ratioX100(best[1].open_visible_us, best[0].open_visible_us), ratioX100(best[3].open_visible_us, best[2].open_visible_us), ratioX100(best[1].edit_us, best[0].edit_us), ratioX100(best[3].edit_us, best[2].edit_us), ratioX100(best[1].delete_line_us, best[0].delete_line_us), ratioX100(best[3].delete_line_us, best[2].delete_line_us), ratioX100(best[1].undo_us, best[0].undo_us), ratioX100(best[3].undo_us, best[2].undo_us), }, ); } fn printStartupSummary(name: []const u8, target_us: ?u64, stats: StartupStats) void { if (target_us) |target| { std.debug.print( "startup summary metric={s} target_us={d} samples={d} best_us={d} avg_us={d} worst_us={d}\n", .{ name, target, stats.count, stats.best_us, stats.avg(), stats.worst_us }, ); return; } std.debug.print( "startup summary metric={s} samples={d} best_us={d} avg_us={d} worst_us={d}\n", .{ name, stats.count, stats.best_us, stats.avg(), stats.worst_us }, ); } fn checkStartupAverage(name: []const u8, target_us: u64, stats: StartupStats) !void { const avg_us = stats.avg(); if (avg_us <= target_us) return; std.debug.print( "FAIL startup metric={s} avg_us={d} target_us={d} samples={d} best_us={d} worst_us={d}\n", .{ name, avg_us, target_us, stats.count, stats.best_us, stats.worst_us }, ); return error.StartupPerfRegression; } pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len != 2) { std.debug.print("usage: {s} \n", .{args[0]}); return error.BadArgs; } const small_txt_path = "/tmp/pardes_perf_small_plain.txt"; const small_zig_path = "/tmp/pardes_perf_small_code.zig"; const large_txt_path = "/tmp/pardes_perf_large_plain.txt"; const large_zig_path = "/tmp/pardes_perf_large_code.zig"; const stdlib_content = try readFileAlloc(gpa, large_stdlib_path); defer gpa.free(stdlib_content); const small_txt_content = try makeSmallFixture(gpa, "SMALLTXTOPENPERFEND", "SMALLTXTSCROLLPERFEND", false); defer gpa.free(small_txt_content); const small_zig_content = try makeSmallFixture(gpa, "SMALLZIGOPENPERFEND", "SMALLZIGSCROLLPERFEND", true); defer gpa.free(small_zig_content); const large_txt_content = try makeStdlibFixture(gpa, stdlib_content, "LARGETXTOPENPERFEND", "LARGETXTSCROLLPERFEND", false); defer gpa.free(large_txt_content); const large_zig_content = try makeStdlibFixture(gpa, stdlib_content, "LARGEZIGOPENPERFEND", "LARGEZIGSCROLLPERFEND", true); defer gpa.free(large_zig_content); try h.writeFile(small_txt_path, small_txt_content); try h.writeFile(small_zig_path, small_zig_content); try h.writeFile(large_txt_path, large_txt_content); try h.writeFile(large_zig_path, large_zig_content); const fixtures = [_]Fixture{ .{ .name = "small_txt", .path = small_txt_path, .open_marker = "SMALLTXTOPENPERFEND", .edit_marker = "SMALLTXTEDITPERFEND", .scroll_marker = "SMALLTXTSCROLLPERFEND", .highlighted = false, .bytes = small_txt_content.len, .lines = lineCount(small_txt_content), }, .{ .name = "small_zig", .path = small_zig_path, .open_marker = "SMALLZIGOPENPERFEND", .edit_marker = "SMALLZIGEDITPERFEND", .scroll_marker = "SMALLZIGSCROLLPERFEND", .highlighted = true, .bytes = small_zig_content.len, .lines = lineCount(small_zig_content), }, .{ .name = "large_txt", .path = large_txt_path, .open_marker = "LARGETXTOPENPERFEND", .edit_marker = "LARGETXTEDITPERFEND", .scroll_marker = "LARGETXTSCROLLPERFEND", .highlighted = false, .bytes = large_txt_content.len, .lines = lineCount(large_txt_content), }, .{ .name = "large_zig", .path = large_zig_path, .open_marker = "LARGEZIGOPENPERFEND", .edit_marker = "LARGEZIGEDITPERFEND", .scroll_marker = "LARGEZIGSCROLLPERFEND", .highlighted = true, .bytes = large_zig_content.len, .lines = lineCount(large_zig_content), }, }; std.debug.print( "syntax-perf backend=vaxis chars_per_edit={d} delete_select_lines={d} wheel_events={d} j_events={d} ctrl_d_events={d} scroll_quiet_ms={d} scroll_marker_line={d} large_stdlib={s}\n", .{ payload_len, delete_select_lines, wheel_events, j_events, ctrl_d_events, scroll_quiet_ms, scroll_marker_line, large_stdlib_path }, ); var startup_only_internal_stats: StartupStats = .{}; var startup_only_harness_stats: StartupStats = .{}; var startup_only_spawn_stats: StartupStats = .{}; for (0..startup_samples) |sample| { const startup = try runStartup(gpa, args[1].ptr); startup_only_internal_stats.add(startup.internal_first_frame_us); startup_only_harness_stats.add(startup.harness_first_frame_us); startup_only_spawn_stats.add(startup.spawn_first_frame_us); std.debug.print( "startup_sample={d} internal_first_frame_us={d} harness_first_frame_us={d} spawn_first_frame_us={d}\n", .{ sample, startup.internal_first_frame_us, startup.harness_first_frame_us, startup.spawn_first_frame_us }, ); } printStartupSummary("startup_only_internal_first_frame", null, startup_only_internal_stats); printStartupSummary("startup_only_harness_first_frame", startup_first_frame_target_us, startup_only_harness_stats); printStartupSummary("startup_only_spawn_first_frame", null, startup_only_spawn_stats); try checkStartupAverage("startup_only_harness_first_frame", startup_first_frame_target_us, startup_only_harness_stats); const movements = [_]Movement{ .wheel, .j, .ctrl_d }; var best_ops: [4]BestOps = .{ .{}, .{}, .{}, .{} }; var startup_app_first_frame_stats: StartupStats = .{}; var startup_spawn_first_frame_stats: StartupStats = .{}; var startup_shell_ready_stats: StartupStats = .{}; for (movements) |movement| { const small_txt = try runFixture(gpa, args[1].ptr, fixtures[0], movement); startup_app_first_frame_stats.add(small_txt.startup_app_first_frame_us); startup_spawn_first_frame_stats.add(small_txt.startup_spawn_first_frame_us); startup_shell_ready_stats.add(small_txt.startup_shell_ready_us); best_ops[0].add(small_txt); printResult(fixtures[0], movement, small_txt); const small_zig = try runFixture(gpa, args[1].ptr, fixtures[1], movement); startup_app_first_frame_stats.add(small_zig.startup_app_first_frame_us); startup_spawn_first_frame_stats.add(small_zig.startup_spawn_first_frame_us); startup_shell_ready_stats.add(small_zig.startup_shell_ready_us); best_ops[1].add(small_zig); printResult(fixtures[1], movement, small_zig); const large_txt = try runFixture(gpa, args[1].ptr, fixtures[2], movement); startup_app_first_frame_stats.add(large_txt.startup_app_first_frame_us); startup_spawn_first_frame_stats.add(large_txt.startup_spawn_first_frame_us); startup_shell_ready_stats.add(large_txt.startup_shell_ready_us); best_ops[2].add(large_txt); printResult(fixtures[2], movement, large_txt); const large_zig = try runFixture(gpa, args[1].ptr, fixtures[3], movement); startup_app_first_frame_stats.add(large_zig.startup_app_first_frame_us); startup_spawn_first_frame_stats.add(large_zig.startup_spawn_first_frame_us); startup_shell_ready_stats.add(large_zig.startup_shell_ready_us); best_ops[3].add(large_zig); printResult(fixtures[3], movement, large_zig); printRatioSummary(movement, small_txt, small_zig, large_txt, large_zig); try checkMovementRatios(movement, small_txt, small_zig, large_txt, large_zig); } printBestOpsRatioSummary(best_ops); printStartupSummary("app_first_frame", null, startup_app_first_frame_stats); printStartupSummary("spawn_first_frame", null, startup_spawn_first_frame_stats); printStartupSummary("shell_ready", null, startup_shell_ready_stats); }