1use std::{error::Error, thread, time::Duration};
2
3use singe_nvml::{library::Library, types::TemperatureSensor};
4
5const GPU_INDEX: u32 = 0;
6const POLL_INTERVAL: Duration = Duration::from_secs(5);
7const POLLS: u32 = 1;
8const APPLY_CHANGES: bool = false;
9
10const CURVE: &[(u32, u32)] = &[(35, 25), (50, 40), (65, 60), (75, 80), (85, 100)];
11
12fn main() -> Result<(), Box<dyn Error>> {
13 let nvml = Library::create()?;
14 let device = nvml.device(GPU_INDEX)?;
15 let name = device.name()?;
16 let fans = device.num_fans()?;
17 let fan_limits = device.min_max_fan_speed()?;
18
19 println!("controlling GPU {GPU_INDEX}: {name}");
20 println!("fan speed range: {}%-{}%", fan_limits.min, fan_limits.max);
21 println!("curve: {CURVE:?}");
22 if !APPLY_CHANGES {
23 println!("dry run: set APPLY_CHANGES to true to write fan speeds");
24 }
25
26 for _ in 0..POLLS {
27 let temperature = device.temperature_reading(TemperatureSensor::Gpu)?;
29 let target_speed =
30 fan_speed_for_temperature(temperature).clamp(fan_limits.min, fan_limits.max);
31
32 for fan in 0..fans {
34 let current_speed = device.fan_speed(fan)?;
35 println!("fan {fan}: {temperature} C -> {target_speed}% (currently {current_speed}%)");
36
37 if APPLY_CHANGES && current_speed != target_speed {
38 device.set_fan_speed(fan, target_speed)?;
39 }
40 }
41
42 thread::sleep(POLL_INTERVAL);
43 }
44
45 Ok(())
46}
47
48fn fan_speed_for_temperature(temperature: u32) -> u32 {
49 let first = CURVE[0];
50 if temperature <= first.0 {
51 return first.1;
52 }
53
54 for window in CURVE.windows(2) {
55 let (low_temperature, low_speed) = window[0];
56 let (high_temperature, high_speed) = window[1];
57 if temperature <= high_temperature {
58 let span = high_temperature - low_temperature;
59 let offset = temperature - low_temperature;
60 let speed_delta = high_speed - low_speed;
61 return low_speed + speed_delta * offset / span;
62 }
63 }
64
65 CURVE[CURVE.len() - 1].1
66}