a73x

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());
}