rta_for_fps_lib/
task.rs

1//! Module for the Task definition
2
3use crate::curve::{AggregateExt, Curve};
4use crate::iterators::curve::CurveDeltaIterator;
5use crate::iterators::task::TaskDemandIterator;
6use crate::iterators::{CurveIterator, ReclassifyIterator};
7use crate::server::ActualServerExecution;
8use crate::system::System;
9use crate::task::curve_types::{
10    ActualTaskExecution, AvailableTaskExecution, HigherPriorityTaskDemand,
11};
12use crate::time::{TimeUnit, UnitNumber};
13use crate::window::WindowEnd;
14use crate::window::{Demand, Window};
15
16pub mod curve_types {
17    //! Module for `CurveType`s of a Task
18
19    /// Marker Type for Curves representing a Tasks Demand
20    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
21    pub struct TaskDemand;
22
23    /// Mark Type for Curves representing aggregated higher priority task demand
24    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
25    pub struct HigherPriorityTaskDemand;
26
27    /// Marker type for Curves representing the available execution for a task
28    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
29    pub struct AvailableTaskExecution;
30
31    /// Marker type for Curves representing the actual execution for a task
32    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
33    pub struct ActualTaskExecution;
34}
35
36/// The Task type based on the Modeling described in the second paragraph
37/// of Chapter 3. in the paper
38#[derive(Debug, Clone, Copy)]
39pub struct Task {
40    /// The offset of the tasks, O index i in the paper
41    pub offset: TimeUnit,
42    /// The demand induced by the task
43    /// called the worst-case execution time (WCET) C index i in the paper
44    pub demand: TimeUnit,
45    /// The interval of the task, called Period P index i in the paper
46    pub interval: TimeUnit,
47}
48
49impl Task {
50    /// Create a new Task with the corresponding parameters
51    ///
52    /// # Panics
53    /// If the interval is shorter than the demand
54    #[must_use]
55    pub fn new<I: Into<TimeUnit>>(demand: I, interval: I, offset: I) -> Self {
56        let demand = demand.into();
57        let interval = interval.into();
58
59        if interval < demand {
60            panic!("Task can't have an interval shorter than its demand!")
61        }
62
63        Task {
64            offset: offset.into(),
65            demand,
66            interval,
67        }
68    }
69
70    /// calculate the Higher Priority task Demand for the task with priority `index` as defined in Definition 14. (1) in the paper,
71    /// for a set of tasks indexed by their priority (lower index <=> higher priority) and up to the specified limit
72    #[must_use]
73    pub fn higher_priority_task_demand_iter(
74        tasks: &[Self],
75        index: usize,
76    ) -> impl CurveIterator<CurveKind = HigherPriorityTaskDemand> + Clone + '_ {
77        tasks[..index]
78            .iter()
79            .map(|task| task.into_iter())
80            .aggregate::<ReclassifyIterator<_, _>>()
81    }
82
83    /// Calculate the available execution Curve for the task with priority `task_index` of the server with priority `server_index`
84    /// up to the specified limit.
85    ///
86    /// Based on Definition 14. (2) of the paper
87    #[must_use]
88    pub fn available_execution_curve_impl<'a, HPTD, ASEC>(
89        constrained_server_execution_curve: ASEC,
90        higher_priority_task_demand: HPTD,
91    ) -> impl CurveIterator<CurveKind = AvailableTaskExecution> + Clone + 'a
92    where
93        HPTD: CurveIterator<CurveKind = HigherPriorityTaskDemand> + Clone + 'a,
94        ASEC: CurveIterator<CurveKind = ActualServerExecution> + Clone + 'a,
95    {
96        let delta = CurveDeltaIterator::new(
97            constrained_server_execution_curve,
98            higher_priority_task_demand,
99        );
100
101        delta
102            .remaining_supply()
103            .reclassify::<AvailableTaskExecution>()
104    }
105
106    /// Calculate the actual execution Curve for the Task with priority `task_index` of the Server with priority `server_index`
107    /// up to the specified limit.
108    ///
109    /// Based on Definition 14. (3) of the paper
110    #[must_use]
111    pub fn original_actual_execution_curve_iter<'a>(
112        system: &'a System,
113        server_index: usize,
114        task_index: usize,
115    ) -> impl CurveIterator<CurveKind = ActualTaskExecution> + Clone + 'a {
116        let asec = system.original_actual_execution_curve_iter(server_index);
117        let hptd = Task::higher_priority_task_demand_iter(
118            system.as_servers()[server_index].as_tasks(),
119            task_index,
120        );
121
122        let available_execution_curve = Task::available_execution_curve_impl(asec, hptd);
123
124        let task_demand_curve =
125            system.as_servers()[server_index].as_tasks()[task_index].into_iter();
126
127        CurveDeltaIterator::new(available_execution_curve, task_demand_curve)
128            .overlap::<ActualTaskExecution>()
129    }
130
131    #[must_use]
132    pub fn fixed_actual_execution_curve_iter<'a>(
133        system: &'a System,
134        server_index: usize,
135        task_index: usize,
136    ) -> impl CurveIterator<CurveKind = ActualTaskExecution> + Clone + 'a {
137        let asec = system.fixed_actual_execution_curve_iter(server_index);
138        let hptd = Task::higher_priority_task_demand_iter(
139            system.as_servers()[server_index].as_tasks(),
140            task_index,
141        );
142
143        let available_execution_curve = Task::available_execution_curve_impl(asec, hptd);
144
145        let task_demand_curve =
146            system.as_servers()[server_index].as_tasks()[task_index].into_iter();
147
148        CurveDeltaIterator::new(available_execution_curve, task_demand_curve)
149            .overlap::<ActualTaskExecution>()
150    }
151
152    /// Calculate the WCRT for the task with priority `task_index` for the Server with priority `server_index`
153    ///
154    /// See definition 15. of the paper for reference
155    ///
156    /// Takes the system of servers that the task which worst case execution time shall be calculated is part of
157    /// the priority/index of the server the Task belongs to
158    /// and the tasks priority/index in that server
159    /// as well as the time till which jobs that arrive prior shall be considered for the analysis
160    ///
161    /// # Panics
162    /// When sanity checks fail
163    #[must_use]
164    pub fn original_worst_case_response_time(
165        system: &System,
166        server_index: usize,
167        task_index: usize,
168        arrival_before: TimeUnit,
169    ) -> TimeUnit {
170        let swh = arrival_before;
171
172        let actual_execution_time_iter =
173            Task::original_actual_execution_curve_iter(system, server_index, task_index);
174
175        let task = &system.as_servers()[server_index].as_tasks()[task_index];
176
177        // arrival of the last job that starts before the swh
178        let last_job = (swh - task.offset - TimeUnit::ONE) / task.interval;
179
180        let total_execution = (last_job + 1) * task.demand;
181        let mut provided = WindowEnd::Finite(TimeUnit::ZERO);
182
183        let actual_execution_time: Curve<_> = actual_execution_time_iter
184            .take_while_curve(|task| {
185                let take = provided < total_execution;
186                provided += task.length();
187                take
188            })
189            .collect_curve();
190
191        // sanity check that last_job arrival is before swh
192        assert!(
193            task.job_arrival(last_job) < swh,
194            "Last job should arrive before the system wide hyper period"
195        );
196
197        // sanity check that job after last_job is not before swh
198        assert!(
199            swh <= task.job_arrival(last_job + 1),
200            "The job after the last job would arrive after or at the system wide hyper period"
201        );
202
203        assert!(
204            WindowEnd::Finite((last_job + 1) * task.demand) <= actual_execution_time.capacity(),
205            "There should be enough capacity for the last job"
206        );
207
208        (0..=last_job)
209            .into_iter()
210            .map(|job| {
211                let arrival = task.job_arrival(job);
212                let t = (job + 1) * task.demand;
213
214                Task::time_to_provide(&actual_execution_time, t) - arrival
215            })
216            .max()
217            .unwrap_or(TimeUnit::ZERO)
218    }
219
220    #[must_use]
221    pub fn fixed_worst_case_response_time(
222        system: &System,
223        server_index: usize,
224        task_index: usize,
225        arrival_before: TimeUnit,
226    ) -> TimeUnit {
227        let swh = arrival_before;
228
229        let actual_execution_time_iter =
230            Task::fixed_actual_execution_curve_iter(system, server_index, task_index);
231
232        let task = &system.as_servers()[server_index].as_tasks()[task_index];
233
234        // arrival of the last job that starts before the swh
235        let last_job = (swh - task.offset - TimeUnit::ONE) / task.interval;
236
237        let total_execution = (last_job + 1) * task.demand;
238        let mut provided = WindowEnd::Finite(TimeUnit::ZERO);
239
240        let actual_execution_time: Curve<_> = actual_execution_time_iter
241            .take_while_curve(|task| {
242                let take = provided < total_execution;
243                provided += task.length();
244                take
245            })
246            .collect_curve();
247
248        // sanity check that last_job arrival is before swh
249        assert!(
250            task.job_arrival(last_job) < swh,
251            "Last job should arrive before the system wide hyper period"
252        );
253
254        // sanity check that job after last_job is not before swh
255        assert!(
256            swh <= task.job_arrival(last_job + 1),
257            "The job after the last job would arrive after or at the system wide hyper period"
258        );
259
260        assert!(
261            WindowEnd::Finite((last_job + 1) * task.demand) <= actual_execution_time.capacity(),
262            "There should be enough capacity for the last job"
263        );
264
265        (0..=last_job)
266            .into_iter()
267            .map(|job| {
268                let arrival = task.job_arrival(job);
269                let t = (job + 1) * task.demand;
270
271                Task::time_to_provide(&actual_execution_time, t) - arrival
272            })
273            .max()
274            .unwrap_or(TimeUnit::ZERO)
275    }
276
277    /// Calculate the time till the execution curve has served t Units of Demand
278    /// Implementing Algorithm 5. form the paper
279    ///
280    /// # Panics
281    /// When the capacity of the curve is less than t
282    /// or t is [`TimeUnit::ZERO`]
283    #[must_use]
284    pub fn time_to_provide(
285        actual_execution_time: &Curve<ActualTaskExecution>,
286        t: TimeUnit,
287    ) -> TimeUnit {
288        // Note: paper lists wants to find largest index k with the sum of the windows 0..=k < t
289        // but when calculating k the sum skips 0
290        // finding the largest index k with the sum of the windows 1..=k < t
291        // this appears to be a mix-up between 0-based and 1-based indexing and
292        // is therefore not replicated in this implementation
293
294        // (1)
295        // index here is exclusive aka. k+1 as appose to inclusive as in the paper
296        let (index, sum) = actual_execution_time
297            .as_windows()
298            .iter()
299            .enumerate()
300            .scan(TimeUnit::ZERO, |acc, (index, window)| {
301                match window.length() {
302                    WindowEnd::Finite(length) => {
303                        *acc += length;
304                        (*acc < t).then(|| (index + 1, *acc))
305                    }
306                    WindowEnd::Infinite => None,
307                }
308            })
309            .last()
310            .unwrap_or((0, TimeUnit::ZERO));
311
312        // (2)
313        let b = t - sum;
314
315        // this should hold as sum is the largest sum of head window lengths less than t
316        debug_assert!(
317            b > TimeUnit::ZERO,
318            "There should be remaining demand, but b = {:?}",
319            b
320        );
321
322        actual_execution_time.as_windows()[index].start + b
323    }
324
325    /// Calculate the arrival for the job_index+1-th job
326    ///
327    /// Note: The paper uses 1-index for jobs while this uses 0-index
328    #[must_use]
329    pub fn job_arrival(&self, job_index: UnitNumber) -> TimeUnit {
330        self.offset + job_index * self.interval
331    }
332}
333
334impl IntoIterator for Task {
335    type Item = Window<Demand>;
336    type IntoIter = TaskDemandIterator;
337
338    /// Generate the Demand Curve for the Task
339    ///
340    /// Based on Definition 9. and 10. of the paper
341    fn into_iter(self) -> Self::IntoIter {
342        TaskDemandIterator::new(self)
343    }
344}