#[allow(unused_imports)]
use crate::error::{Result, TorshError};
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, Instant};
static PERF_MONITOR: OnceLock<Arc<Mutex<RealTimeMonitor>>> = OnceLock::new();
#[derive(Debug, Clone)]
pub struct MonitorConfig {
pub enabled: bool,
pub update_interval: Duration,
pub window_size: usize,
pub enable_hw_counters: bool,
pub enable_gpu_monitoring: bool,
pub enable_bandwidth_tracking: bool,
pub alert_threshold_multiplier: f64,
}
impl Default for MonitorConfig {
fn default() -> Self {
Self {
enabled: true,
update_interval: Duration::from_millis(100),
window_size: 100,
enable_hw_counters: false, enable_gpu_monitoring: cfg!(feature = "gpu"),
enable_bandwidth_tracking: true,
alert_threshold_multiplier: 2.0,
}
}
}
#[derive(Debug, Clone)]
pub struct RealtimeMetrics {
pub ops_per_second: f64,
pub avg_latency_us: f64,
pub p95_latency_us: f64,
pub p99_latency_us: f64,
pub cpu_utilization: f64,
pub memory_usage: usize,
pub gpu_utilization: Option<f64>,
pub memory_bandwidth: Option<f64>,
pub timestamp: Instant,
}
#[derive(Debug, Clone)]
pub struct PerformanceAlert {
pub alert_type: AlertType,
pub severity: AlertSeverity,
pub description: String,
pub current_value: f64,
pub baseline_value: f64,
pub deviation_factor: f64,
pub timestamp: Instant,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlertType {
HighLatency,
LowThroughput,
HighCpuUsage,
HighMemoryUsage,
HighGpuUsage,
LowMemoryBandwidth,
PerformanceRegression,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum AlertSeverity {
Info,
Warning,
Critical,
}
pub struct RealTimeMonitor {
config: MonitorConfig,
start_time: Instant,
operation_counts: HashMap<String, u64>,
operation_times: HashMap<String, VecDeque<Duration>>,
baselines: HashMap<String, f64>,
alerts: VecDeque<PerformanceAlert>,
#[allow(dead_code)] last_update: Instant,
}
impl RealTimeMonitor {
pub fn new(config: MonitorConfig) -> Self {
Self {
config,
start_time: Instant::now(),
operation_counts: HashMap::new(),
operation_times: HashMap::new(),
baselines: HashMap::new(),
alerts: VecDeque::new(),
last_update: Instant::now(),
}
}
pub fn record_operation(&mut self, operation: &str, duration: Duration) {
if !self.config.enabled {
return;
}
*self
.operation_counts
.entry(operation.to_string())
.or_insert(0) += 1;
let times = self
.operation_times
.entry(operation.to_string())
.or_insert_with(VecDeque::new);
times.push_back(duration);
if times.len() > self.config.window_size {
times.pop_front();
}
if let Some(baseline) = self.baselines.get(operation) {
let current_avg = Self::calculate_average(times);
let deviation = current_avg / baseline;
if deviation > self.config.alert_threshold_multiplier {
self.trigger_alert(PerformanceAlert {
alert_type: AlertType::PerformanceRegression,
severity: if deviation > 3.0 {
AlertSeverity::Critical
} else {
AlertSeverity::Warning
},
description: format!(
"Operation '{}' is {:.1}x slower than baseline",
operation, deviation
),
current_value: current_avg,
baseline_value: *baseline,
deviation_factor: deviation,
timestamp: Instant::now(),
});
}
}
}
pub fn get_metrics(&self) -> RealtimeMetrics {
let elapsed = self.start_time.elapsed();
let total_ops: u64 = self.operation_counts.values().sum();
let ops_per_second = total_ops as f64 / elapsed.as_secs_f64();
let all_times: Vec<Duration> = self
.operation_times
.values()
.flat_map(|times| times.iter().copied())
.collect();
let avg_latency = if all_times.is_empty() {
0.0
} else {
all_times.iter().map(|d| d.as_micros() as f64).sum::<f64>() / all_times.len() as f64
};
let (p95, p99) = Self::calculate_percentiles(&all_times);
RealtimeMetrics {
ops_per_second,
avg_latency_us: avg_latency,
p95_latency_us: p95,
p99_latency_us: p99,
cpu_utilization: Self::get_cpu_utilization(),
memory_usage: Self::get_memory_usage(),
gpu_utilization: Self::get_gpu_utilization(),
memory_bandwidth: Self::get_memory_bandwidth(),
timestamp: Instant::now(),
}
}
pub fn set_baseline(&mut self, operation: &str, baseline_time_us: f64) {
self.baselines
.insert(operation.to_string(), baseline_time_us);
}
pub fn get_alerts(&self, max_count: usize) -> Vec<PerformanceAlert> {
self.alerts.iter().rev().take(max_count).cloned().collect()
}
pub fn clear_alerts(&mut self) {
self.alerts.clear();
}
fn trigger_alert(&mut self, alert: PerformanceAlert) {
self.alerts.push_back(alert);
if self.alerts.len() > 1000 {
self.alerts.pop_front();
}
}
fn calculate_average(durations: &VecDeque<Duration>) -> f64 {
if durations.is_empty() {
return 0.0;
}
durations.iter().map(|d| d.as_micros() as f64).sum::<f64>() / durations.len() as f64
}
fn calculate_percentiles(durations: &[Duration]) -> (f64, f64) {
if durations.is_empty() {
return (0.0, 0.0);
}
let mut sorted: Vec<f64> = durations.iter().map(|d| d.as_micros() as f64).collect();
sorted.sort_by(|a, b| {
a.partial_cmp(b)
.expect("duration values should be comparable (no NaN)")
});
let p95_idx = (sorted.len() as f64 * 0.95) as usize;
let p99_idx = (sorted.len() as f64 * 0.99) as usize;
let p95 = sorted.get(p95_idx).copied().unwrap_or(0.0);
let p99 = sorted.get(p99_idx).copied().unwrap_or(0.0);
(p95, p99)
}
fn get_cpu_utilization() -> f64 {
#[cfg(feature = "std")]
{
#[cfg(scirs2_system_monitoring_available)]
{
use scirs2_core::system::cpu_utilization;
if let Ok(util) = cpu_utilization() {
return util;
}
}
0.5 }
#[cfg(not(feature = "std"))]
{
0.0
}
}
fn get_memory_usage() -> usize {
#[cfg(feature = "std")]
{
#[cfg(scirs2_memory_monitoring_available)]
{
use scirs2_core::system::memory_usage;
if let Ok(usage) = memory_usage() {
return usage;
}
}
0 }
#[cfg(not(feature = "std"))]
{
0
}
}
fn get_gpu_utilization() -> Option<f64> {
#[cfg(all(feature = "gpu", scirs2_gpu_available))]
{
use crate::gpu;
if let Ok(device) = gpu::GpuDevice::new(0) {
return Some(device.utilization());
}
}
None
}
fn get_memory_bandwidth() -> Option<f64> {
#[cfg(scirs2_bandwidth_monitoring_available)]
{
use scirs2_core::system::memory_bandwidth;
if let Ok(bw) = memory_bandwidth() {
return Some(bw);
}
}
None
}
}
pub fn get_monitor() -> Arc<Mutex<RealTimeMonitor>> {
PERF_MONITOR
.get_or_init(|| Arc::new(Mutex::new(RealTimeMonitor::new(MonitorConfig::default()))))
.clone()
}
pub fn configure_monitor(config: MonitorConfig) {
let monitor = get_monitor();
*monitor.lock().expect("lock should not be poisoned") = RealTimeMonitor::new(config);
}
pub fn record_operation(operation: &str, duration: Duration) {
let monitor = get_monitor();
monitor
.lock()
.expect("monitor lock should not be poisoned")
.record_operation(operation, duration);
}
pub fn get_current_metrics() -> RealtimeMetrics {
let monitor = get_monitor();
let guard = monitor.lock().expect("lock should not be poisoned");
guard.get_metrics()
}
pub fn set_baseline(operation: &str, baseline_time_us: f64) {
let monitor = get_monitor();
monitor
.lock()
.expect("monitor lock should not be poisoned")
.set_baseline(operation, baseline_time_us);
}
pub fn get_recent_alerts(max_count: usize) -> Vec<PerformanceAlert> {
let monitor = get_monitor();
let guard = monitor.lock().expect("lock should not be poisoned");
guard.get_alerts(max_count)
}
pub struct TimingScope {
operation: String,
start: Instant,
}
impl TimingScope {
pub fn new(operation: impl Into<String>) -> Self {
Self {
operation: operation.into(),
start: Instant::now(),
}
}
}
impl Drop for TimingScope {
fn drop(&mut self) {
let duration = self.start.elapsed();
record_operation(&self.operation, duration);
}
}
#[macro_export]
macro_rules! time_operation {
($name:expr) => {
let _timing_scope = $crate::perf_monitor::TimingScope::new($name);
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_monitor_creation() {
let config = MonitorConfig::default();
let monitor = RealTimeMonitor::new(config);
assert!(monitor.operation_counts.is_empty());
}
#[test]
fn test_operation_recording() {
let mut monitor = RealTimeMonitor::new(MonitorConfig::default());
monitor.record_operation("test_op", Duration::from_micros(100));
assert_eq!(monitor.operation_counts.get("test_op"), Some(&1));
}
#[test]
fn test_metrics_calculation() {
let mut monitor = RealTimeMonitor::new(MonitorConfig::default());
for _ in 0..10 {
monitor.record_operation("test_op", Duration::from_micros(100));
}
let metrics = monitor.get_metrics();
assert!(metrics.ops_per_second > 0.0);
assert!(metrics.avg_latency_us > 0.0);
}
#[test]
fn test_baseline_and_alerts() {
let mut monitor = RealTimeMonitor::new(MonitorConfig {
alert_threshold_multiplier: 2.0,
window_size: 10, ..MonitorConfig::default()
});
monitor.set_baseline("slow_op", 100.0);
for _ in 0..10 {
monitor.record_operation("slow_op", Duration::from_micros(100));
}
for _ in 0..15 {
monitor.record_operation("slow_op", Duration::from_micros(250));
}
let alerts = monitor.get_alerts(10);
assert!(
!alerts.is_empty(),
"Expected performance regression alert to be triggered"
);
assert_eq!(alerts[0].alert_type, AlertType::PerformanceRegression);
}
#[test]
fn test_timing_scope() {
{
let _scope = TimingScope::new("test_scope");
thread::sleep(Duration::from_micros(100));
}
let monitor = get_monitor();
let counts = &monitor
.lock()
.expect("lock should not be poisoned")
.operation_counts;
assert!(counts.contains_key("test_scope"));
}
#[test]
fn test_percentile_calculation() {
let durations = vec![
Duration::from_micros(100),
Duration::from_micros(200),
Duration::from_micros(300),
Duration::from_micros(400),
Duration::from_micros(500),
];
let (p95, p99) = RealTimeMonitor::calculate_percentiles(&durations);
assert!(p95 > 0.0);
assert!(p99 >= p95);
}
#[test]
fn test_global_monitor() {
record_operation("global_test", Duration::from_micros(150));
let metrics = get_current_metrics();
assert!(metrics.timestamp.elapsed().as_secs() < 1);
}
}