Skip to content

Commit 1a58db7

Browse files
committed
updte ui
Signed-off-by: chang-ning <spiderpower02@gmail.com>
1 parent 9ec99b3 commit 1a58db7

1 file changed

Lines changed: 65 additions & 115 deletions

File tree

src/tui/ui.rs

Lines changed: 65 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -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

326285
fn 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+
411376
fn 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)]
24682434
mod 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

Comments
 (0)