embedded_profiling/lib.rs
1//! # Embedded-Profiling
2//!
3//! A lightweight framework for profiling functions, geared towards
4//! `no-std` embedded environments. Initialization is very similar
5//! to how the `log` crate is initialized. By default, there is a
6//! no-op profiler that does nothing until you call [`set_profiler`].
7//! Once your profiler has been installed, your profiling
8//! functionality will be in use.
9//!
10//! ## Usage
11//!
12//! You can manually start & end your snapshot:
13//! ```
14//! let start = embedded_profiling::start_snapshot();
15//! // (...) some expensive computation
16//! if let Some(snapshot) = embedded_profiling::end_snapshot(start, "name-of-computation") {
17//! // Optionally, log it if we didn't overflow
18//! embedded_profiling::log_snapshot(&snapshot);
19//! }
20//! ```
21//!
22//! Or profile some code in a closure:
23//! ```
24//! embedded_profiling::profile("profile println", || {
25//! println!("profiling this closure");
26//! });
27//! ```
28//!
29//! ## With a Procedural Macro
30//!
31//! With the `proc-macros` feature enabled, you can simply annotate
32//! the target function with the procedural macro
33//! [`profile_function`](embedded_profiling_proc_macros::profile_function).
34//! Note that you must first set your profiler with the [`set_profiler`]
35//! function.
36//! ```
37//! # #[cfg(feature = "proc-macros")]
38//! #[embedded_profiling::profile_function]
39//! fn my_long_running_function() {
40//! println!("Hello, world!");
41//! }
42//! ```
43//!
44//! ## Features
45//!
46//! ### `container-u64`
47//!
48//! Use a [`u64`] as the time storage type instead of [`u32`] for longer running profiling.
49//!
50//! ### `proc-macros`
51//!
52//! enables the `proc-macros` feature in [`embedded-profiling`](self). Enables
53//! the [`embedded_profiling::profile_function`](self::profile_function) procedural macro.
54//!
55#![warn(missing_docs)]
56#![cfg_attr(not(test), no_std)]
57
58use core::sync::atomic::{AtomicU8, Ordering};
59
60#[cfg(test)]
61mod mock;
62#[cfg(feature = "proc-macros")]
63pub use embedded_profiling_proc_macros::profile_function;
64
65pub use fugit;
66
67// do the feature gating on a private type so our public documentation is only in one place
68#[cfg(not(feature = "container-u64"))]
69type PrivContainer = u32;
70#[cfg(feature = "container-u64")]
71type PrivContainer = u64;
72
73/// The underlying container of our [`Duration`](fugit::Duration)/[`Instant`](fugit::Instant) types.
74/// Can be either `u32` or `u64`, depending on features (default: `u32`).
75pub type EPContainer = PrivContainer;
76
77/// Our [`Duration`](fugit::Duration) type, representing time elapsed in microseconds.
78pub type EPDuration = fugit::MicrosDuration<EPContainer>;
79
80/// Our [`Instant`](fugit::Instant) type, representing a snapshot in time from
81/// a clock with 1 µs precision (or at least, converted to this representation).
82pub type EPInstant = fugit::Instant<EPContainer, 1, 1_000_000>;
83
84/// An [`Instant`](fugit::Instant) type but with a generic fraction. This needs to be
85/// converted into [`EPInstant`] for use in the [`EmbeddedProfiler::read_clock`] function.
86pub type EPInstantGeneric<const NOM: u32, const DENOM: u32> =
87 fugit::Instant<EPContainer, NOM, DENOM>;
88
89/// A recorded snapshot.
90pub struct EPSnapshot {
91 /// The name of this trace.
92 pub name: &'static str,
93 /// The duration of this trace.
94 pub duration: EPDuration,
95}
96
97impl core::fmt::Display for EPSnapshot {
98 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99 write!(f, "<EPSS {}: {}>", self.name, self.duration)
100 }
101}
102
103/// The main trait to implement. All that is required is a way to read time and a way
104/// to output our results, if desired. You can also implement functions that
105/// get called when a snapshot starts and ends.
106pub trait EmbeddedProfiler {
107 /// Takes a reading from the clock.
108 ///
109 /// Used by the underlying trait methods [`EmbeddedProfiler::start_snapshot`] and
110 /// [`EmbeddedProfiler::end_snapshot`].
111 fn read_clock(&self) -> EPInstant;
112
113 /// Optionally log the snapshot to some output, like a serial port.
114 fn log_snapshot(&self, _snapshot: &EPSnapshot) {}
115
116 /// Optional function that gets called at the start of the snapshot recording.
117 ///
118 /// If one would want to very simple profiling, they could use `at_start` and `at_end`
119 /// to simply toggle a GPIO.
120 fn at_start(&self) {}
121
122 /// Optional function that gets called at the end of the snapshot recording.
123 fn at_end(&self) {}
124
125 /// takes the starting snapshot of a specific trace.
126 ///
127 /// ```
128 /// # use embedded_profiling::*;
129 /// # struct MyProfiler;
130 /// # impl EmbeddedProfiler for MyProfiler { fn read_clock(&self) -> EPInstant {EPInstant::from_ticks(0)} }
131 /// # let my_profiler = MyProfiler;
132 /// # fn function_to_profile() {}
133 /// let start_time = my_profiler.start_snapshot();
134 /// function_to_profile();
135 /// if let Some(snapshot) = my_profiler.end_snapshot(start_time, "function_to_profile") {
136 /// my_profiler.log_snapshot(&snapshot);
137 /// }
138 /// ```
139 fn start_snapshot(&self) -> EPInstant {
140 self.at_start();
141 self.read_clock()
142 }
143
144 /// computes the duration of the snapshot given the start time, if there hasn't been overflow.
145 fn end_snapshot(&self, start: EPInstant, name: &'static str) -> Option<EPSnapshot> {
146 self.at_end();
147 let now = self.read_clock();
148 now.checked_duration_since(start)
149 .map(|duration| EPSnapshot { name, duration })
150 }
151}
152
153/// Converts an instant of an unknown fraction `NOM`/`DENOM` to our microsecond representation.
154///
155/// This function is useful when implementing [`EmbeddedProfiler::read_clock`], to convert from
156/// your clocks native resolution to the 1µs resolution it needs.
157/// ```
158/// # use embedded_profiling::*;
159/// let my_clock_instant = EPInstantGeneric::<1, 1_000>::from_ticks(100);
160/// let converted_instant = convert_instant::<1, 1_000>(my_clock_instant);
161/// assert_eq!(100_000, converted_instant.ticks());
162/// ```
163#[inline]
164pub const fn convert_instant<const NOM: u32, const DENOM: u32>(
165 now: EPInstantGeneric<NOM, DENOM>,
166) -> EPInstant {
167 let us: fugit::MicrosDuration<EPContainer> = now.duration_since_epoch().convert();
168 EPInstant::from_ticks(us.ticks())
169}
170
171struct NoopProfiler;
172
173impl EmbeddedProfiler for NoopProfiler {
174 fn read_clock(&self) -> EPInstant {
175 EPInstant::from_ticks(0)
176 }
177
178 fn log_snapshot(&self, _snapshot: &EPSnapshot) {}
179}
180
181static mut PROFILER: &dyn EmbeddedProfiler = &NoopProfiler;
182
183const UNINITIALIZED: u8 = 0;
184const INITIALIZED: u8 = 2;
185
186static STATE: AtomicU8 = AtomicU8::new(UNINITIALIZED);
187
188/// Indicates that setting the profiler has gone awry, probably because the
189/// profiler has already been set.
190#[derive(Debug)]
191pub struct SetProfilerError;
192
193/// Sets the global profiler.
194///
195/// # Safety
196/// Must be completed with no other threads running
197/// or, in an embedded single core environment, with interrupts disabled.
198///
199/// # Errors
200/// returns `Err(SetProfilerError)` when a global profiler has already been configured
201///
202/// ```
203/// # struct MyProfiler;
204/// # impl embedded_profiling::EmbeddedProfiler for MyProfiler { fn read_clock(&self) -> embedded_profiling::EPInstant { embedded_profiling::EPInstant::from_ticks(0) } }
205/// # static MY_PROFILER: MyProfiler = MyProfiler;
206/// let noop_profiler_ref = embedded_profiling::profiler(); // no-op profiler returned because we haven't set one yet
207/// // interrupts should be disabled while this is called with something like `cortex_m::interrupt::free`
208/// unsafe {
209/// embedded_profiling::set_profiler(&MY_PROFILER).unwrap();
210/// }
211/// let my_profiler_ref = embedded_profiling::profiler(); // our profiler now returned
212/// ```
213pub unsafe fn set_profiler(
214 profiler: &'static dyn EmbeddedProfiler,
215) -> Result<(), SetProfilerError> {
216 match STATE.load(Ordering::Acquire) {
217 UNINITIALIZED => {
218 PROFILER = profiler;
219 STATE.store(INITIALIZED, Ordering::Release);
220 Ok(())
221 }
222 INITIALIZED => Err(SetProfilerError),
223 _ => unreachable!(),
224 }
225}
226
227/// Returns a reference to the configured profiler.
228///
229/// If a profiler hasn't yet been set by [`set_profiler`], the no-op profiler
230/// will be returned. Generally, you should use one of the other provided
231/// functions rather than getting a reference through this function.
232///
233/// ```
234/// let start = embedded_profiling::profiler().start_snapshot();
235/// // (...)
236/// let snapshot = embedded_profiling::profiler().end_snapshot(start, "doc-example");
237/// ```
238#[inline]
239pub fn profiler() -> &'static dyn EmbeddedProfiler {
240 if STATE.load(Ordering::Acquire) == INITIALIZED {
241 unsafe { PROFILER }
242 } else {
243 static NOP: NoopProfiler = NoopProfiler;
244 &NOP
245 }
246}
247
248/// takes the starting snapshot of a specific trace.
249///
250/// ```
251/// let start = embedded_profiling::start_snapshot();
252/// // (...)
253/// let snapshot = embedded_profiling::end_snapshot(start, "doc-example");
254/// ```
255#[inline]
256pub fn start_snapshot() -> EPInstant {
257 profiler().start_snapshot()
258}
259
260/// computes the duration of the snapshot given the start time using the
261/// globally configured profiler.
262#[inline]
263pub fn end_snapshot(start: EPInstant, name: &'static str) -> Option<EPSnapshot> {
264 profiler().end_snapshot(start, name)
265}
266
267/// Logs the given snapshot with the globally configured profiler.
268///
269/// ```
270/// let start = embedded_profiling::start_snapshot();
271/// // (...)
272/// if let Some(snapshot) = embedded_profiling::end_snapshot(start, "doc-example") {
273/// embedded_profiling::log_snapshot(&snapshot);
274/// }
275#[inline]
276pub fn log_snapshot(snapshot: &EPSnapshot) {
277 profiler().log_snapshot(snapshot);
278}
279
280/// Profiles the given closure `target` with name `name`.
281///
282/// ```
283/// embedded_profiling::profile("profile println", || {
284/// println!("profiling this closure");
285/// });
286/// ```
287pub fn profile<T, R>(name: &'static str, target: T) -> R
288where
289 T: Fn() -> R,
290{
291 let start = start_snapshot();
292 let ret = target();
293 if let Some(snapshot) = end_snapshot(start, name) {
294 log_snapshot(&snapshot);
295 }
296 ret
297}
298
299#[cfg(test)]
300mod test {
301 use super::mock::StdMockProfiler;
302 use super::*;
303
304 #[cfg(feature = "proc-macros")]
305 use crate as embedded_profiling;
306
307 use std::sync::Once;
308
309 static INIT_PROFILER: Once = Once::new();
310 static mut MOCK_PROFILER: Option<StdMockProfiler> = None;
311
312 fn set_profiler() {
313 INIT_PROFILER.call_once(|| unsafe {
314 if MOCK_PROFILER.is_none() {
315 MOCK_PROFILER = Some(StdMockProfiler::default());
316 }
317 super::set_profiler(MOCK_PROFILER.as_ref().unwrap()).unwrap();
318 });
319 }
320
321 #[test]
322 #[serial_test::serial]
323 fn basic_duration() {
324 let profiler = StdMockProfiler::default();
325
326 let start = profiler.start_snapshot();
327 std::thread::sleep(std::time::Duration::from_millis(25));
328 let end = profiler.end_snapshot(start, "basic_dur").unwrap();
329 profiler.log_snapshot(&end);
330 }
331
332 #[test]
333 #[serial_test::serial]
334 fn basic_duration_and_set_profiler() {
335 // set the profiler, if it hasn't been already
336 set_profiler();
337
338 let start = start_snapshot();
339 std::thread::sleep(std::time::Duration::from_millis(25));
340 let end = end_snapshot(start, "basic_dur").unwrap();
341 log_snapshot(&end);
342 }
343
344 #[test]
345 #[serial_test::serial]
346 fn profile_closure() {
347 // set the profiler, if it hasn't been already
348 set_profiler();
349
350 profile("25ms closure", || {
351 std::thread::sleep(std::time::Duration::from_millis(25));
352 });
353 }
354
355 #[cfg(feature = "proc-macros")]
356 #[test]
357 #[serial_test::serial]
358 fn profile_proc_macro() {
359 #[profile_function]
360 fn delay_25ms() {
361 std::thread::sleep(std::time::Duration::from_millis(25));
362 }
363
364 // set the profiler, if it hasn't been already
365 set_profiler();
366
367 delay_25ms();
368 }
369
370 #[cfg(feature = "proc-macros")]
371 #[test]
372 #[serial_test::serial]
373 fn check_call_and_order() {
374 use Ordering::SeqCst;
375
376 #[profile_function]
377 fn delay_25ms() {
378 std::thread::sleep(std::time::Duration::from_millis(25));
379 }
380
381 // set the profiler, if it hasn't been already
382 set_profiler();
383
384 delay_25ms();
385
386 // check if our functions were called and if the order is right
387 let stats = unsafe { &MOCK_PROFILER.as_ref().unwrap().funcs_called };
388 let at_start_was_called = stats.at_start.called.load(SeqCst);
389 let read_clock_was_called = stats.read_clock.called.load(SeqCst);
390 let at_end_was_called = stats.at_end.called.load(SeqCst);
391 // stats.read_clock (but skipped since we've already called it)
392 let log_snapshot_was_called = stats.log_snapshot.called.load(SeqCst);
393
394 let at_start_at = stats.at_start.at.load(SeqCst);
395 let read_clock_at = stats.read_clock.at.load(SeqCst);
396 let at_end_at = stats.at_end.at.load(SeqCst);
397 let log_snapshot_at = stats.log_snapshot.at.load(SeqCst);
398
399 if at_start_was_called {
400 println!("at_start called #{}", at_start_at);
401 } else {
402 println!("at_start not called");
403 }
404 if read_clock_was_called {
405 println!("read_clock called #{}", read_clock_at);
406 } else {
407 println!("read_clock not called");
408 }
409 if at_end_was_called {
410 println!("at_end called #{}", at_end_at);
411 } else {
412 println!("at_end not called");
413 }
414 if log_snapshot_was_called {
415 println!("log_snapshot called #{}", log_snapshot_at);
416 } else {
417 println!("log_snapshot not called");
418 }
419
420 assert!(at_start_was_called, "'at_start' was never called");
421 assert!(read_clock_was_called, "'read_clock' was never called");
422 assert!(at_end_was_called, "'at_end' was never called");
423 assert!(log_snapshot_was_called, "'log_snapshot' was never called");
424
425 assert_eq!(at_start_at, 0, "'at_start' called at wrong time");
426 assert_eq!(read_clock_at, 1, "'read_clock' called at wrong time");
427 assert_eq!(at_end_at, 2, "'at_end' called at wrong time");
428 assert_eq!(log_snapshot_at, 3, "'log_snapshot' called at wrong time");
429 }
430
431 #[test]
432 const fn check_conversion() {
433 // check to see if the conversion is naive and saturates or not
434 const NOM: u32 = 4;
435 const DENOM: u32 = 4_000_000;
436 const INITIAL_INSTANT: EPInstantGeneric<NOM, DENOM> =
437 EPInstantGeneric::from_ticks(EPContainer::MAX - 10);
438 const RESULT_INSTANT: EPInstant = convert_instant(INITIAL_INSTANT);
439 assert!(RESULT_INSTANT.ticks() == INITIAL_INSTANT.ticks());
440 }
441}