2019-06-30 05:30:17 -05:00
mod memory_usage ;
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-05-21 16:00:58 -05:00
use itertools ::Itertools ;
2019-03-27 10:09:51 -05:00
2019-06-30 05:30:17 -05:00
pub use crate ::memory_usage ::{ MemoryUsage , Bytes } ;
// We use jemalloc mainly to get heap usage statistics, actual performance
// difference is not measures.
#[ cfg(feature = " jemalloc " ) ]
#[ global_allocator ]
static ALLOC : jemallocator ::Jemalloc = jemallocator ::Jemalloc ;
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 } ;
}
2019-06-01 02:41:47 -05:00
let allowed = & stack . filter_data . allowed ;
if stack . starts . is_empty ( ) & & ! allowed . is_empty ( ) & & ! allowed . contains ( desc ) {
return Profiler { desc : None } ;
2019-03-29 07:34:05 -05:00
}
2019-06-01 02:41:47 -05:00
stack . starts . push ( Instant ::now ( ) ) ;
Profiler { desc : Some ( desc . to_string ( ) ) }
2019-03-29 07:34:05 -05:00
} )
}
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-05-21 16:00:58 -05:00
let longer_than = stack . filter_data . longer_than ;
if duration > = longer_than {
print ( 0 , & stack . messages , & mut stdout . lock ( ) , longer_than ) ;
2019-04-14 15:18:58 -05:00
}
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-05-21 16:00:58 -05:00
fn print ( lvl : usize , msgs : & [ Message ] , out : & mut impl Write , longer_than : Duration ) {
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 > ( ) ;
2019-05-21 16:00:58 -05:00
// We output hierarchy for long calls, but sum up all short calls
let mut short = Vec ::new ( ) ;
for ( i , & Message { level , duration , message : ref msg } ) in msgs . iter ( ) . enumerate ( ) {
if level ! = lvl {
2019-03-27 10:09:51 -05:00
continue ;
}
2019-05-21 16:00:58 -05:00
if duration > = longer_than {
writeln! ( out , " {} {:6}ms - {} " , indent , duration . as_millis ( ) , msg )
. expect ( " printing profiling info to stdout " ) ;
2019-05-21 08:24:53 -05:00
2019-05-21 16:00:58 -05:00
print ( lvl + 1 , & msgs [ last .. i ] , out , longer_than ) ;
} else {
short . push ( ( msg , duration ) )
}
2019-03-29 07:34:05 -05:00
2019-03-27 10:09:51 -05:00
last = i ;
}
2019-05-21 16:00:58 -05:00
short . sort_by_key ( | ( msg , _time ) | * msg ) ;
for ( msg , entires ) in short . iter ( ) . group_by ( | ( msg , _time ) | msg ) . into_iter ( ) {
let mut count = 0 ;
let mut total_duration = Duration ::default ( ) ;
entires . for_each ( | ( _msg , time ) | {
count + = 1 ;
total_duration + = * time ;
} ) ;
writeln! ( out , " {} {:6}ms - {} ({} calls) " , indent , total_duration . as_millis ( ) , msg , count )
. expect ( " printing profiling info to stdout " ) ;
}
2019-03-27 10:09:51 -05:00
}
2019-06-03 16:25:43 -05:00
/// Prints backtrace to stderr, useful for debugging.
pub fn print_backtrace ( ) {
let bt = backtrace ::Backtrace ::new ( ) ;
eprintln! ( " {:?} " , bt ) ;
}
thread_local! ( static IN_SCOPE : RefCell < bool > = RefCell ::new ( false ) ) ;
/// Allows to check if the current code is withing some dynamic scope, can be
/// useful during debugging to figure out why a function is called.
pub struct Scope {
2019-06-04 06:46:22 -05:00
prev : bool ,
2019-06-03 16:25:43 -05:00
}
impl Scope {
pub fn enter ( ) -> Scope {
2019-06-04 06:46:22 -05:00
let prev = IN_SCOPE . with ( | slot | std ::mem ::replace ( & mut * slot . borrow_mut ( ) , true ) ) ;
Scope { prev }
2019-06-03 16:25:43 -05:00
}
pub fn is_active ( ) -> bool {
IN_SCOPE . with ( | slot | * slot . borrow ( ) )
}
}
impl Drop for Scope {
fn drop ( & mut self ) {
2019-06-04 06:46:22 -05:00
IN_SCOPE . with ( | slot | * slot . borrow_mut ( ) = self . prev ) ;
2019-06-03 16:25:43 -05:00
}
}
2019-06-26 03:11:28 -05:00
/// A wrapper around https://github.com/AtheMathmo/cpuprofiler
///
/// It can be used to capture sampling profiles of sections of code.
/// It is not exactly out-of-the-box, as it relies on gperftools.
/// See the docs for the crate for more!
#[ derive(Debug) ]
pub struct CpuProfiler {
_private : ( ) ,
}
pub fn cpu_profiler ( ) -> CpuProfiler {
#[ cfg(feature = " cpuprofiler " ) ]
{
cpuprofiler ::PROFILER . lock ( ) . unwrap ( ) . start ( " ./out.profile " ) . unwrap ( ) ;
}
#[ cfg(not(feature = " cpuprofiler " )) ]
{
eprintln! ( " cpuprofiler feature is disabled " )
}
CpuProfiler { _private : ( ) }
}
impl Drop for CpuProfiler {
fn drop ( & mut self ) {
#[ cfg(feature = " cpuprofiler " ) ]
{
cpuprofiler ::PROFILER . lock ( ) . unwrap ( ) . stop ( ) . unwrap ( ) ;
}
}
}
2019-06-30 05:30:17 -05:00
pub fn memory_usage ( ) -> MemoryUsage {
MemoryUsage ::current ( )
}
2019-03-27 10:09:51 -05:00
#[ 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 " ) ;
}
}