Nothing to see here, move along meow
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);