Skip to documentation
SLOP

tiny.profiling.host.sampling

Reference tiny.profiling host sampling

Defined in host.

API (18)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callshost.sampling.Optionsvalidatehost.sampling.OptionsminimumTimeNs
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callershost.sampling.OptionsminimumTimeNshost.sampling.Optionsvalidate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linksrc.profiling.host.samplingclassifySupporthost.samplingsummarizetest; no linksrc.profiling.host.samplingtest: profiling sampling classifies s...host.samplingmaximumTwoSigmaShareHalfWidthPercenta...
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linksrc.profiling.host.runtimedescriptorhostwraptest; no linksrc.profiling.host.samplingtest: profiling sampling classifies s...test; no linksrc.profiling.host.samplingtest: profiling sampling reports miss...test; no linksrc.profiling.host.samplingtest: profiling sampling wraps worklo...host.samplingplan
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallshostsummarizeprivate; no linksrc.profiling.host.samplingclassifySupportprivate; no linksrc.profiling.host.samplingfailedhost.samplingmaximumTwoSigmaShareHalfWidthPercenta...private; no linksrc.profiling.host.samplingmissingToolprivate; no linksrc.profiling.host.samplingwriteFoldedprivate; no linksrc.profiling.host.samplingwriteReportshost.samplingsummarize
Static calls · unresolved targets: 1 · external targets: 6.
Called byCallsNo direct callshostwraptest; no linksrc.profiling.host.samplingtest: profiling sampling wraps worklo...host.samplingwrapArgv
Static calls · unresolved targets: 0 · external targets: 1.

Source: src/profiling/host/root.zig:8

zig
pub const sampling = runtime.sampling;

Source: src/profiling/host/sampling.zig

