use crate::{
device_info::{ChargingStatus, MobileDeviceInfo, ThermalState},
MobileConfig,
};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
use trustformers_core::error::{CoreError, Result};
use trustformers_core::TrustformersError;
pub struct ThermalPowerManager {
config: ThermalPowerConfig,
thermal_monitor: ThermalMonitor,
power_monitor: PowerMonitor,
throttling_controller: ThrottlingController,
inference_scheduler: PowerAwareScheduler,
stats: ThermalPowerStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalPowerConfig {
pub enable_thermal_monitoring: bool,
pub enable_power_monitoring: bool,
pub thermal_check_interval_ms: u64,
pub power_check_interval_ms: u64,
pub thermal_thresholds: ThermalThresholds,
pub power_thresholds: PowerThresholds,
pub throttling_strategy: ThrottlingStrategy,
pub power_strategy: PowerOptimizationStrategy,
pub max_thermal_history: usize,
pub max_power_history: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalThresholds {
pub light_throttle_celsius: f32,
pub moderate_throttle_celsius: f32,
pub aggressive_throttle_celsius: f32,
pub emergency_celsius: f32,
pub cooldown_celsius: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PowerThresholds {
pub low_battery_percent: u8,
pub critical_battery_percent: u8,
pub power_save_percent: u8,
pub max_power_mw: Option<f32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ThrottlingStrategy {
Conservative,
Balanced,
Aggressive,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PowerOptimizationStrategy {
MaxBatteryLife,
Balanced,
MaxPerformance,
Adaptive,
}
struct ThermalMonitor {
current_state: ThermalState,
temperature_history: VecDeque<TemperatureReading>,
last_check: Instant,
check_interval: Duration,
}
#[derive(Debug, Clone)]
pub struct TemperatureReading {
pub timestamp: Instant,
pub temperature_celsius: f32,
pub thermal_state: ThermalState,
pub sensor_name: String,
}
struct PowerMonitor {
battery_level: Option<u8>,
charging_status: ChargingStatus,
power_consumption_mw: Option<f32>,
power_history: VecDeque<PowerReading>,
last_check: Instant,
check_interval: Duration,
}
#[derive(Debug, Clone)]
pub struct PowerReading {
timestamp: Instant,
battery_level: Option<u8>,
charging_status: ChargingStatus,
power_consumption_mw: Option<f32>,
estimated_time_remaining_minutes: Option<u32>,
}
struct ThrottlingController {
current_throttle_level: ThrottleLevel,
base_config: MobileConfig,
throttled_config: MobileConfig,
throttle_history: VecDeque<ThrottleEvent>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ThrottleLevel {
None,
Light,
Moderate,
Aggressive,
Emergency,
}
#[derive(Debug, Clone)]
struct ThrottleEvent {
timestamp: Instant,
level: ThrottleLevel,
reason: ThrottleReason,
config_changes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ThrottleReason {
ThermalPressure,
PowerConstraint,
BatteryLow,
CombinedFactors,
}
struct PowerAwareScheduler {
inference_queue: VecDeque<InferenceRequest>,
scheduled_inferences: VecDeque<ScheduledInference>,
scheduling_strategy: SchedulingStrategy,
}
#[derive(Debug, Clone)]
pub struct InferenceRequest {
id: String,
priority: InferencePriority,
estimated_duration_ms: u64,
power_budget_mw: Option<f32>,
deadline: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct ScheduledInference {
request: InferenceRequest,
scheduled_time: Instant,
expected_completion: Instant,
config: MobileConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum InferencePriority {
Background,
Normal,
High,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SchedulingStrategy {
FIFO, Priority, PowerAware, ThermalAware, Adaptive, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalPowerStats {
pub total_monitoring_time_seconds: f64,
pub avg_temperature_celsius: f32,
pub peak_temperature_celsius: f32,
pub thermal_state_durations: std::collections::HashMap<String, f64>,
pub total_throttle_events: usize,
pub total_throttle_time_seconds: f64,
pub avg_battery_level: Option<f32>,
pub avg_power_consumption_mw: Option<f32>,
pub power_saved_mwh: f32,
pub throughput_degradation_percent: f32,
}
impl Default for ThermalPowerConfig {
fn default() -> Self {
Self {
enable_thermal_monitoring: true,
enable_power_monitoring: true,
thermal_check_interval_ms: 1000, power_check_interval_ms: 5000, thermal_thresholds: ThermalThresholds::default(),
power_thresholds: PowerThresholds::default(),
throttling_strategy: ThrottlingStrategy::Balanced,
power_strategy: PowerOptimizationStrategy::Adaptive,
max_thermal_history: 1000,
max_power_history: 500,
}
}
}
impl ThermalPowerConfig {
pub fn power_saving() -> Self {
Self {
enable_thermal_monitoring: true,
enable_power_monitoring: true,
thermal_check_interval_ms: 500, power_check_interval_ms: 2000, thermal_thresholds: ThermalThresholds {
light_throttle_celsius: 55.0, moderate_throttle_celsius: 60.0,
aggressive_throttle_celsius: 65.0,
emergency_celsius: 70.0, cooldown_celsius: 50.0,
},
power_thresholds: PowerThresholds {
low_battery_percent: 30, critical_battery_percent: 15,
power_save_percent: 50, max_power_mw: Some(2000.0), },
throttling_strategy: ThrottlingStrategy::Conservative,
power_strategy: PowerOptimizationStrategy::MaxBatteryLife,
max_thermal_history: 1500, max_power_history: 1000,
}
}
pub fn balanced() -> Self {
Self {
enable_thermal_monitoring: true,
enable_power_monitoring: true,
thermal_check_interval_ms: 1000, power_check_interval_ms: 5000,
thermal_thresholds: ThermalThresholds::default(), power_thresholds: PowerThresholds::default(),
throttling_strategy: ThrottlingStrategy::Balanced,
power_strategy: PowerOptimizationStrategy::Balanced,
max_thermal_history: 1000,
max_power_history: 500,
}
}
pub fn high_performance() -> Self {
Self {
enable_thermal_monitoring: true, enable_power_monitoring: false, thermal_check_interval_ms: 2000, power_check_interval_ms: 10000, thermal_thresholds: ThermalThresholds {
light_throttle_celsius: 75.0, moderate_throttle_celsius: 80.0,
aggressive_throttle_celsius: 85.0,
emergency_celsius: 90.0, cooldown_celsius: 70.0,
},
power_thresholds: PowerThresholds {
low_battery_percent: 10, critical_battery_percent: 5,
power_save_percent: 15, max_power_mw: Some(10000.0), },
throttling_strategy: ThrottlingStrategy::Aggressive, power_strategy: PowerOptimizationStrategy::MaxPerformance,
max_thermal_history: 500, max_power_history: 250,
}
}
}
impl Default for ThermalThresholds {
fn default() -> Self {
Self {
light_throttle_celsius: 65.0, moderate_throttle_celsius: 70.0, aggressive_throttle_celsius: 75.0, emergency_celsius: 80.0, cooldown_celsius: 60.0, }
}
}
impl Default for PowerThresholds {
fn default() -> Self {
Self {
low_battery_percent: 20, critical_battery_percent: 10, power_save_percent: 30, max_power_mw: Some(5000.0), }
}
}
impl ThermalPowerManager {
pub fn new(config: ThermalPowerConfig, device_info: &MobileDeviceInfo) -> Result<Self> {
let thermal_monitor = ThermalMonitor::new(
Duration::from_millis(config.thermal_check_interval_ms),
config.max_thermal_history,
);
let power_monitor = PowerMonitor::new(
Duration::from_millis(config.power_check_interval_ms),
config.max_power_history,
);
let throttling_controller = ThrottlingController::new();
let inference_scheduler = PowerAwareScheduler::new();
let stats = ThermalPowerStats::new();
Ok(Self {
config,
thermal_monitor,
power_monitor,
throttling_controller,
inference_scheduler,
stats,
})
}
pub fn start_monitoring(&mut self) -> Result<()> {
if self.config.enable_thermal_monitoring {
self.thermal_monitor.start()?;
tracing::info!("Thermal monitoring started");
}
if self.config.enable_power_monitoring {
self.power_monitor.start()?;
tracing::info!("Power monitoring started");
}
Ok(())
}
pub fn stop_monitoring(&mut self) {
self.thermal_monitor.stop();
self.power_monitor.stop();
tracing::info!("Thermal and power monitoring stopped");
}
pub fn update(&mut self, mobile_config: &mut MobileConfig) -> Result<bool> {
let mut config_changed = false;
if self.config.enable_thermal_monitoring {
self.thermal_monitor.update()?;
if let Some(new_throttle_level) = self.evaluate_thermal_throttling()? {
if new_throttle_level != self.throttling_controller.current_throttle_level {
self.apply_thermal_throttling(mobile_config, new_throttle_level)?;
config_changed = true;
}
}
}
if self.config.enable_power_monitoring {
self.power_monitor.update()?;
if self.evaluate_power_optimization()? {
self.apply_power_optimization(mobile_config)?;
config_changed = true;
}
}
self.update_stats();
Ok(config_changed)
}
pub fn schedule_inference(
&mut self,
request: InferenceRequest,
current_config: &MobileConfig,
) -> Result<Option<ScheduledInference>> {
if self.can_run_inference_now(&request, current_config)? {
let scheduled = ScheduledInference {
scheduled_time: Instant::now(),
expected_completion: Instant::now()
+ Duration::from_millis(request.estimated_duration_ms),
config: current_config.clone(),
request,
};
Ok(Some(scheduled))
} else {
self.inference_scheduler.queue_request(request);
Ok(None)
}
}
pub fn get_next_inference(
&mut self,
current_config: &MobileConfig,
) -> Option<ScheduledInference> {
self.inference_scheduler.get_next_ready_inference(current_config)
}
pub fn get_stats(&self) -> &ThermalPowerStats {
&self.stats
}
pub fn get_current_reading(&self) -> Result<TemperatureReading> {
self.thermal_monitor
.temperature_history
.back()
.cloned()
.ok_or_else(|| TrustformersError::runtime_error("No thermal reading available".into()))
.map_err(|e| e.into())
}
pub fn get_current_power(&self) -> Option<f32> {
self.power_monitor
.power_history
.back()
.and_then(|reading| reading.power_consumption_mw)
}
pub fn get_thermal_history(&self) -> Vec<TemperatureReading> {
self.thermal_monitor.temperature_history.iter().cloned().collect()
}
pub fn get_power_history(&self) -> Vec<PowerReading> {
self.power_monitor.power_history.iter().cloned().collect()
}
pub fn create_optimized_config(&self, base_config: &MobileConfig) -> MobileConfig {
let mut optimized = base_config.clone();
self.apply_thermal_optimizations(&mut optimized);
self.apply_power_optimizations(&mut optimized);
optimized
}
fn evaluate_thermal_throttling(&self) -> Result<Option<ThrottleLevel>> {
let current_temp = self.thermal_monitor.get_current_temperature()?;
let thermal_state = self.thermal_monitor.current_state;
let new_level = match thermal_state {
ThermalState::Critical | ThermalState::Emergency => ThrottleLevel::Emergency,
ThermalState::Serious => {
if current_temp >= self.config.thermal_thresholds.aggressive_throttle_celsius {
ThrottleLevel::Aggressive
} else {
ThrottleLevel::Moderate
}
},
ThermalState::Fair => {
if current_temp >= self.config.thermal_thresholds.moderate_throttle_celsius {
ThrottleLevel::Moderate
} else if current_temp >= self.config.thermal_thresholds.light_throttle_celsius {
ThrottleLevel::Light
} else {
ThrottleLevel::None
}
},
ThermalState::Nominal => {
if current_temp <= self.config.thermal_thresholds.cooldown_celsius {
ThrottleLevel::None
} else {
self.throttling_controller.current_throttle_level }
},
_ => ThrottleLevel::None,
};
Ok(Some(new_level))
}
fn apply_thermal_throttling(
&mut self,
config: &mut MobileConfig,
level: ThrottleLevel,
) -> Result<()> {
let changes = match level {
ThrottleLevel::None => {
*config = self.throttling_controller.base_config.clone();
vec!["Restored base configuration".to_string()]
},
ThrottleLevel::Light => {
config.num_threads = (config.num_threads * 3 / 4).max(1);
config.max_batch_size = (config.max_batch_size * 3 / 4).max(1);
vec!["Reduced threads and batch size by 25%".to_string()]
},
ThrottleLevel::Moderate => {
config.num_threads = (config.num_threads / 2).max(1);
config.max_batch_size = 1;
config.memory_optimization = crate::MemoryOptimization::Balanced;
config.enable_batching = false;
vec!["Reduced threads by 50%, disabled batching".to_string()]
},
ThrottleLevel::Aggressive => {
config.num_threads = 1;
config.max_batch_size = 1;
config.memory_optimization = crate::MemoryOptimization::Maximum;
config.enable_batching = false;
config.backend = crate::MobileBackend::CPU; vec!["Single thread, CPU only, maximum memory optimization".to_string()]
},
ThrottleLevel::Emergency => {
config.num_threads = 0; vec!["Emergency throttling: inference halted".to_string()]
},
};
self.throttling_controller.current_throttle_level = level;
self.throttling_controller.throttle_history.push_back(ThrottleEvent {
timestamp: Instant::now(),
level,
reason: ThrottleReason::ThermalPressure,
config_changes: changes,
});
while self.throttling_controller.throttle_history.len() > 100 {
self.throttling_controller.throttle_history.pop_front();
}
tracing::info!("Applied thermal throttling level: {:?}", level);
Ok(())
}
fn evaluate_power_optimization(&self) -> Result<bool> {
let battery_level = self.power_monitor.battery_level.unwrap_or(100);
let charging = matches!(self.power_monitor.charging_status, ChargingStatus::Charging);
let power_consumption = self.power_monitor.power_consumption_mw.unwrap_or(0.0);
let needs_optimization = match self.config.power_strategy {
PowerOptimizationStrategy::MaxBatteryLife => !charging,
PowerOptimizationStrategy::Balanced => {
battery_level < self.config.power_thresholds.low_battery_percent && !charging
},
PowerOptimizationStrategy::MaxPerformance => false,
PowerOptimizationStrategy::Adaptive => {
(!charging && battery_level < self.config.power_thresholds.power_save_percent)
|| (self
.config
.power_thresholds
.max_power_mw
.is_some_and(|max| power_consumption > max))
},
};
Ok(needs_optimization)
}
fn apply_power_optimization(&mut self, config: &mut MobileConfig) -> Result<()> {
let battery_level = self.power_monitor.battery_level.unwrap_or(100);
if battery_level < self.config.power_thresholds.critical_battery_percent {
config.memory_optimization = crate::MemoryOptimization::Maximum;
config.num_threads = 1;
config.enable_batching = false;
config.backend = crate::MobileBackend::CPU;
tracing::warn!("Critical battery level: applying aggressive power optimization");
} else if battery_level < self.config.power_thresholds.low_battery_percent {
config.num_threads = (config.num_threads / 2).max(1);
config.memory_optimization = crate::MemoryOptimization::Balanced;
tracing::info!("Low battery level: applying moderate power optimization");
}
Ok(())
}
fn can_run_inference_now(
&self,
request: &InferenceRequest,
config: &MobileConfig,
) -> Result<bool> {
if self.thermal_monitor.current_state == ThermalState::Emergency {
return Ok(false);
}
if config.num_threads == 0 {
return Ok(false);
}
if let Some(power_budget) = request.power_budget_mw {
if let Some(current_power) = self.power_monitor.power_consumption_mw {
if current_power + power_budget
> self.config.power_thresholds.max_power_mw.unwrap_or(f32::MAX)
{
return Ok(false);
}
}
}
if matches!(
request.priority,
InferencePriority::Background | InferencePriority::Normal
) {
if let Some(battery) = self.power_monitor.battery_level {
if battery < self.config.power_thresholds.critical_battery_percent {
return Ok(false);
}
}
}
Ok(true)
}
fn apply_thermal_optimizations(&self, config: &mut MobileConfig) {
match self.thermal_monitor.current_state {
ThermalState::Serious | ThermalState::Critical => {
config.memory_optimization = crate::MemoryOptimization::Maximum;
config.num_threads = (config.num_threads / 2).max(1);
config.enable_batching = false;
},
ThermalState::Fair => {
config.num_threads = (config.num_threads * 3 / 4).max(1);
},
_ => {},
}
}
fn apply_power_optimizations(&self, config: &mut MobileConfig) {
if let Some(battery) = self.power_monitor.battery_level {
if battery < self.config.power_thresholds.low_battery_percent {
config.memory_optimization = crate::MemoryOptimization::Maximum;
config.backend = crate::MobileBackend::CPU;
if battery < self.config.power_thresholds.critical_battery_percent {
config.num_threads = 1;
config.enable_batching = false;
}
}
}
}
fn update_stats(&mut self) {
if let Ok(current_temp) = self.thermal_monitor.get_current_temperature() {
self.stats.peak_temperature_celsius =
self.stats.peak_temperature_celsius.max(current_temp);
let history_len = self.thermal_monitor.temperature_history.len() as f32;
if history_len > 0.0 {
let sum: f32 = self
.thermal_monitor
.temperature_history
.iter()
.map(|r| r.temperature_celsius)
.sum();
self.stats.avg_temperature_celsius = sum / history_len;
}
}
if let Some(battery) = self.power_monitor.battery_level {
if let Some(ref mut avg_battery) = self.stats.avg_battery_level {
*avg_battery = (*avg_battery + battery as f32) / 2.0;
} else {
self.stats.avg_battery_level = Some(battery as f32);
}
}
if let Some(power) = self.power_monitor.power_consumption_mw {
if let Some(ref mut avg_power) = self.stats.avg_power_consumption_mw {
*avg_power = (*avg_power + power) / 2.0;
} else {
self.stats.avg_power_consumption_mw = Some(power);
}
}
self.stats.total_throttle_events = self.throttling_controller.throttle_history.len();
}
}
impl ThermalMonitor {
fn new(check_interval: Duration, max_history: usize) -> Self {
Self {
current_state: ThermalState::Nominal,
temperature_history: VecDeque::with_capacity(max_history),
last_check: Instant::now(),
check_interval,
}
}
fn start(&mut self) -> Result<()> {
self.last_check = Instant::now();
Ok(())
}
fn stop(&mut self) {
}
fn update(&mut self) -> Result<()> {
if self.last_check.elapsed() >= self.check_interval {
let temperature = self.read_temperature()?;
let thermal_state = Self::temperature_to_state(temperature);
let reading = TemperatureReading {
timestamp: Instant::now(),
temperature_celsius: temperature,
thermal_state,
sensor_name: "CPU".to_string(), };
self.temperature_history.push_back(reading);
while self.temperature_history.len() > self.temperature_history.capacity() {
self.temperature_history.pop_front();
}
self.current_state = thermal_state;
self.last_check = Instant::now();
}
Ok(())
}
fn get_current_temperature(&self) -> Result<f32> {
self.temperature_history
.back()
.map(|r| r.temperature_celsius)
.ok_or_else(|| {
TrustformersError::runtime_error("No temperature readings available".into())
})
.map_err(|e| e.into())
}
fn read_temperature(&self) -> Result<f32> {
#[cfg(target_os = "android")]
{
self.read_android_temperature()
}
#[cfg(target_os = "ios")]
{
self.read_ios_temperature()
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.last_check.elapsed().as_millis().hash(&mut hasher);
let hash_val = hasher.finish();
let variation = (hash_val % 20) as f32; Ok(45.0 + variation) }
}
#[cfg(target_os = "android")]
fn read_android_temperature(&self) -> Result<f32> {
Ok(50.0) }
#[cfg(target_os = "ios")]
fn read_ios_temperature(&self) -> Result<f32> {
Ok(48.0) }
fn temperature_to_state(temperature: f32) -> ThermalState {
match temperature {
t if t < 55.0 => ThermalState::Nominal,
t if t < 65.0 => ThermalState::Fair,
t if t < 75.0 => ThermalState::Serious,
t if t < 85.0 => ThermalState::Critical,
_ => ThermalState::Emergency,
}
}
}
impl PowerMonitor {
fn new(check_interval: Duration, max_history: usize) -> Self {
Self {
battery_level: None,
charging_status: ChargingStatus::Unknown,
power_consumption_mw: None,
power_history: VecDeque::with_capacity(max_history),
last_check: Instant::now(),
check_interval,
}
}
fn start(&mut self) -> Result<()> {
self.last_check = Instant::now();
Ok(())
}
fn stop(&mut self) {
}
fn update(&mut self) -> Result<()> {
if self.last_check.elapsed() >= self.check_interval {
let (battery_level, charging_status, power_consumption) = self.read_power_info()?;
let reading = PowerReading {
timestamp: Instant::now(),
battery_level,
charging_status,
power_consumption_mw: power_consumption,
estimated_time_remaining_minutes: self
.estimate_time_remaining(battery_level, power_consumption),
};
self.power_history.push_back(reading);
while self.power_history.len() > self.power_history.capacity() {
self.power_history.pop_front();
}
self.battery_level = battery_level;
self.charging_status = charging_status;
self.power_consumption_mw = power_consumption;
self.last_check = Instant::now();
}
Ok(())
}
fn read_power_info(&self) -> Result<(Option<u8>, ChargingStatus, Option<f32>)> {
#[cfg(target_os = "android")]
{
self.read_android_power_info()
}
#[cfg(target_os = "ios")]
{
self.read_ios_power_info()
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
let battery = Some(75u8); let charging = ChargingStatus::Discharging;
let power = Some(2500.0); Ok((battery, charging, power))
}
}
#[cfg(target_os = "android")]
fn read_android_power_info(&self) -> Result<(Option<u8>, ChargingStatus, Option<f32>)> {
Ok((Some(80), ChargingStatus::Discharging, Some(2000.0)))
}
#[cfg(target_os = "ios")]
fn read_ios_power_info(&self) -> Result<(Option<u8>, ChargingStatus, Option<f32>)> {
Ok((Some(85), ChargingStatus::Discharging, Some(1800.0)))
}
fn estimate_time_remaining(
&self,
battery_level: Option<u8>,
power_consumption: Option<f32>,
) -> Option<u32> {
if let (Some(battery), Some(power)) = (battery_level, power_consumption) {
if power > 0.0 {
let battery_energy_wh = 11.0 * (battery as f32 / 100.0);
let power_w = power / 1000.0;
let hours_remaining = battery_energy_wh / power_w;
Some((hours_remaining * 60.0) as u32) } else {
None
}
} else {
None
}
}
}
impl ThrottlingController {
fn new() -> Self {
Self {
current_throttle_level: ThrottleLevel::None,
base_config: MobileConfig::default(),
throttled_config: MobileConfig::default(),
throttle_history: VecDeque::new(),
}
}
}
impl PowerAwareScheduler {
fn new() -> Self {
Self {
inference_queue: VecDeque::new(),
scheduled_inferences: VecDeque::new(),
scheduling_strategy: SchedulingStrategy::Adaptive,
}
}
fn queue_request(&mut self, request: InferenceRequest) {
let insert_pos = self
.inference_queue
.iter()
.position(|r| r.priority < request.priority)
.unwrap_or(self.inference_queue.len());
self.inference_queue.insert(insert_pos, request);
}
fn get_next_ready_inference(&mut self, _config: &MobileConfig) -> Option<ScheduledInference> {
self.inference_queue.pop_front().map(|request| {
ScheduledInference {
scheduled_time: Instant::now(),
expected_completion: Instant::now()
+ Duration::from_millis(request.estimated_duration_ms),
config: MobileConfig::default(), request,
}
})
}
}
impl ThermalPowerStats {
fn new() -> Self {
Self {
total_monitoring_time_seconds: 0.0,
avg_temperature_celsius: 0.0,
peak_temperature_celsius: 0.0,
thermal_state_durations: std::collections::HashMap::new(),
total_throttle_events: 0,
total_throttle_time_seconds: 0.0,
avg_battery_level: None,
avg_power_consumption_mw: None,
power_saved_mwh: 0.0,
throughput_degradation_percent: 0.0,
}
}
}
pub struct ThermalPowerUtils;
impl ThermalPowerUtils {
pub fn create_optimized_config(device_info: &MobileDeviceInfo) -> ThermalPowerConfig {
let mut config = ThermalPowerConfig::default();
match device_info.performance_scores.overall_tier {
crate::device_info::PerformanceTier::Budget => {
config.throttling_strategy = ThrottlingStrategy::Conservative;
config.power_strategy = PowerOptimizationStrategy::MaxBatteryLife;
config.thermal_thresholds.light_throttle_celsius = 60.0; },
crate::device_info::PerformanceTier::Flagship => {
config.throttling_strategy = ThrottlingStrategy::Aggressive;
config.power_strategy = PowerOptimizationStrategy::Balanced;
config.thermal_thresholds.light_throttle_celsius = 70.0; },
_ => {
},
}
if !device_info.thermal_info.throttling_supported {
config.enable_thermal_monitoring = false;
}
config
}
pub fn estimate_power_consumption(
config: &MobileConfig,
device_info: &MobileDeviceInfo,
) -> f32 {
let base_power = match config.backend {
crate::MobileBackend::CPU => 2000.0, crate::MobileBackend::GPU => 3500.0, crate::MobileBackend::CoreML => 1500.0, crate::MobileBackend::NNAPI => 2500.0, crate::MobileBackend::Metal => 3200.0, crate::MobileBackend::Vulkan => 3000.0, crate::MobileBackend::OpenCL => 3100.0, crate::MobileBackend::Custom => 2000.0, };
let thread_factor = if config.num_threads == 0 {
0.5 } else {
(config.num_threads as f32 / device_info.cpu_info.total_cores as f32).max(0.25)
};
let batch_factor = if config.enable_batching {
1.0 + (config.max_batch_size as f32 * 0.1)
} else {
1.0
};
base_power * thread_factor * batch_factor
}
pub fn calculate_thermal_margin(current_temp: f32, thresholds: &ThermalThresholds) -> f32 {
if current_temp >= thresholds.emergency_celsius {
0.0 } else if current_temp >= thresholds.aggressive_throttle_celsius {
(thresholds.emergency_celsius - current_temp)
/ (thresholds.emergency_celsius - thresholds.aggressive_throttle_celsius)
} else {
1.0 }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_thermal_power_config() {
let config = ThermalPowerConfig::default();
assert!(config.enable_thermal_monitoring);
assert!(config.enable_power_monitoring);
assert_eq!(config.throttling_strategy, ThrottlingStrategy::Balanced);
}
#[test]
fn test_thermal_thresholds() {
let thresholds = ThermalThresholds::default();
assert!(thresholds.light_throttle_celsius < thresholds.moderate_throttle_celsius);
assert!(thresholds.moderate_throttle_celsius < thresholds.aggressive_throttle_celsius);
assert!(thresholds.aggressive_throttle_celsius < thresholds.emergency_celsius);
}
#[test]
fn test_power_thresholds() {
let thresholds = PowerThresholds::default();
assert!(thresholds.critical_battery_percent < thresholds.low_battery_percent);
assert!(thresholds.low_battery_percent < thresholds.power_save_percent);
}
#[test]
fn test_throttle_levels() {
assert!(ThrottleLevel::None < ThrottleLevel::Light);
assert!(ThrottleLevel::Light < ThrottleLevel::Moderate);
assert!(ThrottleLevel::Moderate < ThrottleLevel::Aggressive);
assert!(ThrottleLevel::Aggressive < ThrottleLevel::Emergency);
}
#[test]
fn test_inference_priorities() {
assert!(InferencePriority::Background < InferencePriority::Normal);
assert!(InferencePriority::Normal < InferencePriority::High);
assert!(InferencePriority::High < InferencePriority::Critical);
}
#[test]
fn test_thermal_monitor() {
let monitor = ThermalMonitor::new(Duration::from_secs(1), 100);
assert_eq!(monitor.current_state, ThermalState::Nominal);
assert!(monitor.temperature_history.is_empty());
}
#[test]
fn test_power_consumption_estimation() {
let config = MobileConfig::default();
let device_info =
crate::device_info::MobileDeviceDetector::detect().expect("operation failed in test");
let estimated_power = ThermalPowerUtils::estimate_power_consumption(&config, &device_info);
assert!(estimated_power > 0.0);
assert!(estimated_power < 10000.0); }
#[test]
fn test_thermal_margin_calculation() {
let thresholds = ThermalThresholds::default();
let margin_normal = ThermalPowerUtils::calculate_thermal_margin(50.0, &thresholds);
assert_eq!(margin_normal, 1.0);
let margin_emergency =
ThermalPowerUtils::calculate_thermal_margin(thresholds.emergency_celsius, &thresholds);
assert_eq!(margin_emergency, 0.0);
}
#[test]
fn test_temperature_to_thermal_state() {
assert_eq!(
ThermalMonitor::temperature_to_state(45.0),
ThermalState::Nominal
);
assert_eq!(
ThermalMonitor::temperature_to_state(60.0),
ThermalState::Fair
);
assert_eq!(
ThermalMonitor::temperature_to_state(70.0),
ThermalState::Serious
);
assert_eq!(
ThermalMonitor::temperature_to_state(80.0),
ThermalState::Critical
);
assert_eq!(
ThermalMonitor::temperature_to_state(90.0),
ThermalState::Emergency
);
}
}