Prepare code for adding voltage

Add clippy and fmt checks

Prepare code for adding voltage
This commit is contained in:
Adrian Woźniak 2021-08-02 16:00:07 +02:00
parent 90e1dcdad6
commit d9e72d5727
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
11 changed files with 231 additions and 177 deletions

View File

@ -21,6 +21,10 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Add target - name: Add target
run: rustup target install x86_64-unknown-linux-musl 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 - name: Run tests
run: cargo test --verbose run: cargo test --verbose
- name: Install binary compressor - name: Install binary compressor

View File

@ -26,7 +26,7 @@ impl FromStr for Card {
value[4..] value[4..]
.parse::<u32>() .parse::<u32>()
.map_err(|e| AmdFanError::InvalidSuffix(format!("{:?}", e))) .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 { impl LogLevel {
pub fn to_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
LogLevel::Off => "OFF", LogLevel::Off => "OFF",
LogLevel::Error => "ERROR", LogLevel::Error => "ERROR",
@ -149,7 +149,7 @@ impl Config {
return self.max_speed(); return self.max_speed();
} }
crate::linear_map( crate::utils::linear_map(
temp, temp,
self.speed_matrix[idx].temp, self.speed_matrix[idx].temp,
self.speed_matrix[idx + 1].temp, self.speed_matrix[idx + 1].temp,

51
src/fan/change_mode.rs Normal file
View File

@ -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<u32>,
}

25
src/fan/mod.rs Normal file
View File

@ -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),
}

View File

@ -1,7 +1,9 @@
use crate::config::Config;
use crate::{controllers, AmdFanError};
use std::str::FromStr; use std::str::FromStr;
use crate::config::Config;
use crate::utils::controllers;
use crate::AmdFanError;
#[derive(Debug)] #[derive(Debug)]
pub enum MonitorFormat { pub enum MonitorFormat {
Short, Short,
@ -34,6 +36,7 @@ pub struct Monitor {
format: MonitorFormat, format: MonitorFormat,
} }
/// Start print cards temperature and fan speed
pub fn run(monitor: Monitor, config: Config) -> std::io::Result<()> { pub fn run(monitor: Monitor, config: Config) -> std::io::Result<()> {
match monitor.format { match monitor.format {
MonitorFormat::Short => short(config), MonitorFormat::Short => short(config),
@ -57,7 +60,14 @@ pub fn verbose(config: Config) -> std::io::Result<()> {
hw_mon hw_mon
.pwm() .pwm()
.map_or_else(|_e| String::from("FAILED"), |f| f.to_string()), .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!(); println!();
@ -93,7 +103,14 @@ pub fn short(config: Config) -> std::io::Result<()> {
min, min,
max, max,
hw_mon.pwm().unwrap_or_default(), 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)); std::thread::sleep(std::time::Duration::from_secs(4));

39
src/fan/service.rs Normal file
View File

@ -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,
}

View File

@ -1,6 +1,7 @@
use crate::config::Card; use crate::config::Card;
use crate::io_err::{invalid_input, not_found}; 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) /// pulse width modulation fan control minimum level (0)
const PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min"; const PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
@ -21,11 +22,11 @@ const PULSE_WIDTH_MODULATION_MANUAL: &str = "1";
impl HwMon { impl HwMon {
pub fn new(card: &Card, name: &str) -> Self { pub fn new(card: &Card, name: &str) -> Self {
Self { Self {
card: card.clone(), card: *card,
name: String::from(name), name: String::from(name),
pwm_min: None, pwm_min: None,
pwm_max: 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() { for name in self.temp_inputs.iter() {
results.push(self.read_gpu_temp(name).unwrap_or(0)); results.push(self.read_gpu_temp(name).unwrap_or(0));
} }
results.sort(); results.sort_unstable();
results results
.last() .last()
.copied() .copied()
@ -186,7 +187,7 @@ fn hw_mon_dirs_path(card: &Card) -> std::path::PathBuf {
#[inline] #[inline]
fn hw_mon_dir_path(card: &Card, name: &str) -> std::path::PathBuf { 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<HwMon> { pub(crate) fn open_hw_mon(card: Card) -> std::io::Result<HwMon> {

View File

@ -1,14 +1,12 @@
use std::io::Error as IoErr; use std::io::Error as IoErr;
use std::io::ErrorKind; use std::io::ErrorKind;
pub fn invalid_data() -> IoErr { #[inline(always)]
IoErr::from(ErrorKind::InvalidData)
}
pub fn not_found() -> IoErr { pub fn not_found() -> IoErr {
IoErr::from(ErrorKind::NotFound) IoErr::from(ErrorKind::NotFound)
} }
#[inline(always)]
pub fn invalid_input() -> IoErr { pub fn invalid_input() -> IoErr {
IoErr::from(ErrorKind::NotFound) IoErr::from(ErrorKind::NotFound)
} }

View File

@ -1,16 +1,18 @@
mod config; use std::fmt::Formatter;
mod hw_mon;
mod io_err;
mod monitor;
extern crate log;
use std::io::ErrorKind; use std::io::ErrorKind;
use crate::config::{load_config, Card, Config};
use crate::io_err::{invalid_data, not_found};
use gumdrop::Options; 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_DIR: &str = "/etc/amdfand";
static CONFIG_PATH: &str = "/etc/amdfand/config.toml"; 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)] #[derive(Debug)]
pub struct HwMon { pub struct HwMon {
card: Card, card: Card,
@ -65,38 +61,12 @@ pub enum FanMode {
Automatic, Automatic,
} }
#[derive(Debug, Options)] #[derive(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<u32>,
}
#[derive(Debug, Options)]
pub struct AvailableCards {
#[options(help = "Help message")]
help: bool,
}
#[derive(Debug, Options)]
pub enum Command { pub enum Command {
#[options(help = "Print current temp and fan speed")] #[options(help = "GPU card fan control")]
Monitor(monitor::Monitor), Fan(fan::FanCommand),
#[options(help = "Set fan speed depends on GPU temperature")] #[options(help = "Overclock GPU card")]
Service(Service), Voltage(voltage::VoltageCommand),
#[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),
} }
#[derive(Options)] #[derive(Options)]
@ -109,105 +79,6 @@ pub struct Opts {
command: Option<Command>, command: Option<Command>,
} }
fn read_cards() -> std::io::Result<Vec<Card>> {
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::<Card>().map_err(|_| invalid_data()))
{
Ok(card) => {
cards.push(card);
}
_ => continue,
};
}
Ok(cards)
}
fn controllers(config: &Config, filter: bool) -> std::io::Result<Vec<HwMon>> {
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<()> { fn main() -> std::io::Result<()> {
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) { if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
std::fs::create_dir_all(CONFIG_DIR)?; std::fs::create_dir_all(CONFIG_DIR)?;
@ -215,7 +86,7 @@ fn main() -> std::io::Result<()> {
let config = load_config()?; 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(); pretty_env_logger::init();
let opts: Opts = Opts::parse_args_default_or_exit(); let opts: Opts = Opts::parse_args_default_or_exit();
@ -226,14 +97,20 @@ fn main() -> std::io::Result<()> {
} }
match opts.command { match opts.command {
None => service(config), None => fan::service::run(config),
Some(Command::Monitor(monitor)) => monitor::run(monitor, config), Some(Command::Fan(fan::FanCommand::Monitor(monitor))) => fan::monitor::run(monitor, config),
Some(Command::Service(_)) => service(config), Some(Command::Fan(fan::FanCommand::Service(_))) => fan::service::run(config),
Some(Command::SetAutomatic(switcher)) => change_mode(switcher, FanMode::Automatic, config), Some(Command::Fan(fan::FanCommand::SetAutomatic(switcher))) => {
Some(Command::SetManual(switcher)) => change_mode(switcher, FanMode::Manual, config), fan::change_mode::run(switcher, FanMode::Automatic, config)
Some(Command::Available(_)) => { }
Some(Command::Fan(fan::FanCommand::SetManual(switcher))) => {
fan::change_mode::run(switcher, FanMode::Manual, config)
}
Some(Command::Fan(fan::FanCommand::Available(_))) => {
println!("Available cards"); println!("Available cards");
controllers(&config, false)?.into_iter().for_each(|hw_mon| { utils::controllers(&config, false)?
.into_iter()
.for_each(|hw_mon| {
println!( println!(
" * {:6>} - {}", " * {:6>} - {}",
hw_mon.card, hw_mon.card,
@ -242,5 +119,8 @@ fn main() -> std::io::Result<()> {
}); });
Ok(()) Ok(())
} }
Some(Command::Voltage(voltage::VoltageCommand::Placeholder(_))) => {
unimplemented!()
}
} }
} }

30
src/utils.rs Normal file
View File

@ -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<Vec<Card>> {
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::<Card>().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<Vec<HwMon>> {
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())
}

9
src/voltage/mod.rs Normal file
View File

@ -0,0 +1,9 @@
#[derive(Debug, gumdrop::Options)]
pub enum VoltageCommand {
Placeholder(Placeholder),
}
#[derive(Debug, gumdrop::Options)]
pub struct Placeholder {
help: bool,
}