use anyhow::Result;
use std::collections::VecDeque;
use std::time::{Duration, Instant};
use trustformers_core::errors::TrustformersError;
use crate::device_info::ThermalState;
use super::config::MobileProfilerConfig;
use super::types::{CpuMetrics, GpuMetrics, InferenceMetrics, MemoryMetrics, NetworkMetrics};
#[cfg(any(target_os = "ios", target_os = "android"))]
extern crate libc;
pub struct MobileMetricsCollector {
config: MobileProfilerConfig,
current_metrics: MobileMetricsSnapshot,
metrics_history: VecDeque<MobileMetricsSnapshot>,
sampling_timer: Option<Instant>,
collection_start: Option<Instant>,
total_samples: u64,
}
#[derive(Debug, Clone)]
pub struct MobileMetricsSnapshot {
pub timestamp: u64,
pub memory: MemoryMetrics,
pub cpu: CpuMetrics,
pub gpu: GpuMetrics,
pub network: NetworkMetrics,
pub inference: InferenceMetrics,
pub thermal: ThermalMetrics,
pub battery: BatteryMetrics,
pub platform: PlatformMetrics,
}
#[derive(Debug, Clone)]
pub struct ThermalMetrics {
pub temperature_c: f32,
pub thermal_state: ThermalState,
pub throttling_level: f32,
pub temperature_trend: TemperatureTrend,
pub heat_generation_rate: f32,
pub cooling_efficiency: f32,
}
#[derive(Debug, Clone)]
pub struct TemperatureTrend {
pub current: f32,
pub previous: f32,
pub rate_of_change: f32,
pub direction: TrendDirection,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TrendDirection {
Rising,
Falling,
Stable,
}
#[derive(Debug, Clone)]
pub struct BatteryMetrics {
pub level_percent: u8,
pub is_charging: bool,
pub power_consumption_mw: f32,
pub time_remaining_min: Option<u32>,
pub health_percent: u8,
pub temperature_c: f32,
pub voltage_v: f32,
}
#[derive(Debug, Clone, Default)]
pub struct PlatformMetrics {
pub ios_metrics: Option<IosMetrics>,
pub android_metrics: Option<AndroidMetrics>,
pub generic_metrics: GenericMobileMetrics,
}
#[derive(Debug, Clone)]
pub struct IosMetrics {
pub metal_performance: MetalPerformanceMetrics,
pub coreml_metrics: CoreMLMetrics,
pub memory_pressure: MemoryPressureLevel,
pub thermal_pressure: ThermalPressureLevel,
}
#[derive(Debug, Clone)]
pub struct AndroidMetrics {
pub runtime_metrics: AndroidRuntimeMetrics,
pub gpu_vendor_metrics: AndroidGpuMetrics,
pub system_services: AndroidSystemMetrics,
}
#[derive(Debug, Clone)]
pub struct GenericMobileMetrics {
pub screen_brightness: f32,
pub orientation: DeviceOrientation,
pub network_type: NetworkType,
pub location_services_active: bool,
}
#[derive(Debug, Clone)]
pub struct MetalPerformanceMetrics {
pub gpu_utilization: f32,
pub command_buffer_time_ms: f32,
pub render_encoder_time_ms: f32,
pub compute_encoder_time_ms: f32,
}
#[derive(Debug, Clone)]
pub struct CoreMLMetrics {
pub prediction_time_ms: f32,
pub model_load_time_ms: f32,
pub compute_unit: CoreMLComputeUnit,
pub memory_usage_mb: f32,
}
#[derive(Debug, Clone, Copy)]
pub enum CoreMLComputeUnit {
CPUOnly,
CPUAndGPU,
CPUAndNeuralEngine,
All,
}
#[derive(Debug, Clone, Copy)]
pub enum MemoryPressureLevel {
Normal,
Warning,
Urgent,
Critical,
}
#[derive(Debug, Clone, Copy)]
pub enum ThermalPressureLevel {
Nominal,
Fair,
Serious,
Critical,
}
#[derive(Debug, Clone)]
pub struct AndroidRuntimeMetrics {
pub gc_count: u32,
pub gc_time_ms: f32,
pub heap_utilization: f32,
pub compilation_time_ms: f32,
}
#[derive(Debug, Clone)]
pub struct AndroidGpuMetrics {
pub frequency_mhz: u32,
pub busy_percent: f32,
pub memory_usage_mb: f32,
pub power_mw: f32,
}
#[derive(Debug, Clone)]
pub struct AndroidSystemMetrics {
pub system_server_cpu: f32,
pub window_manager_cpu: f32,
pub surface_flinger_cpu: f32,
pub media_server_cpu: f32,
}
#[derive(Debug, Clone, Copy)]
pub enum DeviceOrientation {
Portrait,
LandscapeLeft,
LandscapeRight,
PortraitUpsideDown,
FaceUp,
FaceDown,
}
#[derive(Debug, Clone, Copy)]
pub enum NetworkType {
WiFi,
Cellular,
Ethernet,
None,
Unknown,
}
impl MobileMetricsCollector {
pub fn new(config: MobileProfilerConfig) -> Result<Self> {
Ok(Self {
config,
current_metrics: MobileMetricsSnapshot::default(),
metrics_history: VecDeque::new(),
sampling_timer: None,
collection_start: None,
total_samples: 0,
})
}
pub fn start_collection(&mut self) -> Result<()> {
self.sampling_timer = Some(Instant::now());
self.collection_start = Some(Instant::now());
self.collect_metrics()?;
Ok(())
}
pub fn stop_collection(&mut self) -> Result<()> {
self.sampling_timer = None;
Ok(())
}
pub fn get_current_snapshot(&self) -> Result<MobileMetricsSnapshot> {
Ok(self.current_metrics.clone())
}
pub fn get_all_snapshots(&self) -> Vec<MobileMetricsSnapshot> {
self.metrics_history.iter().cloned().collect()
}
pub fn get_collection_stats(&self) -> CollectionStatistics {
let duration = self.collection_start.map(|start| start.elapsed()).unwrap_or_default();
CollectionStatistics {
total_samples: self.total_samples,
collection_duration: duration,
average_sampling_rate: if duration.as_secs() > 0 {
self.total_samples as f64 / duration.as_secs() as f64
} else {
0.0
},
history_size: self.metrics_history.len(),
current_memory_usage_mb: self.estimate_memory_usage(),
}
}
pub fn collect_metrics(&mut self) -> Result<()> {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| TrustformersError::other(format!("Time error: {}", e)))?
.as_millis() as u64;
let memory = self.collect_memory_metrics()?;
let cpu = self.collect_cpu_metrics()?;
let gpu = self.collect_gpu_metrics()?;
let network = self.collect_network_metrics()?;
let inference = self.collect_inference_metrics()?;
let thermal = self.collect_thermal_metrics()?;
let battery = self.collect_battery_metrics()?;
let platform = self.collect_platform_metrics()?;
let snapshot = MobileMetricsSnapshot {
timestamp,
memory,
cpu,
gpu,
network,
inference,
thermal,
battery,
platform,
};
self.current_metrics = snapshot.clone();
self.metrics_history.push_back(snapshot);
self.total_samples += 1;
if self.metrics_history.len() > self.config.sampling.max_samples {
self.metrics_history.pop_front();
}
Ok(())
}
fn collect_memory_metrics(&self) -> Result<MemoryMetrics> {
#[cfg(target_os = "ios")]
{
self.collect_ios_memory_metrics()
}
#[cfg(target_os = "android")]
{
self.collect_android_memory_metrics()
}
#[cfg(not(any(target_os = "ios", target_os = "android")))]
{
Ok(MemoryMetrics::default())
}
}
#[cfg(target_os = "ios")]
fn collect_ios_memory_metrics(&self) -> Result<MemoryMetrics> {
Ok(MemoryMetrics {
heap_used_mb: 128.0,
heap_free_mb: 256.0,
heap_total_mb: 384.0,
native_used_mb: 64.0,
graphics_used_mb: 32.0,
code_used_mb: 16.0,
stack_used_mb: 8.0,
other_used_mb: 24.0,
available_mb: 1024.0,
})
}
#[cfg(target_os = "android")]
fn collect_android_memory_metrics(&self) -> Result<MemoryMetrics> {
Ok(MemoryMetrics {
heap_used_mb: 96.0,
heap_free_mb: 128.0,
heap_total_mb: 224.0,
native_used_mb: 48.0,
graphics_used_mb: 64.0,
code_used_mb: 12.0,
stack_used_mb: 4.0,
other_used_mb: 16.0,
available_mb: 512.0,
})
}
fn collect_cpu_metrics(&self) -> Result<CpuMetrics> {
#[cfg(target_os = "ios")]
{
self.collect_ios_cpu_metrics()
}
#[cfg(target_os = "android")]
{
self.collect_android_cpu_metrics()
}
#[cfg(not(any(target_os = "ios", target_os = "android")))]
{
Ok(CpuMetrics::default())
}
}
#[cfg(target_os = "ios")]
fn collect_ios_cpu_metrics(&self) -> Result<CpuMetrics> {
Ok(CpuMetrics {
usage_percent: 30.0,
user_percent: 20.0,
system_percent: 10.0,
idle_percent: 70.0,
frequency_mhz: 3200,
temperature_c: 38.0,
throttling_level: 0.1,
})
}
#[cfg(target_os = "android")]
fn collect_android_cpu_metrics(&self) -> Result<CpuMetrics> {
Ok(CpuMetrics {
usage_percent: 35.0,
user_percent: 25.0,
system_percent: 10.0,
idle_percent: 65.0,
frequency_mhz: 2800,
temperature_c: 40.0,
throttling_level: 0.15,
})
}
fn collect_gpu_metrics(&self) -> Result<GpuMetrics> {
Ok(GpuMetrics::default()) }
fn collect_network_metrics(&self) -> Result<NetworkMetrics> {
Ok(NetworkMetrics::default()) }
fn collect_inference_metrics(&self) -> Result<InferenceMetrics> {
Ok(InferenceMetrics::default()) }
fn collect_thermal_metrics(&self) -> Result<ThermalMetrics> {
Ok(ThermalMetrics::default()) }
fn collect_battery_metrics(&self) -> Result<BatteryMetrics> {
Ok(BatteryMetrics::default()) }
fn collect_platform_metrics(&self) -> Result<PlatformMetrics> {
Ok(PlatformMetrics::default()) }
fn estimate_memory_usage(&self) -> f32 {
let snapshot_size = std::mem::size_of::<MobileMetricsSnapshot>();
let total_size = snapshot_size * self.metrics_history.len();
total_size as f32 / (1024.0 * 1024.0) }
}
#[derive(Debug, Clone)]
pub struct CollectionStatistics {
pub total_samples: u64,
pub collection_duration: Duration,
pub average_sampling_rate: f64,
pub history_size: usize,
pub current_memory_usage_mb: f32,
}
impl Default for MobileMetricsSnapshot {
fn default() -> Self {
Self {
timestamp: 0,
memory: MemoryMetrics::default(),
cpu: CpuMetrics::default(),
gpu: GpuMetrics::default(),
network: NetworkMetrics::default(),
inference: InferenceMetrics::default(),
thermal: ThermalMetrics::default(),
battery: BatteryMetrics::default(),
platform: PlatformMetrics::default(),
}
}
}
impl Default for ThermalMetrics {
fn default() -> Self {
Self {
temperature_c: 25.0,
thermal_state: ThermalState::Nominal,
throttling_level: 0.0,
temperature_trend: TemperatureTrend::default(),
heat_generation_rate: 0.0,
cooling_efficiency: 1.0,
}
}
}
impl Default for TemperatureTrend {
fn default() -> Self {
Self {
current: 25.0,
previous: 25.0,
rate_of_change: 0.0,
direction: TrendDirection::Stable,
}
}
}
impl Default for BatteryMetrics {
fn default() -> Self {
Self {
level_percent: 100,
is_charging: false,
power_consumption_mw: 0.0,
time_remaining_min: None,
health_percent: 100,
temperature_c: 25.0,
voltage_v: 3.7,
}
}
}
impl Default for GenericMobileMetrics {
fn default() -> Self {
Self {
screen_brightness: 0.5,
orientation: DeviceOrientation::Portrait,
network_type: NetworkType::WiFi,
location_services_active: false,
}
}
}
#[cfg(test)]
mod tests {
use super::super::config::MobileProfilerConfig;
use super::*;
fn lcg_f32(state: &mut u64) -> f32 {
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(*state % 1000) as f32 / 1000.0
}
#[test]
fn test_mobile_metrics_collector_new() {
let config = MobileProfilerConfig::default();
let collector = MobileMetricsCollector::new(config);
assert!(collector.is_ok());
}
#[test]
fn test_mobile_metrics_collector_initial_total_samples() {
let config = MobileProfilerConfig::default();
let collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
let stats = collector.get_collection_stats();
assert_eq!(stats.total_samples, 0);
}
#[test]
fn test_mobile_metrics_collector_initial_history_empty() {
let config = MobileProfilerConfig::default();
let collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
let snapshots = collector.get_all_snapshots();
assert!(snapshots.is_empty());
}
#[test]
fn test_collect_metrics_increments_samples() {
let config = MobileProfilerConfig::default();
let mut collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
let result = collector.collect_metrics();
assert!(result.is_ok());
let stats = collector.get_collection_stats();
assert_eq!(stats.total_samples, 1);
}
#[test]
fn test_collect_metrics_multiple_accumulates() {
let config = MobileProfilerConfig::default();
let mut collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
for _ in 0..5 {
let _ = collector.collect_metrics();
}
let stats = collector.get_collection_stats();
assert_eq!(stats.total_samples, 5);
}
#[test]
fn test_get_current_snapshot_ok() {
let config = MobileProfilerConfig::default();
let mut collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
let _ = collector.collect_metrics();
let snapshot = collector.get_current_snapshot();
assert!(snapshot.is_ok());
}
#[test]
fn test_stop_collection_ok() {
let config = MobileProfilerConfig::default();
let mut collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
let result = collector.stop_collection();
assert!(result.is_ok());
}
#[test]
fn test_collection_statistics_fields() {
let config = MobileProfilerConfig::default();
let mut collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
let _ = collector.collect_metrics();
let stats = collector.get_collection_stats();
assert_eq!(stats.history_size, 1);
assert!(stats.current_memory_usage_mb >= 0.0);
}
#[test]
fn test_thermal_metrics_default() {
let thermal = ThermalMetrics::default();
assert_eq!(thermal.temperature_c, 25.0);
assert_eq!(thermal.throttling_level, 0.0);
assert_eq!(thermal.cooling_efficiency, 1.0);
}
#[test]
fn test_temperature_trend_default() {
let trend = TemperatureTrend::default();
assert_eq!(trend.current, 25.0);
assert_eq!(trend.previous, 25.0);
assert_eq!(trend.rate_of_change, 0.0);
assert!(matches!(trend.direction, TrendDirection::Stable));
}
#[test]
fn test_trend_direction_variants() {
let rising = TrendDirection::Rising;
let falling = TrendDirection::Falling;
let stable = TrendDirection::Stable;
assert_eq!(rising, TrendDirection::Rising);
assert_eq!(falling, TrendDirection::Falling);
assert_eq!(stable, TrendDirection::Stable);
}
#[test]
fn test_battery_metrics_default() {
let battery = BatteryMetrics::default();
assert_eq!(battery.level_percent, 100);
assert!(!battery.is_charging);
assert_eq!(battery.power_consumption_mw, 0.0);
assert!(battery.time_remaining_min.is_none());
assert_eq!(battery.health_percent, 100);
assert_eq!(battery.voltage_v, 3.7);
}
#[test]
fn test_battery_metrics_construction() {
let mut s = 42u64;
let battery = BatteryMetrics {
level_percent: 75,
is_charging: true,
power_consumption_mw: lcg_f32(&mut s) * 5000.0,
time_remaining_min: Some(120),
health_percent: 95,
temperature_c: 30.0,
voltage_v: 3.8,
};
assert_eq!(battery.level_percent, 75);
assert!(battery.is_charging);
assert_eq!(battery.time_remaining_min, Some(120));
}
#[test]
fn test_platform_metrics_default() {
let platform = PlatformMetrics::default();
assert!(platform.ios_metrics.is_none());
assert!(platform.android_metrics.is_none());
}
#[test]
fn test_generic_mobile_metrics_default() {
let generic = GenericMobileMetrics::default();
assert_eq!(generic.screen_brightness, 0.5);
assert!(!generic.location_services_active);
assert!(matches!(generic.orientation, DeviceOrientation::Portrait));
assert!(matches!(generic.network_type, NetworkType::WiFi));
}
#[test]
fn test_device_orientation_variants() {
let _portrait = DeviceOrientation::Portrait;
let _ll = DeviceOrientation::LandscapeLeft;
let _lr = DeviceOrientation::LandscapeRight;
let _upside = DeviceOrientation::PortraitUpsideDown;
let _face_up = DeviceOrientation::FaceUp;
let _face_down = DeviceOrientation::FaceDown;
}
#[test]
fn test_network_type_variants() {
let _wifi = NetworkType::WiFi;
let _cell = NetworkType::Cellular;
let _eth = NetworkType::Ethernet;
let _none = NetworkType::None;
let _unk = NetworkType::Unknown;
}
#[test]
fn test_memory_pressure_level_variants() {
let _normal = MemoryPressureLevel::Normal;
let _warning = MemoryPressureLevel::Warning;
let _urgent = MemoryPressureLevel::Urgent;
let _critical = MemoryPressureLevel::Critical;
}
#[test]
fn test_thermal_pressure_level_variants() {
let _nominal = ThermalPressureLevel::Nominal;
let _fair = ThermalPressureLevel::Fair;
let _serious = ThermalPressureLevel::Serious;
let _critical = ThermalPressureLevel::Critical;
}
#[test]
fn test_coreml_compute_unit_variants() {
let _cpu_only = CoreMLComputeUnit::CPUOnly;
let _cpu_gpu = CoreMLComputeUnit::CPUAndGPU;
let _cpu_ne = CoreMLComputeUnit::CPUAndNeuralEngine;
let _all = CoreMLComputeUnit::All;
}
#[test]
fn test_mobile_metrics_snapshot_default_timestamp_zero() {
let snapshot = MobileMetricsSnapshot::default();
assert_eq!(snapshot.timestamp, 0);
}
#[test]
fn test_history_size_limited_by_config() {
let mut config = MobileProfilerConfig::default();
config.sampling.max_samples = 3;
let mut collector = MobileMetricsCollector::new(config)
.unwrap_or_else(|_| panic!("collector creation failed"));
for _ in 0..10 {
let _ = collector.collect_metrics();
}
let stats = collector.get_collection_stats();
assert!(stats.history_size <= 3);
assert_eq!(stats.total_samples, 10);
}
#[test]
fn test_thermal_metrics_construction_with_lcg() {
let mut s = 99u64;
let thermal = ThermalMetrics {
temperature_c: lcg_f32(&mut s) * 80.0,
thermal_state: crate::device_info::ThermalState::Nominal,
throttling_level: lcg_f32(&mut s),
temperature_trend: TemperatureTrend::default(),
heat_generation_rate: lcg_f32(&mut s) * 10.0,
cooling_efficiency: lcg_f32(&mut s),
};
assert!(thermal.temperature_c >= 0.0);
assert!(thermal.throttling_level >= 0.0 && thermal.throttling_level <= 1.0);
}
#[test]
fn test_android_runtime_metrics_fields() {
let runtime = AndroidRuntimeMetrics {
gc_count: 5,
gc_time_ms: 12.3,
heap_utilization: 0.7,
compilation_time_ms: 45.0,
};
assert_eq!(runtime.gc_count, 5);
assert_eq!(runtime.gc_time_ms, 12.3);
}
#[test]
fn test_android_gpu_metrics_fields() {
let gpu = AndroidGpuMetrics {
frequency_mhz: 800,
busy_percent: 45.0,
memory_usage_mb: 128.0,
power_mw: 500.0,
};
assert_eq!(gpu.frequency_mhz, 800);
assert_eq!(gpu.busy_percent, 45.0);
}
}