diff --git a/build.zig b/build.zig index a7922ae7e..ce870e27c 100644 --- a/build.zig +++ b/build.zig @@ -72,6 +72,9 @@ pub fn build(b: *std.build.Builder) !void { "Build and install test executables with 'build'", ) orelse false; + // Add our benchmarks + try benchSteps(b, target, mode); + const exe = b.addExecutable("ghostty", "src/main.zig"); const exe_options = b.addOptions(); exe_options.addOption(bool, "tracy_enabled", tracy); @@ -337,6 +340,44 @@ fn addDeps( try glfw.link(b, imgui_step, glfw_opts); } +fn benchSteps( + b: *std.build.Builder, + target: std.zig.CrossTarget, + mode: std.builtin.Mode, +) !void { + // Open the directory ./src/bench + const c_dir_path = (comptime root()) ++ "/src/bench"; + var c_dir = try fs.openIterableDirAbsolute(c_dir_path, .{}); + defer c_dir.close(); + + // Go through and add each as a step + var c_dir_it = c_dir.iterate(); + while (try c_dir_it.next()) |entry| { + // Get the index of the last '.' so we can strip the extension. + const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; + if (index == 0) continue; + + // If it doesn't end in 'zig' then ignore + if (!std.mem.eql(u8, entry.name[index + 1 ..], "zig")) continue; + + // Name of the conformance app and full path to the entrypoint. + const name = entry.name[0..index]; + const path = try fs.path.join(b.allocator, &[_][]const u8{ + c_dir_path, + entry.name, + }); + + // Executable builder. + const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name}); + const c_exe = b.addExecutable(bin_name, path); + c_exe.setTarget(target); + c_exe.setBuildMode(mode); + c_exe.setMainPkgPath("./src"); + c_exe.install(); + try addDeps(b, c_exe, true); + } +} + fn conformanceSteps( b: *std.build.Builder, target: std.zig.CrossTarget, diff --git a/src/bench/parser.zig b/src/bench/parser.zig new file mode 100644 index 000000000..452d61690 --- /dev/null +++ b/src/bench/parser.zig @@ -0,0 +1,71 @@ +//! This benchmark tests the throughput of the terminal escape code parser. +//! +//! To benchmark, this takes an input stream (which is expected to come in +//! as fast as possible), runs it through the parser, and does nothing +//! with the parse result. This bottlenecks and tests the throughput of the +//! parser. +//! +//! Usage: +//! +//! "--f=" - A file to read to parse. If path is "-" then stdin +//! is read. Required. +//! + +const std = @import("std"); +const ArenaAllocator = std.heap.ArenaAllocator; +const cli_args = @import("../cli_args.zig"); +const terminal = @import("../terminal/main.zig"); + +pub fn main() !void { + // Just use a GPA + const GPA = std.heap.GeneralPurposeAllocator(.{}); + var gpa = GPA{}; + defer _ = gpa.deinit(); + const alloc = gpa.allocator(); + + // Parse our args + var args: Args = args: { + var args: Args = .{}; + errdefer args.deinit(); + var iter = try std.process.argsWithAllocator(alloc); + defer iter.deinit(); + try cli_args.parse(Args, alloc, &args, &iter); + break :args args; + }; + defer args.deinit(); + + // Read the file for our input + const file = file: { + if (std.mem.eql(u8, args.f, "-")) + break :file std.io.getStdIn(); + + @panic("file reading not implemented yet"); + }; + + // Read all into memory (TODO: support buffers one day) + const input = try file.reader().readAllAlloc( + alloc, + 1024 * 1024 * 1024 * 1024 * 16, // 16 GB + ); + defer alloc.free(input); + + // Run our parser + var p: terminal.Parser = .{}; + for (input) |c| { + const actions = p.next(c); + //std.log.warn("actions={any}", .{actions}); + _ = actions; + } +} + +const Args = struct { + f: []const u8 = "-", + + /// This is set by the CLI parser for deinit. + _arena: ?ArenaAllocator = null, + + pub fn deinit(self: *Args) void { + if (self._arena) |arena| arena.deinit(); + self.* = undefined; + } +};