From f8fc472ef0e290720ad54e98276eabe829487365 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 15 Nov 2023 11:50:18 -0700 Subject: [PATCH 1/2] set better governor hierarchy and add cli command to change --- core/startos/src/bins/mod.rs | 20 +++------ core/startos/src/db/model.rs | 3 ++ core/startos/src/init.rs | 27 ++++++------ core/startos/src/procedure/js_scripts.rs | 8 ++-- core/startos/src/system.rs | 54 +++++++++++++++++++++++- core/startos/src/util/cpupower.rs | 24 ++++++++++- 6 files changed, 100 insertions(+), 36 deletions(-) diff --git a/core/startos/src/bins/mod.rs b/core/startos/src/bins/mod.rs index c391338fe..f9c88cae9 100644 --- a/core/startos/src/bins/mod.rs +++ b/core/startos/src/bins/mod.rs @@ -18,7 +18,7 @@ fn select_executable(name: &str) -> Option { match name { #[cfg(feature = "avahi-alias")] "avahi-alias" => Some(avahi_alias::main), - #[cfg(feature = "js_engine")] + #[cfg(feature = "js-engine")] "start-deno" => Some(start_deno::main), #[cfg(feature = "cli")] "start-cli" => Some(start_cli::main), @@ -36,24 +36,14 @@ fn select_executable(name: &str) -> Option { pub fn startbox() { let args = std::env::args().take(2).collect::>(); - if let Some(x) = args + let executable = args .get(0) .and_then(|s| Path::new(&*s).file_name()) - .and_then(|s| s.to_str()) - .and_then(|s| select_executable(&s)) - { - x() - } else if let Some(x) = args.get(1).and_then(|s| select_executable(&s)) { + .and_then(|s| s.to_str()); + if let Some(x) = executable.and_then(|s| select_executable(&s)) { x() } else { - eprintln!( - "unknown executable: {}", - args.get(0) - .filter(|x| &**x != "startbox") - .or_else(|| args.get(1)) - .map(|s| s.as_str()) - .unwrap_or("N/A") - ); + eprintln!("unknown executable: {}", executable.unwrap_or("N/A")); std::process::exit(1); } } diff --git a/core/startos/src/db/model.rs b/core/startos/src/db/model.rs index e1c1767ec..bce8886ca 100644 --- a/core/startos/src/db/model.rs +++ b/core/startos/src/db/model.rs @@ -22,6 +22,7 @@ use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; use crate::prelude::*; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::status::Status; +use crate::util::cpupower::{get_preferred_governor, Governor}; use crate::util::Version; use crate::version::{Current, VersionT}; use crate::{ARCH, PLATFORM}; @@ -83,6 +84,7 @@ impl Database { .join(":"), ntp_synced: false, zram: true, + governor: None, }, package_data: AllPackageData::default(), ui: serde_json::from_str(include_str!(concat!( @@ -134,6 +136,7 @@ pub struct ServerInfo { pub ntp_synced: bool, #[serde(default)] pub zram: bool, + pub governor: Option, } #[derive(Debug, Deserialize, Serialize, HasModel)] diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 288149f37..097696dac 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -20,7 +20,7 @@ use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::prelude::*; use crate::sound::BEP; use crate::util::cpupower::{ - current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE, + current_governor, get_available_governors, get_preferred_governor, set_governor, }; use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL}; use crate::util::Invoke; @@ -354,21 +354,20 @@ 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"); + let governor = if let Some(governor) = &server_info.governor { + if get_available_governors().await?.contains(governor) { + Some(governor) } else { - tracing::warn!("CPU Governor \"{}\" Not Available", GOVERNOR_PERFORMANCE) + tracing::warn!("CPU Governor \"{governor}\" Not Available"); + None } + } else { + get_preferred_governor().await? + }; + if let Some(governor) = governor { + tracing::info!("Setting CPU Governor to \"{governor}\""); + set_governor(governor).await?; + tracing::info!("Set CPU Governor"); } let mut time_not_synced = true; diff --git a/core/startos/src/procedure/js_scripts.rs b/core/startos/src/procedure/js_scripts.rs index 88a45988f..43553cee0 100644 --- a/core/startos/src/procedure/js_scripts.rs +++ b/core/startos/src/procedure/js_scripts.rs @@ -226,18 +226,18 @@ async fn test_start_deno_command() -> Result { .arg("build") .invoke(ErrorKind::Unknown) .await?; - if tokio::fs::metadata("target/debug/start-deno") + if tokio::fs::metadata("../target/debug/start-deno") .await .is_err() { Command::new("ln") .arg("-rsf") - .arg("target/debug/startbox") - .arg("target/debug/start-deno") + .arg("../target/debug/startbox") + .arg("../target/debug/start-deno") .invoke(crate::ErrorKind::Filesystem) .await?; } - Ok(Command::new("target/debug/start-deno")) + Ok(Command::new("../target/debug/start-deno")) } #[tokio::test] diff --git a/core/startos/src/system.rs b/core/startos/src/system.rs index 53216f796..b5cd42844 100644 --- a/core/startos/src/system.rs +++ b/core/startos/src/system.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::fmt; use chrono::Utc; @@ -20,11 +21,12 @@ use crate::logs::{ }; use crate::prelude::*; use crate::shutdown::Shutdown; +use crate::util::cpupower::{get_available_governors, set_governor, Governor}; use crate::util::serde::{display_serializable, IoFormat}; use crate::util::{display_none, Invoke}; use crate::{Error, ErrorKind, ResultExt}; -#[command(subcommands(zram))] +#[command(subcommands(zram, governor))] pub async fn experimental() -> Result<(), Error> { Ok(()) } @@ -85,6 +87,56 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), Ok(()) } +#[derive(Debug, Deserialize, Serialize)] +pub struct GovernorInfo { + current: Option, + available: BTreeSet, +} + +fn display_governor_info(arg: GovernorInfo, matches: &ArgMatches) { + use prettytable::*; + + if matches.is_present("format") { + return display_serializable(arg, matches); + } + + let mut table = Table::new(); + table.add_row(row![bc -> "GOVERNORS"]); + for entry in arg.available { + if Some(&entry) == arg.current.as_ref() { + table.add_row(row![g -> format!("* {entry} (current)")]); + } else { + table.add_row(row![entry]); + } + } + table.print_tty(false).unwrap(); +} + +#[command(display(display_governor_info))] +pub async fn governor( + #[context] ctx: RpcContext, + #[allow(unused_variables)] + #[arg(long = "format")] + format: Option, + #[arg] set: Option, +) -> Result { + let available = get_available_governors().await?; + if let Some(set) = set { + if !available.contains(&set) { + return Err(Error::new( + eyre!("Governor {set} not available"), + ErrorKind::InvalidRequest, + )); + } + set_governor(&set).await?; + ctx.db + .mutate(|d| d.as_server_info_mut().as_governor_mut().ser(&Some(set))) + .await?; + } + let current = ctx.db.peek().await.as_server_info().as_governor().de()?; + Ok(GovernorInfo { current, available }) +} + #[derive(Serialize, Deserialize)] pub struct TimeInfo { now: String, diff --git a/core/startos/src/util/cpupower.rs b/core/startos/src/util/cpupower.rs index a48502754..7fa850a88 100644 --- a/core/startos/src/util/cpupower.rs +++ b/core/startos/src/util/cpupower.rs @@ -7,10 +7,20 @@ use tokio::process::Command; use crate::prelude::*; use crate::util::Invoke; -pub const GOVERNOR_PERFORMANCE: Governor = Governor(Cow::Borrowed("performance")); +pub const GOVERNOR_HEIRARCHY: &[Governor] = &[ + Governor(Cow::Borrowed("ondemand")), + Governor(Cow::Borrowed("schedutil")), + Governor(Cow::Borrowed("conservative")), +]; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub struct Governor(Cow<'static, str>); +impl std::str::FromStr for Governor { + type Err = std::convert::Infallible; + fn from_str(s: &str) -> Result { + Ok(Self(s.to_owned().into())) + } +} impl std::fmt::Display for Governor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) @@ -114,6 +124,16 @@ pub async fn current_governor() -> Result, Error> { )) } +pub async fn get_preferred_governor() -> Result, Error> { + let governors = get_available_governors().await?; + for governor in GOVERNOR_HEIRARCHY { + if governors.contains(governor) { + return Ok(Some(governor)); + } + } + Ok(None) +} + pub async fn set_governor(governor: &Governor) -> Result<(), Error> { Command::new("cpupower") .arg("frequency-set") From a1bb9072bef7bc7ee74dddbc64c357792872c9d7 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Thu, 16 Nov 2023 12:50:56 -0700 Subject: [PATCH 2/2] allow non-zero exit `cpupower frequency-info -g` --- core/startos/src/util/cpupower.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/startos/src/util/cpupower.rs b/core/startos/src/util/cpupower.rs index 7fa850a88..cc4ac5ef4 100644 --- a/core/startos/src/util/cpupower.rs +++ b/core/startos/src/util/cpupower.rs @@ -39,13 +39,12 @@ impl std::borrow::Borrow for Governor { } 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 raw = Command::new("cpupower") + .arg("frequency-info") + .arg("-g") + .invoke(ErrorKind::CpuSettings) + .await + .map_or_else(|e| Ok(e.source.to_string()), String::from_utf8)?; let mut for_cpu: OrdMap> = OrdMap::new(); let mut current_cpu = None; for line in raw.lines() {