Skip to main content

nvml_wrapper/
gpm.rs

1use crate::enums::gpm::GpmMetricId;
2use crate::error::{nvml_sym, nvml_try, NvmlError};
3use crate::ffi::bindings::*;
4use crate::struct_wrappers::gpm::GpmMetricResult;
5use crate::Nvml;
6
7use std::mem;
8
9/**
10Handle to a GPM (GPU Performance Monitoring) sample.
11
12GPM enables collecting fine-grained GPU performance metrics (SM occupancy,
13tensor utilization, PCIe/NVLink bandwidth, etc.) on Hopper+ GPUs. Metrics
14are computed by taking two time-separated samples and comparing them via
15[`gpm_metrics_get`].
16
17**Operations on a sample are not thread-safe.** It does not, therefore,
18implement `Sync`.
19
20You can obtain a `GpmSample` via [`crate::Device::gpm_sample()`] or
21[`crate::Device::gpm_mig_sample()`].
22
23Lifetimes are used to enforce that each `GpmSample` instance cannot be used
24after the `Nvml` instance it was obtained from is dropped.
25*/
26#[derive(Debug)]
27pub struct GpmSample<'nvml> {
28    sample: nvmlGpmSample_t,
29    nvml: &'nvml Nvml,
30}
31
32unsafe impl<'nvml> Send for GpmSample<'nvml> {}
33
34impl<'nvml> GpmSample<'nvml> {
35    /// Allocate a new GPM sample.
36    ///
37    /// # Errors
38    ///
39    /// * `Uninitialized`, if the library has not been successfully initialized
40    /// * `Unknown`, on any unexpected error
41    #[doc(alias = "nvmlGpmSampleAlloc")]
42    pub(crate) fn alloc(nvml: &'nvml Nvml) -> Result<Self, NvmlError> {
43        let sym = nvml_sym(nvml.lib.nvmlGpmSampleAlloc.as_ref())?;
44
45        unsafe {
46            let mut sample: nvmlGpmSample_t = mem::zeroed();
47            nvml_try(sym(&mut sample))?;
48
49            Ok(Self { sample, nvml })
50        }
51    }
52
53    /// Wrap a raw sample handle.
54    ///
55    /// The returned `GpmSample` takes ownership of the handle and will free
56    /// it on drop.
57    ///
58    /// # Safety
59    ///
60    /// * The handle must have been allocated by `nvmlGpmSampleAlloc` via the
61    ///   same loaded NVML library as `nvml` and must not already have been
62    ///   freed.
63    /// * Nothing else may free the handle afterward — neither another
64    ///   `GpmSample` wrapping the same handle nor a manual
65    ///   `nvmlGpmSampleFree` call — as that would result in a double-free.
66    ///   Use [`Self::into_handle`] to release ownership from an existing
67    ///   `GpmSample` before reconstructing it with this function.
68    pub unsafe fn from_handle(nvml: &'nvml Nvml, sample: nvmlGpmSample_t) -> Self {
69        Self { sample, nvml }
70    }
71
72    /**
73    Use this to free the sample if you care about handling potential errors
74    (*the `Drop` implementation ignores errors!*).
75
76    # Errors
77
78    * `Uninitialized`, if the library has not been successfully initialized
79    * `Unknown`, on any unexpected error
80    */
81    #[doc(alias = "nvmlGpmSampleFree")]
82    pub fn free(self) -> Result<(), NvmlError> {
83        let sym = nvml_sym(self.nvml.lib.nvmlGpmSampleFree.as_ref())?;
84
85        unsafe {
86            nvml_try(sym(self.sample))?;
87        }
88
89        mem::forget(self);
90        Ok(())
91    }
92
93    /// Get the raw sample handle.
94    ///
95    /// The handle is *borrowed*: this `GpmSample` still owns the sample and
96    /// will free it on drop.
97    ///
98    /// # Safety
99    ///
100    /// This is unsafe to prevent it from being used without care. In
101    /// particular, you must avoid creating a new `GpmSample` from this handle
102    /// (e.g. via [`Self::from_handle`]) and allowing both this `GpmSample`
103    /// and the newly created one to drop (which would result in a
104    /// double-free). To transfer ownership of the sample out of the wrapper,
105    /// use [`Self::into_handle`] instead.
106    pub unsafe fn handle(&self) -> nvmlGpmSample_t {
107        self.sample
108    }
109
110    /// Consume this `GpmSample` and return the raw sample handle without
111    /// freeing it.
112    ///
113    /// The caller takes ownership of the handle and is responsible for
114    /// freeing it, either by calling `nvmlGpmSampleFree` manually or by
115    /// reconstructing a `GpmSample` with [`Self::from_handle`] and letting
116    /// that free it.
117    pub fn into_handle(self) -> nvmlGpmSample_t {
118        let sample = self.sample;
119        mem::forget(self);
120        sample
121    }
122
123    /// Get a reference to the `Nvml` instance this sample was created from.
124    pub fn nvml(&self) -> &'nvml Nvml {
125        self.nvml
126    }
127}
128
129/// This `Drop` implementation ignores errors! Use the `.free()` method on
130/// the `GpmSample` struct if you care about handling them.
131impl<'nvml> Drop for GpmSample<'nvml> {
132    #[doc(alias = "nvmlGpmSampleFree")]
133    fn drop(&mut self) {
134        unsafe {
135            self.nvml.lib.nvmlGpmSampleFree(self.sample);
136        }
137    }
138}
139
140/**
141Retrieve GPM metrics computed between two time-separated samples.
142
143The two samples should have been previously populated via
144[`crate::Device::gpm_sample()`] or [`crate::Device::gpm_mig_sample()`].
145
146Returns a `Vec` with one entry per requested metric. Each entry is itself
147a `Result`: the outer `Result` covers transport-level errors, while the
148inner `Result` covers per-metric failures (e.g. a metric not supported on
149the current GPU).
150
151# Errors
152
153* `Uninitialized`, if the library has not been successfully initialized
154* `InvalidArg`, if any argument is invalid
155* `NotSupported`, if GPM is not supported
156* `Unknown`, on any unexpected error
157
158# Panics
159
160Panics if more than 98 metrics are requested (the maximum supported by NVML).
161
162# Device Support
163
164Supports Hopper and newer architectures.
165*/
166#[doc(alias = "nvmlGpmMetricsGet")]
167pub fn gpm_metrics_get<'nvml>(
168    nvml: &'nvml Nvml,
169    sample1: &GpmSample<'nvml>,
170    sample2: &GpmSample<'nvml>,
171    metric_ids: &[GpmMetricId],
172) -> Result<Vec<Result<GpmMetricResult, NvmlError>>, NvmlError> {
173    assert!(
174        metric_ids.len() <= nvmlGpmMetricId_t_NVML_GPM_METRIC_MAX as usize,
175        "cannot request more than {} GPM metrics at once",
176        nvmlGpmMetricId_t_NVML_GPM_METRIC_MAX
177    );
178
179    let sym = nvml_sym(nvml.lib.nvmlGpmMetricsGet.as_ref())?;
180
181    unsafe {
182        let mut request: nvmlGpmMetricsGet_t = mem::zeroed();
183        request.version = NVML_GPM_METRICS_GET_VERSION;
184        request.numMetrics = metric_ids.len() as u32;
185        request.sample1 = sample1.sample;
186        request.sample2 = sample2.sample;
187
188        for (i, id) in metric_ids.iter().enumerate() {
189            request.metrics[i].metricId = id.as_c();
190        }
191
192        nvml_try(sym(&mut request))?;
193
194        let results = (0..metric_ids.len())
195            .map(|i| GpmMetricResult::try_from_c(&request.metrics[i]))
196            .collect();
197
198        Ok(results)
199    }
200}