use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct Timer {
name: String,
start_time: Instant,
running: bool,
auto_report: bool,
parent: Option<String>,
}
impl Timer {
pub fn start(name: &str) -> Self {
let timer = Self {
name: name.to_string(),
start_time: Instant::now(),
running: true,
auto_report: true,
parent: None,
};
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_timer_start(&timer);
}
timer
}
pub fn start_with_parent(name: &str, parent: &str) -> Self {
let timer = Self {
name: name.to_string(),
start_time: Instant::now(),
running: true,
auto_report: true,
parent: Some(parent.to_string()),
};
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_timer_start(&timer);
}
timer
}
pub fn time_function<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let timer = Self::start(name);
let result = f();
timer.stop();
result
}
pub fn time_function_with_parent<F, R>(name: &str, parent: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let timer = Self::start_with_parent(name, parent);
let result = f();
timer.stop();
result
}
pub fn stop(&self) {
if !self.running {
return;
}
let elapsed = self.start_time.elapsed();
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_timer_stop(&self.name, elapsed, self.parent.as_deref());
}
}
pub fn elapsed(&self) -> Duration {
self.start_time.elapsed()
}
pub fn without_auto_report(mut self) -> Self {
self.auto_report = false;
self
}
}
impl Drop for Timer {
fn drop(&mut self) {
if self.running && self.auto_report {
let elapsed = self.start_time.elapsed();
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_timer_stop(&self.name, elapsed, self.parent.as_deref());
}
}
}
}
pub struct MemoryTracker {
name: String,
start_memory: usize,
running: bool,
auto_report: bool,
}
impl MemoryTracker {
pub fn start(name: &str) -> Self {
let current_memory = Self::current_memory_usage();
let tracker = Self {
name: name.to_string(),
start_memory: current_memory,
running: true,
auto_report: true,
};
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_memory_tracker_start(&tracker);
}
tracker
}
pub fn stop(&self) {
if !self.running {
return;
}
let current_memory = Self::current_memory_usage();
let memory_delta = current_memory.saturating_sub(self.start_memory);
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_memory_tracker_stop(&self.name, memory_delta);
}
}
pub fn track_function<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let tracker = Self::start(name);
let result = f();
tracker.stop();
result
}
pub fn memory_delta(&self) -> isize {
let current_memory = Self::current_memory_usage();
current_memory as isize - self.start_memory as isize
}
pub fn without_auto_report(mut self) -> Self {
self.auto_report = false;
self
}
fn current_memory_usage() -> usize {
#[cfg(target_os = "linux")]
{
0
}
#[cfg(target_os = "macos")]
{
0
}
#[cfg(target_os = "windows")]
{
0
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
0
}
}
}
impl Drop for MemoryTracker {
fn drop(&mut self) {
if self.running && self.auto_report {
let current_memory = Self::current_memory_usage();
let memory_delta = current_memory.saturating_sub(self.start_memory);
if let Ok(mut profiler) = Profiler::global().lock() {
profiler.register_memory_tracker_stop(&self.name, memory_delta);
}
}
}
}
#[derive(Debug, Clone)]
struct TimingEntry {
calls: usize,
total_duration: Duration,
min_duration: Duration,
max_duration: Duration,
#[allow(dead_code)]
parent: Option<String>,
children: Vec<String>,
}
impl TimingEntry {
fn new(duration: Duration, parent: Option<&str>) -> Self {
Self {
calls: 1,
total_duration: duration,
min_duration: duration,
max_duration: duration,
parent: parent.map(String::from),
children: Vec::new(),
}
}
fn add_measurement(&mut self, duration: Duration) {
self.calls += 1;
self.total_duration += duration;
self.min_duration = std::cmp::min(self.min_duration, duration);
self.max_duration = std::cmp::max(self.max_duration, duration);
}
fn add_child(&mut self, child: &str) {
if !self.children.contains(&child.to_string()) {
self.children.push(child.to_string());
}
}
fn average_duration(&self) -> Duration {
if self.calls == 0 {
Duration::from_secs(0)
} else {
self.total_duration / self.calls as u32
}
}
}
#[derive(Debug, Clone)]
struct MemoryEntry {
allocations: usize,
total_delta: isize,
max_delta: usize,
}
impl MemoryEntry {
fn new(delta: usize) -> Self {
Self {
allocations: 1,
total_delta: delta as isize,
max_delta: delta,
}
}
fn add_measurement(&mut self, delta: usize) {
self.allocations += 1;
self.total_delta += delta as isize;
self.max_delta = std::cmp::max(self.max_delta, delta);
}
#[allow(dead_code)]
fn average_delta(&self) -> f64 {
if self.allocations == 0 {
0.0
} else {
self.total_delta as f64 / self.allocations as f64
}
}
}
pub struct Profiler {
timings: HashMap<String, TimingEntry>,
memory: HashMap<String, MemoryEntry>,
active_timers: HashMap<String, Instant>,
running: bool,
}
impl Profiler {
pub fn new() -> Self {
Self {
timings: HashMap::new(),
memory: HashMap::new(),
active_timers: HashMap::new(),
running: false,
}
}
pub fn global() -> &'static Mutex<Profiler> {
static GLOBAL_PROFILER: Lazy<Mutex<Profiler>> = Lazy::new(|| Mutex::new(Profiler::new()));
&GLOBAL_PROFILER
}
pub fn start(&mut self) {
self.running = true;
self.timings.clear();
self.memory.clear();
self.active_timers.clear();
}
pub fn stop(&mut self) {
self.running = false;
}
pub fn reset(&mut self) {
self.timings.clear();
self.memory.clear();
self.active_timers.clear();
}
pub fn register_timer_start(&mut self, timer: &Timer) {
if !self.running {
return;
}
self.active_timers
.insert(timer.name.clone(), timer.start_time);
if let Some(parent) = &timer.parent {
if let Some(entry) = self.timings.get_mut(parent) {
entry.add_child(&timer.name);
}
}
}
pub fn register_timer_stop(&mut self, name: &str, duration: Duration, parent: Option<&str>) {
if !self.running {
return;
}
self.active_timers.remove(name);
match self.timings.get_mut(name) {
Some(entry) => {
entry.add_measurement(duration);
}
None => {
let entry = TimingEntry::new(duration, parent);
self.timings.insert(name.to_string(), entry);
}
}
if let Some(parent) = parent {
if let Some(entry) = self.timings.get_mut(parent) {
entry.add_child(name);
}
}
}
pub fn register_memory_tracker_start(&mut self, _tracker: &MemoryTracker) {
if !self.running {
}
}
pub fn register_memory_tracker_stop(&mut self, name: &str, delta: usize) {
if !self.running {
return;
}
match self.memory.get_mut(name) {
Some(entry) => {
entry.add_measurement(delta);
}
None => {
let entry = MemoryEntry::new(delta);
self.memory.insert(name.to_string(), entry);
}
}
}
pub fn print_report(&self) {
if self.timings.is_empty() && self.memory.is_empty() {
println!("No profiling data collected.");
return;
}
if !self.timings.is_empty() {
println!("\n=== Timing Report ===");
println!(
"{:<30} {:<10} {:<15} {:<15} {:<15}",
"Operation", "Calls", "Total (ms)", "Average (ms)", "Max (ms)"
);
println!("{}", "-".repeat(90));
let mut entries: Vec<(&String, &TimingEntry)> = self.timings.iter().collect();
entries.sort_by(|a, b| b.1.total_duration.cmp(&a.1.total_duration));
for (name, entry) in entries {
println!(
"{:<30} {:<10} {:<15.2} {:<15.2} {:<15.2}",
name,
entry.calls,
entry.total_duration.as_secs_f64() * 1000.0,
entry.average_duration().as_secs_f64() * 1000.0,
entry.max_duration.as_secs_f64() * 1000.0
);
}
}
if !self.memory.is_empty() {
println!("\n=== Memory Report ===");
println!(
"{:<30} {:<10} {:<15} {:<15}",
"Operation", "Counts", "Total (KB)", "Max (KB)"
);
println!("{}", "-".repeat(75));
let mut entries: Vec<(&String, &MemoryEntry)> = self.memory.iter().collect();
entries.sort_by(|a, b| b.1.total_delta.abs().cmp(&a.1.total_delta.abs()));
for (name, entry) in entries {
println!(
"{:<30} {:<10} {:<15.2} {:<15.2}",
name,
entry.allocations,
entry.total_delta as f64 / 1024.0,
entry.max_delta as f64 / 1024.0
);
}
}
}
pub fn get_report(&self) -> String {
use std::fmt::Write;
let mut report = String::new();
if self.timings.is_empty() && self.memory.is_empty() {
writeln!(report, "No profiling data collected.").unwrap();
return report;
}
if !self.timings.is_empty() {
writeln!(report, "\n=== Timing Report ===").unwrap();
writeln!(
report,
"{:<30} {:<10} {:<15} {:<15} {:<15}",
"Operation", "Calls", "Total (ms)", "Average (ms)", "Max (ms)"
)
.unwrap();
writeln!(report, "{}", "-".repeat(90)).unwrap();
let mut entries: Vec<(&String, &TimingEntry)> = self.timings.iter().collect();
entries.sort_by(|a, b| b.1.total_duration.cmp(&a.1.total_duration));
for (name, entry) in entries {
writeln!(
report,
"{:<30} {:<10} {:<15.2} {:<15.2} {:<15.2}",
name,
entry.calls,
entry.total_duration.as_secs_f64() * 1000.0,
entry.average_duration().as_secs_f64() * 1000.0,
entry.max_duration.as_secs_f64() * 1000.0
)
.unwrap();
}
}
if !self.memory.is_empty() {
writeln!(report, "\n=== Memory Report ===").unwrap();
writeln!(
report,
"{:<30} {:<10} {:<15} {:<15}",
"Operation", "Counts", "Total (KB)", "Max (KB)"
)
.unwrap();
writeln!(report, "{}", "-".repeat(75)).unwrap();
let mut entries: Vec<(&String, &MemoryEntry)> = self.memory.iter().collect();
entries.sort_by(|a, b| b.1.total_delta.abs().cmp(&a.1.total_delta.abs()));
for (name, entry) in entries {
writeln!(
report,
"{:<30} {:<10} {:<15.2} {:<15.2}",
name,
entry.allocations,
entry.total_delta as f64 / 1024.0,
entry.max_delta as f64 / 1024.0
)
.unwrap();
}
}
report
}
pub fn get_timing_stats(&self, name: &str) -> Option<(usize, Duration, Duration, Duration)> {
self.timings.get(name).map(|entry| {
(
entry.calls,
entry.total_duration,
entry.average_duration(),
entry.max_duration,
)
})
}
pub fn get_memory_stats(&self, name: &str) -> Option<(usize, isize, usize)> {
self.memory
.get(name)
.map(|entry| (entry.allocations, entry.total_delta, entry.max_delta))
}
}
impl Default for Profiler {
fn default() -> Self {
Self::new()
}
}
pub fn profiling_memory_tracker() -> &'static MemoryTracker {
static MEMORY_TRACKER: once_cell::sync::Lazy<MemoryTracker> =
once_cell::sync::Lazy::new(|| MemoryTracker {
name: "global".to_string(),
start_memory: 0,
running: false,
auto_report: false,
});
&MEMORY_TRACKER
}
#[macro_export]
macro_rules! profile_time {
($name:expr, $body:block) => {{
let timer = $crate::profiling::Timer::start($name);
let result = $body;
timer.stop();
result
}};
}
#[macro_export]
macro_rules! profile_memory {
($name:expr, $body:block) => {{
let tracker = $crate::profiling::MemoryTracker::start($name);
let result = $body;
tracker.stop();
result
}};
}
#[macro_export]
macro_rules! profile_time_with_parent {
($name:expr, $parent:expr, $body:block) => {{
let timer = $crate::profiling::Timer::start_with_parent($name, $parent);
let result = $body;
timer.stop();
result
}};
}