blob: f2a4278ba1df12cee80004e5120af41005b5e060 [file] [log] [blame] [edit]
// 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));
}
}
}
}
}