use std::cell::RefCell;
use std::io;
use std::rc::Rc;
use std::time::Duration;
use instant::Instant;
thread_local!(
pub static PROFILER: RefCell<Profiler> = RefCell::new(Profiler::new())
);
pub fn write<W: io::Write>(out: &mut W) -> io::Result<()> {
PROFILER.with(|p| p.borrow().write(out))
}
pub fn reset() {
PROFILER.with(|p| p.borrow_mut().reset());
}
pub fn enter(name: &'static str) -> Guard {
PROFILER.with(|p| p.borrow_mut().enter(name))
}
#[macro_export]
macro_rules! profile {
($name:expr) => {
let _guard = $crate::PROFILER.with(|p| p.borrow_mut().enter($name));
};
}
struct Scope {
name: &'static str,
pred: Option<Rc<RefCell<Scope>>>,
succs: Vec<Rc<RefCell<Scope>>>,
num_calls: usize,
duration_sum: Duration,
duration_min: Duration,
duration_max: Duration,
}
impl Scope {
fn new(name: &'static str, pred: Option<Rc<RefCell<Scope>>>) -> Scope {
Scope {
name,
pred,
succs: Vec::new(),
num_calls: 0,
duration_sum: Duration::new(0, 0),
duration_min: Duration::new(u64::MAX, u32::MIN),
duration_max: Duration::new(0, 0),
}
}
fn enter(&mut self) -> Guard {
Guard::enter()
}
fn leave(&mut self, duration: Duration) {
self.num_calls += 1;
let duration_sum = self.duration_sum.checked_add(duration);
self.duration_sum = duration_sum.unwrap_or(Duration::from_millis(0));
self.duration_min = self.duration_min.min(duration);
self.duration_max = self.duration_max.max(duration);
}
fn write_recursive<W: io::Write>(
&self,
out: &mut W,
total_duration: Duration,
depth: usize,
) -> io::Result<()> {
let total_duration_secs = total_duration.as_secs_f64();
let duration_sum_secs = self.duration_sum.as_secs_f64();
let pred_sum_secs = self.pred.clone().map_or(total_duration_secs, |pred| {
pred.borrow().duration_sum.as_secs_f64()
});
let percent = duration_sum_secs / pred_sum_secs * 100.0;
for _ in 0..depth {
write!(out, " ")?;
}
writeln!(
out,
"{}: {:3.2}%, {:>4.2}ms avg, {:>4.2}ms min, {:>4.2}ms max @ {:.2}Hz",
self.name,
percent,
duration_sum_secs * 1000.0 / (self.num_calls as f64),
self.duration_min.as_secs_f64() * 1000.0,
self.duration_max.as_secs_f64() * 1000.0,
self.num_calls as f64 / total_duration_secs,
)?;
for succ in &self.succs {
succ.borrow()
.write_recursive(out, total_duration, depth + 1)?;
}
Ok(())
}
}
pub struct Guard {
enter_time: Instant,
}
impl Guard {
fn enter() -> Self {
Self {
enter_time: Instant::now(),
}
}
}
impl Drop for Guard {
fn drop(&mut self) {
let duration = self.enter_time.elapsed();
PROFILER.with(|p| p.borrow_mut().leave(duration));
}
}
pub struct Profiler {
roots: Vec<Rc<RefCell<Scope>>>,
current: Option<Rc<RefCell<Scope>>>,
start_time: Instant,
}
impl Profiler {
fn new() -> Profiler {
Profiler {
roots: Vec::new(),
current: None,
start_time: Instant::now(),
}
}
pub fn enter(&mut self, name: &'static str) -> Guard {
let succ = if let Some(current) = self.current.as_ref() {
let existing_succ = current
.borrow()
.succs
.iter()
.find(|succ| succ.borrow().name == name)
.cloned();
existing_succ.unwrap_or_else(|| {
let new_scope = Scope::new(name, Some(current.clone()));
let succ = Rc::new(RefCell::new(new_scope));
current.borrow_mut().succs.push(succ.clone());
succ
})
} else {
let existing_root = self
.roots
.iter()
.find(|root| root.borrow().name == name)
.cloned();
existing_root.unwrap_or_else(|| {
let new_scope = Scope::new(name, None);
let succ = Rc::new(RefCell::new(new_scope));
self.roots.push(succ.clone());
succ
})
};
let guard = succ.borrow_mut().enter();
self.current = Some(succ);
guard
}
fn reset(&mut self) {
self.roots.clear();
self.start_time = Instant::now();
}
fn leave(&mut self, duration: Duration) {
self.current = if let Some(current) = self.current.as_ref() {
current.borrow_mut().leave(duration);
current.borrow().pred.as_ref().cloned()
} else {
log::error!("Called coarse_prof::leave() while not in any scope");
None
};
}
fn write<W: io::Write>(&self, out: &mut W) -> io::Result<()> {
let total_duration = Instant::now().duration_since(self.start_time);
for root in self.roots.iter() {
root.borrow().write_recursive(out, total_duration, 0)?;
}
out.flush()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_multiple_roots() {
super::reset();
for i in 0..=5 {
if i == 5 {
profile!("a");
}
{
profile!("b");
}
}
super::PROFILER.with(|p| {
let p = p.borrow();
assert_eq!(p.roots.len(), 2);
for root in p.roots.iter() {
assert!(root.borrow().pred.is_none());
assert!(root.borrow().succs.is_empty());
}
assert_eq!(p.roots[0].borrow().name, "b");
assert_eq!(p.roots[1].borrow().name, "a");
assert_eq!(p.roots[0].borrow().num_calls, 6);
assert_eq!(p.roots[1].borrow().num_calls, 1);
});
}
#[test]
fn test_succ_reuse() {
use std::ptr;
super::reset();
for i in 0..=5 {
profile!("a");
if i > 2 {
profile!("b");
}
}
assert_eq!(super::PROFILER.with(|p| p.borrow().roots.len()), 1);
super::PROFILER.with(|p| {
let p = p.borrow();
assert_eq!(p.roots.len(), 1);
let root = p.roots[0].borrow();
assert_eq!(root.name, "a");
assert!(root.pred.is_none());
assert_eq!(root.succs.len(), 1);
assert_eq!(root.num_calls, 6);
let succ = root.succs[0].borrow();
assert_eq!(succ.name, "b");
assert!(ptr::eq(
succ.pred.as_ref().unwrap().as_ref(),
p.roots[0].as_ref()
));
assert!(succ.succs.is_empty());
assert_eq!(succ.num_calls, 3);
});
}
#[test]
fn test_reset_during_frame() {
super::reset();
for i in 0..=5 {
profile!("a");
profile!("b");
{
profile!("c");
if i == 5 {
super::reset();
}
assert!(super::PROFILER.with(|p| p.borrow().current.is_some()));
profile!("d");
}
}
super::PROFILER.with(|p| {
let p = p.borrow();
assert!(p.roots.is_empty());
assert!(p.current.is_none());
});
}
}