@@ -274,53 +274,12 @@ fn ratio_bar(ratio: f64, width: usize) -> String {
274274 bar
275275}
276276
277- /// One " GPUs " panel line: name + util/mem gauges + temp/power/clock.
278- fn gpu_gauge_line ( dev : & str , name : & str , m : & GpuMetrics , tc : & ThemeColors ) -> Line < ' static > {
279- const GAUGE_W : usize = 16 ;
280- let mut spans = vec ! [ styled(
281- & format!( " {:<8} {:<16} " , dev, truncate( name, 16 ) ) ,
282- tc. fg,
283- false ,
284- ) ] ;
285- match m. util_pct {
286- Some ( u) => {
287- let r = u as f64 / 100.0 ;
288- spans. push ( styled ( "Util[" , tc. muted , false ) ) ;
289- spans. push ( styled ( & ratio_bar ( r, GAUGE_W ) , capacity_color ( r, tc) , false ) ) ;
290- spans. push ( styled (
291- & format ! ( " {:>3}%] " , u) ,
292- capacity_color ( r, tc) ,
293- false ,
294- ) ) ;
295- }
296- None => spans. push ( styled ( "Util[ - ] " , tc. muted , false ) ) ,
297- }
298- match ( m. vram_used_mb , m. vram_total_mb ) {
299- ( Some ( u) , Some ( t) ) if t > 0 => {
300- let r = u as f64 / t as f64 ;
301- spans. push ( styled ( "Mem[" , tc. muted , false ) ) ;
302- spans. push ( styled ( & ratio_bar ( r, GAUGE_W ) , capacity_color ( r, tc) , false ) ) ;
303- spans. push ( styled (
304- & format ! ( " {:.1}/{:.1}G] " , u as f64 / 1024.0 , t as f64 / 1024.0 ) ,
305- capacity_color ( r, tc) ,
306- false ,
307- ) ) ;
308- }
309- _ => spans. push ( styled ( "Mem[ - ] " , tc. muted , false ) ) ,
310- }
311- match m. temp_c {
312- Some ( t) => spans. push ( styled ( & format ! ( "{}C " , t) , temp_color ( t, tc) , false ) ) ,
313- None => spans. push ( styled ( "-C " , tc. muted , false ) ) ,
277+ /// "██████░░░░ 87%" gauge text for a 0-100 percent, or "-" when None.
278+ fn pct_gauge_text ( pct : Option < u32 > ) -> String {
279+ match pct {
280+ Some ( p) => format ! ( "{} {:>3}%" , ratio_bar( p as f64 / 100.0 , 10 ) , p) ,
281+ None => "-" . to_string ( ) ,
314282 }
315- spans. push ( match m. power_w {
316- Some ( p) => styled ( & format ! ( "{:.0}W " , p) , tc. fg , false ) ,
317- None => styled ( "-W " , tc. muted , false ) ,
318- } ) ;
319- spans. push ( match m. clock_mhz {
320- Some ( c) => styled ( & format ! ( "{}MHz" , c) , tc. fg , false ) ,
321- None => styled ( "-MHz" , tc. muted , false ) ,
322- } ) ;
323- Line :: from ( spans)
324283}
325284
326285fn column_cell ( col : & TableColumn , t : & PortThroughput , tc : & ThemeColors ) -> Cell < ' static > {
@@ -408,18 +367,45 @@ fn gpu_errs(t: &PortThroughput) -> String {
408367 sum. to_string ( )
409368}
410369
370+ /// Health metrics from whichever GPU meta the row carries.
371+ fn gpu_metrics ( t : & PortThroughput ) -> Option < & GpuMetrics > {
372+ let nv = t. nvlink . as_ref ( ) . and_then ( |m| m. metrics . as_ref ( ) ) ;
373+ nv. or_else ( || t. xgmi . as_ref ( ) . and_then ( |m| m. metrics . as_ref ( ) ) )
374+ }
375+
411376fn gpu_row_cells ( t : & PortThroughput , tc : & ThemeColors ) -> Vec < Cell < ' static > > {
412- let speed = t
413- . link_gbps
414- . map ( |g| format ! ( "{:.0}" , g) )
415- . unwrap_or_else ( || "-" . to_string ( ) ) ;
377+ let m = gpu_metrics ( t) ;
378+ let util = m. and_then ( |m| m. util_pct ) ;
379+ let mem = m. and_then ( |m| match ( m. vram_used_mb , m. vram_total_mb ) {
380+ ( Some ( used) , Some ( total) ) if total > 0 => {
381+ Some ( ( used as f64 / total as f64 * 100.0 ) . round ( ) as u32 )
382+ }
383+ _ => None ,
384+ } ) ;
385+ let pct_cell = |p : Option < u32 > | {
386+ let color = match p {
387+ Some ( v) => capacity_color ( v as f64 / 100.0 , tc) ,
388+ None => tc. muted ,
389+ } ;
390+ Cell :: from ( pct_gauge_text ( p) ) . style ( Style :: default ( ) . fg ( color) )
391+ } ;
392+ let temp_cell = match m. and_then ( |m| m. temp_c ) {
393+ Some ( t) => Cell :: from ( format ! ( "{}C" , t) ) . style ( Style :: default ( ) . fg ( temp_color ( t, tc) ) ) ,
394+ None => Cell :: from ( "-" ) . style ( Style :: default ( ) . fg ( tc. muted ) ) ,
395+ } ;
396+ let pwr_cell = match m. and_then ( |m| m. power_w ) {
397+ Some ( p) => Cell :: from ( format ! ( "{:.0}W" , p) ) . style ( Style :: default ( ) . fg ( tc. fg ) ) ,
398+ None => Cell :: from ( "-" ) . style ( Style :: default ( ) . fg ( tc. muted ) ) ,
399+ } ;
416400 // Bar and Gbps live in separate cells styled with gbps_color, exactly
417401 // like the RDMA table, so the two tabs stay visually aligned.
418402 vec ! [
419403 Cell :: from( t. dev_name. clone( ) ) . style( Style :: default ( ) . fg( tc. fg) ) ,
420404 Cell :: from( gpu_meta_name( t) ) . style( Style :: default ( ) . fg( tc. muted) ) ,
421- Cell :: from( t. port_label. clone( ) . unwrap_or_default( ) ) . style( Style :: default ( ) . fg( tc. fg) ) ,
422- Cell :: from( speed) . style( Style :: default ( ) . fg( tc. fg) ) ,
405+ pct_cell( util) ,
406+ pct_cell( mem) ,
407+ temp_cell,
408+ pwr_cell,
423409 Cell :: from( gbps_bar( t. tx_gbps, t. link_gbps) ) . style( Style :: default ( ) . fg( throughput_color(
424410 t. tx_gbps,
425411 t. link_gbps,
@@ -440,6 +426,7 @@ fn gpu_row_cells(t: &PortThroughput, tc: &ThemeColors) -> Vec<Cell<'static>> {
440426 t. link_gbps,
441427 tc,
442428 ) ) ) ,
429+ Cell :: from( t. port_label. clone( ) . unwrap_or_default( ) ) . style( Style :: default ( ) . fg( tc. fg) ) ,
443430 Cell :: from( gpu_errs( t) ) . style( Style :: default ( ) . fg( tc. warning) ) ,
444431 ]
445432}
@@ -490,38 +477,8 @@ fn draw_gpu_table(frame: &mut Frame, app: &mut App, area: Rect, tc: &ThemeColors
490477 return ;
491478 }
492479
493- // Gauge strip above the table when any GPU reported health metrics.
494- let gauges: Vec < Line > = display
495- . iter ( )
496- . filter_map ( |t| {
497- let ( name, m) = match ( & t. nvlink , & t. xgmi ) {
498- ( Some ( nv) , _) => ( nv. gpu_name . as_str ( ) , nv. metrics . as_ref ( ) ?) ,
499- ( _, Some ( xg) ) => ( xg. gpu_name . as_str ( ) , xg. metrics . as_ref ( ) ?) ,
500- _ => return None ,
501- } ;
502- Some ( gpu_gauge_line ( & t. dev_name , name, m, tc) )
503- } )
504- . collect ( ) ;
505- let mut area = area;
506- if !gauges. is_empty ( ) {
507- // Leave the link table at least 8 rows.
508- let strip_h = ( gauges. len ( ) as u16 + 2 ) . min ( area. height . saturating_sub ( 8 ) ) ;
509- let chunks = Layout :: default ( )
510- . direction ( Direction :: Vertical )
511- . constraints ( [ Constraint :: Length ( strip_h) , Constraint :: Min ( 0 ) ] )
512- . split ( area) ;
513- let block = Block :: default ( )
514- . borders ( Borders :: ALL )
515- . border_set ( super :: glyphs:: border ( ) )
516- . border_style ( Style :: default ( ) . fg ( tc. border ) )
517- . title ( " GPUs " )
518- . title_style ( Style :: default ( ) . fg ( tc. accent ) ) ;
519- frame. render_widget ( Paragraph :: new ( gauges) . block ( block) , chunks[ 0 ] ) ;
520- area = chunks[ 1 ] ;
521- }
522-
523480 let header = Row :: new ( vec ! [
524- "Device" , "Name" , "Links " , "Speed " , "TX" , "Gbps" , "RX" , "Gbps" , "Errs" ,
481+ "Device" , "Name" , "Util " , "Mem " , "Temp" , "Pwr" , " TX", "Gbps" , "RX" , "Gbps" , "Links ", "Errs" ,
525482 ] )
526483 . style (
527484 Style :: default ( )
@@ -535,18 +492,21 @@ fn draw_gpu_table(frame: &mut Frame, app: &mut App, area: Rect, tc: &ThemeColors
535492 . map ( |t| Row :: new ( gpu_row_cells ( t, tc) ) )
536493 . collect ( ) ;
537494
538- // Shared columns copy the RDMA table's widths (Device 16, bars
539- // BAR_WIDTH, Gbps 9) so switching tabs doesn't shift the layout .
495+ // Util/Mem hold a 10-char gauge + " 100%" (15 chars); bars reuse
496+ // BAR_WIDTH so the TX/RX meters match the RDMA tab visually .
540497 let widths = [
498+ Constraint :: Length ( 9 ) ,
499+ Constraint :: Length ( 16 ) ,
541500 Constraint :: Length ( 16 ) ,
542- Constraint :: Length ( 22 ) ,
501+ Constraint :: Length ( 16 ) ,
502+ Constraint :: Length ( 5 ) ,
543503 Constraint :: Length ( 6 ) ,
544- Constraint :: Length ( 7 ) ,
545504 Constraint :: Length ( BAR_WIDTH as u16 ) ,
546- Constraint :: Length ( 9 ) ,
505+ Constraint :: Length ( 7 ) ,
547506 Constraint :: Length ( BAR_WIDTH as u16 ) ,
548- Constraint :: Length ( 9 ) ,
549- Constraint :: Length ( 9 ) ,
507+ Constraint :: Length ( 7 ) ,
508+ Constraint :: Length ( 6 ) ,
509+ Constraint :: Length ( 5 ) ,
550510 ] ;
551511
552512 let table = Table :: new ( rows, widths)
@@ -811,11 +771,17 @@ fn append_gpu_health(
811771 ] ) ) ;
812772 }
813773 }
774+ let mem = match ( m. vram_used_mb , m. vram_total_mb ) {
775+ ( Some ( u) , Some ( t) ) if t > 0 => {
776+ format ! ( "{:.1}/{:.1}G" , u as f64 / 1024.0 , t as f64 / 1024.0 )
777+ }
778+ _ => "-" . to_string ( ) ,
779+ } ;
814780 let temp = m. temp_c . map_or ( "-" . to_string ( ) , |t| format ! ( "{}C" , t) ) ;
815781 let pwr = m. power_w . map_or ( "-" . to_string ( ) , |p| format ! ( "{:.0}W" , p) ) ;
816782 let clk = m. clock_mhz . map_or ( "-" . to_string ( ) , |c| format ! ( "{}MHz" , c) ) ;
817783 lines. push ( Line :: from ( styled (
818- & format ! ( " Temp {} Pwr {} Clk {}" , temp, pwr, clk) ,
784+ & format ! ( " Mem {} Temp {} Pwr {} Clk {}" , mem , temp, pwr, clk) ,
819785 tc. fg ,
820786 false ,
821787 ) ) ) ;
@@ -2467,33 +2433,17 @@ mod color_tests {
24672433#[ cfg( test) ]
24682434mod gauge_tests {
24692435 use super :: * ;
2470- use crate :: gpu:: GpuMetrics ;
2471- use crate :: tui:: theme:: Theme ;
24722436
2473- fn line_text ( line : & Line < ' _ > ) -> String {
2474- line. spans . iter ( ) . map ( |s| s. content . as_ref ( ) ) . collect ( )
2437+ #[ test]
2438+ fn pct_gauge_text_formats_percent_with_bar ( ) {
2439+ let text = pct_gauge_text ( Some ( 87 ) ) ;
2440+ assert ! ( text. contains( "87%" ) , "{text:?}" ) ;
2441+ let ( fill, _) = crate :: tui:: glyphs:: meter ( ) ;
2442+ assert ! ( text. contains( fill) , "expected bar glyph in {text:?}" ) ;
24752443 }
24762444
24772445 #[ test]
2478- fn gauge_line_formats_metrics_and_dashes_missing ( ) {
2479- let tc = Theme :: Default . colors ( ) ;
2480- let m = GpuMetrics {
2481- util_pct : Some ( 87 ) ,
2482- vram_used_mb : Some ( 69_837 ) ,
2483- vram_total_mb : Some ( 81_920 ) ,
2484- temp_c : Some ( 72 ) ,
2485- power_w : Some ( 540.0 ) ,
2486- clock_mhz : None ,
2487- } ;
2488- let text = line_text ( & gpu_gauge_line ( "nvidia0" , "NVIDIA GB10" , & m, & tc) ) ;
2489- assert ! ( text. contains( "nvidia0" ) , "{text:?}" ) ;
2490- assert ! ( text. contains( "87%" ) , "{text:?}" ) ;
2491- assert ! ( text. contains( "68.2/80.0G" ) , "{text:?}" ) ;
2492- assert ! ( text. contains( "72C" ) , "{text:?}" ) ;
2493- assert ! ( text. contains( "540W" ) , "{text:?}" ) ;
2494- assert ! (
2495- text. contains( "-MHz" ) ,
2496- "missing clock renders '-MHz': {text:?}"
2497- ) ;
2446+ fn pct_gauge_text_none_is_dash ( ) {
2447+ assert_eq ! ( pct_gauge_text( None ) , "-" ) ;
24982448 }
24992449}
0 commit comments