#![warn(missing_docs)]
#![cfg_attr(not(test), no_std)]
use core::sync::atomic::{AtomicU8, Ordering};
#[cfg(test)]
mod mock;
#[cfg(feature = "proc-macros")]
pub use embedded_profiling_proc_macros::profile_function;
pub use fugit;
#[cfg(not(feature = "container-u64"))]
type PrivContainer = u32;
#[cfg(feature = "container-u64")]
type PrivContainer = u64;
pub type EPContainer = PrivContainer;
pub type EPDuration = fugit::MicrosDuration<EPContainer>;
pub type EPInstant = fugit::Instant<EPContainer, 1, 1_000_000>;
pub type EPInstantGeneric<const NOM: u32, const DENOM: u32> =
fugit::Instant<EPContainer, NOM, DENOM>;
pub struct EPSnapshot {
pub name: &'static str,
pub duration: EPDuration,
}
impl core::fmt::Display for EPSnapshot {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "<EPSS {}: {}>", self.name, self.duration)
}
}
pub trait EmbeddedProfiler {
fn read_clock(&self) -> EPInstant;
fn log_snapshot(&self, _snapshot: &EPSnapshot) {}
fn at_start(&self) {}
fn at_end(&self) {}
fn start_snapshot(&self) -> EPInstant {
self.at_start();
self.read_clock()
}
fn end_snapshot(&self, start: EPInstant, name: &'static str) -> Option<EPSnapshot> {
self.at_end();
let now = self.read_clock();
now.checked_duration_since(start)
.map(|duration| EPSnapshot { name, duration })
}
}
#[inline]
pub const fn convert_instant<const NOM: u32, const DENOM: u32>(
now: EPInstantGeneric<NOM, DENOM>,
) -> EPInstant {
let us: fugit::MicrosDuration<EPContainer> = now.duration_since_epoch().convert();
EPInstant::from_ticks(us.ticks())
}
struct NoopProfiler;
impl EmbeddedProfiler for NoopProfiler {
fn read_clock(&self) -> EPInstant {
EPInstant::from_ticks(0)
}
fn log_snapshot(&self, _snapshot: &EPSnapshot) {}
}
static mut PROFILER: &dyn EmbeddedProfiler = &NoopProfiler;
const UNINITIALIZED: u8 = 0;
const INITIALIZED: u8 = 2;
static STATE: AtomicU8 = AtomicU8::new(UNINITIALIZED);
#[derive(Debug)]
pub struct SetProfilerError;
pub unsafe fn set_profiler(
profiler: &'static dyn EmbeddedProfiler,
) -> Result<(), SetProfilerError> {
match STATE.load(Ordering::Acquire) {
UNINITIALIZED => {
PROFILER = profiler;
STATE.store(INITIALIZED, Ordering::Release);
Ok(())
}
INITIALIZED => Err(SetProfilerError),
_ => unreachable!(),
}
}
#[inline]
pub fn profiler() -> &'static dyn EmbeddedProfiler {
if STATE.load(Ordering::Acquire) == INITIALIZED {
unsafe { PROFILER }
} else {
static NOP: NoopProfiler = NoopProfiler;
&NOP
}
}
#[inline]
pub fn start_snapshot() -> EPInstant {
profiler().start_snapshot()
}
#[inline]
pub fn end_snapshot(start: EPInstant, name: &'static str) -> Option<EPSnapshot> {
profiler().end_snapshot(start, name)
}
#[inline]
pub fn log_snapshot(snapshot: &EPSnapshot) {
profiler().log_snapshot(snapshot);
}
pub fn profile<T, R>(name: &'static str, target: T) -> R
where
T: Fn() -> R,
{
let start = start_snapshot();
let ret = target();
if let Some(snapshot) = end_snapshot(start, name) {
log_snapshot(&snapshot);
}
ret
}
#[cfg(test)]
mod test {
use super::mock::StdMockProfiler;
use super::*;
#[cfg(feature = "proc-macros")]
use crate as embedded_profiling;
use std::sync::Once;
static INIT_PROFILER: Once = Once::new();
static mut MOCK_PROFILER: Option<StdMockProfiler> = None;
fn set_profiler() {
INIT_PROFILER.call_once(|| unsafe {
if MOCK_PROFILER.is_none() {
MOCK_PROFILER = Some(StdMockProfiler::default());
}
super::set_profiler(MOCK_PROFILER.as_ref().unwrap()).unwrap();
});
}
#[test]
#[serial_test::serial]
fn basic_duration() {
let profiler = StdMockProfiler::default();
let start = profiler.start_snapshot();
std::thread::sleep(std::time::Duration::from_millis(25));
let end = profiler.end_snapshot(start, "basic_dur").unwrap();
profiler.log_snapshot(&end);
}
#[test]
#[serial_test::serial]
fn basic_duration_and_set_profiler() {
set_profiler();
let start = start_snapshot();
std::thread::sleep(std::time::Duration::from_millis(25));
let end = end_snapshot(start, "basic_dur").unwrap();
log_snapshot(&end);
}
#[test]
#[serial_test::serial]
fn profile_closure() {
set_profiler();
profile("25ms closure", || {
std::thread::sleep(std::time::Duration::from_millis(25));
});
}
#[cfg(feature = "proc-macros")]
#[test]
#[serial_test::serial]
fn profile_proc_macro() {
#[profile_function]
fn delay_25ms() {
std::thread::sleep(std::time::Duration::from_millis(25));
}
set_profiler();
delay_25ms();
}
#[cfg(feature = "proc-macros")]
#[test]
#[serial_test::serial]
fn check_call_and_order() {
use Ordering::SeqCst;
#[profile_function]
fn delay_25ms() {
std::thread::sleep(std::time::Duration::from_millis(25));
}
set_profiler();
delay_25ms();
let stats = unsafe { &MOCK_PROFILER.as_ref().unwrap().funcs_called };
let at_start_was_called = stats.at_start.called.load(SeqCst);
let read_clock_was_called = stats.read_clock.called.load(SeqCst);
let at_end_was_called = stats.at_end.called.load(SeqCst);
let log_snapshot_was_called = stats.log_snapshot.called.load(SeqCst);
let at_start_at = stats.at_start.at.load(SeqCst);
let read_clock_at = stats.read_clock.at.load(SeqCst);
let at_end_at = stats.at_end.at.load(SeqCst);
let log_snapshot_at = stats.log_snapshot.at.load(SeqCst);
if at_start_was_called {
println!("at_start called #{}", at_start_at);
} else {
println!("at_start not called");
}
if read_clock_was_called {
println!("read_clock called #{}", read_clock_at);
} else {
println!("read_clock not called");
}
if at_end_was_called {
println!("at_end called #{}", at_end_at);
} else {
println!("at_end not called");
}
if log_snapshot_was_called {
println!("log_snapshot called #{}", log_snapshot_at);
} else {
println!("log_snapshot not called");
}
assert!(at_start_was_called, "'at_start' was never called");
assert!(read_clock_was_called, "'read_clock' was never called");
assert!(at_end_was_called, "'at_end' was never called");
assert!(log_snapshot_was_called, "'log_snapshot' was never called");
assert_eq!(at_start_at, 0, "'at_start' called at wrong time");
assert_eq!(read_clock_at, 1, "'read_clock' called at wrong time");
assert_eq!(at_end_at, 2, "'at_end' called at wrong time");
assert_eq!(log_snapshot_at, 3, "'log_snapshot' called at wrong time");
}
#[test]
const fn check_conversion() {
const NOM: u32 = 4;
const DENOM: u32 = 4_000_000;
const INITIAL_INSTANT: EPInstantGeneric<NOM, DENOM> =
EPInstantGeneric::from_ticks(EPContainer::MAX - 10);
const RESULT_INSTANT: EPInstant = convert_instant(INITIAL_INSTANT);
assert!(RESULT_INSTANT.ticks() == INITIAL_INSTANT.ticks());
}
}