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}