summaryrefslogtreecommitdiff
path: root/src/benchmark/TerminalStream.zig
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-07-08 09:12:03 -0700
committerMitchell Hashimoto <m@mitchellh.com>2025-07-09 15:06:24 -0700
commitb8f5cf9d52506add7d30cacc2142a79949f76ea3 (patch)
tree93699d38f3a0c16cca4d23ee391904df307deef0 /src/benchmark/TerminalStream.zig
parent1739418f6f6fab3bb7df9c2c84eba91ddabe91b2 (diff)
initial `ghostty-bench` program
Diffstat (limited to 'src/benchmark/TerminalStream.zig')
-rw-r--r--src/benchmark/TerminalStream.zig56
1 files changed, 52 insertions, 4 deletions
diff --git a/src/benchmark/TerminalStream.zig b/src/benchmark/TerminalStream.zig
index 8580695f2..6300ba04c 100644
--- a/src/benchmark/TerminalStream.zig
+++ b/src/benchmark/TerminalStream.zig
@@ -14,36 +14,49 @@
const TerminalStream = @This();
const std = @import("std");
+const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const terminalpkg = @import("../terminal/main.zig");
const Benchmark = @import("Benchmark.zig");
const Terminal = terminalpkg.Terminal;
const Stream = terminalpkg.Stream(*Handler);
+opts: Options,
terminal: Terminal,
handler: Handler,
stream: Stream,
+/// The file, opened in the setup function.
+data_f: ?std.fs.File = null,
+
pub const Options = struct {
/// The size of the terminal. This affects benchmarking when
/// dealing with soft line wrapping and the memory impact
/// of page sizes.
@"terminal-rows": u16 = 80,
@"terminal-cols": u16 = 120,
+
+ /// The data to read as a filepath. If this is "-" then
+ /// we will read stdin. If this is unset, then we will
+ /// do nothing (benchmark is a noop). It'd be more unixy to
+ /// use stdin by default but I find that a hanging CLI command
+ /// with no interaction is a bit annoying.
+ data: ?[]const u8 = null,
};
/// Create a new terminal stream handler for the given arguments.
pub fn create(
alloc: Allocator,
- args: Options,
+ opts: Options,
) !*TerminalStream {
const ptr = try alloc.create(TerminalStream);
errdefer alloc.destroy(ptr);
ptr.* = .{
+ .opts = opts,
.terminal = try .init(alloc, .{
- .rows = args.@"terminal-rows",
- .cols = args.@"terminal-cols",
+ .rows = opts.@"terminal-rows",
+ .cols = opts.@"terminal-cols",
}),
.handler = .{ .t = &ptr.terminal },
.stream = .{ .handler = &ptr.handler },
@@ -61,17 +74,52 @@ pub fn benchmark(self: *TerminalStream) Benchmark {
return .init(self, .{
.stepFn = step,
.setupFn = setup,
+ .teardownFn = teardown,
});
}
fn setup(ptr: *anyopaque) Benchmark.Error!void {
const self: *TerminalStream = @ptrCast(@alignCast(ptr));
+
+ // Always reset our terminal state
self.terminal.fullReset();
+
+ // Open our data file to prepare for reading. We can do more
+ // validation here eventually.
+ assert(self.data_f == null);
+ if (self.opts.data) |path| {
+ self.data_f = std.fs.cwd().openFile(path, .{}) catch
+ return error.BenchmarkFailed;
+ }
+}
+
+fn teardown(ptr: *anyopaque) void {
+ const self: *TerminalStream = @ptrCast(@alignCast(ptr));
+ if (self.data_f) |f| {
+ f.close();
+ self.data_f = null;
+ }
}
fn step(ptr: *anyopaque) Benchmark.Error!void {
const self: *TerminalStream = @ptrCast(@alignCast(ptr));
- _ = self;
+
+ // Get our buffered reader so we're not predominantly
+ // waiting on file IO. It'd be better to move this fully into
+ // memory. If we're IO bound though that should show up on
+ // the benchmark results and... I know writing this that we
+ // aren't currently IO bound.
+ const f = self.data_f orelse return;
+ var r = std.io.bufferedReader(f.reader());
+
+ var buf: [4096]u8 = undefined;
+ while (true) {
+ const n = r.read(&buf) catch return error.BenchmarkFailed;
+ if (n == 0) break; // EOF reached
+ const chunk = buf[0..n];
+ self.stream.nextSlice(chunk) catch
+ return error.BenchmarkFailed;
+ }
}
/// Implements the handler interface for the terminal.Stream.