2025-09-11 14:45:41 +02:00

195 lines
6.1 KiB
Rust

//! Basic side-by-side example of the Earliest Deadline First scheduler
//!
//! This test spawns a number of background "ambient system load" workers
//! that are constantly working, and runs two sets of trials.
//!
//! The first trial runs with no deadline set, so our trial task is at the
//! same prioritization level as the background worker tasks.
//!
//! The second trial sets a deadline, meaning that it will be given higher
//! scheduling priority than background tasks, that have no deadline set
#![no_std]
#![no_main]
use core::sync::atomic::{compiler_fence, Ordering};
use defmt::unwrap;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(spawner: Spawner) {
embassy_nrf::init(Default::default());
// Enable flash cache to remove some flash latency jitter
compiler_fence(Ordering::SeqCst);
embassy_nrf::pac::NVMC.icachecnf().write(|w| {
w.set_cacheen(true);
});
compiler_fence(Ordering::SeqCst);
//
// Baseline system load tunables
//
// how many load tasks? More load tasks means more tasks contending
// for the runqueue
let tasks = 32;
// how long should each task work for? The longer the working time,
// the longer the max jitter possible, even when a task is prioritized,
// as EDF is still cooperative and not pre-emptive
//
// 33 ticks ~= 1ms
let work_time_ticks = 33;
// what fraction, 1/denominator, should the system be busy?
// bigger number means **less** busy
//
// 2 => 50%
// 4 => 25%
// 10 => 10%
let denominator = 2;
// Total time window, so each worker is working 1/denominator
// amount of the total time
let time_window = work_time_ticks * u64::from(tasks) * denominator;
// Spawn all of our load workers!
for i in 0..tasks {
spawner.spawn(unwrap!(load_task(i, work_time_ticks, time_window)));
}
// Let all the tasks spin up
defmt::println!("Spinning up load tasks...");
Timer::after_secs(1).await;
//
// Trial task worker tunables
//
// How many steps should the workers under test run?
// More steps means more chances to have to wait for other tasks
// in line ahead of us.
let num_steps = 100;
// How many ticks should the worker take working on each step?
//
// 33 ticks ~= 1ms
let work_ticks = 33;
// How many ticks should the worker wait on each step?
//
// 66 ticks ~= 2ms
let idle_ticks = 66;
// How many times to repeat each trial?
let trials = 3;
// The total time a trial would take, in a perfect unloaded system
let theoretical = (num_steps * work_ticks) + (num_steps * idle_ticks);
defmt::println!("");
defmt::println!("Starting UNPRIORITIZED worker trials");
for _ in 0..trials {
//
// UNPRIORITIZED worker
//
defmt::println!("");
defmt::println!("Starting unprioritized worker");
let start = Instant::now();
for _ in 0..num_steps {
let now = Instant::now();
while now.elapsed().as_ticks() < work_ticks {}
Timer::after_ticks(idle_ticks).await;
}
let elapsed = start.elapsed().as_ticks();
defmt::println!(
"Trial complete, theoretical ticks: {=u64}, actual ticks: {=u64}",
theoretical,
elapsed
);
let ratio = ((elapsed as f32) / (theoretical as f32)) * 100.0;
defmt::println!("Took {=f32}% of ideal time", ratio);
Timer::after_millis(500).await;
}
Timer::after_secs(1).await;
defmt::println!("");
defmt::println!("Starting PRIORITIZED worker trials");
for _ in 0..trials {
//
// PRIORITIZED worker
//
defmt::println!("");
defmt::println!("Starting prioritized worker");
let start = Instant::now();
// Set the deadline to ~2x the theoretical time. In practice, setting any deadline
// here elevates the current task above all other worker tasks.
let meta = embassy_executor::Metadata::for_current_task().await;
meta.set_deadline_after(theoretical * 2);
// Perform the trial
for _ in 0..num_steps {
let now = Instant::now();
while now.elapsed().as_ticks() < work_ticks {}
Timer::after_ticks(idle_ticks).await;
}
let elapsed = start.elapsed().as_ticks();
defmt::println!(
"Trial complete, theoretical ticks: {=u64}, actual ticks: {=u64}",
theoretical,
elapsed
);
let ratio = ((elapsed as f32) / (theoretical as f32)) * 100.0;
defmt::println!("Took {=f32}% of ideal time", ratio);
// Unset the deadline, deadlines are not automatically cleared, and if our
// deadline is in the past, then we get very high priority!
meta.unset_deadline();
Timer::after_millis(500).await;
}
defmt::println!("");
defmt::println!("Trials Complete.");
}
#[embassy_executor::task(pool_size = 32)]
async fn load_task(id: u32, ticks_on: u64, ttl_ticks: u64) {
let mut last_print = Instant::now();
let mut last_tick = last_print;
let mut variance = 0;
let mut max_variance = 0;
loop {
let tgt = last_tick + Duration::from_ticks(ttl_ticks);
assert!(tgt > Instant::now(), "fell too behind!");
Timer::at(tgt).await;
let now = Instant::now();
// How late are we from the target?
let var = now.duration_since(tgt).as_ticks();
max_variance = max_variance.max(var);
variance += var;
// blocking work
while now.elapsed().as_ticks() < ticks_on {}
if last_print.elapsed() >= Duration::from_secs(1) {
defmt::trace!(
"Task {=u32} variance ticks (1s): {=u64}, max: {=u64}, act: {=u64}",
id,
variance,
max_variance,
ticks_on,
);
max_variance = 0;
variance = 0;
last_print = Instant::now();
}
last_tick = tgt;
}
}