//! FIXME: write short doc here mod memory_usage; #[cfg(feature = "cpu_profiler")] mod google_cpu_profiler; use std::{ cell::RefCell, collections::BTreeMap, collections::HashSet, io::{stderr, Write}, sync::{ atomic::{AtomicBool, Ordering}, RwLock, }, time::{Duration, Instant}, }; use once_cell::sync::Lazy; pub use crate::memory_usage::{Bytes, MemoryUsage}; // We use jemalloc mainly to get heap usage statistics, actual performance // difference is not measures. #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] #[global_allocator] static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; pub fn init() { set_filter(match std::env::var("RA_PROFILE") { Ok(spec) => Filter::from_spec(&spec), Err(_) => Filter::disabled(), }); } /// Set profiling filter. It specifies descriptions allowed to profile. /// This is helpful when call stack has too many nested profiling scopes. /// Additionally filter can specify maximum depth of profiling scopes nesting. /// /// #Example /// ``` /// use ra_prof::{set_filter, Filter}; /// let f = Filter::from_spec("profile1|profile2@2"); /// set_filter(f); /// ``` pub fn set_filter(f: Filter) { PROFILING_ENABLED.store(f.depth > 0, Ordering::SeqCst); let set: HashSet<_> = f.allowed.iter().cloned().collect(); let mut old = FILTER.write().unwrap(); let filter_data = FilterData { depth: f.depth, allowed: set, longer_than: f.longer_than, version: old.version + 1, }; *old = filter_data; } pub type Label = &'static str; /// This function starts a profiling scope in the current execution stack with a given description. /// It returns a Profile structure and measure elapsed time between this method invocation and Profile structure drop. /// It supports nested profiling scopes in case when this function invoked multiple times at the execution stack. In this case the profiling information will be nested at the output. /// Profiling information is being printed in the stderr. /// /// # Example /// ``` /// use ra_prof::{profile, set_filter, Filter}; /// /// let f = Filter::from_spec("profile1|profile2@2"); /// set_filter(f); /// profiling_function1(); /// /// fn profiling_function1() { /// let _p = profile("profile1"); /// profiling_function2(); /// } /// /// fn profiling_function2() { /// let _p = profile("profile2"); /// } /// ``` /// This will print in the stderr the following: /// ```text /// 0ms - profile /// 0ms - profile2 /// ``` pub fn profile(label: Label) -> Profiler { assert!(!label.is_empty()); if !PROFILING_ENABLED.load(Ordering::Relaxed) { return Profiler { label: None, detail: None }; } PROFILE_STACK.with(|stack| { let mut stack = stack.borrow_mut(); if stack.starts.is_empty() { if let Ok(f) = FILTER.try_read() { if f.version > stack.filter_data.version { stack.filter_data = f.clone(); } }; } if stack.starts.len() > stack.filter_data.depth { return Profiler { label: None, detail: None }; } let allowed = &stack.filter_data.allowed; if stack.starts.is_empty() && !allowed.is_empty() && !allowed.contains(label) { return Profiler { label: None, detail: None }; } stack.starts.push(Instant::now()); Profiler { label: Some(label), detail: None } }) } pub fn print_time(label: Label) -> impl Drop { struct Guard { label: Label, start: Instant, } impl Drop for Guard { fn drop(&mut self) { eprintln!("{}: {:?}", self.label, self.start.elapsed()) } } Guard { label, start: Instant::now() } } pub struct Profiler { label: Option