1pub const HEAVY_ENTER_PCT: u64 = 10; pub const HEAVY_EXIT_PCT: u64 = 25; pub const LIGHT_ENTER_PCT: u64 = 50; pub const LIGHT_EXIT_PCT: u64 = 30; const LIGHT_SLICE_NS: u64 = 2_000_000; const LIGHT_PREEMPT_NS: u64 = 1_000_000; const LIGHT_LAG_SCALE: u64 = 6;
22const LIGHT_BATCH_NS: u64 = 20_000_000; const MIXED_SLICE_NS: u64 = 1_000_000; const MIXED_PREEMPT_NS: u64 = 1_000_000; const MIXED_LAG_SCALE: u64 = 4;
27const MIXED_BATCH_NS: u64 = 20_000_000; const HEAVY_SLICE_NS: u64 = 4_000_000; const HEAVY_PREEMPT_NS: u64 = 2_000_000; const HEAVY_LAG_SCALE: u64 = 2;
32const HEAVY_BATCH_NS: u64 = 20_000_000; const LIGHT_P99_CEIL_NS: u64 = 3_000_000; const MIXED_P99_CEIL_NS: u64 = 5_000_000; const HEAVY_P99_CEIL_NS: u64 = 10_000_000; pub const DEFAULT_LAT_CRI_THRESH_HIGH: u64 = 32; pub const DEFAULT_LAT_CRI_THRESH_LOW: u64 = 8; pub const AFFINITY_OFF: u64 = 0;
52pub const AFFINITY_WEAK: u64 = 1;
53pub const AFFINITY_STRONG: u64 = 2;
54
55#[repr(C)]
56#[derive(Clone, Copy)]
57pub struct TuningKnobs {
58 pub slice_ns: u64,
59 pub preempt_thresh_ns: u64,
60 pub lag_scale: u64,
61 pub batch_slice_ns: u64,
62 pub lat_cri_thresh_high: u64,
63 pub lat_cri_thresh_low: u64,
64 pub affinity_mode: u64,
65 pub sojourn_thresh_ns: u64,
66 pub burst_slice_ns: u64,
67 pub topology_tau_ns: u64,
72 pub codel_eq_ns: u64,
75}
76
77impl Default for TuningKnobs {
78 fn default() -> Self {
79 Self {
80 slice_ns: 1_000_000,
81 preempt_thresh_ns: 1_000_000,
82 lag_scale: 4,
83 batch_slice_ns: 20_000_000,
84 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
85 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
86 affinity_mode: AFFINITY_OFF,
87 sojourn_thresh_ns: 5_000_000,
88 burst_slice_ns: 1_000_000,
89 topology_tau_ns: 0,
90 codel_eq_ns: 0,
91 }
92 }
93}
94
95#[repr(u8)]
98#[derive(Clone, Copy, PartialEq, Eq, Debug)]
99pub enum Regime {
100 Light = 0,
101 Mixed = 1,
102 Heavy = 2,
103}
104
105impl Regime {
106 pub fn label(self) -> &'static str {
107 match self {
108 Self::Light => "LIGHT",
109 Self::Mixed => "MIXED",
110 Self::Heavy => "HEAVY",
111 }
112 }
113
114 pub fn p99_ceiling(self) -> u64 {
115 match self {
116 Self::Light => LIGHT_P99_CEIL_NS,
117 Self::Mixed => MIXED_P99_CEIL_NS,
118 Self::Heavy => HEAVY_P99_CEIL_NS,
119 }
120 }
121}
122
123pub fn regime_knobs(r: Regime) -> TuningKnobs {
126 match r {
127 Regime::Light => TuningKnobs {
128 slice_ns: LIGHT_SLICE_NS,
129 preempt_thresh_ns: LIGHT_PREEMPT_NS,
130 lag_scale: LIGHT_LAG_SCALE,
131 batch_slice_ns: LIGHT_BATCH_NS,
132 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
133 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
134 affinity_mode: AFFINITY_WEAK,
135 sojourn_thresh_ns: 5_000_000,
136 burst_slice_ns: 1_000_000,
137 topology_tau_ns: 0,
138 codel_eq_ns: 0,
139 },
140 Regime::Mixed => TuningKnobs {
141 slice_ns: MIXED_SLICE_NS,
142 preempt_thresh_ns: MIXED_PREEMPT_NS,
143 lag_scale: MIXED_LAG_SCALE,
144 batch_slice_ns: MIXED_BATCH_NS,
145 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
146 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
147 affinity_mode: AFFINITY_STRONG,
148 sojourn_thresh_ns: 5_000_000,
149 burst_slice_ns: 1_000_000,
150 topology_tau_ns: 0,
151 codel_eq_ns: 0,
152 },
153 Regime::Heavy => TuningKnobs {
154 slice_ns: HEAVY_SLICE_NS,
155 preempt_thresh_ns: HEAVY_PREEMPT_NS,
156 lag_scale: HEAVY_LAG_SCALE,
157 batch_slice_ns: HEAVY_BATCH_NS,
158 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
159 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
160 affinity_mode: AFFINITY_WEAK,
161 sojourn_thresh_ns: 5_000_000,
162 burst_slice_ns: 1_000_000,
163 topology_tau_ns: 0,
164 codel_eq_ns: 0,
165 },
166 }
167}
168
169const K_SLICE_CAP_Q16: u64 = 9830; const K_PREEMPT_CAP_Q16: u64 = 4915; const K_BATCH_CAP_Q16: u64 = 98304; const K_SOJOURN_Q16: u64 = 9830; const K_FORK_STORM_RATE_Q16: u64 = 13107; const FORK_STORM_RATE_FLOOR: u64 = 200; #[inline]
191fn scale_tau_u64(tau_ns: u64, k_q16: u64) -> u64 {
192 (tau_ns as u128 * k_q16 as u128 >> 16) as u64
193}
194
195pub fn scaled_regime_knobs(r: Regime, _nr_cpus: u64, tau_ns: u64) -> TuningKnobs {
196 let mut knobs = regime_knobs(r);
197
198 let slice_cap_tau = scale_tau_u64(tau_ns, K_SLICE_CAP_Q16).clamp(500_000, 8_000_000);
199 let preempt_cap_tau = scale_tau_u64(tau_ns, K_PREEMPT_CAP_Q16).clamp(250_000, 4_000_000);
200 let sojourn_tau = scale_tau_u64(tau_ns, K_SOJOURN_Q16).clamp(2_000_000, 6_000_000);
201
202 knobs.slice_ns = knobs.slice_ns.min(slice_cap_tau);
203 knobs.preempt_thresh_ns = knobs.preempt_thresh_ns.min(preempt_cap_tau);
204 if matches!(r, Regime::Mixed) {
205 let batch_cap_tau = scale_tau_u64(tau_ns, K_BATCH_CAP_Q16).clamp(10_000_000, 80_000_000);
206 knobs.batch_slice_ns = knobs.batch_slice_ns.min(batch_cap_tau);
207 }
208 knobs.sojourn_thresh_ns = sojourn_tau;
209
210 knobs
211}
212
213pub fn detect_regime(current: Regime, idle_pct: u64) -> Regime {
218 match current {
219 Regime::Light => {
220 if idle_pct < LIGHT_EXIT_PCT {
221 Regime::Mixed
222 } else {
223 Regime::Light
224 }
225 }
226 Regime::Mixed => {
227 if idle_pct > LIGHT_ENTER_PCT {
228 Regime::Light
229 } else if idle_pct < HEAVY_ENTER_PCT {
230 Regime::Heavy
231 } else {
232 Regime::Mixed
233 }
234 }
235 Regime::Heavy => {
236 if idle_pct > HEAVY_EXIT_PCT {
237 Regime::Mixed
238 } else {
239 Regime::Heavy
240 }
241 }
242 }
243}
244
245pub const STABILITY_THRESHOLD: u32 = 10; pub fn compute_stability_score(
250 prev_score: u32,
251 regime_changed: bool,
252 reflex_events_delta: u64,
253 p99_ns: u64,
254 p99_ceiling_ns: u64,
255) -> u32 {
256 if regime_changed || reflex_events_delta > 0 || p99_ns > p99_ceiling_ns / 2 {
257 return 0;
258 }
259 (prev_score + 1).min(STABILITY_THRESHOLD)
260}
261
262pub fn should_print_telemetry(tick_counter: u64, stability_score: u32) -> bool {
265 if stability_score >= STABILITY_THRESHOLD {
266 tick_counter % 2 == 0
267 } else {
268 true
269 }
270}
271
272pub const HIST_BUCKETS: usize = 12;
275pub const HIST_EDGES_NS: [u64; HIST_BUCKETS] = [
276 10_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, u64::MAX, ];
289
290pub fn compute_p99_from_histogram(counts: &[u64; HIST_BUCKETS]) -> u64 {
293 let total: u64 = counts.iter().sum();
294 if total == 0 {
295 return 0;
296 }
297 let threshold = (total * 99 + 99) / 100;
298 let mut cumulative = 0u64;
299 for i in 0..HIST_BUCKETS {
300 cumulative += counts[i];
301 if cumulative >= threshold {
302 return HIST_EDGES_NS[i].min(HIST_EDGES_NS[HIST_BUCKETS - 2]);
303 }
304 }
305 HIST_EDGES_NS[HIST_BUCKETS - 2]
306}
307
308const N_EXPERTS: usize = 6;
317const ETA: f64 = 8.0;
318const RELAX_RATE: f64 = 0.80;
319const SPIKE_CONFIRM: u32 = 2;
320const RELAX_HOLD: u32 = 2;
321const RELAX_CEIL_PCT: f64 = 0.70;
322const EQUILIBRIUM: [f64; N_EXPERTS] = [0.08, 0.44, 0.12, 0.12, 0.12, 0.12];
323const WEIGHT_FLOOR: f64 = 1e-6;
324
325const EX_LATENCY: usize = 0;
326const EX_BALANCED: usize = 1;
327const EX_THROUGHPUT: usize = 2;
328const EX_IO_HEAVY: usize = 3;
329const EX_FORK_STORM: usize = 4;
330const EX_SATURATED: usize = 5;
331
332const SC_SLICE: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.49, 1.47];
336const SC_PREEMPT: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.49, 1.47];
337const SC_BATCH: [f64; 6] = [0.78, 1.00, 1.30, 1.30, 0.52, 1.04];
338const SC_LCRI_HI: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.98, 0.98];
339const SC_LCRI_LO: [f64; 6] = [0.70, 1.00, 1.40, 0.93, 0.93, 0.93];
340const SC_SOJOURN: [f64; 6] = [0.80, 1.00, 1.60, 0.93, 0.53, 1.07];
341const SC_BURST: [f64; 6] = [0.74, 1.00, 1.47, 0.98, 0.49, 1.23];
342
343const DV_LAG: [u64; 6] = [6, 4, 3, 4, 4, 3];
345const DV_AFFINITY: [u64; 6] = [
346 AFFINITY_STRONG,
347 AFFINITY_STRONG,
348 AFFINITY_WEAK,
349 AFFINITY_WEAK,
350 AFFINITY_OFF,
351 AFFINITY_WEAK,
352];
353fn blend_continuous(base: u64, scales: &[f64; 6], w: &[f64; N_EXPERTS]) -> u64 {
354 let v: f64 = (0..N_EXPERTS).map(|i| w[i] * base as f64 * scales[i]).sum();
355 (v.round() as u64).max(1)
356}
357
358fn majority_discrete(values: &[u64; 6], w: &[f64; N_EXPERTS]) -> u64 {
359 let mut best_val = values[0];
361 let mut best_w = 0.0f64;
362 for &v in values.iter() {
363 let total: f64 = (0..N_EXPERTS)
364 .filter(|&i| values[i] == v)
365 .map(|i| w[i])
366 .sum();
367 if total > best_w {
368 best_w = total;
369 best_val = v;
370 }
371 }
372 best_val
373}
374
375#[derive(Clone, Copy, PartialEq, Eq, Debug)]
376pub enum IoBucket {
377 Low,
378 Mid,
379 High,
380}
381
382pub fn io_bucket(io_pct: u64) -> IoBucket {
383 if io_pct > 60 {
384 IoBucket::High
385 } else if io_pct < 15 {
386 IoBucket::Low
387 } else {
388 IoBucket::Mid
389 }
390}
391
392pub struct MwuSignals {
393 pub p99_ns: u64,
394 pub interactive_p99_ns: u64,
395 pub io_pct: u64,
396 pub rescue_count: u64,
397 pub wakeup_rate: u64,
398}
399
400#[derive(Clone, Copy, Debug, Default)]
409pub struct OscillatorState {
410 pub codel_target_ns: u64,
411 pub codel_target_floor_ns: u64,
412 pub codel_target_max_ns: u64,
413}
414
415impl OscillatorState {
416 pub fn position(&self) -> f64 {
419 if self.codel_target_max_ns == 0 || self.codel_target_floor_ns >= self.codel_target_max_ns {
420 return 0.5;
421 }
422 let range = (self.codel_target_max_ns - self.codel_target_floor_ns) as f64;
423 let pos = self
424 .codel_target_ns
425 .saturating_sub(self.codel_target_floor_ns) as f64;
426 (pos / range).clamp(0.0, 1.0)
427 }
428}
429
430pub struct MwuController {
431 weights: [f64; N_EXPERTS],
432 baseline: TuningKnobs,
433 spike_streak: u32,
434 healthy_streak: u32,
435 fork_streak: u32,
436 prev_io_bucket: IoBucket,
437 prev_rescuing: bool,
438 losses_applied: bool,
439}
440
441impl MwuController {
442 pub fn new(baseline: TuningKnobs) -> Self {
443 Self {
444 weights: EQUILIBRIUM,
445 baseline,
446 spike_streak: 0,
447 healthy_streak: 0,
448 fork_streak: 0,
449 prev_io_bucket: IoBucket::Mid,
450 prev_rescuing: false,
451 losses_applied: false,
452 }
453 }
454
455 pub fn reset(&mut self) {
456 self.weights = EQUILIBRIUM;
457 self.spike_streak = 0;
458 self.healthy_streak = 0;
459 self.fork_streak = 0;
460 self.prev_io_bucket = IoBucket::Mid;
461 self.prev_rescuing = false;
462 self.losses_applied = false;
463 }
464
465 pub fn set_baseline(&mut self, baseline: TuningKnobs) {
466 self.baseline = baseline;
467 }
468
469 pub fn update(
470 &mut self,
471 sig: &MwuSignals,
472 ceiling: u64,
473 _nr_cpus: u64,
474 tau_ns: u64,
475 osc: &OscillatorState,
476 ) -> TuningKnobs {
477 let worst = sig.p99_ns.max(sig.interactive_p99_ns);
478 let above = worst > ceiling;
479 let below_relax = (worst as f64) < (ceiling as f64 * RELAX_CEIL_PCT);
480
481 let osc_pos = osc.position();
492 let osc_already_tight = osc_pos < 0.40;
493 let osc_already_loose = osc_pos > 0.90;
494 let defer_to_oscillator = osc_already_tight || osc_already_loose;
495
496 let mut losses = [0.0f64; N_EXPERTS];
497 let mut has_loss = false;
498
499 if above {
501 self.healthy_streak = 0;
502 self.spike_streak += 1;
503 if self.spike_streak >= SPIKE_CONFIRM {
504 let v = ((worst - ceiling) as f64 / ceiling as f64).min(3.0);
505 losses[EX_BALANCED] += v * 0.5;
506 losses[EX_THROUGHPUT] += v * 1.0;
507 losses[EX_IO_HEAVY] += v * 0.6;
508 losses[EX_FORK_STORM] += v * 0.3;
509 losses[EX_SATURATED] += v * 0.9;
510 has_loss = true;
511 }
512 } else {
513 self.spike_streak = 0;
514 }
515
516 let rescuing = sig.rescue_count > 0;
522 if rescuing && !self.prev_rescuing && !defer_to_oscillator {
523 let v = (sig.rescue_count as f64 * 1.5).min(3.0);
524 losses[EX_LATENCY] += v * 0.4;
525 losses[EX_THROUGHPUT] += v * 0.6;
526 losses[EX_SATURATED] += v * 0.6;
527 losses[EX_IO_HEAVY] += v * 0.4;
528 losses[EX_BALANCED] += v * 0.2;
529 has_loss = true;
530 }
531 self.prev_rescuing = rescuing;
532
533 let cur_io = io_bucket(sig.io_pct);
535 if cur_io != self.prev_io_bucket {
536 match cur_io {
537 IoBucket::High => {
538 let v = ((sig.io_pct as f64 - 60.0) / 40.0).min(1.0);
539 for i in 0..N_EXPERTS {
540 if i != EX_IO_HEAVY {
541 losses[i] += v * 0.8;
542 }
543 }
544 has_loss = true;
545 }
546 IoBucket::Low => {
547 let v = ((15.0 - sig.io_pct as f64) / 15.0).clamp(0.0, 1.0);
548 losses[EX_IO_HEAVY] += v * 1.0;
549 has_loss = true;
550 }
551 IoBucket::Mid => {}
552 }
553 }
554 self.prev_io_bucket = cur_io;
555
556 let fork_thresh = scale_tau_u64(tau_ns, K_FORK_STORM_RATE_Q16).max(FORK_STORM_RATE_FLOOR);
569 let fork_storm = sig.wakeup_rate > fork_thresh && sig.rescue_count > 0;
570 if fork_storm {
571 self.fork_streak += 1;
572 if self.fork_streak >= SPIKE_CONFIRM && !defer_to_oscillator {
573 let denom = fork_thresh.max(1) as f64;
574 let v = ((sig.wakeup_rate as f64 / denom) - 1.0).clamp(0.0, 3.0);
575 losses[EX_BALANCED] += v * 0.30;
576 losses[EX_THROUGHPUT] += v * 1.00;
577 losses[EX_IO_HEAVY] += v * 0.50;
578 losses[EX_SATURATED] += v * 0.80;
579 has_loss = true;
580 }
581 } else {
582 self.fork_streak = 0;
583 }
584
585 if has_loss {
587 for i in 0..N_EXPERTS {
588 if losses[i] > 0.0 {
589 self.weights[i] *= (-ETA * losses[i]).exp();
590 }
591 if self.weights[i] < WEIGHT_FLOOR {
592 self.weights[i] = WEIGHT_FLOOR;
593 }
594 }
595 let sum: f64 = self.weights.iter().sum();
596 for w in self.weights.iter_mut() {
597 *w /= sum;
598 }
599 }
600
601 if !has_loss && below_relax {
603 self.healthy_streak += 1;
604 if self.healthy_streak >= RELAX_HOLD {
605 for i in 0..N_EXPERTS {
606 self.weights[i] =
607 (1.0 - RELAX_RATE) * self.weights[i] + RELAX_RATE * EQUILIBRIUM[i];
608 }
609 }
610 } else if !has_loss {
611 self.healthy_streak = 0;
612 }
613
614 self.losses_applied = has_loss;
615
616 let b = &self.baseline;
618 let blended_slice = blend_continuous(b.slice_ns, &SC_SLICE, &self.weights);
619 let blended_burst = blend_continuous(b.burst_slice_ns, &SC_BURST, &self.weights);
620 let mut blended_sojourn = blend_continuous(b.sojourn_thresh_ns, &SC_SOJOURN, &self.weights);
621
622 let sojourn_floor = 4_000_000u64.saturating_add(blended_slice);
631 if blended_sojourn < sojourn_floor {
632 blended_sojourn = sojourn_floor;
633 }
634
635 TuningKnobs {
636 slice_ns: blended_slice,
637 preempt_thresh_ns: blend_continuous(b.preempt_thresh_ns, &SC_PREEMPT, &self.weights),
638 lag_scale: majority_discrete(&DV_LAG, &self.weights),
639 batch_slice_ns: blend_continuous(b.batch_slice_ns, &SC_BATCH, &self.weights),
640 lat_cri_thresh_high: blend_continuous(
641 b.lat_cri_thresh_high,
642 &SC_LCRI_HI,
643 &self.weights,
644 ),
645 lat_cri_thresh_low: blend_continuous(b.lat_cri_thresh_low, &SC_LCRI_LO, &self.weights),
646 affinity_mode: majority_discrete(&DV_AFFINITY, &self.weights),
647 sojourn_thresh_ns: blended_sojourn,
648 burst_slice_ns: blended_burst,
649 topology_tau_ns: 0,
653 codel_eq_ns: 0,
654 }
655 }
656
657 pub fn had_losses(&self) -> bool {
658 self.losses_applied
659 }
660
661 pub fn scale(&self) -> f64 {
662 let s: f64 = (0..N_EXPERTS).map(|i| self.weights[i] * SC_SLICE[i]).sum();
663 s
664 }
665}