use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct PerformanceProfile {
pub name: String,
pub total_duration: Duration,
pub children_duration: Duration,
pub self_duration: Duration,
pub invocation_count: usize,
}
impl PerformanceProfile {
pub fn avg_duration(&self) -> Duration {
if self.invocation_count == 0 {
Duration::from_secs(0)
} else {
self.total_duration / self.invocation_count as u32
}
}
pub fn self_time_percentage(&self) -> f64 {
if self.total_duration.as_millis() == 0 {
0.0
} else {
(self.self_duration.as_millis() as f64 / self.total_duration.as_millis() as f64) * 100.0
}
}
}
pub struct PerformanceProfiler {
profiles: Arc<Mutex<HashMap<String, PerformanceProfile>>>,
active_spans: Arc<Mutex<Vec<(String, Instant)>>>,
}
impl PerformanceProfiler {
pub fn new() -> Self {
Self {
profiles: Arc::new(Mutex::new(HashMap::new())),
active_spans: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn start_span(&self, name: impl Into<String>) {
let name = name.into();
self.active_spans
.lock()
.unwrap()
.push((name, Instant::now()));
}
pub fn end_span(&self) {
let span_data = self
.active_spans
.lock()
.expect("lock should not be poisoned")
.pop();
if let Some((name, start_time)) = span_data {
let duration = start_time.elapsed();
let mut profiles = self.profiles.lock().expect("lock should not be poisoned");
let profile = profiles
.entry(name.clone())
.or_insert_with(|| PerformanceProfile {
name: name.clone(),
total_duration: Duration::from_secs(0),
children_duration: Duration::from_secs(0),
self_duration: Duration::from_secs(0),
invocation_count: 0,
});
profile.total_duration += duration;
profile.invocation_count += 1;
profile.self_duration = profile.total_duration - profile.children_duration;
}
}
pub fn get_profile(&self, name: &str) -> Option<PerformanceProfile> {
self.profiles
.lock()
.expect("lock should not be poisoned")
.get(name)
.cloned()
}
pub fn get_all_profiles(&self) -> Vec<PerformanceProfile> {
self.profiles
.lock()
.expect("lock should not be poisoned")
.values()
.cloned()
.collect()
}
pub fn get_slowest_operations(&self, limit: usize) -> Vec<PerformanceProfile> {
let mut profiles = self.get_all_profiles();
profiles.sort_by_key(|p| std::cmp::Reverse(p.total_duration));
profiles.truncate(limit);
profiles
}
pub fn generate_report(&self) -> String {
let profiles = self.get_all_profiles();
if profiles.is_empty() {
return "No profiling data available".to_string();
}
let mut report = String::from("Performance Profile Report\n");
report.push_str(&format!("{}\n", "=".repeat(80)));
for profile in profiles {
report.push_str(&format!(
"{:<30} | Count: {:>6} | Total: {:>8.2}ms | Avg: {:>8.2}ms | Self: {:>6.1}%\n",
profile.name,
profile.invocation_count,
profile.total_duration.as_secs_f64() * 1000.0,
profile.avg_duration().as_secs_f64() * 1000.0,
profile.self_time_percentage()
));
}
report
}
pub fn reset(&self) {
self.profiles
.lock()
.expect("lock should not be poisoned")
.clear();
self.active_spans
.lock()
.expect("lock should not be poisoned")
.clear();
}
}
impl Default for PerformanceProfiler {
fn default() -> Self {
Self::new()
}
}
impl Clone for PerformanceProfiler {
fn clone(&self) -> Self {
Self {
profiles: Arc::clone(&self.profiles),
active_spans: Arc::clone(&self.active_spans),
}
}
}
pub struct ProfileSpan<'a> {
profiler: &'a PerformanceProfiler,
}
impl<'a> ProfileSpan<'a> {
pub fn new(profiler: &'a PerformanceProfiler, name: impl Into<String>) -> Self {
profiler.start_span(name);
Self { profiler }
}
}
impl<'a> Drop for ProfileSpan<'a> {
fn drop(&mut self) {
self.profiler.end_span();
}
}