Skip to documentation
SLOP

tiny.tracy.frame

Reference tiny.tracy frame

Defined in tiny.tracy.

API (20)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callstiny.tracyframe
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callstest sourcelib.tracy.src.frametest: frames aggregate named and defa...test sourcelib.tracy.src.frametest: frames ignore out of order mark...test sourcelib.tracy.src.frametest: frames jsonl emits filtered set...test sourcelib.tracy.src.frametest: frames keep partial cadence evi...test sourcelib.tracy.src.frametest: frames reject a zero direct bud...frame.Analyzerdeinit
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallsframe.AnalyzeringestJsonLineprivate sourcelib.tracy.src.frame.AnalyzerrecordFrameframe.Analyzeringest
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsframe.AnalyzeringestJsonlBytesframe.Analyzeringestframe.AnalyzeringestJsonLine
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallstest sourcelib.tracy.src.frametest: frames aggregate named and defa...test sourcelib.tracy.src.frametest: frames ignore out of order mark...test sourcelib.tracy.src.frametest: frames jsonl emits filtered set...test sourcelib.tracy.src.frametest: frames keep partial cadence evi...frame.AnalyzeringestJsonLineframe.AnalyzeringestJsonlBytes
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.tracy.src.frametest: frames aggregate named and defa...test sourcelib.tracy.src.frametest: frames ignore out of order mark...test sourcelib.tracy.src.frametest: frames jsonl emits filtered set...test sourcelib.tracy.src.frametest: frames keep partial cadence evi...test sourcelib.tracy.src.frametest: frames reject a zero direct bud...frame.Analyzerinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.tracy.src.framewriteJsonlprivate sourcelib.tracy.src.framewriteTextprivate sourcelib.pretty.core.src.position.Summaryappendprivate sourcelib.tracy.src.framesetViewprivate sourcelib.tracy.src.framesortSetViewsframecollectSummaries
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsNo direct callersreportingestJsonlPathframeingestPath
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersreportwriteFromJsonlPathframewriteJsonlFromJsonlPath
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersreportwriteFromJsonlPathframewriteTextFromJsonlPath
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/tracy/src/frame.zig

zig
const std = @import("std");const pretty_json = @import("pretty").json;const capture_mod = @import("capture.zig");const report = @import("report.zig");const event = @import("event.zig");const record_mod = @import("record.zig");pub const schema = "tracy.frames/v0";pub const Sort = enum {    max,    total,    mean,    count,    name,    pub fn fromName(text: []const u8) ?Sort {        if (std.mem.eql(u8, text, "max")) return .max;        if (std.mem.eql(u8, text, "total")) return .total;        if (std.mem.eql(u8, text, "mean")) return .mean;        if (std.mem.eql(u8, text, "count")) return .count;        if (std.mem.eql(u8, text, "name")) return .name;        return null;    }    fn tag(self: Sort) []const u8 {        return switch (self) {            .max => "max",            .total => "total",            .mean => "mean",            .count => "count",            .name => "name",        };    }};pub const Options = struct {    top: usize = 20,    sort: Sort = .max,    min_frame_ns: u64 = 0,    budget_ns: ?u64 = null,    set: ?[]const u8 = null,};pub const Counters = struct {    marks: u64 = 0,    frames: u64 = 0,    out_of_order_marks: u64 = 0,};pub const CaptureIntegrity = capture_mod.Integrity;const FrameRecord = struct {    index: u64,    start_ns: u64,    end_ns: u64,    duration_ns: u64,    thread: u64,};const FrameSet = struct {    name: []const u8,    marks: u64 = 0,    frames: std.ArrayListUnmanaged(FrameRecord) = .empty,    threads: std.ArrayListUnmanaged(u64) = .empty,    first_ns: ?u64 = null,    last_ns: ?u64 = null,    last_mark_ns: ?u64 = null,    last_thread: u64 = 0,    total_ns: u64 = 0,    min_ns: u64 = std.math.maxInt(u64),    max_ns: u64 = 0,    fn deinit(self: *FrameSet, allocator: std.mem.Allocator) void {        self.frames.deinit(allocator);        self.threads.deinit(allocator);        self.* = undefined;    }    fn meanNs(self: FrameSet) u64 {        if (self.frames.items.len == 0) return 0;        return self.total_ns / self.frames.items.len;    }    fn threadCount(self: FrameSet) u64 {        return @intCast(self.threads.items.len);    }};pub const Summary = struct {    name: []const u8,    marks: u64,    frames: u64,    total_ns: u64,    mean_ns: u64,    min_ns: u64,    p50_ns: u64,    p90_ns: u64,    p99_ns: u64,    max_ns: u64,    first_ns: u64,    last_ns: u64,    threads: u64,    frame_time_discrepancy_ns: u64,    budget: ?BudgetSummary,};const SlowFrameView = struct {    set: []const u8,    index: u64,    start_ns: u64,    end_ns: u64,    duration_ns: u64,    thread: u64,    budget: ?FrameBudget,};pub const BudgetSummary = struct {    budget_ns: u64,    over_budget_frames: u64 = 0,    estimated_missed_intervals: u64 = 0,    total_overrun_ns: u64 = 0,    max_overrun_ns: u64 = 0,    longest_overrun_streak: u64 = 0,    longest_overrun_start_index: u64 = 0,    longest_overrun_end_index: u64 = 0,    longest_overrun_start_ns: u64 = 0,    longest_overrun_end_ns: u64 = 0,    longest_overrun_set: ?[]const u8 = null,};const FrameBudget = struct {    budget_ns: u64,    over_budget: bool,    overrun_ns: u64,    estimated_missed_intervals: u64,};pub const Analyzer = struct {    allocator: std.mem.Allocator,    capture: capture_mod.Tracker = .{},    sets: std.StringHashMapUnmanaged(FrameSet) = .{},    counters: Counters = .{},    start_ns: ?u64 = null,    end_ns: ?u64 = null,    pub fn init(allocator: std.mem.Allocator) Analyzer {        return .{ .allocator = allocator };    }    pub fn deinit(self: *Analyzer) void {        var iter = self.sets.iterator();        while (iter.next()) |entry| {            self.allocator.free(entry.key_ptr.*);            entry.value_ptr.deinit(self.allocator);        }        self.sets.deinit(self.allocator);        self.* = undefined;    }    pub fn ingestJsonlBytes(self: *Analyzer, bytes: []const u8) !void {        var lines = std.mem.splitScalar(u8, bytes, '\n');        while (lines.next()) |line| try self.ingestJsonLine(line);    }    pub fn ingestJsonLine(self: *Analyzer, line: []const u8) !void {        const text = std.mem.trim(u8, line, " \t\r\n");        if (text.len == 0) return;        var parsed = try record_mod.parseLine(self.allocator, text);        defer parsed.deinit();        switch (parsed) {            .event => |value| try self.ingest(value),            .flight => |report_value| self.capture.recordFlightReport(report_value),        }    }    pub fn ingest(self: *Analyzer, parsed: event.Parsed) !void {        self.capture.record(parsed);        if (self.start_ns == null and parsed.time_ns != 0) self.start_ns = parsed.time_ns;        if (parsed.time_ns != 0) self.end_ns = parsed.time_ns;        switch (parsed.kind) {            .start => {                if (parsed.time_ns != 0) self.start_ns = parsed.time_ns;            },            .stop => {                if (parsed.time_ns != 0) self.end_ns = parsed.time_ns;            },            .frame => try self.recordFrame(parsed),            else => {},        }    }    pub fn durationNs(self: Analyzer) u64 {        const start_ns = self.start_ns orelse return 0;        const end_ns = self.end_ns orelse return 0;        if (end_ns <= start_ns) return 0;        return end_ns - start_ns;    }    pub fn captureIntegrity(self: *const Analyzer) CaptureIntegrity {        return self.capture.integrity(0);    }    fn recordFrame(self: *Analyzer, parsed: event.Parsed) !void {        const name = parsed.name orelse "default";        const set = try self.frameSet(name);        set.marks += 1;        std.debug.assert(set.marks > 0);        try appendThread(self.allocator, &set.threads, parsed.thread);        if (set.first_ns == null) set.first_ns = parsed.time_ns;        if (set.last_mark_ns) |last_mark_ns| {            if (parsed.time_ns < last_mark_ns) {                self.counters.out_of_order_marks += 1;                self.counters.marks += 1;                return;            }            const duration = parsed.time_ns - last_mark_ns;            const index: u64 = @intCast(set.frames.items.len + 1);            try set.frames.append(self.allocator, .{                .index = index,                .start_ns = last_mark_ns,                .end_ns = parsed.time_ns,                .duration_ns = duration,                .thread = set.last_thread,            });            std.debug.assert(set.frames.items[set.frames.items.len - 1].end_ns >= last_mark_ns);            set.total_ns +|= duration;            set.min_ns = @min(set.min_ns, duration);            set.max_ns = @max(set.max_ns, duration);            self.counters.frames += 1;            std.debug.assert(set.frames.items.len <= set.marks);        }        set.last_mark_ns = parsed.time_ns;        set.last_thread = parsed.thread;        set.last_ns = parsed.time_ns;        self.counters.marks += 1;    }    fn frameSet(self: *Analyzer, name: []const u8) !*FrameSet {        const entry = try self.sets.getOrPut(self.allocator, name);        if (!entry.found_existing) {            const owned_name = try self.allocator.dupe(u8, name);            entry.key_ptr.* = owned_name;            entry.value_ptr.* = .{ .name = owned_name };        }        return entry.value_ptr;    }};pub fn writeTextFromJsonlPath(    allocator: std.mem.Allocator,    path: []const u8,    writer: *std.Io.Writer,    options: Options,) !void {    return report.writeFromJsonlPath(Analyzer, writeText, allocator, path, writer, options);}pub fn writeJsonlFromJsonlPath(    allocator: std.mem.Allocator,    path: []const u8,    writer: *std.Io.Writer,    options: Options,) !void {    return report.writeFromJsonlPath(Analyzer, writeJsonl, allocator, path, writer, options);}pub fn ingestPath(analyzer: *Analyzer, path: []const u8) !void {    return report.ingestJsonlPath(analyzer, path);}fn writeText(    allocator: std.mem.Allocator,    analyzer: *Analyzer,    writer: *std.Io.Writer,    options: Options,) !void {    try validateOptions(options);    var sets = try collectSummaries(allocator, analyzer, options);    defer sets.deinit(allocator);    var slow_frames = try collectSlowFrames(allocator, analyzer, options);    defer slow_frames.deinit(allocator);    const budget = combinedBudget(sets.items, options.budget_ns);    try writer.print(        "tracy frames sets={d} marks={d} frames={d} out_of_order_marks={d}" ++            " duration_ns={d} sort={s}",        .{            sets.items.len,            analyzer.counters.marks,            analyzer.counters.frames,            analyzer.counters.out_of_order_marks,            analyzer.durationNs(),            options.sort.tag(),        },    );    try writeBudgetText(writer, budget);    try writer.writeByte('\n');    try capture_mod.writeText(writer, analyzer.captureIntegrity());    const set_limit = @min(options.top, sets.items.len);    for (sets.items[0..set_limit]) |set| {        try writer.writeAll("frame-set name=");        try pretty_json.writeString(writer, set.name);        try writer.print(            " marks={d} frames={d} total_ns={d} mean_ns={d} min_ns={d}" ++                " p50_ns={d} p90_ns={d} p99_ns={d} max_ns={d}" ++                " first_ns={d} last_ns={d} threads={d}" ++                " frame_time_discrepancy_ns={d}",            .{                set.marks,                set.frames,                set.total_ns,                set.mean_ns,                set.min_ns,                set.p50_ns,                set.p90_ns,                set.p99_ns,                set.max_ns,                set.first_ns,                set.last_ns,                set.threads,                set.frame_time_discrepancy_ns,            },        );        try writeBudgetText(writer, set.budget);        try writer.writeByte('\n');    }    const slow_limit = @min(options.top, slow_frames.items.len);    for (slow_frames.items[0..slow_limit]) |frame| {        try writer.writeAll("slow-frame set=");        try pretty_json.writeString(writer, frame.set);        try writer.print(            " index={d} duration_ns={d} start_ns={d} end_ns={d} thread={d}",            .{ frame.index, frame.duration_ns, frame.start_ns, frame.end_ns, frame.thread },        );        try writeFrameBudgetText(writer, frame.budget);        try writer.writeByte('\n');    }}fn writeJsonl(    allocator: std.mem.Allocator,    analyzer: *Analyzer,    writer: *std.Io.Writer,    options: Options,) !void {    try validateOptions(options);    var sets = try collectSummaries(allocator, analyzer, options);    defer sets.deinit(allocator);    var slow_frames = try collectSlowFrames(allocator, analyzer, options);    defer slow_frames.deinit(allocator);    const budget = combinedBudget(sets.items, options.budget_ns);    try writeJsonSummary(writer, analyzer, sets.items.len, options.sort, budget);    const set_limit = @min(options.top, sets.items.len);    for (sets.items[0..set_limit]) |set| {        var stream = pretty_json.Writer.init(writer, .minified);        const object = try stream.object();        try object.field("schema", schema);        try object.field("kind", "set");        try object.field("name", set.name);        try object.field("marks", set.marks);        try object.field("frames", set.frames);        try object.field("total_ns", set.total_ns);        try object.field("mean_ns", set.mean_ns);        try object.field("min_ns", set.min_ns);        try object.field("p50_ns", set.p50_ns);        try object.field("p90_ns", set.p90_ns);        try object.field("p99_ns", set.p99_ns);        try object.field("max_ns", set.max_ns);        try object.field("first_ns", set.first_ns);        try object.field("last_ns", set.last_ns);        try object.field("threads", set.threads);        try object.field("frame_time_discrepancy_ns", set.frame_time_discrepancy_ns);        try writeBudgetField(object, set.budget);        try object.endLine();    }    const slow_limit = @min(options.top, slow_frames.items.len);    for (slow_frames.items[0..slow_limit]) |frame| {        var stream = pretty_json.Writer.init(writer, .minified);        const object = try stream.object();        try object.field("schema", schema);        try object.field("kind", "slow_frame");        try object.field("set", frame.set);        try object.field("index", frame.index);        try object.field("duration_ns", frame.duration_ns);        try object.field("start_ns", frame.start_ns);        try object.field("end_ns", frame.end_ns);        try object.field("thread", frame.thread);        try writeFrameBudgetField(object, frame.budget);        try object.endLine();    }}fn writeJsonSummary(    writer: *std.Io.Writer,    analyzer: *Analyzer,    set_count: usize,    sort: Sort,    budget: ?BudgetSummary,) !void {    var stream = pretty_json.Writer.init(writer, .minified);    const object = try stream.object();    try object.field("schema", schema);    try object.field("kind", "summary");    try object.field("sets", set_count);    try object.field("marks", analyzer.counters.marks);    try object.field("frames", analyzer.counters.frames);    try object.field("out_of_order_marks", analyzer.counters.out_of_order_marks);    try object.field("duration_ns", analyzer.durationNs());    try object.field("sort", sort.tag());    try writeBudgetField(object, budget);    try capture_mod.writeFields(object, analyzer.captureIntegrity());    try object.endLine();}pub fn collectSummaries(    allocator: std.mem.Allocator,    analyzer: *Analyzer,    options: Options,) !std.ArrayListUnmanaged(Summary) {    if (options.budget_ns) |budget| std.debug.assert(budget > 0);    var views: std.ArrayListUnmanaged(Summary) = .empty;    errdefer views.deinit(allocator);    var iter = analyzer.sets.valueIterator();    while (iter.next()) |set| {        if (options.set) |wanted| {            if (!std.mem.eql(u8, wanted, set.name)) continue;        }        const view = try setView(allocator, set.*, options.budget_ns);        if (view.frames == 0 and options.min_frame_ns != 0) continue;        if (view.max_ns < options.min_frame_ns) continue;        try views.append(allocator, view);    }    sortSetViews(views.items, options.sort);    return views;}fn collectSlowFrames(    allocator: std.mem.Allocator,    analyzer: *Analyzer,    options: Options,) !std.ArrayListUnmanaged(SlowFrameView) {    if (options.budget_ns) |budget| std.debug.assert(budget > 0);    var rows: std.ArrayListUnmanaged(SlowFrameView) = .empty;    errdefer rows.deinit(allocator);    var iter = analyzer.sets.valueIterator();    while (iter.next()) |set| {        if (options.set) |wanted| {            if (!std.mem.eql(u8, wanted, set.name)) continue;        }        for (set.frames.items) |frame| {            if (frame.duration_ns < options.min_frame_ns) continue;            try rows.append(allocator, .{                .set = set.name,                .index = frame.index,                .start_ns = frame.start_ns,                .end_ns = frame.end_ns,                .duration_ns = frame.duration_ns,                .thread = frame.thread,                .budget = frameBudget(frame.duration_ns, options.budget_ns),            });        }    }    std.mem.sort(SlowFrameView, rows.items, {}, slowFrameGreaterThan);    return rows;}fn setView(    allocator: std.mem.Allocator,    set: FrameSet,    budget_ns: ?u64,) !Summary {    std.debug.assert(set.frames.items.len <= set.marks);    var durations = try allocator.alloc(u64, set.frames.items.len);    defer allocator.free(durations);    for (set.frames.items, 0..) |frame, index| durations[index] = frame.duration_ns;    std.mem.sort(u64, durations, {}, std.sort.asc(u64));    return .{        .name = set.name,        .marks = set.marks,        .frames = @intCast(set.frames.items.len),        .total_ns = set.total_ns,        .mean_ns = set.meanNs(),        .min_ns = if (set.min_ns == std.math.maxInt(u64)) 0 else set.min_ns,        .p50_ns = percentile(durations, 50),        .p90_ns = percentile(durations, 90),        .p99_ns = percentile(durations, 99),        .max_ns = set.max_ns,        .first_ns = set.first_ns orelse 0,        .last_ns = set.last_ns orelse 0,        .threads = set.threadCount(),        .frame_time_discrepancy_ns = frameTimeDiscrepancyNs(set.frames.items),        .budget = budgetStats(set.frames.items, budget_ns),    };}fn validateOptions(options: Options) !void {    if (options.budget_ns == 0) return error.InvalidBudget;}fn frameBudget(duration_ns: u64, budget_ns: ?u64) ?FrameBudget {    const budget = budget_ns orelse return null;    std.debug.assert(budget > 0);    const overrun = duration_ns -| budget;    return .{        .budget_ns = budget,        .over_budget = duration_ns > budget,        .overrun_ns = overrun,        .estimated_missed_intervals = estimatedMissedIntervals(duration_ns, budget),    };}fn budgetStats(frames: []const FrameRecord, budget_ns: ?u64) ?BudgetSummary {    const budget = budget_ns orelse return null;    std.debug.assert(budget > 0);    var result = BudgetSummary{ .budget_ns = budget };    var streak: u64 = 0;    var streak_start: u64 = 0;    var streak_start_ns: u64 = 0;    for (frames) |frame| {        std.debug.assert(frame.end_ns >= frame.start_ns);        std.debug.assert(frame.duration_ns == frame.end_ns - frame.start_ns);        if (frame.duration_ns <= budget) {            streak = 0;            streak_start = 0;            streak_start_ns = 0;            continue;        }        const overrun = frame.duration_ns - budget;        result.over_budget_frames +|= 1;        result.estimated_missed_intervals +|= estimatedMissedIntervals(            frame.duration_ns,            budget,        );        result.total_overrun_ns +|= overrun;        result.max_overrun_ns = @max(result.max_overrun_ns, overrun);        if (streak == 0) {            streak_start = frame.index;            streak_start_ns = frame.start_ns;        }        streak +|= 1;        if (streak > result.longest_overrun_streak) {            result.longest_overrun_streak = streak;            result.longest_overrun_start_index = streak_start;            result.longest_overrun_end_index = frame.index;            result.longest_overrun_start_ns = streak_start_ns;            result.longest_overrun_end_ns = frame.end_ns;        }    }    if (std.math.cast(u64, frames.len)) |frame_count| {        std.debug.assert(result.over_budget_frames <= frame_count);    }    return result;}fn combinedBudget(sets: []const Summary, budget_ns: ?u64) ?BudgetSummary {    const budget = budget_ns orelse return null;    var result = BudgetSummary{ .budget_ns = budget };    for (sets) |set| {        const current = set.budget orelse continue;        std.debug.assert(current.budget_ns == budget);        result.over_budget_frames +|= current.over_budget_frames;        result.estimated_missed_intervals +|= current.estimated_missed_intervals;        result.total_overrun_ns +|= current.total_overrun_ns;        result.max_overrun_ns = @max(result.max_overrun_ns, current.max_overrun_ns);        if (preferLongestOverrun(current, set.name, result)) {            result.longest_overrun_streak = current.longest_overrun_streak;            result.longest_overrun_start_index = current.longest_overrun_start_index;            result.longest_overrun_end_index = current.longest_overrun_end_index;            result.longest_overrun_start_ns = current.longest_overrun_start_ns;            result.longest_overrun_end_ns = current.longest_overrun_end_ns;            result.longest_overrun_set = set.name;        }    }    return result;}fn preferLongestOverrun(    current: BudgetSummary,    set_name: []const u8,    selected: BudgetSummary,) bool {    if (current.longest_overrun_streak > selected.longest_overrun_streak) return true;    if (current.longest_overrun_streak < selected.longest_overrun_streak) return false;    if (current.longest_overrun_streak == 0) return false;    const selected_name = selected.longest_overrun_set orelse return true;    return std.mem.lessThan(u8, set_name, selected_name);}fn estimatedMissedIntervals(duration_ns: u64, budget_ns: u64) u64 {    std.debug.assert(budget_ns > 0);    if (duration_ns <= budget_ns) return 0;    return (duration_ns - 1) / budget_ns;}fn frameTimeDiscrepancyNs(frames: []const FrameRecord) u64 {    if (frames.len == 0) return 0;    const first_ns = frames[0].start_ns;    const last_ns = frames[frames.len - 1].end_ns;    if (last_ns <= first_ns) return 0;    const range: i256 = @intCast(last_ns - first_ns);    const intervals: i256 = @intCast(frames.len);    const denominator = 2 * intervals;    var minimum = -range;    var maximum = range;    var previous_ns = first_ns;    for (frames, 0..) |frame, frame_index| {        std.debug.assert(frame.start_ns == previous_ns);        std.debug.assert(frame.end_ns >= frame.start_ns);        std.debug.assert(frame.duration_ns == frame.end_ns - frame.start_ns);        previous_ns = frame.end_ns;        const point: i256 = @as(i256, @intCast(frame_index)) + 2;        const time_ns = frame.end_ns;        const offset: i256 = @intCast(time_ns - first_ns);        const time_term = denominator * offset;        const before = (2 * point - 3) * range - time_term;        const after = (2 * point - 1) * range - time_term;        minimum = @min(minimum, before);        maximum = @max(maximum, after);    }    const spread = maximum - minimum;    const rounded = @divTrunc(spread + denominator - 1, denominator);    return @intCast(@min(rounded, std.math.maxInt(u64)));}fn percentile(sorted: []const u64, percent: u64) u64 {    if (sorted.len == 0) return 0;    const rank: usize = @intCast((@as(u128, percent) * sorted.len + 99) / 100);    const index = @min(@max(rank, 1) - 1, sorted.len - 1);    return sorted[index];}fn appendThread(    allocator: std.mem.Allocator,    threads: *std.ArrayListUnmanaged(u64),    thread: u64,) !void {    for (threads.items) |existing| {        if (existing == thread) return;    }    try threads.append(allocator, thread);}fn sortSetViews(items: []Summary, sort: Sort) void {    switch (sort) {        .max => std.mem.sort(Summary, items, {}, setMaxGreaterThan),        .total => std.mem.sort(Summary, items, {}, setTotalGreaterThan),        .mean => std.mem.sort(Summary, items, {}, setMeanGreaterThan),        .count => std.mem.sort(Summary, items, {}, setCountGreaterThan),        .name => std.mem.sort(Summary, items, {}, setNameLessThan),    }}fn setMaxGreaterThan(_: void, left: Summary, right: Summary) bool {    if (left.max_ns != right.max_ns) return left.max_ns > right.max_ns;    return setNameLessThan({}, left, right);}fn setTotalGreaterThan(_: void, left: Summary, right: Summary) bool {    if (left.total_ns != right.total_ns) return left.total_ns > right.total_ns;    return setNameLessThan({}, left, right);}fn setMeanGreaterThan(_: void, left: Summary, right: Summary) bool {    if (left.mean_ns != right.mean_ns) return left.mean_ns > right.mean_ns;    return setTotalGreaterThan({}, left, right);}fn setCountGreaterThan(_: void, left: Summary, right: Summary) bool {    if (left.frames != right.frames) return left.frames > right.frames;    return setTotalGreaterThan({}, left, right);}fn setNameLessThan(_: void, left: Summary, right: Summary) bool {    return std.mem.lessThan(u8, left.name, right.name);}fn slowFrameGreaterThan(_: void, left: SlowFrameView, right: SlowFrameView) bool {    if (left.duration_ns != right.duration_ns) return left.duration_ns > right.duration_ns;    const set_cmp = std.mem.order(u8, left.set, right.set);    if (set_cmp != .eq) return set_cmp == .lt;    return left.index < right.index;}fn writeBudgetText(writer: *std.Io.Writer, budget: ?BudgetSummary) !void {    const value = budget orelse {        try writer.writeAll(" budget=none");        return;    };    try writer.print(        " budget_ns={d} over_budget_frames={d} estimated_missed_intervals={d}" ++            " total_overrun_ns={d} max_overrun_ns={d} longest_overrun_streak={d}" ++            " longest_overrun_start_index={d} longest_overrun_end_index={d}" ++            " longest_overrun_start_ns={d} longest_overrun_end_ns={d}",        .{            value.budget_ns,            value.over_budget_frames,            value.estimated_missed_intervals,            value.total_overrun_ns,            value.max_overrun_ns,            value.longest_overrun_streak,            value.longest_overrun_start_index,            value.longest_overrun_end_index,            value.longest_overrun_start_ns,            value.longest_overrun_end_ns,        },    );    if (value.longest_overrun_set) |set| {        try writer.writeAll(" longest_overrun_set=");        try pretty_json.writeString(writer, set);    }}fn writeFrameBudgetText(writer: *std.Io.Writer, budget: ?FrameBudget) !void {    const value = budget orelse {        try writer.writeAll(" budget=none");        return;    };    try writer.print(        " budget_ns={d} over_budget={} overrun_ns={d} estimated_missed_intervals={d}",        .{            value.budget_ns,            value.over_budget,            value.overrun_ns,            value.estimated_missed_intervals,        },    );}fn writeBudgetField(object: pretty_json.Object, budget: ?BudgetSummary) !void {    const value = budget orelse {        try object.field("budget", null);        return;    };    const budget_object = try object.object("budget");    try budget_object.field("budget_ns", value.budget_ns);    try budget_object.field("over_budget_frames", value.over_budget_frames);    try budget_object.field("estimated_missed_intervals", value.estimated_missed_intervals);    try budget_object.field("total_overrun_ns", value.total_overrun_ns);    try budget_object.field("max_overrun_ns", value.max_overrun_ns);    try budget_object.field("longest_overrun_streak", value.longest_overrun_streak);    try budget_object.field("longest_overrun_start_index", value.longest_overrun_start_index);    try budget_object.field("longest_overrun_end_index", value.longest_overrun_end_index);    try budget_object.field("longest_overrun_start_ns", value.longest_overrun_start_ns);    try budget_object.field("longest_overrun_end_ns", value.longest_overrun_end_ns);    if (value.longest_overrun_set) |set| try budget_object.field("longest_overrun_set", set);    try budget_object.end();}fn writeFrameBudgetField(object: pretty_json.Object, budget: ?FrameBudget) !void {    const value = budget orelse {        try object.field("budget", null);        return;    };    const budget_object = try object.object("budget");    try budget_object.field("budget_ns", value.budget_ns);    try budget_object.field("over_budget", value.over_budget);    try budget_object.field("overrun_ns", value.overrun_ns);    try budget_object.field("estimated_missed_intervals", value.estimated_missed_intervals);    try budget_object.end();}test "frames aggregate named and default continuous marks" {    var trace = std.Io.Writer.Allocating.init(std.testing.allocator);    defer trace.deinit();    try (event.TraceEvent{ .seq = 1, .kind = .start, .time_ns = 90, .thread = 1, .name = "test" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 2, .kind = .frame, .time_ns = 100, .thread = 1, .name = "main" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 3, .kind = .frame, .time_ns = 125, .thread = 2 }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 4, .kind = .frame, .time_ns = 130, .thread = 1, .name = "main" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 5, .kind = .frame, .time_ns = 170, .thread = 1, .name = "main" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 6, .kind = .frame, .time_ns = 225, .thread = 2 }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 7, .kind = .frame, .time_ns = 250, .thread = 1, .name = "main" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 8, .kind = .stop, .time_ns = 260, .thread = 1 }).writeJsonLine(&trace.writer);    var analyzer = Analyzer.init(std.testing.allocator);    defer analyzer.deinit();    try analyzer.ingestJsonlBytes(trace.written());    try std.testing.expectEqual(@as(u64, 6), analyzer.counters.marks);    try std.testing.expectEqual(@as(u64, 4), analyzer.counters.frames);    var out = std.Io.Writer.Allocating.init(std.testing.allocator);    defer out.deinit();    try writeText(std.testing.allocator, &analyzer, &out.writer, .{ .top = 4, .sort = .name });    const text = out.written();    try std.testing.expect(std.mem.indexOf(u8, text, "tracy frames sets=2 marks=6 frames=4") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "capture_integrity=complete") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "budget=none") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "frame-set name=\"main\" marks=4 frames=3 total_ns=150 mean_ns=50 min_ns=30 p50_ns=40 p90_ns=80 p99_ns=80 max_ns=80 first_ns=100 last_ns=250 threads=1") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "frame-set name=\"default\" marks=2 frames=1 total_ns=100 mean_ns=100") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "slow-frame set=\"default\" index=1 duration_ns=100 start_ns=125 end_ns=225 thread=2") != null);    var json = std.Io.Writer.Allocating.init(std.testing.allocator);    defer json.deinit();    try writeJsonl(std.testing.allocator, &analyzer, &json.writer, .{ .top = 0 });    try std.testing.expect(std.mem.indexOf(u8, json.written(), "\"budget\":null") != null);    try std.testing.expect(std.mem.indexOf(        u8,        json.written(),        "\"capture_integrity\":{\"method\":\"tracy_event_sequence_and_lifecycle_v1\"",    ) != null);}test "frames keep partial cadence evidence beside its capture caveat" {    var trace = std.Io.Writer.Allocating.init(std.testing.allocator);    defer trace.deinit();    try (event.TraceEvent{ .seq = 1, .kind = .start }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 3, .kind = .frame, .time_ns = 100 })        .writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 4, .kind = .frame, .time_ns = 125 })        .writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 5, .kind = .stop }).writeJsonLine(&trace.writer);    var analyzer = Analyzer.init(std.testing.allocator);    defer analyzer.deinit();    try analyzer.ingestJsonlBytes(trace.written());    var text = std.Io.Writer.Allocating.init(std.testing.allocator);    defer text.deinit();    try writeText(std.testing.allocator, &analyzer, &text.writer, .{});    try std.testing.expect(std.mem.indexOf(u8, text.written(), "frames=1") != null);    try std.testing.expect(std.mem.indexOf(        u8,        text.written(),        "capture_integrity=sequence_gaps",    ) != null);    try std.testing.expect(std.mem.indexOf(        u8,        text.written(),        "action=inspect_loss_counters_or_increase_capacity",    ) != null);    var json = std.Io.Writer.Allocating.init(std.testing.allocator);    defer json.deinit();    try writeJsonl(std.testing.allocator, &analyzer, &json.writer, .{ .top = 0 });    try std.testing.expect(std.mem.indexOf(u8, json.written(), "\"frames\":1") != null);    try std.testing.expect(std.mem.indexOf(        u8,        json.written(),        "\"status\":\"sequence_gaps\"",    ) != null);}test "frames jsonl emits filtered set and slow frame rows" {    var trace = std.Io.Writer.Allocating.init(std.testing.allocator);    defer trace.deinit();    try (event.TraceEvent{ .seq = 1, .kind = .frame, .time_ns = 100, .thread = 1, .name = "main" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 2, .kind = .frame, .time_ns = 110, .thread = 1, .name = "other" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 3, .kind = .frame, .time_ns = 190, .thread = 1, .name = "main" }).writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 4, .kind = .frame, .time_ns = 210, .thread = 1, .name = "other" }).writeJsonLine(&trace.writer);    var analyzer = Analyzer.init(std.testing.allocator);    defer analyzer.deinit();    try analyzer.ingestJsonlBytes(trace.written());    var out = std.Io.Writer.Allocating.init(std.testing.allocator);    defer out.deinit();    try writeJsonl(std.testing.allocator, &analyzer, &out.writer, .{        .top = 4,        .set = "main",        .min_frame_ns = 80,        .budget_ns = 80,    });    const text = out.written();    try std.testing.expect(std.mem.indexOf(u8, text, "\"schema\":\"tracy.frames/v0\"") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"kind\":\"set\"") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"name\":\"main\"") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"duration_ns\":90") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"kind\":\"slow_frame\"") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"over_budget_frames\":1") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"longest_overrun_set\":\"main\"") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"overrun_ns\":10") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"estimated_missed_intervals\":1") != null);    try std.testing.expect(std.mem.indexOf(u8, text, "\"name\":\"other\"") == null);    var lines = std.mem.tokenizeScalar(u8, text, '\n');    var line_count: usize = 0;    while (lines.next()) |line| {        var parsed = try std.json.parseFromSlice(            std.json.Value,            std.testing.allocator,            line,            .{},        );        parsed.deinit();        line_count += 1;    }    try std.testing.expectEqual(@as(usize, 3), line_count);}test "frames budget counts severity and consecutive overruns" {    const frames = [_]FrameRecord{        .{ .index = 1, .start_ns = 0, .end_ns = 10, .duration_ns = 10, .thread = 1 },        .{ .index = 2, .start_ns = 10, .end_ns = 31, .duration_ns = 21, .thread = 1 },        .{ .index = 3, .start_ns = 31, .end_ns = 76, .duration_ns = 45, .thread = 1 },        .{ .index = 4, .start_ns = 76, .end_ns = 86, .duration_ns = 10, .thread = 1 },        .{ .index = 5, .start_ns = 86, .end_ns = 117, .duration_ns = 31, .thread = 1 },        .{ .index = 6, .start_ns = 117, .end_ns = 149, .duration_ns = 32, .thread = 1 },        .{ .index = 7, .start_ns = 149, .end_ns = 182, .duration_ns = 33, .thread = 1 },        .{ .index = 8, .start_ns = 182, .end_ns = 192, .duration_ns = 10, .thread = 1 },    };    const budget = budgetStats(&frames, 20).?;    try std.testing.expectEqual(@as(u64, 5), budget.over_budget_frames);    try std.testing.expectEqual(@as(u64, 6), budget.estimated_missed_intervals);    try std.testing.expectEqual(@as(u64, 62), budget.total_overrun_ns);    try std.testing.expectEqual(@as(u64, 25), budget.max_overrun_ns);    try std.testing.expectEqual(@as(u64, 3), budget.longest_overrun_streak);    try std.testing.expectEqual(@as(u64, 5), budget.longest_overrun_start_index);    try std.testing.expectEqual(@as(u64, 7), budget.longest_overrun_end_index);    try std.testing.expectEqual(@as(u64, 86), budget.longest_overrun_start_ns);    try std.testing.expectEqual(@as(u64, 182), budget.longest_overrun_end_ns);}test "frames do not classify exact budget equality as an overrun" {    const exact = frameBudget(20, 20).?;    try std.testing.expect(!exact.over_budget);    try std.testing.expectEqual(@as(u64, 0), exact.overrun_ns);    try std.testing.expectEqual(@as(u64, 0), exact.estimated_missed_intervals);    try std.testing.expectEqual(@as(u64, 1), estimatedMissedIntervals(40, 20));    try std.testing.expectEqual(@as(u64, 2), estimatedMissedIntervals(41, 20));}test "frames break longest overrun ties by set name" {    var selected = BudgetSummary{        .budget_ns = 20,        .longest_overrun_streak = 2,        .longest_overrun_set = "render",    };    const current = BudgetSummary{ .budget_ns = 20, .longest_overrun_streak = 2 };    try std.testing.expect(preferLongestOverrun(current, "physics", selected));    selected.longest_overrun_set = "physics";    try std.testing.expect(!preferLongestOverrun(current, "render", selected));}test "frames discrepancy distinguishes clustered intervals" {    const uniform = [_]FrameRecord{        .{ .index = 1, .start_ns = 0, .end_ns = 10, .duration_ns = 10, .thread = 1 },        .{ .index = 2, .start_ns = 10, .end_ns = 20, .duration_ns = 10, .thread = 1 },        .{ .index = 3, .start_ns = 20, .end_ns = 30, .duration_ns = 10, .thread = 1 },        .{ .index = 4, .start_ns = 30, .end_ns = 40, .duration_ns = 10, .thread = 1 },    };    const separated = [_]FrameRecord{        .{ .index = 1, .start_ns = 0, .end_ns = 20, .duration_ns = 20, .thread = 1 },        .{ .index = 2, .start_ns = 20, .end_ns = 30, .duration_ns = 10, .thread = 1 },        .{ .index = 3, .start_ns = 30, .end_ns = 50, .duration_ns = 20, .thread = 1 },        .{ .index = 4, .start_ns = 50, .end_ns = 60, .duration_ns = 10, .thread = 1 },    };    const clustered = [_]FrameRecord{        .{ .index = 1, .start_ns = 0, .end_ns = 20, .duration_ns = 20, .thread = 1 },        .{ .index = 2, .start_ns = 20, .end_ns = 40, .duration_ns = 20, .thread = 1 },        .{ .index = 3, .start_ns = 40, .end_ns = 50, .duration_ns = 10, .thread = 1 },        .{ .index = 4, .start_ns = 50, .end_ns = 60, .duration_ns = 10, .thread = 1 },    };    try std.testing.expectEqual(@as(u64, 10), frameTimeDiscrepancyNs(&uniform));    try std.testing.expectEqual(@as(u64, 20), frameTimeDiscrepancyNs(&separated));    try std.testing.expectEqual(@as(u64, 25), frameTimeDiscrepancyNs(&clustered));}test "frames ignore out of order marks without shifting the cadence" {    var trace = std.Io.Writer.Allocating.init(std.testing.allocator);    defer trace.deinit();    try (event.TraceEvent{ .seq = 1, .kind = .frame, .time_ns = 100, .thread = 1 })        .writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 2, .kind = .frame, .time_ns = 90, .thread = 1 })        .writeJsonLine(&trace.writer);    try (event.TraceEvent{ .seq = 3, .kind = .frame, .time_ns = 120, .thread = 1 })        .writeJsonLine(&trace.writer);    var analyzer = Analyzer.init(std.testing.allocator);    defer analyzer.deinit();    try analyzer.ingestJsonlBytes(trace.written());    try std.testing.expectEqual(@as(u64, 1), analyzer.counters.frames);    try std.testing.expectEqual(@as(u64, 1), analyzer.counters.out_of_order_marks);    const set = analyzer.sets.get("default").?;    try std.testing.expectEqual(@as(u64, 20), set.frames.items[0].duration_ns);}test "frames reject a zero direct budget" {    var analyzer = Analyzer.init(std.testing.allocator);    defer analyzer.deinit();    var scratch: [1]u8 = undefined;    var out = std.Io.Writer.Discarding.init(&scratch);    try std.testing.expectError(        error.InvalidBudget,        writeText(std.testing.allocator, &analyzer, &out.writer, .{ .budget_ns = 0 }),    );}

Source: lib/tracy/src/root.zig:50

zig
pub const frame = frame_mod;

Audit

Definitions20
Public names20
Members45
Version26.7.0
Revisiondaab053ee433