From 51ee688d2b7cfcc1d2fb18107b901c0b718cdcb4 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Mon, 10 Nov 2025 17:31:45 +0800 Subject: [PATCH] feat(tui/device): add extra information to bar charts --- nvitop/tui/library/utils.py | 18 +- nvitop/tui/screens/main/__init__.py | 2 +- nvitop/tui/screens/main/panels/device.py | 236 +++++++++++++---------- 3 files changed, 153 insertions(+), 103 deletions(-) diff --git a/nvitop/tui/library/utils.py b/nvitop/tui/library/utils.py index 61ac8b3..bfba074 100644 --- a/nvitop/tui/library/utils.py +++ b/nvitop/tui/library/utils.py @@ -98,7 +98,14 @@ def cut_string( # pylint: disable=disallowed-name -def make_bar(prefix: str, percent: float | str, width: int, *, extra_text: str = '') -> str: +def make_bar( + prefix: str, + percent: float | str, + width: int, + *, + extra_text: str = '', + swap_text: bool = False, +) -> str: bar = f'{prefix}: ' if percent != NA and not (isinstance(percent, float) and not math.isfinite(percent)): if isinstance(percent, str) and percent.endswith('%'): @@ -116,6 +123,11 @@ def make_bar(prefix: str, percent: float | str, width: int, *, extra_text: str = else: bar += '░' * (width - len(bar) - 4) text = 'N/A' - if extra_text and len(f'{bar} {text} {extra_text}') <= width: - return f'{bar} {text}'.ljust(width - len(extra_text) - 1) + f' {extra_text}' + if extra_text: + if len(f'{bar} {text} {extra_text}') <= width: + if swap_text: + text, extra_text = extra_text, text + return f'{bar} {text}'.ljust(width - len(extra_text) - 3) + f' {extra_text}' + if len(f'{bar} {extra_text}') <= width and swap_text: + return f'{bar} {extra_text}'.ljust(width) return f'{bar} {text}'.ljust(width) diff --git a/nvitop/tui/screens/main/__init__.py b/nvitop/tui/screens/main/__init__.py index 1d8bc7f..fa6d54b 100644 --- a/nvitop/tui/screens/main/__init__.py +++ b/nvitop/tui/screens/main/__init__.py @@ -189,7 +189,7 @@ class MainScreen(BaseSelectableScreen): # pylint: disable=too-many-instance-att def print(self) -> None: if self.device_count > 0: print_width = min(panel.print_width() for panel in self.container) - self.width = max(print_width, min(self.width, 128)) + self.width = max(print_width, min(self.width, 140)) else: self.width = 79 for panel in self.container: diff --git a/nvitop/tui/screens/main/panels/device.py b/nvitop/tui/screens/main/panels/device.py index bb38171..8dc7ac3 100644 --- a/nvitop/tui/screens/main/panels/device.py +++ b/nvitop/tui/screens/main/panels/device.py @@ -353,39 +353,43 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes if draw_bars: left_width = (remaining_width - 6 + 1) // 2 - 1 right_width = (remaining_width - 6) // 2 + 1 - matrix: list[tuple[int, int, int, str, float, str]] = [] + matrix: list[list[tuple[int, int, str, float, str, str]]] = [] if device.is_mig_device: matrix = [ - ( - self.x + 80, - y_start, - remaining_width - 3, - 'MEM', - device.memory_percent, - device.memory_display_color, - ), + [ + ( + self.x + 80, + remaining_width - 3, + 'MEM', + device.memory_percent, + device.memory_display_color, + device.memory_used_human, + ), + ], ] if remaining_width >= 44 and len(prev_device_index) == 1: self.addstr(y_start - 1, self.x + 80 + left_width + 1, '┴') elif self.compact: if remaining_width >= 44: matrix = [ - ( - self.x + 80, - y_start, - left_width, - 'MEM', - device.memory_percent, - device.memory_display_color, - ), - ( - self.x + 80 + left_width + 3, - y_start, - right_width, - 'UTL', - device.gpu_utilization, - device.gpu_display_color, - ), + [ + ( + self.x + 80, + left_width, + 'MEM', + device.memory_percent, + device.memory_display_color, + device.memory_used_human, + ), + ( + self.x + 80 + left_width + 3, + right_width, + 'UTL', + device.gpu_utilization, + device.gpu_display_color, + f'@ {device.clock_infos.sm}MHz', + ), + ], ] separator = '┼' if index > 0 else '╤' if len(prev_device_index) == 2: @@ -395,50 +399,56 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes self.addstr(y_start + 1, self.x + 80 + left_width + 1, '╧') else: matrix = [ - ( - self.x + 80, - y_start, - remaining_width - 3, - 'MEM', - device.memory_percent, - device.memory_display_color, - ), + [ + ( + self.x + 80, + remaining_width - 3, + 'MEM', + device.memory_percent, + device.memory_display_color, + device.memory_used_human, + ), + ], ] else: if remaining_width >= 44: matrix = [ - ( - self.x + 80, - y_start, - left_width, - 'MEM', - device.memory_percent, - device.memory_display_color, - ), - ( - self.x + 80, - y_start + 1, - left_width, - 'UTL', - device.gpu_utilization, - device.gpu_display_color, - ), - ( - self.x + 80 + left_width + 3, - y_start, - right_width, - 'MBW', - device.memory_utilization, - device.bandwidth_display_color, - ), - ( - self.x + 80 + left_width + 3, - y_start + 1, - right_width, - 'PWR', - device.power_utilization, - device.power_display_color, - ), + [ + ( + self.x + 80, + left_width, + 'MEM', + device.memory_percent, + device.memory_display_color, + device.memory_used_human, + ), + ( + self.x + 80 + left_width + 3, + right_width, + 'MBW', + device.memory_utilization, + device.bandwidth_display_color, + f'@ {device.clock_infos.memory}MHz', + ), + ], + [ + ( + self.x + 80, + left_width, + 'UTL', + device.gpu_utilization, + device.gpu_display_color, + f'@ {device.clock_infos.sm}MHz', + ), + ( + self.x + 80 + left_width + 3, + right_width, + 'PWR', + device.power_utilization, + device.power_display_color, + f'{device.power_status.partition(" / ")[0]}', + ), + ], ] separator = '┼' if index > 0 else '╤' if len(prev_device_index) == 2: @@ -448,40 +458,59 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes self.addstr(y_start + 2, self.x + 80 + left_width + 1, '╧') else: matrix = [ - ( - self.x + 80, - y_start, - remaining_width - 3, - 'MEM', - device.memory_percent, - device.memory_display_color, - ), - ( - self.x + 80, - y_start + 1, - remaining_width - 3, - 'UTL', - device.gpu_utilization, - device.gpu_display_color, - ), + [ + ( + self.x + 80, + remaining_width - 3, + 'MEM', + device.memory_percent, + device.memory_display_color, + device.memory_used_human, + ), + ], + [ + ( + self.x + 80, + remaining_width - 3, + 'UTL', + device.gpu_utilization, + device.gpu_display_color, + f'@ {device.clock_infos.sm}MHz', + ), + ], ] - for x_offset, y, width, prefix, utilization, color in matrix: - # pylint: disable-next=disallowed-name - bar = make_bar(prefix, utilization, width=width) - self.addstr(y, x_offset - 2, f'│ {bar}') - if self.TERM_256COLOR: - parts = bar.rstrip().split(' ') - prefix_len = len(parts[0]) - bar_len = len(parts[1]) - full_bar_len = width - prefix_len - 5 - self.color_at(y, x_offset, width=width, fg=float(bar_len / full_bar_len)) - for i, x in enumerate( - range(x_offset + prefix_len + 1, x_offset + prefix_len + 1 + bar_len), - ): - self.color_at(y, x, width=1, fg=float(i / full_bar_len)) - else: - self.color_at(y, x_offset, width=width, fg=color, attr=attr) + for y, row in enumerate(matrix, start=y_start): + for x_offset, width, prefix, utilization, color, extra_text in row: + # pylint: disable-next=disallowed-name + bar = make_bar( + prefix, + utilization, + width=width, + extra_text=extra_text, + swap_text=not extra_text.endswith('MHz'), + ) + self.addstr(y, x_offset, f'{bar} │') + if self.TERM_256COLOR: + parts = bar.rstrip().split(' ') + prefix_len = len(parts[0]) + bar_len = len(parts[1]) + full_bar_len = width - prefix_len - 5 + self.color_at( + y, + x_offset, + width=width, + fg=float(bar_len / full_bar_len), + ) + for i, x in enumerate( + range( + x_offset + prefix_len + 1, + x_offset + prefix_len + 1 + bar_len, + ), + ): + self.color_at(y, x, width=1, fg=float(i / full_bar_len)) + else: + self.color_at(y, x_offset, width=width, fg=color, attr=attr) y_start += len(fmts) prev_device_index = device.tuple_index @@ -546,7 +575,7 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes left_width = (remaining_width - 6 + 1) // 2 - 1 right_width = (remaining_width - 6) // 2 + 1 - matrix: list[list[tuple[str, float, str, int]]] = [] + matrix: list[list[tuple[str, float, str, int, str]]] = [] if device.is_mig_device: matrix = [ [ @@ -555,6 +584,7 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes device.memory_percent, device.memory_display_color, remaining_width - 3, + device.memory_used_human, ), ], ] @@ -573,12 +603,14 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes device.memory_percent, device.memory_display_color, left_width, + device.memory_used_human, ), ( 'MBW', device.memory_utilization, device.bandwidth_display_color, right_width, + f'@ {device.clock_infos.memory}MHz', ), ], [ @@ -587,12 +619,14 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes device.gpu_utilization, device.gpu_display_color, left_width, + f'@ {device.clock_infos.sm}MHz', ), ( 'PWR', device.power_utilization, device.power_display_color, right_width, + f'{device.power_status.partition(" / ")[0]}', ), ], ] @@ -612,6 +646,7 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes device.memory_percent, device.memory_display_color, remaining_width - 3, + device.memory_used_human, ), ], [ @@ -620,15 +655,18 @@ class DevicePanel(BasePanel): # pylint: disable=too-many-instance-attributes device.gpu_utilization, device.gpu_display_color, remaining_width - 3, + f'@ {device.clock_infos.sm}MHz', ), ], ] for y, row in enumerate(matrix, start=y_start): - for prefix, utilization, color, width in row: + for prefix, utilization, color, width, extra_text in row: bar = make_bar( # pylint: disable=disallowed-name prefix, utilization, width=width, + extra_text=extra_text, + swap_text=not extra_text.endswith('MHz'), ) lines[y] += f' {colored(bar, color)} │' # type: ignore[arg-type]