From 6f588196cbde254d7ff8763af7e504c7de85aa7c Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:52:56 -0600 Subject: [PATCH] set governor to "performance" if available (#2438) * set governor to "performance" if available * add linux-cpupower * fix: Boolean blindness, thanks @dr-bones --------- Co-authored-by: J H <2364004+Blu-J@users.noreply.github.com> --- backend/Cargo.lock | 59 ++++++++++++++++- backend/Cargo.toml | 1 + backend/src/init.rs | 20 ++++++ backend/src/util/cpupower.rs | 125 +++++++++++++++++++++++++++++++++++ backend/src/util/mod.rs | 1 + build/lib/depends | 1 + libs/models/src/errors.rs | 2 + 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 backend/src/util/cpupower.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b72396e65..3c82288fa 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -661,6 +661,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2 1.0.66", + "quote 1.0.31", + "unicode-xid 0.2.4", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -679,6 +699,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -1074,7 +1103,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2 1.0.66", "quote 1.0.31", "rustc_version 0.4.0", @@ -4519,6 +4548,33 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "sscanf" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c713ebd15ce561dd4a13ed62bc2a0368e16806fc30dcaf66ecf1256b2a3fdde6" +dependencies = [ + "const_format", + "lazy_static", + "regex", + "sscanf_macro", +] + +[[package]] +name = "sscanf_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84955aa74a157e5834d58a07be11af7f0ab923f0194a0bb2ea6b3db8b5d1611d" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2 1.0.66", + "quote 1.0.31", + "regex-syntax 0.6.29", + "strsim 0.10.0", + "syn 2.0.18", + "unicode-width", +] + [[package]] name = "ssh-encoding" version = "0.1.0" @@ -4657,6 +4713,7 @@ dependencies = [ "sha2 0.9.9", "simple-logging", "sqlx", + "sscanf", "ssh-key", "stderrlog", "tar", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 20e4473a6..461ae48c7 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -121,6 +121,7 @@ rpassword = "7.0.0" rpc-toolkit = "0.2.2" rust-argon2 = "1.0.0" scopeguard = "1.1" # because avahi-sys fucks your shit up +sscanf = "0.4.1" serde = { version = "1.0.139", features = ["derive", "rc"] } serde_cbor = { package = "ciborium", version = "0.2.0" } serde_json = "1.0.82" diff --git a/backend/src/init.rs b/backend/src/init.rs index da6c2b8f4..58d888b48 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -20,6 +20,9 @@ use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::prelude::*; use crate::sound::BEP; use crate::system::time; +use crate::util::cpupower::{ + current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE, +}; use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL}; use crate::util::Invoke; use crate::{Error, ARCH}; @@ -341,6 +344,23 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { .await?; tracing::info!("Enabled Docker QEMU Emulation"); + if current_governor() + .await? + .map(|g| &g != &GOVERNOR_PERFORMANCE) + .unwrap_or(false) + { + tracing::info!("Setting CPU Governor to \"{}\"", GOVERNOR_PERFORMANCE); + if get_available_governors() + .await? + .contains(&GOVERNOR_PERFORMANCE) + { + set_governor(&GOVERNOR_PERFORMANCE).await?; + tracing::info!("Set CPU Governor"); + } else { + tracing::warn!("CPU Governor \"{}\" Not Available", GOVERNOR_PERFORMANCE) + } + } + let mut warn_time_not_synced = true; for _ in 0..60 { if check_time_is_synchronized().await? { diff --git a/backend/src/util/cpupower.rs b/backend/src/util/cpupower.rs new file mode 100644 index 000000000..a48502754 --- /dev/null +++ b/backend/src/util/cpupower.rs @@ -0,0 +1,125 @@ +use std::borrow::Cow; +use std::collections::BTreeSet; + +use imbl::OrdMap; +use tokio::process::Command; + +use crate::prelude::*; +use crate::util::Invoke; + +pub const GOVERNOR_PERFORMANCE: Governor = Governor(Cow::Borrowed("performance")); + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Governor(Cow<'static, str>); +impl std::fmt::Display for Governor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} +impl std::ops::Deref for Governor { + type Target = str; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} +impl std::borrow::Borrow for Governor { + fn borrow(&self) -> &str { + &**self + } +} + +pub async fn get_available_governors() -> Result, Error> { + let raw = String::from_utf8( + Command::new("cpupower") + .arg("frequency-info") + .arg("-g") + .invoke(ErrorKind::CpuSettings) + .await?, + )?; + let mut for_cpu: OrdMap> = OrdMap::new(); + let mut current_cpu = None; + for line in raw.lines() { + if line.starts_with("analyzing") { + current_cpu = Some( + sscanf::sscanf!(line, "analyzing CPU {u32}:") + .map_err(|e| eyre!("{e}")) + .with_kind(ErrorKind::ParseSysInfo)?, + ); + } else if let Some(rest) = line + .trim() + .strip_prefix("available cpufreq governors:") + .map(|s| s.trim()) + { + if rest != "Not Available" { + for_cpu + .entry(current_cpu.ok_or_else(|| { + Error::new( + eyre!("governors listed before cpu"), + ErrorKind::ParseSysInfo, + ) + })?) + .or_default() + .extend( + rest.split_ascii_whitespace() + .map(|g| Governor(Cow::Owned(g.to_owned()))), + ); + } + } + } + Ok(for_cpu + .into_iter() + .fold(None, |acc: Option>, (_, x)| { + if let Some(acc) = acc { + Some(acc.intersection(&x).cloned().collect()) + } else { + Some(x) + } + }) + .unwrap_or_default()) // include only governors available for ALL cpus +} + +pub async fn current_governor() -> Result, Error> { + let Some(raw) = Command::new("cpupower") + .arg("frequency-info") + .arg("-p") + .invoke(ErrorKind::CpuSettings) + .await + .and_then(|s| Ok(Some(String::from_utf8(s)?))) + .or_else(|e| { + if e.source + .to_string() + .contains("Unable to determine current policy") + { + Ok(None) + } else { + Err(e) + } + })? + else { + return Ok(None); + }; + + for line in raw.lines() { + if let Some(governor) = line + .trim() + .strip_prefix("The governor \"") + .and_then(|s| s.strip_suffix("\" may decide which speed to use")) + { + return Ok(Some(Governor(Cow::Owned(governor.to_owned())))); + } + } + Err(Error::new( + eyre!("Failed to parse cpupower output:\n{raw}"), + ErrorKind::ParseSysInfo, + )) +} + +pub async fn set_governor(governor: &Governor) -> Result<(), Error> { + Command::new("cpupower") + .arg("frequency-set") + .arg("-g") + .arg(&*governor.0) + .invoke(ErrorKind::CpuSettings) + .await?; + Ok(()) +} diff --git a/backend/src/util/mod.rs b/backend/src/util/mod.rs index 90b6ddcdf..428bf5c6e 100644 --- a/backend/src/util/mod.rs +++ b/backend/src/util/mod.rs @@ -24,6 +24,7 @@ use tracing::instrument; use crate::shutdown::Shutdown; use crate::{Error, ErrorKind, ResultExt as _}; pub mod config; +pub mod cpupower; pub mod docker; pub mod http_reader; pub mod io; diff --git a/build/lib/depends b/build/lib/depends index b64d81ba8..6376a0762 100644 --- a/build/lib/depends +++ b/build/lib/depends @@ -24,6 +24,7 @@ iw jq libavahi-client3 libyajl2 +linux-cpupower lm-sensors lshw lvm2 diff --git a/libs/models/src/errors.rs b/libs/models/src/errors.rs index 30a26b412..637ac9aa5 100644 --- a/libs/models/src/errors.rs +++ b/libs/models/src/errors.rs @@ -78,6 +78,7 @@ pub enum ErrorKind { OpenSsh = 66, Zram = 67, Lshw = 68, + CpuSettings = 69, } impl ErrorKind { pub fn as_str(&self) -> &'static str { @@ -151,6 +152,7 @@ impl ErrorKind { OpenSsh => "OpenSSH Error", Zram => "Zram Error", Lshw => "LSHW Error", + CpuSettings => "CPU Settings Error", } } }