Skip to documentation
SLOP

tiny.profiling.report.flame

Reference tiny.profiling report flame

Defined in report.

API (18)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callsreport.flameloadBlockedtest; no linksrc.profiling.report.flametest: flame blocked rows keep offcput...report.flamefromBlocked
Static calls · unresolved targets: 3 · external targets: 2.
Called byCallsreport.pageflamePagereport.flamefromBlockedreport.flameloadBlocked
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linksrc.profiling.report.pageflameSummariesreport.flameloadChildren
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsreport.pageflamePagereport.flameparseFoldedreport.flameloadFolded
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsprivate; no linksrc.profiling.report.pageflameSummariesreport.flameloadSrcline
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linksrc.profiling.report.pagecallchainQualityprivate; no linksrc.profiling.report.pageflameSummariesreport.flameloadSymbols
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsreport.flameloadFoldedtest; no linksrc.profiling.report.flametest: flame parses folded stacks and ...report.flameparseFolded
Static calls · unresolved targets: 3 · external targets: 3.
Called byCallstest; no linksrc.profiling.report.flametest: flame differential baseline req...report.pageflamePagereport.flamesourcesreport.flamepreviousCpuFolded
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest; no linksrc.profiling.report.flametest: flame diff colors grown frames ...test; no linksrc.profiling.report.flametest: flame render merges prefixes le...test; no linksrc.profiling.report.flametest: flame render prunes below the m...test; no linksrc.profiling.report.flametest: flame render reports empty stac...report.pageflamePageprivate; no linksrc.profiling.report.flameaddStackprivate; no linksrc.profiling.report.flameappendFmtprivate; no linksrc.profiling.report.flameemitNodeprivate; no linksrc.profiling.report.flamemaxShiftprivate; no linksrc.profiling.report.flameprunedDepthreport.flamerender
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsreport.flamepreviousCpuFoldedtest; no linksrc.profiling.report.pagetest: page run and workload render ta...report.sitegenerateprivate; no linksrc.profiling.report.flameexistingArtifactreport.flamesources
Static calls · unresolved targets: 1 · external targets: 0.

Source: src/profiling/report/flame.zig

