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 }