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