blob: 28808d707e15de7176787d4a4540aa0d147344a2 [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::read_to_string;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{Context, Result};
use glob::glob;
use libchromeos::sys::{error, info};
use crate::common;
use crate::common::{FullscreenVideo, GameMode, RTCAudioActive, VmBootMode};
use crate::config;
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<config::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 {
pub root: PathBuf,
}
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<config::PowerSourceType> {
let path = self.root.join(POWER_SUPPLY_PATH);
if !path.exists() {
return Ok(config::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 = common::read_file_to_u64(&online_path)
.with_context(|| format!("Error reading online from {}", online_path.display()))?
as u32;
if online != 1 {
continue;
}
let status_path = charger_path.path().join(POWER_SUPPLY_STATUS);
if !status_path.exists() {
continue;
}
let status_string = read_to_string(&status_path)
.with_context(|| format!("Error reading status from {}", status_path.display()))?;
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(config::PowerSourceType::AC);
}
Ok(config::PowerSourceType::DC)
}
}
pub trait PowerPreferencesManager {
/// Chooses a [power preference](config::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](config::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](config::PowerPreferencesType::BorealisGaming)
/// 2) [ARCVM Gaming](config::PowerPreferencesType::ArcvmGaming)
/// 3) [WebRTC](config::PowerPreferencesType::WebRTC)
/// 4) [Fullscreen Video](config::PowerPreferencesType::Fullscreen)
/// 5) [VM boot Mode] (config::PowerPreferencesType::VmBoot)
///
/// The [default](config::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,
) -> Result<()>;
}
fn write_to_path_patterns(pattern: &str, new_value: &str) -> Result<()> {
for entry in glob(pattern)? {
let path = entry?;
let current_value = read_to_string(&path)
.with_context(|| format!("Error reading attribute from {}", path.display()))?;
if current_value.trim_end_matches('\n') != new_value {
std::fs::write(&path, new_value).with_context(|| {
format!(
"Failed to set attribute to {}, new value: {}",
path.display(),
new_value
)
})?;
}
}
Ok(())
}
#[derive(Clone, Debug)]
/// Applies [power preferences](config::PowerPreferences) to the system by writing to
/// the system's sysfs nodes.
///
/// This struct is using generics for the [ConfigProvider](config::ConfigProvider) and
/// [PowerSourceProvider] to make unit testing easier.
pub struct DirectoryPowerPreferencesManager<C: config::ConfigProvider, P: PowerSourceProvider> {
pub root: PathBuf,
pub config_provider: C,
pub power_source_provider: P,
}
impl<C: config::ConfigProvider, P: PowerSourceProvider> DirectoryPowerPreferencesManager<C, P> {
// 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_path_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_path_patterns(&pattern, new_governor)
}
fn apply_governor_preferences(&self, governor: config::Governor) -> Result<()> {
self.set_scaling_governor(governor.to_name())?;
if let config::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: config::PowerPreferences) -> Result<()> {
if let Some(governor) = preferences.governor {
self.apply_governor_preferences(governor)?
}
Ok(())
}
}
// Set EPP value in sysfs for Intel devices with X86_FEATURE_HWP_EPP support.
// On !X86_FEATURE_HWP_EPP Intel devices, an integer write to the sysfs node
// will fail with -EINVAL.
fn set_epp(root_path: &str, value: &str) -> Result<()> {
let pattern = root_path.to_owned()
+ "/sys/devices/system/cpu/cpufreq/policy*/energy_performance_preference";
for entry in glob(&pattern)? {
std::fs::write(entry?, value)
.with_context(|| format!("Failed to set EPP sysfs value to {}!", value))?;
}
Ok(())
}
impl<C: config::ConfigProvider, P: PowerSourceProvider> PowerPreferencesManager
for DirectoryPowerPreferencesManager<C, P>
{
fn update_power_preferences(
&self,
rtc: RTCAudioActive,
fullscreen: FullscreenVideo,
game: GameMode,
vmboot: VmBootMode,
) -> Result<()> {
let mut preferences: Option<config::PowerPreferences> = None;
let power_source = self.power_source_provider.get_power_source()?;
info!("Power source {:?}", power_source);
if game == GameMode::Borealis {
preferences = self.config_provider.read_power_preferences(
power_source,
config::PowerPreferencesType::BorealisGaming,
)?;
} else if game == GameMode::Arc {
preferences = self
.config_provider
.read_power_preferences(power_source, config::PowerPreferencesType::ArcvmGaming)?;
}
if preferences.is_none() && rtc == RTCAudioActive::Active {
preferences = self
.config_provider
.read_power_preferences(power_source, config::PowerPreferencesType::WebRTC)?;
}
if preferences.is_none() && fullscreen == FullscreenVideo::Active {
preferences = self
.config_provider
.read_power_preferences(power_source, config::PowerPreferencesType::Fullscreen)?;
}
if preferences.is_none() && vmboot == VmBootMode::Active {
preferences = self
.config_provider
.read_power_preferences(power_source, config::PowerPreferencesType::VmBoot)?;
}
if preferences.is_none() {
preferences = self
.config_provider
.read_power_preferences(power_source, config::PowerPreferencesType::Default)?;
}
if let Some(preferences) = preferences {
self.apply_power_preferences(preferences)?
}
if let Some(root) = self.root.to_str() {
if rtc == RTCAudioActive::Active || fullscreen == FullscreenVideo::Active {
if let Err(err) = set_epp(root, "balance_power") {
error!("Failed to set energy performance preference: {:#}", err);
}
} else if rtc != RTCAudioActive::Active && fullscreen != FullscreenVideo::Active {
set_epp(root, "balance_performance")?; // Default EPP
}
} else {
info!("Converting root path failed: {}", self.root.display());
}
Ok(())
}
}
pub fn new_directory_power_preferences_manager(
root: &Path,
) -> DirectoryPowerPreferencesManager<config::DirectoryConfigProvider, DirectoryPowerSourceProvider>
{
DirectoryPowerPreferencesManager {
root: root.to_path_buf(),
config_provider: config::DirectoryConfigProvider {
root: root.to_path_buf(),
},
power_source_provider: DirectoryPowerSourceProvider {
root: root.to_path_buf(),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::bail;
use std::fs;
use std::path::Path;
use tempfile::{tempdir, TempDir};
#[test]
fn test_parse_power_supply_status() -> anyhow::Result<()> {
assert_eq!(
PowerSupplyStatus::from_str("Unknown\n")?,
PowerSupplyStatus::Unknown
);
assert_eq!(
PowerSupplyStatus::from_str("Charging\n")?,
PowerSupplyStatus::Charging
);
assert_eq!(
PowerSupplyStatus::from_str("Discharging\n")?,
PowerSupplyStatus::Discharging
);
assert_eq!(
PowerSupplyStatus::from_str("Not charging\n")?,
PowerSupplyStatus::NotCharging
);
assert_eq!(
PowerSupplyStatus::from_str("Full\n")?,
PowerSupplyStatus::Full
);
assert!(PowerSupplyStatus::from_str("").is_err());
assert!(PowerSupplyStatus::from_str("abc").is_err());
Ok(())
}
#[test]
fn test_set_epp() {
let dir = TempDir::new().unwrap();
// Create the fake sysfs paths in temp directory
let mut tpb0 = dir.path().to_owned();
tpb0.push("sys/devices/system/cpu/cpufreq/policy0/");
// let dirpath_str0 = tpb0.clone().into_os_string().into_string().unwrap();
fs::create_dir_all(&tpb0).unwrap();
let mut tpb1 = dir.path().to_owned();
tpb1.push("sys/devices/system/cpu/cpufreq/policy1/");
fs::create_dir_all(&tpb1).unwrap();
tpb0.push("energy_performance_preference");
tpb1.push("energy_performance_preference");
// Create energy_performance_preference files.
fs::write(&tpb0, "balance_performance").unwrap();
fs::write(&tpb1, "balance_performance").unwrap();
// Set the EPP
set_epp(dir.path().to_str().unwrap(), "179").unwrap();
// Verify that files were written
assert_eq!(fs::read_to_string(&tpb0).unwrap(), "179".to_string());
assert_eq!(fs::read_to_string(&tpb1).unwrap(), "179".to_string());
}
#[test]
fn test_power_source_provider_empty_root() -> Result<()> {
let root = tempdir()?;
let provider = DirectoryPowerSourceProvider {
root: root.path().to_path_buf(),
};
let power_source = provider.get_power_source()?;
assert_eq!(power_source, config::PowerSourceType::DC);
Ok(())
}
const POWER_SUPPLY_PATH: &str = "sys/class/power_supply";
#[test]
fn test_power_source_provider_empty_path() -> Result<()> {
let root = tempdir()?;
let path = root.path().join(POWER_SUPPLY_PATH);
fs::create_dir_all(path)?;
let provider = DirectoryPowerSourceProvider {
root: root.path().to_path_buf(),
};
let power_source = provider.get_power_source()?;
assert_eq!(power_source, config::PowerSourceType::DC);
Ok(())
}
/// Tests that the `DirectoryPowerSourceProvider` can parse the charger sysfs
/// `online` and `status` attributes.
#[test]
fn test_power_source_provider_disconnected_then_connected() -> Result<()> {
let root = tempdir()?;
let path = root.path().join(POWER_SUPPLY_PATH);
fs::create_dir_all(&path)?;
let provider = DirectoryPowerSourceProvider {
root: root.path().to_path_buf(),
};
let charger = path.join("charger-1");
fs::create_dir_all(&charger)?;
let online = charger.join("online");
fs::write(&online, b"0")?;
let power_source = provider.get_power_source()?;
assert_eq!(power_source, config::PowerSourceType::DC);
let status = charger.join("status");
fs::write(&online, b"1")?;
fs::write(&status, b"Charging\n")?;
let power_source = provider.get_power_source()?;
assert_eq!(power_source, config::PowerSourceType::AC);
fs::write(&online, b"1")?;
fs::write(&status, b"Not Charging\n")?;
let power_source = provider.get_power_source()?;
assert_eq!(power_source, config::PowerSourceType::DC);
Ok(())
}
struct FakeConfigProvider {
default_power_preferences:
fn(config::PowerSourceType) -> Result<Option<config::PowerPreferences>>,
web_rtc_power_preferences:
fn(config::PowerSourceType) -> Result<Option<config::PowerPreferences>>,
fullscreen_power_preferences:
fn(config::PowerSourceType) -> Result<Option<config::PowerPreferences>>,
vm_boot_power_preferences:
fn(config::PowerSourceType) -> Result<Option<config::PowerPreferences>>,
borealis_gaming_power_preferences:
fn(config::PowerSourceType) -> Result<Option<config::PowerPreferences>>,
arcvm_gaming_power_preferences:
fn(config::PowerSourceType) -> Result<Option<config::PowerPreferences>>,
}
impl Default for FakeConfigProvider {
fn default() -> FakeConfigProvider {
FakeConfigProvider {
// We bail on default to ensure the tests correctly setup a default power
// preference.
default_power_preferences: |_| bail!("Default not Implemented"),
web_rtc_power_preferences: |_| bail!("WebRTC not Implemented"),
fullscreen_power_preferences: |_| bail!("Fullscreen not Implemented"),
vm_boot_power_preferences: |_| bail!("VM boot mode not Implemented"),
borealis_gaming_power_preferences: |_| bail!("Borealis gaming not Implemented"),
arcvm_gaming_power_preferences: |_| bail!("ARCVM gaming not Implemented"),
}
}
}
impl config::ConfigProvider for FakeConfigProvider {
fn read_power_preferences(
&self,
power_source_type: config::PowerSourceType,
power_preference_type: config::PowerPreferencesType,
) -> Result<Option<config::PowerPreferences>> {
match power_preference_type {
config::PowerPreferencesType::Default => {
(self.default_power_preferences)(power_source_type)
}
config::PowerPreferencesType::WebRTC => {
(self.web_rtc_power_preferences)(power_source_type)
}
config::PowerPreferencesType::Fullscreen => {
(self.fullscreen_power_preferences)(power_source_type)
}
config::PowerPreferencesType::VmBoot => {
(self.vm_boot_power_preferences)(power_source_type)
}
config::PowerPreferencesType::BorealisGaming => {
(self.borealis_gaming_power_preferences)(power_source_type)
}
config::PowerPreferencesType::ArcvmGaming => {
(self.arcvm_gaming_power_preferences)(power_source_type)
}
}
}
}
struct FakePowerSourceProvider {
power_source: config::PowerSourceType,
}
impl PowerSourceProvider for FakePowerSourceProvider {
fn get_power_source(&self) -> Result<config::PowerSourceType> {
Ok(self.power_source)
}
}
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)
}
// In the following per policy access functions, there are 2 cpufreq policies: policy0 and
// policy1.
const TEST_CPUFREQ_POLICIES: &[&str] = &[
"sys/devices/system/cpu/cpufreq/policy0",
"sys/devices/system/cpu/cpufreq/policy1",
];
const SCALING_GOVERNOR_FILENAME: &str = "scaling_governor";
const ONDEMAND_DIRECTORY: &str = "ondemand";
const POWERSAVE_BIAS_FILENAME: &str = "powersave_bias";
const SAMPLING_RATE_FILENAME: &str = "sampling_rate";
// Instead of returning an error, crash/assert immediately in a test utility function makes it
// easier to debug an unittest.
fn write_per_policy_scaling_governor(root: &Path, governor: config::Governor) {
for policy in TEST_CPUFREQ_POLICIES {
let policy_path = root.join(policy);
fs::create_dir_all(&policy_path).unwrap();
std::fs::write(
policy_path.join(SCALING_GOVERNOR_FILENAME),
governor.to_name().to_string() + "\n",
)
.unwrap();
}
}
fn check_per_policy_scaling_governor(root: &Path, expected: config::Governor) {
for policy in TEST_CPUFREQ_POLICIES {
let governor_path = root.join(policy).join(SCALING_GOVERNOR_FILENAME);
let scaling_governor = std::fs::read_to_string(governor_path).unwrap();
assert_eq!(scaling_governor.trim_end_matches('\n'), expected.to_name());
}
}
fn write_per_policy_powersave_bias(root: &Path, value: u32) {
for policy in TEST_CPUFREQ_POLICIES {
let ondemand_path = root.join(policy).join(ONDEMAND_DIRECTORY);
println!("ondemand_path: {}", ondemand_path.display());
fs::create_dir_all(&ondemand_path).unwrap();
std::fs::write(
ondemand_path.join(POWERSAVE_BIAS_FILENAME),
value.to_string() + "\n",
)
.unwrap();
}
}
fn check_per_policy_powersave_bias(root: &Path, expected: u32) {
for policy in TEST_CPUFREQ_POLICIES {
let powersave_bias_path = root
.join(policy)
.join(ONDEMAND_DIRECTORY)
.join(POWERSAVE_BIAS_FILENAME);
let powersave_bias = std::fs::read_to_string(powersave_bias_path).unwrap();
assert_eq!(powersave_bias.trim_end_matches('\n'), expected.to_string());
}
}
fn write_per_policy_sampling_rate(root: &Path, value: u32) {
for policy in TEST_CPUFREQ_POLICIES {
let ondemand_path = root.join(policy).join(ONDEMAND_DIRECTORY);
fs::create_dir_all(&ondemand_path).unwrap();
std::fs::write(
ondemand_path.join(SAMPLING_RATE_FILENAME),
value.to_string(),
)
.unwrap();
}
}
fn check_per_policy_sampling_rate(root: &Path, expected: u32) {
for policy in TEST_CPUFREQ_POLICIES {
let sampling_rate_path = root
.join(policy)
.join(ONDEMAND_DIRECTORY)
.join(SAMPLING_RATE_FILENAME);
let sampling_rate = std::fs::read_to_string(sampling_rate_path).unwrap();
assert_eq!(sampling_rate, expected.to_string());
}
}
fn write_epp(root: &Path, value: &str) -> Result<()> {
let policy_path = root.join("sys/devices/system/cpu/cpufreq/policy0");
fs::create_dir_all(&policy_path)?;
std::fs::write(policy_path.join("energy_performance_preference"), value)?;
Ok(())
}
fn read_epp(root: &Path) -> Result<String> {
let epp_path = root
.join("sys/devices/system/cpu/cpufreq/policy0/")
.join("energy_performance_preference");
let epp = std::fs::read_to_string(epp_path)?;
Ok(epp)
}
#[test]
fn test_power_update_power_preferences_wrong_governor() -> Result<()> {
let root = tempdir()?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
default_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: None,
}),
}))
},
..Default::default()
};
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,
)?;
// We shouldn't have written anything.
let powersave_bias = read_global_powersave_bias(root.path());
assert!(powersave_bias.is_err());
Ok(())
}
#[test]
fn test_power_update_power_preferences_none() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
default_power_preferences: |_| Ok(None),
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "0");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "2000");
Ok(())
}
#[test]
fn test_power_update_power_preferences_default_ac() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
default_power_preferences: |power_source| {
assert_eq!(power_source, config::PowerSourceType::AC);
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: Some(16000),
}),
}))
},
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "16000");
Ok(())
}
#[test]
fn test_power_update_power_preferences_default_dc() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::DC,
};
let config_provider = FakeConfigProvider {
default_power_preferences: |power_source| {
assert_eq!(power_source, config::PowerSourceType::DC);
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: None,
}),
}))
},
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "2000");
Ok(())
}
#[test]
fn test_power_update_power_preferences_default_rtc_active() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
default_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: Some(4000),
}),
}))
},
web_rtc_power_preferences: |_| Ok(None),
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "4000");
Ok(())
}
#[test]
fn test_power_update_power_preferences_rtc_active() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
web_rtc_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: Some(16000),
}),
}))
},
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "16000");
Ok(())
}
#[test]
/// Tests the various EPP permutations
fn test_power_update_power_preferences_epp() -> Result<()> {
let root = tempdir()?;
write_epp(root.path(), "balance_performance")?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
// Let's assume we have no config
let config_provider = FakeConfigProvider {
default_power_preferences: |_| Ok(None),
web_rtc_power_preferences: |_| Ok(None),
fullscreen_power_preferences: |_| Ok(None),
..Default::default()
};
let manager = DirectoryPowerPreferencesManager {
root: root.path().to_path_buf(),
config_provider,
power_source_provider,
};
let tests = [
(
RTCAudioActive::Active,
FullscreenVideo::Inactive,
"balance_power",
),
(
RTCAudioActive::Inactive,
FullscreenVideo::Active,
"balance_power",
),
(
RTCAudioActive::Active,
FullscreenVideo::Active,
"balance_power",
),
(
RTCAudioActive::Inactive,
FullscreenVideo::Inactive,
"balance_performance",
),
];
for test in tests {
manager.update_power_preferences(
test.0,
test.1,
common::GameMode::Off,
common::VmBootMode::Inactive,
)?;
let epp = read_epp(root.path())?;
assert_eq!(epp, test.2);
}
Ok(())
}
#[test]
fn test_power_update_power_preferences_fullscreen_active() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
fullscreen_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: Some(16000),
}),
}))
},
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "16000");
Ok(())
}
#[test]
fn test_power_update_power_preferences_borealis_gaming_active() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
borealis_gaming_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: Some(16000),
}),
}))
},
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "16000");
Ok(())
}
#[test]
fn test_power_update_power_preferences_arcvm_gaming_active() -> Result<()> {
let root = tempdir()?;
write_global_powersave_bias(root.path(), 0)?;
write_global_sampling_rate(root.path(), 2000)?;
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
arcvm_gaming_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: 200,
sampling_rate: Some(16000),
}),
}))
},
..Default::default()
};
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,
)?;
let powersave_bias = read_global_powersave_bias(root.path())?;
assert_eq!(powersave_bias, "200");
let sampling_rate = read_global_sampling_rate(root.path())?;
assert_eq!(sampling_rate, "16000");
Ok(())
}
#[test]
fn test_per_policy_ondemand_governor() -> Result<()> {
let temp_dir = tempdir()?;
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 = config::Governor::Ondemand {
powersave_bias: INIT_POWERSAVE_BIAS,
sampling_rate: Some(INIT_SAMPLING_RATE),
};
write_per_policy_scaling_governor(root, ondemand);
write_per_policy_powersave_bias(root, INIT_POWERSAVE_BIAS);
write_per_policy_sampling_rate(root, INIT_SAMPLING_RATE);
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
let config_provider = FakeConfigProvider {
arcvm_gaming_power_preferences: |_| {
Ok(Some(config::PowerPreferences {
governor: Some(config::Governor::Ondemand {
powersave_bias: CONFIG_POWERSAVE_BIAS,
sampling_rate: Some(CONFIG_SAMPLING_RATE),
}),
}))
},
..Default::default()
};
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,
)?;
check_per_policy_scaling_governor(root, ondemand);
check_per_policy_powersave_bias(root, CONFIG_POWERSAVE_BIAS);
check_per_policy_sampling_rate(root, CONFIG_SAMPLING_RATE);
Ok(())
}
struct ArcvmGamingConfigProvider {
arcvm_gaming_power_preferences: config::PowerPreferences,
}
impl config::ConfigProvider for ArcvmGamingConfigProvider {
fn read_power_preferences(
&self,
_power_source_type: config::PowerSourceType,
power_preference_type: config::PowerPreferencesType,
) -> Result<Option<config::PowerPreferences>> {
match power_preference_type {
config::PowerPreferencesType::ArcvmGaming => {
Ok(Some(self.arcvm_gaming_power_preferences))
}
_ => bail!("Unexpected power preference type"),
}
}
}
#[test]
fn test_scaling_governors() -> Result<()> {
let temp_dir = tempdir()?;
let root = temp_dir.path();
const INIT_POWERSAVE_BIAS: u32 = 0;
const INIT_SAMPLING_RATE: u32 = 2000;
let ondemand = config::Governor::Ondemand {
powersave_bias: INIT_POWERSAVE_BIAS,
sampling_rate: Some(INIT_SAMPLING_RATE),
};
write_per_policy_scaling_governor(root, ondemand);
let governors = [
config::Governor::Conservative,
config::Governor::Performance,
config::Governor::Powersave,
config::Governor::Schedutil,
config::Governor::Userspace,
];
for governor in governors {
let power_source_provider = FakePowerSourceProvider {
power_source: config::PowerSourceType::AC,
};
// The governor is a variable that we cannot use FakeConfigProvider.
// Got the following error when using FakeConfigProvider:
// closures can only be coerced to `fn` types if they do not capture any variables
let config_provider = ArcvmGamingConfigProvider {
arcvm_gaming_power_preferences: config::PowerPreferences {
governor: Some(governor),
},
};
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,
)?;
check_per_policy_scaling_governor(root, governor);
}
Ok(())
}
}