use core::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub struct Task {
pub id: String,
pub period: Duration,
pub deadline: Duration,
pub wcet: Duration,
pub priority: Option<u32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TaskSet {
pub tasks: Vec<Task>,
pub scheduler: SchedulerType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchedulerType {
RateMonotonic,
EarliestDeadlineFirst,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SchedulabilityResult {
pub scheduler: SchedulerType,
pub is_schedulable: bool,
pub utilization: f64,
pub utilization_bound: f64,
pub violations: Vec<TaskViolationDetail>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaskViolationDetail {
pub task_id: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SchedulabilityError {
MissingPeriod(String),
InvalidTaskSet(String),
}
impl core::fmt::Display for SchedulabilityError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SchedulabilityError::MissingPeriod(event) => {
write!(f, "Missing period for event: {event}")
}
SchedulabilityError::InvalidTaskSet(msg) => {
write!(f, "Invalid task set: {msg}")
}
}
}
}
fn calculate_utilization(task_set: &TaskSet) -> Result<f64, SchedulabilityError> {
if task_set.tasks.is_empty() {
return Err(SchedulabilityError::InvalidTaskSet("Empty task set".to_string()));
}
let utilization: f64 = task_set
.tasks
.iter()
.map(|t| {
let ci = t.wcet.as_secs_f64();
let ti = t.period.as_secs_f64();
if ti == 0.0 {
return f64::INFINITY;
}
ci / ti
})
.sum();
Ok(utilization)
}
fn create_utilization_violation(utilization: f64, bound: f64) -> TaskViolationDetail {
TaskViolationDetail {
task_id: "system".to_string(),
message: format!("Utilization {utilization} exceeds bound {bound}"),
}
}
pub fn is_rm_schedulable(task_set: &TaskSet) -> Result<SchedulabilityResult, SchedulabilityError> {
let scheduler = SchedulerType::RateMonotonic;
let utilization = calculate_utilization(task_set)?;
let n = task_set.tasks.len() as f64;
let utilization_bound = if n == 1.0 {
1.0
} else {
n * (2f64.powf(1.0 / n) - 1.0)
};
let is_schedulable = utilization <= utilization_bound;
let mut violations = Vec::new();
if !is_schedulable {
violations.push(create_utilization_violation(utilization, utilization_bound));
}
Ok(SchedulabilityResult { scheduler, is_schedulable, utilization, utilization_bound, violations })
}
pub fn is_edf_schedulable(task_set: &TaskSet) -> Result<SchedulabilityResult, SchedulabilityError> {
let scheduler = SchedulerType::EarliestDeadlineFirst;
let utilization = calculate_utilization(task_set)?;
let utilization_bound = 1.0;
let is_schedulable = utilization <= utilization_bound;
let mut violations = Vec::new();
if !is_schedulable {
violations.push(create_utilization_violation(utilization, utilization_bound));
}
Ok(SchedulabilityResult { scheduler, is_schedulable, utilization, utilization_bound, violations })
}
pub fn response_time_analysis(task_set: &TaskSet) -> Result<Vec<Duration>, SchedulabilityError> {
if task_set.tasks.is_empty() {
return Err(SchedulabilityError::InvalidTaskSet("Empty task set".to_string()));
}
let mut tasks = task_set.tasks.clone();
match task_set.scheduler {
SchedulerType::RateMonotonic => {
tasks.sort_by(|a, b| {
a.period.cmp(&b.period)
});
}
SchedulerType::EarliestDeadlineFirst => {
tasks.sort_by(|a, b| {
a.deadline.cmp(&b.deadline)
});
}
}
let mut response_times = Vec::new();
for (i, task) in tasks.iter().enumerate() {
let mut r = task.wcet;
let mut prev_r = Duration::ZERO;
let max_iterations = 1000;
let mut iterations = 0;
while r != prev_r && r <= task.deadline && iterations < max_iterations {
prev_r = r;
iterations += 1;
let mut interference = Duration::ZERO;
for higher_priority_task in &tasks[..i] {
let r_nanos = r.as_nanos() as f64;
let t_nanos = higher_priority_task.period.as_nanos() as f64;
if t_nanos > 0.0 {
let ceil = (r_nanos / t_nanos).ceil() as u64;
interference += higher_priority_task.wcet * ceil as u32;
}
}
r = task.wcet + interference;
}
response_times.push(r);
}
Ok(response_times)
}
#[cfg(test)]
mod tests {
use super::*;
struct SchedulabilityTestCase {
tasks: &'static [(&'static str, u64, u64, u64, Option<u32>)],
scheduler: SchedulerType,
expected_schedulable: bool,
expected_utilization: Option<f64>,
}
struct ResponseTimeTestCase {
tasks: &'static [(&'static str, u64, u64, u64, Option<u32>)],
scheduler: SchedulerType,
expected_response_times: &'static [u64], }
fn create_task_set_from_data(
tasks_data: &[(&'static str, u64, u64, u64, Option<u32>)],
scheduler: SchedulerType,
) -> TaskSet {
let tasks: Vec<Task> = tasks_data
.iter()
.map(|(id, period_ms, deadline_ms, wcet_ms, priority)| Task {
id: id.to_string(),
period: Duration::from_millis(*period_ms),
deadline: Duration::from_millis(*deadline_ms),
wcet: Duration::from_millis(*wcet_ms),
priority: *priority,
})
.collect();
TaskSet { tasks, scheduler }
}
fn run_rm_test_case(case: &SchedulabilityTestCase) -> Result<(), SchedulabilityError> {
let task_set = create_task_set_from_data(case.tasks, case.scheduler);
let result = is_rm_schedulable(&task_set)?;
assert_eq!(result.is_schedulable, case.expected_schedulable);
if let Some(expected_util) = case.expected_utilization {
assert!((result.utilization - expected_util).abs() < 0.01);
}
Ok(())
}
fn run_edf_test_case(case: &SchedulabilityTestCase) -> Result<(), SchedulabilityError> {
let task_set = create_task_set_from_data(case.tasks, case.scheduler);
let result = is_edf_schedulable(&task_set)?;
assert_eq!(result.is_schedulable, case.expected_schedulable);
if let Some(expected_util) = case.expected_utilization {
assert!((result.utilization - expected_util).abs() < 0.01);
}
Ok(())
}
fn run_rta_test_case(case: &ResponseTimeTestCase) -> Result<(), SchedulabilityError> {
let task_set = create_task_set_from_data(case.tasks, case.scheduler);
let response_times = response_time_analysis(&task_set)?;
assert_eq!(response_times.len(), case.expected_response_times.len());
for (actual, expected_ms) in response_times.iter().zip(case.expected_response_times.iter()) {
assert_eq!(*actual, Duration::from_millis(*expected_ms));
}
Ok(())
}
const RMA_TEST_CASES: &[SchedulabilityTestCase] = &[
SchedulabilityTestCase {
tasks: &[("T1", 10, 10, 3, Some(1)), ("T2", 20, 20, 5, Some(2))],
scheduler: SchedulerType::RateMonotonic,
expected_schedulable: true,
expected_utilization: Some(0.55),
},
SchedulabilityTestCase {
tasks: &[("T1", 10, 10, 8, Some(1)), ("T2", 20, 20, 5, Some(2))],
scheduler: SchedulerType::RateMonotonic,
expected_schedulable: false,
expected_utilization: Some(1.05),
},
];
const EDF_TEST_CASES: &[SchedulabilityTestCase] = &[
SchedulabilityTestCase {
tasks: &[("T1", 10, 10, 3, None), ("T2", 20, 20, 5, None)],
scheduler: SchedulerType::EarliestDeadlineFirst,
expected_schedulable: true,
expected_utilization: Some(0.55),
},
SchedulabilityTestCase {
tasks: &[("T1", 10, 10, 6, None), ("T2", 20, 20, 5, None)],
scheduler: SchedulerType::EarliestDeadlineFirst,
expected_schedulable: true,
expected_utilization: Some(0.85),
},
SchedulabilityTestCase {
tasks: &[("T1", 10, 10, 7, None), ("T2", 20, 20, 5, None)],
scheduler: SchedulerType::EarliestDeadlineFirst,
expected_schedulable: true,
expected_utilization: Some(0.95),
},
SchedulabilityTestCase {
tasks: &[("T1", 10, 10, 8, None), ("T2", 20, 20, 5, None)],
scheduler: SchedulerType::EarliestDeadlineFirst,
expected_schedulable: false,
expected_utilization: Some(1.05),
},
];
const RTA_TEST_CASES: &[ResponseTimeTestCase] = &[ResponseTimeTestCase {
tasks: &[("T1", 10, 10, 3, Some(1)), ("T2", 20, 20, 5, Some(2))],
scheduler: SchedulerType::RateMonotonic,
expected_response_times: &[3, 8],
}];
#[test]
fn test_rm_schedulability() -> Result<(), SchedulabilityError> {
for case in RMA_TEST_CASES {
run_rm_test_case(case)?;
}
Ok(())
}
#[test]
fn test_edf_schedulability() -> Result<(), SchedulabilityError> {
for case in EDF_TEST_CASES {
run_edf_test_case(case)?;
}
Ok(())
}
#[test]
fn test_response_time_analysis() -> Result<(), SchedulabilityError> {
for case in RTA_TEST_CASES {
run_rta_test_case(case)?;
}
Ok(())
}
}