| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use std::fs::read_to_string; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use std::str::FromStr; | |
| use anyhow::Context; | |
| use anyhow::Result; | |
| use log::info; | |
| use crate::arch; | |
| use crate::common; | |
| use crate::common::read_from_file; | |
| use crate::common::BatterySaverMode; | |
| use crate::common::FullscreenVideo; | |
| use crate::common::GameMode; | |
| use crate::common::RTCAudioActive; | |
| use crate::common::VmBootMode; | |
| use crate::config::ConfigProvider; | |
| use crate::config::CpuOfflinePreference; | |
| use crate::config::Governor; | |
| use crate::config::PowerPreferences; | |
| use crate::config::PowerPreferencesType; | |
| use crate::config::PowerSourceType; | |
| use crate::cpu_utils::hotplug_cpus; | |
| use crate::cpu_utils::write_to_cpu_policy_patterns; | |
| use crate::cpu_utils::HotplugCpuAction; | |
| const POWER_SUPPLY_PATH: &str = "sys/class/power_supply"; | |
| const POWER_SUPPLY_ONLINE: &str = "online"; | |
| const POWER_SUPPLY_STATUS: &str = "status"; | |
| const GLOBAL_ONDEMAND_PATH: &str = "sys/devices/system/cpu/cpufreq/ondemand"; | |
| pub trait PowerSourceProvider { | |
| /// Returns the current power source of the system. | |
| fn get_power_source(&self) -> Result<PowerSourceType>; | |
| } | |
| /// See the `POWER_SUPPLY_STATUS_` enum in the linux kernel. | |
| /// These values are intended to describe the battery status. They are also used | |
| /// to describe the charger status, which adds a little bit of confusion. A | |
| /// charger will only return `Charging` or `NotCharging`. | |
| #[derive(Copy, Clone, Debug, PartialEq)] | |
| enum PowerSupplyStatus { | |
| Unknown, | |
| Charging, | |
| Discharging, | |
| NotCharging, | |
| Full, | |
| } | |
| impl FromStr for PowerSupplyStatus { | |
| type Err = anyhow::Error; | |
| fn from_str(s: &str) -> Result<Self, Self::Err> { | |
| let s = s.trim_end(); | |
| match s { | |
| "Unknown" => Ok(PowerSupplyStatus::Unknown), | |
| "Charging" => Ok(PowerSupplyStatus::Charging), | |
| "Discharging" => Ok(PowerSupplyStatus::Discharging), | |
| "Not charging" => Ok(PowerSupplyStatus::NotCharging), | |
| "Full" => Ok(PowerSupplyStatus::Full), | |
| _ => anyhow::bail!("Unknown Power Supply Status: '{}'", s), | |
| } | |
| } | |
| } | |
| #[derive(Clone, Debug)] | |
| pub struct DirectoryPowerSourceProvider { | |
| root: PathBuf, | |
| } | |
| impl DirectoryPowerSourceProvider { | |
| pub fn new(root: PathBuf) -> Self { | |
| Self { root } | |
| } | |
| } | |
| impl PowerSourceProvider for DirectoryPowerSourceProvider { | |
| /// Iterates through all the power supplies in sysfs and looks for the `online` property. | |
| /// This indicates an external power source is connected (AC), but it doesn't necessarily | |
| /// mean it's powering the system. Tests will sometimes disable the charger to get power | |
| /// measurements. In order to determine if the charger is powering the system we need to | |
| /// look at the `status` property. If there is no charger connected and powering the system | |
| /// then we assume we are running off a battery (DC). | |
| fn get_power_source(&self) -> Result<PowerSourceType> { | |
| let path = self.root.join(POWER_SUPPLY_PATH); | |
| if !path.exists() { | |
| return Ok(PowerSourceType::DC); | |
| } | |
| let dirs = path | |
| .read_dir() | |
| .with_context(|| format!("Failed to enumerate power supplies in {}", path.display()))?; | |
| for result in dirs { | |
| let charger_path = result?; | |
| let online_path = charger_path.path().join(POWER_SUPPLY_ONLINE); | |
| if !online_path.exists() { | |
| continue; | |
| } | |
| let online: u32 = read_from_file(&online_path) | |
| .with_context(|| format!("Error reading online from {}", online_path.display()))?; | |
| if online != 1 { | |
| continue; | |
| } | |
| let status_path = charger_path.path().join(POWER_SUPPLY_STATUS); | |
| if !status_path.exists() { | |
| continue; | |
| } | |
| let status_string = match read_to_string(&status_path) { | |
| Ok(data) => data, | |
| Err(e) => { | |
| info!( | |
| "Error reading status from {}, error: {}", | |
| status_path.display(), | |
| e | |
| ); | |
| // On some boards, reading some power supply status may return ENODATA, it's an | |
| // uncategorized Rust IO error kind. Continue checking other power supplies. | |
| continue; | |
| } | |
| }; | |
| let status_result = PowerSupplyStatus::from_str(&status_string); | |
| let status = match status_result { | |
| Err(_) => { | |
| info!( | |
| "Failure parsing '{}' from {}", | |
| status_string, | |
| status_path.display() | |
| ); | |
| continue; | |
| } | |
| Ok(status) => status, | |
| }; | |
| if status != PowerSupplyStatus::Charging { | |
| continue; | |
| } | |
| return Ok(PowerSourceType::AC); | |
| } | |
| Ok(PowerSourceType::DC) | |
| } | |
| } | |
| pub trait PowerPreferencesManager { | |
| /// Chooses a [power preference](PowerPreferences) using the parameters and the | |
| /// system's current power source. It then applies it to the system. | |
| /// | |
| /// If more then one activity is active, the following priority list is used | |
| /// to determine which [power preference](PowerPreferences) to apply. If there is no | |
| /// power preference defined for an activity, the next activity in the list will be tried. | |
| /// | |
| /// 1) [Borealis Gaming](PowerPreferencesType::BorealisGaming) | |
| /// 2) [ARCVM Gaming](PowerPreferencesType::ArcvmGaming) | |
| /// 3) [WebRTC](PowerPreferencesType::WebRTC) | |
| /// 4) [Fullscreen Video](PowerPreferencesType::Fullscreen) | |
| /// 5) [VM boot Mode] (PowerPreferencesType::VmBoot) | |
| /// | |
| /// The [default](PowerPreferencesType::Default) preference will be applied when no | |
| /// activity is active. | |
| fn update_power_preferences( | |
| &self, | |
| rtc: common::RTCAudioActive, | |
| fullscreen: common::FullscreenVideo, | |
| game: common::GameMode, | |
| vmboot: common::VmBootMode, | |
| batterysaver: common::BatterySaverMode, | |
| ) -> Result<()>; | |
| fn get_root(&self) -> &Path; | |
| } | |
| #[derive(Clone, Debug)] | |
| /// Applies [power preferences](PowerPreferences) to the system by writing to | |
| /// the system's sysfs nodes. | |
| /// | |
| /// This struct is using generics for the [ConfigProvider](ConfigProvider) and | |
| /// [PowerSourceProvider] to make unit testing easier. | |
| pub struct DirectoryPowerPreferencesManager<P: PowerSourceProvider> { | |
| root: PathBuf, | |
| config_provider: ConfigProvider, | |
| power_source_provider: P, | |
| } | |
| impl<P: PowerSourceProvider> DirectoryPowerPreferencesManager<P> { | |
| #[cfg(test)] | |
| pub fn new(root: &Path, config_provider: ConfigProvider, power_source_provider: P) -> Self { | |
| let root = root.to_path_buf(); | |
| Self { | |
| root: root.to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| } | |
| } | |
| // The global ondemand parameters are in /sys/devices/system/cpu/cpufreq/ondemand/. | |
| fn set_global_ondemand_governor_value(&self, attr: &str, value: u32) -> Result<()> { | |
| let path = self.root.join(GLOBAL_ONDEMAND_PATH).join(attr); | |
| let current_value_str = read_to_string(&path) | |
| .with_context(|| format!("Error reading ondemand parameter from {}", path.display()))?; | |
| let current_value = current_value_str.trim_end_matches('\n').parse::<u32>()?; | |
| // Check current value before writing to avoid permission error when the new value and | |
| // current value are the same but resourced didn't own the parameter file. | |
| if current_value != value { | |
| std::fs::write(&path, value.to_string()).with_context(|| { | |
| format!("Error writing {} {} to {}", attr, value, path.display()) | |
| })?; | |
| info!("Updating ondemand {} to {}", attr, value); | |
| } | |
| Ok(()) | |
| } | |
| // The per-policy ondemand parameters are in /sys/devices/system/cpu/cpufreq/policy*/ondemand/. | |
| fn set_per_policy_ondemand_governor_value(&self, attr: &str, value: u32) -> Result<()> { | |
| const ONDEMAND_PATTERN: &str = "sys/devices/system/cpu/cpufreq/policy*/ondemand"; | |
| let pattern = self | |
| .root | |
| .join(ONDEMAND_PATTERN) | |
| .join(attr) | |
| .to_str() | |
| .context("Cannot convert ondemand path to string")? | |
| .to_owned(); | |
| write_to_cpu_policy_patterns(&pattern, &value.to_string()) | |
| } | |
| fn set_scaling_governor(&self, new_governor: &str) -> Result<()> { | |
| const GOVERNOR_PATTERN: &str = "sys/devices/system/cpu/cpufreq/policy*/scaling_governor"; | |
| let pattern = self | |
| .root | |
| .join(GOVERNOR_PATTERN) | |
| .to_str() | |
| .context("Cannot convert scaling_governor path to string")? | |
| .to_owned(); | |
| write_to_cpu_policy_patterns(&pattern, new_governor) | |
| } | |
| fn apply_governor_preferences(&self, governor: Governor) -> Result<()> { | |
| self.set_scaling_governor(governor.name())?; | |
| if let Governor::Ondemand { | |
| powersave_bias, | |
| sampling_rate, | |
| } = governor | |
| { | |
| let global_path = self.root.join(GLOBAL_ONDEMAND_PATH); | |
| // There are 2 use cases now: | |
| // 1. on guybrush, the scaling_governor is always ondemand, the ondemand directory is | |
| // chowned to resourced so resourced can set the ondemand parameters. | |
| // 2. on herobrine, resourced only changes the scaling_governor, so the permission of | |
| // the new governor's sysfs nodes doesn't matter. | |
| // TODO: support changing both the scaling_governor and the governor's parameters. | |
| // The ondemand tunable could be global (system-wide) or per-policy, depending on the | |
| // scaling driver in use [1]. The global ondemand tunable is in | |
| // /sys/devices/system/cpu/cpufreq/ondemand. The per-policy ondemand tunable is in | |
| // /sys/devices/system/cpu/cpufreq/policy*/ondemand. | |
| // | |
| // [1]: https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html | |
| if global_path.exists() { | |
| self.set_global_ondemand_governor_value("powersave_bias", powersave_bias)?; | |
| if let Some(sampling_rate) = sampling_rate { | |
| self.set_global_ondemand_governor_value("sampling_rate", sampling_rate)?; | |
| } | |
| } else { | |
| self.set_per_policy_ondemand_governor_value("powersave_bias", powersave_bias)?; | |
| if let Some(sampling_rate) = sampling_rate { | |
| self.set_per_policy_ondemand_governor_value("sampling_rate", sampling_rate)?; | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn apply_power_preferences(&self, preferences: PowerPreferences) -> Result<()> { | |
| arch::apply_platform_power_preferences(self.get_root(), &preferences)?; | |
| if let Some(governor) = preferences.governor { | |
| self.apply_governor_preferences(governor)? | |
| } | |
| Ok(()) | |
| } | |
| fn apply_cpu_hotplug(&self, preferences: PowerPreferences) -> Result<()> { | |
| match preferences.cpu_offline { | |
| Some(CpuOfflinePreference::Smt { min_active_threads }) => hotplug_cpus( | |
| self.get_root(), | |
| HotplugCpuAction::OfflineSMT { min_active_threads }, | |
| )?, | |
| Some(CpuOfflinePreference::Half { min_active_threads }) => hotplug_cpus( | |
| self.get_root(), | |
| HotplugCpuAction::OfflineHalf { min_active_threads }, | |
| )?, | |
| Some(CpuOfflinePreference::SmallCore { min_active_threads }) => hotplug_cpus( | |
| self.get_root(), | |
| HotplugCpuAction::OfflineSmallCore { min_active_threads }, | |
| )?, | |
| None => { | |
| hotplug_cpus(self.get_root(), HotplugCpuAction::OnlineAll)?; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn apply_cpufreq_boost(&self, preferences: PowerPreferences) -> Result<()> { | |
| let boost_value = if preferences.cpufreq_disable_boost { | |
| // Disable boost by writing '0'. | |
| "0" | |
| } else { | |
| // Enable boost by writing '1'. | |
| "1" | |
| }; | |
| const CPUFREQ_BOOST_PATH: &str = "sys/devices/system/cpu/cpufreq/boost"; | |
| let cpufreq_boost_path = self.root.join(CPUFREQ_BOOST_PATH); | |
| if cpufreq_boost_path.exists() { | |
| let boost = std::fs::read_to_string(&cpufreq_boost_path)?; | |
| if boost.trim() != boost_value { | |
| std::fs::write(&cpufreq_boost_path, boost_value).with_context(|| { | |
| format!( | |
| "Error writing {} to {}", | |
| boost_value, | |
| cpufreq_boost_path.display() | |
| ) | |
| })?; | |
| info!("Updating cpufreq boost {}", boost_value); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| } | |
| impl<P: PowerSourceProvider> PowerPreferencesManager for DirectoryPowerPreferencesManager<P> { | |
| fn update_power_preferences( | |
| &self, | |
| rtc: RTCAudioActive, | |
| fullscreen: FullscreenVideo, | |
| game: GameMode, | |
| vmboot: VmBootMode, | |
| batterysaver: BatterySaverMode, | |
| ) -> Result<()> { | |
| let mut preferences: Option<PowerPreferences> = None; | |
| let power_source = self.power_source_provider.get_power_source()?; | |
| info!("Power source {:?}", power_source); | |
| if batterysaver == BatterySaverMode::Active { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::BatterySaver)?; | |
| } else if game == GameMode::Borealis { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::BorealisGaming)?; | |
| } else if game == GameMode::Arc { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::ArcvmGaming)?; | |
| } | |
| if preferences.is_none() && rtc == RTCAudioActive::Active { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::WebRTC)?; | |
| } | |
| if preferences.is_none() && fullscreen == FullscreenVideo::Active { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::Fullscreen)?; | |
| } | |
| if preferences.is_none() && vmboot == VmBootMode::Active { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::VmBoot)?; | |
| } | |
| if preferences.is_none() { | |
| preferences = self | |
| .config_provider | |
| .read_power_preferences(power_source, PowerPreferencesType::Default)?; | |
| } | |
| // For non-dynamic EPP, it may need default EPP | |
| let mut need_default_epp = true; | |
| if let Some(preferences) = preferences { | |
| self.apply_cpu_hotplug(preferences)?; | |
| self.apply_power_preferences(preferences)?; | |
| self.apply_cpufreq_boost(preferences)?; | |
| if preferences.epp.is_some() { | |
| need_default_epp = false; | |
| } | |
| } | |
| arch::apply_platform_power_settings( | |
| self.get_root(), | |
| power_source, | |
| rtc, | |
| fullscreen, | |
| batterysaver, | |
| need_default_epp, /* For x86 non-dynamic EPP */ | |
| )?; | |
| Ok(()) | |
| } | |
| fn get_root(&self) -> &Path { | |
| self.root.as_path() | |
| } | |
| } | |
| pub fn new_directory_power_preferences_manager( | |
| root: &Path, | |
| config_provider: ConfigProvider, | |
| ) -> DirectoryPowerPreferencesManager<DirectoryPowerSourceProvider> { | |
| let root = root.to_path_buf(); | |
| DirectoryPowerPreferencesManager { | |
| root: root.clone(), | |
| config_provider, | |
| power_source_provider: DirectoryPowerSourceProvider::new(root), | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use std::fs; | |
| use std::path::Path; | |
| use tempfile::tempdir; | |
| use super::*; | |
| use crate::test_utils::*; | |
| #[test] | |
| fn test_parse_power_supply_status() { | |
| assert_eq!( | |
| PowerSupplyStatus::from_str("Unknown\n").unwrap(), | |
| PowerSupplyStatus::Unknown | |
| ); | |
| assert_eq!( | |
| PowerSupplyStatus::from_str("Charging\n").unwrap(), | |
| PowerSupplyStatus::Charging | |
| ); | |
| assert_eq!( | |
| PowerSupplyStatus::from_str("Discharging\n").unwrap(), | |
| PowerSupplyStatus::Discharging | |
| ); | |
| assert_eq!( | |
| PowerSupplyStatus::from_str("Not charging\n").unwrap(), | |
| PowerSupplyStatus::NotCharging | |
| ); | |
| assert_eq!( | |
| PowerSupplyStatus::from_str("Full\n").unwrap(), | |
| PowerSupplyStatus::Full | |
| ); | |
| assert!(PowerSupplyStatus::from_str("").is_err()); | |
| assert!(PowerSupplyStatus::from_str("abc").is_err()); | |
| } | |
| #[test] | |
| fn test_power_source_provider_empty_root() { | |
| let root = tempdir().unwrap(); | |
| let provider = DirectoryPowerSourceProvider { | |
| root: root.path().to_path_buf(), | |
| }; | |
| let power_source = provider.get_power_source().unwrap(); | |
| assert_eq!(power_source, PowerSourceType::DC); | |
| } | |
| const POWER_SUPPLY_PATH: &str = "sys/class/power_supply"; | |
| #[test] | |
| fn test_power_source_provider_empty_path() { | |
| let root = tempdir().unwrap(); | |
| let path = root.path().join(POWER_SUPPLY_PATH); | |
| fs::create_dir_all(path).unwrap(); | |
| let provider = DirectoryPowerSourceProvider { | |
| root: root.path().to_path_buf(), | |
| }; | |
| let power_source = provider.get_power_source().unwrap(); | |
| assert_eq!(power_source, PowerSourceType::DC); | |
| } | |
| /// Tests that the `DirectoryPowerSourceProvider` can parse the charger sysfs | |
| /// `online` and `status` attributes. | |
| #[test] | |
| fn test_power_source_provider_disconnected_then_connected() { | |
| let root = tempdir().unwrap(); | |
| let path = root.path().join(POWER_SUPPLY_PATH); | |
| fs::create_dir_all(&path).unwrap(); | |
| let provider = DirectoryPowerSourceProvider { | |
| root: root.path().to_path_buf(), | |
| }; | |
| let charger = path.join("charger-1"); | |
| fs::create_dir_all(&charger).unwrap(); | |
| let online = charger.join("online"); | |
| fs::write(&online, b"0").unwrap(); | |
| let power_source = provider.get_power_source().unwrap(); | |
| assert_eq!(power_source, PowerSourceType::DC); | |
| let status = charger.join("status"); | |
| fs::write(&online, b"1").unwrap(); | |
| fs::write(&status, b"Charging\n").unwrap(); | |
| let power_source = provider.get_power_source().unwrap(); | |
| assert_eq!(power_source, PowerSourceType::AC); | |
| fs::write(&online, b"1").unwrap(); | |
| fs::write(&status, b"Not Charging\n").unwrap(); | |
| let power_source = provider.get_power_source().unwrap(); | |
| assert_eq!(power_source, PowerSourceType::DC); | |
| } | |
| fn write_global_powersave_bias(root: &Path, value: u32) -> Result<()> { | |
| let ondemand_path = root.join("sys/devices/system/cpu/cpufreq/ondemand"); | |
| fs::create_dir_all(&ondemand_path)?; | |
| std::fs::write( | |
| ondemand_path.join("powersave_bias"), | |
| value.to_string() + "\n", | |
| )?; | |
| Ok(()) | |
| } | |
| fn read_global_powersave_bias(root: &Path) -> Result<String> { | |
| let powersave_bias_path = root | |
| .join("sys/devices/system/cpu/cpufreq/ondemand") | |
| .join("powersave_bias"); | |
| let mut powersave_bias = std::fs::read_to_string(powersave_bias_path)?; | |
| if powersave_bias.ends_with('\n') { | |
| powersave_bias.pop(); | |
| } | |
| Ok(powersave_bias) | |
| } | |
| fn write_global_sampling_rate(root: &Path, value: u32) -> Result<()> { | |
| let ondemand_path = root.join("sys/devices/system/cpu/cpufreq/ondemand"); | |
| fs::create_dir_all(&ondemand_path)?; | |
| std::fs::write(ondemand_path.join("sampling_rate"), value.to_string())?; | |
| Ok(()) | |
| } | |
| fn read_global_sampling_rate(root: &Path) -> Result<String> { | |
| let sampling_rate_path = root | |
| .join("sys/devices/system/cpu/cpufreq/ondemand") | |
| .join("sampling_rate"); | |
| let mut sampling_rate = std::fs::read_to_string(sampling_rate_path)?; | |
| if sampling_rate.ends_with('\n') { | |
| sampling_rate.pop(); | |
| } | |
| Ok(sampling_rate) | |
| } | |
| fn read_global_cpufreq_boost(root: &Path) -> Result<String> { | |
| let cpufreq_boost_path = root.join("sys/devices/system/cpu/cpufreq/boost"); | |
| let mut boost = std::fs::read_to_string(cpufreq_boost_path)?; | |
| boost = boost.trim().to_string(); | |
| Ok(boost) | |
| } | |
| fn write_global_cpufreq_boost(root: &Path, value: u32) -> Result<()> { | |
| let cpufreq_boost_path = root.join("sys/devices/system/cpu/cpufreq"); | |
| fs::create_dir_all(&cpufreq_boost_path)?; | |
| std::fs::write(cpufreq_boost_path.join("boost"), value.to_string())?; | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_wrong_governor() { | |
| let root = tempdir().unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let fake_config = FakeConfig::new(); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| // We shouldn't have written anything. | |
| let powersave_bias = read_global_powersave_bias(root.path()); | |
| assert!(powersave_bias.is_err()); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_none() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let fake_config = FakeConfig::new(); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "0"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "2000"); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_default_ac() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| write_global_cpufreq_boost(root.path(), 1).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::Default, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: Some(16000), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "16000"); | |
| let cpufreq_boost = read_global_cpufreq_boost(root.path()).unwrap(); | |
| assert_eq!(cpufreq_boost, "1"); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_default_dc() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| write_global_cpufreq_boost(root.path(), 1).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::DC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::DC, | |
| PowerPreferencesType::Default, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: None, | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: true, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "2000"); | |
| let cpufreq_boost = read_global_cpufreq_boost(root.path()).unwrap(); | |
| assert_eq!(cpufreq_boost, "0"); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_default_rtc_active() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::Default, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: Some(4000), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Active, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "4000"); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_rtc_active() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::WebRTC, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: Some(16000), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Active, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "16000"); | |
| } | |
| /// Tests default battery saver mode | |
| #[test] | |
| fn test_apply_hotplug_cpus() { | |
| struct Test<'a> { | |
| cpus: &'a str, | |
| big_little: bool, | |
| cluster1_state: [&'a str; 2], | |
| cluster2_state: [&'a str; 2], | |
| cluster1_freq: [u32; 2], | |
| cluster2_freq: [u32; 2], | |
| preferences: PowerPreferences, | |
| smt_offlined: bool, | |
| smt_orig_state: &'a str, | |
| cluster1_expected_state: [&'a str; 2], | |
| cluster2_expected_state: [&'a str; 2], | |
| smt_expected_state: &'a str, | |
| } | |
| let tests = [ | |
| // Test offline small core | |
| Test { | |
| cpus: "0-3", | |
| big_little: true, | |
| cluster1_state: ["1"; 2], | |
| cluster2_state: ["1"; 2], | |
| cluster1_freq: [2400000; 2], | |
| cluster2_freq: [1800000; 2], | |
| preferences: PowerPreferences { | |
| governor: Some(Governor::Conservative), | |
| epp: None, | |
| cpu_offline: Some(CpuOfflinePreference::SmallCore { | |
| min_active_threads: 2, | |
| }), | |
| cpufreq_disable_boost: false, | |
| }, | |
| smt_offlined: false, | |
| smt_orig_state: "on", | |
| cluster1_expected_state: ["1"; 2], | |
| cluster2_expected_state: ["0"; 2], | |
| smt_expected_state: "", | |
| }, | |
| // Test offline SMT | |
| Test { | |
| cpus: "0-3", | |
| big_little: false, | |
| cluster1_state: ["1"; 2], | |
| cluster2_state: ["1"; 2], | |
| cluster1_freq: [2400000; 2], | |
| cluster2_freq: [2400000; 2], | |
| preferences: PowerPreferences { | |
| governor: None, | |
| epp: None, | |
| cpu_offline: Some(CpuOfflinePreference::Smt { | |
| min_active_threads: 2, | |
| }), | |
| cpufreq_disable_boost: false, | |
| }, | |
| smt_offlined: true, | |
| smt_orig_state: "on", | |
| cluster1_expected_state: ["1"; 2], | |
| cluster2_expected_state: ["0"; 2], | |
| smt_expected_state: "off", | |
| }, | |
| // Test offline half | |
| Test { | |
| cpus: "0-3", | |
| big_little: false, | |
| cluster1_state: ["1"; 2], | |
| cluster2_state: ["1"; 2], | |
| cluster1_freq: [2400000; 2], | |
| cluster2_freq: [2400000; 2], | |
| preferences: PowerPreferences { | |
| governor: Some(Governor::Conservative), | |
| epp: None, | |
| cpu_offline: Some(CpuOfflinePreference::Half { | |
| min_active_threads: 2, | |
| }), | |
| cpufreq_disable_boost: false, | |
| }, | |
| smt_offlined: false, | |
| smt_orig_state: "on", | |
| cluster1_expected_state: ["1"; 2], | |
| cluster2_expected_state: ["0"; 2], | |
| smt_expected_state: "off", | |
| }, | |
| // Test online all | |
| Test { | |
| cpus: "0-3", | |
| big_little: false, | |
| cluster1_state: ["1"; 2], | |
| cluster2_state: ["0"; 2], | |
| cluster1_freq: [2400000; 2], | |
| cluster2_freq: [2400000; 2], | |
| preferences: PowerPreferences { | |
| governor: None, | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| smt_offlined: false, | |
| smt_orig_state: "on", | |
| cluster1_expected_state: ["1"; 2], | |
| cluster2_expected_state: ["1"; 2], | |
| smt_expected_state: "off", | |
| }, | |
| ]; | |
| for test in tests { | |
| //Setup | |
| let temp_dir = tempdir().unwrap(); | |
| let root = temp_dir.path(); | |
| let fake_config = FakeConfig::new(); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.to_path_buf(), | |
| config_provider, | |
| power_source_provider: FakePowerSourceProvider { | |
| power_source: PowerSourceType::DC, | |
| }, | |
| }; | |
| test_write_cpuset_root_cpus(root, test.cpus); | |
| test_write_smt_control(root, test.smt_orig_state); | |
| // Setup core cpus list for two physical cores and two virtual cores | |
| test_write_core_cpus_list(root, 0, "0,2"); | |
| test_write_core_cpus_list(root, 1, "1,3"); | |
| test_write_core_cpus_list(root, 2, "0,2"); | |
| test_write_core_cpus_list(root, 3, "1,3"); | |
| if test.big_little { | |
| test_write_ui_use_flags(root, "big_little"); | |
| } | |
| for (i, freq) in test.cluster1_freq.iter().enumerate() { | |
| test_write_online_cpu(root, i.try_into().unwrap(), test.cluster1_state[i]); | |
| test_write_cpu_max_freq(root, i.try_into().unwrap(), *freq); | |
| } | |
| for (i, freq) in test.cluster2_freq.iter().enumerate() { | |
| test_write_online_cpu( | |
| root, | |
| (test.cluster1_freq.len() + i).try_into().unwrap(), | |
| test.cluster2_state[i], | |
| ); | |
| test_write_cpu_max_freq( | |
| root, | |
| (test.cluster1_freq.len() + i).try_into().unwrap(), | |
| *freq, | |
| ); | |
| } | |
| // Call function to test | |
| manager.apply_cpu_hotplug(test.preferences).unwrap(); | |
| // Check result. | |
| if test.smt_offlined { | |
| // The mock sysfs cannot offline the SMT CPUs, here to check the smt control state | |
| test_check_smt_control(root, test.smt_expected_state); | |
| continue; | |
| } | |
| for (i, state) in test.cluster1_expected_state.iter().enumerate() { | |
| test_check_online_cpu(root, i.try_into().unwrap(), state); | |
| } | |
| for (i, state) in test.cluster2_expected_state.iter().enumerate() { | |
| test_check_online_cpu( | |
| root, | |
| (test.cluster1_expected_state.len() + i).try_into().unwrap(), | |
| state, | |
| ); | |
| } | |
| } | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_fullscreen_active() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::Fullscreen, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: Some(16000), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Active, | |
| common::GameMode::Off, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "16000"); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_borealis_gaming_active() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::BorealisGaming, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: Some(16000), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Borealis, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "16000"); | |
| } | |
| #[test] | |
| fn test_power_update_power_preferences_arcvm_gaming_active() { | |
| let root = tempdir().unwrap(); | |
| write_global_powersave_bias(root.path(), 0).unwrap(); | |
| write_global_sampling_rate(root.path(), 2000).unwrap(); | |
| test_write_cpuset_root_cpus(root.path(), "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::ArcvmGaming, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: 200, | |
| sampling_rate: Some(16000), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.path().to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Arc, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| let powersave_bias = read_global_powersave_bias(root.path()).unwrap(); | |
| assert_eq!(powersave_bias, "200"); | |
| let sampling_rate = read_global_sampling_rate(root.path()).unwrap(); | |
| assert_eq!(sampling_rate, "16000"); | |
| } | |
| #[test] | |
| fn test_per_policy_ondemand_governor() { | |
| let temp_dir = tempdir().unwrap(); | |
| let root = temp_dir.path(); | |
| const INIT_POWERSAVE_BIAS: u32 = 0; | |
| const INIT_SAMPLING_RATE: u32 = 2000; | |
| const CONFIG_POWERSAVE_BIAS: u32 = 200; | |
| const CONFIG_SAMPLING_RATE: u32 = 16000; | |
| let ondemand = Governor::Ondemand { | |
| powersave_bias: INIT_POWERSAVE_BIAS, | |
| sampling_rate: Some(INIT_SAMPLING_RATE), | |
| }; | |
| let policy0 = PolicyConfigs { | |
| policy_path: TEST_CPUFREQ_POLICIES[0], | |
| governor: &ondemand, | |
| affected_cpus: "0", | |
| }; | |
| let policy1 = PolicyConfigs { | |
| policy_path: TEST_CPUFREQ_POLICIES[1], | |
| governor: &ondemand, | |
| affected_cpus: "1", | |
| }; | |
| let policies = vec![policy0, policy1]; | |
| write_per_policy_scaling_governor(root, policies); | |
| write_per_policy_powersave_bias(root, INIT_POWERSAVE_BIAS); | |
| write_per_policy_sampling_rate(root, INIT_SAMPLING_RATE); | |
| test_write_cpuset_root_cpus(root, "0-3"); | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::ArcvmGaming, | |
| &PowerPreferences { | |
| governor: Some(Governor::Ondemand { | |
| powersave_bias: CONFIG_POWERSAVE_BIAS, | |
| sampling_rate: Some(CONFIG_SAMPLING_RATE), | |
| }), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Arc, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| check_per_policy_scaling_governor(root, vec![ondemand, ondemand]); | |
| check_per_policy_powersave_bias(root, CONFIG_POWERSAVE_BIAS); | |
| check_per_policy_sampling_rate(root, CONFIG_SAMPLING_RATE); | |
| } | |
| #[test] | |
| fn test_scaling_governors() { | |
| let temp_dir = tempdir().unwrap(); | |
| let root = temp_dir.path(); | |
| test_write_cpuset_root_cpus(root, "0-3"); | |
| const INIT_POWERSAVE_BIAS: u32 = 0; | |
| const INIT_SAMPLING_RATE: u32 = 2000; | |
| let ondemand = Governor::Ondemand { | |
| powersave_bias: INIT_POWERSAVE_BIAS, | |
| sampling_rate: Some(INIT_SAMPLING_RATE), | |
| }; | |
| let policy0 = PolicyConfigs { | |
| policy_path: TEST_CPUFREQ_POLICIES[0], | |
| governor: &ondemand, | |
| affected_cpus: AFFECTED_CPU0, | |
| }; | |
| let policy1 = PolicyConfigs { | |
| policy_path: TEST_CPUFREQ_POLICIES[1], | |
| governor: &ondemand, | |
| affected_cpus: AFFECTED_CPU1, | |
| }; | |
| let policies = vec![policy0, policy1]; | |
| write_per_policy_scaling_governor(root, policies); | |
| let governors = [ | |
| Governor::Conservative, | |
| Governor::Performance, | |
| Governor::Powersave, | |
| Governor::Schedutil, | |
| Governor::Userspace, | |
| ]; | |
| for governor in governors { | |
| let power_source_provider = FakePowerSourceProvider { | |
| power_source: PowerSourceType::AC, | |
| }; | |
| let mut fake_config = FakeConfig::new(); | |
| fake_config.write_power_preference( | |
| PowerSourceType::AC, | |
| PowerPreferencesType::ArcvmGaming, | |
| &PowerPreferences { | |
| governor: Some(governor), | |
| epp: None, | |
| cpu_offline: None, | |
| cpufreq_disable_boost: false, | |
| }, | |
| ); | |
| let config_provider = fake_config.provider(); | |
| let manager = DirectoryPowerPreferencesManager { | |
| root: root.to_path_buf(), | |
| config_provider, | |
| power_source_provider, | |
| }; | |
| manager | |
| .update_power_preferences( | |
| common::RTCAudioActive::Inactive, | |
| common::FullscreenVideo::Inactive, | |
| common::GameMode::Arc, | |
| common::VmBootMode::Inactive, | |
| common::BatterySaverMode::Inactive, | |
| ) | |
| .unwrap(); | |
| check_per_policy_scaling_governor(root, vec![governor, governor]); | |
| } | |
| } | |
| } |