use anyhow::{Context, Result};
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tracing::{debug, info, warn};
use super::collector::{CollectionStatistics, MobileMetricsCollector};
use super::config::MobileProfilerConfig;
use super::types::*;
use crate::device_info::{MobileDeviceDetector, MobileDeviceInfo, ThermalState};
pub trait DataFormatter: std::fmt::Debug {
fn format(&self, data: &ProfilingData) -> Result<Vec<u8>>;
fn file_extension(&self) -> &str;
fn mime_type(&self) -> &str;
fn estimate_size(&self, data: &ProfilingData) -> usize;
}
pub trait NotificationHandler: std::fmt::Debug {
fn send_notification(&self, alert: &PerformanceAlert) -> Result<()>;
}
fn get_platform_capabilities() -> PlatformCapabilities {
let mut capabilities = PlatformCapabilities::default();
#[cfg(target_os = "ios")]
{
capabilities.ios_features = vec![
"Metal".to_string(), "CoreML".to_string(), "Instruments".to_string(),
"iOS Memory Pressure".to_string(),
];
}
#[cfg(target_os = "android")]
{
capabilities.android_features = vec![
"NNAPI".to_string(), "GPU Delegate".to_string(), "Android Profiler"
.to_string(), "System Trace".to_string(),
];
}
capabilities.generic_features = vec![
"CPU Profiling".to_string(), "Memory Profiling".to_string(), "Network Monitoring"
.to_string(), "Battery Monitoring".to_string(), "Thermal Monitoring".to_string(),
];
capabilities
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn fast_test_config() -> MobileProfilerConfig {
let mut config = MobileProfilerConfig::default();
config.memory_profiling.enabled = false;
config.cpu_profiling.enabled = false;
config.gpu_profiling.enabled = false;
config.network_profiling.enabled = false;
config.real_time_monitoring.enabled = false;
config.sampling.interval_ms = 10000;
config.sampling.max_samples = 10;
config
}
#[test]
fn test_profiler_creation() {
let config = fast_test_config();
let result = MobilePerformanceProfiler::new(config);
assert!(result.is_ok(), "Failed to create profiler: {:?}", result.err());
}
#[test]
fn test_profiling_lifecycle() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
assert!(! profiler.is_profiling_active());
let session_id = profiler.start_profiling()?;
assert!(! session_id.is_empty());
assert!(profiler.is_profiling_active());
assert!(profiler.start_profiling().is_err());
let profiling_data = profiler.stop_profiling()?;
assert!(! profiler.is_profiling_active());
assert_eq!(profiling_data.session_info.metadata.session_id, session_id);
Ok(())
}
#[test]
fn test_event_recording() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
profiler.record_inference_event("model_load", Some(250.0))?;
profiler.record_inference_event("inference_start", None)?;
profiler.record_inference_event("inference_end", Some(85.0))?;
let profiling_data = profiler.stop_profiling()?;
assert_eq!(profiling_data.events.len(), 3);
Ok(())
}
#[test]
fn test_metrics_collection() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
std::thread::sleep(Duration::from_millis(1));
let metrics = profiler.get_current_metrics()?;
assert!(metrics.timestamp > 0);
let _stats = profiler.get_collection_stats()?;
profiler.stop_profiling()?;
Ok(())
}
#[test]
#[ignore]
fn test_bottleneck_detection() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
std::thread::sleep(std::time::Duration::from_millis(1));
let _bottlenecks = profiler.detect_bottlenecks()?;
profiler.stop_profiling()?;
std::thread::sleep(std::time::Duration::from_millis(1));
Ok(())
}
#[test]
fn test_optimization_suggestions() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
let _suggestions = profiler.get_optimization_suggestions()?;
profiler.stop_profiling()?;
Ok(())
}
#[test]
fn test_pause_resume_profiling() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
assert!(profiler.is_profiling_active());
profiler.pause_profiling()?;
assert!(profiler.is_profiling_active());
profiler.resume_profiling()?;
assert!(profiler.is_profiling_active());
profiler.stop_profiling()?;
assert!(! profiler.is_profiling_active());
Ok(())
}
#[test]
fn test_config_validation() {
let mut config = MobileProfilerConfig::default();
assert!(MobilePerformanceProfiler::validate_config(& config).is_ok());
config.sampling.interval_ms = 0;
assert!(MobilePerformanceProfiler::validate_config(& config).is_err());
config = MobileProfilerConfig::default();
config.sampling.max_samples = 0;
assert!(MobilePerformanceProfiler::validate_config(& config).is_err());
config = MobileProfilerConfig::default();
config.export_config.compression_level = 10;
assert!(MobilePerformanceProfiler::validate_config(& config).is_err());
}
#[test]
fn test_config_update() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
let mut new_config = MobileProfilerConfig::default();
new_config.sampling.interval_ms = 200;
new_config.memory_profiling.heap_analysis = true;
profiler.update_config(new_config)?;
Ok(())
}
#[test]
fn test_export_functionality() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
profiler.record_inference_event("test_event", Some(100.0))?;
profiler.stop_profiling()?;
let export_path = profiler.export_data(ExportFormat::JSON)?;
assert!(! export_path.is_empty());
Ok(())
}
#[test]
fn test_system_health_assessment() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
let health = profiler.get_system_health()?;
assert!(health.overall_score >= 0.0 && health.overall_score <= 100.0);
profiler.stop_profiling()?;
Ok(())
}
#[test]
fn test_performance_report_generation() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
profiler.start_profiling()?;
profiler.record_inference_event("test_inference", Some(50.0))?;
profiler.stop_profiling()?;
let report = profiler.generate_performance_report()?;
assert!(! report.is_empty());
assert!(report.contains("html"));
Ok(())
}
#[test]
fn test_session_state_tracking() -> Result<()> {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config)?;
let state = profiler.get_profiling_state();
assert!(! state.is_active);
assert_eq!(state.events_recorded, 0);
profiler.start_profiling()?;
let state = profiler.get_profiling_state();
assert!(state.is_active);
assert!(state.current_session_id.is_some());
assert!(state.start_time.is_some());
profiler.record_inference_event("event1", None)?;
profiler.record_inference_event("event2", None)?;
let state = profiler.get_profiling_state();
assert_eq!(state.events_recorded, 2);
profiler.stop_profiling()?;
Ok(())
}
#[test]
fn test_error_handling() {
let config = fast_test_config();
let profiler = MobilePerformanceProfiler::new(config).expect("operation failed in test");
assert!(profiler.stop_profiling().is_err());
assert!(profiler.pause_profiling().is_err());
assert!(profiler.resume_profiling().is_err());
let mut invalid_config = MobileProfilerConfig::default();
invalid_config.sampling.interval_ms = 0;
assert!(profiler.update_config(invalid_config).is_err());
}
#[test]
fn test_thread_safety() -> Result<()> {
use std::sync::Arc;
use std::thread;
let config = fast_test_config();
let profiler = Arc::new(MobilePerformanceProfiler::new(config)?);
profiler.start_profiling()?;
let handles: Vec<_> = (0..5)
.map(|i| {
let profiler_clone = Arc::clone(&profiler);
thread::spawn(move || {
for j in 0..10 {
let event_name = format!("thread_{}_event_{}", i, j);
let _ = profiler_clone
.record_inference_event(&event_name, Some(10.0));
let _ = profiler_clone.get_current_metrics();
let _ = profiler_clone.detect_bottlenecks();
}
})
})
.collect();
for handle in handles {
handle.join().expect("thread join failed");
}
let profiling_data = profiler.stop_profiling()?;
assert!(profiling_data.events.len() > 0);
Ok(())
}
}