use crate::device::{
ChassisInfo, ChassisReader, CpuInfo, CpuReader, GpuInfo, GpuReader, MemoryInfo, MemoryReader,
MigGpuInfo, ProcessInfo, VgpuHostInfo, create_chassis_reader, get_cpu_readers, get_gpu_readers,
get_memory_readers,
};
use crate::error::Result;
use crate::storage::{StorageInfo, StorageReader, create_storage_reader};
#[cfg(target_os = "macos")]
use crate::device::macos_native::{acquire_native_metrics_manager, release_native_metrics_manager};
#[cfg(target_os = "linux")]
use crate::device::hlsmi::{initialize_hlsmi_manager, shutdown_hlsmi_manager};
#[cfg(target_os = "linux")]
use crate::device::platform_detection::has_gaudi;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeviceType {
NvidiaGpu,
AmdGpu,
AppleSiliconGpu,
NvidiaJetson,
IntelGaudi,
FuriosaNpu,
RebellionsNpu,
TenstorrentNpu,
GoogleTpu,
}
impl std::fmt::Display for DeviceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeviceType::NvidiaGpu => write!(f, "NVIDIA GPU"),
DeviceType::AmdGpu => write!(f, "AMD GPU"),
DeviceType::AppleSiliconGpu => write!(f, "Apple Silicon GPU"),
DeviceType::NvidiaJetson => write!(f, "NVIDIA Jetson"),
DeviceType::IntelGaudi => write!(f, "Intel Gaudi"),
DeviceType::FuriosaNpu => write!(f, "Furiosa NPU"),
DeviceType::RebellionsNpu => write!(f, "Rebellions NPU"),
DeviceType::TenstorrentNpu => write!(f, "Tenstorrent NPU"),
DeviceType::GoogleTpu => write!(f, "Google TPU"),
}
}
}
pub struct AllSmi {
gpu_readers: Vec<Box<dyn GpuReader>>,
cpu_readers: Vec<Box<dyn CpuReader>>,
memory_readers: Vec<Box<dyn MemoryReader>>,
chassis_reader: Box<dyn ChassisReader>,
storage_reader: Box<dyn StorageReader>,
#[cfg(target_os = "macos")]
_macos_initialized: bool,
#[cfg(target_os = "linux")]
_gaudi_initialized: bool,
}
impl AllSmi {
#[must_use = "AllSmi instance must be stored to access hardware information"]
pub fn new() -> Result<Self> {
Self::with_config(AllSmiConfig::default())
}
#[must_use = "AllSmi instance must be stored to access hardware information"]
pub fn with_config(config: AllSmiConfig) -> Result<Self> {
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
let _ = &config;
#[cfg(target_os = "macos")]
let macos_initialized = {
if crate::device::is_apple_silicon() {
match acquire_native_metrics_manager(config.sample_interval_ms) {
Ok(()) => true,
Err(e) => {
if config.verbose {
eprintln!("Warning: macOS native metrics init failed: {e}");
}
false
}
}
} else {
false
}
};
#[cfg(target_os = "linux")]
let gaudi_initialized = {
if has_gaudi() {
match initialize_hlsmi_manager(config.sample_interval_ms / 1000) {
Ok(()) => true,
Err(e) => {
if config.verbose {
eprintln!("Warning: Intel Gaudi hl-smi init failed: {e}");
}
false
}
}
} else {
false
}
};
let gpu_readers = get_gpu_readers();
let cpu_readers = get_cpu_readers();
let memory_readers = get_memory_readers();
let chassis_reader = create_chassis_reader();
let storage_reader = create_storage_reader();
Ok(AllSmi {
gpu_readers,
cpu_readers,
memory_readers,
chassis_reader,
storage_reader,
#[cfg(target_os = "macos")]
_macos_initialized: macos_initialized,
#[cfg(target_os = "linux")]
_gaudi_initialized: gaudi_initialized,
})
}
pub fn get_gpu_info(&self) -> Vec<GpuInfo> {
let mut all_gpus = Vec::new();
for reader in &self.gpu_readers {
all_gpus.extend(reader.get_gpu_info());
}
all_gpus
}
pub fn get_gpu_by_uuid(&self, uuid: &str) -> Option<GpuInfo> {
for reader in &self.gpu_readers {
if let Some(gpu) = reader.get_gpu_info_by_uuid(uuid) {
return Some(gpu);
}
}
None
}
pub fn refresh_gpu(&self, info: &mut GpuInfo) -> bool {
match self.get_gpu_by_uuid(&info.uuid) {
Some(fresh) => {
*info = fresh;
true
}
None => false,
}
}
pub fn get_process_info(&self) -> Vec<ProcessInfo> {
let mut all_processes = Vec::new();
for reader in &self.gpu_readers {
all_processes.extend(reader.get_process_info());
}
all_processes
}
pub fn get_vgpu_info(&self) -> Vec<VgpuHostInfo> {
let mut all_vgpu = Vec::new();
for reader in &self.gpu_readers {
all_vgpu.extend(reader.get_vgpu_info());
}
all_vgpu
}
pub fn get_mig_info(&self) -> Vec<MigGpuInfo> {
let mut all_mig = Vec::new();
for reader in &self.gpu_readers {
all_mig.extend(reader.get_mig_info());
}
all_mig
}
pub fn get_cpu_info(&self) -> Vec<CpuInfo> {
let mut all_cpus = Vec::new();
for reader in &self.cpu_readers {
all_cpus.extend(reader.get_cpu_info());
}
for (idx, cpu) in all_cpus.iter_mut().enumerate() {
cpu.index = idx as u32;
}
all_cpus
}
pub fn get_cpu_by_index(&self, index: u32) -> Option<CpuInfo> {
self.get_cpu_info().into_iter().find(|c| c.index == index)
}
pub fn refresh_cpu(&self, info: &mut CpuInfo) -> bool {
match self.get_cpu_by_index(info.index) {
Some(fresh) => {
*info = fresh;
true
}
None => false,
}
}
pub fn get_memory_info(&self) -> Vec<MemoryInfo> {
let mut all_memory = Vec::new();
for reader in &self.memory_readers {
all_memory.extend(reader.get_memory_info());
}
for (idx, mem) in all_memory.iter_mut().enumerate() {
mem.index = idx as u32;
}
all_memory
}
pub fn get_memory_by_index(&self, index: u32) -> Option<MemoryInfo> {
self.get_memory_info()
.into_iter()
.find(|m| m.index == index)
}
pub fn refresh_memory(&self, info: &mut MemoryInfo) -> bool {
match self.get_memory_by_index(info.index) {
Some(fresh) => {
*info = fresh;
true
}
None => false,
}
}
pub fn get_chassis_info(&self) -> Option<ChassisInfo> {
self.chassis_reader.get_chassis_info()
}
pub fn get_storage_info(&self) -> Vec<StorageInfo> {
self.storage_reader.get_storage_info()
}
pub fn gpu_reader_count(&self) -> usize {
self.gpu_readers.len()
}
pub fn has_gpus(&self) -> bool {
!self.gpu_readers.is_empty()
}
pub fn has_cpu_monitoring(&self) -> bool {
!self.cpu_readers.is_empty()
}
pub fn has_memory_monitoring(&self) -> bool {
!self.memory_readers.is_empty()
}
pub fn has_storage_monitoring(&self) -> bool {
true
}
}
impl Drop for AllSmi {
fn drop(&mut self) {
#[cfg(target_os = "macos")]
if self._macos_initialized {
release_native_metrics_manager();
}
#[cfg(target_os = "linux")]
if self._gaudi_initialized {
shutdown_hlsmi_manager();
}
}
}
unsafe impl Send for AllSmi {}
unsafe impl Sync for AllSmi {}
#[derive(Debug, Clone)]
pub struct AllSmiConfig {
pub sample_interval_ms: u64,
pub verbose: bool,
}
impl Default for AllSmiConfig {
fn default() -> Self {
Self {
sample_interval_ms: 1000,
verbose: false,
}
}
}
impl AllSmiConfig {
pub fn new() -> Self {
Self::default()
}
pub fn sample_interval(mut self, interval_ms: u64) -> Self {
self.sample_interval_ms = interval_ms;
self
}
pub fn verbose(mut self, verbose: bool) -> Self {
self.verbose = verbose;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allsmi_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AllSmi>();
}
#[test]
fn test_device_type_display() {
assert_eq!(DeviceType::NvidiaGpu.to_string(), "NVIDIA GPU");
assert_eq!(DeviceType::AppleSiliconGpu.to_string(), "Apple Silicon GPU");
assert_eq!(DeviceType::IntelGaudi.to_string(), "Intel Gaudi");
}
#[test]
fn test_config_default() {
let config = AllSmiConfig::default();
assert_eq!(config.sample_interval_ms, 1000);
assert!(!config.verbose);
}
#[test]
fn test_config_builder() {
let config = AllSmiConfig::new().sample_interval(500).verbose(true);
assert_eq!(config.sample_interval_ms, 500);
assert!(config.verbose);
}
#[test]
fn test_allsmi_new() {
let result = AllSmi::new();
assert!(result.is_ok());
let smi = result.unwrap();
let _ = smi.get_gpu_info();
let _ = smi.get_cpu_info();
let _ = smi.get_memory_info();
let _ = smi.get_process_info();
let _ = smi.get_chassis_info();
let _ = smi.get_storage_info();
}
#[test]
fn test_storage_info() {
let smi = AllSmi::new().unwrap();
assert!(smi.has_storage_monitoring());
let storage_info = smi.get_storage_info();
for storage in &storage_info {
assert!(!storage.mount_point.is_empty());
assert!(storage.available_bytes <= storage.total_bytes);
assert!(!storage.hostname.is_empty());
}
}
#[test]
fn test_allsmi_with_config() {
let config = AllSmiConfig::new().sample_interval(500);
let result = AllSmi::with_config(config);
assert!(result.is_ok());
}
}