Merge branch 'new-script' into 'main'
convert fan script from bash to rust See merge request c4181/server-fan-controller!1
This commit is contained in:
commit
4bbd766bd0
5 changed files with 1567 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/target
|
||||||
|
config.toml
|
||||||
|
|
||||||
|
# Jetbrains
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
1309
Cargo.lock
generated
Normal file
1309
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "server-fan-controller"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
confy = "0.6.1"
|
||||||
|
nvml-wrapper = "0.10.0"
|
||||||
|
rust-ipmi = "0.1.1"
|
||||||
|
sysinfo = "0.31.2"
|
||||||
|
serde = { version = "1.0.208", features = ["derive"] }
|
||||||
|
log4rs = "1.3.0"
|
||||||
|
log = "0.4.22"
|
||||||
9
log4rs.yaml
Normal file
9
log4rs.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
appenders:
|
||||||
|
stdout:
|
||||||
|
kind: console
|
||||||
|
# encoder:
|
||||||
|
# kind: json
|
||||||
|
root:
|
||||||
|
level: info
|
||||||
|
appenders:
|
||||||
|
- stdout
|
||||||
228
src/main.rs
Normal file
228
src/main.rs
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
use log::{debug, error, info, trace};
|
||||||
|
use nvml_wrapper::enum_wrappers::device::TemperatureSensor;
|
||||||
|
use nvml_wrapper::Nvml;
|
||||||
|
use rust_ipmi::{IPMIClient, IPMIClientError};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{env, thread};
|
||||||
|
use sysinfo::Components;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let config_path = if args.len() > 1 { Path::new(&args[1] ) } else { Path::new("config.toml") };
|
||||||
|
let cfg: AppConfig = confy::load_path(Path::new(config_path)).expect("Couldn't read config");
|
||||||
|
let sleep_duration = Duration::from_secs(cfg.run_interval);
|
||||||
|
match Path::new("log4rs.yaml").exists() {
|
||||||
|
true => log4rs::init_file("log4rs.yaml", Default::default()).unwrap(),
|
||||||
|
false => println!("No log4rs.yaml file found. Logging will not be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let max_core_temp = get_max_core_temp();
|
||||||
|
let cpu_speed = calculate_cpu_curve_speed(max_core_temp as i32);
|
||||||
|
|
||||||
|
let max_gpu_temp = if cfg.monitor_gpus { get_max_gpu_temp() } else { 0 };
|
||||||
|
let gpu_speed = calculate_gpu_curve_speed(max_gpu_temp);
|
||||||
|
|
||||||
|
let fan_speed = max(cpu_speed, gpu_speed);
|
||||||
|
info!("Setting fan speed to {}", fan_speed);
|
||||||
|
match set_fan_speed(fan_speed, &cfg) {
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("Successfully set fan speed");
|
||||||
|
thread::sleep(sleep_duration);
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
thread::sleep(Duration::from_secs(3))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_cpu_curve_speed(temp: i32) -> FanSpeed {
|
||||||
|
let fan_speed = match temp {
|
||||||
|
t if t <= 35 => {
|
||||||
|
trace!("Setting fan speeds to 4.3k & 3.0k RPM");
|
||||||
|
FanSpeed::VeryLow
|
||||||
|
}
|
||||||
|
t if t <= 45 => {
|
||||||
|
trace!("Setting fan speeds to 5.3k & 3.4k RPM");
|
||||||
|
FanSpeed::Low
|
||||||
|
}
|
||||||
|
t if t <= 55 => {
|
||||||
|
trace!("Setting fan speeds to 5.6k & 3.8k RPM");
|
||||||
|
FanSpeed::Moderate
|
||||||
|
}
|
||||||
|
t if t <= 65 => {
|
||||||
|
trace!("Setting fan speeds to 5.9k & 4.1k RPM");
|
||||||
|
FanSpeed::High
|
||||||
|
}
|
||||||
|
t if t <= 75 => {
|
||||||
|
trace!("Setting fan speeds to 6.2k & 4.3k RPM");
|
||||||
|
FanSpeed::VeryHigh
|
||||||
|
}
|
||||||
|
t if t <= 85 => {
|
||||||
|
trace!("Setting fan speeds to 6.6k & 4.6k RPM");
|
||||||
|
FanSpeed::BeyondVeryHigh
|
||||||
|
}
|
||||||
|
t if t <= 95 => {
|
||||||
|
trace!("Setting fan speeds to 7.5k & 5.0k RPM");
|
||||||
|
FanSpeed::OneStepFromHell
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
trace!("Setting fan speeds to 8.0k & 5.5k RPM");
|
||||||
|
FanSpeed::MaxLoad
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fan_speed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_gpu_curve_speed(temp: u32) -> FanSpeed {
|
||||||
|
let fan_speed = match temp {
|
||||||
|
t if t <= 35 => {
|
||||||
|
FanSpeed::VeryLow
|
||||||
|
}
|
||||||
|
t if t <= 45 => {
|
||||||
|
FanSpeed::Low
|
||||||
|
}
|
||||||
|
t if t <= 55 => {
|
||||||
|
FanSpeed::Moderate
|
||||||
|
}
|
||||||
|
t if t <= 65 => {
|
||||||
|
FanSpeed::VeryHigh
|
||||||
|
}
|
||||||
|
t if t <= 75 => {
|
||||||
|
FanSpeed::BeyondVeryHigh
|
||||||
|
}
|
||||||
|
t if t <= 80 => {
|
||||||
|
FanSpeed::OneStepFromHell
|
||||||
|
}
|
||||||
|
t if t <= 85 => {
|
||||||
|
FanSpeed::MaxLoad
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
FanSpeed::GpuHigh
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fan_speed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_fan_speed(speed: FanSpeed, cfg: &AppConfig) -> Result<bool, IPMIClientError> {
|
||||||
|
let mut client: IPMIClient = match IPMIClient::new(&cfg.ipmi_config.ip) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to construct IPMI Client because: {e}");
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// establish a session with the BMC using the credentials specified
|
||||||
|
match client.establish_connection(&cfg.ipmi_config.user, &cfg.ipmi_config.password) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to establish IPMI connection because: {e}");
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// send a command to the BMC using raw values
|
||||||
|
match client.send_raw_request(0x30, 0x30, Some(vec![0x02, 0xff, speed as u8])) {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to send IPMI request because: {e}");
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_max_core_temp() -> f32 {
|
||||||
|
let components = Components::new_with_refreshed_list();
|
||||||
|
|
||||||
|
let mut max_temp:f32 = 0.0;
|
||||||
|
for component in components.iter() {
|
||||||
|
if component.label().starts_with("coretemp Core") {
|
||||||
|
if component.temperature() > max_temp {
|
||||||
|
max_temp = component.temperature();
|
||||||
|
}
|
||||||
|
} else if component.label().contains("coretemp Package id 0") {
|
||||||
|
info!("CPU Package 0 is {}C", component.temperature())
|
||||||
|
} else if component.label().starts_with("coretemp Package id 1") {
|
||||||
|
info!("CPU Package 1 is {}C", component.temperature())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max_temp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_max_gpu_temp() -> u32 {
|
||||||
|
let nvml = Nvml::init().unwrap();
|
||||||
|
|
||||||
|
let count = nvml.device_count().unwrap();
|
||||||
|
let mut max_temp:u32 = 0;
|
||||||
|
for index in 0..count {
|
||||||
|
let device = nvml.device_by_index(index).unwrap();
|
||||||
|
let name = device.name().unwrap();
|
||||||
|
let temp = device.temperature(TemperatureSensor::Gpu).unwrap();
|
||||||
|
|
||||||
|
if temp > max_temp {
|
||||||
|
max_temp = temp;
|
||||||
|
}
|
||||||
|
info!("GPU {} is {}C", name, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
max_temp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum FanSpeed {
|
||||||
|
VeryLow = 24, // 0x18 in decimal
|
||||||
|
Low = 29, // 0x1d in decimal
|
||||||
|
Moderate = 31, // 0x1f in decimal
|
||||||
|
High = 32, // 0x20 in decimal
|
||||||
|
VeryHigh = 34, // 0x22 in decimal
|
||||||
|
BeyondVeryHigh = 36, // 0x24 in decimal
|
||||||
|
OneStepFromHell = 38, // 0x26 in decimal
|
||||||
|
MaxLoad = 43, // 0x2b in decimal
|
||||||
|
GpuHigh = 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FanSpeed {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}%, {:?}", self.value(), self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FanSpeed {
|
||||||
|
fn value(&self) -> u8 {
|
||||||
|
unsafe { *(self as *const Self as *const u8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_hex(&self) -> String {
|
||||||
|
format!("{:#x}", self.value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct AppConfig {
|
||||||
|
monitor_gpus: bool,
|
||||||
|
run_interval: u64,
|
||||||
|
ipmi_config: IpmiConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
panic!("Could not find config file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct IpmiConfig {
|
||||||
|
ip: String,
|
||||||
|
user: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue