2019-05-07 12:54:33 -05:00
use std ::{
cell ::RefCell ,
time ::{ Duration , Instant } ,
mem ,
io ::{ stderr , Write } ,
iter ::repeat ,
collections ::HashSet ,
sync ::{ RwLock , atomic ::{ AtomicBool , Ordering } } ,
} ;
use once_cell ::sync ::Lazy ;
2019-03-27 10:09:51 -05:00
2019-04-03 08:05:55 -05: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
/// ```
2019-04-14 17:03:11 -05:00
/// use ra_prof::{set_filter, Filter};
/// let f = Filter::from_spec("profile1|profile2@2");
2019-04-03 08:05:55 -05:00
/// set_filter(f);
/// ```
2019-03-29 07:34:05 -05:00
pub fn set_filter ( f : Filter ) {
2019-04-14 14:54:57 -05:00
PROFILING_ENABLED . store ( f . depth > 0 , Ordering ::SeqCst ) ;
2019-05-07 12:54:33 -05:00
let set : HashSet < _ > = f . allowed . iter ( ) . cloned ( ) . collect ( ) ;
2019-03-29 07:34:05 -05:00
let mut old = FILTER . write ( ) . unwrap ( ) ;
2019-04-14 15:20:12 -05:00
let filter_data = FilterData {
depth : f . depth ,
allowed : set ,
longer_than : f . longer_than ,
version : old . version + 1 ,
} ;
2019-03-29 07:34:05 -05:00
* old = filter_data ;
}
2019-04-03 08:05:55 -05: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
/// ```
2019-04-14 17:03:11 -05:00
/// use ra_prof::{profile, set_filter, Filter};
2019-04-03 08:05:55 -05:00
///
2019-04-14 17:03:11 -05:00
/// let f = Filter::from_spec("profile1|profile2@2");
2019-04-03 08:05:55 -05:00
/// 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 07:34:05 -05:00
pub fn profile ( desc : & str ) -> Profiler {
2019-04-14 14:54:57 -05:00
assert! ( ! desc . is_empty ( ) ) ;
if ! PROFILING_ENABLED . load ( Ordering ::Relaxed ) {
return Profiler { desc : None } ;
}
2019-03-29 07:34:05 -05:00
PROFILE_STACK . with ( | stack | {
let mut stack = stack . borrow_mut ( ) ;
2019-04-26 10:42:56 -05:00
if stack . starts . is_empty ( ) {
if let Ok ( f ) = FILTER . try_read ( ) {
if f . version > stack . filter_data . version {
stack . filter_data = f . clone ( ) ;
2019-03-29 07:34:05 -05:00
}
} ;
}
2019-04-14 14:54:57 -05: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 07:34:05 -05:00
stack . starts . push ( Instant ::now ( ) ) ;
2019-04-14 14:54:57 -05:00
Profiler { desc : Some ( desc . to_string ( ) ) }
2019-03-29 07:34:05 -05:00
} else {
Profiler { desc : None }
}
} )
}
2019-03-27 10:09:51 -05:00
pub struct Profiler {
2019-03-29 07:34:05 -05:00
desc : Option < String > ,
2019-03-27 10:09:51 -05:00
}
pub struct Filter {
depth : usize ,
allowed : Vec < String > ,
2019-04-14 15:20:12 -05:00
longer_than : Duration ,
2019-03-27 10:09:51 -05:00
}
2019-04-02 09:52:04 -05:00
impl Filter {
2019-04-14 15:18:58 -05:00
// Filtering syntax
// env RA_PROFILE=* // dump everything
// env RA_PROFILE=foo|bar|baz // enabled only selected entries
// env RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 ms
pub fn from_spec ( mut spec : & str ) -> Filter {
2019-04-26 10:41:58 -05:00
let longer_than = if let Some ( idx ) = spec . rfind ( '>' ) {
2019-04-14 15:20:12 -05:00
let longer_than = spec [ idx + 1 .. ] . parse ( ) . expect ( " invalid profile longer_than " ) ;
2019-04-14 15:18:58 -05:00
spec = & spec [ .. idx ] ;
2019-04-14 15:20:12 -05:00
Duration ::from_millis ( longer_than )
2019-04-14 15:18:58 -05:00
} else {
Duration ::new ( 0 , 0 )
} ;
2019-04-26 10:41:58 -05:00
let depth = if let Some ( idx ) = spec . rfind ( '@' ) {
2019-04-14 15:18:58 -05:00
let depth : usize = spec [ idx + 1 .. ] . parse ( ) . expect ( " invalid profile depth " ) ;
spec = & spec [ .. idx ] ;
depth
} else {
999
} ;
let allowed =
2019-04-26 10:41:58 -05:00
if spec = = " * " { Vec ::new ( ) } else { spec . split ( '|' ) . map ( String ::from ) . collect ( ) } ;
2019-04-14 15:20:12 -05:00
Filter ::new ( depth , allowed , longer_than )
2019-04-14 15:18:58 -05:00
}
2019-04-14 15:04:08 -05:00
pub fn disabled ( ) -> Filter {
2019-04-14 15:18:58 -05:00
Filter ::new ( 0 , Vec ::new ( ) , Duration ::new ( 0 , 0 ) )
2019-04-14 15:04:08 -05:00
}
2019-04-14 15:20:12 -05:00
pub fn new ( depth : usize , allowed : Vec < String > , longer_than : Duration ) -> Filter {
Filter { depth , allowed , longer_than }
2019-04-02 09:52:04 -05:00
}
}
2019-03-27 10:09:51 -05:00
struct ProfileStack {
2019-03-29 07:34:05 -05:00
starts : Vec < Instant > ,
2019-03-27 10:09:51 -05:00
messages : Vec < Message > ,
filter_data : FilterData ,
}
2019-03-29 07:34:05 -05:00
struct Message {
level : usize ,
duration : Duration ,
message : String ,
}
2019-03-27 10:09:51 -05:00
impl ProfileStack {
fn new ( ) -> ProfileStack {
ProfileStack { starts : Vec ::new ( ) , messages : Vec ::new ( ) , filter_data : Default ::default ( ) }
}
}
2019-03-29 07:34:05 -05:00
#[ derive(Default, Clone) ]
2019-03-27 10:09:51 -05:00
struct FilterData {
depth : usize ,
version : usize ,
allowed : HashSet < String > ,
2019-04-14 15:20:12 -05:00
longer_than : Duration ,
2019-03-27 10:09:51 -05:00
}
2019-04-14 14:54:57 -05:00
static PROFILING_ENABLED : AtomicBool = AtomicBool ::new ( false ) ;
2019-05-07 12:54:33 -05:00
static FILTER : Lazy < RwLock < FilterData > > = Lazy ::new ( Default ::default ) ;
2019-03-27 10:09:51 -05:00
thread_local! ( static PROFILE_STACK : RefCell < ProfileStack > = RefCell ::new ( ProfileStack ::new ( ) ) ) ;
impl Drop for Profiler {
fn drop ( & mut self ) {
2019-03-29 07:34:05 -05: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 ( ) ;
2019-04-14 15:20:12 -05:00
if duration > = stack . filter_data . longer_than {
2019-04-14 15:18:58 -05:00
print ( 0 , & stack . messages , & mut stdout . lock ( ) ) ;
}
2019-03-29 07:34:05 -05:00
stack . messages . clear ( ) ;
}
} ) ;
2019-03-27 10:09:51 -05:00
}
2019-03-29 07:34:05 -05:00
Profiler { desc : None } = > ( ) ,
}
2019-03-27 10:09:51 -05:00
}
}
2019-03-29 07:34:05 -05:00
fn print ( lvl : usize , msgs : & [ Message ] , out : & mut impl Write ) {
2019-03-27 10:09:51 -05:00
let mut last = 0 ;
2019-03-29 07:34:05 -05: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 10:09:51 -05:00
if l ! = lvl {
continue ;
}
2019-03-29 07:34:05 -05: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 10:09:51 -05:00
last = i ;
}
}
#[ cfg(test) ]
mod tests {
2019-04-14 17:03:11 -05:00
use super ::* ;
2019-03-27 10:09:51 -05:00
#[ test ]
fn test_basic_profile ( ) {
let s = vec! [ " profile1 " . to_string ( ) , " profile2 " . to_string ( ) ] ;
2019-04-14 17:03:11 -05:00
let f = Filter ::new ( 2 , s , Duration ::new ( 0 , 0 ) ) ;
2019-03-27 10:09:51 -05:00
set_filter ( f ) ;
profiling_function1 ( ) ;
}
fn profiling_function1 ( ) {
let _p = profile ( " profile1 " ) ;
profiling_function2 ( ) ;
}
fn profiling_function2 ( ) {
let _p = profile ( " profile2 " ) ;
}
}