55use std:: ffi:: CStr ;
66use std:: io;
77use std:: ptr;
8- use std:: sync:: OnceLock ;
8+ use std:: sync:: { Mutex , OnceLock } ;
99
1010/// Snapshot of a single XGMI link at one sample.
1111#[ derive( Clone , Debug ) ]
@@ -255,7 +255,7 @@ pub fn read_all_xgmi_stats() -> io::Result<Vec<XgmiSnapshot>> {
255255
256256 let mut snapshots = Vec :: with_capacity ( handles. len ( ) ) ;
257257 for idx in 0 ..handles. len ( ) {
258- if let Some ( snap) = read_processor_snapshot ( api, & handles, & bdfs, names, speeds, idx) {
258+ if let Some ( snap) = read_processor_snapshot ( api, & handles, & bdfs, names, & speeds, idx) {
259259 snapshots. push ( snap) ;
260260 }
261261 }
@@ -275,15 +275,33 @@ fn gpu_names(api: &Amdsmi, handles: &[ffi::ProcessorHandle]) -> &'static [String
275275 } )
276276}
277277
278- /// Per-GPU (bit_rate, bandwidth) Gb/s, read once and cached by GPU index:
279- /// link speed is static and `amdsmi_get_pcie_info` costs ~0.5 ms per call —
280- /// roughly half of the whole refresh when polled for every GPU each second.
281- fn link_speeds (
282- api : & Amdsmi ,
283- handles : & [ ffi:: ProcessorHandle ] ,
284- ) -> & ' static [ ( Option < f64 > , Option < f64 > ) ] {
285- static SPEEDS : OnceLock < Vec < ( Option < f64 > , Option < f64 > ) > > = OnceLock :: new ( ) ;
286- SPEEDS . get_or_init ( || handles. iter ( ) . map ( |& h| read_link_speed ( api, h) ) . collect ( ) )
278+ /// Per-link (bit_rate, bandwidth) in Gb/s from PCIe static info.
279+ type LinkSpeed = ( Option < f64 > , Option < f64 > ) ;
280+
281+ /// Per-GPU link speeds. Link speed is static and `amdsmi_get_pcie_info`
282+ /// costs ~0.5 ms (two SMU round-trips), so successful reads are cached by
283+ /// GPU index; failed reads retry on the next poll rather than degrading
284+ /// speed reporting for the process lifetime.
285+ fn link_speeds ( api : & Amdsmi , handles : & [ ffi:: ProcessorHandle ] ) -> Vec < LinkSpeed > {
286+ static CACHE : Mutex < Vec < Option < LinkSpeed > > > = Mutex :: new ( Vec :: new ( ) ) ;
287+ let mut cache = CACHE . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
288+ if cache. len ( ) < handles. len ( ) {
289+ cache. resize ( handles. len ( ) , None ) ;
290+ }
291+ handles
292+ . iter ( )
293+ . zip ( cache. iter_mut ( ) )
294+ . map ( |( & handle, slot) | match slot {
295+ Some ( speed) => * speed,
296+ None => {
297+ let speed = read_link_speed ( api, handle) ;
298+ if speed != ( None , None ) {
299+ * slot = Some ( speed) ;
300+ }
301+ speed
302+ }
303+ } )
304+ . collect ( )
287305}
288306
289307/// Flatten socket/processor enumeration into one ordered GPU handle list.
@@ -331,7 +349,7 @@ fn read_processor_snapshot(
331349 handles : & [ ffi:: ProcessorHandle ] ,
332350 bdfs : & [ Option < u64 > ] ,
333351 names : & [ String ] ,
334- speeds : & [ ( Option < f64 > , Option < f64 > ) ] ,
352+ speeds : & [ LinkSpeed ] ,
335353 idx : usize ,
336354) -> Option < XgmiSnapshot > {
337355 let handle = handles[ idx] ;
@@ -408,7 +426,7 @@ fn is_xgmi_peer(api: &Amdsmi, src: ffi::ProcessorHandle, dst: ffi::ProcessorHand
408426
409427/// Per-link (bit_rate, bandwidth) in Gb/s from PCIe static info, mirroring
410428/// amd-smi: bit_rate = max_pcie_speed/1000, bandwidth = bit_rate * width.
411- fn read_link_speed ( api : & Amdsmi , handle : ffi:: ProcessorHandle ) -> ( Option < f64 > , Option < f64 > ) {
429+ fn read_link_speed ( api : & Amdsmi , handle : ffi:: ProcessorHandle ) -> LinkSpeed {
412430 let mut info: Padded < ffi:: PcieInfo > = Padded :: zeroed ( ) ;
413431 let rc = unsafe { ( api. get_pcie_info ) ( handle, & mut info. value ) } ;
414432 if rc != ffi:: AMDSMI_STATUS_SUCCESS || info. value . max_pcie_speed == 0 {
0 commit comments