2019-03-27 15:09:51 +00:00
use std ::cell ::RefCell ;
2019-03-29 12:34:05 +00:00
use std ::time ::{ Duration , Instant } ;
2019-03-27 15:09:51 +00:00
use std ::mem ;
2019-03-29 12:34:05 +00:00
use std ::io ::{ stderr , Write } ;
2019-03-27 15:09:51 +00:00
use std ::iter ::repeat ;
use std ::collections ::{ HashSet } ;
use std ::default ::Default ;
use std ::iter ::FromIterator ;
2019-04-14 19:54:57 +00:00
use std ::sync ::{ RwLock , atomic ::{ AtomicBool , Ordering } } ;
2019-03-27 15:09:51 +00:00
use lazy_static ::lazy_static ;
2019-04-03 13:05:55 +00:00
/// 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;
/// use ra_prof::Filter;
/// let max_depth = 2;
/// let allowed = vec!["profile1".to_string(), "profile2".to_string()];
/// let f = Filter::new( max_depth, allowed );
/// set_filter(f);
/// ```
///
2019-03-29 12:34:05 +00:00
pub fn set_filter ( f : Filter ) {
2019-04-14 19:54:57 +00:00
PROFILING_ENABLED . store ( f . depth > 0 , Ordering ::SeqCst ) ;
2019-03-29 12:34:05 +00:00
let set = HashSet ::from_iter ( f . allowed . iter ( ) . cloned ( ) ) ;
let mut old = FILTER . write ( ) . unwrap ( ) ;
let filter_data = FilterData { depth : f . depth , allowed : set , version : old . version + 1 } ;
* old = filter_data ;
}
2019-04-03 13:05:55 +00:00
/// 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;
/// use ra_prof::set_filter;
/// use ra_prof::Filter;
///
/// let allowed = vec!["profile1".to_string(), "profile2".to_string()];
/// let f = Filter::new(2, allowed);
/// 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
/// ```
///
2019-03-29 12:34:05 +00:00
pub fn profile ( desc : & str ) -> Profiler {
2019-04-14 19:54:57 +00:00
assert! ( ! desc . is_empty ( ) ) ;
if ! PROFILING_ENABLED . load ( Ordering ::Relaxed ) {
return Profiler { desc : None } ;
}
2019-03-29 12:34:05 +00:00
PROFILE_STACK . with ( | stack | {
let mut stack = stack . borrow_mut ( ) ;
if stack . starts . len ( ) = = 0 {
match FILTER . try_read ( ) {
Ok ( f ) = > {
if f . version > stack . filter_data . version {
stack . filter_data = f . clone ( ) ;
}
}
Err ( _ ) = > ( ) ,
} ;
}
2019-04-14 19:54:57 +00:00
if stack . starts . len ( ) > stack . filter_data . depth {
return Profiler { desc : None } ;
}
if stack . filter_data . allowed . is_empty ( ) | | stack . filter_data . allowed . contains ( desc ) {
2019-03-29 12:34:05 +00:00
stack . starts . push ( Instant ::now ( ) ) ;
2019-04-14 19:54:57 +00:00
Profiler { desc : Some ( desc . to_string ( ) ) }
2019-03-29 12:34:05 +00:00
} else {
Profiler { desc : None }
}
} )
}
2019-03-27 15:09:51 +00:00
pub struct Profiler {
2019-03-29 12:34:05 +00:00
desc : Option < String > ,
2019-03-27 15:09:51 +00:00
}
pub struct Filter {
depth : usize ,
allowed : Vec < String > ,
}
2019-04-02 14:52:04 +00:00
impl Filter {
pub fn new ( depth : usize , allowed : Vec < String > ) -> Filter {
Filter { depth , allowed }
}
}
2019-03-27 15:09:51 +00:00
struct ProfileStack {
2019-03-29 12:34:05 +00:00
starts : Vec < Instant > ,
2019-03-27 15:09:51 +00:00
messages : Vec < Message > ,
filter_data : FilterData ,
}
2019-03-29 12:34:05 +00:00
struct Message {
level : usize ,
duration : Duration ,
message : String ,
}
2019-03-27 15:09:51 +00:00
impl ProfileStack {
fn new ( ) -> ProfileStack {
ProfileStack { starts : Vec ::new ( ) , messages : Vec ::new ( ) , filter_data : Default ::default ( ) }
}
}
2019-03-29 12:34:05 +00:00
#[ derive(Default, Clone) ]
2019-03-27 15:09:51 +00:00
struct FilterData {
depth : usize ,
version : usize ,
allowed : HashSet < String > ,
}
2019-04-14 19:54:57 +00:00
static PROFILING_ENABLED : AtomicBool = AtomicBool ::new ( false ) ;
2019-03-27 15:09:51 +00:00
lazy_static! {
static ref FILTER : RwLock < FilterData > = RwLock ::new ( Default ::default ( ) ) ;
}
thread_local! ( static PROFILE_STACK : RefCell < ProfileStack > = RefCell ::new ( ProfileStack ::new ( ) ) ) ;
impl Drop for Profiler {
fn drop ( & mut self ) {
2019-03-29 12:34:05 +00:00
match self {
Profiler { desc : Some ( desc ) } = > {
PROFILE_STACK . with ( | stack | {
let mut stack = stack . borrow_mut ( ) ;
let start = stack . starts . pop ( ) . unwrap ( ) ;
let duration = start . elapsed ( ) ;
let level = stack . starts . len ( ) ;
let message = mem ::replace ( desc , String ::new ( ) ) ;
stack . messages . push ( Message { level , duration , message } ) ;
if level = = 0 {
let stdout = stderr ( ) ;
print ( 0 , & stack . messages , & mut stdout . lock ( ) ) ;
stack . messages . clear ( ) ;
}
} ) ;
2019-03-27 15:09:51 +00:00
}
2019-03-29 12:34:05 +00:00
Profiler { desc : None } = > ( ) ,
}
2019-03-27 15:09:51 +00:00
}
}
2019-03-29 12:34:05 +00:00
fn print ( lvl : usize , msgs : & [ Message ] , out : & mut impl Write ) {
2019-03-27 15:09:51 +00:00
let mut last = 0 ;
2019-03-29 12:34:05 +00:00
let indent = repeat ( " " ) . take ( lvl + 1 ) . collect ::< String > ( ) ;
for ( i , & Message { level : l , duration : dur , message : ref msg } ) in msgs . iter ( ) . enumerate ( ) {
2019-03-27 15:09:51 +00:00
if l ! = lvl {
continue ;
}
2019-03-29 12:34:05 +00:00
writeln! ( out , " {} {:6}ms - {} " , indent , dur . as_millis ( ) , msg )
. expect ( " printing profiling info to stdout " ) ;
print ( lvl + 1 , & msgs [ last .. i ] , out ) ;
2019-03-27 15:09:51 +00:00
last = i ;
}
}
#[ cfg(test) ]
mod tests {
use super ::profile ;
use super ::set_filter ;
use super ::Filter ;
#[ test ]
fn test_basic_profile ( ) {
let s = vec! [ " profile1 " . to_string ( ) , " profile2 " . to_string ( ) ] ;
2019-04-03 13:05:55 +00:00
let f = Filter ::new ( 2 , s ) ;
2019-03-27 15:09:51 +00:00
set_filter ( f ) ;
profiling_function1 ( ) ;
}
fn profiling_function1 ( ) {
let _p = profile ( " profile1 " ) ;
profiling_function2 ( ) ;
}
fn profiling_function2 ( ) {
let _p = profile ( " profile2 " ) ;
}
}