use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct Profiler {
timings: HashMap<String, Vec<Duration>>,
current_timers: HashMap<String, Instant>,
}
impl Profiler {
pub fn new() -> Self {
Self {
timings: HashMap::new(),
current_timers: HashMap::new(),
}
}
pub fn start(&mut self, operation: &str) {
self.current_timers
.insert(operation.to_string(), Instant::now());
}
pub fn stop(&mut self, operation: &str) {
if let Some(start) = self.current_timers.remove(operation) {
let duration = start.elapsed();
self.timings
.entry(operation.to_string())
.or_default()
.push(duration);
}
}
pub fn time<F, R>(&mut self, operation: &str, f: F) -> R
where
F: FnOnce() -> R,
{
self.start(operation);
let result = f();
self.stop(operation);
result
}
pub fn summary(&self) -> HashMap<String, TimingStats> {
self.timings
.iter()
.map(|(name, durations)| {
let total: Duration = durations.iter().sum();
let count = durations.len();
let avg = if count > 0 {
total / count as u32
} else {
Duration::ZERO
};
let min = durations.iter().min().copied().unwrap_or(Duration::ZERO);
let max = durations.iter().max().copied().unwrap_or(Duration::ZERO);
(
name.clone(),
TimingStats {
total,
count,
avg,
min,
max,
},
)
})
.collect()
}
pub fn print_summary(&self) {
let summary = self.summary();
eprintln!("\n=== Profiling Summary ===");
eprintln!(
"{:<30} {:>10} {:>10} {:>10} {:>10} {:>10}",
"Operation", "Count", "Total (ms)", "Avg (ms)", "Min (ms)", "Max (ms)"
);
eprintln!("{}", "-".repeat(90));
let mut sorted: Vec<_> = summary.iter().collect();
sorted.sort_by(|a, b| b.1.total.cmp(&a.1.total));
for (name, stats) in sorted {
eprintln!(
"{:<30} {:>10} {:>10.2} {:>10.2} {:>10.2} {:>10.2}",
name,
stats.count,
stats.total.as_secs_f64() * 1000.0,
stats.avg.as_secs_f64() * 1000.0,
stats.min.as_secs_f64() * 1000.0,
stats.max.as_secs_f64() * 1000.0,
);
}
eprintln!();
}
}
impl Default for Profiler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TimingStats {
pub total: Duration,
pub count: usize,
pub avg: Duration,
pub min: Duration,
pub max: Duration,
}
#[cfg(feature = "eval-profiling")]
thread_local! {
static PROFILER: std::cell::RefCell<Profiler> = std::cell::RefCell::new(Profiler::new());
}
#[cfg(feature = "eval-profiling")]
pub fn start(operation: &str) {
PROFILER.with(|p| p.borrow_mut().start(operation));
}
#[cfg(feature = "eval-profiling")]
pub fn stop(operation: &str) {
PROFILER.with(|p| p.borrow_mut().stop(operation));
}
#[cfg(feature = "eval-profiling")]
pub fn time<F, R>(operation: &str, f: F) -> R
where
F: FnOnce() -> R,
{
PROFILER.with(|p| p.borrow_mut().time(operation, f))
}
#[cfg(feature = "eval-profiling")]
pub fn print_summary() {
PROFILER.with(|p| p.borrow().print_summary());
}
#[cfg(not(feature = "eval-profiling"))]
pub fn start(_operation: &str) {}
#[cfg(not(feature = "eval-profiling"))]
pub fn stop(_operation: &str) {}
#[cfg(not(feature = "eval-profiling"))]
pub fn time<F, R>(_operation: &str, f: F) -> R
where
F: FnOnce() -> R,
{
f()
}
#[cfg(not(feature = "eval-profiling"))]
pub fn print_summary() {}