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    /// Sets the pulse width modulation fan level.
137    pub fn set_fan_pwm(&self, pwm: u8) -> Result<()> {
138        self.write_file("pwm1", pwm.to_string())
139    }
140
141    /// Gets the current fan speed in RPM.
142    pub fn get_fan_current(&self) -> Result<u32> {
143        let s = self.read_file("fan1_input")?;
144        s.parse().context("Unexpected fan1_input (driver bug?)")
145    }
146
147    /// Gets the maximum possible fan speed in RPM.
148    pub fn get_fan_max(&self) -> Result<u32> {
149        let s = self.read_file("fan1_max")?;
150        s.parse().context("Unexpected fan1_max (driver bug?)")
151    }
152
153    /// Gets the minimum possible fan speed in RPM.
154    pub fn get_fan_min(&self) -> Result<u32> {
155        let s = self.read_file("fan1_min")?;
156        s.parse().context("Unexpected fan1_min (driver bug?)")
157    }
158
159    /// Gets the currently desired fan speed in RPM.
160    pub fn get_fan_target(&self) -> Result<u32> {
161        self.read_file("fan1_target")
162            .map(|s| s.parse().expect("Unexpected fan1_target (driver bug?)"))
163    }
164
165    /// Sets the desired fan speed in RPM.
166    pub fn set_fan_target(&self, target: u32) -> Result<()> {
167        self.write_file("fan1_target", target.to_string())?;
168        Ok(())
169    }
170
171    /// Gets the pulse width modulation control method.
172    pub fn get_fan_control_method(&self) -> Result<FanControlMethod> {
173        self.read_file("pwm1_enable").and_then(|pwm1_enable| {
174            let repr = pwm1_enable
175                .parse()
176                .context("Unexpected pwm1_enable (driver bug?)")?;
177            FanControlMethod::from_repr(repr).ok_or_else(|| {
178                ErrorKind::Unsupported(
179                    "Unexpected pwm1_enable (driver bug or unsupported?)".to_owned(),
180                )
181                .into()
182            })
183        })
184    }
185
186    /// Sets the fan control method (`pwm1_enable`).
187    pub fn set_fan_control_method(&self, method: FanControlMethod) -> Result<()> {
188        let repr = method as u32;
189        self.write_file("pwm1_enable", repr.to_string())
190    }
191
192    /// Gets the GPU voltage in millivolts.
193    pub fn get_gpu_voltage(&self) -> Result<u64> {
194        self.read_file_parsed("in0_input")
195    }
196
197    /// Gets the north bridge voltage in millivolts.
198    pub fn get_northbridge_voltage(&self) -> Result<u64> {
199        self.read_file_parsed("in1_input")
200    }
201}
202
203impl SysFS for HwMon {
204    fn get_path(&self) -> &Path {
205        &self.path
206    }
207}
208
209/// Temperature reported by the GPU.
210#[derive(Debug, Clone, Copy, PartialEq)]
211#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
212pub struct Temperature {
213    /// The current temperature.
214    pub current: Option<f32>,
215    /// The maximum allowed temperature.
216    pub crit: Option<f32>,
217    /// The minimum allowed temperature.
218    pub crit_hyst: Option<f32>,
219}
220
221/// The way the fan speed is controlled.
222#[derive(Debug, Clone, Copy)]
223#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
224#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
225pub enum FanControlMethod {
226    /// No fan speed control.
227    None = 0,
228    /// Manual fan speed control via the PWM interface.
229    Manual = 1,
230    /// Automatic fan speed control (by the kernel).
231    Auto = 2,
232}
233
234impl FanControlMethod {
235    /// Create [FanControlMethod] from a digit in the SysFS.
236    pub fn from_repr(repr: u32) -> Option<Self> {
237        match repr {
238            0 => Some(Self::None),
239            1 => Some(Self::Manual),
240            2 => Some(Self::Auto),
241            _ => None,
242        }
243    }
244}