tiny.profiling.cycle
Defined in tiny.profiling.
API (7)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: src/profiling/cycle.zig
zig
const std = @import("std");const pretty_usage = @import("pretty_usage");const sys = @import("sys");const analyze = @import("analyze/root.zig");const baseline = @import("baseline.zig");const catalog = @import("catalog.zig");const driver = @import("driver/root.zig");const execute = @import("execute.zig");const host = @import("host/root.zig");const perturbation = @import("perturbation.zig");const record = @import("record.zig");pub const run_id_prefix = "cycle";pub const default_step_baseline = "cycle";pub const default_binary_baseline = "cycle-binary";pub const default_mixed_baseline = "cycle-mixed";pub const Options = struct { suite: catalog.Suite = .smoke, filters: []const []const u8 = &.{}, output_dir: []const u8 = record.default_output_dir, baseline_name: []const u8 = default_binary_baseline, keep: usize = 7, threshold_percent: f64 = 10, top: usize = 10, stop_on_failure: bool = false, host_lanes: host.Options = .{}, execution_scope: execute.Scope = .binary, warmup_repeat: u32 = 0, measure_repeat: u32 = 1, interleave_seed: ?u64 = null, capture_control: perturbation.Options = .{}, forwarded: []const []const u8 = &.{},};pub fn defaultBaselineName(scope: execute.Scope) []const u8 { return switch (scope) { .step => default_step_baseline, .binary => default_binary_baseline, .mixed => default_mixed_baseline, };}pub fn run( memory: driver.Memory, process_io: std.Io, environ_map: ?*const sys.process.Environ.Map, options: Options,) !u8 { const allocator = memory.command; if (options.host_lanes.requiresDirect()) return error.DirectLaneInCycle; const stderr_terminal = pretty_usage.Terminal.stderr(allocator, .{}); const previous = resolvePrevious(allocator, options.baseline_name); if (previous == null) { try stderr_terminal.writeTextFmt("profile cycle: no baseline named {s}; this cycle records the first one\n", .{options.baseline_name}); } const run_id = try record.prefixedRunId(allocator, process_io, run_id_prefix); const outcome = try driver.executeRun(memory, process_io, environ_map, .{ .selection = .{ .suite = options.suite, .filters = options.filters }, .run_id = run_id, .output_dir = options.output_dir, .forwarded = options.forwarded, .continue_on_failure = !options.stop_on_failure, .host_lanes = options.host_lanes, .execution_scope = options.execution_scope, .warmup_repeat = options.warmup_repeat, .measure_repeat = options.measure_repeat, .interleave_seed = options.interleave_seed, .capture_control = options.capture_control, }); const candidate = try analyze.loadRun(allocator, outcome.paths.root); const baseline_run: ?analyze.Run = if (previous) |ref| analyze.loadRun(allocator, ref.root) catch |err| blk: { try stderr_terminal.writeTextFmt("profile cycle: baseline {s} unreadable ({s}); analyzing without a baseline\n", .{ ref.run_id, @errorName(err) }); break :blk null; } else null; const analyze_options = analyze.Options{ .input = outcome.paths.root, .top = options.top, .regression_threshold_percent = options.threshold_percent, }; try writeAnalysisArtifacts(allocator, outcome.paths.root, candidate, baseline_run, analyze_options); try analyze.writeText(allocator, pretty_usage.Terminal.stdout(allocator, .{}), candidate, baseline_run, analyze_options); const entry = try baseline.save(allocator, options.baseline_name, outcome.paths.root); try stderr_terminal.writeTextFmt("profile cycle: baseline {s} -> {s}\n", .{ entry.name, entry.run.run_id }); const pruned = try pruneRuns(allocator, options.output_dir, options.keep); for (pruned) |name| { try stderr_terminal.writeTextFmt("profile cycle: pruned run {s}\n", .{name}); } return outcome.exit_code;}fn resolvePrevious(allocator: std.mem.Allocator, baseline_name: []const u8) ?baseline.RunRef { return baseline.resolveRun(allocator, baseline_name) catch null;}fn writeAnalysisArtifacts( allocator: std.mem.Allocator, run_root: []const u8, candidate: analyze.Run, baseline_run: ?analyze.Run, options: analyze.Options,) !void { const json_path = try std.fs.path.join(allocator, &.{ run_root, "analysis.json" }); var json_file = try sys.fs.createFile(json_path, .{ .truncate = true }); defer json_file.close(sys.fs.debugIo()); var json_buffer: [64 * 1024]u8 = undefined; var json_writer = json_file.writer(sys.fs.debugIo(), &json_buffer); try analyze.writeJson(allocator, &json_writer.interface, candidate, baseline_run, options); try json_writer.interface.flush(); const text_path = try std.fs.path.join(allocator, &.{ run_root, "analysis.md" }); const text_file = try sys.fs.createFile(text_path, .{ .truncate = true }); defer sys.fs.closeHandle(text_file); try analyze.writeText(allocator, pretty_usage.Terminal.init(allocator, text_file, .{}), candidate, baseline_run, options);}fn pruneRuns(allocator: std.mem.Allocator, output_dir: []const u8, keep: usize) ![]const []const u8 { const entries = sys.fs.listDirAlloc(allocator, output_dir) catch |err| switch (err) { error.FileNotFound => return &.{}, else => |actual| return actual, }; var names: std.ArrayList([]const u8) = .empty; for (entries) |entry| { if (entry.kind != .directory) continue; if (!isCycleRun(entry.name)) continue; try names.append(allocator, try allocator.dupe(u8, entry.name)); } const doomed = try pruneSelection(allocator, names.items, keep); for (doomed) |name| { const path = try std.fs.path.join(allocator, &.{ output_dir, name }); try sys.fs.deleteTree(path); } return doomed;}fn run_name_descending(_: void, left: []const u8, right: []const u8) bool { return std.mem.order(u8, left, right) == .gt;}fn pruneSelection(allocator: std.mem.Allocator, names: []const []const u8, keep: usize) ![]const []const u8 { const kept_count = @max(keep, 1); if (names.len <= kept_count) return &.{}; const sorted = try allocator.dupe([]const u8, names); std.mem.sort([]const u8, sorted, {}, run_name_descending); return sorted[kept_count..];}fn isCycleRun(name: []const u8) bool { if (!std.mem.startsWith(u8, name, run_id_prefix)) return false; if (name.len == run_id_prefix.len) return false; return name[run_id_prefix.len] == '-';}test "profiling cycle prune keeps the newest runs" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const names = [_][]const u8{ "cycle-1700000000003-ccc", "cycle-1700000000001-aaa", "cycle-1700000000002-bbb", }; const doomed = try pruneSelection(allocator, &names, 2); try std.testing.expectEqual(@as(usize, 1), doomed.len); try std.testing.expectEqualStrings("cycle-1700000000001-aaa", doomed[0]);}test "profiling cycle prune never deletes the last kept run" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const names = [_][]const u8{"cycle-1700000000001-aaa"}; try std.testing.expectEqual(@as(usize, 0), (try pruneSelection(allocator, &names, 0)).len); try std.testing.expectEqual(@as(usize, 0), (try pruneSelection(allocator, &names, 7)).len);}test "profiling cycle only prunes cycle-prefixed run directories" { try std.testing.expect(isCycleRun("cycle-1700000000001-aaa")); try std.testing.expect(!isCycleRun("cycle")); try std.testing.expect(!isCycleRun("cyclex-1700000000001")); try std.testing.expect(!isCycleRun("run-1700000000001-aaa")); try std.testing.expect(!isCycleRun("baselines"));}test "profiling cycle rejects timing-perturbing direct lanes" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const memory = driver.Memory{ .command = allocator, .workload = allocator }; try std.testing.expectError( error.DirectLaneInCycle, run(memory, std.Options.debug_io, null, .{ .host_lanes = .{ .dhat = .{} } }), ); try std.testing.expectError( error.DirectLaneInCycle, run(memory, std.Options.debug_io, null, .{ .host_lanes = .{ .offcpu = .{} } }), );}Source: src/profiling/root.zig:20
zig
pub const cycle = @import("cycle.zig");Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 15 |
| Version | 26.7.0 |
| Revision | daab053ee433 |