#[cfg(feature = "dev-tools")]
use std::collections::HashMap;
#[cfg(feature = "dev-tools")]
use std::fmt;
#[cfg(feature = "dev-tools")]
use std::time::{Duration, Instant};
#[cfg(feature = "dev-tools")]
#[derive(Debug)]
pub struct DependencyProfiler {
records: HashMap<String, Vec<ProfileRecord>>,
active: HashMap<String, Instant>,
}
#[cfg(feature = "dev-tools")]
#[derive(Debug, Clone)]
pub struct ProfileRecord {
pub name: String,
pub duration: Duration,
pub timestamp: Instant,
pub cached: bool,
}
#[cfg(feature = "dev-tools")]
#[derive(Debug, Clone)]
pub struct ProfileReport {
pub dependencies: HashMap<String, DependencyStats>,
pub total_resolutions: usize,
pub total_duration: Duration,
}
#[cfg(feature = "dev-tools")]
#[derive(Debug, Clone)]
pub struct DependencyStats {
pub name: String,
pub count: usize,
pub cache_hits: usize,
pub avg_duration: Duration,
pub min_duration: Duration,
pub max_duration: Duration,
pub total_duration: Duration,
}
#[cfg(feature = "dev-tools")]
impl DependencyProfiler {
pub fn new() -> Self {
Self {
records: HashMap::new(),
active: HashMap::new(),
}
}
pub fn start_resolve(&mut self, name: impl Into<String>) {
let name = name.into();
self.active.insert(name, Instant::now());
}
pub fn end_resolve(&mut self, name: impl Into<String>) {
self.end_resolve_with_cache(name, false);
}
pub fn end_resolve_with_cache(&mut self, name: impl Into<String>, cached: bool) {
let name = name.into();
if let Some(start) = self.active.remove(&name) {
let duration = start.elapsed();
let record = ProfileRecord {
name: name.clone(),
duration,
timestamp: start,
cached,
};
self.records.entry(name).or_default().push(record);
}
}
pub fn record(&mut self, name: impl Into<String>, duration: Duration, cached: bool) {
let name = name.into();
let record = ProfileRecord {
name: name.clone(),
duration,
timestamp: Instant::now(),
cached,
};
self.records.entry(name).or_default().push(record);
}
pub fn generate_report(&self) -> ProfileReport {
let mut dependencies = HashMap::new();
let mut total_resolutions = 0;
let mut total_duration = Duration::ZERO;
for (name, records) in &self.records {
let count = records.len();
let cache_hits = records.iter().filter(|r| r.cached).count();
let mut durations: Vec<Duration> = records.iter().map(|r| r.duration).collect();
durations.sort();
let min_duration = durations.first().copied().unwrap_or(Duration::ZERO);
let max_duration = durations.last().copied().unwrap_or(Duration::ZERO);
let total_dep_duration: Duration = durations.iter().sum();
let avg_duration = if count > 0 {
total_dep_duration / count as u32
} else {
Duration::ZERO
};
dependencies.insert(
name.clone(),
DependencyStats {
name: name.clone(),
count,
cache_hits,
avg_duration,
min_duration,
max_duration,
total_duration: total_dep_duration,
},
);
total_resolutions += count;
total_duration += total_dep_duration;
}
ProfileReport {
dependencies,
total_resolutions,
total_duration,
}
}
pub fn clear(&mut self) {
self.records.clear();
self.active.clear();
}
}
#[cfg(feature = "dev-tools")]
impl Default for DependencyProfiler {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "dev-tools")]
impl ProfileReport {
pub fn slowest_dependencies(&self, n: usize) -> Vec<&DependencyStats> {
let mut deps: Vec<_> = self.dependencies.values().collect();
deps.sort_by(|a, b| b.avg_duration.cmp(&a.avg_duration));
deps.into_iter().take(n).collect()
}
pub fn low_cache_hit_dependencies(&self, threshold: f64) -> Vec<&DependencyStats> {
self.dependencies
.values()
.filter(|s| {
if s.count == 0 {
false
} else {
(s.cache_hits as f64 / s.count as f64) < threshold
}
})
.collect()
}
}
#[cfg(feature = "dev-tools")]
impl fmt::Display for ProfileReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== Dependency Injection Profile Report ===\n")?;
writeln!(f, "Total Resolutions: {}", self.total_resolutions)?;
writeln!(
f,
"Total Time: {:.2}ms\n",
self.total_duration.as_secs_f64() * 1000.0
)?;
let mut deps: Vec<_> = self.dependencies.values().collect();
deps.sort_by(|a, b| b.total_duration.cmp(&a.total_duration));
writeln!(f, "Per-Dependency Statistics:")?;
writeln!(
f,
"{:<30} {:>10} {:>10} {:>10} {:>10} {:>10}",
"Name", "Count", "Cache Hits", "Avg (ms)", "Min (ms)", "Max (ms)"
)?;
writeln!(f, "{}", "-".repeat(90))?;
for stats in deps {
writeln!(
f,
"{:<30} {:>10} {:>10} {:>10.2} {:>10.2} {:>10.2}",
stats.name,
stats.count,
stats.cache_hits,
stats.avg_duration.as_secs_f64() * 1000.0,
stats.min_duration.as_secs_f64() * 1000.0,
stats.max_duration.as_secs_f64() * 1000.0,
)?;
}
Ok(())
}
}