Skip to main content

nvml_wrapper/
unit.rs

1use crate::device::Device;
2use crate::enum_wrappers::unit::LedColor;
3use crate::enums::unit::{LedState, TemperatureReading};
4use crate::error::{nvml_sym, nvml_try, nvml_try_count, NvmlError};
5use crate::ffi::bindings::*;
6use crate::struct_wrappers::unit::{FansInfo, PsuInfo, UnitInfo};
7use crate::Nvml;
8use static_assertions::assert_impl_all;
9use std::mem;
10use std::{convert::TryFrom, os::raw::c_uint};
11
12/**
13Struct that represents a unit.
14
15Obtain a `Unit` with the various methods available to you on the `Nvml`
16struct.
17
18Lifetimes are used to enforce that each `Unit` instance cannot be used after
19the `Nvml` instance it was obtained from is dropped:
20
21```compile_fail
22use nvml_wrapper::Nvml;
23# use nvml_wrapper::error::*;
24
25# fn main() -> Result<(), NvmlError> {
26let nvml = Nvml::init()?;
27let unit = nvml.unit_by_index(0)?;
28
29drop(nvml);
30
31// This won't compile
32let unit_devices = unit.devices()?;
33# Ok(())
34# }
35```
36
37Note that I cannot test any `Unit` methods myself as I do not have access to
38such hardware. **Test the functionality in this module before you use it**.
39*/
40#[derive(Debug)]
41pub struct Unit<'nvml> {
42    unit: nvmlUnit_t,
43    nvml: &'nvml Nvml,
44}
45
46unsafe impl<'nvml> Send for Unit<'nvml> {}
47unsafe impl<'nvml> Sync for Unit<'nvml> {}
48
49assert_impl_all!(Unit: Send, Sync);
50
51impl<'nvml> Unit<'nvml> {
52    /**
53    Create a new `Unit` wrapper.
54
55    You will most likely never need to call this; see the methods available to you
56    on the `Nvml` struct to get one.
57
58    # Safety
59
60    It is your responsibility to ensure that the given `nvmlUnit_t` pointer
61    is valid.
62    */
63    // Clippy bug, see https://github.com/rust-lang/rust-clippy/issues/5593
64    #[allow(clippy::missing_safety_doc)]
65    pub unsafe fn new(unit: nvmlUnit_t, nvml: &'nvml Nvml) -> Self {
66        Self { unit, nvml }
67    }
68
69    /// Access the `NVML` reference this struct wraps
70    pub fn nvml(&self) -> &'nvml Nvml {
71        self.nvml
72    }
73
74    /// Get the raw unit handle contained in this struct
75    ///
76    /// Sometimes necessary for C interop.
77    ///
78    /// # Safety
79    ///
80    /// This is unsafe to prevent it from being used without care.
81    pub unsafe fn handle(&self) -> nvmlUnit_t {
82        self.unit
83    }
84
85    /**
86    Gets the set of GPU devices that are attached to this `Unit`.
87
88    **I do not have the hardware to test this call. Verify for yourself that it
89    works before you use it**. If it works, please let me know; if it doesn't,
90    I would love a PR. If NVML is sane this should work, but NVIDIA's docs
91    on this call are _anything_ but clear.
92
93    # Errors
94
95    * `Uninitialized`, if the library has not been successfully initialized
96    * `InvalidArg`, if the unit is invalid
97    * `Unknown`, on any unexpected error
98
99    # Device Support
100
101    For S-class products.
102    */
103    // Checked against local
104    // Tested
105    #[doc(alias = "nvmlUnitGetDevices")]
106    pub fn devices(&self) -> Result<Vec<Device<'_>>, NvmlError> {
107        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetDevices.as_ref())?;
108
109        unsafe {
110            let mut count: c_uint = match self.device_count()? {
111                0 => return Ok(vec![]),
112                value => value,
113            };
114            let mut devices: Vec<nvmlDevice_t> = vec![mem::zeroed(); count as usize];
115
116            nvml_try(sym(self.unit, &mut count, devices.as_mut_ptr()))?;
117
118            Ok(devices
119                .into_iter()
120                .map(|d| Device::new(d, self.nvml))
121                .collect())
122        }
123    }
124
125    /**
126    Gets the count of GPU devices that are attached to this `Unit`.
127
128    **I do not have the hardware to test this call. Verify for yourself that it
129    works before you use it**. If it works, please let me know; if it doesn't,
130    I would love a PR. If NVML is sane this should work, but NVIDIA's docs
131    on this call are _anything_ but clear.
132
133    # Errors
134
135    * `Uninitialized`, if the library has not been successfully initialized
136    * `InvalidArg`, if the unit is invalid
137    * `Unknown`, on any unexpected error
138
139    # Device Support
140
141    For S-class products.
142    */
143    // Tested as part of the above
144    #[doc(alias = "nvmlUnitGetDevices")]
145    pub fn device_count(&self) -> Result<u32, NvmlError> {
146        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetDevices.as_ref())?;
147
148        /*
149        From the docs:
150        deviceCount
151          Reference in which to provide the devices array size,
152          and to return the number of attached GPU devices
153        */
154        let mut count: c_uint = 0;
155        unsafe {
156            nvml_try_count(sym(self.unit, &mut count, std::ptr::null_mut()))?;
157        }
158        Ok(count)
159    }
160
161    /**
162    Gets fan information for this `Unit` (fan count and state + speed for each).
163
164    # Errors
165
166    * `Uninitialized`, if the library has not been successfully initialized
167    * `InvalidArg`, if the unit is invalid
168    * `NotSupported`, if this is not an S-class product
169    * `UnexpectedVariant`, for which you can read the docs for
170    * `Unknown`, on any unexpected error
171
172    # Device Support
173
174    For S-class products.
175    */
176    // Checked against local
177    // Tested
178    #[doc(alias = "nvmlUnitGetFanSpeedInfo")]
179    pub fn fan_info(&self) -> Result<FansInfo, NvmlError> {
180        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetFanSpeedInfo.as_ref())?;
181
182        unsafe {
183            let mut fans_info: nvmlUnitFanSpeeds_t = mem::zeroed();
184            nvml_try(sym(self.unit, &mut fans_info))?;
185
186            FansInfo::try_from(fans_info)
187        }
188    }
189
190    /**
191    Gets the LED state associated with this `Unit`.
192
193    # Errors
194
195    * `Uninitialized`, if the library has not been successfully initialized
196    * `InvalidArg`, if the unit is invalid
197    * `NotSupported`, if this is not an S-class product
198    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
199    * `Unknown`, on any unexpected error
200
201    # Device Support
202
203    For S-class products.
204    */
205    // Checked against local
206    // Tested
207    #[doc(alias = "nvmlUnitGetLedState")]
208    pub fn led_state(&self) -> Result<LedState, NvmlError> {
209        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetLedState.as_ref())?;
210
211        unsafe {
212            let mut state: nvmlLedState_t = mem::zeroed();
213            nvml_try(sym(self.unit, &mut state))?;
214
215            LedState::try_from(state)
216        }
217    }
218
219    /**
220    Gets the PSU stats for this `Unit`.
221
222    # Errors
223
224    * `Uninitialized`, if the library has not been successfully initialized
225    * `InvalidArg`, if the unit is invalid
226    * `NotSupported`, if this is not an S-class product
227    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
228    * `Unknown`, on any unexpected error
229
230    # Device Support
231
232    For S-class products.
233    */
234    // Checked against local
235    // Tested
236    #[doc(alias = "nvmlUnitGetPsuInfo")]
237    pub fn psu_info(&self) -> Result<PsuInfo, NvmlError> {
238        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetPsuInfo.as_ref())?;
239        unsafe {
240            let mut info: nvmlPSUInfo_t = mem::zeroed();
241            nvml_try(sym(self.unit, &mut info))?;
242
243            PsuInfo::try_from(info)
244        }
245    }
246
247    /**
248    Gets the temperature for the specified `UnitTemperatureReading`, in °C.
249
250    Available readings depend on the product.
251
252    # Errors
253
254    * `Uninitialized`, if the library has not been successfully initialized
255    * `InvalidArg`, if the unit is invalid
256    * `NotSupported`, if this is not an S-class product
257    * `Unknown`, on any unexpected error
258
259    # Device Support
260
261    For S-class products. Available readings depend on the product.
262    */
263    // Checked against local
264    // Tested
265    #[doc(alias = "nvmlUnitGetTemperature")]
266    pub fn temperature(&self, reading_type: TemperatureReading) -> Result<u32, NvmlError> {
267        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetTemperature.as_ref())?;
268
269        unsafe {
270            let mut temp: c_uint = mem::zeroed();
271
272            nvml_try(sym(self.unit, reading_type as c_uint, &mut temp))?;
273
274            Ok(temp)
275        }
276    }
277
278    /**
279    Gets the static information associated with this `Unit`.
280
281    # Errors
282
283    * `Uninitialized`, if the library has not been successfully initialized
284    * `InvalidArg`, if the unit is invalid
285    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
286
287    # Device Support
288
289    For S-class products.
290    */
291    // Checked against local
292    // Tested
293    #[doc(alias = "nvmlUnitGetUnitInfo")]
294    pub fn info(&self) -> Result<UnitInfo, NvmlError> {
295        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetUnitInfo.as_ref())?;
296
297        unsafe {
298            let mut info: nvmlUnitInfo_t = mem::zeroed();
299            nvml_try(sym(self.unit, &mut info))?;
300
301            UnitInfo::try_from(info)
302        }
303    }
304
305    // Unit commands starting here
306
307    /**
308    Sets the LED color for this `Unit`.
309
310    Requires root/admin permissions. This operation takes effect immediately.
311
312    Note: Current S-class products don't provide unique LEDs for each unit. As such,
313    both front and back LEDs will be toggled in unison regardless of which unit is
314    specified with this method (aka the `Unit` represented by this struct).
315
316    # Errors
317
318    * `Uninitialized`, if the library has not been successfully initialized
319    * `InvalidArg`, if the unit is invalid
320    * `NotSupported`, if this is not an S-class product
321    * `NoPermission`, if the user doesn't have permission to perform this operation
322    * `Unknown`, on any unexpected error
323
324    # Device Support
325
326    For S-class products.
327    */
328    // checked against local
329    // Tested (no-run)
330    #[doc(alias = "nvmlUnitSetLedState")]
331    pub fn set_led_color(&mut self, color: LedColor) -> Result<(), NvmlError> {
332        let sym = nvml_sym(self.nvml.lib.nvmlUnitSetLedState.as_ref())?;
333
334        unsafe { nvml_try(sym(self.unit, color.as_c())) }
335    }
336}
337
338// I do not have access to this hardware and cannot test anything
339#[cfg(test)]
340#[deny(unused_mut)]
341mod test {
342    use crate::enum_wrappers::unit::LedColor;
343    use crate::enums::unit::TemperatureReading;
344    use crate::test_utils::*;
345
346    #[test]
347    #[ignore = "my machine does not support this call"]
348    fn devices() {
349        let nvml = nvml();
350        let unit = unit(&nvml);
351        unit.devices().expect("devices");
352    }
353
354    #[test]
355    #[ignore = "my machine does not support this call"]
356    fn fan_info() {
357        let nvml = nvml();
358        test_with_unit(3, &nvml, |unit| unit.fan_info())
359    }
360
361    #[test]
362    #[ignore = "my machine does not support this call"]
363    fn led_state() {
364        let nvml = nvml();
365        test_with_unit(3, &nvml, |unit| unit.led_state())
366    }
367
368    #[test]
369    #[ignore = "my machine does not support this call"]
370    fn psu_info() {
371        let nvml = nvml();
372        test_with_unit(3, &nvml, |unit| unit.psu_info())
373    }
374
375    #[test]
376    #[ignore = "my machine does not support this call"]
377    fn temperature() {
378        let nvml = nvml();
379        test_with_unit(3, &nvml, |unit| unit.temperature(TemperatureReading::Board))
380    }
381
382    #[test]
383    #[ignore = "my machine does not support this call"]
384    fn info() {
385        let nvml = nvml();
386        test_with_unit(3, &nvml, |unit| unit.info())
387    }
388
389    // This modifies unit state, so we don't want to actually run the test
390    #[allow(dead_code)]
391    fn set_led_color() {
392        let nvml = nvml();
393        let mut unit = unit(&nvml);
394
395        unit.set_led_color(LedColor::Amber).expect("set to true")
396    }
397}