| // 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; |
| use std::fs::DirEntry; |
| use std::path::Path; |
| use std::path::PathBuf; |
| |
| use anyhow::bail; |
| use anyhow::Context; |
| use anyhow::Result; |
| use schedqos::compute_uclamp_min; |
| use schedqos::CpusetCgroup; |
| |
| use crate::common::read_from_file; |
| |
| const CHROMEOS_CONFIG_PATH: &str = "run/chromeos-config/v1"; |
| const RESOURCE_CONFIG_DIR: &str = "resource"; |
| const SCHEDQOS_CONFIG_DIR: &str = "schedqos"; |
| const DEFUALT_MIN_ACTIVE_THREADS: u32 = 2; |
| |
| pub trait FromDir { |
| // The return type is Result<Option<Self>> so parse_config_from_path() don't have to |
| // parse the result. |
| fn from_dir(dir: DirEntry) -> Result<Option<Self>> |
| where |
| Self: Sized; |
| } |
| |
| /* TODO: Can we use `rust-protobuf` to generate all the structs? */ |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum Governor { |
| Conservative, |
| Ondemand { |
| powersave_bias: u32, |
| sampling_rate: Option<u32>, |
| }, |
| Performance, |
| Powersave, |
| Schedutil, |
| Userspace, |
| } |
| |
| impl Governor { |
| pub fn name(&self) -> &'static str { |
| match self { |
| Governor::Conservative => "conservative", |
| Governor::Ondemand { .. } => "ondemand", |
| Governor::Performance => "performance", |
| Governor::Powersave => "powersave", |
| Governor::Schedutil => "schedutil", |
| Governor::Userspace => "userspace", |
| } |
| } |
| } |
| |
| impl FromDir for Governor { |
| fn from_dir(dir: DirEntry) -> Result<Option<Governor>> { |
| match dir.file_name().to_str() { |
| Some("conservative") => Ok(Some(Governor::Conservative)), |
| Some("ondemand") => Ok(Some(parse_ondemand_governor(&dir.path())?)), |
| Some("performance") => Ok(Some(Governor::Performance)), |
| Some("powersave") => Ok(Some(Governor::Powersave)), |
| Some("schedutil") => Ok(Some(Governor::Schedutil)), |
| Some("userspace") => Ok(Some(Governor::Userspace)), |
| _ => bail!("Unknown governor {:?}!", dir.file_name()), |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum EnergyPerformancePreference { |
| Default, |
| Performance, |
| BalancePerformance, |
| BalancePower, |
| Power, |
| } |
| |
| impl EnergyPerformancePreference { |
| #[cfg(test)] |
| fn dir_name(&self) -> &'static str { |
| match self { |
| EnergyPerformancePreference::Default => "default", |
| EnergyPerformancePreference::Performance => "performance", |
| EnergyPerformancePreference::BalancePerformance => "balance-performance", |
| EnergyPerformancePreference::BalancePower => "balance-power", |
| EnergyPerformancePreference::Power => "power", |
| } |
| } |
| } |
| |
| impl FromDir for EnergyPerformancePreference { |
| fn from_dir(dir: DirEntry) -> Result<Option<EnergyPerformancePreference>> { |
| match dir.file_name().to_str() { |
| Some("default") => Ok(Some(EnergyPerformancePreference::Default)), |
| Some("performance") => Ok(Some(EnergyPerformancePreference::Performance)), |
| Some("balance-performance") => { |
| Ok(Some(EnergyPerformancePreference::BalancePerformance)) |
| } |
| Some("balance-power") => Ok(Some(EnergyPerformancePreference::BalancePower)), |
| Some("power") => Ok(Some(EnergyPerformancePreference::Power)), |
| _ => bail!("Unknown epp {:?}!", dir.file_name()), |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum CpuOfflinePreference { |
| SmallCore { min_active_threads: u32 }, |
| Smt { min_active_threads: u32 }, |
| Half { min_active_threads: u32 }, |
| } |
| |
| // CpuOfflinePreference::dir_name() is only used in tests. |
| #[cfg(test)] |
| impl CpuOfflinePreference { |
| fn dir_name(&self) -> &'static str { |
| match self { |
| CpuOfflinePreference::SmallCore { .. } => "small-core", |
| CpuOfflinePreference::Smt { .. } => "smt", |
| CpuOfflinePreference::Half { .. } => "half", |
| } |
| } |
| } |
| |
| impl FromDir for CpuOfflinePreference { |
| fn from_dir(dir: DirEntry) -> Result<Option<CpuOfflinePreference>> { |
| let min_active_threads_path = dir.path().join("min-active-threads"); |
| |
| // The min-active-threads config is optional and it will be set to |
| // DEFUALT_MIN_ACTIVE_THREADS if not specified |
| let min_active_threads: u32 = if min_active_threads_path.exists() { |
| read_from_file(&min_active_threads_path).with_context(|| { |
| format!( |
| "Error reading min-active-threads from {}", |
| min_active_threads_path.display() |
| ) |
| })? |
| } else { |
| DEFUALT_MIN_ACTIVE_THREADS |
| }; |
| |
| match dir.file_name().to_str() { |
| Some("small-core") => Ok(Some(CpuOfflinePreference::SmallCore { min_active_threads })), |
| Some("smt") => Ok(Some(CpuOfflinePreference::Smt { min_active_threads })), |
| Some("half") => Ok(Some(CpuOfflinePreference::Half { min_active_threads })), |
| _ => bail!("Unknown cpu-offline {:?}!", dir.file_name()), |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| pub struct PowerPreferences { |
| pub governor: Option<Governor>, |
| pub epp: Option<EnergyPerformancePreference>, |
| pub cpu_offline: Option<CpuOfflinePreference>, |
| pub cpufreq_disable_boost: bool, |
| } |
| |
| #[derive(Copy, Clone)] |
| pub enum PowerPreferencesType { |
| Default, |
| WebRTC, |
| Fullscreen, |
| VmBoot, |
| BorealisGaming, |
| ArcvmGaming, |
| BatterySaver, |
| } |
| |
| impl PowerPreferencesType { |
| fn dir_name(&self) -> &'static str { |
| match self { |
| PowerPreferencesType::Default => "default-power-preferences", |
| PowerPreferencesType::WebRTC => "web-rtc-power-preferences", |
| PowerPreferencesType::Fullscreen => "fullscreen-power-preferences", |
| PowerPreferencesType::VmBoot => "vm-boot-power-preferences", |
| PowerPreferencesType::BorealisGaming => "borealis-gaming-power-preferences", |
| PowerPreferencesType::ArcvmGaming => "arcvm-gaming-power-preferences", |
| PowerPreferencesType::BatterySaver => "battery-saver-power-preferences", |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| pub enum PowerSourceType { |
| AC, |
| DC, |
| } |
| |
| impl PowerSourceType { |
| fn dir_name(&self) -> &'static str { |
| match self { |
| PowerSourceType::AC => "ac", |
| PowerSourceType::DC => "dc", |
| } |
| } |
| } |
| |
| fn parse_ondemand_governor(path: &Path) -> Result<Governor> { |
| let powersave_bias_path = path.join("powersave-bias"); |
| |
| let powersave_bias: u32 = read_from_file(&powersave_bias_path).with_context(|| { |
| format!( |
| "Error reading powersave-bias from {}", |
| powersave_bias_path.display() |
| ) |
| })?; |
| |
| let sampling_rate_path = path.join("sampling-rate-ms"); |
| |
| // The sampling-rate config is optional in the config |
| let sampling_rate = if sampling_rate_path.exists() { |
| let sampling_rate_ms: u32 = read_from_file(&sampling_rate_path).with_context(|| { |
| format!( |
| "Error reading sampling-rate-ms from {}", |
| sampling_rate_path.display() |
| ) |
| })?; |
| |
| // We treat the default value of 0 as unset. We do this because the kernel treats |
| // a sampling rate of 0 as invalid. |
| if sampling_rate_ms == 0 { |
| None |
| } else { |
| // We convert from ms to uS to match what the kernel expects |
| Some(sampling_rate_ms * 1000) |
| } |
| } else { |
| None |
| }; |
| |
| Ok(Governor::Ondemand { |
| powersave_bias, |
| sampling_rate, |
| }) |
| } |
| |
| // Returns Ok(None) when there is no sub directory in path. |
| // Returns error when there are multiple sub directories in path or when the |
| // sub directory name is not a supported governor. |
| fn parse_config_from_path<T: FromDir>(path: &Path) -> Result<Option<T>> { |
| let mut dirs = path |
| .read_dir() |
| .with_context(|| format!("Failed to read governors from {}", path.display()))?; |
| |
| let first_dir = match dirs.next() { |
| None => return Ok(None), |
| Some(dir) => dir?, |
| }; |
| |
| if dirs.next().is_some() { |
| bail!("Multiple governors detected in {}", path.display()); |
| } |
| |
| T::from_dir(first_dir) |
| } |
| |
| /// The schedqos configuration to use for threads of a given priority. |
| /// |
| /// Each field corresponds to fields in [schedqos::ThreadStateConfig]. |
| #[derive(Default, Debug, PartialEq)] |
| pub struct SchedqosThreadConfig { |
| pub rt_priority: Option<Option<u32>>, |
| pub nice: Option<i32>, |
| pub uclamp_min: Option<u32>, |
| pub cpuset_cgroup: Option<CpusetCgroup>, |
| pub latency_sensitive: Option<bool>, |
| } |
| |
| impl SchedqosThreadConfig { |
| fn merge_into(&self, config: &mut schedqos::ThreadStateConfig) { |
| if let Some(rt_priority) = self.rt_priority { |
| config.rt_priority = rt_priority |
| } |
| if let Some(nice) = self.nice { |
| config.nice = nice |
| } |
| if let Some(uclamp_min) = self.uclamp_min { |
| config.uclamp_min = compute_uclamp_min(uclamp_min); |
| } |
| if let Some(cpuset_cgroup) = self.cpuset_cgroup { |
| config.cpuset_cgroup = cpuset_cgroup |
| } |
| if let Some(latency_sensitive) = self.latency_sensitive { |
| config.latency_sensitive = latency_sensitive |
| } |
| } |
| |
| fn parse(path: &Path) -> Result<Self> { |
| let mut config = Self::default(); |
| |
| let rt_priority_path = path.join("rt-priority"); |
| if rt_priority_path.exists() { |
| let rt_priority: i32 = |
| read_from_file(&rt_priority_path).context("failed to read rt-priority")?; |
| let rt_priority = if rt_priority < 0 { |
| None |
| } else { |
| Some(rt_priority as u32) |
| }; |
| config.rt_priority = Some(rt_priority); |
| } |
| |
| let nice_path = path.join("nice"); |
| if nice_path.exists() { |
| let nice = read_from_file(&nice_path).context("failed to read nice")?; |
| config.nice = Some(nice); |
| } |
| |
| let uclamp_min_path = path.join("uclamp-min"); |
| if uclamp_min_path.exists() { |
| let uclamp_min = |
| read_from_file(&uclamp_min_path).context("failed to read uclamp-min")?; |
| if !(0..=100).contains(&uclamp_min) { |
| bail!("uclamp-min {} is out of range", uclamp_min); |
| } |
| config.uclamp_min = Some(uclamp_min); |
| } |
| |
| let cpuset_cgroup_path = path.join("cpuset-cgroup"); |
| if cpuset_cgroup_path.exists() { |
| let cpuset_cgroup = |
| fs::read_to_string(cpuset_cgroup_path).context("failed to read cpuset-cgroup")?; |
| let cpuset_cgroup = match cpuset_cgroup.as_str() { |
| "all" => CpusetCgroup::All, |
| "efficient" => CpusetCgroup::Efficient, |
| other => bail!("invalid cpuset-cgroup: {}", other), |
| }; |
| config.cpuset_cgroup = Some(cpuset_cgroup); |
| } |
| |
| let latency_sensitive_path = path.join("latency-sensitive"); |
| if latency_sensitive_path.exists() { |
| let latency_sensitive = fs::read_to_string(latency_sensitive_path) |
| .context("failed to read latency-sensitive")?; |
| let latency_sensitive = match latency_sensitive.as_str() { |
| "true" => true, |
| "false" => false, |
| other => bail!("invalid latency-sensitive: {}", other), |
| }; |
| config.latency_sensitive = Some(latency_sensitive); |
| } |
| |
| Ok(config) |
| } |
| } |
| |
| /// Config for the schedqos feature. |
| #[derive(Default, Debug, PartialEq)] |
| pub struct SchedqosConfig { |
| /// The cpu share of normal cpu cgroup. |
| pub normal_cpu_share: Option<u16>, |
| /// The cpu share of background cpu cgroup. |
| pub background_cpu_share: Option<u16>, |
| /// The thread config for URGENT_BURSTY state. |
| pub thread_urgent_bursty: Option<SchedqosThreadConfig>, |
| /// The thread config for URGENT state. |
| pub thread_urgent: Option<SchedqosThreadConfig>, |
| /// The thread config for BALANCED state. |
| pub thread_balanced: Option<SchedqosThreadConfig>, |
| /// The thread config for ECO state. |
| pub thread_eco: Option<SchedqosThreadConfig>, |
| /// The thread config for UTILITY state. |
| pub thread_utility: Option<SchedqosThreadConfig>, |
| /// The thread config for BACKGROUND state. |
| pub thread_background: Option<SchedqosThreadConfig>, |
| /// The thread config for URGENT_BURSTY_SERVER state. |
| pub thread_urgent_bursty_server: Option<SchedqosThreadConfig>, |
| } |
| |
| impl SchedqosConfig { |
| pub fn merge_thread_configs_into( |
| &self, |
| thread_configs: &mut [schedqos::ThreadStateConfig; schedqos::NUM_THREAD_STATES], |
| ) { |
| let configs = [ |
| ( |
| &self.thread_urgent_bursty, |
| schedqos::ThreadState::UrgentBursty, |
| ), |
| (&self.thread_urgent, schedqos::ThreadState::Urgent), |
| (&self.thread_balanced, schedqos::ThreadState::Balanced), |
| (&self.thread_eco, schedqos::ThreadState::Eco), |
| (&self.thread_utility, schedqos::ThreadState::Utility), |
| (&self.thread_background, schedqos::ThreadState::Background), |
| ( |
| &self.thread_urgent_bursty_server, |
| schedqos::ThreadState::UrgentBurstyServer, |
| ), |
| ]; |
| |
| for (config, config_type) in configs { |
| if let Some(config) = config { |
| config.merge_into(&mut thread_configs[config_type as usize]); |
| } |
| } |
| } |
| |
| fn parse(path: &Path) -> Result<Self> { |
| let mut config = Self::default(); |
| |
| let normal_cpu_share_path = path.join("normal-cpu-share"); |
| if normal_cpu_share_path.exists() { |
| let normal_cpu_share = read_from_file(&normal_cpu_share_path) |
| .context("failed to read normal-cpu-share")?; |
| config.normal_cpu_share = Some(normal_cpu_share); |
| } |
| |
| let background_cpu_share_path = path.join("background-cpu-share"); |
| if background_cpu_share_path.exists() { |
| let background_cpu_share = read_from_file(&background_cpu_share_path) |
| .context("failed to read background-cpu-share")?; |
| config.background_cpu_share = Some(background_cpu_share); |
| } |
| |
| let configs = [ |
| ("thread-urgent-bursty", &mut config.thread_urgent_bursty), |
| ("thread-urgent", &mut config.thread_urgent), |
| ("thread-balanced", &mut config.thread_balanced), |
| ("thread-eco", &mut config.thread_eco), |
| ("thread-utility", &mut config.thread_utility), |
| ("thread-background", &mut config.thread_background), |
| ( |
| "thread-urgent-bursty-server", |
| &mut config.thread_urgent_bursty_server, |
| ), |
| ]; |
| for (dir_name, config) in configs { |
| let config_path = path.join(dir_name); |
| if config_path.exists() { |
| *config = Some(SchedqosThreadConfig::parse(&config_path).with_context(|| { |
| format!("failed to parse schedqos thread config for {}", dir_name) |
| })?); |
| } |
| } |
| |
| Ok(config) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct ConfigProvider { |
| config_path: PathBuf, |
| } |
| |
| impl ConfigProvider { |
| pub fn from_root(root: &Path) -> Self { |
| Self { |
| config_path: root.join(CHROMEOS_CONFIG_PATH), |
| } |
| } |
| |
| /// Expects to find a directory tree as follows: |
| /// * /run/chromeos-config/v1/resource/ |
| /// * {ac,dc} |
| /// * web-rtc-power-preferences/governor/ |
| /// * ondemand/ |
| /// * powersave-bias |
| /// * fullscreen-power-preferences/governor/ |
| /// * schedutil/ |
| /// * vm-boot-power-preferences/governor/.. |
| /// * borealis-gaming-power-preferences/governor/.. |
| /// * arcvm-gaming-power-preferences/governor/.. |
| /// * battery-saver-power-preferences/governor/.. |
| /// * default-power-preferences/governor/.. |
| pub fn read_power_preferences( |
| &self, |
| power_source_type: PowerSourceType, |
| power_preference_type: PowerPreferencesType, |
| ) -> Result<Option<PowerPreferences>> { |
| let path = self |
| .config_path |
| .join(RESOURCE_CONFIG_DIR) |
| .join(power_source_type.dir_name()) |
| .join(power_preference_type.dir_name()); |
| |
| if !path.exists() { |
| return Ok(None); |
| } |
| |
| let mut preferences: PowerPreferences = PowerPreferences { |
| governor: None, |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }; |
| |
| let governor_path = path.join("governor"); |
| if governor_path.exists() { |
| preferences.governor = parse_config_from_path::<Governor>(&governor_path)?; |
| } |
| |
| let epp_path = path.join("epp"); |
| if epp_path.exists() { |
| preferences.epp = parse_config_from_path::<EnergyPerformancePreference>(&epp_path)?; |
| } |
| |
| let cpu_offline_path = path.join("cpu-offline"); |
| if cpu_offline_path.exists() { |
| preferences.cpu_offline = |
| parse_config_from_path::<CpuOfflinePreference>(&cpu_offline_path)?; |
| } |
| |
| let cpufreq_disable_boost_path = path.join("cpufreq-disable-boost"); |
| if cpufreq_disable_boost_path.exists() { |
| let cpufreq_disable_boost = fs::read_to_string(cpufreq_disable_boost_path) |
| .context("failed to read cpufreq-disable-boost")?; |
| let cpufreq_disable_boost = match cpufreq_disable_boost.as_str() { |
| "true" => true, |
| "false" => false, |
| other => bail!("invalid cpufreq-disable-boost: {}", other), |
| }; |
| preferences.cpufreq_disable_boost = cpufreq_disable_boost; |
| } |
| |
| Ok(Some(preferences)) |
| } |
| |
| pub fn read_sched_qos_config(&self, name: &str) -> Result<Option<SchedqosConfig>> { |
| let path = self.config_path.join(SCHEDQOS_CONFIG_DIR).join(name); |
| |
| if !path.exists() { |
| return Ok(None); |
| } |
| |
| Ok(Some(SchedqosConfig::parse(&path)?)) |
| } |
| } |
| |
| #[cfg(test)] |
| pub struct FakeConfig { |
| root: tempfile::TempDir, |
| config_path: PathBuf, |
| } |
| |
| #[cfg(test)] |
| impl FakeConfig { |
| pub fn new() -> Self { |
| let root = tempfile::tempdir().unwrap(); |
| let config_path = root.path().join(CHROMEOS_CONFIG_PATH); |
| Self { root, config_path } |
| } |
| |
| pub fn mkdir(&mut self, path: &Path) { |
| let path = self.config_path.join(path); |
| fs::create_dir_all(path).unwrap(); |
| } |
| |
| pub fn write(&mut self, path: &Path, value: &[u8]) { |
| let path = self.config_path.join(path); |
| let parent = path.parent().unwrap(); |
| fs::create_dir_all(parent).unwrap(); |
| fs::write(path, value).unwrap(); |
| } |
| |
| pub fn write_power_preference( |
| &mut self, |
| power_source_type: PowerSourceType, |
| power_preference_type: PowerPreferencesType, |
| preference: &PowerPreferences, |
| ) { |
| let preference_path = self |
| .config_path |
| .join(RESOURCE_CONFIG_DIR) |
| .join(power_source_type.dir_name()) |
| .join(power_preference_type.dir_name()); |
| |
| fs::create_dir_all(&preference_path).unwrap(); |
| |
| if let Some(governor) = &preference.governor { |
| let governor_path = preference_path.join("governor").join(governor.name()); |
| fs::create_dir_all(&governor_path).unwrap(); |
| if let Governor::Ondemand { |
| powersave_bias, |
| sampling_rate, |
| } = governor |
| { |
| fs::write( |
| governor_path.join("powersave-bias"), |
| powersave_bias.to_string().as_bytes(), |
| ) |
| .unwrap(); |
| if let Some(sampling_rate) = sampling_rate { |
| // sampling_rate is in uS. |
| fs::write( |
| governor_path.join("sampling-rate-ms"), |
| (sampling_rate / 1000).to_string().as_bytes(), |
| ) |
| .unwrap(); |
| } |
| } |
| } |
| |
| if let Some(epp) = preference.epp { |
| let epp_path = preference_path.join("epp").join(epp.dir_name()); |
| fs::create_dir_all(epp_path).unwrap(); |
| } |
| |
| if let Some(cpu_offline) = preference.cpu_offline { |
| let cpu_offline_path = preference_path |
| .join("cpu-offline") |
| .join(cpu_offline.dir_name()); |
| fs::create_dir_all(&cpu_offline_path).unwrap(); |
| |
| let min_active_threads = match cpu_offline { |
| CpuOfflinePreference::SmallCore { min_active_threads } => min_active_threads, |
| CpuOfflinePreference::Smt { min_active_threads } => min_active_threads, |
| CpuOfflinePreference::Half { min_active_threads } => min_active_threads, |
| }; |
| fs::write( |
| cpu_offline_path.join("min-active-threads"), |
| min_active_threads.to_string().as_bytes(), |
| ) |
| .unwrap(); |
| } |
| |
| if preference.cpufreq_disable_boost { |
| let cpufreq_boost_path = preference_path.join("cpufreq-disable-boost"); |
| fs::write(cpufreq_boost_path, "true").unwrap(); |
| } |
| } |
| |
| pub fn provider(&self) -> ConfigProvider { |
| ConfigProvider::from_root(self.root.path()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_config_provider_empty_root() { |
| let fake = FakeConfig::new(); |
| let provider = fake.provider(); |
| |
| let preference = provider |
| .read_power_preferences(PowerSourceType::AC, PowerPreferencesType::Default) |
| .unwrap(); |
| |
| assert!(preference.is_none()); |
| |
| let preference = provider |
| .read_power_preferences(PowerSourceType::DC, PowerPreferencesType::Default) |
| .unwrap(); |
| |
| assert!(preference.is_none()); |
| } |
| |
| #[test] |
| fn test_config_provider_empty_dir() { |
| let mut fake = FakeConfig::new(); |
| fake.mkdir(Path::new(RESOURCE_CONFIG_DIR)); |
| let provider = fake.provider(); |
| |
| let preference = provider |
| .read_power_preferences(PowerSourceType::AC, PowerPreferencesType::Default) |
| .unwrap(); |
| |
| assert!(preference.is_none()); |
| |
| let preference = provider |
| .read_power_preferences(PowerSourceType::DC, PowerPreferencesType::Default) |
| .unwrap(); |
| |
| assert!(preference.is_none()); |
| } |
| |
| #[test] |
| fn test_config_provider_epp() { |
| let power_source = (PowerSourceType::AC, "ac"); |
| let preference = (PowerPreferencesType::WebRTC, "web-rtc-power-preferences"); |
| let mut fake = FakeConfig::new(); |
| let path = Path::new(RESOURCE_CONFIG_DIR) |
| .join(power_source.1) |
| .join(preference.1) |
| .join("epp") |
| .join("balance-performance"); |
| fake.mkdir(&path); |
| let provider = fake.provider(); |
| |
| let actual = provider |
| .read_power_preferences(power_source.0, preference.0) |
| .unwrap(); |
| |
| let expected = PowerPreferences { |
| governor: None, |
| epp: Some(EnergyPerformancePreference::BalancePerformance), |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }; |
| |
| assert_eq!(actual, Some(expected)); |
| } |
| |
| #[test] |
| fn test_config_provider_ondemand_all_types() { |
| let power_source_params = [(PowerSourceType::AC, "ac"), (PowerSourceType::DC, "dc")]; |
| |
| let preference_params = [ |
| (PowerPreferencesType::Default, "default-power-preferences"), |
| (PowerPreferencesType::WebRTC, "web-rtc-power-preferences"), |
| ( |
| PowerPreferencesType::Fullscreen, |
| "fullscreen-power-preferences", |
| ), |
| (PowerPreferencesType::VmBoot, "vm-boot-power-preferences"), |
| ( |
| PowerPreferencesType::BorealisGaming, |
| "borealis-gaming-power-preferences", |
| ), |
| ( |
| PowerPreferencesType::ArcvmGaming, |
| "arcvm-gaming-power-preferences", |
| ), |
| ( |
| PowerPreferencesType::BatterySaver, |
| "battery-saver-power-preferences", |
| ), |
| ]; |
| |
| for (power_source, power_source_path) in power_source_params { |
| for (preference, preference_path) in preference_params { |
| let mut fake = FakeConfig::new(); |
| let ondemand_path = Path::new(RESOURCE_CONFIG_DIR) |
| .join(power_source_path) |
| .join(preference_path) |
| .join("governor") |
| .join("ondemand"); |
| fake.mkdir(&ondemand_path); |
| |
| let powersave_bias_path = ondemand_path.join("powersave-bias"); |
| fake.write(&powersave_bias_path, b"340"); |
| let provider = fake.provider(); |
| |
| let actual = provider |
| .read_power_preferences(power_source, preference) |
| .unwrap(); |
| |
| let expected = PowerPreferences { |
| governor: Some(Governor::Ondemand { |
| powersave_bias: 340, |
| sampling_rate: None, |
| }), |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }; |
| |
| assert_eq!(actual, Some(expected)); |
| |
| // Now try with a sampling_rate 0 (unset) |
| let powersave_bias_path = ondemand_path.join("sampling-rate-ms"); |
| fake.write(&powersave_bias_path, b"0"); |
| let provider = fake.provider(); |
| |
| let actual = provider |
| .read_power_preferences(power_source, preference) |
| .unwrap(); |
| |
| let expected = PowerPreferences { |
| governor: Some(Governor::Ondemand { |
| powersave_bias: 340, |
| sampling_rate: None, |
| }), |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }; |
| |
| assert_eq!(actual, Some(expected)); |
| |
| // Now try with a sampling_rate 16 |
| fake.write(&powersave_bias_path, b"16"); |
| let provider = fake.provider(); |
| |
| let actual = provider |
| .read_power_preferences(power_source, preference) |
| .unwrap(); |
| |
| let expected = PowerPreferences { |
| governor: Some(Governor::Ondemand { |
| powersave_bias: 340, |
| sampling_rate: Some(16000), |
| }), |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }; |
| |
| assert_eq!(actual, Some(expected)); |
| } |
| } |
| } |
| |
| #[test] |
| fn test_config_provider_empty_root_schedqos() { |
| let fake = FakeConfig::new(); |
| let provider = fake.provider(); |
| |
| let config = provider.read_sched_qos_config("default").unwrap(); |
| |
| assert!(config.is_none()); |
| } |
| |
| #[test] |
| fn test_config_provider_empty_dir_schedqos() { |
| let mut fake = FakeConfig::new(); |
| fake.mkdir(Path::new(SCHEDQOS_CONFIG_DIR)); |
| let provider = fake.provider(); |
| |
| let config = provider.read_sched_qos_config("default").unwrap(); |
| |
| assert!(config.is_none()); |
| } |
| |
| #[test] |
| fn test_config_provider_empty_default_schedqos() { |
| let mut fake = FakeConfig::new(); |
| fake.mkdir(&Path::new(SCHEDQOS_CONFIG_DIR).join("default")); |
| let provider = fake.provider(); |
| |
| let config = provider.read_sched_qos_config("default").unwrap(); |
| |
| assert_eq!(config.unwrap(), SchedqosConfig::default()); |
| } |
| |
| #[test] |
| fn test_config_provider_schedqos() { |
| let mut fake = FakeConfig::new(); |
| let default_config_path = Path::new(SCHEDQOS_CONFIG_DIR).join("default"); |
| fake.write(&default_config_path.join("normal-cpu-share"), b"1000"); |
| fake.write(&default_config_path.join("background-cpu-share"), b"20"); |
| let thread_urgent_bursty_path = default_config_path.join("thread-urgent-bursty"); |
| fake.write(&thread_urgent_bursty_path.join("rt-priority"), b"1"); |
| fake.write(&thread_urgent_bursty_path.join("nice"), b"-10"); |
| fake.write(&thread_urgent_bursty_path.join("uclamp-min"), b"80"); |
| fake.write(&thread_urgent_bursty_path.join("cpuset-cgroup"), b"all"); |
| fake.write( |
| &thread_urgent_bursty_path.join("latency-sensitive"), |
| b"true", |
| ); |
| let thread_urgent_path = default_config_path.join("thread-urgent"); |
| fake.write(&thread_urgent_path.join("rt-priority"), b"10"); |
| fake.write(&thread_urgent_path.join("nice"), b"5"); |
| fake.write(&thread_urgent_path.join("uclamp-min"), b"70"); |
| fake.write(&thread_urgent_path.join("cpuset-cgroup"), b"efficient"); |
| fake.write(&thread_urgent_path.join("latency-sensitive"), b"false"); |
| let thread_balanced_path = default_config_path.join("thread-balanced"); |
| fake.write(&thread_balanced_path.join("rt-priority"), b"-1"); |
| fake.write(&thread_balanced_path.join("nice"), b"0"); |
| fake.write(&thread_balanced_path.join("uclamp-min"), b"0"); |
| let thread_eco_path = default_config_path.join("thread-eco"); |
| fake.write(&thread_eco_path.join("nice"), b"19"); |
| fake.write(&thread_eco_path.join("uclamp-min"), b"100"); |
| let thread_utility_path = default_config_path.join("thread-utility"); |
| fake.mkdir(&thread_utility_path); |
| let thread_background_path = default_config_path.join("thread-background"); |
| fake.mkdir(&thread_background_path); |
| let thread_urgent_bursty_server_path = |
| default_config_path.join("thread-urgent-bursty-server"); |
| fake.mkdir(&thread_urgent_bursty_server_path); |
| |
| let partialconfig_path = Path::new(SCHEDQOS_CONFIG_DIR).join("partial"); |
| fake.write(&partialconfig_path.join("normal-cpu-share"), b"900"); |
| let thread_urgent_bursty_path = partialconfig_path.join("thread-urgent-bursty"); |
| fake.write(&thread_urgent_bursty_path.join("rt-priority"), b"2"); |
| let thread_urgent_path = partialconfig_path.join("thread-urgent"); |
| fake.mkdir(&thread_urgent_path); |
| |
| let provider = fake.provider(); |
| |
| let default_config = provider.read_sched_qos_config("default").unwrap(); |
| assert_eq!( |
| default_config.unwrap(), |
| SchedqosConfig { |
| normal_cpu_share: Some(1000), |
| background_cpu_share: Some(20), |
| thread_urgent_bursty: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(1)), |
| nice: Some(-10), |
| uclamp_min: Some(80), |
| cpuset_cgroup: Some(CpusetCgroup::All), |
| latency_sensitive: Some(true), |
| }), |
| thread_urgent: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(10)), |
| nice: Some(5), |
| uclamp_min: Some(70), |
| cpuset_cgroup: Some(CpusetCgroup::Efficient), |
| latency_sensitive: Some(false), |
| }), |
| thread_balanced: Some(SchedqosThreadConfig { |
| rt_priority: Some(None), |
| nice: Some(0), |
| uclamp_min: Some(0), |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_eco: Some(SchedqosThreadConfig { |
| rt_priority: None, |
| nice: Some(19), |
| uclamp_min: Some(100), |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_utility: Some(SchedqosThreadConfig { |
| rt_priority: None, |
| nice: None, |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_background: Some(SchedqosThreadConfig { |
| rt_priority: None, |
| nice: None, |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_urgent_bursty_server: Some(SchedqosThreadConfig { |
| rt_priority: None, |
| nice: None, |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| } |
| ); |
| |
| let partial_config = provider.read_sched_qos_config("partial").unwrap(); |
| assert_eq!( |
| partial_config.unwrap(), |
| SchedqosConfig { |
| normal_cpu_share: Some(900), |
| background_cpu_share: None, |
| thread_urgent_bursty: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(2)), |
| nice: None, |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_urgent: Some(SchedqosThreadConfig::default()), |
| thread_balanced: None, |
| thread_eco: None, |
| thread_utility: None, |
| thread_background: None, |
| thread_urgent_bursty_server: None, |
| } |
| ); |
| |
| let empty_config = provider.read_sched_qos_config("empty").unwrap(); |
| assert!(empty_config.is_none()); |
| } |
| |
| #[test] |
| fn test_schedqos_config_merge_into() { |
| let mut thread_configs = [ |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(2), |
| nice: -8, |
| uclamp_min: 100, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: None, |
| nice: 0, |
| uclamp_min: 200, |
| cpuset_cgroup: CpusetCgroup::All, |
| latency_sensitive: true, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(3), |
| nice: 2, |
| uclamp_min: 300, |
| cpuset_cgroup: CpusetCgroup::All, |
| latency_sensitive: true, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(3), |
| nice: 2, |
| uclamp_min: 300, |
| cpuset_cgroup: CpusetCgroup::All, |
| latency_sensitive: true, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: None, |
| nice: 3, |
| uclamp_min: 10, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: None, |
| nice: 4, |
| uclamp_min: 10, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: None, |
| nice: 5, |
| uclamp_min: 10, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| ]; |
| SchedqosConfig { |
| normal_cpu_share: None, |
| background_cpu_share: None, |
| thread_urgent_bursty: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(1)), |
| nice: Some(-10), |
| uclamp_min: Some(80), |
| cpuset_cgroup: Some(CpusetCgroup::All), |
| latency_sensitive: Some(true), |
| }), |
| thread_urgent: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(10)), |
| nice: Some(5), |
| uclamp_min: Some(70), |
| cpuset_cgroup: Some(CpusetCgroup::Efficient), |
| latency_sensitive: Some(false), |
| }), |
| thread_balanced: Some(SchedqosThreadConfig { |
| rt_priority: Some(None), |
| nice: Some(0), |
| uclamp_min: Some(0), |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_eco: Some(SchedqosThreadConfig { |
| rt_priority: None, |
| nice: Some(19), |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_utility: Some(SchedqosThreadConfig { |
| rt_priority: None, |
| nice: Some(18), |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_background: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(3)), |
| nice: None, |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| thread_urgent_bursty_server: Some(SchedqosThreadConfig { |
| rt_priority: Some(Some(4)), |
| nice: None, |
| uclamp_min: None, |
| cpuset_cgroup: None, |
| latency_sensitive: None, |
| }), |
| } |
| .merge_thread_configs_into(&mut thread_configs); |
| |
| assert_eq!( |
| thread_configs, |
| [ |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(1), |
| nice: -10, |
| uclamp_min: 819, |
| cpuset_cgroup: CpusetCgroup::All, |
| latency_sensitive: true, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(10), |
| nice: 5, |
| uclamp_min: 717, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: None, |
| nice: 0, |
| uclamp_min: 0, |
| cpuset_cgroup: CpusetCgroup::All, |
| latency_sensitive: true, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(3), |
| nice: 19, |
| uclamp_min: 300, |
| cpuset_cgroup: CpusetCgroup::All, |
| latency_sensitive: true, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: None, |
| nice: 18, |
| uclamp_min: 10, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(3), |
| nice: 4, |
| uclamp_min: 10, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| schedqos::ThreadStateConfig { |
| rt_priority: Some(4), |
| nice: 5, |
| uclamp_min: 10, |
| cpuset_cgroup: CpusetCgroup::Efficient, |
| latency_sensitive: false, |
| }, |
| ] |
| ); |
| |
| let before_thread_configs = thread_configs.clone(); |
| SchedqosConfig::default().merge_thread_configs_into(&mut thread_configs); |
| assert_eq!(thread_configs, before_thread_configs); |
| } |
| |
| #[test] |
| fn test_fake_config_write_power_preference() { |
| for power_source_type in [PowerSourceType::AC, PowerSourceType::DC] { |
| for power_preference_type in [ |
| PowerPreferencesType::Default, |
| PowerPreferencesType::WebRTC, |
| PowerPreferencesType::Fullscreen, |
| PowerPreferencesType::VmBoot, |
| PowerPreferencesType::BorealisGaming, |
| PowerPreferencesType::ArcvmGaming, |
| PowerPreferencesType::BatterySaver, |
| ] { |
| for preference in [ |
| PowerPreferences { |
| governor: Some(Governor::Conservative), |
| epp: Some(EnergyPerformancePreference::Default), |
| cpu_offline: Some(CpuOfflinePreference::SmallCore { |
| min_active_threads: 3, |
| }), |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: Some(Governor::Ondemand { |
| powersave_bias: 4, |
| sampling_rate: Some(5000), |
| }), |
| epp: Some(EnergyPerformancePreference::Performance), |
| cpu_offline: Some(CpuOfflinePreference::Smt { |
| min_active_threads: 6, |
| }), |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: Some(Governor::Ondemand { |
| powersave_bias: 7, |
| sampling_rate: None, |
| }), |
| epp: Some(EnergyPerformancePreference::BalancePerformance), |
| cpu_offline: Some(CpuOfflinePreference::Half { |
| min_active_threads: 8, |
| }), |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: Some(Governor::Performance), |
| epp: Some(EnergyPerformancePreference::BalancePower), |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: Some(Governor::Powersave), |
| epp: Some(EnergyPerformancePreference::Power), |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: Some(Governor::Schedutil), |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: Some(Governor::Userspace), |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }, |
| PowerPreferences { |
| governor: None, |
| epp: None, |
| cpu_offline: None, |
| cpufreq_disable_boost: false, |
| }, |
| ] { |
| let mut fake = FakeConfig::new(); |
| fake.write_power_preference( |
| power_source_type, |
| power_preference_type, |
| &preference, |
| ); |
| let provider = fake.provider(); |
| let actual = provider |
| .read_power_preferences(power_source_type, power_preference_type) |
| .unwrap(); |
| assert_eq!(actual, Some(preference)); |
| } |
| } |
| } |
| } |
| } |