1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum SpillAction {
13 None,
15 BeginSpill,
17 ContinueSpill,
19 BeginRestore,
21}
22
23#[derive(Debug, Clone, Copy)]
25pub struct SpillConfig {
26 pub spill_threshold: u8,
28 pub restore_threshold: u8,
30}
31
32impl Default for SpillConfig {
33 fn default() -> Self {
34 Self {
35 spill_threshold: 90,
36 restore_threshold: 75,
37 }
38 }
39}
40
41pub struct SpillController {
53 config: SpillConfig,
54 spilling: bool,
56 spill_count: u64,
58 entries_spilled: u64,
60}
61
62impl SpillController {
63 pub fn new(config: SpillConfig) -> Self {
65 Self {
66 config,
67 spilling: false,
68 spill_count: 0,
69 entries_spilled: 0,
70 }
71 }
72
73 pub fn check(&mut self, utilization: u8) -> SpillAction {
78 match (self.spilling, utilization >= self.config.spill_threshold) {
79 (false, false) => SpillAction::None,
81 (false, true) => {
83 self.spilling = true;
84 self.spill_count += 1;
85 self.entries_spilled = 0;
86 SpillAction::BeginSpill
87 }
88 (true, true) => SpillAction::ContinueSpill,
90 (true, false) if utilization <= self.config.restore_threshold => {
92 self.spilling = false;
93 SpillAction::BeginRestore
94 }
95 (true, false) => SpillAction::None,
97 }
98 }
99
100 pub fn is_spilling(&self) -> bool {
102 self.spilling
103 }
104
105 pub fn spill_count(&self) -> u64 {
107 self.spill_count
108 }
109
110 pub fn entries_spilled(&self) -> u64 {
112 self.entries_spilled
113 }
114
115 pub fn record_spill(&mut self, count: u64) {
117 self.entries_spilled += count;
118 }
119
120 pub fn reset_cycle(&mut self) {
122 self.entries_spilled = 0;
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn default_config() {
132 let config = SpillConfig::default();
133 assert_eq!(config.spill_threshold, 90);
134 assert_eq!(config.restore_threshold, 75);
135 }
136
137 #[test]
138 fn state_none_below_threshold() {
139 let mut controller = SpillController::new(SpillConfig::default());
140 assert_eq!(controller.check(50), SpillAction::None);
141 assert!(!controller.is_spilling());
142 }
143
144 #[test]
145 fn state_begin_spill_at_threshold() {
146 let mut controller = SpillController::new(SpillConfig::default());
147 assert_eq!(controller.check(90), SpillAction::BeginSpill);
148 assert!(controller.is_spilling());
149 assert_eq!(controller.spill_count(), 1);
150 assert_eq!(controller.entries_spilled(), 0);
151 }
152
153 #[test]
154 fn state_continue_spill_above_threshold() {
155 let mut controller = SpillController::new(SpillConfig::default());
156 controller.check(90);
157 assert_eq!(controller.check(92), SpillAction::ContinueSpill);
158 assert!(controller.is_spilling());
159 }
160
161 #[test]
162 fn state_begin_restore_below_threshold() {
163 let mut controller = SpillController::new(SpillConfig::default());
164 controller.check(90);
165 assert_eq!(controller.check(74), SpillAction::BeginRestore);
166 assert!(!controller.is_spilling());
167 }
168
169 #[test]
170 fn state_wait_between_thresholds() {
171 let mut controller = SpillController::new(SpillConfig::default());
172 controller.check(90);
173 assert_eq!(controller.check(80), SpillAction::None);
175 assert!(controller.is_spilling());
176 }
177
178 #[test]
179 fn record_spill() {
180 let mut controller = SpillController::new(SpillConfig::default());
181 controller.check(90);
182 controller.record_spill(10);
183 assert_eq!(controller.entries_spilled(), 10);
184 controller.record_spill(5);
185 assert_eq!(controller.entries_spilled(), 15);
186 }
187
188 #[test]
189 fn reset_cycle() {
190 let mut controller = SpillController::new(SpillConfig::default());
191 controller.check(90);
192 controller.record_spill(20);
193 assert_eq!(controller.spill_count(), 1);
194 assert_eq!(controller.entries_spilled(), 20);
195 controller.reset_cycle();
196 assert_eq!(controller.spill_count(), 1); assert_eq!(controller.entries_spilled(), 0); }
199
200 #[test]
201 fn multiple_cycles() {
202 let mut controller = SpillController::new(SpillConfig::default());
203
204 controller.check(90);
206 controller.record_spill(10);
207 assert_eq!(controller.spill_count(), 1);
208
209 controller.check(74);
211 assert!(!controller.is_spilling());
212
213 controller.check(91);
215 assert_eq!(controller.spill_count(), 2);
216 assert_eq!(controller.entries_spilled(), 0); }
218
219 #[test]
220 fn threshold_boundary() {
221 let config = SpillConfig {
222 spill_threshold: 80,
223 restore_threshold: 60,
224 };
225 let mut controller = SpillController::new(config);
226
227 assert_eq!(controller.check(80), SpillAction::BeginSpill);
229 assert!(controller.is_spilling());
230
231 assert_eq!(controller.check(79), SpillAction::None);
233 assert!(controller.is_spilling());
234
235 assert_eq!(controller.check(60), SpillAction::BeginRestore);
237 assert!(!controller.is_spilling());
238
239 assert_eq!(controller.check(59), SpillAction::None);
241 assert!(!controller.is_spilling());
242 }
243}