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}