use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
pub struct Profiler {
profiles: Arc<RwLock<HashMap<String, Vec<ProfileSample>>>>,
}
#[derive(Debug, Clone)]
pub struct ProfileSample {
pub operation: String,
pub duration_ms: f64,
pub timestamp: DateTime<Utc>,
pub metadata: HashMap<String, String>,
}
impl Profiler {
pub fn new() -> Self {
Self {
profiles: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn start(&self, operation: impl Into<String>) -> ProfileGuard {
ProfileGuard {
operation: operation.into(),
start: Instant::now(),
profiler: self.profiles.clone(),
metadata: HashMap::new(),
}
}
pub fn get_samples(&self, operation: &str) -> Vec<ProfileSample> {
self.profiles
.read()
.get(operation)
.cloned()
.unwrap_or_default()
}
pub fn get_stats(&self, operation: &str) -> Option<ProfileStats> {
let samples = self.get_samples(operation);
if samples.is_empty() {
return None;
}
let durations: Vec<f64> = samples.iter().map(|s| s.duration_ms).collect();
let count = durations.len();
let sum: f64 = durations.iter().sum();
let mean = sum / count as f64;
let variance: f64 =
durations.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / count as f64;
let std_dev = variance.sqrt();
let mut sorted = durations.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let min = sorted[0];
let max = sorted[count - 1];
let p50 = sorted[count / 2];
let p95 = sorted[(count * 95) / 100];
let p99 = sorted[(count * 99) / 100];
Some(ProfileStats {
operation: operation.to_string(),
count,
mean,
std_dev,
min,
max,
p50,
p95,
p99,
})
}
pub fn clear(&self) {
self.profiles.write().clear();
}
}
impl Default for Profiler {
fn default() -> Self {
Self::new()
}
}
pub struct ProfileGuard {
operation: String,
start: Instant,
profiler: Arc<RwLock<HashMap<String, Vec<ProfileSample>>>>,
metadata: HashMap<String, String>,
}
impl ProfileGuard {
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
impl Drop for ProfileGuard {
fn drop(&mut self) {
let duration = self.start.elapsed();
let sample = ProfileSample {
operation: self.operation.clone(),
duration_ms: duration.as_secs_f64() * 1000.0,
timestamp: Utc::now(),
metadata: self.metadata.clone(),
};
let mut profiles = self.profiler.write();
profiles
.entry(self.operation.clone())
.or_default()
.push(sample);
}
}
#[derive(Debug, Clone)]
pub struct ProfileStats {
pub operation: String,
pub count: usize,
pub mean: f64,
pub std_dev: f64,
pub min: f64,
pub max: f64,
pub p50: f64,
pub p95: f64,
pub p99: f64,
}
impl std::fmt::Display for ProfileStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Operation: {}\n\
Count: {}\n\
Mean: {:.2}ms\n\
StdDev: {:.2}ms\n\
Min: {:.2}ms\n\
Max: {:.2}ms\n\
P50: {:.2}ms\n\
P95: {:.2}ms\n\
P99: {:.2}ms",
self.operation,
self.count,
self.mean,
self.std_dev,
self.min,
self.max,
self.p50,
self.p95,
self.p99
)
}
}
pub struct MemoryProfiler {
snapshots: Arc<RwLock<Vec<MemorySnapshot>>>,
}
#[derive(Debug, Clone)]
pub struct MemorySnapshot {
pub timestamp: DateTime<Utc>,
pub allocated_bytes: u64,
pub deallocated_bytes: u64,
pub active_allocations: u64,
pub operation: String,
}
impl MemoryProfiler {
pub fn new() -> Self {
Self {
snapshots: Arc::new(RwLock::new(Vec::new())),
}
}
pub fn snapshot(&self, operation: impl Into<String>) {
let snapshot = MemorySnapshot {
timestamp: Utc::now(),
allocated_bytes: 0,
deallocated_bytes: 0,
active_allocations: 0,
operation: operation.into(),
};
self.snapshots.write().push(snapshot);
}
pub fn get_snapshots(&self) -> Vec<MemorySnapshot> {
self.snapshots.read().clone()
}
pub fn clear(&self) {
self.snapshots.write().clear();
}
}
impl Default for MemoryProfiler {
fn default() -> Self {
Self::new()
}
}
pub struct CpuProfiler {
samples: Arc<RwLock<Vec<CpuSample>>>,
}
#[derive(Debug, Clone)]
pub struct CpuSample {
pub timestamp: DateTime<Utc>,
pub cpu_percent: f64,
pub operation: String,
}
impl CpuProfiler {
pub fn new() -> Self {
Self {
samples: Arc::new(RwLock::new(Vec::new())),
}
}
pub fn record(&self, operation: impl Into<String>, cpu_percent: f64) {
let sample = CpuSample {
timestamp: Utc::now(),
cpu_percent,
operation: operation.into(),
};
self.samples.write().push(sample);
}
pub fn get_samples(&self) -> Vec<CpuSample> {
self.samples.read().clone()
}
pub fn get_average(&self, operation: &str) -> Option<f64> {
let samples: Vec<CpuSample> = self
.samples
.read()
.iter()
.filter(|s| s.operation == operation)
.cloned()
.collect();
if samples.is_empty() {
return None;
}
let sum: f64 = samples.iter().map(|s| s.cpu_percent).sum();
Some(sum / samples.len() as f64)
}
}
impl Default for CpuProfiler {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_profiler() {
let profiler = Profiler::new();
{
let _guard = profiler.start("test_operation");
thread::sleep(Duration::from_millis(10));
}
let samples = profiler.get_samples("test_operation");
assert_eq!(samples.len(), 1);
assert!(samples[0].duration_ms >= 10.0);
}
#[test]
fn test_profiler_stats() {
let profiler = Profiler::new();
for _ in 0..10 {
let _guard = profiler.start("test_op");
thread::sleep(Duration::from_millis(1));
}
let stats = profiler.get_stats("test_op");
assert!(stats.is_some());
let stats = stats.expect("No stats");
assert_eq!(stats.count, 10);
assert!(stats.mean > 0.0);
}
#[test]
fn test_memory_profiler() {
let profiler = MemoryProfiler::new();
profiler.snapshot("test_operation");
assert_eq!(profiler.get_snapshots().len(), 1);
}
#[test]
fn test_cpu_profiler() {
let profiler = CpuProfiler::new();
profiler.record("test_op", 50.0);
profiler.record("test_op", 60.0);
let avg = profiler.get_average("test_op");
assert_eq!(avg, Some(55.0));
}
}