zig
const std = @import("std");const capture = @import("capture");const sys = @import("sys");const root = @import("root.zig");pub const tool = "perf";pub const kind = "perf_record";pub const default_frequency: u32 = 99;pub const default_min_samples: u64 = 400;pub const default_call_graph = "fp";pub const default_percent_limit: f64 = 0.5;pub const folded_name = "perf.folded.txt";pub const symbols_summary_name = "perf.symbols.summary.json";pub const children_summary_name = "perf.children.summary.json";pub const srcline_summary_name = "perf.srcline.summary.json";pub const Options = struct {    frequency: u32 = default_frequency,    min_samples: u64 = default_min_samples,    call_graph: []const u8 = default_call_graph,    report_percent_limit: f64 = default_percent_limit,    pub fn validate(self: Options) !void {        if (self.call_graph.len == 0) return error.InvalidSamplingCallGraph;        if (!std.math.isFinite(self.report_percent_limit) or            self.report_percent_limit < 0 or            self.report_percent_limit > 100)        {            return error.InvalidSamplingPercentLimit;        }        _ = try self.minimumTimeNs();    }    pub fn minimumTimeNs(self: Options) !u64 {        if (self.frequency == 0) return error.InvalidSamplingFrequency;        if (self.min_samples == 0) return error.InvalidMinimumSampleCount;        const numerator = std.math.mul(            u64,            self.min_samples,            std.time.ns_per_s,        ) catch return error.SamplingDurationOverflow;        return std.math.divCeil(u64, numerator, self.frequency) catch            error.SamplingDurationOverflow;    }};pub const Lane = struct {    frequency: u32,    min_samples: u64,    call_graph: []const u8,    report_percent_limit: f64,    data_path: []const u8,    symbols_tsv_path: []const u8,    symbols_summary_path: []const u8,    children_tsv_path: []const u8,    children_summary_path: []const u8,    srcline_tsv_path: []const u8,    srcline_summary_path: []const u8,    folded_path: []const u8,};pub fn plan(    allocator: std.mem.Allocator,    options: Options,    workload_root: []const u8,) !Lane {    std.debug.assert(workload_root.len > 0);    try options.validate();    return .{        .frequency = options.frequency,        .min_samples = options.min_samples,        .call_graph = options.call_graph,        .report_percent_limit = options.report_percent_limit,        .data_path = try std.fs.path.join(allocator, &.{ workload_root, "perf.data" }),        .symbols_tsv_path = try std.fs.path.join(            allocator,            &.{ workload_root, "perf.symbols.tsv" },        ),        .symbols_summary_path = try std.fs.path.join(            allocator,            &.{ workload_root, symbols_summary_name },        ),        .children_tsv_path = try std.fs.path.join(            allocator,            &.{ workload_root, "perf.children.tsv" },        ),        .children_summary_path = try std.fs.path.join(            allocator,            &.{ workload_root, children_summary_name },        ),        .srcline_tsv_path = try std.fs.path.join(            allocator,            &.{ workload_root, "perf.srcline.tsv" },        ),        .srcline_summary_path = try std.fs.path.join(            allocator,            &.{ workload_root, srcline_summary_name },        ),        .folded_path = try std.fs.path.join(allocator, &.{ workload_root, folded_name }),    };}pub fn wrapArgv(    allocator: std.mem.Allocator,    options: Options,    argv: []const []const u8,    lane: Lane,) ![]const []const u8 {    std.debug.assert(argv.len > 0);    std.debug.assert(lane.data_path.len > 0);    return try capture.perfdata.recordCommand(        allocator,        tool,        argv,        lane.data_path,        options.call_graph,        options.frequency,    );}pub fn summarize(    allocator: std.mem.Allocator,    process_io: std.Io,    lane: Lane,    exit_code: i64,    stderr_path: []const u8,    tool_available: bool,) !root.Capture {    std.debug.assert(lane.data_path.len > 0);    std.debug.assert(stderr_path.len > 0);    if (!tool_available) return missingTool(lane);    if (!sys.fs.exists(lane.data_path)) {        return try failed(allocator, lane, exit_code, stderr_path);    }    try writeReports(allocator, process_io, lane);    var symbols = try capture.perfreport.summarizeSymbolReportFile(        allocator,        lane.symbols_tsv_path,    );    if (symbols.rows.len == 0) {        return try failed(allocator, lane, exit_code, stderr_path);    }    symbols.sampling_frequency_hz = lane.frequency;    symbols.minimum_sample_count = lane.min_samples;    symbols.call_graph = lane.call_graph;    symbols.report_percent_limit = lane.report_percent_limit;    symbols.maximum_two_sigma_share_half_width_percentage_points =        maximumTwoSigmaShareHalfWidthPercentagePoints(symbols.sample_count_approx);    symbols.callchain_quality = try writeFolded(allocator, process_io, lane);    try capture.perfreport.writeSymbolSummaryFile(lane.symbols_summary_path, symbols);    var children = try capture.perfreport.summarizeChildrenReportFile(        allocator,        lane.children_tsv_path,    );    children.sampling_frequency_hz = lane.frequency;    children.minimum_sample_count = lane.min_samples;    children.call_graph = lane.call_graph;    children.report_percent_limit = lane.report_percent_limit;    try capture.perfreport.writeChildrenSummaryFile(lane.children_summary_path, children);    var srcline = try capture.perfreport.summarizeSrclineReportFile(        allocator,        lane.srcline_tsv_path,    );    srcline.sampling_frequency_hz = lane.frequency;    srcline.minimum_sample_count = lane.min_samples;    srcline.call_graph = lane.call_graph;    srcline.report_percent_limit = lane.report_percent_limit;    try capture.perfreport.writeSrclineSummaryFile(lane.srcline_summary_path, srcline);    return try classifySupport(        allocator,        lane,        symbols.sample_count_approx,        symbols.sample_count_text,        symbols.total_lost_samples,        symbols.callchain_quality,    );}fn writeReports(    allocator: std.mem.Allocator,    process_io: std.Io,    lane: Lane,) !void {    const percent_limit_text = try std.fmt.allocPrint(        allocator,        "{d}",        .{lane.report_percent_limit},    );    const symbols = try capture.perfdata.symbolsTsvCommand(        allocator,        tool,        lane.data_path,        percent_limit_text,    );    try reportToFile(allocator, process_io, symbols, lane.symbols_tsv_path);    const children = try capture.perfdata.childrenTsvCommand(        allocator,        tool,        lane.data_path,        percent_limit_text,    );    try reportToFile(allocator, process_io, children, lane.children_tsv_path);    const srcline = try capture.perfdata.srclineTsvCommand(        allocator,        tool,        lane.data_path,        percent_limit_text,    );    try reportToFile(allocator, process_io, srcline, lane.srcline_tsv_path);}fn classifySupport(    allocator: std.mem.Allocator,    lane: Lane,    sample_count: ?u64,    sample_count_text: ?[]const u8,    total_lost_samples: ?u64,    callchain_quality: ?capture.stackcollapse.Summary,) !root.Capture {    std.debug.assert(lane.min_samples > 0);    if (sample_count != null and sample_count.? >= lane.min_samples) {        if (total_lost_samples == null) return .{            .kind = kind,            .tool = tool,            .capture_path = lane.data_path,            .summary_path = lane.symbols_summary_path,            .state = "summary_written",            .caveat_kind = "sample_loss_unknown",            .caveat_message = "perf did not report a lost-sample count; sampling completeness is unknown",        };        if (total_lost_samples.? != 0) {            const observed = sample_count.?;            const lost = total_lost_samples.?;            const lost_percent = @as(f64, @floatFromInt(lost)) /                (@as(f64, @floatFromInt(observed)) + @as(f64, @floatFromInt(lost))) * 100.0;            return .{                .kind = kind,                .tool = tool,                .capture_path = lane.data_path,                .summary_path = lane.symbols_summary_path,                .state = "summary_written",                .caveat_kind = "samples_lost",                .caveat_message = try std.fmt.allocPrint(                    allocator,                    "perf lost {d} sample(s) ({d:.2}% of observed plus lost samples); attribution is caveated because loss need not be random",                    .{ lost, lost_percent },                ),            };        }        const quality = callchain_quality orelse return .{            .kind = kind,            .tool = tool,            .capture_path = lane.data_path,            .summary_path = lane.symbols_summary_path,            .state = "summary_written",            .caveat_kind = "callchain_unavailable",            .caveat_message = "perf reported samples without extractable callchains; flame attribution is unavailable",        };        if (parseExactSampleCount(sample_count_text)) |report_count| {            if (report_count != quality.sample_count) return .{                .kind = kind,                .tool = tool,                .capture_path = lane.data_path,                .summary_path = lane.symbols_summary_path,                .state = "summary_written",                .caveat_kind = "callchain_sample_mismatch",                .caveat_message = try std.fmt.allocPrint(                    allocator,                    "perf report counted {d} samples but perf script yielded {d} callchain samples; flame attribution is incomplete",                    .{ report_count, quality.sample_count },                ),            };        }        return .{            .kind = kind,            .tool = tool,            .capture_path = lane.data_path,            .summary_path = lane.symbols_summary_path,            .state = "summary_written",        };    }    const support_error = maximumTwoSigmaShareHalfWidthPercentagePoints(        lane.min_samples,    ).?;    const message = if (sample_count) |observed|        try std.fmt.allocPrint(            allocator,            "perf captured {d} samples; {d} are required for an approximate " ++                "worst-case two-standard-error share half-width no wider than " ++                "{d:.2} percentage points",            .{ observed, lane.min_samples, support_error },        )    else        try std.fmt.allocPrint(            allocator,            "perf did not report a sample count; {d} are required for an " ++                "approximate worst-case two-standard-error share half-width no " ++                "wider than {d:.2} percentage points",            .{ lane.min_samples, support_error },        );    return .{        .kind = kind,        .tool = tool,        .capture_path = lane.data_path,        .summary_path = lane.symbols_summary_path,        .state = "insufficient_samples",        .caveat_kind = "insufficient_sample_support",        .caveat_message = message,    };}pub fn maximumTwoSigmaShareHalfWidthPercentagePoints(sample_count: ?u64) ?f64 {    const count = sample_count orelse return null;    if (count == 0) return null;    return 100.0 / @sqrt(@as(f64, @floatFromInt(count)));}fn parseExactSampleCount(sample_count_text: ?[]const u8) ?u64 {    const trimmed = std.mem.trim(u8, sample_count_text orelse return null, " \t\r");    if (trimmed.len == 0) return null;    for (trimmed) |byte| if (!std.ascii.isDigit(byte)) return null;    return std.fmt.parseInt(u64, trimmed, 10) catch null;}fn writeFolded(    allocator: std.mem.Allocator,    process_io: std.Io,    lane: Lane,) !?capture.stackcollapse.Summary {    const argv = try capture.perfdata.scriptCommand(allocator, tool, lane.data_path);    const result = sys.process.run(allocator, process_io, .{        .argv = argv,        .stdout_limit = .limited(512 * 1024 * 1024),        .stderr_limit = .limited(64 * 1024),    }) catch return null;    defer allocator.free(result.stdout);    defer allocator.free(result.stderr);    if (sys.process.exitCode(result.term) != 0 or result.stdout.len == 0) return null;    const artifact = try capture.stackcollapse.collapsePerfScriptArtifact(        allocator,        result.stdout,    );    if (artifact.folded.len == 0) return null;    try sys.fs.writeFile(lane.folded_path, artifact.folded);    return artifact.summary;}fn reportToFile(    allocator: std.mem.Allocator,    process_io: std.Io,    argv: []const []const u8,    output_path: []const u8,) !void {    std.debug.assert(argv.len > 0);    std.debug.assert(output_path.len > 0);    const file = try sys.fs.createFile(output_path, .{ .truncate = true });    defer sys.fs.closeHandle(file);    var child = try sys.process.spawn(process_io, .{        .argv = argv,        .stdin = .ignore,        .stdout = .{ .file = file },        .stderr = .ignore,    });    errdefer sys.process.killAndReap(&child, process_io);    _ = try sys.process.wait(&child, process_io);    _ = allocator;}fn missingTool(lane: Lane) root.Capture {    return .{        .kind = kind,        .tool = tool,        .capture_path = lane.data_path,        .summary_path = lane.symbols_summary_path,        .state = "tool_missing",        .caveat_kind = "tool_unavailable",        .caveat_message = "perf is not runnable on this host; samples were not captured",    };}fn failed(    allocator: std.mem.Allocator,    lane: Lane,    exit_code: i64,    stderr_path: []const u8,) !root.Capture {    const row = try capture.caveat.classifyCommandCapture(allocator, .{        .phase = kind,        .exit_code = if (exit_code == 0) 1 else exit_code,        .timed_out = false,        .capture_path = stderr_path,    }) orelse capture.caveat.classifyCommandText(.{        .phase = kind,        .exit_code = exit_code,        .timed_out = false,        .capture_path = stderr_path,    }, "");    return .{        .kind = kind,        .tool = tool,        .capture_path = lane.data_path,        .summary_path = lane.symbols_summary_path,        .state = "tool_failed",        .caveat_kind = row.kind,        .caveat_message = row.message,    };}test "profiling sampling wraps workload argv in perf record" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const lane = try plan(allocator, .{}, "zig-out/profiling/run/workloads/w");    const argv = [_][]const u8{ "zig", "build", "bench" };    const wrapped = try wrapArgv(allocator, .{}, &argv, lane);    try std.testing.expectEqualStrings("perf", wrapped[0]);    try std.testing.expectEqualStrings("record", wrapped[1]);    try std.testing.expectEqualStrings(        "zig-out/profiling/run/workloads/w/perf.data",        lane.data_path,    );    try std.testing.expectEqualStrings(        "zig-out/profiling/run/workloads/w/perf.folded.txt",        lane.folded_path,    );}test "profiling sampling derives runtime from its support floor" {    const options = Options{};    try options.validate();    try std.testing.expectEqual(@as(u64, 4_040_404_041), try options.minimumTimeNs());    try std.testing.expectError(        error.InvalidSamplingFrequency,        (Options{ .frequency = 0 }).validate(),    );    try std.testing.expectError(        error.InvalidMinimumSampleCount,        (Options{ .min_samples = 0 }).validate(),    );    try std.testing.expectError(        error.InvalidSamplingCallGraph,        (Options{ .call_graph = "" }).validate(),    );    try std.testing.expectError(        error.InvalidSamplingPercentLimit,        (Options{ .report_percent_limit = 101 }).validate(),    );    try std.testing.expectError(        error.SamplingDurationOverflow,        (Options{ .min_samples = std.math.maxInt(u64) }).validate(),    );}test "profiling sampling classifies support at the configured floor" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const lane = try plan(allocator, .{}, "zig-out/profiling/run/workloads/w");    const quality: capture.stackcollapse.Summary = .{        .sample_count = 400,        .unique_callchain_count = 10,        .weighted_frame_count = 2000,        .resolved_frame_count = 1900,        .unresolved_frame_count = 100,        .samples_with_unresolved_frames = 50,        .unresolved_leaf_samples = 10,        .leaf_only_samples = 2,        .maximum_depth_frames = 10,    };    const short = try classifySupport(allocator, lane, 399, "399", 0, null);    try std.testing.expectEqualStrings("insufficient_samples", short.state);    try std.testing.expectEqualStrings(        "insufficient_sample_support",        short.caveat_kind.?,    );    try std.testing.expect(std.mem.indexOf(        u8,        short.caveat_message.?,        "5.00 percentage points",    ) != null);    const unknown = try classifySupport(allocator, lane, null, null, 0, null);    try std.testing.expectEqualStrings("insufficient_samples", unknown.state);    const unknown_loss = try classifySupport(allocator, lane, 400, "400", null, null);    try std.testing.expectEqualStrings("summary_written", unknown_loss.state);    try std.testing.expectEqualStrings("sample_loss_unknown", unknown_loss.caveat_kind.?);    const lost = try classifySupport(allocator, lane, 400, "400", 1, null);    try std.testing.expectEqualStrings("summary_written", lost.state);    try std.testing.expectEqualStrings("samples_lost", lost.caveat_kind.?);    try std.testing.expect(std.mem.indexOf(u8, lost.caveat_message.?, "0.25%") != null);    const missing_callchains = try classifySupport(allocator, lane, 400, "400", 0, null);    try std.testing.expectEqualStrings("callchain_unavailable", missing_callchains.caveat_kind.?);    var short_quality = quality;    short_quality.sample_count = 399;    const mismatch = try classifySupport(allocator, lane, 400, "400", 0, short_quality);    try std.testing.expectEqualStrings("callchain_sample_mismatch", mismatch.caveat_kind.?);    try std.testing.expect(std.mem.indexOf(u8, mismatch.caveat_message.?, "399") != null);    const abbreviated = try classifySupport(allocator, lane, 400, "0.4K", 0, short_quality);    try std.testing.expect(abbreviated.caveat_kind == null);    const supported = try classifySupport(allocator, lane, 400, "400", 0, quality);    try std.testing.expectEqualStrings("summary_written", supported.state);    try std.testing.expect(supported.caveat_kind == null);    try std.testing.expectApproxEqAbs(        @as(f64, 5),        maximumTwoSigmaShareHalfWidthPercentagePoints(400).?,        0.0000001,    );    try std.testing.expectEqual(@as(u64, 400), parseExactSampleCount(" 400 ").?);    try std.testing.expect(parseExactSampleCount("400K") == null);}test "profiling sampling reports missing tools as caveats" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const lane = try plan(allocator, .{}, "zig-out/profiling/run/workloads/w");    const row = missingTool(lane);    try std.testing.expectEqualStrings("tool_missing", row.state);    try std.testing.expectEqualStrings("perf_record", row.kind);}

Audit

Definitions19
Public names19
Members16
Version26.7.0
Revisiondaab053ee433