zig
const std = @import("std");const capture = @import("capture");const sys = @import("sys");const zen = @import("zen");const profiling = @import("../root.zig");const chart = @import("root.zig").chart;const model = @import("root.zig").model;const analyze = profiling.analyze;const host = profiling.host;const Allocator = std.mem.Allocator;const max_folded_bytes = 256 * 1024 * 1024;const max_summary_bytes = 64 * 1024 * 1024;const label_char_width = 6.6;const label_pad = 4;const min_label_width = 24;pub const Stack = struct {    frames: []const []const u8,    weight: u64,};pub const Palette = enum {    warm,    cool,};pub const Options = struct {    class: []const u8,    palette: Palette,    format: chart.Format,    unit: []const u8,    baseline: ?[]const Stack = null,    width: f64 = 1200,    row_height: f64 = 17,    min_fraction: f64 = 0.001,};pub const Sources = struct {    cpu_folded: ?[]const u8 = null,    offcpu_summary: ?[]const u8 = null,    symbols_summary: ?[]const u8 = null,    children_summary: ?[]const u8 = null,    srcline_summary: ?[]const u8 = null,    pub fn any(self: Sources) bool {        return self.cpu_folded != null or self.offcpu_summary != null or            self.symbols_summary != null or self.children_summary != null or            self.srcline_summary != null;    }};pub fn sources(allocator: Allocator, row: *const analyze.Workload) !Sources {    var result = Sources{};    for (row.captures) |row_capture| {        if (!std.mem.eql(u8, row_capture.state, "summary_written")) continue;        if (std.mem.eql(u8, row_capture.kind, host.sampling.kind)) {            const dir = std.fs.path.dirname(row_capture.capture_path) orelse continue;            result.cpu_folded = try existingArtifact(allocator, dir, host.sampling.folded_name);            result.symbols_summary = try existingArtifact(allocator, dir, host.sampling.symbols_summary_name);            result.children_summary = try existingArtifact(allocator, dir, host.sampling.children_summary_name);            result.srcline_summary = try existingArtifact(allocator, dir, host.sampling.srcline_summary_name);        } else if (std.mem.eql(u8, row_capture.kind, host.offcpu.kind)) {            if (row_capture.summary_path.len != 0 and sys.fs.exists(row_capture.summary_path)) {                result.offcpu_summary = row_capture.summary_path;            }        }    }    return result;}fn existingArtifact(allocator: Allocator, dir: []const u8, name: []const u8) !?[]const u8 {    const path = try std.fs.path.join(allocator, &.{ dir, name });    if (sys.fs.exists(path)) return path;    allocator.free(path);    return null;}pub const Previous = struct {    path: []const u8,    run_id: []const u8,};pub fn previousCpuFolded(    allocator: Allocator,    site: model.Site,    entry: *const model.Entry,    name: []const u8,) !?Previous {    var index = site.runs.len;    var passed_current = false;    while (index > 0) {        index -= 1;        const candidate = &site.runs[index];        if (!passed_current) {            if (candidate == entry) passed_current = true;            continue;        }        if (analyze.comparisonSupport(candidate.run, entry.run) != .supported) continue;        const workload = candidate.findWorkload(name) orelse continue;        const found = try sources(allocator, workload);        if (found.cpu_folded) |path| return .{ .path = path, .run_id = candidate.manifest.run_id };    }    return null;}pub fn loadFolded(allocator: Allocator, path: []const u8) ![]Stack {    const text = sys.fs.readFileAlloc(allocator, path, max_folded_bytes) catch |err| switch (err) {        error.FileNotFound => return &.{},        else => |actual| return actual,    };    return try parseFolded(allocator, text);}pub fn parseFolded(allocator: Allocator, text: []const u8) ![]Stack {    var stacks: std.ArrayList(Stack) = .empty;    var lines = std.mem.splitScalar(u8, text, '\n');    while (lines.next()) |raw_line| {        const line = std.mem.trim(u8, raw_line, " \t\r");        if (line.len == 0) continue;        const split = std.mem.lastIndexOfScalar(u8, line, ' ') orelse continue;        const stack_text = std.mem.trimEnd(u8, line[0..split], " \t");        const weight = std.fmt.parseInt(u64, line[split + 1 ..], 10) catch continue;        if (stack_text.len == 0) continue;        var frames: std.ArrayList([]const u8) = .empty;        var parts = std.mem.splitScalar(u8, stack_text, ';');        while (parts.next()) |part| {            if (part.len != 0) try frames.append(allocator, part);        }        if (frames.items.len == 0) continue;        try stacks.append(allocator, .{            .frames = try frames.toOwnedSlice(allocator),            .weight = weight,        });    }    return try stacks.toOwnedSlice(allocator);}pub fn loadSymbols(allocator: Allocator, path: []const u8) !?capture.perfreport.SymbolSummary {    const text = sys.fs.readFileAlloc(allocator, path, max_summary_bytes) catch |err| switch (err) {        error.FileNotFound => return null,        else => |actual| return actual,    };    return try capture.perfreport.parseSymbolSummaryText(allocator, path, text);}pub fn loadChildren(allocator: Allocator, path: []const u8) !?capture.perfreport.ChildrenSummary {    const text = sys.fs.readFileAlloc(allocator, path, max_summary_bytes) catch |err| switch (err) {        error.FileNotFound => return null,        else => |actual| return actual,    };    return try capture.perfreport.parseChildrenSummaryText(allocator, path, text);}pub fn loadSrcline(allocator: Allocator, path: []const u8) !?capture.perfreport.SrclineSummary {    const text = sys.fs.readFileAlloc(allocator, path, max_summary_bytes) catch |err| switch (err) {        error.FileNotFound => return null,        else => |actual| return actual,    };    return try capture.perfreport.parseSrclineSummaryText(allocator, path, text);}pub const Blocked = struct {    stacks: []Stack,    weight_unit: []const u8,};pub fn loadBlocked(allocator: Allocator, path: []const u8) !?Blocked {    const text = sys.fs.readFileAlloc(allocator, path, max_summary_bytes) catch |err| switch (err) {        error.FileNotFound => return null,        else => |actual| return actual,    };    const summary = (capture.offcpu.parseBlockedSummaryText(allocator, path, text) catch return null) orelse return null;    return .{        .stacks = try fromBlocked(allocator, summary),        .weight_unit = summary.weight_unit,    };}pub fn fromBlocked(allocator: Allocator, summary: capture.offcpu.BlockedSummary) ![]Stack {    var stacks: std.ArrayList(Stack) = .empty;    for (summary.rows) |row| {        if (row.weight == 0) continue;        var frames: std.ArrayList([]const u8) = .empty;        if (row.task) |task| {            try frames.append(allocator, task);            try frames.appendSlice(allocator, row.frames);        } else {            try frames.append(allocator, row.stack_kind);            var index = row.frames.len;            while (index > 0) {                index -= 1;                try frames.append(allocator, row.frames[index]);            }        }        try stacks.append(allocator, .{            .frames = try frames.toOwnedSlice(allocator),            .weight = row.weight,        });    }    return try stacks.toOwnedSlice(allocator);}const Node = struct {    name: []const u8,    total: u64 = 0,    self: u64 = 0,    base: u64 = 0,    children: std.StringArrayHashMapUnmanaged(*Node) = .empty,};const Side = enum {    candidate,    baseline,};fn addStack(allocator: Allocator, node: *Node, frames: []const []const u8, weight: u64, side: Side) !void {    switch (side) {        .candidate => node.total += weight,        .baseline => node.base += weight,    }    if (frames.len == 0) {        if (side == .candidate) node.self += weight;        return;    }    const slot = try node.children.getOrPut(allocator, frames[0]);    if (!slot.found_existing) {        const child = try allocator.create(Node);        child.* = .{ .name = frames[0] };        slot.value_ptr.* = child;    }    try addStack(allocator, slot.value_ptr.*, frames[1..], weight, side);}fn sortedChildren(allocator: Allocator, node: *const Node) ![]*Node {    const children = try allocator.dupe(*Node, node.children.values());    std.mem.sort(*Node, children, {}, childBefore);    return children;}fn childBefore(_: void, left: *Node, right: *Node) bool {    if (left.total == right.total) return std.mem.lessThan(u8, left.name, right.name);    return left.total > right.total;}fn prunedDepth(allocator: Allocator, node: *const Node, depth: usize, min_weight: u64) !usize {    var deepest = depth;    for (try sortedChildren(allocator, node)) |child| {        if (child.total < min_weight) continue;        deepest = @max(deepest, try prunedDepth(allocator, child, depth + 1, min_weight));    }    return deepest;}pub fn render(allocator: Allocator, stacks: []const Stack, options: Options) ![]u8 {    var out: std.ArrayList(u8) = .empty;    var root = Node{ .name = "all" };    for (stacks) |stack| try addStack(allocator, &root, stack.frames, stack.weight, .candidate);    if (options.baseline) |baseline| {        for (baseline) |stack| try addStack(allocator, &root, stack.frames, stack.weight, .baseline);    }    if (root.total == 0) {        try appendFmt(&out, allocator, "<svg class=\"flame {s}\" viewBox=\"0 0 {d:.0} 40\" width=\"100%\" role=\"img\">", .{ options.class, options.width });        try appendFmt(&out, allocator, "<text class=\"chart-empty\" x=\"{d:.0}\" y=\"24\" text-anchor=\"middle\">no stacks recorded</text></svg>", .{options.width / 2});        return try out.toOwnedSlice(allocator);    }    const min_fraction_weight: f64 = @as(f64, @floatFromInt(root.total)) * options.min_fraction;    const min_weight: u64 = @max(1, @as(u64, @intFromFloat(min_fraction_weight)));    const depth = try prunedDepth(allocator, &root, 0, min_weight);    const height = @as(f64, @floatFromInt(depth + 1)) * options.row_height + 2;    try appendFmt(&out, allocator, "<svg class=\"flame {s}\" viewBox=\"0 0 {d:.0} {d:.0}\" width=\"100%\" data-w=\"{d:.0}\" data-zs=\"0\" data-zw=\"{d}\" role=\"img\">", .{        options.class,        options.width,        height,        options.width,        root.total,    });    const context = Emit{        .allocator = allocator,        .out = &out,        .options = options,        .total = root.total,        .base_total = if (options.baseline != null) root.base else null,        .max_shift = if (options.baseline != null) maxShift(allocator, &root, root.total, root.base, min_weight) catch 0 else 0,        .min_weight = min_weight,        .height = height,    };    try emitNode(context, &root, 0, 0);    try out.appendSlice(allocator, "</svg>");    return try out.toOwnedSlice(allocator);}const Emit = struct {    allocator: Allocator,    out: *std.ArrayList(u8),    options: Options,    total: u64,    base_total: ?u64,    max_shift: f64,    min_weight: u64,    height: f64,};fn shareShift(node: *const Node, total: u64, base_total: u64) f64 {    const candidate_share = @as(f64, @floatFromInt(node.total)) / @as(f64, @floatFromInt(total));    const base_share = if (base_total == 0) 0 else @as(f64, @floatFromInt(node.base)) / @as(f64, @floatFromInt(base_total));    return (candidate_share - base_share) * 100;}fn maxShift(allocator: Allocator, node: *const Node, total: u64, base_total: u64, min_weight: u64) !f64 {    var largest: f64 = 0;    for (try sortedChildren(allocator, node)) |child| {        if (child.total < min_weight) continue;        largest = @max(largest, @abs(shareShift(child, total, base_total)));        largest = @max(largest, try maxShift(allocator, child, total, base_total, min_weight));    }    return largest;}fn emitNode(context: Emit, node: *const Node, depth: usize, offset: u64) !void {    const options = context.options;    const total: f64 = @floatFromInt(context.total);    const weight: f64 = @floatFromInt(node.total);    const x = @as(f64, @floatFromInt(offset)) / total * options.width;    const width = weight / total * options.width;    const y = context.height - @as(f64, @floatFromInt(depth + 1)) * options.row_height - 1;    const shift: ?f64 = if (context.base_total) |base_total| shareShift(node, context.total, base_total) else null;    const color = if (shift) |actual| shiftColor(actual, context.max_shift) else frameColor(node.name, options.palette);    try appendFmt(context.out, context.allocator, "<g class=\"ff\" data-s=\"{d}\" data-w=\"{d}\" data-d=\"{d}\">", .{ offset, node.total, depth });    try appendFmt(context.out, context.allocator, "<rect x=\"{d:.2}\" y=\"{d:.2}\" width=\"{d:.2}\" height=\"{d:.2}\" fill=\"rgb({d},{d},{d})\"/>", .{        x,        y,        width,        options.row_height - 1,        color[0],        color[1],        color[2],    });    try context.out.appendSlice(context.allocator, "<title>");    try appendEscaped(context.out, context.allocator, node.name);    const value = try options.format(context.allocator, weight);    const percent = weight / total * 100;    try appendFmt(context.out, context.allocator, " \u{2014} {s} {s} ({d:.1}%)", .{ value, options.unit, percent });    if (shift) |actual| {        try appendFmt(context.out, context.allocator, ", {s}{d:.1}pp vs baseline", .{ if (actual >= 0) "+" else "", actual });    }    try context.out.appendSlice(context.allocator, "</title>");    try appendFmt(context.out, context.allocator, "<text x=\"{d:.2}\" y=\"{d:.2}\">", .{ x + 3, y + options.row_height - 5 });    if (width >= min_label_width) {        try appendEscaped(context.out, context.allocator, try fitLabel(context.allocator, node.name, width));    }    try context.out.appendSlice(context.allocator, "</text></g>");    var child_offset = offset;    for (try sortedChildren(context.allocator, node)) |child| {        if (child.total >= context.min_weight) {            try emitNode(context, child, depth + 1, child_offset);        }        child_offset += child.total;    }}fn fitLabel(allocator: Allocator, name: []const u8, width: f64) ![]const u8 {    const capacity: usize = @intFromFloat(@max(0, (width - label_pad) / label_char_width));    if (name.len <= capacity) return name;    if (capacity < 3) return "";    var end = capacity - 2;    while (end > 0 and (name[end] & 0xC0) == 0x80) end -= 1;    return try std.fmt.allocPrint(allocator, "{s}..", .{name[0..end]});}fn shiftColor(shift_pp: f64, max_shift_pp: f64) [3]u8 {    const neutral = [3]u8{ 246, 243, 238 };    if (max_shift_pp <= 0) return neutral;    const strength = @min(@abs(shift_pp) / max_shift_pp, 1.0);    const target: [3]u8 = if (shift_pp >= 0) .{ 198, 44, 32 } else .{ 52, 92, 199 };    var color: [3]u8 = undefined;    for (&color, neutral, target) |*channel, from, to| {        const from_f: f64 = @floatFromInt(from);        const to_f: f64 = @floatFromInt(to);        channel.* = @intFromFloat(from_f + (to_f - from_f) * strength);    }    return color;}fn frameColor(name: []const u8, palette: Palette) [3]u8 {    const hash = std.hash.Wyhash.hash(0, name);    const h0: f64 = @floatFromInt(hash & 0xff);    const h1: f64 = @floatFromInt((hash >> 8) & 0xff);    const h2: f64 = @floatFromInt((hash >> 16) & 0xff);    return switch (palette) {        .warm => .{            205 + @as(u8, @intFromFloat(h0 / 255 * 50)),            90 + @as(u8, @intFromFloat(h1 / 255 * 120)),            30 + @as(u8, @intFromFloat(h2 / 255 * 35)),        },        .cool => .{            60 + @as(u8, @intFromFloat(h0 / 255 * 55)),            120 + @as(u8, @intFromFloat(h1 / 255 * 70)),            185 + @as(u8, @intFromFloat(h2 / 255 * 65)),        },    };}pub const script =    "(function () {\n" ++    "  function refit(svg) {\n" ++    "    var W = parseFloat(svg.dataset.w);\n" ++    "    var zs = parseFloat(svg.dataset.zs);\n" ++    "    var zw = parseFloat(svg.dataset.zw);\n" ++    "    svg.querySelectorAll('g.ff').forEach(function (g) {\n" ++    "      var s = parseFloat(g.dataset.s);\n" ++    "      var w = parseFloat(g.dataset.w);\n" ++    "      var rect = g.querySelector('rect');\n" ++    "      var text = g.querySelector('text');\n" ++    "      if (s + w <= zs || s >= zs + zw) { g.style.display = 'none'; return; }\n" ++    "      g.style.display = '';\n" ++    "      var cs = Math.max(s, zs);\n" ++    "      var ce = Math.min(s + w, zs + zw);\n" ++    "      var x = (cs - zs) / zw * W;\n" ++    "      var px = (ce - cs) / zw * W;\n" ++    "      rect.setAttribute('x', x);\n" ++    "      rect.setAttribute('width', px);\n" ++    "      text.setAttribute('x', x + 3);\n" ++    "      var name = g.querySelector('title').textContent.split(' \\u2014 ')[0];\n" ++    "      var chars = Math.floor((px - 4) / 6.6);\n" ++    "      text.textContent = px < 24 || chars < 3 ? '' :\n" ++    "        (name.length <= chars ? name : name.slice(0, chars - 2) + '..');\n" ++    "    });\n" ++    "  }\n" ++    "  document.addEventListener('click', function (event) {\n" ++    "    var g = event.target.closest('g.ff');\n" ++    "    if (!g) return;\n" ++    "    var svg = g.closest('svg.flame');\n" ++    "    if (!svg) return;\n" ++    "    svg.dataset.zs = g.dataset.s;\n" ++    "    svg.dataset.zw = g.dataset.w;\n" ++    "    refit(svg);\n" ++    "  });\n" ++    "})();\n";fn appendFmt(out: *std.ArrayList(u8), allocator: Allocator, comptime fmt: []const u8, args: anytype) Allocator.Error!void {    const text = try std.fmt.allocPrint(allocator, fmt, args);    defer allocator.free(text);    try out.appendSlice(allocator, text);}fn appendEscaped(out: *std.ArrayList(u8), allocator: Allocator, text: []const u8) Allocator.Error!void {    try zen.html.appendEscaped(out, allocator, text);}fn formatRaw(allocator: Allocator, value: f64) Allocator.Error![]u8 {    return std.fmt.allocPrint(allocator, "{d:.0}", .{value});}test "flame parses folded stacks and skips junk lines" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const stacks = try parseFolded(allocator,        \\bench;main;inner 3        \\bench;main;other 1        \\Tracing enabled        \\    );    try std.testing.expectEqual(@as(usize, 2), stacks.len);    try std.testing.expectEqual(@as(usize, 3), stacks[0].frames.len);    try std.testing.expectEqualStrings("bench", stacks[0].frames[0]);    try std.testing.expectEqualStrings("inner", stacks[0].frames[2]);    try std.testing.expectEqual(@as(u64, 3), stacks[0].weight);}test "flame render merges prefixes left-heavy with zoom metadata" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const stacks = [_]Stack{        .{ .frames = &.{ "main", "small" }, .weight = 1 },        .{ .frames = &.{ "main", "big" }, .weight = 3 },    };    const svg = try render(allocator, &stacks, .{        .class = "flame-cpu",        .palette = .warm,        .format = formatRaw,        .unit = "samples",    });    try std.testing.expectEqual(@as(usize, 4), std.mem.count(u8, svg, "<g class=\"ff\""));    try std.testing.expect(std.mem.indexOf(u8, svg, "data-zw=\"4\"") != null);    try std.testing.expect(std.mem.indexOf(u8, svg, "all \u{2014} 4 samples (100.0%)") != null);    try std.testing.expect(std.mem.indexOf(u8, svg, "big \u{2014} 3 samples (75.0%)") != null);    const big = std.mem.indexOf(u8, svg, "data-s=\"0\" data-w=\"3\" data-d=\"2\"").?;    const small = std.mem.indexOf(u8, svg, "data-s=\"3\" data-w=\"1\" data-d=\"2\"").?;    try std.testing.expect(big < small);}test "flame render prunes below the minimum fraction" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const stacks = [_]Stack{        .{ .frames = &.{ "main", "hot", "deep" }, .weight = 5000 },        .{ .frames = &.{ "main", "rare" }, .weight = 1 },    };    const svg = try render(allocator, &stacks, .{        .class = "flame-cpu",        .palette = .warm,        .format = formatRaw,        .unit = "samples",        .min_fraction = 0.01,    });    try std.testing.expect(std.mem.indexOf(u8, svg, ">deep<") != null);    try std.testing.expect(std.mem.indexOf(u8, svg, "rare") == null);}test "flame render reports empty stacks" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const svg = try render(allocator, &.{}, .{        .class = "flame-offcpu",        .palette = .cool,        .format = formatRaw,        .unit = "events",    });    try std.testing.expect(std.mem.indexOf(u8, svg, "no stacks recorded") != null);}test "flame blocked rows keep offcputime order and reverse bpftrace stacks" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const rows = [_]capture.offcpu.BlockedRow{        .{            .key = "a",            .source = "offcputime",            .subclass = "blocking_io",            .task = "worker",            .stack_kind = "folded",            .weight = 7000,            .weight_unit = "nanoseconds",            .duration_ns = 7000,            .frames = &.{ "poll", "epoll_wait" },        },        .{            .key = "b",            .source = "bpftrace",            .subclass = "cpu_scheduling",            .task = null,            .stack_kind = "kernel",            .weight = 3,            .weight_unit = "events",            .count = 3,            .frames = &.{ "finish_task_switch", "schedule", "start_thread" },        },    };    const summary = capture.offcpu.BlockedSummary{        .source_path = "offcpu.summary.json",        .source = "offcputime",        .weight_unit = "nanoseconds",        .total_weight = 7003,        .rows = &rows,    };    const stacks = try fromBlocked(allocator, summary);    try std.testing.expectEqual(@as(usize, 2), stacks.len);    try std.testing.expectEqualStrings("worker", stacks[0].frames[0]);    try std.testing.expectEqualStrings("poll", stacks[0].frames[1]);    try std.testing.expectEqualStrings("epoll_wait", stacks[0].frames[2]);    try std.testing.expectEqualStrings("kernel", stacks[1].frames[0]);    try std.testing.expectEqualStrings("start_thread", stacks[1].frames[1]);    try std.testing.expectEqualStrings("finish_task_switch", stacks[1].frames[3]);}test "flame diff colors grown frames red and shrunk frames blue" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const candidate = [_]Stack{        .{ .frames = &.{ "main", "grew" }, .weight = 6 },        .{ .frames = &.{ "main", "shrank" }, .weight = 2 },    };    const baseline = [_]Stack{        .{ .frames = &.{ "main", "grew" }, .weight = 2 },        .{ .frames = &.{ "main", "shrank" }, .weight = 6 },        .{ .frames = &.{ "main", "vanished" }, .weight = 4 },    };    const svg = try render(allocator, &candidate, .{        .class = "flame-diff",        .palette = .warm,        .format = formatRaw,        .unit = "samples",        .baseline = &baseline,    });    const grew = std.mem.indexOf(u8, svg, ">grew \u{2014} 6 samples (75.0%), +58.3pp vs baseline<").?;    const shrank = std.mem.indexOf(u8, svg, ">shrank \u{2014} 2 samples (25.0%), -25.0pp vs baseline<").?;    try std.testing.expect(grew < shrank);    try std.testing.expect(std.mem.indexOf(u8, svg, "vanished") == null);    try std.testing.expect(std.mem.indexOf(u8, svg, "main \u{2014} 8 samples (100.0%), +0.0pp vs baseline") != null);    const grew_rect = std.mem.lastIndexOf(u8, svg[0..grew], "fill=\"rgb(").?;    const grew_color = svg[grew_rect + 10 .. grew_rect + 20];    try std.testing.expect(std.mem.startsWith(u8, grew_color, "198,44,32"));    const shrank_rect = std.mem.lastIndexOf(u8, svg[0..shrank], "fill=\"rgb(").?;    var shrank_channels = std.mem.splitScalar(u8, svg[shrank_rect + 10 ..], ',');    const red = try std.fmt.parseInt(u16, shrank_channels.next().?, 10);    try std.testing.expect(red < 198);}test "flame differential baseline requires compatible host context" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const profiling_dir = ".zig-cache/profile-flame-host-context-test";    defer sys.fs.deleteTree(profiling_dir) catch {};    try model.writeTestFixture(allocator, profiling_dir);    var site = try model.load(allocator, profiling_dir);    const latest = &site.runs[1];    try std.testing.expect(        try previousCpuFolded(allocator, site, latest, "gpalloc.allocator") != null,    );    site.runs[0].run.host.hostname = "bench-b";    try std.testing.expectEqual(        @as(?Previous, null),        try previousCpuFolded(allocator, site, latest, "gpalloc.allocator"),    );}test "flame labels truncate to the frame width" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    try std.testing.expectEqualStrings("short", try fitLabel(allocator, "short", 200));    const truncated = try fitLabel(allocator, "averyverylongsymbolname", 80);    try std.testing.expect(std.mem.endsWith(u8, truncated, ".."));    try std.testing.expect(truncated.len < "averyverylongsymbolname".len);    try std.testing.expectEqualStrings("", try fitLabel(allocator, "abcdef", 10));}

Source: src/profiling/report/root.zig:3

zig
pub const flame = @import("flame.zig");

Audit

Definitions19
Public names19
Members21
Version26.7.0
Revisiondaab053ee433