@@ -60,6 +60,9 @@ pub struct XgmiThroughputMeta {
6060 pub gpu_name : String ,
6161 pub active_links : u32 ,
6262 pub links : Vec < crate :: xgmi:: XgmiLinkSnapshot > ,
63+ /// Live GPU health (util/vram/temp/power/clock) for the gauge strip
64+ /// and detail pane.
65+ pub metrics : Option < crate :: gpu:: GpuMetrics > ,
6366}
6467
6568/// Per-GPU NVLink metadata. The TUI uses this to show per-link details
@@ -75,6 +78,9 @@ pub struct NvLinkThroughputMeta {
7578 pub gpu_name : String ,
7679 pub active_links : u32 ,
7780 pub links : Vec < crate :: nvlink:: LinkSnapshot > ,
81+ /// Live GPU health (util/vram/temp/power/clock) for the gauge strip
82+ /// and detail pane.
83+ pub metrics : Option < crate :: gpu:: GpuMetrics > ,
7884}
7985
8086#[ derive( Clone , Debug ) ]
@@ -401,13 +407,15 @@ const HISTORY_LEN: usize = 60;
401407pub struct DeviceHistory {
402408 pub tx : Vec < f64 > ,
403409 pub rx : Vec < f64 > ,
410+ pub util : Vec < f64 > ,
404411}
405412
406413impl DeviceHistory {
407414 fn new ( ) -> Self {
408415 Self {
409416 tx : Vec :: with_capacity ( HISTORY_LEN ) ,
410417 rx : Vec :: with_capacity ( HISTORY_LEN ) ,
418+ util : Vec :: with_capacity ( HISTORY_LEN ) ,
411419 }
412420 }
413421
@@ -419,6 +427,13 @@ impl DeviceHistory {
419427 self . tx . push ( tx) ;
420428 self . rx . push ( rx) ;
421429 }
430+
431+ fn push_util ( & mut self , util : f64 ) {
432+ if self . util . len ( ) >= HISTORY_LEN {
433+ self . util . remove ( 0 ) ;
434+ }
435+ self . util . push ( util) ;
436+ }
422437}
423438
424439#[ derive( Clone ) ]
@@ -597,10 +612,20 @@ impl App {
597612
598613 fn update_history ( & mut self ) {
599614 for t in & self . throughputs {
600- self . history
615+ let util = t
616+ . nvlink
617+ . as_ref ( )
618+ . and_then ( |m| m. metrics . as_ref ( ) )
619+ . or_else ( || t. xgmi . as_ref ( ) . and_then ( |m| m. metrics . as_ref ( ) ) )
620+ . and_then ( |m| m. util_pct ) ;
621+ let entry = self
622+ . history
601623 . entry ( t. dev_name . clone ( ) )
602- . or_insert_with ( DeviceHistory :: new)
603- . push ( t. tx_gbps , t. rx_gbps ) ;
624+ . or_insert_with ( DeviceHistory :: new) ;
625+ entry. push ( t. tx_gbps , t. rx_gbps ) ;
626+ if let Some ( u) = util {
627+ entry. push_util ( u as f64 ) ;
628+ }
604629 }
605630 }
606631
@@ -1207,6 +1232,7 @@ fn compute_nvlink_throughputs(
12071232 gpu_name : gpu. gpu_name . clone ( ) ,
12081233 active_links : active,
12091234 links,
1235+ metrics : gpu. metrics . clone ( ) ,
12101236 } ) ,
12111237 xgmi : None ,
12121238 class : DeviceClass :: Nvlink ,
@@ -1306,6 +1332,7 @@ fn compute_xgmi_throughputs(
13061332 gpu_name : gpu. gpu_name . clone ( ) ,
13071333 active_links : active,
13081334 links,
1335+ metrics : gpu. metrics . clone ( ) ,
13091336 } ) ,
13101337 class : DeviceClass :: Xgmi ,
13111338 } ) ;
@@ -1339,6 +1366,7 @@ mod xgmi_tests {
13391366 link_gbps : Some ( 512.0 * active as f64 ) ,
13401367 correctable_errors : Some ( 0 ) ,
13411368 uncorrectable_errors : Some ( 0 ) ,
1369+ metrics : None ,
13421370 links,
13431371 }
13441372 }
@@ -1494,6 +1522,7 @@ mod nvlink_tests {
14941522 let prev = vec ! [ NvLinkSnapshot {
14951523 gpu_index: 0 ,
14961524 gpu_name: "H100" . to_string( ) ,
1525+ metrics: None ,
14971526 link_count: 2 ,
14981527 link_gbps: Some ( 100.0 ) ,
14991528 tx_bytes: None ,
@@ -1506,6 +1535,7 @@ mod nvlink_tests {
15061535 let curr = vec ! [ NvLinkSnapshot {
15071536 gpu_index: 0 ,
15081537 gpu_name: "H100" . to_string( ) ,
1538+ metrics: None ,
15091539 link_count: 3 ,
15101540 link_gbps: Some ( 150.0 ) ,
15111541 tx_bytes: None ,
@@ -1581,6 +1611,7 @@ mod nvlink_tests {
15811611 let prev = vec ! [ NvLinkSnapshot {
15821612 gpu_index: 0 ,
15831613 gpu_name: "H100" . to_string( ) ,
1614+ metrics: None ,
15841615 link_count: 2 ,
15851616 link_gbps: Some ( 100.0 ) ,
15861617 tx_bytes: None ,
@@ -1593,6 +1624,7 @@ mod nvlink_tests {
15931624 let curr = vec ! [ NvLinkSnapshot {
15941625 gpu_index: 0 ,
15951626 gpu_name: "H100" . to_string( ) ,
1627+ metrics: None ,
15961628 link_count: 2 ,
15971629 link_gbps: Some ( 100.0 ) ,
15981630 tx_bytes: None ,
@@ -1649,6 +1681,7 @@ mod nvlink_tests {
16491681 let prev = vec ! [ NvLinkSnapshot {
16501682 gpu_index: 0 ,
16511683 gpu_name: "H100" . to_string( ) ,
1684+ metrics: None ,
16521685 link_count: 2 ,
16531686 link_gbps: Some ( 100.0 ) ,
16541687 tx_bytes: None ,
@@ -1661,6 +1694,7 @@ mod nvlink_tests {
16611694 let curr = vec ! [ NvLinkSnapshot {
16621695 gpu_index: 0 ,
16631696 gpu_name: "H100" . to_string( ) ,
1697+ metrics: None ,
16641698 link_count: 2 ,
16651699 link_gbps: Some ( 100.0 ) ,
16661700 tx_bytes: None ,
@@ -1692,6 +1726,7 @@ mod nvlink_tests {
16921726 let curr = vec ! [ NvLinkSnapshot {
16931727 gpu_index: 1 ,
16941728 gpu_name: "A100" . to_string( ) ,
1729+ metrics: None ,
16951730 link_count: 1 ,
16961731 link_gbps: Some ( 50.0 ) ,
16971732 tx_bytes: None ,
@@ -1719,6 +1754,7 @@ mod nvlink_tests {
17191754 let prev = vec ! [ NvLinkSnapshot {
17201755 gpu_index: 0 ,
17211756 gpu_name: "H100" . to_string( ) ,
1757+ metrics: None ,
17221758 link_count: 2 ,
17231759 link_gbps: Some ( 100.0 ) ,
17241760 tx_bytes: None ,
@@ -1728,6 +1764,7 @@ mod nvlink_tests {
17281764 let curr_first = vec ! [ NvLinkSnapshot {
17291765 gpu_index: 0 ,
17301766 gpu_name: "H100" . to_string( ) ,
1767+ metrics: None ,
17311768 link_count: 3 ,
17321769 link_gbps: Some ( 150.0 ) ,
17331770 tx_bytes: None ,
@@ -1752,6 +1789,7 @@ mod nvlink_tests {
17521789 let curr_second = vec ! [ NvLinkSnapshot {
17531790 gpu_index: 0 ,
17541791 gpu_name: "H100" . to_string( ) ,
1792+ metrics: None ,
17551793 link_count: 3 ,
17561794 link_gbps: Some ( 150.0 ) ,
17571795 tx_bytes: None ,
@@ -1791,6 +1829,7 @@ mod nvlink_tests {
17911829 let prev = vec ! [ NvLinkSnapshot {
17921830 gpu_index: 0 ,
17931831 gpu_name: "H100" . to_string( ) ,
1832+ metrics: None ,
17941833 link_count: 1 ,
17951834 link_gbps: Some ( 50.0 ) ,
17961835 tx_bytes: None ,
@@ -1800,6 +1839,7 @@ mod nvlink_tests {
18001839 let curr = vec ! [ NvLinkSnapshot {
18011840 gpu_index: 0 ,
18021841 gpu_name: "H100" . to_string( ) ,
1842+ metrics: None ,
18031843 link_count: 1 ,
18041844 link_gbps: Some ( 50.0 ) ,
18051845 tx_bytes: None ,
@@ -1838,6 +1878,7 @@ mod nvlink_tests {
18381878 let prev = vec ! [ NvLinkSnapshot {
18391879 gpu_index: 2 ,
18401880 gpu_name: "B200" . to_string( ) ,
1881+ metrics: None ,
18411882 link_count: 1 ,
18421883 link_gbps: Some ( 100.0 ) ,
18431884 tx_bytes: None ,
@@ -1852,6 +1893,7 @@ mod nvlink_tests {
18521893 let curr = vec ! [ NvLinkSnapshot {
18531894 gpu_index: 2 ,
18541895 gpu_name: "B200" . to_string( ) ,
1896+ metrics: None ,
18551897 link_count: 1 ,
18561898 link_gbps: Some ( 100.0 ) ,
18571899 tx_bytes: None ,
@@ -1878,6 +1920,7 @@ mod nvlink_tests {
18781920 let prev_a = vec ! [ NvLinkSnapshot {
18791921 gpu_index: 0 ,
18801922 gpu_name: "H100" . to_string( ) ,
1923+ metrics: None ,
18811924 link_count: 3 ,
18821925 link_gbps: Some ( 150.0 ) ,
18831926 tx_bytes: None ,
@@ -1887,6 +1930,7 @@ mod nvlink_tests {
18871930 let curr_a = vec ! [ NvLinkSnapshot {
18881931 gpu_index: 0 ,
18891932 gpu_name: "H100" . to_string( ) ,
1933+ metrics: None ,
18901934 link_count: 3 ,
18911935 link_gbps: Some ( 150.0 ) ,
18921936 tx_bytes: None ,
@@ -1906,6 +1950,7 @@ mod nvlink_tests {
19061950 let curr_b = vec ! [ NvLinkSnapshot {
19071951 gpu_index: 0 ,
19081952 gpu_name: "H100" . to_string( ) ,
1953+ metrics: None ,
19091954 link_count: 3 ,
19101955 link_gbps: Some ( 150.0 ) ,
19111956 tx_bytes: None ,
@@ -1946,6 +1991,7 @@ mod nvlink_tests {
19461991 let prev = vec ! [ NvLinkSnapshot {
19471992 gpu_index: 0 ,
19481993 gpu_name: "H100" . to_string( ) ,
1994+ metrics: None ,
19491995 link_count: 2 ,
19501996 link_gbps: Some ( 100.0 ) ,
19511997 tx_bytes: Some ( 10_000_000_000 ) ,
@@ -1958,6 +2004,7 @@ mod nvlink_tests {
19582004 let curr = vec ! [ NvLinkSnapshot {
19592005 gpu_index: 0 ,
19602006 gpu_name: "H100" . to_string( ) ,
2007+ metrics: None ,
19612008 link_count: 2 ,
19622009 link_gbps: Some ( 100.0 ) ,
19632010 tx_bytes: Some ( 15_000_000_000 ) ,
@@ -2010,6 +2057,7 @@ mod nvlink_tests {
20102057 gpu_name : "H100" . to_string ( ) ,
20112058 active_links : 2 ,
20122059 links : Vec :: new ( ) ,
2060+ metrics : None ,
20132061 } ) ,
20142062 xgmi : None ,
20152063 class : DeviceClass :: Nvlink ,
@@ -2065,6 +2113,7 @@ mod nvlink_tests {
20652113 gpu_name : "H100" . to_string ( ) ,
20662114 active_links : 3 ,
20672115 links : Vec :: new ( ) ,
2116+ metrics : None ,
20682117 } ) ,
20692118 xgmi : None ,
20702119 class : DeviceClass :: Nvlink ,
@@ -2309,6 +2358,10 @@ mod apply_snapshot_tests {
23092358 let gpu = crate :: nvlink:: NvLinkSnapshot {
23102359 gpu_index : 0 ,
23112360 gpu_name : "test-gpu" . to_string ( ) ,
2361+ metrics : Some ( crate :: gpu:: GpuMetrics {
2362+ util_pct : Some ( 42 ) ,
2363+ ..Default :: default ( )
2364+ } ) ,
23122365 link_count : 0 ,
23132366 link_gbps : None ,
23142367 tx_bytes : Some ( tx_bytes) ,
@@ -2325,6 +2378,17 @@ mod apply_snapshot_tests {
23252378 }
23262379 }
23272380
2381+ #[ test]
2382+ fn gpu_util_flows_into_history ( ) {
2383+ let mut app = App :: new ( ) ;
2384+ let t0 = Instant :: now ( ) ;
2385+ app. apply_snapshot ( gpu_snapshot ( 0 , t0) ) ;
2386+ app. apply_snapshot ( gpu_snapshot ( 1_000 , t0 + Duration :: from_secs ( 2 ) ) ) ;
2387+ let h = app. history . get ( "nvidia0" ) . expect ( "history for nvidia0" ) ;
2388+ assert_eq ! ( h. util. len( ) , 2 ) ;
2389+ assert_eq ! ( h. util[ 0 ] , 42.0 ) ;
2390+ }
2391+
23282392 #[ test]
23292393 fn rates_use_per_subsystem_read_timestamps ( ) {
23302394 // A slow pass must not skew a subsystem read later in it: the GPU
0 commit comments