use crate::{DiagnosticError, DiagnosticResult, MetricValue};
use std::ffi::{CStr, CString, c_char, c_double, c_void};
use std::fmt;
use std::ptr::null_mut;
pub const DEFAULT_NVIDIA_GPU_COUNTERS: &[&str] = &[
"gpu__compute_memory_throughput.avg.pct_of_peak_sustained_elapsed",
"lts__throughput.avg.pct_of_peak_sustained_elapsed",
"sm__throughput.avg.pct_of_peak_sustained_elapsed",
"sm__inst_executed_pipe_tensor.avg.pct_of_peak_sustained_active",
];
unsafe extern "C" {
fn micromeasure_gpu_counter_create(
metric_names: *const *const c_char,
metric_count: usize,
out_handle: *mut *mut c_void,
error: *mut c_char,
error_len: usize,
) -> i32;
fn micromeasure_gpu_counter_begin(
handle: *mut c_void,
range_name: *const c_char,
error: *mut c_char,
error_len: usize,
) -> i32;
fn micromeasure_gpu_counter_end(
handle: *mut c_void,
all_passes_submitted: *mut i32,
error: *mut c_char,
error_len: usize,
) -> i32;
fn micromeasure_gpu_counter_decode(
handle: *mut c_void,
error: *mut c_char,
error_len: usize,
) -> i32;
fn micromeasure_gpu_counter_value_count(handle: *mut c_void) -> usize;
fn micromeasure_gpu_counter_value(
handle: *mut c_void,
index: usize,
name: *mut *const c_char,
value: *mut c_double,
) -> i32;
fn micromeasure_gpu_counter_destroy(handle: *mut c_void);
}
pub type GpuCounterResult<T> = Result<T, GpuCounterError>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GpuCounterError {
InsufficientPrivileges(String),
MetricUnavailable(String),
ProfilerUnavailable(String),
CollectionFailed(String),
InvalidName(&'static str),
}
impl GpuCounterError {
fn from_detail(detail: String) -> Self {
let lower = detail.to_ascii_lowercase();
if detail.contains("ERR_NVGPUCTRPERM")
|| detail.contains("CUPTI_ERROR_INSUFFICIENT_PRIVILEGES")
|| lower.contains("permission")
|| lower.contains("privilege")
{
Self::InsufficientPrivileges(detail)
} else if lower.contains("configaddmetrics")
|| lower.contains("metric")
|| lower.contains("unavailable")
{
Self::MetricUnavailable(detail)
} else if lower.contains("initialize")
|| lower.contains("getchipname")
|| lower.contains("counteravailability")
|| lower.contains("no current cuda context")
{
Self::ProfilerUnavailable(detail)
} else {
Self::CollectionFailed(detail)
}
}
pub fn metric_name(&self) -> &'static str {
match self {
Self::InsufficientPrivileges(_) => "gpu_counter_permission_error",
Self::MetricUnavailable(_) => "gpu_counter_metric_error",
Self::ProfilerUnavailable(_) => "gpu_counter_profiler_error",
Self::CollectionFailed(_) => "gpu_counter_collection_error",
Self::InvalidName(_) => "gpu_counter_invalid_name",
}
}
pub fn display_name(&self) -> &'static str {
match self {
Self::InsufficientPrivileges(_) => "Counter permission error",
Self::MetricUnavailable(_) => "Counter metric error",
Self::ProfilerUnavailable(_) => "Counter profiler error",
Self::CollectionFailed(_) => "Counter collection error",
Self::InvalidName(_) => "Counter invalid name",
}
}
pub fn into_diagnostic_result(self) -> DiagnosticResult {
DiagnosticResult::new("gpu counters").push_metric(
MetricValue::integer(self.metric_name(), 1, "errors")
.with_display_name(self.display_name()),
)
}
}
impl fmt::Display for GpuCounterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InsufficientPrivileges(detail)
| Self::MetricUnavailable(detail)
| Self::ProfilerUnavailable(detail)
| Self::CollectionFailed(detail) => f.write_str(detail),
Self::InvalidName(label) => write!(f, "{label} contains NUL"),
}
}
}
impl std::error::Error for GpuCounterError {}
impl From<GpuCounterError> for DiagnosticError {
fn from(error: GpuCounterError) -> Self {
Self::new(error.to_string())
}
}
#[derive(Clone, Debug)]
pub struct GpuCounterMetric {
pub name: String,
pub value: f64,
}
impl GpuCounterMetric {
pub fn to_metric_value(&self) -> MetricValue {
match self.name.as_str() {
"gpu__compute_memory_throughput.avg.pct_of_peak_sustained_elapsed" => {
MetricValue::new("gpu_memory_peak_pct", self.value, "%")
.with_display_name("GPU memory peak")
}
"lts__throughput.avg.pct_of_peak_sustained_elapsed" => {
MetricValue::new("gpu_l2_peak_pct", self.value, "%")
.with_display_name("GPU L2 peak")
}
"sm__throughput.avg.pct_of_peak_sustained_elapsed" => {
MetricValue::new("gpu_sm_peak_pct", self.value, "%")
.with_display_name("GPU SM peak")
}
"sm__inst_executed_pipe_tensor.avg.pct_of_peak_sustained_active" => {
MetricValue::new("gpu_tensor_active_pct", self.value, "%")
.with_display_name("GPU tensor active")
}
_ => MetricValue::new("gpu_counter", self.value, "")
.with_display_name("GPU counter")
.with_section("gpu counters"),
}
}
}
pub struct GpuCounterCollector {
handle: *mut c_void,
_metric_names: Vec<CString>,
range_name: CString,
}
impl GpuCounterCollector {
pub fn new(metrics: &[&str], range_name: &str) -> GpuCounterResult<Self> {
let metric_names = metrics
.iter()
.map(|metric| {
CString::new(*metric).map_err(|_| GpuCounterError::InvalidName("metric name"))
})
.collect::<GpuCounterResult<Vec<_>>>()?;
let metric_ptrs = metric_names
.iter()
.map(|metric| metric.as_ptr())
.collect::<Vec<_>>();
let range_name =
CString::new(range_name).map_err(|_| GpuCounterError::InvalidName("range name"))?;
let mut handle = null_mut();
let mut error = ErrorBuffer::new();
let status = unsafe {
micromeasure_gpu_counter_create(
metric_ptrs.as_ptr(),
metric_ptrs.len(),
&mut handle,
error.as_mut_ptr(),
error.len(),
)
};
if status != 0 {
return Err(GpuCounterError::from_detail(error.message()));
}
Ok(Self {
handle,
_metric_names: metric_names,
range_name,
})
}
pub fn begin(&mut self) -> GpuCounterResult<()> {
let mut error = ErrorBuffer::new();
let status = unsafe {
micromeasure_gpu_counter_begin(
self.handle,
self.range_name.as_ptr(),
error.as_mut_ptr(),
error.len(),
)
};
if status == 0 {
Ok(())
} else {
Err(GpuCounterError::from_detail(error.message()))
}
}
pub fn end(&mut self) -> GpuCounterResult<bool> {
let mut all_passes_submitted = 0;
let mut error = ErrorBuffer::new();
let status = unsafe {
micromeasure_gpu_counter_end(
self.handle,
&mut all_passes_submitted,
error.as_mut_ptr(),
error.len(),
)
};
if status == 0 {
Ok(all_passes_submitted != 0)
} else {
Err(GpuCounterError::from_detail(error.message()))
}
}
pub fn decode(&mut self) -> GpuCounterResult<Vec<GpuCounterMetric>> {
let mut error = ErrorBuffer::new();
let status = unsafe {
micromeasure_gpu_counter_decode(self.handle, error.as_mut_ptr(), error.len())
};
if status != 0 {
return Err(GpuCounterError::from_detail(error.message()));
}
let count = unsafe { micromeasure_gpu_counter_value_count(self.handle) };
let mut metrics = Vec::with_capacity(count);
for index in 0..count {
let mut name: *const c_char = std::ptr::null();
let mut value = 0.0 as c_double;
let status = unsafe {
micromeasure_gpu_counter_value(self.handle, index, &mut name, &mut value)
};
if status != 0 || name.is_null() {
return Err(GpuCounterError::CollectionFailed(
"GPU counter value read failed".to_string(),
));
}
let name = unsafe { CStr::from_ptr(name) }
.to_string_lossy()
.into_owned();
metrics.push(GpuCounterMetric { name, value });
}
Ok(metrics)
}
}
impl Drop for GpuCounterCollector {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe {
micromeasure_gpu_counter_destroy(self.handle);
}
}
}
}
struct ErrorBuffer {
bytes: [c_char; 1024],
}
impl ErrorBuffer {
fn new() -> Self {
Self { bytes: [0; 1024] }
}
fn as_mut_ptr(&mut self) -> *mut c_char {
self.bytes.as_mut_ptr()
}
fn len(&self) -> usize {
self.bytes.len()
}
fn message(&self) -> String {
unsafe { CStr::from_ptr(self.bytes.as_ptr()) }
.to_string_lossy()
.into_owned()
}
}