response_time_analysis/ros2/
rr.rs

1use crate::fixed_point;
2use crate::supply::SupplyBound;
3use crate::time::{Duration, Service};
4
5use crate::arrival::ArrivalBound;
6use crate::wcet::JobCostModel;
7
8const EPSILON: Duration = Duration::epsilon();
9const EPSILON_SERVICE: Service = Service::in_interval(EPSILON);
10
11/// Relative priority of a polled callback.
12///
13/// Numerically smaller value == higher priority. This corresponds to
14/// the order in which callbacks are registered with the ROS2 runtime
15/// system.
16pub type PolledCallbackPriority = i32;
17
18/// The priority order among polled callbacks.
19///
20/// A numerically smaller value corresponds to higher priority.
21pub fn is_higher_callback_priority_than(
22    a: PolledCallbackPriority,
23    b: PolledCallbackPriority,
24) -> bool {
25    a < b
26}
27
28/// Variants of Def. 1 in the paper.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum CallbackType {
31    /// A timer callback.
32    Timer,
33    /// An event source pseudo-callback.
34    EventSource,
35    /// A polled callback, for which we don't know the priority.
36    PolledUnknownPrio,
37    /// A polled callback, for which we do know the priority.
38    Polled(PolledCallbackPriority),
39}
40
41impl CallbackType {
42    pub fn is_pp(&self) -> bool {
43        matches!(
44            self,
45            CallbackType::PolledUnknownPrio | CallbackType::Polled(_)
46        )
47    }
48}
49
50/// A shallow wrapper type for callbacks to compute the direct and
51/// self-interference bounds needed for the round-robin-aware
52/// analysis (Theorem 2 of [the paper](https://people.mpi-sws.org/~bbb/papers/pdf/rtss21-ros.pdf)).
53///
54/// Note: this is intentionally not the same type as
55/// [bw::Callback][super::bw::Callback], even though they look very
56/// similar, since different assumptions on the wrapped
57/// `arrival_bound` are made.
58pub struct Callback<'a, 'b, AB: ArrivalBound + ?Sized, CM: JobCostModel + ?Sized> {
59    response_time_bound: Duration,
60    arrival_bound: &'a AB,
61    cost_model: &'b CM,
62    kind: CallbackType,
63}
64
65impl<'a, 'b, AB: ArrivalBound + ?Sized, CM: JobCostModel + ?Sized> Callback<'a, 'b, AB, CM> {
66    /// Create a new wrapper around a given arrival model, cost
67    /// model, and assumed response-time bound.
68    ///
69    /// **NB**: the `arrival_bound` should conform to the regular
70    /// propagation rules as given in Equation (7) in the paper, and
71    /// not make any assumptions about busy windows.
72    pub fn new(
73        response_time_bound: Duration,
74        arrival_bound: &'a AB,
75        cost_model: &'b CM,
76        kind: CallbackType,
77    ) -> Callback<'a, 'b, AB, CM> {
78        Callback {
79            response_time_bound,
80            arrival_bound,
81            cost_model,
82            kind,
83        }
84    }
85
86    /// Direct interference bound (see Def. 1 in the paper).
87    fn direct_rbf(
88        &self,
89        interfered_with: &CallbackType,
90        delta: Duration,
91        num_polling_points: usize,
92    ) -> Service {
93        let effective_interval = (delta + self.response_time_bound).saturating_sub(EPSILON);
94        let arrived = self.arrival_bound.number_arrivals(effective_interval);
95        let n = match self.kind {
96            CallbackType::Timer | CallbackType::EventSource => arrived,
97            CallbackType::PolledUnknownPrio => arrived.min(num_polling_points + 1),
98            CallbackType::Polled(inf_prio) => match *interfered_with {
99                CallbackType::Polled(ref_prio) => arrived.min(
100                    num_polling_points
101                        + is_higher_callback_priority_than(inf_prio, ref_prio) as usize,
102                ),
103                _ => arrived.min(num_polling_points + 1),
104            },
105        };
106        self.cost_model.cost_of_jobs(n)
107    }
108
109    /// A bound on the number of self-interfering instances (see Def. 2 in the paper).
110    fn max_self_interfering_instances(&self, delta: Duration) -> usize {
111        let effective_interval = (delta + self.response_time_bound).saturating_sub(EPSILON);
112        self.arrival_bound
113            .number_arrivals(effective_interval)
114            .saturating_sub(1)
115    }
116
117    /// A bound on self-interference.
118    fn self_interference_rbf(&self, delta: Duration) -> Service {
119        self.cost_model
120            .cost_of_jobs(self.max_self_interfering_instances(delta))
121    }
122
123    /// A bound on the marginal execution cost, denoted `\Omega` in Theorem 2.
124    fn marginal_execution_cost(&self, delta: Duration) -> Service {
125        let n = self.max_self_interfering_instances(delta);
126        self.cost_model.cost_of_jobs(n + 1) - self.cost_model.cost_of_jobs(n)
127    }
128
129    /// A bound on the maximum number of polling points "incurred"
130    /// (see Def. 3 in the paper).
131    fn polling_point_bound(&self) -> usize {
132        self.arrival_bound.number_arrivals(self.response_time_bound)
133    }
134
135    /// The total polling-point bound of a subchain (i.e., a sequence of
136    /// callbacks). The subchain is expressed as a slice of callback
137    /// references. See Def. 3 in the paper.
138    fn subchain_polling_point_bound(subchain: &[&Callback<AB, CM>]) -> usize {
139        subchain.iter().map(|cb| cb.polling_point_bound()).sum()
140    }
141}
142
143/// Round-robin-aware chain analysis (see Theorem 2 in [the paper](https://people.mpi-sws.org/~bbb/papers/pdf/rtss21-ros.pdf)).
144///
145/// # Parameters
146///
147/// - `supply`: the supply model of the shared executor
148/// - `workload`: all callbacks served by the shared executor
149/// - `subchain`: the subchain under analysis --- each reference must
150///               be unique and point to one of the callbacks in `workload`
151/// - `limit`: the divergence threshold at which the search for a
152///   fixed point is aborted
153///
154/// If no fixed point is found below the divergence limit given by
155/// `limit`, return a [SearchFailure][fixed_point::SearchFailure]
156/// instead.
157#[allow(non_snake_case)]
158pub fn rta_subchain<SBF, AB, CM>(
159    supply: &SBF,
160    workload: &[Callback<AB, CM>],
161    subchain: &[&Callback<AB, CM>],
162    limit: Duration,
163) -> fixed_point::SearchResult
164where
165    SBF: SupplyBound + ?Sized,
166    AB: ArrivalBound + ?Sized,
167    CM: JobCostModel + ?Sized,
168{
169    // callback at the end of the chain under analysis
170    let eoc = subchain.last().expect("subchain must not be empty");
171
172    // check that we are actually given a proper subchain, i.e., all references
173    // in the subchain must point to something in the workload
174    debug_assert!(
175        subchain
176            .iter()
177            .all(|sc_cb| workload.iter().any(|wl_cb| std::ptr::eq(*sc_cb, wl_cb))),
178        "subchain not wholly part of workload"
179    );
180
181    // Step 1: find the fixed point S*.
182
183    // compute a bound on the maximum number of polling points in the analysis window
184    let max_num_polling_points = Callback::subchain_polling_point_bound(subchain);
185
186    let rhs_S_star = |s_star: Duration| {
187        let di = workload
188            .iter()
189            .map(|cb| {
190                if std::ptr::eq(*eoc, cb) {
191                    // Don't count the end of the chain, which is
192                    // accounted for as self-interference.
193                    Service::none()
194                } else {
195                    cb.direct_rbf(&eoc.kind, s_star, max_num_polling_points)
196                }
197            })
198            .sum();
199        let si = eoc.self_interference_rbf(s_star);
200        EPSILON_SERVICE + di + si
201    };
202
203    let S_star = fixed_point::search(supply, limit, rhs_S_star)?;
204
205    // Step 2: find the response-time bound R*.
206
207    let supply_star = supply.provided_service(S_star);
208    let omega = eoc.marginal_execution_cost(S_star);
209    let rhs_R_star = supply_star.saturating_sub(EPSILON_SERVICE) + omega;
210
211    // we can directly solve the inequality
212    Ok(supply.service_time(rhs_R_star))
213}