tiny.profiling.experiment.variant
Defined in experiment.
API (6)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: src/profiling/experiment/root.zig:8
zig
pub const variant = @import("variant.zig");Source: src/profiling/experiment/variant.zig
zig
const std = @import("std");const capture = @import("capture");const sys = @import("sys");const profiling = @import("../root.zig");const model = @import("model.zig");const catalog = profiling.catalog;const code_layout = profiling.code_layout;const execute = profiling.execute;const fingerprint = profiling.fingerprint;const scenario = profiling.scenario;const maximum_git_output_bytes: usize = 4096;pub const Prepared = struct { side: model.Side, source: model.Variant, resolved_ref: ?[]const u8, worktree: ?[]const u8, root: []const u8, binary_path: []const u8, archived_binary_path: []const u8, default_cwd: []const u8, file: fingerprint.File, code_layout: ?capture.code_layout.Fingerprint, build_command: ?[]const u8,};pub const Context = struct { allocator: std.mem.Allocator, process_io: std.Io, environ_map: ?*const sys.process.Environ.Map, artifact_root: []const u8, run_id: []const u8, definition: scenario.Definition,};pub fn prepare( context: Context, side: model.Side, source: model.Variant,) !Prepared { const variant_root = try variantArtifactRoot( context.allocator, context.artifact_root, side, ); try sys.fs.createDirPath(variant_root); return switch (source) { .binary => |path| try prepareBinary( context, side, source, path, variant_root, ), .git_ref => |git_ref| try prepareRef( context, side, source, git_ref, variant_root, ), };}fn prepareBinary( context: Context, side: model.Side, source: model.Variant, path: []const u8, variant_root: []const u8,) !Prepared { const allocator = context.allocator; const source_path = try sys.fs.realPathAlloc(allocator, path); const root = try sys.fs.realPathAlloc(allocator, "."); const default_cwd = try defaultCwd( allocator, root, context.definition, ); return try archivePrepared( context, side, source, null, null, root, source_path, variant_root, default_cwd, null, );}fn prepareRef( context: Context, side: model.Side, source: model.Variant, git_ref: model.Ref, variant_root: []const u8,) !Prepared { const allocator = context.allocator; const resolved_ref = try resolveRef( allocator, context.process_io, git_ref.name, ); const worktree = try worktreePath( allocator, context.run_id, side, ); if (sys.fs.exists(worktree)) return error.ExperimentWorktreeExists; try addWorktree(context, worktree, resolved_ref, variant_root); errdefer cleanup(context, .{ .side = side, .source = source, .resolved_ref = resolved_ref, .worktree = worktree, .root = worktree, .binary_path = "", .archived_binary_path = "", .default_cwd = worktree, .file = undefined, .code_layout = null, .build_command = null, }) catch {}; const built = try buildRef(context, worktree, git_ref, variant_root); const default_cwd = try defaultCwd( allocator, worktree, context.definition, ); return try archivePrepared( context, side, source, resolved_ref, worktree, worktree, built.artifact_path, variant_root, default_cwd, built.command, );}const Built = struct { artifact_path: []const u8, command: []const u8,};fn buildRef( context: Context, worktree: []const u8, git_ref: model.Ref, variant_root: []const u8,) !Built { const build = if (git_ref.build) |definition| try explicitBuild(context, worktree, definition) else try catalogBuild(context, worktree); const stdout_path = try std.fs.path.join( context.allocator, &.{ variant_root, "build.stdout.txt" }, ); const stderr_path = try std.fs.path.join( context.allocator, &.{ variant_root, "build.stderr.txt" }, ); const result = try execute.runCommand( context.process_io, context.environ_map, build.cwd, build.argv, stdout_path, stderr_path, ); if (result.exit_code != 0) return error.ExperimentBuildFailed; return .{ .artifact_path = build.artifact_path, .command = try execute.commandText( context.allocator, build.cwd, null, build.argv, ), };}const BuildCommand = struct { argv: []const []const u8, cwd: []const u8, artifact_path: []const u8,};fn explicitBuild( context: Context, worktree: []const u8, build: model.Build,) !BuildCommand { const allocator = context.allocator; const cwd = try joinScope(allocator, worktree, build.cwd); const values = scenario.Substitutions{ .binary = build.artifact, .root = worktree, .side = "build", }; const argv = try allocator.alloc([]const u8, build.argv.len); for (build.argv, 0..) |argument, index| { argv[index] = try scenario.substitute(allocator, argument, values); } if (std.mem.eql(u8, argv[0], "zig")) { argv[0] = execute.zig(context.environ_map).executable; } return .{ .argv = argv, .cwd = cwd, .artifact_path = try std.fs.path.join( allocator, &.{ cwd, build.artifact }, ), };}fn catalogBuild( context: Context, worktree: []const u8,) !BuildCommand { const workload = try catalogWorkload(context.definition); const execution_plan = try execute.plan( context.allocator, execute.zig(context.environ_map), workload, &.{}, true, &.{execute.optimizeBuildArg(execute.default_optimize)}, ); const setup_argv = execution_plan.setup_argv orelse return error.CatalogWorkloadMissingBinary; const cwd = try joinScope( context.allocator, worktree, execution_plan.cwd, ); return .{ .argv = setup_argv, .cwd = cwd, .artifact_path = try std.fs.path.join( context.allocator, &.{ cwd, execution_plan.argv[0] }, ), };}fn addWorktree( context: Context, path: []const u8, resolved_ref: []const u8, variant_root: []const u8,) !void { const stdout_path = try std.fs.path.join( context.allocator, &.{ variant_root, "worktree.stdout.txt" }, ); const stderr_path = try std.fs.path.join( context.allocator, &.{ variant_root, "worktree.stderr.txt" }, ); const result = try execute.runCommand( context.process_io, context.environ_map, null, &.{ "git", "worktree", "add", "--detach", path, resolved_ref }, stdout_path, stderr_path, ); if (result.exit_code != 0) return error.ExperimentWorktreeAddFailed;}fn archivePrepared( context: Context, side: model.Side, source: model.Variant, resolved_ref: ?[]const u8, worktree: ?[]const u8, root: []const u8, binary_path: []const u8, variant_root: []const u8, default_cwd: []const u8, build_command: ?[]const u8,) !Prepared { const archived = try std.fs.path.join( context.allocator, &.{ variant_root, "binary" }, ); const absolute_binary = try absolutePath(context.allocator, binary_path); const absolute_archived = try unresolvedAbsolutePath( context.allocator, archived, ); try std.Io.Dir.copyFileAbsolute( absolute_binary, absolute_archived, std.Options.debug_io, .{ .replace = false }, ); const file = try fingerprint.inspect(absolute_archived); const layout_report = code_layout.inspect( context.allocator, &.{absolute_archived}, ) catch null; return .{ .side = side, .source = source, .resolved_ref = resolved_ref, .worktree = worktree, .root = root, .binary_path = absolute_archived, .archived_binary_path = absolute_archived, .default_cwd = default_cwd, .file = file, .code_layout = if (layout_report) |report| report.images[0].fingerprint else null, .build_command = build_command, };}pub fn command( allocator: std.mem.Allocator, prepared: Prepared, definition: scenario.Definition,) !scenario.Command { const values = substitutions(prepared); const default_cwd = if (definition.cwd) |cwd| try scenario.substitute(allocator, cwd, values) else prepared.default_cwd; return switch (definition.input) { .command => |argv| try scenario.resolveCommand( allocator, .{ .argv = argv }, default_cwd, values, ), .catalog => |input| try catalogCommand( allocator, prepared, input, default_cwd, ), };}fn catalogCommand( allocator: std.mem.Allocator, prepared: Prepared, input: scenario.CatalogInput, cwd: []const u8,) !scenario.Command { const workload = catalog.find(input.workload) orelse return error.UnknownWorkload; const bin = workload.bin orelse return error.CatalogWorkloadMissingBinary; const argument_count = 1 + bin.args.len + input.args.len; if (argument_count > scenario.maximum_arguments) { return error.InvalidScenarioArguments; } const argv = try allocator.alloc([]const u8, argument_count); argv[0] = prepared.binary_path; @memcpy(argv[1 .. 1 + bin.args.len], bin.args); @memcpy(argv[1 + bin.args.len ..], input.args); return .{ .argv = argv, .cwd = cwd };}pub fn substitutions(prepared: Prepared) scenario.Substitutions { return .{ .binary = prepared.binary_path, .root = prepared.root, .side = @tagName(prepared.side), };}pub fn cleanup(context: Context, prepared: Prepared) !void { const worktree = prepared.worktree orelse return; const result = try sys.process.run( context.allocator, context.process_io, .{ .argv = &.{ "git", "worktree", "remove", "--force", worktree, }, .stdout_limit = .limited(maximum_git_output_bytes), .stderr_limit = .limited(maximum_git_output_bytes), .environ_map = context.environ_map, }, ); defer context.allocator.free(result.stdout); defer context.allocator.free(result.stderr); if (sys.process.exitCode(result.term) != 0) { return error.ExperimentWorktreeCleanupFailed; }}fn resolveRef( allocator: std.mem.Allocator, process_io: std.Io, ref_name: []const u8,) ![]const u8 { const commit_ref = try std.fmt.allocPrint( allocator, "{s}^{{commit}}", .{ref_name}, ); const result = try sys.process.run(allocator, process_io, .{ .argv = &.{ "git", "rev-parse", "--verify", "--end-of-options", commit_ref, }, .stdout_limit = .limited(maximum_git_output_bytes), .stderr_limit = .limited(maximum_git_output_bytes), }); defer allocator.free(result.stderr); defer allocator.free(result.stdout); if (sys.process.exitCode(result.term) != 0) return error.InvalidVariantRef; const value = std.mem.trim(u8, result.stdout, " \t\r\n"); if (value.len != 40 and value.len != 64) return error.InvalidVariantRef; return try allocator.dupe(u8, value);}fn defaultCwd( allocator: std.mem.Allocator, root: []const u8, definition: scenario.Definition,) ![]const u8 { return switch (definition.input) { .command => root, .catalog => { const workload = try catalogWorkload(definition); const bin = workload.bin orelse return error.CatalogWorkloadMissingBinary; return try joinScope( allocator, root, bin.scope(workload.package), ); }, };}fn catalogWorkload(definition: scenario.Definition) !catalog.Workload { return switch (definition.input) { .catalog => |input| catalog.find(input.workload) orelse return error.UnknownWorkload, .command => return error.InvalidCatalogScenario, };}fn joinScope( allocator: std.mem.Allocator, root: []const u8, scope: ?[]const u8,) ![]const u8 { const value = scope orelse return root; if (std.fs.path.isAbsolute(value)) return value; if (std.mem.eql(u8, value, ".")) return root; return try std.fs.path.join(allocator, &.{ root, value });}fn variantArtifactRoot( allocator: std.mem.Allocator, root: []const u8, side: model.Side,) ![]const u8 { return try std.fs.path.join( allocator, &.{ root, "variants", @tagName(side) }, );}fn worktreePath( allocator: std.mem.Allocator, run_id: []const u8, side: model.Side,) ![]const u8 { return try std.fmt.allocPrint( allocator, "/tmp/tiny-profile-{s}-{s}", .{ run_id, @tagName(side) }, );}fn absolutePath( allocator: std.mem.Allocator, path: []const u8,) ![]const u8 { if (std.fs.path.isAbsolute(path)) return path; return try sys.fs.realPathAlloc(allocator, path);}fn unresolvedAbsolutePath( allocator: std.mem.Allocator, path: []const u8,) ![]const u8 { if (std.fs.path.isAbsolute(path)) return path; const root = try sys.fs.realPathAlloc(allocator, "."); return try std.fs.path.join(allocator, &.{ root, path });}test "profiling experiment prepares immutable binary variants" { var temporary = std.testing.tmpDir(.{}); defer temporary.cleanup(); var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const artifact_root = try temporary.parent_dir.realPathFileAlloc( std.Options.debug_io, temporary.sub_path[0..], allocator, ); const definition = scenario.Definition{ .name = "true", .input = .{ .command = &.{"{binary}"} }, }; const prepared = try prepare(.{ .allocator = allocator, .process_io = std.Options.debug_io, .environ_map = null, .artifact_root = artifact_root, .run_id = "variant-test", .definition = definition, }, .baseline, .{ .binary = "/bin/true" }); try std.testing.expect(prepared.file.bytes > 0); try std.testing.expect(prepared.code_layout != null); const measured = try command(allocator, prepared, definition); try std.testing.expectEqualStrings( prepared.binary_path, measured.argv[0], );}Audit
| Definitions | 7 |
|---|---|
| Public names | 7 |
| Members | 17 |
| Version | 26.7.0 |
| Revision | daab053ee433 |