1use llmosafe::ResourceGuard;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PressureZone {
12 Green,
14 Yellow,
16 Orange,
18 Red,
20}
21
22#[derive(Debug, Clone)]
24pub struct CacheDirective {
25 pub zone: PressureZone,
27 pub pressure: u8,
29 pub evict_fraction: f64,
31 pub allow_new_entries: bool,
33 pub allow_mmap_pin: bool,
35}
36
37#[allow(clippy::missing_fields_in_debug)]
45pub struct AdaptiveCachePolicy {
46 guard: ResourceGuard,
47 ceiling: usize,
48}
49
50impl AdaptiveCachePolicy {
51 #[must_use]
56 #[allow(
57 clippy::cast_precision_loss,
58 clippy::cast_sign_loss,
59 clippy::cast_possible_truncation,
60 clippy::as_conversions
61 )]
62 pub fn new(ceiling_fraction: f64) -> Self {
63 let guard = ResourceGuard::auto(ceiling_fraction);
64 let ceiling = (ResourceGuard::system_memory_bytes() as f64 * ceiling_fraction) as usize;
65 Self { guard, ceiling }
66 }
67
68 #[must_use]
77 pub fn new_with_guard(guard: ResourceGuard, ceiling_bytes: usize) -> Self {
78 Self {
79 guard,
80 ceiling: ceiling_bytes,
81 }
82 }
83
84 #[must_use]
86 pub fn guard(&self) -> &ResourceGuard {
87 &self.guard
88 }
89
90 #[must_use]
106 pub fn directive(&self) -> CacheDirective {
107 let pressure = self.guard.pressure();
108 let (zone, evict_fraction, allow_new_entries, allow_mmap_pin) = match pressure {
109 0..=40 => (PressureZone::Green, 0.0_f64, true, true),
110 41..=70 => (PressureZone::Yellow, 0.1_f64, true, true),
111 71..=90 => (PressureZone::Orange, 0.5_f64, false, false),
112 _ => (PressureZone::Red, 1.0_f64, false, false),
113 };
114 CacheDirective {
115 zone,
116 pressure,
117 evict_fraction,
118 allow_new_entries,
119 allow_mmap_pin,
120 }
121 }
122
123 #[must_use]
127 pub fn pressure(&self) -> u8 {
128 self.guard.pressure()
129 }
130
131 #[must_use]
133 pub fn rss_bytes(&self) -> usize {
134 ResourceGuard::current_rss_bytes()
135 }
136
137 #[must_use]
139 pub fn ceiling_bytes(&self) -> usize {
140 self.ceiling
141 }
142}
143
144impl std::fmt::Debug for AdaptiveCachePolicy {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 f.debug_struct("AdaptiveCachePolicy")
147 .field("ceiling", &self.ceiling)
148 .finish_non_exhaustive()
149 }
150}
151
152impl PressureZone {
153 #[must_use]
155 pub fn from_pressure(pressure: u8) -> Self {
156 match pressure {
157 0..=40 => Self::Green,
158 41..=70 => Self::Yellow,
159 71..=90 => Self::Orange,
160 _ => Self::Red,
161 }
162 }
163}
164
165#[cfg(test)]
166#[allow(clippy::as_conversions, clippy::unwrap_used, clippy::indexing_slicing)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn zone_classification_all_zones() {
172 assert_eq!(PressureZone::from_pressure(0), PressureZone::Green);
173 assert_eq!(PressureZone::from_pressure(20), PressureZone::Green);
174 assert_eq!(PressureZone::from_pressure(40), PressureZone::Green);
175 assert_eq!(PressureZone::from_pressure(41), PressureZone::Yellow);
176 assert_eq!(PressureZone::from_pressure(55), PressureZone::Yellow);
177 assert_eq!(PressureZone::from_pressure(70), PressureZone::Yellow);
178 assert_eq!(PressureZone::from_pressure(71), PressureZone::Orange);
179 assert_eq!(PressureZone::from_pressure(80), PressureZone::Orange);
180 assert_eq!(PressureZone::from_pressure(90), PressureZone::Orange);
181 assert_eq!(PressureZone::from_pressure(91), PressureZone::Red);
182 assert_eq!(PressureZone::from_pressure(100), PressureZone::Red);
183 }
184
185 #[test]
186 fn directive_fields_per_zone() {
187 let policy = AdaptiveCachePolicy::new(0.75);
188
189 let green = CacheDirective {
190 zone: PressureZone::Green,
191 pressure: 25,
192 evict_fraction: 0.0,
193 allow_new_entries: true,
194 allow_mmap_pin: true,
195 };
196 let yellow = CacheDirective {
197 zone: PressureZone::Yellow,
198 pressure: 55,
199 evict_fraction: 0.1,
200 allow_new_entries: true,
201 allow_mmap_pin: true,
202 };
203 let orange = CacheDirective {
204 zone: PressureZone::Orange,
205 pressure: 80,
206 evict_fraction: 0.5,
207 allow_new_entries: false,
208 allow_mmap_pin: false,
209 };
210 let red = CacheDirective {
211 zone: PressureZone::Red,
212 pressure: 95,
213 evict_fraction: 1.0,
214 allow_new_entries: false,
215 allow_mmap_pin: false,
216 };
217
218 for (label, pressure, expected) in [
219 ("green", 25, &green),
220 ("yellow", 55, &yellow),
221 ("orange", 80, &orange),
222 ("red", 95, &red),
223 ] {
224 let zone = PressureZone::from_pressure(pressure);
225 let (evict_fraction, allow_new_entries, allow_mmap_pin) = match zone {
226 PressureZone::Green => (0.0_f64, true, true),
227 PressureZone::Yellow => (0.1_f64, true, true),
228 PressureZone::Orange => (0.5_f64, false, false),
229 PressureZone::Red => (1.0_f64, false, false),
230 };
231 assert_eq!(
232 zone, expected.zone,
233 "{label}: zone mismatch for pressure {pressure}"
234 );
235 assert!(
236 (evict_fraction - expected.evict_fraction).abs() < f64::EPSILON,
237 "{label}: evict_fraction mismatch"
238 );
239 assert_eq!(
240 allow_new_entries, expected.allow_new_entries,
241 "{label}: allow_new_entries mismatch"
242 );
243 assert_eq!(
244 allow_mmap_pin, expected.allow_mmap_pin,
245 "{label}: allow_mmap_pin mismatch"
246 );
247 }
248
249 let directive = policy.directive();
250 assert!(directive.pressure <= 100);
251 assert_eq!(
252 directive.zone,
253 PressureZone::from_pressure(directive.pressure)
254 );
255 }
256
257 #[test]
258 #[expect(
259 clippy::integer_division,
260 reason = "intentional ceiling via floor(sys_mem * fraction)"
261 )]
262 fn ceiling_fraction_applied_correctly() {
263 let sys_mem = ResourceGuard::system_memory_bytes();
264 if sys_mem == 0 {
265 return;
266 }
267
268 let expected_ceiling = sys_mem / 2;
270 let policy = AdaptiveCachePolicy::new(0.5_f64);
271 assert_eq!(
272 policy.ceiling_bytes(),
273 expected_ceiling,
274 "ceiling_bytes should equal system_memory * 0.5"
275 );
276
277 let expected_75 = sys_mem.saturating_mul(3) / 4;
279 let policy_75 = AdaptiveCachePolicy::new(0.75_f64);
280 assert_eq!(
281 policy_75.ceiling_bytes(),
282 expected_75,
283 "ceiling_bytes with 0.75 fraction"
284 );
285 }
286
287 #[test]
288 fn pressure_returns_bounded_value() {
289 let policy = AdaptiveCachePolicy::new(0.75);
290 let pressure = policy.pressure();
291 assert!(pressure <= 100, "pressure {pressure} should be <= 100");
292 }
293
294 #[test]
295 fn rss_bytes_is_non_negative() {
296 let policy = AdaptiveCachePolicy::new(0.75);
297 let rss = policy.rss_bytes();
298 assert!(
299 rss <= ResourceGuard::system_memory_bytes(),
300 "RSS {rss} should not exceed system memory"
301 );
302 }
303
304 #[test]
305 fn debug_impl_works() {
306 let policy = AdaptiveCachePolicy::new(0.75);
307 let debug_str = format!("{policy:?}");
308 assert!(
309 debug_str.contains("AdaptiveCachePolicy"),
310 "Debug output should contain type name"
311 );
312 assert!(
313 debug_str.contains("ceiling"),
314 "Debug output should contain ceiling field"
315 );
316 }
317
318 #[test]
319 #[expect(clippy::integer_division, reason = "intentional ceiling computation")]
320 fn new_with_guard_shares_ceiling() {
321 let sys_mem = ResourceGuard::system_memory_bytes();
322 if sys_mem == 0 {
323 return;
324 }
325 let ceiling = sys_mem / 2;
326 let guard = ResourceGuard::new(ceiling);
327 let policy = AdaptiveCachePolicy::new_with_guard(guard, ceiling);
328 assert_eq!(
329 policy.ceiling_bytes(),
330 ceiling,
331 "new_with_guard should use the provided ceiling"
332 );
333 assert_eq!(
334 policy.guard().pressure(),
335 ResourceGuard::new(ceiling).pressure(),
336 "guard pressure should match"
337 );
338 }
339}