diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9276741..7a329a8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,6 +21,10 @@ jobs: - uses: actions/checkout@v2 - name: Add target run: rustup target install x86_64-unknown-linux-musl + - name: Run clippy + run: cargo clippy -- -D warnings + - name: Run fmt check + run: cargo fmt -- --check - name: Run tests run: cargo test --verbose - name: Install binary compressor diff --git a/src/config.rs b/src/config.rs index 219ed8e..d6ac0ff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,7 +26,7 @@ impl FromStr for Card { value[4..] .parse::() .map_err(|e| AmdFanError::InvalidSuffix(format!("{:?}", e))) - .map(|n| Card(n)) + .map(Card) } } @@ -71,7 +71,7 @@ impl<'de> Deserialize<'de> for Card { } } } - deserializer.deserialize_str(CardVisitor).map(|v| Card(v)) + deserializer.deserialize_str(CardVisitor).map(Card) } } @@ -115,7 +115,7 @@ pub enum LogLevel { } impl LogLevel { - pub fn to_str(&self) -> &str { + pub fn as_str(&self) -> &str { match self { LogLevel::Off => "OFF", LogLevel::Error => "ERROR", @@ -149,7 +149,7 @@ impl Config { return self.max_speed(); } - crate::linear_map( + crate::utils::linear_map( temp, self.speed_matrix[idx].temp, self.speed_matrix[idx + 1].temp, diff --git a/src/fan/change_mode.rs b/src/fan/change_mode.rs new file mode 100644 index 0000000..e250a2e --- /dev/null +++ b/src/fan/change_mode.rs @@ -0,0 +1,51 @@ +use gumdrop::Options; + +use crate::config::Config; +use crate::io_err::not_found; +use crate::FanMode; + +/// Change card fan mode to either automatic or manual +pub fn run(switcher: Switcher, mode: FanMode, config: Config) -> std::io::Result<()> { + let mut controllers = crate::utils::controllers(&config, true)?; + + let cards = match switcher.card { + Some(card_id) => match controllers + .iter() + .position(|hw_mon| *hw_mon.card == card_id) + { + Some(card) => vec![controllers.remove(card)], + None => { + eprintln!("Card does not exists. Available cards: "); + for hw_mon in controllers { + eprintln!(" * {}", *hw_mon.card); + } + return Err(not_found()); + } + }, + None => controllers, + }; + + for hw_mon in cards { + match mode { + FanMode::Automatic => { + if let Err(e) = hw_mon.set_automatic() { + log::error!("{:?}", e); + } + } + FanMode::Manual => { + if let Err(e) = hw_mon.set_manual() { + log::error!("{:?}", e); + } + } + } + } + Ok(()) +} + +#[derive(Debug, Options)] +pub struct Switcher { + #[options(help = "Print help message")] + help: bool, + #[options(help = "GPU Card number")] + card: Option, +} diff --git a/src/fan/mod.rs b/src/fan/mod.rs new file mode 100644 index 0000000..462a473 --- /dev/null +++ b/src/fan/mod.rs @@ -0,0 +1,25 @@ +use gumdrop::Options; + +pub mod change_mode; +pub mod monitor; +pub mod service; + +#[derive(Debug, Options)] +pub struct AvailableCards { + #[options(help = "Help message")] + help: bool, +} + +#[derive(Debug, Options)] +pub enum FanCommand { + #[options(help = "Print current temp and fan speed")] + Monitor(monitor::Monitor), + #[options(help = "Set fan speed depends on GPU temperature")] + Service(service::Service), + #[options(help = "Switch to GPU automatic fan speed control")] + SetAutomatic(change_mode::Switcher), + #[options(help = "Switch to GPU manual fan speed control")] + SetManual(change_mode::Switcher), + #[options(help = "Print available cards")] + Available(AvailableCards), +} diff --git a/src/monitor.rs b/src/fan/monitor.rs similarity index 82% rename from src/monitor.rs rename to src/fan/monitor.rs index 24703fb..89bd6a0 100644 --- a/src/monitor.rs +++ b/src/fan/monitor.rs @@ -1,7 +1,9 @@ -use crate::config::Config; -use crate::{controllers, AmdFanError}; use std::str::FromStr; +use crate::config::Config; +use crate::utils::controllers; +use crate::AmdFanError; + #[derive(Debug)] pub enum MonitorFormat { Short, @@ -34,6 +36,7 @@ pub struct Monitor { format: MonitorFormat, } +/// Start print cards temperature and fan speed pub fn run(monitor: Monitor, config: Config) -> std::io::Result<()> { match monitor.format { MonitorFormat::Short => short(config), @@ -57,7 +60,14 @@ pub fn verbose(config: Config) -> std::io::Result<()> { hw_mon .pwm() .map_or_else(|_e| String::from("FAILED"), |f| f.to_string()), - (crate::linear_map(hw_mon.pwm().unwrap_or_default() as f64, min as f64, max as f64, 0f64, 100f64)).round(), + (crate::utils::linear_map( + hw_mon.pwm().unwrap_or_default() as f64, + min as f64, + max as f64, + 0f64, + 100f64 + )) + .round(), ); println!(); @@ -93,7 +103,14 @@ pub fn short(config: Config) -> std::io::Result<()> { min, max, hw_mon.pwm().unwrap_or_default(), - crate::linear_map(hw_mon.pwm().unwrap_or_default() as f64, min as f64, max as f64, 0f64, 100f64).round(), + crate::utils::linear_map( + hw_mon.pwm().unwrap_or_default() as f64, + min as f64, + max as f64, + 0f64, + 100f64 + ) + .round(), ); } std::thread::sleep(std::time::Duration::from_secs(4)); diff --git a/src/fan/service.rs b/src/fan/service.rs new file mode 100644 index 0000000..fbbdbe6 --- /dev/null +++ b/src/fan/service.rs @@ -0,0 +1,39 @@ +use gumdrop::Options; + +use crate::config::Config; +use crate::io_err::not_found; + +/// Start service which will change fan speed according to config and GPU temperature +pub fn run(config: Config) -> std::io::Result<()> { + let mut controllers = crate::utils::controllers(&config, true)?; + if controllers.is_empty() { + return Err(not_found()); + } + let mut cache = std::collections::HashMap::new(); + loop { + for hw_mon in controllers.iter_mut() { + let gpu_temp = hw_mon.max_gpu_temp().unwrap_or_default(); + log::debug!("Current {} temperature: {}", hw_mon.card, gpu_temp); + let last = *cache.entry(*hw_mon.card).or_insert(1_000f64); + + if (last - gpu_temp).abs() < 0.001f64 { + log::debug!("Temperature didn't change"); + continue; + }; + let speed = config.speed_for_temp(gpu_temp); + log::debug!("Resolved speed {:.2}", speed); + + if let Err(e) = hw_mon.set_speed(speed) { + log::error!("Failed to change speed to {}. {:?}", speed, e); + } + cache.insert(*hw_mon.card, gpu_temp); + } + std::thread::sleep(std::time::Duration::from_secs(4)); + } +} + +#[derive(Debug, Options)] +pub struct Service { + #[options(help = "Help message")] + help: bool, +} diff --git a/src/hw_mon.rs b/src/hw_mon.rs index 932c5ea..b89228d 100644 --- a/src/hw_mon.rs +++ b/src/hw_mon.rs @@ -1,6 +1,7 @@ use crate::config::Card; use crate::io_err::{invalid_input, not_found}; -use crate::{linear_map, AmdFanError, HwMon, HW_MON_DIR, ROOT_DIR}; +use crate::utils::linear_map; +use crate::{AmdFanError, HwMon, HW_MON_DIR, ROOT_DIR}; /// pulse width modulation fan control minimum level (0) const PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min"; @@ -21,11 +22,11 @@ const PULSE_WIDTH_MODULATION_MANUAL: &str = "1"; impl HwMon { pub fn new(card: &Card, name: &str) -> Self { Self { - card: card.clone(), + card: *card, name: String::from(name), pwm_min: None, pwm_max: None, - temp_inputs: load_temp_inputs(&card, name), + temp_inputs: load_temp_inputs(card, name), } } @@ -34,7 +35,7 @@ impl HwMon { for name in self.temp_inputs.iter() { results.push(self.read_gpu_temp(name).unwrap_or(0)); } - results.sort(); + results.sort_unstable(); results .last() .copied() @@ -186,7 +187,7 @@ fn hw_mon_dirs_path(card: &Card) -> std::path::PathBuf { #[inline] fn hw_mon_dir_path(card: &Card, name: &str) -> std::path::PathBuf { - hw_mon_dirs_path(&card).join(name) + hw_mon_dirs_path(card).join(name) } pub(crate) fn open_hw_mon(card: Card) -> std::io::Result { diff --git a/src/io_err.rs b/src/io_err.rs index 2962c52..eea3263 100644 --- a/src/io_err.rs +++ b/src/io_err.rs @@ -1,14 +1,12 @@ use std::io::Error as IoErr; use std::io::ErrorKind; -pub fn invalid_data() -> IoErr { - IoErr::from(ErrorKind::InvalidData) -} - +#[inline(always)] pub fn not_found() -> IoErr { IoErr::from(ErrorKind::NotFound) } +#[inline(always)] pub fn invalid_input() -> IoErr { IoErr::from(ErrorKind::NotFound) } diff --git a/src/main.rs b/src/main.rs index f882c0e..9e1977e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,18 @@ -mod config; -mod hw_mon; -mod io_err; -mod monitor; - -extern crate log; - +use std::fmt::Formatter; use std::io::ErrorKind; -use crate::config::{load_config, Card, Config}; -use crate::io_err::{invalid_data, not_found}; use gumdrop::Options; -use std::fmt::Formatter; + +use crate::config::{load_config, Card}; + +mod config; +mod fan; +mod hw_mon; +mod io_err; +mod utils; +mod voltage; + +extern crate log; static CONFIG_DIR: &str = "/etc/amdfand"; static CONFIG_PATH: &str = "/etc/amdfand/config.toml"; @@ -45,12 +47,6 @@ impl std::fmt::Display for AmdFanError { } } -// linear mapping from the xrange to the yrange -fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 { - let m = (y2 - y1) / (x2 - x1); - m * (x - x1) + y1 -} - #[derive(Debug)] pub struct HwMon { card: Card, @@ -65,38 +61,12 @@ pub enum FanMode { Automatic, } -#[derive(Debug, Options)] -pub struct Service { - #[options(help = "Help message")] - help: bool, -} - -#[derive(Debug, Options)] -pub struct Switcher { - #[options(help = "Print help message")] - help: bool, - #[options(help = "GPU Card number")] - card: Option, -} - -#[derive(Debug, Options)] -pub struct AvailableCards { - #[options(help = "Help message")] - help: bool, -} - -#[derive(Debug, Options)] +#[derive(Options)] pub enum Command { - #[options(help = "Print current temp and fan speed")] - Monitor(monitor::Monitor), - #[options(help = "Set fan speed depends on GPU temperature")] - Service(Service), - #[options(help = "Switch to GPU automatic fan speed control")] - SetAutomatic(Switcher), - #[options(help = "Switch to GPU manual fan speed control")] - SetManual(Switcher), - #[options(help = "Print available cards")] - Available(AvailableCards), + #[options(help = "GPU card fan control")] + Fan(fan::FanCommand), + #[options(help = "Overclock GPU card")] + Voltage(voltage::VoltageCommand), } #[derive(Options)] @@ -109,105 +79,6 @@ pub struct Opts { command: Option, } -fn read_cards() -> std::io::Result> { - let mut cards = vec![]; - let entries = std::fs::read_dir(ROOT_DIR)?; - for entry in entries { - match entry - .and_then(|entry| { - entry - .file_name() - .as_os_str() - .to_str() - .map(String::from) - .ok_or_else(invalid_data) - }) - .and_then(|file_name| file_name.parse::().map_err(|_| invalid_data())) - { - Ok(card) => { - cards.push(card); - } - _ => continue, - }; - } - Ok(cards) -} - -fn controllers(config: &Config, filter: bool) -> std::io::Result> { - Ok(read_cards()? - .into_iter() - .filter(|card| !filter || config.cards().iter().find(|name| **name == *card).is_some()) - .filter_map(|card| hw_mon::open_hw_mon(card).ok()) - .filter(|hw_mon| !filter || { hw_mon.is_amd() }) - .filter(|hw_mon| !filter || hw_mon.name_is_amd()) - .collect()) -} - -fn service(config: Config) -> std::io::Result<()> { - let mut controllers = controllers(&config, true)?; - if controllers.is_empty() { - return Err(not_found()); - } - let mut cache = std::collections::HashMap::new(); - loop { - for hw_mon in controllers.iter_mut() { - let gpu_temp = hw_mon.max_gpu_temp().unwrap_or_default(); - log::debug!("Current {} temperature: {}", hw_mon.card, gpu_temp); - let last = *cache.entry(*hw_mon.card).or_insert(1_000f64); - - if (last - gpu_temp).abs() < 0.001f64 { - log::debug!("Temperature didn't change"); - continue; - }; - let speed = config.speed_for_temp(gpu_temp); - log::debug!("Resolved speed {:.2}", speed); - - if let Err(e) = hw_mon.set_speed(speed) { - log::error!("Failed to change speed to {}. {:?}", speed, e); - } - cache.insert(*hw_mon.card, gpu_temp); - } - std::thread::sleep(std::time::Duration::from_secs(4)); - } -} - -fn change_mode(switcher: Switcher, mode: FanMode, config: Config) -> std::io::Result<()> { - let mut controllers = controllers(&config, true)?; - - let cards = match switcher.card { - Some(card_id) => match controllers - .iter() - .position(|hw_mon| *hw_mon.card == card_id) - { - Some(card) => vec![controllers.remove(card)], - None => { - eprintln!("Card does not exists. Available cards: "); - for hw_mon in controllers { - eprintln!(" * {}", *hw_mon.card); - } - return Err(not_found()); - } - }, - None => controllers, - }; - - for hw_mon in cards { - match mode { - FanMode::Automatic => { - if let Err(e) = hw_mon.set_automatic() { - log::error!("{:?}", e); - } - } - FanMode::Manual => { - if let Err(e) = hw_mon.set_manual() { - log::error!("{:?}", e); - } - } - } - } - Ok(()) -} - fn main() -> std::io::Result<()> { if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) { std::fs::create_dir_all(CONFIG_DIR)?; @@ -215,7 +86,7 @@ fn main() -> std::io::Result<()> { let config = load_config()?; - std::env::set_var("RUST_LOG", config.log_level().to_str()); + std::env::set_var("RUST_LOG", config.log_level().as_str()); pretty_env_logger::init(); let opts: Opts = Opts::parse_args_default_or_exit(); @@ -226,21 +97,30 @@ fn main() -> std::io::Result<()> { } match opts.command { - None => service(config), - Some(Command::Monitor(monitor)) => monitor::run(monitor, config), - Some(Command::Service(_)) => service(config), - Some(Command::SetAutomatic(switcher)) => change_mode(switcher, FanMode::Automatic, config), - Some(Command::SetManual(switcher)) => change_mode(switcher, FanMode::Manual, config), - Some(Command::Available(_)) => { + None => fan::service::run(config), + Some(Command::Fan(fan::FanCommand::Monitor(monitor))) => fan::monitor::run(monitor, config), + Some(Command::Fan(fan::FanCommand::Service(_))) => fan::service::run(config), + Some(Command::Fan(fan::FanCommand::SetAutomatic(switcher))) => { + fan::change_mode::run(switcher, FanMode::Automatic, config) + } + Some(Command::Fan(fan::FanCommand::SetManual(switcher))) => { + fan::change_mode::run(switcher, FanMode::Manual, config) + } + Some(Command::Fan(fan::FanCommand::Available(_))) => { println!("Available cards"); - controllers(&config, false)?.into_iter().for_each(|hw_mon| { - println!( - " * {:6>} - {}", - hw_mon.card, - hw_mon.name().unwrap_or_default() - ); - }); + utils::controllers(&config, false)? + .into_iter() + .for_each(|hw_mon| { + println!( + " * {:6>} - {}", + hw_mon.card, + hw_mon.name().unwrap_or_default() + ); + }); Ok(()) } + Some(Command::Voltage(voltage::VoltageCommand::Placeholder(_))) => { + unimplemented!() + } } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..89a803b --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,30 @@ +use crate::config::{Card, Config}; +use crate::{hw_mon, HwMon, ROOT_DIR}; + +/// linear mapping from the xrange to the yrange +pub fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 { + let m = (y2 - y1) / (x2 - x1); + m * (x - x1) + y1 +} + +/// Read all available graphic cards from direct rendering manager +pub fn read_cards() -> std::io::Result> { + Ok(std::fs::read_dir(ROOT_DIR)? + .into_iter() + .filter_map(|entry| entry.ok()) + .filter_map(|entry| entry.file_name().as_os_str().to_str().map(String::from)) + .filter_map(|file_name| file_name.parse::().ok()) + .collect()) +} + +/// Wrap cards in HW Mon manipulator and +/// filter cards so only amd and listed in config cards are accessible +pub fn controllers(config: &Config, filter: bool) -> std::io::Result> { + Ok(read_cards()? + .into_iter() + .filter(|card| !filter || config.cards().iter().any(|name| **name == **card)) + .filter_map(|card| hw_mon::open_hw_mon(card).ok()) + .filter(|hw_mon| !filter || { hw_mon.is_amd() }) + .filter(|hw_mon| !filter || hw_mon.name_is_amd()) + .collect()) +} diff --git a/src/voltage/mod.rs b/src/voltage/mod.rs new file mode 100644 index 0000000..1f33363 --- /dev/null +++ b/src/voltage/mod.rs @@ -0,0 +1,9 @@ +#[derive(Debug, gumdrop::Options)] +pub enum VoltageCommand { + Placeholder(Placeholder), +} + +#[derive(Debug, gumdrop::Options)] +pub struct Placeholder { + help: bool, +}