src/gui/bench.zig
Ref: Size: 3.6 KiB History
//! Per-frame stage timings. `total` is window work only; pump apply time is
//! reported beside it rather than folded into window latency.
const std = @import("std");
pub const Frame = struct {
apply_us: u32 = 0,
rebuild_us: u32 = 0,
atlas_us: u32 = 0,
upload_us: u32 = 0,
draw_us: u32 = 0,
fn windowTotal(f: Frame) u32 {
return f.rebuild_us +| f.atlas_us +| f.upload_us +| f.draw_us;
}
};
pub const Ring = struct {
pub const capacity = 256;
frames: [capacity]Frame = undefined,
next: usize = 0,
recorded: u64 = 0,
pub fn record(self: *Ring, f: Frame) void {
self.frames[self.next] = f;
self.next = (self.next + 1) % capacity;
self.recorded += 1;
}
pub fn count(self: *const Ring) u64 {
return self.recorded;
}
fn held(self: *const Ring) usize {
return @intCast(@min(self.recorded, capacity));
}
const Stats = struct { min: u32, avg: u32, p99: u32, max: u32 };
fn stats(self: *const Ring, comptime pick: fn (Frame) u32) Stats {
const n = self.held();
var vals: [capacity]u32 = undefined;
var sum: u64 = 0;
for (self.frames[0..n], 0..) |f, i| {
vals[i] = pick(f);
sum += vals[i];
}
std.mem.sort(u32, vals[0..n], {}, std.sort.asc(u32));
const rank = (n * 99 + 99) / 100;
return .{ .min = vals[0], .avg = @intCast(sum / n), .p99 = vals[rank - 1], .max = vals[n - 1] };
}
pub fn report(self: *const Ring, buf: []u8) []const u8 {
var w = std.io.Writer.fixed(buf);
w.print("=== muxg frame timing ({d} frames) ===\n", .{self.recorded}) catch return w.buffered();
w.print("{s:<16}{s:>6}{s:>7}{s:>7}{s:>7} (us)\n", .{ "stage", "min", "avg", "p99", "max" }) catch return w.buffered();
if (self.held() == 0) {
w.print("{s:<16}{d:>6}{d:>7}{d:>7}{d:>7}\n", .{ "total", 0, 0, 0, 0 }) catch {};
return w.buffered();
}
const rows = .{
.{ "apply", struct {
fn f(x: Frame) u32 {
return x.apply_us;
}
}.f },
.{ "rebuild", struct {
fn f(x: Frame) u32 {
return x.rebuild_us;
}
}.f },
.{ "atlas_upload", struct {
fn f(x: Frame) u32 {
return x.atlas_us;
}
}.f },
.{ "instance_upload", struct {
fn f(x: Frame) u32 {
return x.upload_us;
}
}.f },
.{ "draw_swap", struct {
fn f(x: Frame) u32 {
return x.draw_us;
}
}.f },
.{ "total", Frame.windowTotal },
};
inline for (rows) |row| {
const s = self.stats(row[1]);
w.print("{s:<16}{d:>6}{d:>7}{d:>7}{d:>7}\n", .{ row[0], s.min, s.avg, s.p99, s.max }) catch return w.buffered();
}
return w.buffered();
}
};
pub fn usSince(timer: *std.time.Timer) u32 {
return @intCast(@min(timer.lap() / std.time.ns_per_us, std.math.maxInt(u32)));
}
test "total excludes apply and ring retains newest samples" {
var r: Ring = .{};
var i: u32 = 0;
while (i < Ring.capacity + 10) : (i += 1) r.record(.{ .apply_us = 999, .rebuild_us = i, .draw_us = 1 });
var buf: [2048]u8 = undefined;
const out = r.report(&buf);
try std.testing.expect(std.mem.indexOf(u8, out, "total 11") != null);
try std.testing.expectEqual(@as(u64, 266), r.count());
}