// Copyright (C) 2024 Advanced Micro Devices. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // use crate::amdsmi_metric::*; use crate::collectors::collectors_create; use amdsmi::*; use prometheus_client::metrics::family::Family; use prometheus_client::metrics::gauge::Gauge; use prometheus_client::registry::Registry; use std::collections::HashMap; use std::fmt::Debug; use std::fmt::Display; pub struct AmdsmiCollectors { pub collectors: HashMap<&'static str, AmdsmiCollector>, pub gpu_filter: Option>, pub whitelist: Option>, pub blacklist: Option>, } impl Default for AmdsmiCollectors { fn default() -> Self { AmdsmiCollectors::new() } } impl AmdsmiCollectors { pub fn new() -> Self { let mut instance = AmdsmiCollectors { collectors: HashMap::new(), gpu_filter: None, whitelist: None, blacklist: None, }; collectors_create(&mut instance); instance } pub fn set_gpu_filter(&mut self, filters: Option>) -> &mut Self { self.gpu_filter = filters; self } pub fn set_field_filter( &mut self, whitelist: Option>, blacklist: Option>, ) -> &mut Self { self.whitelist = whitelist; self.blacklist = blacklist; // Re-generate the collectors after setting the field filters self.collectors.clear(); collectors_create(self); self } pub fn add_metric(&mut self, collector: AmdsmiGauge) -> &mut Self where T: 'static + Into + Display + Copy, F: 'static + Fn(AmdsmiProcessorHandle) -> AmdsmiResult + Send + Sync, { let name = collector.info.name; let description = collector.info.description; let unit = collector.info.unit; let func = collector.func; // Apply whitelist and blacklist filters before inserting the collector if let Some(ref whitelist) = self.whitelist { if !whitelist.contains(&name.to_string()) { return self; } } if let Some(ref blacklist) = self.blacklist { if blacklist.contains(&name.to_string()) { return self; } } self.collectors.insert( name, Box::new(move |processor_handle| match func(processor_handle) { Ok(value) => vec![AmdsmiMetric { info: AmdsmiInfo { name, description, unit: unit.clone(), }, value: value.into(), status: AmdsmiStatusT::AmdsmiStatusSuccess, }], Err(e) => vec![AmdsmiMetric { info: AmdsmiInfo { name, description, unit: unit.clone(), }, value: 0, status: e, }], }), ); self } pub fn add_metric_group(&mut self, collector: &AmdsmiGaugeGroup) -> &mut Self where T: 'static + Debug, F: 'static + Fn(AmdsmiProcessorHandle) -> AmdsmiResult + Send + Sync + Clone, { let name = collector.name; let func = collector.func.clone(); // Apply whitelist and blacklist filters before the Box::new let filtered_info_list: Vec<_> = collector .info_list .iter() .filter(|(info, _)| { if let Some(ref whitelist) = self.whitelist { if !whitelist.contains(&info.name.to_string()) { return false; } } if let Some(ref blacklist) = self.blacklist { if blacklist.contains(&info.name.to_string()) { return false; } } true }) .cloned() .collect(); // If the filtered_info_list is empty, return early if filtered_info_list.is_empty() { return self; } self.collectors.insert( name, Box::new(move |processor_handle| { let mut results = Vec::new(); match func(processor_handle) { Ok(value) => { for (info, accessor) in &filtered_info_list { results.push(AmdsmiMetric { info: AmdsmiInfo { name: info.name, description: info.description, unit: info.unit.clone(), }, value: accessor(&value), status: AmdsmiStatusT::AmdsmiStatusSuccess, }); } } Err(e) => { for (info, _) in &filtered_info_list { results.push(AmdsmiMetric { info: AmdsmiInfo { name: info.name, description: info.description, unit: info.unit.clone(), }, value: 0, status: e, }); } } } results }), ); self } pub fn print_supported(&self) { // Initialize the AMD SMI library if let Err(e) = amdsmi_init(AmdsmiInitFlagsT::AmdsmiInitAmdGpus) { eprintln!("Failed to initialize AMD SMI: {}", e); return; } // Get socket handles let socket_handles = match amdsmi_get_socket_handles() { Ok(handles) => handles, Err(e) => { eprintln!("Failed to get socket handles: {}", e); return; } }; for (socket_index, socket_handle) in socket_handles.iter().enumerate() { let socket_id = socket_index.to_string(); // Get processor handles for each socket handle let processor_handles = match amdsmi_get_processor_handles(*socket_handle) { Ok(handles) => handles, Err(e) => { eprintln!("Failed to get processor handles: {}", e); continue; } }; for (processor_index, processor_handle) in processor_handles.iter().enumerate() { let processor_id = processor_index.to_string(); let bdf = match amdsmi_get_gpu_device_bdf(*processor_handle) { Ok(bdf) => bdf.to_string(), Err(e) => { eprintln!("Failed to get GPU device BDF: {}", e); continue; } }; println!("========================================"); println!("GPU Information:"); println!(" Socket ID: {}", socket_id); println!(" Processor ID: {}", processor_id); println!(" BDF: {}", bdf); println!("----------------------------------------"); println!("Supported Metrics:"); println!( "{:<30} | {:<10} | {:<10} | {:<50}", "Metric ID", "Value", "Unit", "Help" ); println!("{:-<30}-+-{:-<10}-+-{:-<10}-+-{:-<50}", "", "", "", ""); // Collect metrics for (_, collector) in &self.collectors { let results = collector(*processor_handle); for metric in results { let value_str = if metric.status == AmdsmiStatusT::AmdsmiStatusSuccess { metric.value.to_string() } else { "NA".to_string() }; println!( "{:<30} | {:<10} | {:<10} | {:<50}", metric.info.name, value_str, metric.info.unit.as_str(), metric.info.description ); } } println!("========================================\n"); } } // Shutdown the AMD SMI library if let Err(e) = amdsmi_shut_down() { eprintln!("Failed to shutdown AMD SMI: {}", e); } } pub fn run_collect(&mut self) -> Registry { // Initialize the AMD SMI library if let Err(e) = amdsmi_init(AmdsmiInitFlagsT::AmdsmiInitAmdGpus) { eprintln!("Failed to initialize AMD SMI: {}", e); return Registry::default(); } let mut registry = Registry::default(); // Get socket handles let socket_handles = match amdsmi_get_socket_handles() { Ok(handles) => handles, Err(e) => { eprintln!("Failed to get socket handles: {}", e); return registry; } }; for (socket_index, socket_handle) in socket_handles.iter().enumerate() { let socket_id = socket_index.to_string(); // Get processor handles for each socket handle let processor_handles = match amdsmi_get_processor_handles(*socket_handle) { Ok(handles) => handles, Err(e) => { eprintln!("Failed to get processor handles: {}", e); continue; } }; for (processor_index, processor_handle) in processor_handles.iter().enumerate() { let processor_id = processor_index.to_string(); let bdf = match amdsmi_get_gpu_device_bdf(*processor_handle) { Ok(bdf) => bdf.to_string(), Err(e) => { eprintln!("Failed to get GPU device BDF: {}", e); continue; } }; let labels = AmdsmiLabels { socket_id: socket_id.clone(), processor_id: processor_id.clone(), bdf, }; // Only for GPUs which match the provided gpu filter argument if let Some(ref filters) = self.gpu_filter { if !filters.iter().any(|filter| { filter == &labels.bdf || filter == &format!("{}.{}", socket_id, processor_id) }) { continue; } } // Collect metrics for (_, collector) in &self.collectors { let results = collector(*processor_handle); for metric in results { if metric.status == AmdsmiStatusT::AmdsmiStatusSuccess { let metric_family = Family::::new_with_constructor(Gauge::default); if metric.info.unit.as_str().is_empty() { registry.register( metric.info.name, metric.info.description, metric_family.clone(), ); } else { registry.register_with_unit( metric.info.name, metric.info.description, metric.info.unit.clone(), metric_family.clone(), ); } metric_family.get_or_create(&labels).set(metric.value); } else { #[cfg(debug_assertions)] eprintln!( "Failed to collect metric {}: {} for socket {} processor {}", metric.info.name, metric.status, labels.socket_id, labels.processor_id ); } } } } } // Shutdown the AMD SMI library if let Err(e) = amdsmi_shut_down() { eprintln!("Failed to shutdown AMD SMI: {}", e); } registry } } /// A macro to add a metric collector. /// /// This macro creates an `AmdsmiGauge` instance and adds it to the collectors. /// /// # Parameters /// - `$collectors`: The collectors to add the metric to. /// - `$name`: The name of the metric. /// - `$description`: The description of the metric. /// - `$unit`: The unit of the metric. /// - `$func`: The function to collect the metric. /// /// # Example /// /// ```rust /// # use amdsmi::*; /// # use amdsmi_collectors::*; /// /// # fn main() { /// let mut collectors = AmdsmiCollectors::default(); /// add_metric!( /// collectors, /// "AMDSMI_FI_TEMPERATURE_EDGE", /// "Currnt edge temperature", /// Unit::Celsius, /// amdsmi_func!(amdsmi_get_temp_metric, AmdsmiTemperatureTypeT::AmdsmiTemperatureTypeEdge, AmdsmiTemperatureMetricT::AmdsmiTempCurrent) /// ); /// # } /// ``` macro_rules! add_metric { ($collectors:expr, $name:expr, $description:expr, $unit:expr, $func:expr) => { let collector = AmdsmiGauge::new($name, $description, $unit, $func); $collectors.add_metric(collector); }; } /// A macro to add a single metric from a struct to the collectors. /// /// This macro creates an `AmdsmiGauge` instance from a field of struct and adds it to the collectors. /// /// # Parameters /// - `$collectors`: The collectors to add the metric to. /// - `$name`: The name of the metric. /// - `$description`: The description of the metric. /// - `$unit`: The unit of the metric. /// - `$func`: The function to collect the metric. /// - `$field`: The struct field from which the metric is collected. /// /// # Example /// /// ```rust /// # use amdsmi::*; /// # use amdsmi_collectors::*; /// /// # fn main() { /// let mut collectors = AmdsmiCollectors::default(); /// add_metric_field!( /// collectors, /// "AMDSMI_FI_CURRENT_SOCKET_POWER", /// "Current Socket Power", /// Unit::Watts, /// amdsmi_get_power_info, /// current_socket_power /// ); /// # } /// ``` #[allow(unused_macros)] macro_rules! add_metric_field { ($collectors:expr, $name:expr, $description:expr, $unit:expr, $func:expr, $field:ident) => { let collector = AmdsmiGauge::new($name, $description, $unit, |handle| { $func(handle).map(|info| info.$field.into()) }); $collectors.add_metric(collector); }; } /// A macro to add a group of metrics within a single struct. /// /// This macro creates an `AmdsmiGaugeGroup` instance and adds it to the collectors. /// /// # Parameters /// - `$collectors`: The collectors to add the metric group to. /// - `$group_name`: The name of the metric group. /// - `$func`: The function to collect the metrics. /// - `$t`: The type of the struct containing the metrics. /// - `$name`: The name of each metric. /// - `$description`: The description of each metric. /// - `$unit`: The unit of each metric. /// - `$field`: The struct field from which each metric is collected. /// /// # Example /// /// ```rust /// # use amdsmi::*; /// # use amdsmi_collectors::*; /// /// # fn main() { /// let mut collectors = AmdsmiCollectors::default(); /// add_metric_group!( /// collectors, /// "AmdsmiPowerInfoMetrics", /// amdsmi_get_power_info, /// AmdsmiPowerInfoT, /// ("AMDSMI_FI_CURRENT_SOCKET_POWER", "Current Socket Power", Unit::Watts, current_socket_power), /// ("AMDSMI_FI_AVERAGE_SOCKET_POWER", "Average Socket Power", Unit::Watts, average_socket_power), /// ("AMDSMI_FI_GFX_VOLTAGE", "GFX Voltage", Unit::Volts, gfx_voltage), /// ("AMDSMI_FI_SOC_VOLTAGE", "SOC Voltage", Unit::Volts, soc_voltage), /// ("AMDSMI_FI_MEM_VOLTAGE", "Memory Voltage", Unit::Volts, mem_voltage), /// ("AMDSMI_FI_POWER_LIMIT", "Power Limit", Unit::Watts, power_limit) /// ); /// # } /// ``` #[allow(unused_macros)] macro_rules! add_metric_group { ($collectors:expr, $group_name:expr, $func:expr, $t:ty, $(($name:expr, $description:expr, $unit:expr, $field:ident)),+ $(,)?) => { let info_list = vec![ $( ( $name, $description, $unit, Arc::new(|info: &$t| info.$field as i64) as Arc i64 + Send + Sync>, ), )+ ]; let mut collector = AmdsmiGaugeGroup::new($group_name, info_list, $func); $collectors.add_metric_group(&mut collector); }; } /// A macro to create a closure for an AMD SMI function with dynamic arguments. /// /// This macro generates a closure that calls the specified function with the provided arguments. /// /// # Parameters /// - `$func`: The function to create the closure for. /// - `$arg`: The arguments to pass to the function. /// /// # Example /// /// ```rust /// # use amdsmi::*; /// # use amdsmi_collectors::*; /// /// # fn main() { /// let func = amdsmi_func!(amdsmi_get_temp_metric, AmdsmiTemperatureTypeT::AmdsmiTemperatureTypeEdge, AmdsmiTemperatureMetricT::AmdsmiTempCurrent); /// # } /// `` #[allow(unused_macros)] macro_rules! amdsmi_func { ($func:expr, $($arg:expr),*) => { move |handle| $func(handle, $($arg),*) }; }