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, 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        unsafe {
149            /*
150            NVIDIA doesn't even say that `count` will be set to the count if
151            `InsufficientSize` is returned. But we can assume sanity, right?
152
153            The idea here is:
154            If there are 0 devices, NVML_SUCCESS is returned, `count` is set
155              to 0. We return count, all good.
156            If there is 1 device, NVML_SUCCESS is returned, `count` is set to
157              1. We return count, all good.
158            If there are >= 2 devices, NVML_INSUFFICIENT_SIZE is returned.
159             `count` is theoretically set to the actual count, and we
160              return it.
161            */
162            let mut count: c_uint = 1;
163            let mut devices: [nvmlDevice_t; 1] = [mem::zeroed()];
164
165            match sym(self.unit, &mut count, devices.as_mut_ptr()) {
166                nvmlReturn_enum_NVML_SUCCESS | nvmlReturn_enum_NVML_ERROR_INSUFFICIENT_SIZE => {
167                    Ok(count)
168                }
169                // We know that this will be an error
170                other => nvml_try(other).map(|_| 0),
171            }
172        }
173    }
174
175    /**
176    Gets fan information for this `Unit` (fan count and state + speed for each).
177
178    # Errors
179
180    * `Uninitialized`, if the library has not been successfully initialized
181    * `InvalidArg`, if the unit is invalid
182    * `NotSupported`, if this is not an S-class product
183    * `UnexpectedVariant`, for which you can read the docs for
184    * `Unknown`, on any unexpected error
185
186    # Device Support
187
188    For S-class products.
189    */
190    // Checked against local
191    // Tested
192    #[doc(alias = "nvmlUnitGetFanSpeedInfo")]
193    pub fn fan_info(&self) -> Result<FansInfo, NvmlError> {
194        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetFanSpeedInfo.as_ref())?;
195
196        unsafe {
197            let mut fans_info: nvmlUnitFanSpeeds_t = mem::zeroed();
198            nvml_try(sym(self.unit, &mut fans_info))?;
199
200            FansInfo::try_from(fans_info)
201        }
202    }
203
204    /**
205    Gets the LED state associated with this `Unit`.
206
207    # Errors
208
209    * `Uninitialized`, if the library has not been successfully initialized
210    * `InvalidArg`, if the unit is invalid
211    * `NotSupported`, if this is not an S-class product
212    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
213    * `Unknown`, on any unexpected error
214
215    # Device Support
216
217    For S-class products.
218    */
219    // Checked against local
220    // Tested
221    #[doc(alias = "nvmlUnitGetLedState")]
222    pub fn led_state(&self) -> Result<LedState, NvmlError> {
223        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetLedState.as_ref())?;
224
225        unsafe {
226            let mut state: nvmlLedState_t = mem::zeroed();
227            nvml_try(sym(self.unit, &mut state))?;
228
229            LedState::try_from(state)
230        }
231    }
232
233    /**
234    Gets the PSU stats for this `Unit`.
235
236    # Errors
237
238    * `Uninitialized`, if the library has not been successfully initialized
239    * `InvalidArg`, if the unit is invalid
240    * `NotSupported`, if this is not an S-class product
241    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
242    * `Unknown`, on any unexpected error
243
244    # Device Support
245
246    For S-class products.
247    */
248    // Checked against local
249    // Tested
250    #[doc(alias = "nvmlUnitGetPsuInfo")]
251    pub fn psu_info(&self) -> Result<PsuInfo, NvmlError> {
252        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetPsuInfo.as_ref())?;
253        unsafe {
254            let mut info: nvmlPSUInfo_t = mem::zeroed();
255            nvml_try(sym(self.unit, &mut info))?;
256
257            PsuInfo::try_from(info)
258        }
259    }
260
261    /**
262    Gets the temperature for the specified `UnitTemperatureReading`, in °C.
263
264    Available readings depend on the product.
265
266    # Errors
267
268    * `Uninitialized`, if the library has not been successfully initialized
269    * `InvalidArg`, if the unit is invalid
270    * `NotSupported`, if this is not an S-class product
271    * `Unknown`, on any unexpected error
272
273    # Device Support
274
275    For S-class products. Available readings depend on the product.
276    */
277    // Checked against local
278    // Tested
279    #[doc(alias = "nvmlUnitGetTemperature")]
280    pub fn temperature(&self, reading_type: TemperatureReading) -> Result<u32, NvmlError> {
281        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetTemperature.as_ref())?;
282
283        unsafe {
284            let mut temp: c_uint = mem::zeroed();
285
286            nvml_try(sym(self.unit, reading_type as c_uint, &mut temp))?;
287
288            Ok(temp)
289        }
290    }
291
292    /**
293    Gets the static information associated with this `Unit`.
294
295    # Errors
296
297    * `Uninitialized`, if the library has not been successfully initialized
298    * `InvalidArg`, if the unit is invalid
299    * `Utf8Error`, if the string obtained from the C function is not valid Utf8
300
301    # Device Support
302
303    For S-class products.
304    */
305    // Checked against local
306    // Tested
307    #[doc(alias = "nvmlUnitGetUnitInfo")]
308    pub fn info(&self) -> Result<UnitInfo, NvmlError> {
309        let sym = nvml_sym(self.nvml.lib.nvmlUnitGetUnitInfo.as_ref())?;
310
311        unsafe {
312            let mut info: nvmlUnitInfo_t = mem::zeroed();
313            nvml_try(sym(self.unit, &mut info))?;
314
315            UnitInfo::try_from(info)
316        }
317    }
318
319    // Unit commands starting here
320
321    /**
322    Sets the LED color for this `Unit`.
323
324    Requires root/admin permissions. This operation takes effect immediately.
325
326    Note: Current S-class products don't provide unique LEDs for each unit. As such,
327    both front and back LEDs will be toggled in unison regardless of which unit is
328    specified with this method (aka the `Unit` represented by this struct).
329
330    # Errors
331
332    * `Uninitialized`, if the library has not been successfully initialized
333    * `InvalidArg`, if the unit is invalid
334    * `NotSupported`, if this is not an S-class product
335    * `NoPermission`, if the user doesn't have permission to perform this operation
336    * `Unknown`, on any unexpected error
337
338    # Device Support
339
340    For S-class products.
341    */
342    // checked against local
343    // Tested (no-run)
344    #[doc(alias = "nvmlUnitSetLedState")]
345    pub fn set_led_color(&mut self, color: LedColor) -> Result<(), NvmlError> {
346        let sym = nvml_sym(self.nvml.lib.nvmlUnitSetLedState.as_ref())?;
347
348        unsafe { nvml_try(sym(self.unit, color.as_c())) }
349    }
350}
351
352// I do not have access to this hardware and cannot test anything
353#[cfg(test)]
354#[deny(unused_mut)]
355mod test {
356    use crate::enum_wrappers::unit::LedColor;
357    use crate::enums::unit::TemperatureReading;
358    use crate::test_utils::*;
359
360    #[test]
361    #[ignore = "my machine does not support this call"]
362    fn devices() {
363        let nvml = nvml();
364        let unit = unit(&nvml);
365        unit.devices().expect("devices");
366    }
367
368    #[test]
369    #[ignore = "my machine does not support this call"]
370    fn fan_info() {
371        let nvml = nvml();
372        test_with_unit(3, &nvml, |unit| unit.fan_info())
373    }
374
375    #[test]
376    #[ignore = "my machine does not support this call"]
377    fn led_state() {
378        let nvml = nvml();
379        test_with_unit(3, &nvml, |unit| unit.led_state())
380    }
381
382    #[test]
383    #[ignore = "my machine does not support this call"]
384    fn psu_info() {
385        let nvml = nvml();
386        test_with_unit(3, &nvml, |unit| unit.psu_info())
387    }
388
389    #[test]
390    #[ignore = "my machine does not support this call"]
391    fn temperature() {
392        let nvml = nvml();
393        test_with_unit(3, &nvml, |unit| unit.temperature(TemperatureReading::Board))
394    }
395
396    #[test]
397    #[ignore = "my machine does not support this call"]
398    fn info() {
399        let nvml = nvml();
400        test_with_unit(3, &nvml, |unit| unit.info())
401    }
402
403    // This modifies unit state, so we don't want to actually run the test
404    #[allow(dead_code)]
405    fn set_led_color() {
406        let nvml = nvml();
407        let mut unit = unit(&nvml);
408
409        unit.set_led_color(LedColor::Amber).expect("set to true")
410    }
411}