Nothing to see here, move along meow
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 10 kB View raw
1use crate::cap::pool::POOL; 2use crate::mem::phys::BitmapFrameAllocator; 3use crate::proc::PROCESSES; 4use crate::tests::helpers::{dequeue_ours, destroy_batch_and_verify, spawn_batch_with_sched}; 5use crate::types::Pid; 6use crate::wcet::tsc; 7use lancer_core::object_layout::SchedContextObject; 8 9crate::kernel_test!( 10 fn budget_lifecycle_under_sustained_load() { 11 let baseline = BitmapFrameAllocator::free_frames(); 12 let thread_count = 500usize; 13 let budget_us = 200u64; 14 let period_us = 2000u64; 15 let tick_step = 1000u64; 16 let total_ticks = 200u32; 17 let dispatch_per_tick = 100usize; 18 19 let batch = spawn_batch_with_sched(thread_count, budget_us, period_us, |_| 128); 20 21 let mut cycle_counts = [0u32; 1024]; 22 23 let mut clock = 1_000_000u64; 24 let mut total_exhaustions = 0u64; 25 let mut total_replenishments = 0u64; 26 let mut per_tick_ns = crate::static_vec::StaticVec::<u64, 256>::new(); 27 28 { 29 let mut ptable = PROCESSES.lock(); 30 ptable.timer_seed(clock); 31 } 32 33 (0..total_ticks).for_each(|_| { 34 let tick_start = tsc::read_tsc_fenced(); 35 let mut ptable = PROCESSES.lock(); 36 37 let mut dispatched = crate::static_vec::StaticVec::<Pid, 128>::new(); 38 dequeue_ours(&mut ptable, &batch, dispatch_per_tick, &mut dispatched); 39 40 { 41 let mut pool = POOL.lock_after(&ptable); 42 dispatched.iter().for_each(|&pid| { 43 let (sc_id, sc_gen) = ptable[pid].sched_context().expect("has sched context"); 44 let sc = pool 45 .write_as::<SchedContextObject>(sc_id, sc_gen) 46 .expect("write sc"); 47 crate::sched::context::consume(sc, tick_step, clock); 48 match crate::sched::context::is_exhausted(sc) { 49 true => { 50 let replenish_at = clock + sc.period_us; 51 ptable.timer_insert(pid, replenish_at); 52 total_exhaustions += 1; 53 } 54 false => { 55 ptable.enqueue_ready(pid); 56 } 57 } 58 }); 59 } 60 61 clock += tick_step; 62 63 let mut fired = crate::static_vec::StaticVec::<u32, 128>::new(); 64 ptable.timer_advance(clock, |pid_raw| { 65 fired.push(pid_raw).expect("fired overflow"); 66 }); 67 68 { 69 let mut pool = POOL.lock_after(&ptable); 70 fired.iter().for_each(|&pid_raw| { 71 let pid = Pid::new(pid_raw); 72 let (sc_id, sc_gen) = ptable[pid].sched_context().expect("has sched context"); 73 let sc = pool 74 .write_as::<SchedContextObject>(sc_id, sc_gen) 75 .expect("write sc"); 76 crate::sched::context::replenish(sc, clock); 77 assert!( 78 sc.remaining_us == budget_us, 79 "replenishment didn't restore full budget: got {}", 80 sc.remaining_us, 81 ); 82 ptable.enqueue_ready(pid); 83 84 let idx = batch 85 .pids 86 .iter() 87 .position(|&p| p == pid) 88 .expect("fired pid not in batch"); 89 cycle_counts[idx] += 1; 90 total_replenishments += 1; 91 }); 92 } 93 94 drop(ptable); 95 let tick_end = tsc::read_tsc_fenced(); 96 let _ = per_tick_ns.push(tsc::cycles_to_ns(tick_end.saturating_sub(tick_start))); 97 }); 98 99 let in_flight = total_exhaustions.saturating_sub(total_replenishments); 100 101 let threads_that_cycled = cycle_counts[..thread_count] 102 .iter() 103 .filter(|&&c| c > 0) 104 .count(); 105 106 let min_cycles = cycle_counts[..thread_count] 107 .iter() 108 .copied() 109 .fold(u32::MAX, u32::min); 110 let max_cycles = cycle_counts[..thread_count] 111 .iter() 112 .copied() 113 .fold(0u32, u32::max); 114 let avg_cycles = total_replenishments as u32 / thread_count as u32; 115 116 let max_tick_ns = per_tick_ns.iter().copied().fold(0u64, u64::max); 117 let avg_tick_ns = per_tick_ns.iter().sum::<u64>() / total_ticks as u64; 118 let half = total_ticks as usize / 2; 119 let first_half = per_tick_ns.as_slice()[..half].iter().sum::<u64>() / half as u64; 120 let second_half = per_tick_ns.as_slice()[half..].iter().sum::<u64>() / half as u64; 121 122 crate::kprintln!( 123 "[timer_stress] lifecycle: {}t, {} ticks, exhaust={}, replenish={}, in_flight={}", 124 thread_count, 125 total_ticks, 126 total_exhaustions, 127 total_replenishments, 128 in_flight, 129 ); 130 crate::kprintln!( 131 "[timer_stress] cycles: min={}, max={}, avg={}, coverage={}/{}", 132 min_cycles, 133 max_cycles, 134 avg_cycles, 135 threads_that_cycled, 136 thread_count, 137 ); 138 crate::kprintln!( 139 "[timer_stress] timing: avg={}ns, max={}ns, 1st_half={}ns, 2nd_half={}ns", 140 avg_tick_ns, 141 max_tick_ns, 142 first_half, 143 second_half, 144 ); 145 146 assert!( 147 threads_that_cycled == thread_count, 148 "only {}/{} threads completed a full exhaust/replenish cycle", 149 threads_that_cycled, 150 thread_count, 151 ); 152 153 assert!( 154 max_cycles <= min_cycles * 4 + 1, 155 "unfair scheduling: min {} cycles, max {} cycles ({}x spread)", 156 min_cycles, 157 max_cycles, 158 max_cycles / min_cycles.max(1), 159 ); 160 161 assert!( 162 second_half < first_half * 3 + 1000, 163 "per-tick cost growing: first half {}ns, second half {}ns", 164 first_half, 165 second_half, 166 ); 167 168 assert!( 169 max_tick_ns < 500_000, 170 "worst-case tick {}ns exceeds 500us", 171 max_tick_ns, 172 ); 173 174 destroy_batch_and_verify(&batch, baseline); 175 } 176); 177 178crate::kernel_test!( 179 fn timer_fire_timing_accuracy() { 180 let baseline = BitmapFrameAllocator::free_frames(); 181 let thread_count = 500usize; 182 let batch = spawn_batch_with_sched(thread_count, 50, 500, |_| 128); 183 184 let mut ptable = PROCESSES.lock(); 185 let base_us = 1_000_000u64; 186 ptable.timer_seed(base_us); 187 188 let spacing = 20u64; 189 let tick_step = 10u64; 190 191 let mut our_pids = crate::static_vec::StaticVec::<Pid, 512>::new(); 192 let mut foreign = crate::static_vec::StaticVec::<Pid, 64>::new(); 193 194 core::iter::from_fn(|| ptable.dequeue_highest()) 195 .for_each(|pid| match batch.pids.iter().any(|&p| p == pid) { 196 true => our_pids.push(pid).expect("our_pids overflow"), 197 false => foreign.push(pid).expect("foreign overflow"), 198 }); 199 200 let mut deadlines = crate::static_vec::StaticVec::<(u32, u64), 512>::new(); 201 our_pids.iter().enumerate().for_each(|(i, &pid)| { 202 let deadline = base_us + (i as u64 + 1) * spacing; 203 ptable.timer_insert(pid, deadline); 204 deadlines 205 .push((pid.raw(), deadline)) 206 .expect("deadline vec overflow"); 207 }); 208 209 assert!( 210 deadlines.len() == thread_count, 211 "expected {} timers inserted, got {}", 212 thread_count, 213 deadlines.len(), 214 ); 215 216 let end_us = base_us + (thread_count as u64 + 2) * spacing; 217 let mut fires = crate::static_vec::StaticVec::<(u32, u64), 512>::new(); 218 219 core::iter::successors(Some(base_us + tick_step), |&t| { 220 (t < end_us).then_some(t + tick_step) 221 }) 222 .for_each(|tick| { 223 ptable.timer_advance(tick, |pid_raw| { 224 fires.push((pid_raw, tick)).expect("fire vec overflow"); 225 }); 226 }); 227 228 assert!( 229 fires.len() == thread_count, 230 "lost timers: inserted {}, fired {}", 231 thread_count, 232 fires.len(), 233 ); 234 235 let mut max_drift = 0u64; 236 fires.iter().for_each(|&(pid_raw, actual_tick)| { 237 let expected = deadlines 238 .iter() 239 .find(|&&(id, _)| id == pid_raw) 240 .map(|&(_, d)| d) 241 .expect("fired pid not in deadline set"); 242 243 let delta_at_insert = expected.saturating_sub(base_us); 244 let level = match delta_at_insert { 245 0..64 => 0, 246 64..4096 => 1, 247 4096..262144 => 2, 248 _ => 3, 249 }; 250 let granularity = [1u64, 64, 4096, 262144][level]; 251 252 let drift = actual_tick.saturating_sub(expected); 253 assert!( 254 drift <= granularity + tick_step, 255 "pid {} drift {}us exceeds level-{} bound {}us (expected {}, actual {})", 256 pid_raw, 257 drift, 258 level, 259 granularity + tick_step, 260 expected, 261 actual_tick, 262 ); 263 max_drift = max_drift.max(drift); 264 }); 265 266 crate::kprintln!( 267 "[timer_stress] timing: {} entries, spacing={}us, tick={}us, max_drift={}us", 268 thread_count, 269 spacing, 270 tick_step, 271 max_drift, 272 ); 273 274 foreign 275 .iter() 276 .for_each(|&pid| ptable.enqueue_ready(pid)); 277 batch 278 .pids 279 .iter() 280 .for_each(|&pid| ptable.enqueue_ready(pid)); 281 drop(ptable); 282 283 destroy_batch_and_verify(&batch, baseline); 284 } 285);