@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
22use std:: fs;
33use std:: os:: unix:: fs:: MetadataExt ;
44use std:: path:: PathBuf ;
5+ use std:: process:: Command ;
56use sysinfo:: System ;
67
78use crate :: registry:: model_registry:: ModelRegistry ;
@@ -15,12 +16,20 @@ pub struct SystemInfo {
1516 pub architecture : String ,
1617 pub cpu_cores : usize ,
1718 pub total_memory : String ,
19+ pub gpu_info : Vec < GpuInfo > ,
1820 pub cache_dir : String ,
1921 pub cache_size : String ,
2022 pub models_count : usize ,
2123 pub running_models : usize ,
2224}
2325
26+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
27+ pub struct GpuInfo {
28+ pub name : String ,
29+ pub backend : String , // "CUDA", "Metal", "ROCm", or "Unknown"
30+ pub memory : Option < String > ,
31+ }
32+
2433impl SystemInfo {
2534 pub fn collect ( ) -> Self {
2635 let mut sys = System :: new_all ( ) ;
@@ -32,19 +41,173 @@ impl SystemInfo {
3241 let registry = ModelRegistry :: new ( None ) ;
3342 let models_count = registry. load_models ( ) . unwrap_or_default ( ) . len ( ) ;
3443
44+ let gpu_info = Self :: detect_gpus ( ) ;
45+
3546 SystemInfo {
3647 version : env ! ( "CARGO_PKG_VERSION" ) . to_string ( ) ,
3748 os : System :: name ( ) . unwrap_or_else ( || "Unknown" . to_string ( ) ) ,
3849 architecture : System :: cpu_arch ( ) . unwrap_or_else ( || "Unknown" . to_string ( ) ) ,
3950 cpu_cores : sys. cpus ( ) . len ( ) ,
4051 total_memory : format_size ( sys. total_memory ( ) ) ,
52+ gpu_info,
4153 cache_dir : cache_dir. to_string_lossy ( ) . to_string ( ) ,
4254 cache_size : format_size ( cache_size) ,
4355 models_count,
4456 running_models : 0 , // TODO: implement running models tracking
4557 }
4658 }
4759
60+ fn detect_gpus ( ) -> Vec < GpuInfo > {
61+ let mut gpus = Vec :: new ( ) ;
62+
63+ // Try NVIDIA GPUs first (Linux/Windows)
64+ if let Some ( nvidia_gpus) = Self :: detect_nvidia_gpus ( ) {
65+ gpus. extend ( nvidia_gpus) ;
66+ }
67+
68+ // Try Metal (macOS)
69+ if let Some ( metal_gpu) = Self :: detect_metal_gpu ( ) {
70+ gpus. push ( metal_gpu) ;
71+ }
72+
73+ // Try AMD ROCm (Linux)
74+ if let Some ( amd_gpus) = Self :: detect_amd_gpus ( ) {
75+ gpus. extend ( amd_gpus) ;
76+ }
77+
78+ gpus
79+ }
80+
81+ fn detect_nvidia_gpus ( ) -> Option < Vec < GpuInfo > > {
82+ let output = Command :: new ( "nvidia-smi" )
83+ . args ( [
84+ "--query-gpu=name,memory.total" ,
85+ "--format=csv,noheader,nounits" ,
86+ ] )
87+ . output ( )
88+ . ok ( ) ?;
89+
90+ if !output. status . success ( ) {
91+ return None ;
92+ }
93+
94+ let output_str = String :: from_utf8 ( output. stdout ) . ok ( ) ?;
95+ let mut gpus = Vec :: new ( ) ;
96+
97+ for line in output_str. lines ( ) {
98+ let parts: Vec < & str > = line. split ( ',' ) . map ( |s| s. trim ( ) ) . collect ( ) ;
99+ if parts. len ( ) >= 2 {
100+ gpus. push ( GpuInfo {
101+ name : parts[ 0 ] . to_string ( ) ,
102+ backend : "CUDA" . to_string ( ) ,
103+ memory : Some ( format ! ( "{} MB" , parts[ 1 ] ) ) ,
104+ } ) ;
105+ }
106+ }
107+
108+ if gpus. is_empty ( ) {
109+ None
110+ } else {
111+ Some ( gpus)
112+ }
113+ }
114+
115+ fn detect_metal_gpu ( ) -> Option < GpuInfo > {
116+ // Check if running on macOS
117+ if !cfg ! ( target_os = "macos" ) {
118+ return None ;
119+ }
120+
121+ let output = Command :: new ( "system_profiler" )
122+ . arg ( "SPDisplaysDataType" )
123+ . output ( )
124+ . ok ( ) ?;
125+
126+ if !output. status . success ( ) {
127+ return None ;
128+ }
129+
130+ let output_str = String :: from_utf8 ( output. stdout ) . ok ( ) ?;
131+ let lines: Vec < & str > = output_str. lines ( ) . collect ( ) ;
132+
133+ // Find GPU name and cores
134+ let mut gpu_name = None ;
135+ let mut core_count = None ;
136+
137+ for ( i, line) in lines. iter ( ) . enumerate ( ) {
138+ if line. contains ( "Chipset Model:" ) {
139+ let parts: Vec < & str > = line. split ( "Chipset Model:" ) . collect ( ) ;
140+ if parts. len ( ) >= 2 {
141+ let name = parts[ 1 ] . trim ( ) ;
142+ if !name. is_empty ( ) {
143+ gpu_name = Some ( name. to_string ( ) ) ;
144+
145+ // Look for core count in the next few lines
146+ for line in lines. iter ( ) . skip ( i + 1 ) . take ( 10 ) {
147+ if line. contains ( "Total Number of Cores:" ) {
148+ let core_parts: Vec < & str > =
149+ line. split ( "Total Number of Cores:" ) . collect ( ) ;
150+ if core_parts. len ( ) >= 2 {
151+ core_count = Some ( core_parts[ 1 ] . trim ( ) . to_string ( ) ) ;
152+ }
153+ break ;
154+ }
155+ }
156+ break ;
157+ }
158+ }
159+ }
160+ }
161+
162+ if let Some ( name) = gpu_name {
163+ let memory_str = core_count. map ( |cores| format ! ( "{} GPU cores" , cores) ) ;
164+
165+ return Some ( GpuInfo {
166+ name,
167+ backend : "Metal" . to_string ( ) ,
168+ memory : memory_str,
169+ } ) ;
170+ }
171+
172+ None
173+ }
174+
175+ fn detect_amd_gpus ( ) -> Option < Vec < GpuInfo > > {
176+ let output = Command :: new ( "rocm-smi" )
177+ . arg ( "--showproductname" )
178+ . output ( )
179+ . ok ( ) ?;
180+
181+ if !output. status . success ( ) {
182+ return None ;
183+ }
184+
185+ let output_str = String :: from_utf8 ( output. stdout ) . ok ( ) ?;
186+ let mut gpus = Vec :: new ( ) ;
187+
188+ for line in output_str. lines ( ) {
189+ if line. contains ( "Card series:" ) || line. contains ( "Card model:" ) {
190+ let parts: Vec < & str > = line. split ( ':' ) . collect ( ) ;
191+ if parts. len ( ) >= 2 {
192+ let name = parts[ 1 ] . trim ( ) . to_string ( ) ;
193+ if !name. is_empty ( ) {
194+ gpus. push ( GpuInfo {
195+ name,
196+ backend : "ROCm" . to_string ( ) ,
197+ memory : None ,
198+ } ) ;
199+ }
200+ }
201+ }
202+ }
203+
204+ if gpus. is_empty ( ) {
205+ None
206+ } else {
207+ Some ( gpus)
208+ }
209+ }
210+
48211 fn calculate_cache_size ( cache_dir : & PathBuf ) -> u64 {
49212 if !cache_dir. exists ( ) {
50213 return 0 ;
@@ -93,6 +256,22 @@ impl SystemInfo {
93256 println ! ( " Architecture: {}" , self . architecture) ;
94257 println ! ( " CPU Cores: {}" , self . cpu_cores) ;
95258 println ! ( " Total Memory: {}" , self . total_memory) ;
259+
260+ if !self . gpu_info . is_empty ( ) {
261+ for ( i, gpu) in self . gpu_info . iter ( ) . enumerate ( ) {
262+ if i == 0 {
263+ print ! ( " GPU: " ) ;
264+ } else {
265+ print ! ( " " ) ;
266+ }
267+ print ! ( "{} ({})" , gpu. name, gpu. backend) ;
268+ if let Some ( ref memory) = gpu. memory {
269+ print ! ( " - {}" , memory) ;
270+ }
271+ println ! ( ) ;
272+ }
273+ }
274+
96275 println ! ( ) ;
97276 println ! ( "PUMA Information:" ) ;
98277 println ! ( " PUMA Version: {}" , self . version) ;
0 commit comments