1use anyhow::bail;
21use anyhow::Context;
22use anyhow::Result;
23
24const NSEC_PER_USEC: u64 = 1_000;
26const NSEC_PER_MSEC: u64 = 1_000_000;
27const NSEC_PER_SEC: u64 = 1_000_000_000;
28
29const Q1_SLICE_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_Q1_SLICE_NS as u64;
41const Q2_SLICE_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_Q2_SLICE_NS as u64;
42const Q3_SLICE_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_Q3_SLICE_NS as u64;
43
44const BUDGET_MAX_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_BUDGET_MAX_NS as u64;
46
47const ALPHA: u64 = crate::bpf_intf::mlfq_consts_MLFQ_ALPHA as u64;
49
50const T_L_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_T_L_NS as u64;
52const T_H_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_T_H_NS as u64;
53
54const EMA_HALF_LIFE_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_EMA_HALF_LIFE_NS as u64;
56
57const AGING_PERIOD_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_AGING_PERIOD_NS as u64;
59
60const SHORT_SLEEP_NS: u64 = 32 * NSEC_PER_MSEC;
66const SHORT_SLEEP_RATE_LIMIT_NS: u64 =
67 crate::bpf_intf::mlfq_consts_MLFQ_SHORT_SLEEP_RATE_LIMIT_NS as u64;
68const HYSTERESIS_SLEEP_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_HYSTERESIS_SLEEP_NS as u64;
69
70const LONG_SLEEP_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_LONG_SLEEP_NS as u64;
72
73const SAMEQ_PREEMPT_MIN_RUN_NS: u64 =
76 crate::bpf_intf::mlfq_consts_MLFQ_SAMEQ_PREEMPT_MIN_RUN_NS as u64;
77
78const PREEMPT_SLICE_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_PREEMPT_SLICE_NS as u64;
81
82const Q1_QUOTA: u32 = crate::bpf_intf::mlfq_consts_MLFQ_Q1_QUOTA;
84const Q2_QUOTA: u32 = crate::bpf_intf::mlfq_consts_MLFQ_Q2_QUOTA;
85const DISPATCH_MAX_BATCH: u32 = crate::bpf_intf::mlfq_consts_MLFQ_DISPATCH_MAX_BATCH;
86
87const RTDL_DRAIN_INTERVAL_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_RTDL_DRAIN_INTERVAL_NS as u64;
89
90const TREE_T_INT_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_TREE_T_INT_NS as u64;
92const TREE_T_BOUND_NS: u64 = crate::bpf_intf::mlfq_consts_MLFQ_TREE_T_BOUND_NS as u64;
93
94#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct Config {
101 pub q1_slice_ns: u64,
103 pub q2_slice_ns: u64,
105 pub q3_slice_ns: u64,
107 pub budget_max_ns: u64,
109 pub alpha: u64,
111 pub t_l_ns: u64,
113 pub t_h_ns: u64,
115 pub ema_half_life_ns: u64,
117 pub aging_period_ns: u64,
119 pub short_sleep_ns: u64,
121 pub short_sleep_rate_limit_ns: u64,
123 pub hysteresis_sleep_ns: u64,
125 pub long_sleep_ns: u64,
127 pub sameq_preempt_min_run_ns: u64,
130 pub preempt_slice_ns: u64,
132 pub q1_quota: u32,
134 pub q2_quota: u32,
136 pub dispatch_max_batch: u32,
138 pub rtdl_drain_interval_ns: u64,
140 pub tree_t_int_ns: u64,
142 pub tree_t_bound_ns: u64,
144 pub adapt_enabled: bool,
146}
147
148impl Default for Config {
149 fn default() -> Self {
151 Self {
152 q1_slice_ns: Q1_SLICE_NS,
153 q2_slice_ns: Q2_SLICE_NS,
154 q3_slice_ns: Q3_SLICE_NS,
155 budget_max_ns: BUDGET_MAX_NS,
156 alpha: ALPHA,
157 t_l_ns: T_L_NS,
158 t_h_ns: T_H_NS,
159 ema_half_life_ns: EMA_HALF_LIFE_NS,
160 aging_period_ns: AGING_PERIOD_NS,
161 short_sleep_ns: SHORT_SLEEP_NS,
162 short_sleep_rate_limit_ns: SHORT_SLEEP_RATE_LIMIT_NS,
163 hysteresis_sleep_ns: HYSTERESIS_SLEEP_NS,
164 long_sleep_ns: LONG_SLEEP_NS,
165 sameq_preempt_min_run_ns: SAMEQ_PREEMPT_MIN_RUN_NS,
166 preempt_slice_ns: PREEMPT_SLICE_NS,
167 q1_quota: Q1_QUOTA,
168 q2_quota: Q2_QUOTA,
169 dispatch_max_batch: DISPATCH_MAX_BATCH,
170 rtdl_drain_interval_ns: RTDL_DRAIN_INTERVAL_NS,
171 tree_t_int_ns: TREE_T_INT_NS,
172 tree_t_bound_ns: TREE_T_BOUND_NS,
173 adapt_enabled: true,
174 }
175 }
176}
177
178impl Config {
179 pub fn validate(&self) -> Result<()> {
187 if self.q1_slice_ns == 0 || self.q2_slice_ns == 0 || self.q3_slice_ns == 0 {
188 bail!(
189 "queue slices must be non-zero (got Q1={} Q2={} Q3={})",
190 self.q1_slice_ns,
191 self.q2_slice_ns,
192 self.q3_slice_ns
193 );
194 }
195 if self.budget_max_ns == 0 {
196 bail!("budget_max must be non-zero");
197 }
198 if self.alpha == 0 {
199 bail!("alpha must be non-zero");
200 }
201 if self.t_l_ns == 0 {
202 bail!("T_L must be non-zero");
203 }
204 if self.t_l_ns >= self.t_h_ns {
205 bail!(
206 "T_L ({}) must be strictly below T_H ({})",
207 self.t_l_ns,
208 self.t_h_ns
209 );
210 }
211 if self.t_h_ns >= self.budget_max_ns {
212 bail!(
213 "T_H ({}) must be strictly below BUDGET_MAX ({})",
214 self.t_h_ns,
215 self.budget_max_ns
216 );
217 }
218 if self.ema_half_life_ns == 0 {
219 bail!("EMA half-life must be non-zero");
220 }
221 if self.aging_period_ns == 0 {
222 bail!("aging period must be non-zero");
223 }
224 if self.short_sleep_ns == 0 {
225 bail!("short-sleep window must be non-zero");
226 }
227 if self.short_sleep_rate_limit_ns == 0 {
228 bail!("short-sleep rate limit must be non-zero");
229 }
230 if self.hysteresis_sleep_ns == 0 {
231 bail!("hysteresis sleep window must be non-zero");
232 }
233 if self.long_sleep_ns == 0 {
234 bail!("long-sleep window must be non-zero");
235 }
236 if self.q1_quota == 0 || self.q2_quota == 0 {
237 bail!(
238 "dispatch quotas must be non-zero (got Q1={} Q2={})",
239 self.q1_quota,
240 self.q2_quota
241 );
242 }
243 if self.dispatch_max_batch == 0 {
244 bail!("dispatch_max_batch must be non-zero");
245 }
246 if u64::from(self.q1_quota) + u64::from(self.q2_quota) >= u64::from(self.dispatch_max_batch)
253 {
254 bail!(
255 "Q1+Q2 quotas ({}+{}) must leave headroom for Q3 within the dispatch batch ({})",
256 self.q1_quota,
257 self.q2_quota,
258 self.dispatch_max_batch
259 );
260 }
261 if self.dispatch_max_batch > crate::bpf_intf::mlfq_consts_MLFQ_DISPATCH_MAX_BATCH {
269 bail!(
270 "dispatch_max_batch ({}) exceeds the ops-table bound ({})",
271 self.dispatch_max_batch,
272 crate::bpf_intf::mlfq_consts_MLFQ_DISPATCH_MAX_BATCH
273 );
274 }
275 if self.rtdl_drain_interval_ns == 0 {
276 bail!("rtdl drain interval must be non-zero");
277 }
278 if self.tree_t_int_ns == 0 {
279 bail!("tree T_INT must be non-zero");
280 }
281 if self.tree_t_int_ns >= self.tree_t_bound_ns {
282 bail!(
283 "tree T_INT ({}) must be strictly below T_BOUND ({})",
284 self.tree_t_int_ns,
285 self.tree_t_bound_ns
286 );
287 }
288 Ok(())
289 }
290
291 pub fn apply(&self, skel: &mut crate::bpf_skel::OpenBpfSkel<'_>) -> Result<()> {
296 let rodata = skel
297 .maps
298 .rodata_data
299 .as_mut()
300 .context("rodata missing, the BPF object has no .rodata section")?;
301 rodata.mlfq_q1_slice_ns = self.q1_slice_ns;
302 rodata.mlfq_q2_slice_ns = self.q2_slice_ns;
303 rodata.mlfq_q3_slice_ns = self.q3_slice_ns;
304 rodata.mlfq_budget_max_ns = self.budget_max_ns;
305 rodata.mlfq_alpha = self.alpha;
306 rodata.mlfq_t_l_ns = self.t_l_ns;
307 rodata.mlfq_t_h_ns = self.t_h_ns;
308 rodata.mlfq_ema_half_life_ns = self.ema_half_life_ns;
309 rodata.mlfq_aging_period_ns = self.aging_period_ns;
310 rodata.mlfq_short_sleep_ns = self.short_sleep_ns;
311 rodata.mlfq_short_sleep_rate_limit_ns = self.short_sleep_rate_limit_ns;
312 rodata.mlfq_hysteresis_sleep_ns = self.hysteresis_sleep_ns;
313 rodata.mlfq_long_sleep_ns = self.long_sleep_ns;
314 rodata.mlfq_sameq_preempt_min_run_ns = self.sameq_preempt_min_run_ns;
315 rodata.mlfq_preempt_slice_ns = self.preempt_slice_ns;
316 rodata.mlfq_q1_quota = self.q1_quota;
317 rodata.mlfq_q2_quota = self.q2_quota;
318 rodata.mlfq_dispatch_max_batch = self.dispatch_max_batch;
319 rodata.mlfq_rtdl_drain_interval_ns = self.rtdl_drain_interval_ns;
320 rodata.mlfq_tree_t_int_ns = self.tree_t_int_ns;
321 rodata.mlfq_tree_t_bound_ns = self.tree_t_bound_ns;
322 rodata.mlfq_adapt_enabled = self.adapt_enabled;
323 Ok(())
324 }
325
326 pub fn describe(&self) -> String {
328 format!(
329 "slices: Q1={}us Q2={}us Q3={}us, T_L={}us, T_H={}us, \
330 budget_max={}us, alpha={}, ema_half_life={}us, aging_period={}s, \
331 short_sleep={}us, ss_rate_limit={}us, hysteresis_sleep={}us, \
332 long_sleep={}ms, sameq_min_run={}us, preempt_slice={}us, \
333 rtdl_drain_interval={}us, quotas: Q1={} Q2={} max_batch={}, \
334 tree_bands: T_INT={}us T_BOUND={}us, adapt_enabled={}",
335 self.q1_slice_ns / NSEC_PER_USEC,
336 self.q2_slice_ns / NSEC_PER_USEC,
337 self.q3_slice_ns / NSEC_PER_USEC,
338 self.t_l_ns / NSEC_PER_USEC,
339 self.t_h_ns / NSEC_PER_USEC,
340 self.budget_max_ns / NSEC_PER_USEC,
341 self.alpha,
342 self.ema_half_life_ns / NSEC_PER_USEC,
343 self.aging_period_ns / NSEC_PER_SEC,
344 self.short_sleep_ns / NSEC_PER_USEC,
345 self.short_sleep_rate_limit_ns / NSEC_PER_USEC,
346 self.hysteresis_sleep_ns / NSEC_PER_USEC,
347 self.long_sleep_ns / NSEC_PER_MSEC,
348 self.sameq_preempt_min_run_ns / NSEC_PER_USEC,
349 self.preempt_slice_ns / NSEC_PER_USEC,
350 self.rtdl_drain_interval_ns / NSEC_PER_USEC,
351 self.q1_quota,
352 self.q2_quota,
353 self.dispatch_max_batch,
354 self.tree_t_int_ns / NSEC_PER_USEC,
355 self.tree_t_bound_ns / NSEC_PER_USEC,
356 self.adapt_enabled,
357 )
358 }
359}
360
361#[cfg(test)]
372#[derive(Debug, Clone, Default)]
373pub struct ConfigBuilder {
374 q1_slice_ns: Option<u64>,
375 q2_slice_ns: Option<u64>,
376 q3_slice_ns: Option<u64>,
377 budget_max_ns: Option<u64>,
378 alpha: Option<u64>,
379 t_l_ns: Option<u64>,
380 t_h_ns: Option<u64>,
381 ema_half_life_ns: Option<u64>,
382 aging_period_ns: Option<u64>,
383 short_sleep_ns: Option<u64>,
384 short_sleep_rate_limit_ns: Option<u64>,
385 hysteresis_sleep_ns: Option<u64>,
386 long_sleep_ns: Option<u64>,
387 sameq_preempt_min_run_ns: Option<u64>,
388 preempt_slice_ns: Option<u64>,
389 q1_quota: Option<u32>,
390 q2_quota: Option<u32>,
391 dispatch_max_batch: Option<u32>,
392 rtdl_drain_interval_ns: Option<u64>,
393 tree_t_int_ns: Option<u64>,
394 tree_t_bound_ns: Option<u64>,
395 adapt_enabled: Option<bool>,
396}
397
398#[cfg(test)]
404impl ConfigBuilder {
405 pub fn q1_slice_ns(mut self, v: u64) -> Self {
407 self.q1_slice_ns = Some(v);
408 self
409 }
410
411 pub fn q2_slice_ns(mut self, v: u64) -> Self {
413 self.q2_slice_ns = Some(v);
414 self
415 }
416
417 pub fn q3_slice_ns(mut self, v: u64) -> Self {
419 self.q3_slice_ns = Some(v);
420 self
421 }
422
423 pub fn budget_max_ns(mut self, v: u64) -> Self {
425 self.budget_max_ns = Some(v);
426 self
427 }
428
429 pub fn alpha(mut self, v: u64) -> Self {
431 self.alpha = Some(v);
432 self
433 }
434
435 pub fn t_l_ns(mut self, v: u64) -> Self {
437 self.t_l_ns = Some(v);
438 self
439 }
440
441 pub fn t_h_ns(mut self, v: u64) -> Self {
443 self.t_h_ns = Some(v);
444 self
445 }
446
447 pub fn ema_half_life_ns(mut self, v: u64) -> Self {
449 self.ema_half_life_ns = Some(v);
450 self
451 }
452
453 pub fn aging_period_ns(mut self, v: u64) -> Self {
455 self.aging_period_ns = Some(v);
456 self
457 }
458
459 pub fn short_sleep_ns(mut self, v: u64) -> Self {
461 self.short_sleep_ns = Some(v);
462 self
463 }
464
465 pub fn short_sleep_rate_limit_ns(mut self, v: u64) -> Self {
467 self.short_sleep_rate_limit_ns = Some(v);
468 self
469 }
470
471 pub fn hysteresis_sleep_ns(mut self, v: u64) -> Self {
473 self.hysteresis_sleep_ns = Some(v);
474 self
475 }
476
477 pub fn long_sleep_ns(mut self, v: u64) -> Self {
479 self.long_sleep_ns = Some(v);
480 self
481 }
482
483 pub fn sameq_preempt_min_run_ns(mut self, v: u64) -> Self {
485 self.sameq_preempt_min_run_ns = Some(v);
486 self
487 }
488
489 pub fn preempt_slice_ns(mut self, v: u64) -> Self {
491 self.preempt_slice_ns = Some(v);
492 self
493 }
494
495 pub fn q1_quota(mut self, v: u32) -> Self {
497 self.q1_quota = Some(v);
498 self
499 }
500
501 pub fn q2_quota(mut self, v: u32) -> Self {
503 self.q2_quota = Some(v);
504 self
505 }
506
507 pub fn dispatch_max_batch(mut self, v: u32) -> Self {
509 self.dispatch_max_batch = Some(v);
510 self
511 }
512
513 pub fn rtdl_drain_interval_ns(mut self, v: u64) -> Self {
515 self.rtdl_drain_interval_ns = Some(v);
516 self
517 }
518
519 pub fn tree_t_int_ns(mut self, v: u64) -> Self {
521 self.tree_t_int_ns = Some(v);
522 self
523 }
524
525 pub fn tree_t_bound_ns(mut self, v: u64) -> Self {
527 self.tree_t_bound_ns = Some(v);
528 self
529 }
530
531 pub fn adapt_enabled(mut self, v: bool) -> Self {
533 self.adapt_enabled = Some(v);
534 self
535 }
536
537 pub fn build(self) -> Result<Config> {
542 let defaults = Config::default();
543 let cfg = Config {
544 q1_slice_ns: self.q1_slice_ns.unwrap_or(defaults.q1_slice_ns),
545 q2_slice_ns: self.q2_slice_ns.unwrap_or(defaults.q2_slice_ns),
546 q3_slice_ns: self.q3_slice_ns.unwrap_or(defaults.q3_slice_ns),
547 budget_max_ns: self.budget_max_ns.unwrap_or(defaults.budget_max_ns),
548 alpha: self.alpha.unwrap_or(defaults.alpha),
549 t_l_ns: self.t_l_ns.unwrap_or(defaults.t_l_ns),
550 t_h_ns: self.t_h_ns.unwrap_or(defaults.t_h_ns),
551 ema_half_life_ns: self.ema_half_life_ns.unwrap_or(defaults.ema_half_life_ns),
552 aging_period_ns: self.aging_period_ns.unwrap_or(defaults.aging_period_ns),
553 short_sleep_ns: self.short_sleep_ns.unwrap_or(defaults.short_sleep_ns),
554 short_sleep_rate_limit_ns: self
555 .short_sleep_rate_limit_ns
556 .unwrap_or(defaults.short_sleep_rate_limit_ns),
557 hysteresis_sleep_ns: self
558 .hysteresis_sleep_ns
559 .unwrap_or(defaults.hysteresis_sleep_ns),
560 long_sleep_ns: self.long_sleep_ns.unwrap_or(defaults.long_sleep_ns),
561 sameq_preempt_min_run_ns: self
562 .sameq_preempt_min_run_ns
563 .unwrap_or(defaults.sameq_preempt_min_run_ns),
564 preempt_slice_ns: self.preempt_slice_ns.unwrap_or(defaults.preempt_slice_ns),
565 q1_quota: self.q1_quota.unwrap_or(defaults.q1_quota),
566 q2_quota: self.q2_quota.unwrap_or(defaults.q2_quota),
567 dispatch_max_batch: self
568 .dispatch_max_batch
569 .unwrap_or(defaults.dispatch_max_batch),
570 rtdl_drain_interval_ns: self
571 .rtdl_drain_interval_ns
572 .unwrap_or(defaults.rtdl_drain_interval_ns),
573 tree_t_int_ns: self.tree_t_int_ns.unwrap_or(defaults.tree_t_int_ns),
574 tree_t_bound_ns: self.tree_t_bound_ns.unwrap_or(defaults.tree_t_bound_ns),
575 adapt_enabled: self.adapt_enabled.unwrap_or(defaults.adapt_enabled),
576 };
577 cfg.validate()?;
578 Ok(cfg)
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use super::*;
585
586 #[test]
587 fn defaults_match_intf_h() {
588 use crate::bpf_intf::{
596 mlfq_consts_MLFQ_AGING_PERIOD_NS, mlfq_consts_MLFQ_ALPHA,
597 mlfq_consts_MLFQ_BUDGET_MAX_NS, mlfq_consts_MLFQ_DISPATCH_MAX_BATCH,
598 mlfq_consts_MLFQ_EMA_HALF_LIFE_NS, mlfq_consts_MLFQ_HYSTERESIS_SLEEP_NS,
599 mlfq_consts_MLFQ_LONG_SLEEP_NS, mlfq_consts_MLFQ_PREEMPT_SLICE_NS,
600 mlfq_consts_MLFQ_Q1_QUOTA, mlfq_consts_MLFQ_Q1_SLICE_NS, mlfq_consts_MLFQ_Q2_QUOTA,
601 mlfq_consts_MLFQ_Q2_SLICE_NS, mlfq_consts_MLFQ_Q3_SLICE_NS,
602 mlfq_consts_MLFQ_RTDL_DRAIN_INTERVAL_NS, mlfq_consts_MLFQ_SAMEQ_PREEMPT_MIN_RUN_NS,
603 mlfq_consts_MLFQ_SHORT_SLEEP_NS, mlfq_consts_MLFQ_SHORT_SLEEP_RATE_LIMIT_NS,
604 mlfq_consts_MLFQ_TREE_T_BOUND_NS, mlfq_consts_MLFQ_TREE_T_INT_NS,
605 mlfq_consts_MLFQ_T_H_NS, mlfq_consts_MLFQ_T_L_NS,
606 };
607
608 let cfg = Config::default();
609 cfg.validate().unwrap();
610 assert_eq!(cfg.q1_slice_ns, mlfq_consts_MLFQ_Q1_SLICE_NS as u64);
611 assert_eq!(cfg.q2_slice_ns, mlfq_consts_MLFQ_Q2_SLICE_NS as u64);
612 assert_eq!(cfg.q3_slice_ns, mlfq_consts_MLFQ_Q3_SLICE_NS as u64);
613 assert_eq!(cfg.budget_max_ns, mlfq_consts_MLFQ_BUDGET_MAX_NS as u64);
614 assert_eq!(cfg.alpha, mlfq_consts_MLFQ_ALPHA as u64);
615 assert_eq!(cfg.t_l_ns, mlfq_consts_MLFQ_T_L_NS as u64);
616 assert_eq!(cfg.t_h_ns, mlfq_consts_MLFQ_T_H_NS as u64);
617 assert_eq!(
618 cfg.ema_half_life_ns,
619 mlfq_consts_MLFQ_EMA_HALF_LIFE_NS as u64
620 );
621 assert_eq!(cfg.aging_period_ns, mlfq_consts_MLFQ_AGING_PERIOD_NS as u64);
622 assert_eq!(cfg.short_sleep_ns, mlfq_consts_MLFQ_SHORT_SLEEP_NS as u64);
623 assert_eq!(
624 cfg.short_sleep_rate_limit_ns,
625 mlfq_consts_MLFQ_SHORT_SLEEP_RATE_LIMIT_NS as u64
626 );
627 assert_eq!(
628 cfg.hysteresis_sleep_ns,
629 mlfq_consts_MLFQ_HYSTERESIS_SLEEP_NS as u64
630 );
631 assert_eq!(cfg.long_sleep_ns, mlfq_consts_MLFQ_LONG_SLEEP_NS as u64);
632 assert_eq!(
633 cfg.sameq_preempt_min_run_ns,
634 mlfq_consts_MLFQ_SAMEQ_PREEMPT_MIN_RUN_NS as u64
635 );
636 assert_eq!(
637 cfg.preempt_slice_ns,
638 mlfq_consts_MLFQ_PREEMPT_SLICE_NS as u64
639 );
640 assert_eq!(cfg.q1_quota, mlfq_consts_MLFQ_Q1_QUOTA);
641 assert_eq!(cfg.q2_quota, mlfq_consts_MLFQ_Q2_QUOTA);
642 assert_eq!(cfg.dispatch_max_batch, mlfq_consts_MLFQ_DISPATCH_MAX_BATCH);
643 assert_eq!(
644 cfg.rtdl_drain_interval_ns,
645 mlfq_consts_MLFQ_RTDL_DRAIN_INTERVAL_NS as u64
646 );
647 assert_eq!(cfg.tree_t_int_ns, mlfq_consts_MLFQ_TREE_T_INT_NS as u64);
648 assert_eq!(cfg.tree_t_bound_ns, mlfq_consts_MLFQ_TREE_T_BOUND_NS as u64);
649 assert!(cfg.adapt_enabled, "the adaptation ships enabled");
650 }
651
652 #[test]
653 fn builder_defaults_equal_config_defaults() {
654 let cfg = ConfigBuilder::default().build().unwrap();
655 assert_eq!(cfg, Config::default());
656 }
657
658 #[test]
659 fn builder_overrides_individual_fields() {
660 let cfg = ConfigBuilder::default()
661 .q1_slice_ns(500_000)
662 .build()
663 .unwrap();
664 assert_eq!(cfg.q1_slice_ns, 500_000);
665 assert_eq!(cfg.q2_slice_ns, Config::default().q2_slice_ns);
666 }
667
668 #[test]
669 fn builder_overrides_sameq_min_run() {
670 let cfg = ConfigBuilder::default()
671 .sameq_preempt_min_run_ns(250_000)
672 .build()
673 .unwrap();
674 assert_eq!(cfg.sameq_preempt_min_run_ns, 250_000);
675 assert_eq!(cfg.q1_slice_ns, Config::default().q1_slice_ns);
676 }
677
678 #[test]
679 fn builder_overrides_preempt_slice() {
680 let cfg = ConfigBuilder::default()
681 .preempt_slice_ns(100_000)
682 .build()
683 .unwrap();
684 assert_eq!(cfg.preempt_slice_ns, 100_000);
685 assert_eq!(cfg.q1_slice_ns, Config::default().q1_slice_ns);
686 }
687
688 #[test]
689 fn rejects_zero_slices() {
690 assert!(ConfigBuilder::default().q1_slice_ns(0).build().is_err());
691 assert!(ConfigBuilder::default().q2_slice_ns(0).build().is_err());
692 assert!(ConfigBuilder::default().q3_slice_ns(0).build().is_err());
693 }
694
695 #[test]
696 fn rejects_t_l_at_or_above_t_h() {
697 assert!(ConfigBuilder::default().t_l_ns(2_000_000).build().is_err());
698 assert!(ConfigBuilder::default().t_l_ns(3_000_000).build().is_err());
699 }
700
701 #[test]
702 fn rejects_t_h_at_or_above_budget_max() {
703 assert!(ConfigBuilder::default().t_h_ns(6_000_000).build().is_err());
704 assert!(ConfigBuilder::default().t_h_ns(7_000_000).build().is_err());
705 }
706
707 #[test]
708 fn rejects_zero_quotas() {
709 assert!(ConfigBuilder::default().q1_quota(0).build().is_err());
710 assert!(ConfigBuilder::default().q2_quota(0).build().is_err());
711 assert!(ConfigBuilder::default()
712 .dispatch_max_batch(0)
713 .build()
714 .is_err());
715 }
716
717 #[test]
718 fn rejects_quotas_consuming_the_batch() {
719 let cfg = ConfigBuilder::default()
721 .q1_quota(16)
722 .q2_quota(16)
723 .dispatch_max_batch(32)
724 .build();
725 assert!(cfg.is_err());
726 }
727
728 #[test]
729 fn rejects_dispatch_batch_above_ops_bound() {
730 let cfg = ConfigBuilder::default()
733 .dispatch_max_batch(crate::bpf_intf::mlfq_consts_MLFQ_DISPATCH_MAX_BATCH + 1)
734 .build();
735 assert!(cfg.is_err());
736 let cfg = ConfigBuilder::default()
738 .dispatch_max_batch(crate::bpf_intf::mlfq_consts_MLFQ_DISPATCH_MAX_BATCH)
739 .build();
740 assert!(cfg.is_ok());
741 }
742
743 #[test]
744 fn rejects_zero_aging_period() {
745 assert!(ConfigBuilder::default().aging_period_ns(0).build().is_err());
746 }
747
748 #[test]
749 fn rejects_tree_bands_out_of_order() {
750 assert!(ConfigBuilder::default().tree_t_int_ns(0).build().is_err());
751 let cfg = ConfigBuilder::default()
752 .tree_t_int_ns(3_000_000)
753 .tree_t_bound_ns(3_000_000)
754 .build();
755 assert!(cfg.is_err());
756 let cfg = ConfigBuilder::default()
757 .tree_t_int_ns(4_000_000)
758 .tree_t_bound_ns(3_000_000)
759 .build();
760 assert!(cfg.is_err());
761 assert!(ConfigBuilder::default().build().is_ok());
763 }
764
765 #[test]
766 fn adapt_gate_flag_round_trips() {
767 let cfg = ConfigBuilder::default()
768 .adapt_enabled(true)
769 .build()
770 .unwrap();
771 assert!(cfg.adapt_enabled);
772 let cfg = ConfigBuilder::default().build().unwrap();
773 assert!(cfg.adapt_enabled);
774 }
775
776 #[test]
777 fn describe_is_stable() {
778 let cfg = Config::default();
779 let s = cfg.describe();
780 assert!(s.contains("slices: Q1=1000us Q2=2000us Q3=4000us"));
781 assert!(s.contains("T_L=250us, T_H=2000us"));
782 assert!(s.contains("aging_period=1s"));
783 assert!(s.contains("rtdl_drain_interval=1000us"));
784 assert!(s.contains("quotas: Q1=4 Q2=8 max_batch=32"));
785 assert!(s.contains("tree_bands: T_INT=1000us T_BOUND=3000us"));
786 assert!(s.contains("adapt_enabled=true"));
787 }
788}