tiny.profiling.experiment.acquire
Defined in experiment.
API (11)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: src/profiling/experiment/acquire.zig
zig
const std = @import("std");const sys = @import("sys");const profiling = @import("../root.zig");const model = @import("model.zig");const oracle = @import("oracle.zig");const variant = @import("variant.zig");const execute = profiling.execute;const host = profiling.host;const scenario = profiling.scenario;pub const PairOrder = enum { baseline_first, candidate_first,};pub const CommandResult = struct { command: []const u8, cwd: ?[]const u8, stdout_path: []const u8, stderr_path: []const u8, execution: execute.Result,};pub const Observation = struct { sequence: usize, phase: model.Phase, pair_index: usize, position_in_pair: usize, pair_order: PairOrder, side: model.Side, command: []const u8, cwd: ?[]const u8, reset: ?CommandResult, stdout_path: []const u8, stderr_path: []const u8, execution: execute.Result, metric_value: ?f64, oracle: oracle.Result, host_state: host.state.Window, support: model.Support,};pub const PhaseOutcome = struct { observations: []const Observation, orders: []const PairOrder, baseline_values: []const f64, candidate_values: []const f64, support: model.Support, pub fn complete(self: PhaseOutcome) bool { return self.support == .supported and self.baseline_values.len == self.orders.len and self.candidate_values.len == self.orders.len; }};const SideRuntime = struct { prepared: variant.Prepared, measured: scenario.Command, setup: ?scenario.Command, reset: ?scenario.Command, environment: sys.process.Environ.Map,};const PairOutcome = struct { support: model.Support, observation_count: usize,};const OutputPaths = struct { stdout: []const u8, stderr: []const u8,};const Measurement = struct { command: []const u8, paths: OutputPaths, execution: execute.Result, metric_value: ?f64, oracle: oracle.Result, host_state: host.state.Window, support: model.Support,};pub const Runner = struct { allocator: std.mem.Allocator, process_io: std.Io, artifact_root: []const u8, definition: scenario.Definition, metric: model.Metric, baseline: SideRuntime, candidate: SideRuntime, pub fn init( allocator: std.mem.Allocator, process_io: std.Io, base_environment: ?*const sys.process.Environ.Map, artifact_root: []const u8, definition: scenario.Definition, metric: model.Metric, baseline: variant.Prepared, candidate: variant.Prepared, ) !Runner { var baseline_runtime = try prepareSide( allocator, base_environment, definition, baseline, ); errdefer baseline_runtime.environment.deinit(); const candidate_runtime = try prepareSide( allocator, base_environment, definition, candidate, ); return .{ .allocator = allocator, .process_io = process_io, .artifact_root = artifact_root, .definition = definition, .metric = metric, .baseline = baseline_runtime, .candidate = candidate_runtime, }; } pub fn deinit(self: *Runner) void { self.baseline.environment.deinit(); self.candidate.environment.deinit(); } pub fn setup(self: *Runner) ![2]?CommandResult { return .{ try runOptionalCommand( self, &self.baseline, self.baseline.setup, "variants/baseline/setup", ), try runOptionalCommand( self, &self.candidate, self.candidate.setup, "variants/candidate/setup", ), }; } pub fn runPhase( self: *Runner, phase: model.Phase, pair_count: usize, seed: u64, sequence_start: usize, ) !PhaseOutcome { if (pair_count < 2 or pair_count > model.maximum_pairs) { return error.InvalidExperimentPairCount; } const orders = try schedule(self.allocator, pair_count, seed); const observations = try self.allocator.alloc( Observation, pair_count * 2, ); const baseline_values = try self.allocator.alloc(f64, pair_count); const candidate_values = try self.allocator.alloc(f64, pair_count); var observation_count: usize = 0; var completed_pairs: usize = 0; var support = model.Support.supported; for (orders, 0..) |order, pair_index| { const pair = try self.runPair( phase, pair_index, order, sequence_start + observation_count, observations[observation_count..], &baseline_values[pair_index], &candidate_values[pair_index], ); support = pair.support; observation_count += pair.observation_count; if (support != .supported) break; completed_pairs += 1; } return .{ .observations = observations[0..observation_count], .orders = orders, .baseline_values = baseline_values[0..completed_pairs], .candidate_values = candidate_values[0..completed_pairs], .support = support, }; } fn runPair( self: *Runner, phase: model.Phase, pair_index: usize, order: PairOrder, sequence_start: usize, observations: []Observation, baseline_value: *f64, candidate_value: *f64, ) !PairOutcome { std.debug.assert(observations.len >= 2); const sides = switch (order) { .baseline_first => [2]model.Side{ .baseline, .candidate }, .candidate_first => [2]model.Side{ .candidate, .baseline }, }; for (sides, 0..) |side, position| { const observation = try self.runObservation( phase, pair_index, position, order, side, sequence_start + position, ); observations[position] = observation; if (observation.support != .supported) { return .{ .support = observation.support, .observation_count = position + 1, }; } const value = observation.metric_value.?; switch (side) { .baseline => baseline_value.* = value, .candidate => candidate_value.* = value, } } return .{ .support = .supported, .observation_count = 2 }; } fn runObservation( self: *Runner, phase: model.Phase, pair_index: usize, position: usize, order: PairOrder, side: model.Side, sequence: usize, ) !Observation { const runtime = self.runtimeFor(side); const prefix = try observationPrefix( self.allocator, phase, pair_index, position, side, ); const reset = try runOptionalCommand( self, runtime, runtime.reset, try std.fmt.allocPrint( self.allocator, "observations/{s}.reset", .{prefix}, ), ); if (reset) |result| { if (result.execution.exit_code != 0) { return try failedResetObservation( self, runtime, phase, pair_index, position, order, side, sequence, result, ); } } return try self.measure( runtime, phase, pair_index, position, order, side, sequence, prefix, reset, ); } fn measure( self: *Runner, runtime: *SideRuntime, phase: model.Phase, pair_index: usize, position: usize, order: PairOrder, side: model.Side, sequence: usize, prefix: []const u8, reset: ?CommandResult, ) !Observation { const measurement = try self.captureMeasurement( runtime, prefix, ); return .{ .sequence = sequence + 1, .phase = phase, .pair_index = pair_index + 1, .position_in_pair = position + 1, .pair_order = order, .side = side, .command = measurement.command, .cwd = runtime.measured.cwd, .reset = reset, .stdout_path = measurement.paths.stdout, .stderr_path = measurement.paths.stderr, .execution = measurement.execution, .metric_value = measurement.metric_value, .oracle = measurement.oracle, .host_state = measurement.host_state, .support = measurement.support, }; } fn captureMeasurement( self: *Runner, runtime: *SideRuntime, prefix: []const u8, ) !Measurement { const paths = try outputPaths( self.allocator, self.artifact_root, try std.fmt.allocPrint( self.allocator, "observations/{s}", .{prefix}, ), ); const before = host.state.capture(); const execution = try execute.runCommand( self.process_io, &runtime.environment, runtime.measured.cwd, runtime.measured.argv, paths.stdout, paths.stderr, ); const after = host.state.capture(); const verification = try oracle.verify( self.allocator, self.definition.oracle, variant.substitutions(runtime.prepared), runtime.measured.cwd, execution.exit_code, paths.stdout, paths.stderr, ); const metric_value = metricValue(self.metric, execution); const support: model.Support = if (!verification.passed) .oracle_failed else if (metric_value == null) .metric_unavailable else if (!validMetric(metric_value.?)) .invalid_metric_value else .supported; return .{ .command = try execute.commandText( self.allocator, runtime.measured.cwd, null, runtime.measured.argv, ), .paths = paths, .execution = execution, .metric_value = metric_value, .oracle = verification, .host_state = .{ .condition = .measurement, .before = before, .after = after, }, .support = support, }; } fn runtimeFor(self: *Runner, selected: model.Side) *SideRuntime { return switch (selected) { .baseline => &self.baseline, .candidate => &self.candidate, }; }};fn failedResetObservation( runner: *Runner, runtime: *SideRuntime, phase: model.Phase, pair_index: usize, position: usize, order: PairOrder, side: model.Side, sequence: usize, reset: CommandResult,) !Observation { const empty = try emptyOracle(runner.allocator, reset); return .{ .sequence = sequence + 1, .phase = phase, .pair_index = pair_index + 1, .position_in_pair = position + 1, .pair_order = order, .side = side, .command = try execute.commandText( runner.allocator, runtime.measured.cwd, null, runtime.measured.argv, ), .cwd = runtime.measured.cwd, .reset = reset, .stdout_path = reset.stdout_path, .stderr_path = reset.stderr_path, .execution = reset.execution, .metric_value = null, .oracle = empty, .host_state = .{ .condition = .measurement, .before = host.state.capture(), .after = host.state.capture(), }, .support = .reset_failed, };}fn emptyOracle( allocator: std.mem.Allocator, command: CommandResult,) !oracle.Result { const empty_expected = scenario.Oracle{ .exit_code = command.execution.exit_code, }; return try oracle.verify( allocator, empty_expected, .{ .binary = "none", .root = ".", .side = "none" }, command.cwd, command.execution.exit_code, command.stdout_path, command.stderr_path, );}fn prepareSide( allocator: std.mem.Allocator, base_environment: ?*const sys.process.Environ.Map, definition: scenario.Definition, prepared: variant.Prepared,) !SideRuntime { const measured = try variant.command(allocator, prepared, definition); const values = variant.substitutions(prepared); const setup = if (definition.setup) |command| try scenario.resolveCommand( allocator, command, prepared.default_cwd, values, ) else null; const reset = if (definition.reset) |command| try scenario.resolveCommand( allocator, command, prepared.default_cwd, values, ) else null; var environment = if (base_environment) |base| try base.clone(allocator) else sys.process.Environ.Map.init(allocator); const resolved_environment = try scenario.resolveEnvironment( allocator, definition.environment, values, ); for (resolved_environment) |entry| { try environment.put(entry.name, entry.value); } return .{ .prepared = prepared, .measured = measured, .setup = setup, .reset = reset, .environment = environment, };}fn runOptionalCommand( runner: *Runner, runtime: *SideRuntime, command: ?scenario.Command, relative_prefix: []const u8,) !?CommandResult { const actual = command orelse return null; const paths = try outputPaths( runner.allocator, runner.artifact_root, relative_prefix, ); const execution = try execute.runCommand( runner.process_io, &runtime.environment, actual.cwd, actual.argv, paths.stdout, paths.stderr, ); return .{ .command = try execute.commandText( runner.allocator, actual.cwd, null, actual.argv, ), .cwd = actual.cwd, .stdout_path = paths.stdout, .stderr_path = paths.stderr, .execution = execution, };}fn outputPaths( allocator: std.mem.Allocator, artifact_root: []const u8, relative_prefix: []const u8,) !OutputPaths { const stdout = try std.fmt.allocPrint( allocator, "{s}/{s}.stdout.txt", .{ artifact_root, relative_prefix }, ); const stderr = try std.fmt.allocPrint( allocator, "{s}/{s}.stderr.txt", .{ artifact_root, relative_prefix }, ); if (std.fs.path.dirname(stdout)) |directory| { try sys.fs.createDirPath(directory); } return .{ .stdout = stdout, .stderr = stderr };}fn observationPrefix( allocator: std.mem.Allocator, phase: model.Phase, pair_index: usize, position: usize, side: model.Side,) ![]const u8 { return try std.fmt.allocPrint( allocator, "{s}-{d:0>4}-{d}-{s}", .{ @tagName(phase), pair_index + 1, position + 1, @tagName(side) }, );}fn metricValue( metric: model.Metric, execution: execute.Result,) ?f64 { return switch (metric) { .wall_ns => @floatFromInt(execution.wall_ns), .max_rss_kib => if (execution.maxrss_kib) |value| @floatFromInt(value) else null, };}fn validMetric(value: f64) bool { return std.math.isFinite(value) and value > 0;}pub fn schedule( allocator: std.mem.Allocator, pair_count: usize, seed: u64,) ![]PairOrder { if (pair_count < 2 or pair_count > model.maximum_pairs) { return error.InvalidExperimentPairCount; } const orders = try allocator.alloc(PairOrder, pair_count); var baseline_first_count = pair_count / 2; if (pair_count % 2 != 0 and seed & 1 == 0) baseline_first_count += 1; for (orders, 0..) |*order, index| { order.* = if (index < baseline_first_count) .baseline_first else .candidate_first; } var prng = std.Random.DefaultPrng.init(seed); const random = prng.random(); var remaining = orders.len; while (remaining > 1) { const index = random.uintLessThan(usize, remaining); remaining -= 1; std.mem.swap(PairOrder, &orders[index], &orders[remaining]); } return orders;}test "profiling experiment pair order is deterministic and balanced" { const first = try schedule(std.testing.allocator, 11, 42); defer std.testing.allocator.free(first); const second = try schedule(std.testing.allocator, 11, 42); defer std.testing.allocator.free(second); try std.testing.expectEqualSlices(PairOrder, first, second); var baseline_first: usize = 0; for (first) |order| { if (order == .baseline_first) baseline_first += 1; } try std.testing.expect( baseline_first == 5 or baseline_first == 6, );}Source: src/profiling/experiment/root.zig:1
zig
pub const acquire = @import("acquire.zig");Audit
| Definitions | 12 |
|---|---|
| Public names | 12 |
| Members | 35 |
| Version | 26.7.0 |
| Revision | daab053ee433 |