#![allow(unused_variables)]
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileResult {
pub name: String,
pub total_time: Duration,
pub call_count: usize,
pub avg_time: Duration,
pub min_time: Duration,
pub max_time: Duration,
pub self_time: Duration,
pub children: Vec<ProfileResult>,
pub percent_of_parent: f64,
}
impl ProfileResult {
pub fn new(name: String) -> Self {
Self {
name,
total_time: Duration::ZERO,
call_count: 0,
avg_time: Duration::ZERO,
min_time: Duration::MAX,
max_time: Duration::ZERO,
self_time: Duration::ZERO,
children: Vec::new(),
percent_of_parent: 0.0,
}
}
pub fn add_timing(&mut self, duration: Duration) {
self.total_time += duration;
self.call_count += 1;
self.min_time = self.min_time.min(duration);
self.max_time = self.max_time.max(duration);
self.avg_time = self.total_time / self.call_count as u32;
}
fn calculate_self_time(&mut self) {
let children_time: Duration = self.children.iter().map(|c| c.total_time).sum();
self.self_time = self.total_time.saturating_sub(children_time);
let total_ms = self.total_time.as_secs_f64() * 1000.0;
for child in &mut self.children {
child.percent_of_parent = if total_ms > 0.0 {
(child.total_time.as_secs_f64() * 1000.0 / total_ms) * 100.0
} else {
0.0
};
}
}
pub fn record_operation_start(&mut self, _name: &str) {
}
pub fn record_operation_end(&mut self, name: &str) {
let duration = Duration::from_millis(5);
self.add_timing(duration);
}
pub fn print(&self, indent: usize) {
let indent_str = " ".repeat(indent);
println!(
"{}{:<40} {:>8} {:>10.2}ms {:>10.2}ms {:>10.2}ms {:>6.1}%",
indent_str,
self.name,
self.call_count,
self.total_time.as_secs_f64() * 1000.0,
self.avg_time.as_secs_f64() * 1000.0,
self.self_time.as_secs_f64() * 1000.0,
self.percent_of_parent,
);
let mut children = self.children.clone();
children.sort_by_key(|child| std::cmp::Reverse(child.total_time));
for child in children {
child.print(indent + 2);
}
}
}
pub struct PerformanceProfiler {
stack: Arc<Mutex<Vec<ProfileNode>>>,
roots: Arc<Mutex<HashMap<String, ProfileResult>>>,
enabled: Arc<Mutex<bool>>,
}
#[derive(Debug)]
struct ProfileNode {
name: String,
start_time: Instant,
result: ProfileResult,
}
impl Default for PerformanceProfiler {
fn default() -> Self {
Self::new()
}
}
impl PerformanceProfiler {
pub fn new() -> Self {
Self {
stack: Arc::new(Mutex::new(Vec::new())),
roots: Arc::new(Mutex::new(HashMap::new())),
enabled: Arc::new(Mutex::new(false)),
}
}
pub fn enable(&self) {
*self.enabled.lock().expect("Lock poisoned") = true;
}
pub fn disable(&self) {
*self.enabled.lock().expect("Lock poisoned") = false;
}
pub fn is_enabled(&self) -> bool {
*self.enabled.lock().expect("Lock poisoned")
}
pub fn start_operation(&self, name: &str) -> ProfileGuard {
if !self.is_enabled() {
return ProfileGuard {
profiler: None,
name: String::new(),
};
}
let node = ProfileNode {
name: name.to_string(),
start_time: Instant::now(),
result: ProfileResult::new(name.to_string()),
};
self.stack.lock().expect("Lock poisoned").push(node);
ProfileGuard {
profiler: Some(self.clone()),
name: name.to_string(),
}
}
fn end_operation(&self, name: &str) {
let mut stack = self.stack.lock().expect("Lock poisoned");
if let Some(node) = stack.pop() {
if node.name != name {
eprintln!("Profile mismatch: expected {}, got {}", name, node.name);
return;
}
let duration = node.start_time.elapsed();
let mut result = node.result;
result.add_timing(duration);
if stack.is_empty() {
let mut roots = self.roots.lock().expect("Lock poisoned");
roots
.entry(name.to_string())
.and_modify(|r| {
r.total_time += duration;
r.call_count += 1;
r.avg_time = r.total_time / r.call_count as u32;
r.min_time = r.min_time.min(duration);
r.max_time = r.max_time.max(duration);
})
.or_insert(result);
} else {
if let Some(parent) = stack.last_mut() {
parent.result.children.push(result);
}
}
}
}
pub fn get_results(&self) -> HashMap<String, ProfileResult> {
let mut results = self.roots.lock().expect("Lock poisoned").clone();
for result in results.values_mut() {
result.calculate_self_time();
}
results
}
pub fn clear(&self) {
self.stack.lock().expect("Lock poisoned").clear();
self.roots.lock().expect("Lock poisoned").clear();
}
pub fn print_summary(&self) {
let results = self.get_results();
if results.is_empty() {
println!("No profile data collected.");
return;
}
println!("\n=== Performance Profile Summary ===");
println!(
"{:<40} {:>8} {:>10} {:>10} {:>10} {:>6}",
"Operation", "Calls", "Total (ms)", "Avg (ms)", "Self (ms)", "%"
);
println!("{}", "-".repeat(84));
let mut sorted: Vec<_> = results.values().collect();
sorted.sort_by_key(|item| std::cmp::Reverse(item.total_time));
let total_time: Duration = sorted.iter().map(|r| r.total_time).sum();
let total_ms = total_time.as_secs_f64() * 1000.0;
for result in sorted {
let percent = if total_ms > 0.0 {
(result.total_time.as_secs_f64() * 1000.0 / total_ms) * 100.0
} else {
0.0
};
println!(
"{:<40} {:>8} {:>10.2} {:>10.2} {:>10.2} {:>6.1}",
result.name,
result.call_count,
result.total_time.as_secs_f64() * 1000.0,
result.avg_time.as_secs_f64() * 1000.0,
result.self_time.as_secs_f64() * 1000.0,
percent,
);
}
println!("\nTotal time: {:.2}ms", total_ms);
}
pub fn export_flamegraph(&self, path: &str) -> Result<()> {
let results = self.get_results();
let mut file = std::fs::File::create(path)?;
for (name, result) in results {
self.write_flamegraph_entry(&mut file, &name, &result, Vec::new())?;
}
Ok(())
}
fn write_flamegraph_entry(
&self,
file: &mut std::fs::File,
name: &str,
result: &ProfileResult,
stack: Vec<String>,
) -> Result<()> {
Self::write_flamegraph_entry_helper(file, name, result, stack)
}
fn write_flamegraph_entry_helper(
file: &mut std::fs::File,
name: &str,
result: &ProfileResult,
mut stack: Vec<String>,
) -> Result<()> {
use std::io::Write;
stack.push(name.to_string());
let stack_str = stack.join(";");
let microseconds = result.self_time.as_micros();
if microseconds > 0 {
writeln!(file, "{} {}", stack_str, microseconds)?;
}
for child in &result.children {
Self::write_flamegraph_entry_helper(file, &child.name, child, stack.clone())?;
}
Ok(())
}
}
impl Clone for PerformanceProfiler {
fn clone(&self) -> Self {
Self {
stack: Arc::clone(&self.stack),
roots: Arc::clone(&self.roots),
enabled: Arc::clone(&self.enabled),
}
}
}
pub struct ProfileGuard {
profiler: Option<PerformanceProfiler>,
name: String,
}
impl Drop for ProfileGuard {
fn drop(&mut self) {
if let Some(profiler) = &self.profiler {
profiler.end_operation(&self.name);
}
}
}
#[macro_export]
macro_rules! profile {
($profiler:expr, $name:expr, $code:block) => {{
let _guard = $profiler.start_operation($name);
$code
}};
}
lazy_static::lazy_static! {
pub static ref GLOBAL_PROFILER: PerformanceProfiler = PerformanceProfiler::new();
}
pub fn profile_fn<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let _guard = GLOBAL_PROFILER.start_operation(name);
f()
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn test_basic_profiling() {
let profiler = PerformanceProfiler::new();
profiler.enable();
{
let _guard = profiler.start_operation("test_operation");
sleep(Duration::from_millis(10));
}
let results = profiler.get_results();
assert!(results.contains_key("test_operation"));
let result = &results["test_operation"];
assert_eq!(result.call_count, 1);
assert!(result.total_time >= Duration::from_millis(10));
}
#[test]
fn test_nested_profiling() {
let profiler = PerformanceProfiler::new();
profiler.enable();
{
let _outer = profiler.start_operation("outer");
sleep(Duration::from_millis(5));
{
let _inner = profiler.start_operation("inner");
sleep(Duration::from_millis(10));
}
sleep(Duration::from_millis(5));
}
let results = profiler.get_results();
let outer = &results["outer"];
assert_eq!(outer.call_count, 1);
assert!(outer.total_time >= Duration::from_millis(20));
assert_eq!(outer.children.len(), 1);
let inner = &outer.children[0];
assert_eq!(inner.name, "inner");
assert!(inner.total_time >= Duration::from_millis(10));
}
#[test]
fn test_profile_macro() {
let profiler = PerformanceProfiler::new();
profiler.enable();
profile!(profiler, "macro_test", {
sleep(Duration::from_millis(5));
});
let results = profiler.get_results();
assert!(results.contains_key("macro_test"));
}
}