1#![allow(non_camel_case_types)]
26
27use std::ffi::{c_uint, c_ulonglong, c_void};
28
29use libloading::Library;
30
31pub type NvmlReturn = std::ffi::c_int;
33pub type NvmlDeviceHandle = *mut c_void;
35
36pub const NVML_SUCCESS: NvmlReturn = 0;
37pub const NVML_ERROR_NOT_SUPPORTED: NvmlReturn = 3;
38pub const NVML_ERROR_NO_PERMISSION: NvmlReturn = 4;
39
40pub const NVML_CLOCK_GRAPHICS: c_uint = 0;
42pub const NVML_CLOCK_SM: c_uint = 1;
43pub const NVML_CLOCK_MEM: c_uint = 2;
44pub const NVML_TEMPERATURE_GPU: c_uint = 0;
46pub const NVML_PCIE_UTIL_TX_BYTES: c_uint = 0;
48pub const NVML_PCIE_UTIL_RX_BYTES: c_uint = 1;
49pub const NVML_NVLINK_MAX_LINKS: c_uint = 18;
51
52#[repr(C)]
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
55pub struct NvmlMemory {
56 pub total: c_ulonglong,
57 pub free: c_ulonglong,
58 pub used: c_ulonglong,
59}
60
61pub type FnVoid = unsafe extern "C" fn() -> NvmlReturn;
62pub type FnHandleByIndex = unsafe extern "C" fn(c_uint, *mut NvmlDeviceHandle) -> NvmlReturn;
63pub type FnDevU32 = unsafe extern "C" fn(NvmlDeviceHandle, *mut c_uint) -> NvmlReturn;
64pub type FnDevU64 = unsafe extern "C" fn(NvmlDeviceHandle, *mut c_ulonglong) -> NvmlReturn;
65pub type FnDevArgU32 = unsafe extern "C" fn(NvmlDeviceHandle, c_uint, *mut c_uint) -> NvmlReturn;
66pub type FnDevMemory = unsafe extern "C" fn(NvmlDeviceHandle, *mut NvmlMemory) -> NvmlReturn;
67
68#[derive(Clone, Copy, Debug)]
71pub struct NvmlFns {
72 pub init: FnVoid,
73 pub shutdown: FnVoid,
74 pub device_by_index: FnHandleByIndex,
75 pub power_usage: Option<FnDevU32>,
77 pub total_energy: Option<FnDevU64>,
80 pub clock_info: Option<FnDevArgU32>,
82 pub temperature: Option<FnDevArgU32>,
84 pub clocks_event_reasons: Option<FnDevU64>,
87 pub memory_info: Option<FnDevMemory>,
89 pub pcie_throughput: Option<FnDevArgU32>,
91 pub nvlink_state: Option<FnDevArgU32>,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum NvmlUnavailable {
99 NotInstalled(String),
101 MissingSymbol(&'static str),
103 InitFailed(NvmlReturn),
105 NoDevice { index: u32, code: NvmlReturn },
107}
108
109impl std::fmt::Display for NvmlUnavailable {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Self::NotInstalled(why) => write!(f, "NVML library not loadable: {why}"),
113 Self::MissingSymbol(s) => write!(f, "NVML library lacks required symbol {s}"),
114 Self::InitFailed(c) => write!(f, "nvmlInit_v2 failed with code {c}"),
115 Self::NoDevice { index, code } => {
116 write!(f, "NVML has no device {index} (code {code})")
117 }
118 }
119 }
120}
121
122impl std::error::Error for NvmlUnavailable {}
123
124pub const LIBRARY_NAMES: [&str; 2] = ["libnvidia-ml.so.1", "libnvidia-ml.so"];
126
127pub struct NvmlLibrary {
130 fns: NvmlFns,
131 _lib: Library,
132}
133
134impl NvmlLibrary {
135 pub fn open() -> Result<Self, NvmlUnavailable> {
137 Self::open_from(&LIBRARY_NAMES)
138 }
139
140 pub fn open_from(candidates: &[&str]) -> Result<Self, NvmlUnavailable> {
144 let mut last = String::from("no candidate names");
145 for name in candidates {
146 match unsafe { Library::new(name) } {
150 Ok(lib) => {
151 let fns = resolve(&lib)?;
152 return Ok(Self { fns, _lib: lib });
153 }
154 Err(e) => last = format!("{name}: {e}"),
155 }
156 }
157 Err(NvmlUnavailable::NotInstalled(last))
158 }
159
160 pub fn fns(&self) -> NvmlFns {
161 self.fns
162 }
163}
164
165fn required<T: Copy>(lib: &Library, name: &'static str) -> Result<T, NvmlUnavailable> {
166 optional(lib, name).ok_or(NvmlUnavailable::MissingSymbol(name))
167}
168
169fn optional<T: Copy>(lib: &Library, name: &str) -> Option<T> {
170 let mut sym = Vec::with_capacity(name.len() + 1);
171 sym.extend_from_slice(name.as_bytes());
172 sym.push(0);
173 unsafe { lib.get::<T>(&sym).ok().map(|s| *s) }
176}
177
178fn resolve(lib: &Library) -> Result<NvmlFns, NvmlUnavailable> {
179 Ok(NvmlFns {
180 init: required(lib, "nvmlInit_v2")?,
181 shutdown: required(lib, "nvmlShutdown")?,
182 device_by_index: required(lib, "nvmlDeviceGetHandleByIndex_v2")?,
183 power_usage: optional(lib, "nvmlDeviceGetPowerUsage"),
184 total_energy: optional(lib, "nvmlDeviceGetTotalEnergyConsumption"),
185 clock_info: optional(lib, "nvmlDeviceGetClockInfo"),
186 temperature: optional(lib, "nvmlDeviceGetTemperature"),
187 clocks_event_reasons: optional(lib, "nvmlDeviceGetCurrentClocksEventReasons")
188 .or_else(|| optional(lib, "nvmlDeviceGetCurrentClocksThrottleReasons")),
189 memory_info: optional(lib, "nvmlDeviceGetMemoryInfo"),
190 pcie_throughput: optional(lib, "nvmlDeviceGetPcieThroughput"),
191 nvlink_state: optional(lib, "nvmlDeviceGetNvLinkState"),
192 })
193}
194
195pub struct Nvml {
198 fns: NvmlFns,
199 _keep: Option<NvmlLibrary>,
202}
203
204impl Nvml {
205 pub fn open() -> Result<Self, NvmlUnavailable> {
207 let lib = NvmlLibrary::open()?;
208 let fns = lib.fns();
209 Self::init(fns, Some(lib))
210 }
211
212 pub fn with_fns(fns: NvmlFns) -> Result<Self, NvmlUnavailable> {
215 Self::init(fns, None)
216 }
217
218 fn init(fns: NvmlFns, keep: Option<NvmlLibrary>) -> Result<Self, NvmlUnavailable> {
219 let rc = unsafe { (fns.init)() };
222 if rc != NVML_SUCCESS {
223 return Err(NvmlUnavailable::InitFailed(rc));
224 }
225 Ok(Self { fns, _keep: keep })
226 }
227
228 pub fn device(&self, index: u32) -> Result<NvmlDevice<'_>, NvmlUnavailable> {
230 let mut handle: NvmlDeviceHandle = std::ptr::null_mut();
231 let rc = unsafe { (self.fns.device_by_index)(index, &mut handle) };
233 if rc != NVML_SUCCESS {
234 return Err(NvmlUnavailable::NoDevice { index, code: rc });
235 }
236 Ok(NvmlDevice {
237 fns: &self.fns,
238 handle,
239 })
240 }
241}
242
243impl Drop for Nvml {
244 fn drop(&mut self) {
245 let _ = unsafe { (self.fns.shutdown)() };
248 }
249}
250
251pub struct NvmlDevice<'a> {
255 fns: &'a NvmlFns,
256 handle: NvmlDeviceHandle,
257}
258
259unsafe impl Send for NvmlDevice<'_> {}
262
263fn answer<T>(rc: NvmlReturn, v: T) -> Result<Option<T>, NvmlReturn> {
264 match rc {
265 NVML_SUCCESS => Ok(Some(v)),
266 NVML_ERROR_NOT_SUPPORTED | NVML_ERROR_NO_PERMISSION => Ok(None),
267 other => Err(other),
268 }
269}
270
271impl NvmlDevice<'_> {
272 fn u32_query(&self, f: Option<FnDevU32>) -> Result<Option<u32>, NvmlReturn> {
273 let Some(f) = f else { return Ok(None) };
274 let mut v: c_uint = 0;
275 answer(unsafe { f(self.handle, &mut v) }, v)
278 }
279
280 fn u64_query(&self, f: Option<FnDevU64>) -> Result<Option<u64>, NvmlReturn> {
281 let Some(f) = f else { return Ok(None) };
282 let mut v: c_ulonglong = 0;
283 answer(unsafe { f(self.handle, &mut v) }, v)
286 }
287
288 fn arg_query(&self, f: Option<FnDevArgU32>, arg: c_uint) -> Result<Option<u32>, NvmlReturn> {
289 let Some(f) = f else { return Ok(None) };
290 let mut v: c_uint = 0;
291 answer(unsafe { f(self.handle, arg, &mut v) }, v)
294 }
295
296 pub fn power_mw(&self) -> Result<Option<u32>, NvmlReturn> {
297 self.u32_query(self.fns.power_usage)
298 }
299
300 pub fn total_energy_mj(&self) -> Result<Option<u64>, NvmlReturn> {
301 self.u64_query(self.fns.total_energy)
302 }
303
304 pub fn clock_mhz(&self, clock: c_uint) -> Result<Option<u32>, NvmlReturn> {
305 self.arg_query(self.fns.clock_info, clock)
306 }
307
308 pub fn temperature_c(&self) -> Result<Option<u32>, NvmlReturn> {
309 self.arg_query(self.fns.temperature, NVML_TEMPERATURE_GPU)
310 }
311
312 pub fn clocks_event_reasons(&self) -> Result<Option<u64>, NvmlReturn> {
313 self.u64_query(self.fns.clocks_event_reasons)
314 }
315
316 pub fn memory(&self) -> Result<Option<NvmlMemory>, NvmlReturn> {
317 let Some(f) = self.fns.memory_info else {
318 return Ok(None);
319 };
320 let mut m = NvmlMemory::default();
321 answer(unsafe { f(self.handle, &mut m) }, m)
323 }
324
325 pub fn pcie_kbps(&self, counter: c_uint) -> Result<Option<u32>, NvmlReturn> {
326 self.arg_query(self.fns.pcie_throughput, counter)
327 }
328
329 pub fn nvlink_active_links(&self) -> Option<u32> {
333 let f = self.fns.nvlink_state?;
334 let mut active = 0u32;
335 let mut any = false;
336 for link in 0..NVML_NVLINK_MAX_LINKS {
337 let mut state: c_uint = 0;
338 if unsafe { f(self.handle, link, &mut state) } == NVML_SUCCESS {
341 any = true;
342 active += u32::from(state == 1);
343 }
344 }
345 any.then_some(active)
346 }
347}
348
349#[cfg(test)]
350#[path = "nvml_tests.rs"]
351mod tests;