Refactor, fix broken resolve conflicts, add verbose monitor, skip update speed if temp didn't changed.

This commit is contained in:
Adrian Woźniak 2021-07-30 21:34:31 +02:00
parent 1484daee9a
commit cfa9867e3d
No known key found for this signature in database
GPG Key ID: DE43476F72AD3F6C
2 changed files with 386 additions and 223 deletions

View File

@ -1,5 +1,4 @@
use crate::{AmdFanError, CONFIG_PATH}; use crate::{AmdFanError, CONFIG_PATH};
use log::LevelFilter;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter; use std::fmt::Formatter;
use std::io::ErrorKind; use std::io::ErrorKind;
@ -52,7 +51,7 @@ impl<'de> Deserialize<'de> for Card {
E: de::Error, E: de::Error,
{ {
match value.parse::<Card>() { match value.parse::<Card>() {
Ok(card) => Ok(card.0), Ok(card) => Ok(*card),
Err(AmdFanError::InvalidPrefix) => { Err(AmdFanError::InvalidPrefix) => {
Err(E::custom(format!("expect cardX but got {}", value))) Err(E::custom(format!("expect cardX but got {}", value)))
} }
@ -68,6 +67,7 @@ impl<'de> Deserialize<'de> for Card {
"Failed to read vendor file for {}", "Failed to read vendor file for {}",
value value
))), ))),
_ => unreachable!(),
} }
} }
} }
@ -84,6 +84,14 @@ impl Serialize for Card {
} }
} }
impl std::ops::Deref for Card {
type Target = u32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MatrixPoint { pub struct MatrixPoint {
pub temp: f64, pub temp: f64,
@ -106,15 +114,15 @@ pub enum LogLevel {
Trace, Trace,
} }
impl From<LogLevel> for LevelFilter { impl LogLevel {
fn from(level: LogLevel) -> Self { pub fn to_str(&self) -> &str {
match level { match self {
LogLevel::Off => LevelFilter::Off, LogLevel::Off => "OFF",
LogLevel::Error => LevelFilter::Error, LogLevel::Error => "ERROR",
LogLevel::Warn => LevelFilter::Warn, LogLevel::Warn => "WARN",
LogLevel::Info => LevelFilter::Info, LogLevel::Info => "INFO",
LogLevel::Debug => LevelFilter::Debug, LogLevel::Debug => "DEBUG",
LogLevel::Trace => LevelFilter::Trace, LogLevel::Trace => "TRACE",
} }
} }
} }
@ -141,7 +149,13 @@ impl Config {
return self.max_speed(); return self.max_speed();
} }
crate::linear_map(temp, self.speed_matrix[idx].temp, self.speed_matrix[idx+1].temp, self.speed_matrix[idx].speed, self.speed_matrix[idx+1].speed) crate::linear_map(
temp,
self.speed_matrix[idx].temp,
self.speed_matrix[idx + 1].temp,
self.speed_matrix[idx].speed,
self.speed_matrix[idx + 1].speed,
)
} }
pub fn log_level(&self) -> LogLevel { pub fn log_level(&self) -> LogLevel {
@ -222,7 +236,10 @@ pub fn load_config() -> std::io::Result<Config> {
return Err(std::io::Error::from(ErrorKind::InvalidData)); return Err(std::io::Error::from(ErrorKind::InvalidData));
} }
if matrix_point.speed > 100f64 { if matrix_point.speed > 100f64 {
log::error!("Fan speed can't be above 100.0 found {}", matrix_point.speed); log::error!(
"Fan speed can't be above 100.0 found {}",
matrix_point.speed
);
return Err(std::io::Error::from(ErrorKind::InvalidData)); return Err(std::io::Error::from(ErrorKind::InvalidData));
} }
if let Some(last_point) = last_point { if let Some(last_point) = last_point {

View File

@ -2,11 +2,14 @@ mod config;
extern crate log; extern crate log;
use std::io::Error as IoErr;
use std::io::ErrorKind; use std::io::ErrorKind;
use crate::config::{load_config, Card, Config}; use crate::config::{load_config, Card, Config};
use gumdrop::Options; use gumdrop::Options;
use std::fmt::Formatter;
static CONFIG_DIR: &str = "/etc/amdfand";
static CONFIG_PATH: &str = "/etc/amdfand/config.toml"; static CONFIG_PATH: &str = "/etc/amdfand/config.toml";
static ROOT_DIR: &str = "/sys/class/drm"; static ROOT_DIR: &str = "/sys/class/drm";
@ -19,6 +22,24 @@ pub enum AmdFanError {
InvalidSuffix(String), InvalidSuffix(String),
NotAmdCard, NotAmdCard,
FailedReadVendor, FailedReadVendor,
InvalidMonitorFormat,
}
impl std::fmt::Display for AmdFanError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AmdFanError::InvalidPrefix => f.write_str("Card must starts with `card`."),
AmdFanError::InputTooShort => f.write_str(
"Card must starts with `card` and ends with number. Given name is too short.",
),
AmdFanError::InvalidSuffix(s) => {
f.write_fmt(format_args!("Value after `card` is invalid {}", s))
}
AmdFanError::NotAmdCard => f.write_str("Vendor is not AMD"),
AmdFanError::FailedReadVendor => f.write_str("Unable to read GPU vendor"),
AmdFanError::InvalidMonitorFormat => f.write_str("Monitor format is not valid. Available values are: short, s, long l, verbose and v"),
}
}
} }
// linear mapping from the xrange to the yrange // linear mapping from the xrange to the yrange
@ -33,159 +54,220 @@ pub struct HwMon {
name: String, name: String,
pwm_min: Option<u32>, pwm_min: Option<u32>,
pwm_max: Option<u32>, pwm_max: Option<u32>,
temp_inputs: Vec<String>,
} }
impl HwMon { /// pulse width modulation fan control minimum level (0)
pub fn new(card: &Card, name: &str) -> Self { static PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
Self {
card: card.clone(), /// pulse width modulation fan control maximum level (255)
name: String::from(name), static PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
pwm_min: None,
pwm_max: None, /// pulse width modulation fan level (0-255)
static PULSE_WIDTH_MODULATION: &str = "pwm1";
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
static PULSE_WIDTH_MODULATION_ENABLED: &str = "pwm1_enable";
mod hw_mon {
use std::io::Error as IoErr;
use crate::config::Card;
use crate::{
linear_map, AmdFanError, HwMon, HW_MON_DIR, PULSE_WIDTH_MODULATION,
PULSE_WIDTH_MODULATION_ENABLED, PULSE_WIDTH_MODULATION_MAX, PULSE_WIDTH_MODULATION_MIN,
ROOT_DIR,
};
use std::io::ErrorKind;
impl HwMon {
pub fn new(card: &Card, name: &str) -> Self {
Self {
card: card.clone(),
name: String::from(name),
pwm_min: None,
pwm_max: None,
temp_inputs: load_temp_inputs(&card, name),
}
}
pub fn max_gpu_temp(&self) -> std::io::Result<f64> {
let mut results = Vec::with_capacity(self.temp_inputs.len());
for name in self.temp_inputs.iter() {
results.push(self.read_gpu_temp(name).unwrap_or(0));
}
results.sort();
results
.last()
.copied()
.map(|temp| temp as f64 / 1000f64)
.ok_or_else(|| IoErr::from(ErrorKind::InvalidInput))
}
pub fn gpu_temp(&self) -> Vec<(String, std::io::Result<f64>)> {
self.temp_inputs
.clone()
.into_iter()
.map(|name| {
let temp = self
.read_gpu_temp(name.as_str())
.map(|temp| temp as f64 / 1000f64);
(name, temp)
})
.collect()
}
fn read_gpu_temp(&self, name: &str) -> std::io::Result<u64> {
self.read(name)?.parse::<u64>().map_err(|e| {
log::warn!("Read from gpu monitor failed. Invalid temperature. {}", e);
IoErr::from(ErrorKind::InvalidInput)
})
}
#[inline]
pub(crate) fn name(&self) -> std::io::Result<String> {
self.read("name")
}
pub fn pwm_min(&mut self) -> u32 {
if self.pwm_min.is_none() {
self.pwm_min = Some(self.value_or_fallback(PULSE_WIDTH_MODULATION_MIN, 0));
};
self.pwm_min.unwrap_or_default()
}
pub fn pwm_max(&mut self) -> u32 {
if self.pwm_max.is_none() {
self.pwm_max = Some(self.value_or_fallback(PULSE_WIDTH_MODULATION_MAX, 255));
};
self.pwm_max.unwrap_or(255)
}
pub fn pwm(&self) -> std::io::Result<u32> {
self.read(PULSE_WIDTH_MODULATION)?.parse().map_err(|_e| {
log::warn!("Read from gpu monitor failed. Invalid pwm value");
IoErr::from(ErrorKind::InvalidInput)
})
}
pub fn is_fan_manual(&self) -> bool {
self.read(PULSE_WIDTH_MODULATION_ENABLED)
.map(|s| s.as_str() == "1")
.unwrap_or_default()
}
pub fn is_fan_automatic(&self) -> bool {
self.read(PULSE_WIDTH_MODULATION_ENABLED)
.map(|s| s.as_str() == "2")
.unwrap_or_default()
}
#[inline]
pub fn is_amd(&self) -> bool {
std::fs::read_to_string(format!("{}/{}/device/vendor", ROOT_DIR, self.card))
.map_err(|_| AmdFanError::FailedReadVendor)
.map(|vendor| vendor.trim() == "0x1002")
.unwrap_or_default()
}
#[inline]
pub fn name_is_amd(&self) -> bool {
self.name().ok().filter(|s| s.trim() == "amdgpu").is_some()
}
pub fn set_manual(&self) -> std::io::Result<()> {
self.write("pwm1_enable", 1)
}
pub fn set_automatic(&self) -> std::io::Result<()> {
self.write("pwm1_enable", 2)
}
pub fn set_pwm(&self, value: u32) -> std::io::Result<()> {
if self.is_fan_automatic() {
self.set_manual()?;
}
self.write("pwm1", value as u64)
}
pub fn set_speed(&mut self, speed: f64) -> std::io::Result<()> {
let min = self.pwm_min() as f64;
let max = self.pwm_max() as f64;
let pwm = linear_map(speed, 0f64, 100f64, min, max).round() as u32;
self.set_pwm(pwm)
}
fn read(&self, name: &str) -> std::io::Result<String> {
std::fs::read_to_string(self.path(name)).map(|s| String::from(s.trim()))
}
fn write(&self, name: &str, value: u64) -> std::io::Result<()> {
std::fs::write(self.path(name), format!("{}", value))?;
Ok(())
}
fn path(&self, name: &str) -> std::path::PathBuf {
self.mon_dir().join(name)
}
fn mon_dir(&self) -> std::path::PathBuf {
hw_mon_dir_path(&self.card, &self.name)
}
#[inline]
fn value_or_fallback<R: std::str::FromStr>(&self, name: &str, fallback: R) -> R {
self.read(name)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(fallback)
} }
} }
pub fn gpu_temp(&self) -> std::io::Result<f64> { fn load_temp_inputs(card: &Card, name: &str) -> Vec<String> {
let value = self.read("temp1_input")?.parse::<u64>().map_err(|_| { let dir = match std::fs::read_dir(hw_mon_dir_path(card, name)) {
log::warn!("Read from gpu monitor failed. Invalid temperature"); Ok(d) => d,
std::io::Error::from(ErrorKind::InvalidInput) _ => return vec![],
})?;
Ok(value as f64 / 1000f64)
}
fn name(&self) -> std::io::Result<String> {
self.read("name")
}
pub fn pwm_min(&mut self) -> u32 {
if self.pwm_min.is_none() {
self.pwm_min = Some(
self.read("pwm1_min")
.unwrap_or_default()
.parse()
.unwrap_or(0),
)
}; };
self.pwm_min.unwrap_or_default() dir.filter_map(|f| f.ok())
.filter_map(|f| {
f.file_name()
.to_str()
.filter(|s| s.starts_with("temp") && s.ends_with("_input"))
.map(String::from)
})
.collect()
} }
pub fn pwm_max(&mut self) -> u32 { #[inline]
if self.pwm_max.is_none() { fn hw_mon_dirs_path(card: &Card) -> std::path::PathBuf {
self.pwm_max = Some( std::path::PathBuf::new()
self.read("pwm1_max") .join(ROOT_DIR)
.unwrap_or_default() .join(card.to_string())
.parse()
.unwrap_or(255),
)
};
self.pwm_max.unwrap_or(255)
}
pub fn pwm(&self) -> std::io::Result<u32> {
self.read("pwm1")?.parse().map_err(|_e| {
log::warn!("Read from gpu monitor failed. Invalid pwm value");
std::io::Error::from(ErrorKind::InvalidInput)
})
}
pub fn pwm_speed(&self) -> std::io::Result<u64> {
self.read("pwm1")?.parse().map_err(|_e| {
log::warn!("Read from gpu monitor failed. Invalid fan speed");
std::io::Error::from(ErrorKind::InvalidInput)
})
}
pub fn is_fan_manual(&self) -> bool {
self.read("pwm1_enable")
.map(|s| s.as_str() == "1")
.unwrap_or_default()
}
pub fn is_fan_automatic(&self) -> bool {
self.read("pwm1_enable")
.map(|s| s.as_str() == "2")
.unwrap_or_default()
}
pub fn is_amd(&self) -> bool {
std::fs::read_to_string(format!("{}/{}/device/vendor", ROOT_DIR, self.card))
.map_err(|_| AmdFanError::FailedReadVendor)
.map(|vendor| vendor.trim() == "0x1002")
.unwrap_or_default()
}
pub fn set_manual(&self) -> std::io::Result<()> {
self.write("pwm1_enable", 1)
}
pub fn set_automatic(&self) -> std::io::Result<()> {
self.write("pwm1_enable", 2)
}
pub fn set_pwm(&self, value: u32) -> std::io::Result<()> {
if self.is_fan_automatic() {
self.set_manual()?;
}
self.write("pwm1", value as u64)
}
pub fn set_speed(&mut self, speed: f64) -> std::io::Result<()> {
let min = self.pwm_min() as f64;
let max = self.pwm_max() as f64;
let pwm = linear_map(speed, 0f64, 100f64, min, max).round() as u32;
self.set_pwm(pwm)
}
fn read(&self, name: &str) -> std::io::Result<String> {
let read_path = self.path(name);
std::fs::read_to_string(read_path).map(|s| s.trim().to_string())
}
fn write(&self, name: &str, value: u64) -> std::io::Result<()> {
std::fs::write(self.path(name), format!("{}", value))?;
Ok(())
}
fn path(&self, name: &str) -> std::path::PathBuf {
self.mon_dir().join(name)
}
fn mon_dir(&self) -> std::path::PathBuf {
std::path::Path::new(ROOT_DIR)
.join(self.card.to_string())
.join(HW_MON_DIR) .join(HW_MON_DIR)
.join(&self.name)
}
}
pub struct CardController {
pub hw_mon: HwMon,
pub last_temp: f64,
}
impl CardController {
pub fn new(card: Card) -> std::io::Result<Self> {
let name = Self::find_hw_mon(&card)?;
Ok(Self {
hw_mon: HwMon::new(&card, &name),
last_temp: 1_000f64,
})
} }
fn find_hw_mon(card: &Card) -> std::io::Result<String> { #[inline]
let read_path = format!("{}/{}/{}", ROOT_DIR, card.to_string(), HW_MON_DIR); fn hw_mon_dir_path(card: &Card, name: &str) -> std::path::PathBuf {
hw_mon_dirs_path(&card).join(name)
}
pub(crate) fn open_hw_mon(card: Card) -> std::io::Result<HwMon> {
let read_path = hw_mon_dirs_path(&card);
let entries = std::fs::read_dir(read_path)?; let entries = std::fs::read_dir(read_path)?;
entries let name = entries
.filter_map(|entry| entry.ok()) .filter_map(|entry| entry.ok())
.filter_map(|entry| { .filter_map(|entry| {
entry entry
.file_name() .file_name()
.as_os_str() .as_os_str()
.to_str() .to_str()
.map(|s| s.to_string()) .filter(|name| name.starts_with("hwmon"))
.map(String::from)
}) })
.find(|name| name.starts_with("hwmon")) .take(1)
.ok_or_else(|| std::io::Error::from(ErrorKind::NotFound)) .last()
.ok_or_else(|| IoErr::from(ErrorKind::NotFound))?;
Ok(HwMon::new(&card, &name))
} }
} }
@ -194,12 +276,6 @@ pub enum FanMode {
Automatic, Automatic,
} }
#[derive(Debug, Options)]
pub struct Monitor {
#[options(help = "Help message")]
help: bool,
}
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub struct Service { pub struct Service {
#[options(help = "Help message")] #[options(help = "Help message")]
@ -223,7 +299,7 @@ pub struct AvailableCards {
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub enum Command { pub enum Command {
#[options(help = "Print current temp and fan speed")] #[options(help = "Print current temp and fan speed")]
Monitor(Monitor), Monitor(monitor::Monitor),
#[options(help = "Set fan speed depends on GPU temperature")] #[options(help = "Set fan speed depends on GPU temperature")]
Service(Service), Service(Service),
#[options(help = "Switch to GPU automatic fan speed control")] #[options(help = "Switch to GPU automatic fan speed control")]
@ -244,6 +320,10 @@ pub struct Opts {
command: Option<Command>, command: Option<Command>,
} }
fn invalid_data() -> IoErr {
IoErr::from(ErrorKind::InvalidData)
}
fn read_cards() -> std::io::Result<Vec<Card>> { fn read_cards() -> std::io::Result<Vec<Card>> {
let mut cards = vec![]; let mut cards = vec![];
let entries = std::fs::read_dir(ROOT_DIR)?; let entries = std::fs::read_dir(ROOT_DIR)?;
@ -254,14 +334,11 @@ fn read_cards() -> std::io::Result<Vec<Card>> {
.file_name() .file_name()
.as_os_str() .as_os_str()
.to_str() .to_str()
.map(|s| s.to_string()) .map(String::from)
.ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData)) .ok_or_else(invalid_data)
}) })
.and_then(|file_name| { .and_then(|file_name| file_name.parse::<Card>().map_err(|_| invalid_data()))
file_name {
.parse::<Card>()
.map_err(|_| std::io::Error::from(ErrorKind::InvalidData))
}) {
Ok(card) => { Ok(card) => {
cards.push(card); cards.push(card);
} }
@ -271,47 +348,36 @@ fn read_cards() -> std::io::Result<Vec<Card>> {
Ok(cards) Ok(cards)
} }
fn controllers(config: &Config, filter: bool) -> std::io::Result<Vec<CardController>> { fn controllers(config: &Config, filter: bool) -> std::io::Result<Vec<HwMon>> {
Ok(read_cards()? Ok(read_cards()?
.into_iter() .into_iter()
.filter(|card| { .filter(|card| !filter || config.cards().iter().find(|name| **name == *card).is_some())
!filter .filter_map(|card| hw_mon::open_hw_mon(card).ok())
|| config .filter(|hw_mon| !filter || { hw_mon.is_amd() })
.cards() .filter(|hw_mon| !filter || hw_mon.name_is_amd())
.iter()
.find(|name| name.0 == card.0)
.is_some()
})
.map(|card| CardController::new(card).unwrap())
.filter(|controller| !filter || controller.hw_mon.is_amd())
.filter(|reader| {
!filter
|| reader
.hw_mon
.name()
.ok()
.filter(|s| s.as_str() == "amdgpu")
.is_some()
})
.collect()) .collect())
} }
fn service(config: Config) -> std::io::Result<()> { fn service(config: Config) -> std::io::Result<()> {
let mut controllers = controllers(&config, true)?; let mut controllers = controllers(&config, true)?;
let mut cache = std::collections::HashMap::new();
loop { loop {
for controller in controllers.iter_mut() { for hw_mon in controllers.iter_mut() {
let gpu_temp = controller.hw_mon.gpu_temp().unwrap_or_default(); let gpu_temp = hw_mon.max_gpu_temp().unwrap_or_default();
log::info!("Current {} temperature: {}", hw_mon.card, gpu_temp);
let last = *cache.entry(*hw_mon.card).or_insert(1_000f64);
let speed = config.speed_for_temp(gpu_temp); if ((last - 0.001f64)..(last + 0.001f64)).contains(&gpu_temp) {
let target_pwm = (speed as f32 * 2.55).round() as u32; log::info!("temperature didn't changed");
if controller.hw_mon.pwm_min() > target_pwm || controller.hw_mon.pwm_max() < target_pwm {
continue; continue;
} };
let speed = config.speed_for_temp(gpu_temp);
log::info!("Resolved speed {:.2}", speed);
if let Err(e) = controller.hw_mon.set_speed(target_pwm as f64) { if let Err(e) = hw_mon.set_speed(speed) {
log::error!("Failed to change speed to {}. {:?}", target_pwm, e); log::error!("Failed to change speed to {}. {:?}", speed, e);
} }
controller.last_temp = gpu_temp; cache.insert(*hw_mon.card, gpu_temp);
} }
std::thread::sleep(std::time::Duration::from_secs(4)); std::thread::sleep(std::time::Duration::from_secs(4));
} }
@ -321,28 +387,31 @@ fn change_mode(switcher: Switcher, mode: FanMode, config: Config) -> std::io::Re
let mut controllers = controllers(&config, true)?; let mut controllers = controllers(&config, true)?;
let cards = match switcher.card { let cards = match switcher.card {
Some(card_id) => match controllers.iter().position(|c| c.hw_mon.card.0 == card_id) { Some(card_id) => match controllers
.iter()
.position(|hw_mon| *hw_mon.card == card_id)
{
Some(card) => vec![controllers.remove(card)], Some(card) => vec![controllers.remove(card)],
None => { None => {
eprintln!("Card does not exists. Available cards: "); eprintln!("Card does not exists. Available cards: ");
for card in controllers { for hw_mon in controllers {
eprintln!(" * {}", card.hw_mon.card.0); eprintln!(" * {}", *hw_mon.card);
} }
return Err(std::io::Error::from(ErrorKind::NotFound)); return Err(IoErr::from(ErrorKind::NotFound));
} }
}, },
None => controllers, None => controllers,
}; };
for card in cards { for hw_mon in cards {
match mode { match mode {
FanMode::Automatic => { FanMode::Automatic => {
if let Err(e) = card.hw_mon.set_automatic() { if let Err(e) = hw_mon.set_automatic() {
log::error!("{:?}", e); log::error!("{:?}", e);
} }
} }
FanMode::Manual => { FanMode::Manual => {
if let Err(e) = card.hw_mon.set_manual() { if let Err(e) = hw_mon.set_manual() {
log::error!("{:?}", e); log::error!("{:?}", e);
} }
} }
@ -351,37 +420,114 @@ fn change_mode(switcher: Switcher, mode: FanMode, config: Config) -> std::io::Re
Ok(()) Ok(())
} }
fn monitor_cards(config: Config) -> std::io::Result<()> { mod monitor {
let mut controllers = controllers(&config, true)?; use crate::config::Config;
loop { use crate::{controllers, AmdFanError};
print!("{esc}[2J{esc}[1;1H", esc = 27 as char); use std::str::FromStr;
for card in controllers.iter_mut() {
println!( #[derive(Debug)]
"Card {:3} | Temp | RPM | MIN | MAX | PWM | %", pub enum MonitorFormat {
card.hw_mon.card.to_string().replace("card", "") Short,
); Verbose,
println!( }
" | {:>5.2} | {:>4} | {:>4} | {:>4} | {:>4} | {:>3}",
card.hw_mon.gpu_temp().unwrap_or_default(), impl Default for MonitorFormat {
card.hw_mon.pwm_speed().unwrap_or_default(), fn default() -> Self {
card.hw_mon.pwm_min(), MonitorFormat::Short
card.hw_mon.pwm_max(), }
card.hw_mon.pwm_speed().unwrap_or_default(), }
(card.hw_mon.pwm_speed().unwrap_or_default() as f32 / 2.55).round(),
); impl FromStr for MonitorFormat {
type Err = AmdFanError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"short" | "s" => Ok(MonitorFormat::Short),
"verbose" | "v" | "long" | "l" => Ok(MonitorFormat::Verbose),
_ => Err(AmdFanError::InvalidMonitorFormat),
}
}
}
#[derive(Debug, gumdrop::Options)]
pub struct Monitor {
#[options(help = "Help message")]
help: bool,
#[options(help = "Help message")]
format: MonitorFormat,
}
pub fn run(monitor: Monitor, config: Config) -> std::io::Result<()> {
match monitor.format {
MonitorFormat::Short => short(config),
MonitorFormat::Verbose => verbose(config),
}
}
pub fn verbose(config: Config) -> std::io::Result<()> {
let mut controllers = controllers(&config, true)?;
loop {
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
for hw_mon in controllers.iter_mut() {
println!("Card {:3}", hw_mon.card.to_string().replace("card", ""));
println!(" MIN | MAX | PWM | %");
println!(
" {:>4} | {:>4} | {:>6} | {:>3}",
hw_mon.pwm_min(),
hw_mon.pwm_max(),
hw_mon
.pwm()
.map_or_else(|_e| String::from("FAILED"), |f| f.to_string()),
(hw_mon.pwm().unwrap_or_default() as f32 / 2.55).round(),
);
println!();
println!(" Current temperature");
hw_mon.gpu_temp().into_iter().for_each(|(name, temp)| {
println!(
" {:6} | {:>9.2}",
name.replace("_input", ""),
temp.unwrap_or_default(),
);
});
}
println!();
println!("> PWM may be 0 even if RPM is higher");
std::thread::sleep(std::time::Duration::from_secs(4));
}
}
pub fn short(config: Config) -> std::io::Result<()> {
let mut controllers = controllers(&config, true)?;
loop {
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
for hw_mon in controllers.iter_mut() {
println!(
"Card {:3} | Temp | MIN | MAX | PWM | %",
hw_mon.card.to_string().replace("card", "")
);
println!(
" | {:>5.2} | {:>4} | {:>4} | {:>4} | {:>3}",
hw_mon.max_gpu_temp().unwrap_or_default(),
hw_mon.pwm_min(),
hw_mon.pwm_max(),
hw_mon.pwm().unwrap_or_default(),
(hw_mon.pwm().unwrap_or_default() as f32 / 2.55).round(),
);
}
std::thread::sleep(std::time::Duration::from_secs(4));
} }
std::thread::sleep(std::time::Duration::from_secs(4));
} }
} }
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
if std::fs::read("/etc/amdfand").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("/etc/amdfand")?; std::fs::create_dir_all(CONFIG_DIR)?;
} }
let config = load_config()?; let config = load_config()?;
log::set_max_level(config.log_level().into()); std::env::set_var("RUST_LOG", config.log_level().to_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();
@ -393,17 +539,17 @@ fn main() -> std::io::Result<()> {
match opts.command { match opts.command {
None => service(config), None => service(config),
Some(Command::Monitor(_)) => monitor_cards(config), Some(Command::Monitor(monitor)) => monitor::run(monitor, config),
Some(Command::Service(_)) => service(config), Some(Command::Service(_)) => service(config),
Some(Command::SetAutomatic(switcher)) => change_mode(switcher, FanMode::Automatic, config), Some(Command::SetAutomatic(switcher)) => change_mode(switcher, FanMode::Automatic, config),
Some(Command::SetManual(switcher)) => change_mode(switcher, FanMode::Manual, config), Some(Command::SetManual(switcher)) => change_mode(switcher, FanMode::Manual, config),
Some(Command::Available(_)) => { Some(Command::Available(_)) => {
println!("Available cards"); println!("Available cards");
controllers(&config, false)?.into_iter().for_each(|card| { controllers(&config, false)?.into_iter().for_each(|hw_mon| {
println!( println!(
" * {:6>} - {}", " * {:6>} - {}",
card.hw_mon.card.to_string(), hw_mon.card,
card.hw_mon.name().unwrap_or_default() hw_mon.name().unwrap_or_default()
); );
}); });
Ok(()) Ok(())