amdgpu_sysfs/
hw_mon.rs

1//! Hardware monitoring
2use crate::{
3    error::{ErrorContext, ErrorKind},
4    sysfs::SysFS,
5    Result,
6};
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9use std::{
10    collections::HashMap,
11    path::{Path, PathBuf},
12};
13
14/// Represents a hardware monitor.
15/// Hardware monitors are used to report real-time information about the device, such as temperatures and power usage.
16#[derive(Clone, Debug)]
17pub struct HwMon {
18    path: PathBuf,
19}
20
21impl HwMon {
22    /// Most of the time you may want to access `HwMon`s through the
23    /// [GpuHandle](../gpu_handle/struct.GpuHandle.html) they're bound to.
24    pub fn new_from_path(path: PathBuf) -> Result<Self> {
25        let hw_mon = Self { path };
26        hw_mon.read_file("name")?;
27        Ok(hw_mon)
28    }
29
30    fn read_temp(&self, file: &str) -> Result<f32> {
31        let temp_str = self.read_file(file)?;
32        Ok(temp_str
33            .trim()
34            .parse::<f32>()
35            .context("Invalid temperature value (driver bug?)")?
36            / 1000.0)
37    }
38
39    /// Returns a HashMap of temperatures(in degress celsius), indexed by the labels (example: "edge").
40    pub fn get_temps(&self) -> HashMap<String, Temperature> {
41        let mut temps = HashMap::new();
42
43        let mut i = 1;
44
45        while let Ok(current) = self.read_temp(&format!("temp{i}_input")) {
46            let temperature = Temperature {
47                current: Some(current),
48                crit: self.read_temp(&format!("temp{i}_crit")).ok(),
49                crit_hyst: self.read_temp(&format!("temp{i}_crit_hyst")).ok(),
50            };
51
52            match self.read_file(format!("temp{i}_label")) {
53                Ok(label) => {
54                    temps.insert(label, temperature);
55                }
56                Err(_) => {
57                    temps.insert(i.to_string(), temperature);
58                    break;
59                }
60            }
61
62            i += 1;
63        }
64
65        temps
66    }
67
68    fn read_clockspeed(&self, file: &str) -> Result<u64> {
69        let raw_clockspeed = self.read_file(file)?;
70        Ok(raw_clockspeed
71            .parse::<u64>()
72            .context("Unexpected GPU clockspeed (driver bug?)")?
73            / 1000000)
74    }
75
76    /// Gets the current GFX/compute clockspeed in MHz.
77    pub fn get_gpu_clockspeed(&self) -> Result<u64> {
78        self.read_clockspeed("freq1_input")
79    }
80
81    /// Gets the current memory clockspeed in MHz.
82    pub fn get_vram_clockspeed(&self) -> Result<u64> {
83        self.read_clockspeed("freq2_input")
84    }
85
86    fn read_power(&self, file: &str) -> Result<f64> {
87        let raw_power = self.read_file(file)?;
88        Ok(raw_power
89            .parse::<f64>()
90            .context("Unexpected power value (driver bug?)")?
91            / 1000000.0)
92    }
93
94    /// Gets the average power (currently) used by the GPU in watts.
95    pub fn get_power_average(&self) -> Result<f64> {
96        self.read_power("power1_average")
97    }
98
99    /// Gets the instantaneous power (currently) used by the GPU in watts.
100    pub fn get_power_input(&self) -> Result<f64> {
101        self.read_power("power1_input")
102    }
103
104    /// Gets the current power cap of the GPU in watts.
105    pub fn get_power_cap(&self) -> Result<f64> {
106        self.read_power("power1_cap")
107    }
108
109    /// Sets the current power cap of the GPU in watts.
110    pub fn set_power_cap(&self, cap: f64) -> Result<()> {
111        let value = (cap * 1000000.0).round() as i64;
112        self.write_file("power1_cap", value.to_string())
113    }
114
115    /// Gets the maximum possible power cap for the GPU in watts. If overclocking is disabled, this is probably the same as the default cap.
116    pub fn get_power_cap_max(&self) -> Result<f64> {
117        self.read_power("power1_cap_max")
118    }
119
120    /// Gets the minimum possible power cap for the GPU in watts.
121    pub fn get_power_cap_min(&self) -> Result<f64> {
122        self.read_power("power1_cap_min")
123    }
124
125    /// Gets the default power cap for the GPU in watts.
126    pub fn get_power_cap_default(&self) -> Result<f64> {
127        self.read_power("power1_cap_default")
128    }
129
130    /// Gets the pulse width modulation fan level.
131    pub fn get_fan_pwm(&self) -> Result<u8> {
132        let pwm = self.read_file("pwm1")?;
133        pwm.parse().context("Unexpected PWM (driver bug?)")
134    }
135
136    /// Gets the minimum pulse width modulation fan level.
137    pub fn get_fan_min_pwm(&self) -> Result<u8> {
138        let pwm = self.read_file("pwm1_min")?;
139        pwm.parse().context("Unexpected PWM (driver bug?)")
140    }
141
142    /// Gets the maximum pulse width modulation fan level.
143    pub fn get_fan_max_pwm(&self) -> Result<u8> {
144        let pwm = self.read_file("pwm1_max")?;
145        pwm.parse().context("Unexpected PWM (driver bug?)")
146    }
147
148    /// Sets the pulse width modulation fan level.
149    pub fn set_fan_pwm(&self, pwm: u8) -> Result<()> {
150        self.write_file("pwm1", pwm.to_string())
151    }
152
153    /// Gets the current fan speed in RPM.
154    pub fn get_fan_current(&self) -> Result<u32> {
155        let s = self.read_file("fan1_input")?;
156        s.parse().context("Unexpected fan1_input (driver bug?)")
157    }
158
159    /// Gets the maximum possible fan speed in RPM.
160    pub fn get_fan_max(&self) -> Result<u32> {
161        let s = self.read_file("fan1_max")?;
162        s.parse().context("Unexpected fan1_max (driver bug?)")
163    }
164
165    /// Gets the minimum possible fan speed in RPM.
166    pub fn get_fan_min(&self) -> Result<u32> {
167        let s = self.read_file("fan1_min")?;
168        s.parse().context("Unexpected fan1_min (driver bug?)")
169    }
170
171    /// Gets the currently desired fan speed in RPM.
172    pub fn get_fan_target(&self) -> Result<u32> {
173        self.read_file("fan1_target")
174            .map(|s| s.parse().expect("Unexpected fan1_target (driver bug?)"))
175    }
176
177    /// Sets the desired fan speed in RPM.
178    pub fn set_fan_target(&self, target: u32) -> Result<()> {
179        self.write_file("fan1_target", target.to_string())?;
180        Ok(())
181    }
182
183    /// Gets the pulse width modulation control method.
184    pub fn get_fan_control_method(&self) -> Result<FanControlMethod> {
185        self.read_file("pwm1_enable").and_then(|pwm1_enable| {
186            let repr = pwm1_enable
187                .parse()
188                .context("Unexpected pwm1_enable (driver bug?)")?;
189            FanControlMethod::from_repr(repr).ok_or_else(|| {
190                ErrorKind::Unsupported(
191                    "Unexpected pwm1_enable (driver bug or unsupported?)".to_owned(),
192                )
193                .into()
194            })
195        })
196    }
197
198    /// Sets the fan control method (`pwm1_enable`).
199    pub fn set_fan_control_method(&self, method: FanControlMethod) -> Result<()> {
200        let repr = method as u32;
201        self.write_file("pwm1_enable", repr.to_string())
202    }
203
204    /// Gets the GPU voltage in millivolts.
205    pub fn get_gpu_voltage(&self) -> Result<u64> {
206        self.read_file_parsed("in0_input")
207    }
208
209    /// Gets the north bridge voltage in millivolts.
210    pub fn get_northbridge_voltage(&self) -> Result<u64> {
211        self.read_file_parsed("in1_input")
212    }
213}
214
215impl SysFS for HwMon {
216    fn get_path(&self) -> &Path {
217        &self.path
218    }
219}
220
221/// Temperature reported by the GPU.
222#[derive(Debug, Clone, Copy, PartialEq)]
223#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
224pub struct Temperature {
225    /// The current temperature.
226    pub current: Option<f32>,
227    /// The maximum allowed temperature.
228    pub crit: Option<f32>,
229    /// The minimum allowed temperature.
230    pub crit_hyst: Option<f32>,
231}
232
233/// The way the fan speed is controlled.
234#[derive(Debug, Clone, Copy)]
235#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
236#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
237pub enum FanControlMethod {
238    /// No fan speed control.
239    None = 0,
240    /// Manual fan speed control via the PWM interface.
241    Manual = 1,
242    /// Automatic fan speed control (by the kernel).
243    Auto = 2,
244}
245
246impl FanControlMethod {
247    /// Create [FanControlMethod] from a digit in the SysFS.
248    pub fn from_repr(repr: u32) -> Option<Self> {
249        match repr {
250            0 => Some(Self::None),
251            1 => Some(Self::Manual),
252            2 => Some(Self::Auto),
253            _ => None,
254        }
255    }
256}