aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/benchmark/ApcParser.zig
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:28:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:29:28 +0300
commit4a3302d19a2baa3a33e910e0088c24e780905b90 (patch)
treedcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/benchmark/ApcParser.zig
parent550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff)
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/benchmark/ApcParser.zig')
-rw-r--r--vendor/libghostty-vt/src/benchmark/ApcParser.zig143
1 files changed, 143 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/benchmark/ApcParser.zig b/vendor/libghostty-vt/src/benchmark/ApcParser.zig
new file mode 100644
index 00000000..73b7c57a
--- /dev/null
+++ b/vendor/libghostty-vt/src/benchmark/ApcParser.zig
@@ -0,0 +1,143 @@
+//! This benchmark tests the throughput of APC sequence parsing
+//! through the terminal stream: VT state machine dispatch, APC
+//! protocol identification, and the protocol command parsers
+//! (e.g. Kitty graphics). Completed commands are parsed and then
+//! discarded; command execution (image decoding, storage) is not
+//! included so this isolates the parsing path.
+const ApcParser = @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 options = @import("options.zig");
+
+const log = std.log.scoped(.@"apc-parser-bench");
+
+opts: Options,
+stream: Stream,
+
+/// The file, opened in the setup function.
+data_f: ?std.fs.File = null,
+
+pub const Options = struct {
+ /// 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,
+};
+
+const Stream = terminalpkg.Stream(Handler);
+
+/// A stream handler that only processes APC actions, parsing and
+/// immediately discarding completed commands.
+const Handler = struct {
+ alloc: Allocator,
+ apc: terminalpkg.apc.Handler = .{},
+
+ pub fn deinit(self: *Handler) void {
+ self.apc.deinit();
+ }
+
+ pub fn vt(
+ self: *Handler,
+ comptime action: Stream.Action.Tag,
+ value: Stream.Action.Value(action),
+ ) void {
+ switch (action) {
+ .apc_start => self.apc.start(),
+ .apc_put => self.apc.feed(self.alloc, value),
+ .apc_put_slice => self.apc.feedSlice(self.alloc, value.bytes),
+ .apc_end => if (self.apc.end()) |cmd| {
+ var c = cmd;
+ std.mem.doNotOptimizeAway(&c);
+ c.deinit(self.alloc);
+ },
+ else => {},
+ }
+ }
+};
+
+/// Create a new APC parser benchmark for the given arguments.
+pub fn create(
+ alloc: Allocator,
+ opts: Options,
+) !*ApcParser {
+ const ptr = try alloc.create(ApcParser);
+ errdefer alloc.destroy(ptr);
+ ptr.* = .{
+ .opts = opts,
+ .stream = .init(.{ .alloc = alloc }),
+ };
+ return ptr;
+}
+
+pub fn destroy(self: *ApcParser, alloc: Allocator) void {
+ self.stream.deinit();
+ alloc.destroy(self);
+}
+
+pub fn benchmark(self: *ApcParser) Benchmark {
+ return .init(self, .{
+ .stepFn = step,
+ .setupFn = setup,
+ .teardownFn = teardown,
+ });
+}
+
+fn setup(ptr: *anyopaque) Benchmark.Error!void {
+ const self: *ApcParser = @ptrCast(@alignCast(ptr));
+
+ // Open our data file to prepare for reading. We can do more
+ // validation here eventually.
+ assert(self.data_f == null);
+ self.data_f = options.dataFile(self.opts.data) catch |err| {
+ log.warn("error opening data file err={}", .{err});
+ return error.BenchmarkFailed;
+ };
+}
+
+fn teardown(ptr: *anyopaque) void {
+ const self: *ApcParser = @ptrCast(@alignCast(ptr));
+ if (self.data_f) |f| {
+ f.close();
+ self.data_f = null;
+ }
+}
+
+fn step(ptr: *anyopaque) Benchmark.Error!void {
+ const self: *ApcParser = @ptrCast(@alignCast(ptr));
+
+ const f = self.data_f orelse return;
+
+ var read_buf: [64 * 1024]u8 align(std.atomic.cache_line) = undefined;
+ var f_reader = f.reader(&read_buf);
+ const r = &f_reader.interface;
+
+ // This buffer size matches the read buffer size used by the
+ // real IO thread (see termio Exec.zig buffer_capacity) so that
+ // the benchmark exercises the stream with realistic chunk sizes.
+ var buf: [64 * 1024]u8 = undefined;
+ while (true) {
+ const n = r.readSliceShort(&buf) catch {
+ log.warn("error reading data file err={?}", .{f_reader.err});
+ return error.BenchmarkFailed;
+ };
+ if (n == 0) break; // EOF reached
+ self.stream.nextSlice(buf[0..n]);
+ }
+}
+
+test ApcParser {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const impl: *ApcParser = try .create(alloc, .{});
+ defer impl.destroy(alloc);
+
+ const bench = impl.benchmark();
+ _ = try bench.run(.once);
+}