lib/coz/src/experiment.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 pub const sample_period_ns: u64 = 1_000_000;
  4 pub const sample_batch_size: u64 = 10;
  5 pub const speedup_divisions: u64 = 20;
  6 pub const zero_speedup_weight: u64 = 7;
  7 pub const experiment_min_time_ns: u64 = sample_period_ns * sample_batch_size * 50;
  8 pub const experiment_cool_off_time_ns: u64 = sample_period_ns * sample_batch_size;
  9 pub const experiment_target_delta: u64 = 5;
 10 pub const experiment_max_time_ns: u64 = experiment_min_time_ns * 16;
 11 
 12 pub const Draw = struct {
 13     value: u64,
 14 
 15     pub fn init(value: u64) !Draw {
 16         if (value > maxDraw()) return error.InvalidSpeedupDraw;
 17         return .{ .value = value };
 18     }
 19 
 20     pub fn maxDraw() u64 {
 21         return zero_speedup_weight + speedup_divisions;
 22     }
 23 };
 24 
 25 pub const Plan = struct {
 26     delay_size_ns: u64,
 27     virtual_speedup: f64,
 28     duration_ns: u64,
 29 };
 30 
 31 pub fn planFromDraw(draw: Draw, duration_ns: u64) Plan {
 32     const delay_size = delaySizeFromDraw(draw);
 33     return .{
 34         .delay_size_ns = delay_size,
 35         .virtual_speedup = virtualSpeedupFromDelay(delay_size),
 36         .duration_ns = duration_ns,
 37     };
 38 }
 39 
 40 pub fn delaySizeFromDraw(draw: Draw) u64 {
 41     if (draw.value <= zero_speedup_weight) return 0;
 42     return (draw.value - zero_speedup_weight) * sample_period_ns / speedup_divisions;
 43 }
 44 
 45 pub fn fixedDelaySize(speedup_percent: i32) ?u64 {
 46     if (speedup_percent < 0 or speedup_percent > 100) return null;
 47     return sample_period_ns * @as(u64, @intCast(speedup_percent)) / 100;
 48 }
 49 
 50 pub fn virtualSpeedupFromDelay(delay_size_ns: u64) f64 {
 51     return @as(f64, @floatFromInt(delay_size_ns)) / @as(f64, @floatFromInt(sample_period_ns));
 52 }
 53 
 54 pub fn adjustDuration(current_duration_ns: u64, min_delta: u64) u64 {
 55     if (min_delta < experiment_target_delta) {
 56         return @min(current_duration_ns * 2, experiment_max_time_ns);
 57     }
 58     if (min_delta > experiment_target_delta * 2 and current_duration_ns >= experiment_min_time_ns * 2) {
 59         return current_duration_ns / 2;
 60     }
 61     return current_duration_ns;
 62 }
 63 
 64 pub fn correctedDurationNs(elapsed_ns: u64, inserted_delay_ns: u64, overshoot_ns: u64) u64 {
 65     return elapsed_ns -| inserted_delay_ns -| overshoot_ns;
 66 }
 67 
 68 test "draw range matches upstream uniform distribution bounds" {
 69     try std.testing.expectEqual(@as(u64, 27), Draw.maxDraw());
 70     try std.testing.expectError(error.InvalidSpeedupDraw, Draw.init(28));
 71 }
 72 
 73 test "delay size from random draw preserves upstream weighting" {
 74     try std.testing.expectEqual(@as(u64, 0), delaySizeFromDraw(try Draw.init(0)));
 75     try std.testing.expectEqual(@as(u64, 0), delaySizeFromDraw(try Draw.init(7)));
 76     try std.testing.expectEqual(@as(u64, 50_000), delaySizeFromDraw(try Draw.init(8)));
 77     try std.testing.expectEqual(@as(u64, 500_000), delaySizeFromDraw(try Draw.init(17)));
 78     try std.testing.expectEqual(@as(u64, 1_000_000), delaySizeFromDraw(try Draw.init(27)));
 79 }
 80 
 81 test "fixed delay size accepts only percentage speedups" {
 82     try std.testing.expectEqual(@as(?u64, null), fixedDelaySize(-1));
 83     try std.testing.expectEqual(@as(?u64, null), fixedDelaySize(101));
 84     try std.testing.expectEqual(@as(?u64, 0), fixedDelaySize(0));
 85     try std.testing.expectEqual(@as(?u64, 250_000), fixedDelaySize(25));
 86     try std.testing.expectEqual(@as(?u64, 1_000_000), fixedDelaySize(100));
 87 }
 88 
 89 test "virtual speedup is delay size divided by sample period" {
 90     try std.testing.expectEqual(@as(f64, 0), virtualSpeedupFromDelay(0));
 91     try std.testing.expectEqual(@as(f64, 0.25), virtualSpeedupFromDelay(250_000));
 92     try std.testing.expectEqual(@as(f64, 1), virtualSpeedupFromDelay(sample_period_ns));
 93 }
 94 
 95 test "experiment duration adapts to progress-point deltas" {
 96     try std.testing.expectEqual(
 97         @as(u64, experiment_min_time_ns * 2),
 98         adjustDuration(experiment_min_time_ns, experiment_target_delta - 1),
 99     );
100     try std.testing.expectEqual(
101         @as(u64, experiment_max_time_ns),
102         adjustDuration(experiment_max_time_ns, experiment_target_delta - 1),
103     );
104     try std.testing.expectEqual(
105         @as(u64, experiment_min_time_ns),
106         adjustDuration(experiment_min_time_ns * 2, experiment_target_delta * 2 + 1),
107     );
108     try std.testing.expectEqual(
109         @as(u64, experiment_min_time_ns),
110         adjustDuration(experiment_min_time_ns, experiment_target_delta * 2 + 1),
111     );
112     try std.testing.expectEqual(
113         @as(u64, experiment_min_time_ns),
114         adjustDuration(experiment_min_time_ns, experiment_target_delta),
115     );
116 }
117 
118 test "corrected duration removes inserted delay and overshoot" {
119     try std.testing.expectEqual(@as(u64, 70), correctedDurationNs(100, 20, 10));
120     try std.testing.expectEqual(@as(u64, 0), correctedDurationNs(100, 80, 30));
121 }
122 
123 test "plan bundles delay speedup and duration" {
124     const plan = planFromDraw(try Draw.init(12), experiment_min_time_ns);
125 
126     try std.testing.expectEqual(@as(u64, 250_000), plan.delay_size_ns);
127     try std.testing.expectEqual(@as(f64, 0.25), plan.virtual_speedup);
128     try std.testing.expectEqual(@as(u64, experiment_min_time_ns), plan.duration_ns);
129 }