use std::collections::{BTreeMap, HashMap};
use std::time::SystemTime;
use std::time::{Duration, Instant};
pub use super::storage::{
PerformanceEvent, PerformanceEventType, RingBuffer, SamplingConfig, SamplingStrategy,
};
#[derive(Debug, Clone)]
pub struct FunctionTraceHandle {
pub id: String,
pub function_name: String,
pub start_time: Instant,
pub start_memory: usize,
pub call_depth: usize,
}
#[derive(Debug, Clone)]
pub struct FunctionStats {
pub function_name: String,
pub total_time: Duration,
pub avg_time: Duration,
pub min_time: Duration,
pub max_time: Duration,
pub p95_time: Duration,
pub p99_time: Duration,
pub call_count: u64,
pub total_memory: usize,
pub avg_memory: f64,
}
#[derive(Debug)]
pub struct FunctionTracker {
active_traces: HashMap<String, FunctionTraceHandle>,
function_stats: HashMap<String, FunctionStats>,
event_buffer: RingBuffer<PerformanceEvent>,
sampler: SamplingStrategy,
stats: TrackerStats,
}
#[derive(Debug, Clone, Default)]
pub struct TrackerStats {
pub total_traces: u64,
pub active_traces: u64,
pub completed_traces: u64,
pub memory_traces: u64,
pub error_count: u64,
}
impl FunctionTracker {
pub fn new(buffer_capacity: usize, sampling_config: SamplingConfig) -> Self {
Self {
active_traces: HashMap::new(),
function_stats: HashMap::new(),
event_buffer: RingBuffer::with_capacity(buffer_capacity),
sampler: SamplingStrategy::new(sampling_config),
stats: TrackerStats::default(),
}
}
pub fn with_default_config() -> Self {
Self::new(10000, SamplingConfig::default())
}
pub fn track_function(
&mut self,
function_name: &str,
start_memory: usize,
call_depth: usize,
) -> FunctionTraceHandle {
let trace: _ = FunctionTraceHandle {
id: Uuid::new_v4().to_string(),
function_name: function_name.to_string(),
start_time: Instant::now(),
start_memory,
call_depth,
};
self.active_traces.insert(trace.id.clone(), trace.clone());
self.stats.total_traces += 1;
self.stats.active_traces += 1;
let decision: _ = self.sampler.should_sample_event(&PerformanceEvent {
event_type: PerformanceEventType::FunctionCall,
importance: 0.8,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
metadata: Some(format!("function_start:{}", function_name)),
});
if decision.should_sample {
self.event_buffer.push(PerformanceEvent {
event_type: PerformanceEventType::FunctionCall,
importance: 0.8,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
metadata: Some(format!("function_start:{}", function_name)),
});
}
trace
}
pub fn record_return(
&mut self,
mut handle: FunctionTraceHandle,
end_memory: usize,
) -> Option<FunctionStats> {
let end_time: _ = Instant::now();
let execution_time: _ = end_time.duration_since(handle.start_time);
let memory_used: _ = if end_memory >= handle.start_memory {
end_memory - handle.start_memory
} else {
0
};
if let Some(_trace) = self.active_traces.remove(&handle.id) {
self.stats.active_traces = self.stats.active_traces.saturating_sub(1);
self.stats.completed_traces += 1;
}
let stats: _ =
self.update_function_stats(&handle.function_name, execution_time, memory_used);
let event: _ = PerformanceEvent {
event_type: PerformanceEventType::FunctionCall,
importance: 0.8,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
metadata: Some(format!(
"function_end:{}:{:?}:{}",
handle.function_name, execution_time, memory_used
)),
};
let decision: _ = self.sampler.should_sample_event(&event);
if decision.should_sample {
self.event_buffer.push(event);
}
stats
}
fn update_function_stats(
&mut self,
function_name: &str,
execution_time: Duration,
memory_used: usize,
) -> Option<FunctionStats> {
let stats: _ = self
.function_stats
.entry(function_name.to_string())
.or_insert_with(|| FunctionStats {
function_name: function_name.to_string(),
total_time: Duration::from_nanos(0),
avg_time: Duration::from_nanos(0),
min_time: Duration::from_secs(u64::MAX),
max_time: Duration::from_nanos(0),
p95_time: Duration::from_nanos(0),
p99_time: Duration::from_nanos(0),
call_count: 0,
total_memory: 0,
avg_memory: 0.0,
});
stats.total_time += execution_time;
stats.call_count += 1;
stats.total_memory += memory_used;
if execution_time < stats.min_time {
stats.min_time = execution_time;
}
if execution_time > stats.max_time {
stats.max_time = execution_time;
}
stats.avg_time =
Duration::from_nanos(stats.total_time.as_nanos() as u64 / stats.call_count);
stats.avg_memory = stats.total_memory as f64 / stats.call_count as f64;
if stats.call_count >= 20 {
stats.p95_time = Duration::from_nanos(
(stats.total_time.as_nanos() as f64 * 0.95) as u64 / stats.call_count,
);
stats.p99_time = Duration::from_nanos(
(stats.total_time.as_nanos() as f64 * 0.99) as u64 / stats.call_count,
);
} else {
stats.p95_time = stats.avg_time;
stats.p99_time = stats.avg_time;
}
Some(stats.clone())
}
pub fn get_function_stats(&self, function_name: &str) -> Option<&FunctionStats> {
self.function_stats.get(function_name)
}
pub fn get_all_function_stats(&self) -> HashMap<String, FunctionStats> {
self.function_stats.clone()
}
pub fn get_hotspot_functions(&self, limit: usize) -> Vec<FunctionStats> {
let mut stats: Vec<FunctionStats> = self.function_stats.values().cloned().collect();
stats.sort_by(|a, b| b.total_time.cmp(&a.total_time));
stats.into_iter().take(limit).collect()
}
pub fn get_event_buffer(&self) -> &RingBuffer<PerformanceEvent> {
&self.event_buffer
}
pub fn get_sampling_stats(&self) -> &super::storage::SamplingStats {
self.sampler.get_stats()
}
pub fn get_tracker_stats(&self) -> &TrackerStats {
&self.stats
}
pub fn clear(&mut self) {
self.active_traces.clear();
self.function_stats.clear();
self.event_buffer.clear();
self.stats = TrackerStats::default();
self.sampler.reset_stats();
}
pub fn get_active_trace_count(&self) -> usize {
self.active_traces.len()
}
pub fn force_sample_event(&mut self, event: PerformanceEvent) {
let decision: _ = self.sampler.force_sample();
if decision.should_sample {
self.event_buffer.push(event);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_function_tracker_creation() {
let tracker: _ = FunctionTracker::with_default_config();
assert_eq!(tracker.get_active_trace_count(), 0);
}
#[test]
fn test_track_function() {
let mut tracker = FunctionTracker::with_default_config();
let handle: _ = tracker.track_function("test_function", 1024, 0);
assert_eq!(tracker.get_active_trace_count(), 1);
assert_eq!(handle.function_name, "test_function");
}
#[test]
fn test_record_return() {
let mut tracker = FunctionTracker::with_default_config();
let handle: _ = tracker.track_function("test_function", 1024, 0);
std::thread::sleep(Duration::from_millis(10));
let stats: _ = tracker.record_return(handle, 2048);
assert!(stats.is_some());
assert_eq!(tracker.get_active_trace_count(), 0);
let stats: _ = stats.unwrap();
assert_eq!(stats.function_name, "test_function");
assert!(stats.call_count >= 1);
}
#[test]
fn test_get_function_stats() {
let mut tracker = FunctionTracker::with_default_config();
for _ in 0..5 {
let handle: _ = tracker.track_function("test_function", 1024, 0);
std::thread::sleep(Duration::from_millis(1));
tracker.record_return(handle, 2048);
}
let stats: _ = tracker.get_function_stats("test_function");
assert!(stats.is_some());
let stats: _ = stats.unwrap();
assert_eq!(stats.function_name, "test_function");
assert_eq!(stats.call_count, 5);
}
#[test]
fn test_get_hotspot_functions() {
let mut tracker = FunctionTracker::with_default_config();
for i in 0..3 {
let func_name: _ = format!("function_{}", i);
for _ in 0..(i + 1) * 10 {
let handle: _ = tracker.track_function(&func_name, 1024, 0);
std::thread::sleep(Duration::from_millis(i as u64));
tracker.record_return(handle, 2048);
}
}
let hotspots: _ = tracker.get_hotspot_functions(3);
assert_eq!(hotspots.len(), 3);
assert_eq!(hotspots[0].function_name, "function_2");
}
#[test]
fn test_clear() {
let mut tracker = FunctionTracker::with_default_config();
tracker.track_function("test_function", 1024, 0);
tracker.clear();
assert_eq!(tracker.get_active_trace_count(), 0);
assert_eq!(tracker.get_all_function_stats().len(), 0);
}
#[test]
fn test_memory_tracking() {
let mut tracker = FunctionTracker::with_default_config();
let handle: _ = tracker.track_function("test_function", 1024, 0);
let stats: _ = tracker.record_return(handle, 3072);
assert!(stats.is_some());
assert!(stats.unwrap().total_memory > 0);
}
}