msr_core/control/
cyclic.rs

1use std::{
2    fmt,
3    time::{Duration, Instant},
4};
5
6use crate::register;
7
8pub type CycleIdValue = u16;
9
10/// Numeric identifier of a control cycle
11///
12/// Periodic control cycles are usually distinguished by their
13/// frequency. This identifier allows to reference control cycles
14/// independent of their actual properties.
15#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
16pub struct CycleId(CycleIdValue);
17
18impl CycleId {
19    #[must_use]
20    pub const fn from_value(value: CycleIdValue) -> Self {
21        Self(value)
22    }
23
24    #[must_use]
25    pub const fn to_value(self) -> CycleIdValue {
26        self.0
27    }
28}
29
30impl From<CycleIdValue> for CycleId {
31    fn from(from: CycleIdValue) -> Self {
32        Self::from_value(from)
33    }
34}
35
36impl From<CycleId> for CycleIdValue {
37    fn from(from: CycleId) -> Self {
38        from.to_value()
39    }
40}
41
42impl fmt::Display for CycleId {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "@{}", self.to_value())
45    }
46}
47
48#[derive(Debug, Clone, Eq, PartialEq)]
49pub struct CycleTimeStamp {
50    pub id: CycleId,
51
52    pub ts: Instant,
53}
54
55impl CycleTimeStamp {
56    #[must_use]
57    pub fn now(id: CycleId) -> Self {
58        Self {
59            id,
60            ts: Instant::now(),
61        }
62    }
63}
64
65#[derive(Debug, Clone)]
66pub struct CyclicRegisterMeasurements<RegisterValue> {
67    pub cycle_id: CycleId,
68
69    /// The cycle during which the measurements have been collected
70    pub cycle_ts: CycleTimeStamp,
71
72    /// Measurements for a set of registers
73    ///
74    /// Each register is supposed to appear at most once in the
75    /// vector!
76    pub registers: Vec<register::IndexedMeasurement<RegisterValue>>,
77}
78
79impl<RegisterValue> CyclicRegisterMeasurements<RegisterValue> {
80    #[must_use]
81    pub fn count_number_unique_of_registers(&self) -> usize {
82        let mut register_indices: Vec<_> = self.registers.iter().map(|m| m.index).collect();
83        register_indices.sort_unstable();
84        register_indices.dedup();
85        register_indices.len()
86    }
87
88    #[must_use]
89    pub fn contains_duplicate_registers(&self) -> bool {
90        self.registers.len() > self.count_number_unique_of_registers()
91    }
92}
93
94/// Adjust the expected cycle start time by skipping missed cycles
95///
96/// Adjust the expected start time of the current cycle to the
97/// deadline of the previous cycle depending on the cycle time.
98///
99/// The function either returns the unmodified expected start time
100/// or otherwise the adjusted expected start time together with the
101/// number of missed cycles that have been skipped.
102pub fn skip_missed_cycles(
103    cycle_time: Duration,
104    expected_cycle_start: Instant,
105    actual_cycle_start: Instant,
106) -> Result<Instant, (Instant, u32)> {
107    debug_assert!(cycle_time > Duration::ZERO);
108    if expected_cycle_start >= actual_cycle_start {
109        return Ok(expected_cycle_start);
110    }
111    let elapsed_cycles = actual_cycle_start
112        .duration_since(expected_cycle_start)
113        .as_secs_f64()
114        / cycle_time.as_secs_f64();
115    debug_assert!(elapsed_cycles > 0.0);
116    if elapsed_cycles < 1.0 {
117        return Ok(expected_cycle_start);
118    }
119    // We missed at least 1 entire cycle
120    let missed_cycles = elapsed_cycles.floor();
121    debug_assert!(missed_cycles <= f64::from(u32::MAX));
122    #[allow(clippy::cast_sign_loss)]
123    let missed_cycles = missed_cycles.min(f64::from(u32::MAX)) as u32;
124    // Adjust the deadline of the previous cycle
125    let skipped_cycles_duration = missed_cycles * cycle_time;
126    Err((
127        expected_cycle_start + skipped_cycles_duration,
128        missed_cycles,
129    ))
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    #[allow(clippy::too_many_lines)] // TODO
138    fn should_skip_missed_cycles() {
139        let cycle_time = Duration::from_millis(2);
140        let half_cycle_time = cycle_time / 2;
141        let quarter_cycle_time = cycle_time / 4;
142        let three_quarter_cycle_time = half_cycle_time + quarter_cycle_time;
143        let actual_cycle_start = Instant::now();
144
145        // Exact
146        assert_eq!(
147            Ok(actual_cycle_start),
148            skip_missed_cycles(cycle_time, actual_cycle_start, actual_cycle_start),
149        );
150
151        // Earlier than expected
152        assert_eq!(
153            Ok(actual_cycle_start + 100 * cycle_time),
154            skip_missed_cycles(
155                cycle_time,
156                actual_cycle_start + 100 * cycle_time,
157                actual_cycle_start
158            ),
159        );
160
161        // Less than 1 cycle later as expected
162        assert_eq!(
163            Ok(actual_cycle_start
164                .checked_sub(Duration::from_nanos(1))
165                .unwrap()),
166            skip_missed_cycles(
167                cycle_time,
168                actual_cycle_start
169                    .checked_sub(Duration::from_nanos(1))
170                    .unwrap(),
171                actual_cycle_start
172            ),
173        );
174        assert_eq!(
175            Ok(actual_cycle_start.checked_sub(quarter_cycle_time).unwrap()),
176            skip_missed_cycles(
177                cycle_time,
178                actual_cycle_start.checked_sub(quarter_cycle_time).unwrap(),
179                actual_cycle_start
180            ),
181        );
182        assert_eq!(
183            Ok(actual_cycle_start.checked_sub(half_cycle_time).unwrap()),
184            skip_missed_cycles(
185                cycle_time,
186                actual_cycle_start.checked_sub(half_cycle_time).unwrap(),
187                actual_cycle_start
188            ),
189        );
190        assert_eq!(
191            Ok(actual_cycle_start
192                .checked_sub(three_quarter_cycle_time)
193                .unwrap()),
194            skip_missed_cycles(
195                cycle_time,
196                actual_cycle_start
197                    .checked_sub(three_quarter_cycle_time)
198                    .unwrap(),
199                actual_cycle_start
200            ),
201        );
202        assert_eq!(
203            Ok(actual_cycle_start
204                .checked_sub(cycle_time - Duration::from_nanos(1))
205                .unwrap()),
206            skip_missed_cycles(
207                cycle_time,
208                actual_cycle_start
209                    .checked_sub(cycle_time - Duration::from_nanos(1))
210                    .unwrap(),
211                actual_cycle_start
212            ),
213        );
214
215        // 1 or more cycles later than expected
216        for i in 1u32..10u32 {
217            assert_eq!(
218                Err((actual_cycle_start, i)),
219                skip_missed_cycles(
220                    cycle_time,
221                    actual_cycle_start.checked_sub(i * cycle_time).unwrap(),
222                    actual_cycle_start
223                ),
224            );
225            assert_eq!(
226                Err((
227                    actual_cycle_start
228                        .checked_sub(Duration::from_nanos(1))
229                        .unwrap(),
230                    i
231                )),
232                skip_missed_cycles(
233                    cycle_time,
234                    actual_cycle_start
235                        .checked_sub(i * cycle_time)
236                        .unwrap()
237                        .checked_sub(Duration::from_nanos(1))
238                        .unwrap(),
239                    actual_cycle_start
240                ),
241            );
242            assert_eq!(
243                Err((
244                    actual_cycle_start.checked_sub(quarter_cycle_time).unwrap(),
245                    i
246                )),
247                skip_missed_cycles(
248                    cycle_time,
249                    actual_cycle_start
250                        .checked_sub(i * cycle_time)
251                        .unwrap()
252                        .checked_sub(quarter_cycle_time)
253                        .unwrap(),
254                    actual_cycle_start
255                ),
256            );
257            assert_eq!(
258                Err((actual_cycle_start.checked_sub(half_cycle_time).unwrap(), i)),
259                skip_missed_cycles(
260                    cycle_time,
261                    actual_cycle_start
262                        .checked_sub(i * cycle_time)
263                        .unwrap()
264                        .checked_sub(half_cycle_time)
265                        .unwrap(),
266                    actual_cycle_start
267                ),
268            );
269            assert_eq!(
270                Err((
271                    actual_cycle_start
272                        .checked_sub(three_quarter_cycle_time)
273                        .unwrap(),
274                    i
275                )),
276                skip_missed_cycles(
277                    cycle_time,
278                    actual_cycle_start
279                        .checked_sub(i * cycle_time)
280                        .unwrap()
281                        .checked_sub(three_quarter_cycle_time)
282                        .unwrap(),
283                    actual_cycle_start
284                ),
285            );
286            assert_eq!(
287                Err((
288                    actual_cycle_start
289                        .checked_sub(cycle_time - Duration::from_nanos(1))
290                        .unwrap(),
291                    i
292                )),
293                skip_missed_cycles(
294                    cycle_time,
295                    actual_cycle_start
296                        .checked_sub(i * cycle_time)
297                        .unwrap()
298                        .checked_sub(cycle_time - Duration::from_nanos(1))
299                        .unwrap(),
300                    actual_cycle_start
301                ),
302            );
303        }
304    }
305}