Skip to main content

rustmeter_beacon_core/
time_delta.rs

1#![allow(unused)] // in test mode, things might be unused
2use arbitrary_int::traits::Integer;
3
4use crate::{
5    buffer::{BufferReader, BufferWriter},
6    protocol::{EventPayload, TypeDefinitionPayload},
7    tracing::{ReadTracingError, write_tracing_event},
8};
9
10#[cfg(not(feature = "multicore"))]
11static LAST_TIMESTAMP: [portable_atomic::AtomicU32; 1] = [portable_atomic::AtomicU32::new(0)];
12#[cfg(feature = "multicore")]
13static LAST_TIMESTAMP: [portable_atomic::AtomicU32; 2] = [
14    portable_atomic::AtomicU32::new(0),
15    portable_atomic::AtomicU32::new(0),
16];
17
18/// Public static flag to check if local core clock (cpu performance counter) was referenced to
19/// systemtime. Can be reflagged to false via Host
20pub static CORE_CLOCK_REFERENCED: [portable_atomic::AtomicBool; 2] = [
21    portable_atomic::AtomicBool::new(false),
22    portable_atomic::AtomicBool::new(false),
23];
24/// Static flag to check if preinit clock method run. This does only run a single time in the whole
25/// application life cycle and not be resetted!
26static PREINIT_CLOCK_RUN: [portable_atomic::AtomicBool; 2] = [
27    portable_atomic::AtomicBool::new(false),
28    portable_atomic::AtomicBool::new(false),
29];
30
31#[inline(always)]
32fn do_core_clock_referencing(core_id: usize) {
33    CORE_CLOCK_REFERENCED[core_id].store(true, portable_atomic::Ordering::Relaxed);
34
35    // Send a CoreClockReference event to establish the baseline timestamp for this core. This must be done inside a
36    // critical section to avoid interrupts interfering with the timestamp measurement. (Core-local would be sufficient,
37    // but critical section is easier to implement cross-platform.)
38    // Normally TimeDelta is already inside a critical section when called from tracing event writing, so this should be safe to do
39    // without critical section here again. But we do it anyway to be sure.
40    critical_section::with(|_| {
41        // Do Clock Preeinit one time
42        #[cfg(not(feature = "std"))]
43        unsafe {
44            if !PREINIT_CLOCK_RUN[core_id].load(portable_atomic::Ordering::Relaxed) {
45                PREINIT_CLOCK_RUN[core_id].store(true, portable_atomic::Ordering::Relaxed);
46                preinit_clock_reference();
47            }
48        };
49
50        // Send Core Clock Reference
51        let cpu_ticks = unsafe { get_tracing_raw_ticks() };
52        let systimer_us = unsafe { get_system_time_us() };
53        write_tracing_event(EventPayload::TypeDefinition(
54            TypeDefinitionPayload::CoreClockReference {
55                core_id: core_id as u8,
56                systimer_us,
57                cpu_ticks,
58            },
59        ));
60    });
61}
62
63unsafe extern "Rust" {
64    /// Low-level function to get the current tracing time in microseconds. Implemented in the target crate.
65    /// In tests this already should return the timedelta directly.
66    // pub fn get_tracing_time_us() -> u32;
67
68    /// Low-level function to preinitialize the clock reference for the target core. Implemented in the target crate.
69    /// Runs in critical section.
70    pub fn preinit_clock_reference();
71
72    /// Low-level function to get the current tracing raw ticks. Implemented in the target crate.
73    /// In tests this already should return the timedelta directly.
74    pub fn get_tracing_raw_ticks() -> u32;
75
76    pub fn get_system_time_us() -> u64;
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct TimeDelta {
81    delta: u32,
82}
83
84impl TimeDelta {
85    /// This has to be called inside a critical section
86    #[inline(always)]
87    #[cfg(not(test))]
88    pub fn from_now() -> Self {
89        let core_id = unsafe { crate::get_current_core_id() as usize };
90        if !CORE_CLOCK_REFERENCED[core_id].load(portable_atomic::Ordering::Relaxed) {
91            do_core_clock_referencing(core_id);
92        }
93
94        // estimate time between last timestamp and now
95        let now = unsafe { get_tracing_raw_ticks() };
96        let last = LAST_TIMESTAMP[core_id].swap(now, portable_atomic::Ordering::Relaxed);
97
98        if now < last {
99            // Handle wrap-around
100            let last_till_end = arbitrary_int::u26::MAX.as_u32() - last;
101            return TimeDelta {
102                delta: last_till_end + now,
103            };
104        } else {
105            TimeDelta { delta: now - last }
106        }
107    }
108
109    #[cfg(test)]
110    pub fn from_now() -> Self {
111        let now = unsafe { get_tracing_raw_ticks() };
112        TimeDelta { delta: now }
113    }
114
115    /// Returns true if the TimeDelta requires extended format (4 bytes), false if it can be represented in single format (2 bytes).
116    #[inline(always)]
117    pub const fn is_extended(&self) -> bool {
118        self.delta >= 2u32.pow(15)
119    }
120
121    /// Write the TimeDelta into the provided writer. It will use either 2 or 4 bytes depending on the size:
122    /// - If the delta is less than 2^15, it will be written as a 2-byte value with the highest bit set to 0.
123    /// - If the delta is 2^15 or more, it will be written as a 4-byte value with the highest bit set to 1. If the delta exceeds 2^31 - 1, it will be capped to that value.
124    pub fn write_bytes(&self, writer: &mut BufferWriter) {
125        if self.is_extended() {
126            // Cap value at 2^31 - 1
127            let capped_delta = if self.delta > (2u32.pow(31) - 1) {
128                2u32.pow(31) - 1
129            } else {
130                self.delta
131            };
132
133            // Use extended format (4 bytes)
134            let extended_value = capped_delta | 0x8000_0000; // Set highest bit to 1
135            writer.write_bytes(&extended_value.to_be_bytes());
136        } else {
137            // Single format (2 bytes)
138            let single_value = (self.delta & 0x7FFF) as u16; // Ensure highest bit is 0
139            writer.write_bytes(&single_value.to_be_bytes());
140        }
141    }
142
143    #[inline(always)]
144    pub fn write_bytes_mut(&self, writer: &mut [u8]) -> usize {
145        if self.is_extended() {
146            // Cap value at 2^31 - 1
147            let capped_delta = if self.delta > (2u32.pow(31) - 1) {
148                2u32.pow(31) - 1
149            } else {
150                self.delta
151            };
152
153            // Use extended format (4 bytes)
154            let extended_value = capped_delta | 0x8000_0000; // Set highest bit to 1
155            let bytes = extended_value.to_be_bytes();
156            writer[..4].copy_from_slice(&bytes);
157            4
158        } else {
159            // Single format (2 bytes)
160            let single_value = self.delta as u16;
161            let bytes = single_value.to_be_bytes();
162            writer[..2].copy_from_slice(&bytes);
163            2
164        }
165    }
166
167    /// Reads a TimeDelta from the provided reader. Returns None if reading fails.
168    /// It automatically detects whether the format is single (2 bytes) or extended (4 bytes) based on the highest bit.
169    /// This method can only "fail" if there is not enough data in the reader.
170    pub fn read_bytes(reader: &mut BufferReader) -> Result<Self, ReadTracingError> {
171        // Read first 2 bytes to determine format
172        let first_byte = reader.read_byte()?;
173        let second_byte = reader.read_byte()?;
174
175        if (first_byte & 0x80) == 0 {
176            // Single format
177            let delta = u16::from_be_bytes([first_byte, second_byte]) as u32;
178            Ok(TimeDelta { delta })
179        } else {
180            // Extended format, read additional 2 bytes
181            let next_two_bytes = reader.read_bytes(2)?;
182            let extended_value = u32::from_be_bytes([
183                first_byte,
184                second_byte,
185                next_two_bytes[0],
186                next_two_bytes[1],
187            ]);
188            let delta = extended_value & 0x7FFF_FFFF; // Clear highest bit
189            Ok(TimeDelta { delta })
190        }
191    }
192
193    /// Returns the delta in microseconds
194    pub fn delta(&self) -> u32 {
195        self.delta
196    }
197}
198
199#[cfg(all(feature = "std", not(test)))]
200mod std_time {
201    #[unsafe(no_mangle)]
202    unsafe fn get_tracing_raw_ticks() -> u32 {
203        use std::time::{SystemTime, UNIX_EPOCH};
204
205        let now = SystemTime::now()
206            .duration_since(UNIX_EPOCH)
207            .expect("Time went backwards");
208        now.as_micros() as u32
209    }
210
211    #[unsafe(no_mangle)]
212    unsafe fn get_system_time_us() -> u64 {
213        use std::time::{SystemTime, UNIX_EPOCH};
214
215        let now = SystemTime::now()
216            .duration_since(UNIX_EPOCH)
217            .expect("Time went backwards");
218        now.as_micros() as u64
219    }
220}
221
222#[cfg(test)]
223mod tests {
224
225    use super::*;
226    use crate::buffer::BufferWriter;
227
228    #[test]
229    fn test_time_delta_read_and_write_exponents() {
230        // Simply test all exponents from 0 to 32
231        for exponent in 0..=32 {
232            let delta = (2u64.pow(exponent) - 1) as u32; // u64 because 2^32 doesn't fit in u32
233            let time_delta = TimeDelta { delta };
234
235            // Write to buffer
236            let mut writer = BufferWriter::new();
237            time_delta.write_bytes(&mut writer);
238            let written_bytes = writer.as_slice();
239
240            if exponent <= 15 {
241                // Single format (2 bytes)
242                assert_eq!(
243                    written_bytes.len(),
244                    2,
245                    "Expected 2 bytes for delta {}",
246                    delta
247                );
248            } else {
249                // Extended format (4 bytes)
250                assert_eq!(
251                    written_bytes.len(),
252                    4,
253                    "Expected 4 bytes for delta {}",
254                    delta
255                );
256            }
257
258            // Read from buffer
259            let mut reader = BufferReader::new(written_bytes);
260            let read_time_delta =
261                TimeDelta::read_bytes(&mut reader).expect("Failed to read TimeDelta");
262
263            // 2^31 - 1 capping check
264            let expected_delta = delta.min(2u32.pow(31) - 1);
265
266            assert_eq!(
267                expected_delta, read_time_delta.delta,
268                "Mismatch for delta {}",
269                delta
270            );
271        }
272    }
273
274    #[test]
275    fn test_time_delta_read_and_write_specials() {
276        let deltas = [
277            (0u32, 2),
278            (1u32, 2),
279            (2u32.pow(15) - 1, 2),
280            (2u32.pow(15), 4),
281            (2u32.pow(15) + 1, 4),
282            (2u32.pow(16), 4),
283            (2u32.pow(31) - 1, 4),
284            (2u32.pow(31), 4),
285        ];
286
287        for (delta, byte_size) in &deltas {
288            let time_delta = TimeDelta { delta: *delta };
289
290            // Write to buffer
291            let mut writer = BufferWriter::new();
292            time_delta.write_bytes(&mut writer);
293            let written_bytes = writer.as_slice();
294
295            assert_eq!(
296                written_bytes.len(),
297                *byte_size,
298                "Expected {} bytes for delta {}",
299                byte_size,
300                delta
301            );
302
303            // Read from buffer
304            let mut reader = BufferReader::new(written_bytes);
305            let read_time_delta =
306                TimeDelta::read_bytes(&mut reader).expect("Failed to read TimeDelta");
307
308            // 2^31 - 1 capping check
309            let expected_delta = (*delta).min(2u32.pow(31) - 1);
310
311            assert_eq!(
312                expected_delta, read_time_delta.delta,
313                "Mismatch for delta {}",
314                delta
315            );
316        }
317    }
318}