From 46bd638c5fbead4c4a386b640f66490aed70f099 Mon Sep 17 00:00:00 2001 From: Han Yin Date: Fri, 11 Apr 2025 14:38:38 -0700 Subject: [PATCH] util: implement performance monitor; wrap it with a viewmodel --- .../revamp/monitoring/PerformanceMonitor.kt | 161 ++++++++++++++++++ .../revamp/viewmodel/PerformanceViewModel.kt | 148 ++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 examples/llama.android/app/src/main/java/com/example/llama/revamp/monitoring/PerformanceMonitor.kt create mode 100644 examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/PerformanceViewModel.kt diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/monitoring/PerformanceMonitor.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/monitoring/PerformanceMonitor.kt new file mode 100644 index 0000000000..fe16c189d5 --- /dev/null +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/monitoring/PerformanceMonitor.kt @@ -0,0 +1,161 @@ +package com.example.llama.revamp.monitoring + +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.withContext +import kotlin.math.roundToInt + +/** + * Service that monitors device performance metrics such as memory usage, + * battery level, and temperature. + */ +class PerformanceMonitor(private val context: Context) { + + /** + * Provides a flow of memory usage information that updates at the specified interval. + */ + fun monitorMemoryUsage(intervalMs: Long = 5000): Flow = flow { + while(true) { + emit(getMemoryInfo()) + delay(intervalMs) + } + } + + /** + * Provides a flow of battery information that updates at the specified interval. + */ + fun monitorBattery(intervalMs: Long = 10000): Flow = flow { + while(true) { + emit(getBatteryInfo()) + delay(intervalMs) + } + } + + /** + * Provides a flow of temperature information that updates at the specified interval. + */ + fun monitorTemperature(intervalMs: Long = 10000): Flow = flow { + while(true) { + emit(getTemperatureInfo()) + delay(intervalMs) + } + } + + /** + * Gets the current memory usage information. + */ + private suspend fun getMemoryInfo(): MemoryMetrics = withContext(Dispatchers.IO) { + val mi = ActivityManager.MemoryInfo() + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + activityManager.getMemoryInfo(mi) + + val availableMem = mi.availMem + val totalMem = mi.totalMem + val percentUsed = ((totalMem - availableMem) / totalMem.toFloat() * 100).roundToInt() + + // Convert to more readable units (GB) + val availableGb = (availableMem / (1024.0 * 1024.0 * 1024.0)).toFloat().round(1) + val totalGb = (totalMem / (1024.0 * 1024.0 * 1024.0)).toFloat().round(1) + + MemoryMetrics( + availableMem = availableMem, + totalMem = totalMem, + percentUsed = percentUsed, + availableGb = availableGb, + totalGb = totalGb + ) + } + + /** + * Gets the current battery information. + */ + private fun getBatteryInfo(): BatteryMetrics { + val intent = context.registerReceiver(null, + IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + + val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) ?: 0 + val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, 100) ?: 100 + val batteryPct = level * 100 / scale + + val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1 + val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL + + return BatteryMetrics( + level = batteryPct, + isCharging = isCharging + ) + } + + /** + * Gets the current temperature information. + */ + private fun getTemperatureInfo(): TemperatureMetrics { + val intent = context.registerReceiver(null, + IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + + // Battery temperature is reported in tenths of a degree Celsius + val tempTenthsC = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0 + val tempC = tempTenthsC / 10.0f + + val warningLevel = when { + tempC >= 45.0f -> TemperatureWarningLevel.HIGH + tempC >= 40.0f -> TemperatureWarningLevel.MEDIUM + else -> TemperatureWarningLevel.NORMAL + } + + return TemperatureMetrics( + temperature = tempC, + warningLevel = warningLevel + ) + } + + private fun Float.round(decimals: Int): Float { + var multiplier = 1.0f + repeat(decimals) { multiplier *= 10 } + return (this * multiplier).roundToInt() / multiplier + } +} + +/** + * Data class containing memory usage metrics. + */ +data class MemoryMetrics( + val availableMem: Long, + val totalMem: Long, + val percentUsed: Int, + val availableGb: Float, + val totalGb: Float +) + +/** + * Data class containing battery information. + */ +data class BatteryMetrics( + val level: Int, + val isCharging: Boolean +) + +/** + * Warning levels for temperature. + */ +enum class TemperatureWarningLevel { + NORMAL, + MEDIUM, + HIGH +} + +/** + * Data class containing temperature information. + */ +data class TemperatureMetrics( + val temperature: Float, + val warningLevel: TemperatureWarningLevel +) diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/PerformanceViewModel.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/PerformanceViewModel.kt new file mode 100644 index 0000000000..29e5284ad2 --- /dev/null +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/PerformanceViewModel.kt @@ -0,0 +1,148 @@ +package com.example.llama.revamp.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.example.llama.revamp.data.preferences.UserPreferences +import com.example.llama.revamp.monitoring.BatteryMetrics +import com.example.llama.revamp.monitoring.MemoryMetrics +import com.example.llama.revamp.monitoring.PerformanceMonitor +import com.example.llama.revamp.monitoring.TemperatureMetrics +import com.example.llama.revamp.monitoring.TemperatureWarningLevel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +/** + * ViewModel that manages performance monitoring for the app. + */ +class PerformanceViewModel( + private val performanceMonitor: PerformanceMonitor, + private val userPreferences: UserPreferences +) : ViewModel() { + + // Memory usage metrics + private val _memoryUsage = MutableStateFlow(MemoryMetrics(0, 0, 0, 0f, 0f)) + val memoryUsage: StateFlow = _memoryUsage.asStateFlow() + + // Battery information + private val _batteryInfo = MutableStateFlow(BatteryMetrics(0, false)) + val batteryInfo: StateFlow = _batteryInfo.asStateFlow() + + // Temperature information + private val _temperatureInfo = MutableStateFlow(TemperatureMetrics(0f, TemperatureWarningLevel.NORMAL)) + val temperatureInfo: StateFlow = _temperatureInfo.asStateFlow() + + // User preferences + private val _isMonitoringEnabled = MutableStateFlow(true) + val isMonitoringEnabled: StateFlow = _isMonitoringEnabled.asStateFlow() + + private val _useFahrenheitUnit = MutableStateFlow(false) + val useFahrenheitUnit: StateFlow = _useFahrenheitUnit.asStateFlow() + + private val _monitoringInterval = MutableStateFlow(5000L) + val monitoringInterval: StateFlow = _monitoringInterval.asStateFlow() + + init { + viewModelScope.launch { + // Load user preferences + _isMonitoringEnabled.value = userPreferences.isPerformanceMonitoringEnabled().first() + _useFahrenheitUnit.value = userPreferences.usesFahrenheitTemperature().first() + _monitoringInterval.value = userPreferences.getMonitoringInterval().first() + + // Start monitoring if enabled + if (_isMonitoringEnabled.value) { + startMonitoring() + } + } + } + + /** + * Starts monitoring device performance. + */ + private fun startMonitoring() { + val interval = _monitoringInterval.value + + viewModelScope.launch { + performanceMonitor.monitorMemoryUsage(interval).collect { metrics -> + _memoryUsage.value = metrics + } + } + + viewModelScope.launch { + performanceMonitor.monitorBattery(interval * 2).collect { metrics -> + _batteryInfo.value = metrics + } + } + + viewModelScope.launch { + performanceMonitor.monitorTemperature(interval * 2).collect { metrics -> + _temperatureInfo.value = metrics + } + } + } + + /** + * Sets whether performance monitoring is enabled. + */ + fun setMonitoringEnabled(enabled: Boolean) { + viewModelScope.launch { + userPreferences.setPerformanceMonitoringEnabled(enabled) + _isMonitoringEnabled.value = enabled + + if (enabled && !isMonitoringActive()) { + startMonitoring() + } + } + } + + /** + * Sets the temperature unit preference. + */ + fun setUseFahrenheitUnit(useFahrenheit: Boolean) { + viewModelScope.launch { + userPreferences.setUseFahrenheitTemperature(useFahrenheit) + _useFahrenheitUnit.value = useFahrenheit + } + } + + /** + * Sets the monitoring interval. + */ + fun setMonitoringInterval(intervalMs: Long) { + viewModelScope.launch { + userPreferences.setMonitoringInterval(intervalMs) + _monitoringInterval.value = intervalMs + + // Restart monitoring with new interval if active + if (isMonitoringActive()) { + startMonitoring() + } + } + } + + /** + * Checks if monitoring is currently active. + */ + private fun isMonitoringActive(): Boolean { + return _isMonitoringEnabled.value + } + + /** + * Factory for creating PerformanceViewModel instances. + */ + class Factory( + private val performanceMonitor: PerformanceMonitor, + private val userPreferences: UserPreferences + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(PerformanceViewModel::class.java)) { + return PerformanceViewModel(performanceMonitor, userPreferences) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } + } +}