tiny.profiling.host.offcpu
Defined in host.
API (11)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: src/profiling/host/offcpu.zig
zig
const std = @import("std");const capture = @import("capture");const sys = @import("sys");const root = @import("root.zig");pub const default_time_tool = "offcputime-bpfcc";pub const default_stacks_tool = "bpftrace";pub const kind = "offcpu_time";pub const Mode = enum { time, stacks, pub fn parse(value: []const u8) ?Mode { if (std.mem.eql(u8, value, "time")) return .time; if (std.mem.eql(u8, value, "stacks")) return .stacks; return null; }};pub const Options = struct { tool: ?[]const u8 = null, mode: Mode = .time, pub fn toolName(self: Options) []const u8 { return self.tool orelse switch (self.mode) { .time => default_time_tool, .stacks => default_stacks_tool, }; }};pub const Lane = struct { tool: []const u8, mode: Mode, capture_path: []const u8, diagnostics_path: []const u8, summary_path: []const u8,};pub fn plan(allocator: std.mem.Allocator, options: Options, workload_root: []const u8) !Lane { const capture_name = switch (options.mode) { .time => "offcpu.time.txt", .stacks => "offcpu.stacks.txt", }; return .{ .tool = options.toolName(), .mode = options.mode, .capture_path = try std.fs.path.join(allocator, &.{ workload_root, capture_name }), .diagnostics_path = try std.fs.path.join( allocator, &.{ workload_root, "offcpu.diagnostics.txt" }, ), .summary_path = try std.fs.path.join(allocator, &.{ workload_root, "offcpu.summary.json" }), };}pub fn wrapArgv(allocator: std.mem.Allocator, options: Options, argv: []const []const u8, lane: Lane) ![]const []const u8 { _ = options; switch (lane.mode) { .time => return try capture.offcpu.timeCommand( allocator, lane.tool, argv, lane.capture_path, lane.diagnostics_path, ), .stacks => { const command = try std.mem.join(allocator, " ", argv); return try capture.offcpu.stacksCommand(allocator, lane.tool, command, lane.capture_path); }, }}pub fn summarize( allocator: std.mem.Allocator, lane: Lane, exit_code: i64, stderr_path: []const u8, tool_available: bool,) !root.Capture { if (!tool_available) return missingTool(lane); var blocked = switch (lane.mode) { .time => try loadBlockedTime(allocator, lane.capture_path), .stacks => try loadBlockedStacks(allocator, lane.capture_path), } orelse { return try failed(allocator, lane, exit_code, stderr_path); }; if (lane.mode == .time) { const integrity = capture.offcpu.summarizeCollectorDiagnosticsFile( allocator, lane.diagnostics_path, ) catch |err| switch (err) { error.FileNotFound => capture.offcpu.missingCaptureIntegrity(), else => return err, }; blocked.diagnostics_path = lane.diagnostics_path; blocked.capture_integrity = integrity; } try capture.offcpu.writeBlockedSummaryFile(lane.summary_path, blocked); return summarized(lane, blocked.capture_integrity);}fn loadBlockedTime( allocator: std.mem.Allocator, capture_path: []const u8,) !?capture.offcpu.BlockedSummary { const summary = capture.offcpu.summarizeTimeFile( allocator, capture_path, ) catch |err| switch (err) { error.FileNotFound => return null, else => |actual| return actual, }; if (summary.rows.len == 0) return null; return try capture.offcpu.blockedFromTimeSummary(allocator, summary);}fn loadBlockedStacks( allocator: std.mem.Allocator, capture_path: []const u8,) !?capture.offcpu.BlockedSummary { const summary = capture.offcpu.summarizeStacksFile( allocator, capture_path, ) catch |err| switch (err) { error.FileNotFound => return null, else => |actual| return actual, }; if (summary.rows.len == 0) return null; return try capture.offcpu.blockedFromStackSummary(allocator, summary);}fn summarized( lane: Lane, integrity: ?capture.offcpu.CaptureIntegrity,) root.Capture { var result: root.Capture = .{ .kind = kind, .tool = lane.tool, .capture_path = lane.capture_path, .summary_path = lane.summary_path, .state = "summary_written", }; const actual = integrity orelse return result; if (std.mem.eql(u8, actual.status, "complete")) return result; result.caveat_kind = if (std.mem.eql(u8, actual.status, "partial")) "offcpu_capture_partial" else "offcpu_capture_integrity_unknown"; result.caveat_message = actual.message; return result;}fn missingTool(lane: Lane) root.Capture { return .{ .kind = kind, .tool = lane.tool, .capture_path = lane.capture_path, .summary_path = lane.summary_path, .state = "tool_missing", .caveat_kind = "tool_unavailable", .caveat_message = "the off-CPU profiler is not runnable on this host; blocked time was not captured", };}fn failed(allocator: std.mem.Allocator, lane: Lane, exit_code: i64, stderr_path: []const u8) !root.Capture { const failure_path = try failureCapturePath( allocator, lane.diagnostics_path, stderr_path, ); const row = try capture.caveat.classifyCommandCapture(allocator, .{ .phase = kind, .exit_code = if (exit_code == 0) 1 else exit_code, .timed_out = false, .capture_path = failure_path, }) orelse capture.caveat.classifyCommandText(.{ .phase = kind, .exit_code = exit_code, .timed_out = false, .capture_path = failure_path, }, ""); return .{ .kind = kind, .tool = lane.tool, .capture_path = lane.capture_path, .summary_path = lane.summary_path, .state = "tool_failed", .caveat_kind = row.kind, .caveat_message = row.message, };}fn failureCapturePath( allocator: std.mem.Allocator, diagnostics_path: []const u8, stderr_path: []const u8,) ![]const u8 { const diagnostics = sys.fs.readFileAlloc( allocator, diagnostics_path, 64 * 1024 * 1024, ) catch |err| switch (err) { error.OutOfMemory => return err, else => return stderr_path, }; defer allocator.free(diagnostics); if (std.mem.trim(u8, diagnostics, " \t\r\n").len == 0) return stderr_path; return diagnostics_path;}test "profiling offcpu wraps direct binaries in the offcputime sidecar" { 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-out/bin/mprompt-bench", "--workers", "128" }; const wrapped = try wrapArgv(allocator, .{}, &argv, lane); try std.testing.expectEqualStrings("bash", wrapped[0]); try std.testing.expectEqualStrings("-c", wrapped[1]); try std.testing.expectEqualStrings("offcputime-bpfcc", wrapped[4]); try std.testing.expectEqualStrings("zig-out/profiling/run/workloads/w/offcpu.time.txt", wrapped[5]); try std.testing.expectEqualStrings( "zig-out/profiling/run/workloads/w/offcpu.diagnostics.txt", wrapped[6], ); try std.testing.expectEqualStrings("zig-out/bin/mprompt-bench", wrapped[7]); try std.testing.expectEqualStrings("--workers", wrapped[8]);}test "profiling offcpu wraps direct binaries in the bpftrace stacks tracer" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const lane = try plan(allocator, .{ .mode = .stacks }, "zig-out/profiling/run/workloads/w"); try std.testing.expectEqualStrings("bpftrace", lane.tool); const argv = [_][]const u8{ "zig-out/bin/mprompt-bench", "--workers", "128" }; const wrapped = try wrapArgv(allocator, .{ .mode = .stacks }, &argv, lane); try std.testing.expectEqualStrings("bpftrace", wrapped[0]); try std.testing.expectEqualStrings("zig-out/profiling/run/workloads/w/offcpu.stacks.txt", wrapped[3]); try std.testing.expectEqualStrings("zig-out/bin/mprompt-bench --workers 128", wrapped[wrapped.len - 1]);}test "profiling offcpu summarizes captured blocked time" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const dir = try std.fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }); const lane = try plan(allocator, .{}, dir); try sys.fs.writeFile(lane.capture_path, \\bench-worker;futex_wait;do_futex 4200 \\bench-main;epoll_wait 1300 \\ ); const stderr_path = try std.fs.path.join(allocator, &.{ dir, "stderr.txt" }); try sys.fs.writeFile(stderr_path, ""); try sys.fs.writeFile(lane.diagnostics_path, ""); const row = try summarize(allocator, lane, 0, stderr_path, true); try std.testing.expectEqualStrings("summary_written", row.state); try std.testing.expect(row.caveat_kind == null); try std.testing.expect(sys.fs.exists(lane.summary_path));}test "profiling offcpu retains rows and caveats collector loss" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const dir = try std.fs.path.join( allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }, ); const lane = try plan(allocator, .{}, dir); try sys.fs.writeFile(lane.capture_path, "bench;futex_wait;do_futex 4200\n"); try sys.fs.writeFile( lane.diagnostics_path, "WARNING: 2 stack traces lost and could not be displayed.\n", ); const stderr_path = try std.fs.path.join(allocator, &.{ dir, "stderr.txt" }); try sys.fs.writeFile(stderr_path, "workload warning that is not collector evidence\n"); const row = try summarize(allocator, lane, 0, stderr_path, true); try std.testing.expectEqualStrings("summary_written", row.state); try std.testing.expectEqualStrings("offcpu_capture_partial", row.caveat_kind.?); try std.testing.expect(std.mem.indexOf( u8, row.caveat_message.?, "2 stack trace(s) lost", ) != null); const text = try sys.fs.readFileAlloc(allocator, lane.summary_path, 64 * 1024); try std.testing.expect(std.mem.indexOf(u8, text, "\"lost_stack_trace_count\": 2") != null); try std.testing.expect(std.mem.indexOf(u8, text, "workload warning") == null);}test "profiling offcpu marks missing collector diagnostics unknown" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const dir = try std.fs.path.join( allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }, ); const lane = try plan(allocator, .{}, dir); try sys.fs.writeFile(lane.capture_path, "bench;futex_wait;do_futex 4200\n"); const stderr_path = try std.fs.path.join(allocator, &.{ dir, "stderr.txt" }); try sys.fs.writeFile(stderr_path, ""); const row = try summarize(allocator, lane, 0, stderr_path, true); try std.testing.expectEqualStrings("summary_written", row.state); try std.testing.expectEqualStrings( "offcpu_capture_integrity_unknown", row.caveat_kind.?, );}test "profiling offcpu summarizes captured blocked stacks" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const dir = try std.fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }); const lane = try plan(allocator, .{ .mode = .stacks }, dir); try sys.fs.writeFile(lane.capture_path, \\@offcpu_kernel[ \\ finish_task_switch+1 \\ schedule+108 \\]: 42 \\ ); const stderr_path = try std.fs.path.join(allocator, &.{ dir, "stderr.txt" }); try sys.fs.writeFile(stderr_path, ""); const row = try summarize(allocator, lane, 0, stderr_path, true); try std.testing.expectEqualStrings("summary_written", row.state); try std.testing.expect(sys.fs.exists(lane.summary_path));}test "profiling offcpu classifies permission failures from stderr" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const dir = try std.fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }); const lane = try plan(allocator, .{}, dir); const stderr_path = try std.fs.path.join(allocator, &.{ dir, "stderr.txt" }); try sys.fs.writeFile(stderr_path, "could not open bpf map: Operation not permitted\n"); const row = try summarize(allocator, lane, 1, stderr_path, true); try std.testing.expectEqualStrings("tool_failed", row.state); try std.testing.expectEqualStrings("permission_denied", row.caveat_kind.?);}test "profiling offcpu 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 = try summarize(allocator, lane, 0, "missing-stderr.txt", false); try std.testing.expectEqualStrings("tool_missing", row.state); try std.testing.expectEqualStrings("tool_unavailable", row.caveat_kind.?);}Source: src/profiling/host/root.zig:6
zig
pub const offcpu = runtime.offcpu;Complete caller list for host.offcpu.plan
10 direct callers.
src.profiling.host.offcpu.test_profiling_offcpu_classifies_permission_failures_from_stderr[function] — test; no exact target atsrc/profiling/host/offcpu.zig:343in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_marks_missing_collector_diagnostics_unknown[function] — test; no exact target atsrc/profiling/host/offcpu.zig:299in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_reports_missing_tools_as_caveats[function] — test; no exact target atsrc/profiling/host/offcpu.zig:358in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_retains_rows_and_caveats_collector_loss[function] — test; no exact target atsrc/profiling/host/offcpu.zig:268in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_summarizes_captured_blocked_stacks[function] — test; no exact target atsrc/profiling/host/offcpu.zig:321in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_summarizes_captured_blocked_time[function] — test; no exact target atsrc/profiling/host/offcpu.zig:246in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_wraps_direct_binaries_in_the_bpftrace_stacks_tracer[function] — test; no exact target atsrc/profiling/host/offcpu.zig:233in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_wraps_direct_binaries_in_the_offcputime_sidecar[function] — test; no exact target atsrc/profiling/host/offcpu.zig:214in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.runtime.descriptor[function] — private; no exact target atsrc/profiling/host/runtime.zig:276in nearest public ownersrc.profiling.host.runtimetiny.profiling.host.wrap[function] atsrc/profiling/host/runtime.zig:138
Complete caller list for host.offcpu.summarize
7 direct callers.
src.profiling.host.offcpu.test_profiling_offcpu_classifies_permission_failures_from_stderr[function] — test; no exact target atsrc/profiling/host/offcpu.zig:343in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_marks_missing_collector_diagnostics_unknown[function] — test; no exact target atsrc/profiling/host/offcpu.zig:299in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_reports_missing_tools_as_caveats[function] — test; no exact target atsrc/profiling/host/offcpu.zig:358in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_retains_rows_and_caveats_collector_loss[function] — test; no exact target atsrc/profiling/host/offcpu.zig:268in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_summarizes_captured_blocked_stacks[function] — test; no exact target atsrc/profiling/host/offcpu.zig:321in nearest public ownertiny.profiling.host.offcpusrc.profiling.host.offcpu.test_profiling_offcpu_summarizes_captured_blocked_time[function] — test; no exact target atsrc/profiling/host/offcpu.zig:246in nearest public ownertiny.profiling.host.offcputiny.profiling.host.summarize[function] atsrc/profiling/host/runtime.zig:210
Audit
| Definitions | 12 |
|---|---|
| Public names | 12 |
| Members | 9 |
| Version | 26.7.0 |
| Revision | daab053ee433 |