use amdgpu::utils::linear_map;
use amdgpu::{LogLevel, TempInput};
use std::io::ErrorKind;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct MatrixPoint {
pub temp: f64,
pub speed: f64,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Config {
cards: Option<Vec<String>>,
log_level: LogLevel,
speed_matrix: Vec<MatrixPoint>,
temp_input: Option<TempInput>,
}
impl Config {
#[deprecated(
since = "1.0.6",
note = "Multi-card used is halted until we will have PC with multiple AMD GPU"
)]
pub fn cards(&self) -> Option<&Vec<String>> {
self.cards.as_ref()
}
pub fn speed_for_temp(&self, temp: f64) -> f64 {
let idx = match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
Some(idx) => idx,
_ => return self.min_speed(),
};
if idx == self.speed_matrix.len() - 1 {
return self.max_speed();
}
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 {
self.log_level
}
pub fn temp_input(&self) -> Option<&TempInput> {
self.temp_input.as_ref()
}
fn min_speed(&self) -> f64 {
self.speed_matrix.first().map(|p| p.speed).unwrap_or(0f64)
}
fn max_speed(&self) -> f64 {
self.speed_matrix.last().map(|p| p.speed).unwrap_or(100f64)
}
}
impl Default for Config {
fn default() -> Self {
Self {
#[allow(deprecated)]
cards: None,
log_level: LogLevel::Error,
speed_matrix: vec![
MatrixPoint {
temp: 4f64,
speed: 4f64,
},
MatrixPoint {
temp: 30f64,
speed: 33f64,
},
MatrixPoint {
temp: 45f64,
speed: 50f64,
},
MatrixPoint {
temp: 60f64,
speed: 66f64,
},
MatrixPoint {
temp: 65f64,
speed: 69f64,
},
MatrixPoint {
temp: 70f64,
speed: 75f64,
},
MatrixPoint {
temp: 75f64,
speed: 89f64,
},
MatrixPoint {
temp: 80f64,
speed: 100f64,
},
],
temp_input: Some(TempInput(1)),
}
}
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum ConfigError {
#[error("Fan speed {value:?} for config entry {index:} is too low (minimal value is 0.0)")]
FanSpeedTooLow { value: f64, index: usize },
#[error("Fan speed {value:?} for config entry {index:} is too high (maximal value is 100.0)")]
FanSpeedTooHigh { value: f64, index: usize },
#[error(
"Fan speed {current:?} for config entry {index} is lower than previous value {last:?}. Entries must be sorted"
)]
UnsortedFanSpeed {
current: f64,
index: usize,
last: f64,
},
#[error(
"Fan temperature {current:?} for config entry {index} is lower than previous value {last:?}. Entries must be sorted"
)]
UnsortedFanTemp {
current: f64,
index: usize,
last: f64,
},
}
pub fn load_config(config_path: &str) -> crate::Result<Config> {
let config = match std::fs::read_to_string(config_path) {
Ok(s) => toml::from_str(&s).unwrap(),
Err(e) if e.kind() == ErrorKind::NotFound => {
let config = Config::default();
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
config
}
Err(e) => {
log::error!("{:?}", e);
panic!();
}
};
let mut last_point: Option<&MatrixPoint> = None;
for (index, matrix_point) in config.speed_matrix.iter().enumerate() {
if matrix_point.speed < 0f64 {
log::error!("Fan speed can't be below 0.0 found {}", matrix_point.speed);
return Err(ConfigError::FanSpeedTooLow {
value: matrix_point.speed,
index,
}
.into());
}
if matrix_point.speed > 100f64 {
log::error!(
"Fan speed can't be above 100.0 found {}",
matrix_point.speed
);
return Err(ConfigError::FanSpeedTooHigh {
value: matrix_point.speed,
index,
}
.into());
}
if let Some(last_point) = last_point {
if matrix_point.speed < last_point.speed {
log::error!(
"Curve fan speeds should be monotonically increasing, found {} then {}",
last_point.speed,
matrix_point.speed
);
return Err(ConfigError::UnsortedFanSpeed {
current: matrix_point.speed,
last: last_point.speed,
index,
}
.into());
}
if matrix_point.temp < last_point.temp {
log::error!(
"Curve fan temps should be monotonically increasing, found {} then {}",
last_point.temp,
matrix_point.temp
);
return Err(ConfigError::UnsortedFanTemp {
current: matrix_point.temp,
last: last_point.temp,
index,
}
.into());
}
}
last_point = Some(matrix_point)
}
Ok(config)
}
#[cfg(test)]
mod parse_config {
use crate::config::TempInput;
use amdgpu::{AmdGpuError, Card};
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Debug)]
pub struct Foo {
card: Card,
}
#[test]
fn parse_card0() {
assert_eq!("card0".parse::<Card>(), Ok(Card(0)))
}
#[test]
fn parse_card1() {
assert_eq!("card1".parse::<Card>(), Ok(Card(1)))
}
#[test]
fn toml_card0() {
assert_eq!(toml::from_str("card = 'card0'"), Ok(Foo { card: Card(0) }))
}
#[test]
fn parse_invalid_temp_input() {
assert_eq!(
"".parse::<TempInput>(),
Err(AmdGpuError::InvalidTempInput("".to_string()))
);
assert_eq!(
"12".parse::<TempInput>(),
Err(AmdGpuError::InvalidTempInput("12".to_string()))
);
assert_eq!(
"temp12".parse::<TempInput>(),
Err(AmdGpuError::InvalidTempInput("temp12".to_string()))
);
assert_eq!(
"12_input".parse::<TempInput>(),
Err(AmdGpuError::InvalidTempInput("12_input".to_string()))
);
assert_eq!(
"temp_12_input".parse::<TempInput>(),
Err(AmdGpuError::InvalidTempInput("temp_12_input".to_string()))
);
}
#[test]
fn parse_valid_temp_input() {
assert_eq!("temp12_input".parse::<TempInput>(), Ok(TempInput(12)));
}
}
#[cfg(test)]
mod speed_for_temp {
use super::*;
#[test]
fn below_minimal() {
let config = Config::default();
assert_eq!(config.speed_for_temp(1f64), 4f64);
}
#[test]
fn minimal() {
let config = Config::default();
assert_eq!(config.speed_for_temp(4f64), 4f64);
}
#[test]
fn between_3_and_4_temp_46() {
let config = Config::default();
assert_eq!(config.speed_for_temp(46f64).round(), 51f64);
}
#[test]
fn between_3_and_4_temp_58() {
let config = Config::default();
assert_eq!(config.speed_for_temp(58f64).round(), 64f64);
}
#[test]
fn between_3_and_4_temp_59() {
let config = Config::default();
assert_eq!(config.speed_for_temp(59f64).round(), 65f64);
}
#[test]
fn average() {
let config = Config::default();
assert_eq!(config.speed_for_temp(60f64), 66f64);
}
#[test]
fn max() {
let config = Config::default();
assert_eq!(config.speed_for_temp(80f64), 100f64);
}
#[test]
fn above_max() {
let config = Config::default();
assert_eq!(config.speed_for_temp(160f64), 100f64);
}
}