[PR #230] [CLOSED] feat(tui): add a narrow layout for terminals with fewer than 79 columns #231

Closed
opened 2026-10-23 22:05:40 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/XuehaiPan/nvitop/pull/230
Author: @Windyskr
Created: 10/15/2026
Status: ❌ Closed

Base: main ← Head: mobile-layout


📝 Commits (1)

  • e7598e2 feat(tui): add a narrow layout for terminals with fewer than 79 columns

📊 Changes

13 files changed (+568 additions, -91 deletions)

View changed files

📝 CHANGELOG.md (+1 -0)
📝 nvitop/tui/library/__init__.py (+4 -0)
📝 nvitop/tui/library/libcurses.py (+10 -0)
📝 nvitop/tui/library/messagebox.py (+52 -20)
📝 nvitop/tui/library/utils.py (+7 -0)
📝 nvitop/tui/screens/help.py (+140 -2)
📝 nvitop/tui/screens/main/__init__.py (+3 -1)
📝 nvitop/tui/screens/main/panels/device.py (+230 -34)
📝 nvitop/tui/screens/main/panels/host.py (+25 -4)
📝 nvitop/tui/screens/main/panels/process.py (+42 -13)
📝 nvitop/tui/screens/metrics.py (+26 -10)
📝 nvitop/tui/screens/treeview.py (+5 -2)
📝 nvitop/tui/tui.py (+23 -5)

📄 Description

Issue Type

  • Improvement/feature implementation

Runtime Environment

  • Operating system and version: Ubuntu 22.04 LTS (8x A800-SXM4-80GB server), Ubuntu 26.04 LTS on WSL2 (GeForce RTX 5080)
  • Terminal emulator and version: SSH client app on a phone (73 columns in portrait mode); tmux 3.6 for the automated tests
  • Python version: 3.12.14 / 3.12.13
  • NVML version (driver version): 590.48.01 / 617.42
  • nvitop version or commit: main@4796321 + this PR
  • nvidia-ml-py version: 13.615.71
  • Locale: en_US.UTF-8

Description

Terminals from 50 to 78 columns now get a narrow layout instead of the "needs at least a width of 79" message. Layouts at 79 columns and above are unchanged.

  • Device panel: one line per device (MIG devices included): index, fan, temperature and power, followed by memory and utilization bars that share the remaining width. The fan column is left out when no device reports a fan speed (e.g., passively cooled data center boards). An 8-GPU server fits in 15 rows.
  • Host panel: always uses the existing two-line compact view on narrow terminals, with a shorter load average when the bars would get too narrow.
  • Process panel: the fixed columns fit; the host columns (%CPU, %MEM, TIME, COMMAND) scroll with the arrow keys as before. The header row is cut to the panel so it no longer overwrites the right border.
  • Message box: when the box is wider than the terminal, the buttons use tighter spacing, so the signal confirmation fits in 50 columns (49 instead of 62). At 62 columns and above the box is unchanged.
  • Process metrics screen: the labels are cut to their column.
  • Help screen: a single-column layout built from the existing HELP_TEMPLATE, with no duplicated text. The header is wrapped to the width, and every keybinding keeps its color and the --readonly dimming. When the help is taller than the terminal, the arrow keys, j/k and PageUp/PageDown scroll it, and any other key returns as before. The 79+ column help screen is unchanged.
  • Superuser caution: on narrow terminals it shares its row with the signal hint, so while the hint is shown it is shortened to the room left (!SUPERUSER or !), and it is restored when the selection is cleared.
  • The user@host context is cut so it doesn't overwrite the panel title.
  • CursesShortcuts.addstr clips strings at the right edge of the window instead of letting curses wrap them onto the next line, so screens without a dedicated narrow layout degrade gracefully.
  • nvitop -1 keeps printing the standard 79+ column layout on narrow terminals.

The minimum width (50) and the standard layout width (79) are the new MIN_WIDTH and LAYOUT_WIDTH constants in nvitop/tui/library/utils.py.

Motivation and Context

I often check my GPU server from my phone with a remote SSH app. In portrait mode the terminal is only 73 columns wide, so all nvitop shows is:

╒════════════════════════════════════════════════════╕
│                                                    │
│ nvitop needs at least a width of 79 to render, the │
│ current width is 73.                               │
│                                                    │
╘════════════════════════════════════════════════════╛

I couldn't see the GPUs at all unless I rotated the phone and shrank the font until it was unreadable. With this change the same 73-column terminal shows all 8 GPUs, the host and the processes on one screen.

Testing

  • pre-commit run --all-files: ruff, ruff-format, codespell, mypy and pylint pass on the changed files. The only pylint messages are the existing E1120 ones in nvitop/api/libnvml.py, which show up on main as well with my local nvidia-ml-py. xdoctest passes.
  • Wide layouts unchanged: I captured screens from main and from this branch side by side in tmux, with digits and bar glyphs masked. They are identical at 79x20, 79x40, 100x40 and 140x50 in auto, compact (c) and full (f) modes, the help screen (including pressing Down and other keys there) and the tree view, apart from live data in the history graphs and the version string. nvitop -1 output is identical at COLUMNS = 50, 60, 73, 79 and 120.
  • Automated matrix, 212 cases, run on both machines (a single GPU with a fan, and 8 GPUs without fans running real workloads):
    • Sizes: widths 50, 52, 56, 57, 60, 66, 73, 78, 79, 80, 99, 100 and 140, at heights 24 and 45.
    • Keys at 50, 73 and 79 columns: a, c, f, h, t, e, r, PageDown, process selection, horizontal scrolling, tagging, sort order (. and /), the metrics screen (Enter), live resizes to 45, 60 and 140 columns, and scrolling the help screen.
    • Options: --ascii, --colorful, --light, -m compact, -m full, --only 0 and --readonly.
    • Every case checks that each frame line is closed, that nothing is wider than the terminal, that there is no traceback, and that q exits with status 0. Widths 40 and 49 still show the "needs at least a width of 50" message.
  • Manual: on the 8-GPU server, at 50, 55, 61, 62 and 73 columns, I selected a GPU process I own, opened the T confirmation and closed it with Esc. At 73 I sent SIGTERM; at 50 I moved to SIGKILL with the arrow keys and confirmed. In both cases the highlight covered the selected button and the process received the signal.
  • MIG, simulated: I have no MIG-enabled GPU, so a small wrapper inserts two MIG instances after GPU 0. They are built from the real GPU 0 snapshot plus the MIG-specific fields. With the same wrapper, main and this branch render identically at 79, 100 and 140 columns in auto, compact and full modes. At 50 and 73 columns the narrow rows show 0:0 / 0:1, the instance name and the memory / utilization bars, including an N/A utilization.
  • Superuser, simulated: with IS_SUPERUSER patched to True on the 8-GPU server, at 50, 60, 73, 78, 79 and 100 columns: the caution is shown, then shortened next to the signal hint while a process is selected (only below 78 columns), then restored after Esc. 79 and 100 columns match main.

Images / Videos

73 columns, 8x A800 (the process list is omitted here because it shows other users' jobs):

Fri Oct 16 03:41:26 2026                  (Press h for help or q to quit)
╒═══════════════════════════════════════════════════════════════════════╕
│ NVITOP 1.7.1  Driver: 590.48.01  CUDA: 13.1                           │
├────────────────┬───────────────────────────┬──────────────────────────┤
│ GPU Temp   Pwr │ Memory-Usage              │ GPU-Util                 │
╞════════════════╪═══════════════════════════╪══════════════════════════╡
│   0  26C   61W │ MEM: █▊ 8740MiB     10.7% │ UTL: ▏ 0%      @ 1140MHz │
│   1  42C   79W │ MEM: ████████████▉ 80.5%  │ UTL: █████▌ 37%          │
│   2  37C   67W │ MEM: █ 5217MiB       6.4% │ UTL: ▏ 0%      @ 1140MHz │
│   3  46C  267W │ MEM: ████████████▉ 80.2%  │ UTL: ███████████▊ 78%    │
│   4  46C  285W │ MEM: ████████████▉ 80.7%  │ UTL: ██████████████▏ 94% │
│   5  50C  234W │ MEM: ████████████▉ 80.5%  │ UTL: █████████████▊ 92%  │
│   6  59C  299W │ MEM: ████████████▊ 80.0%  │ UTL: █████████████▌ 90%  │
│   7  44C  253W │ MEM: ████████████▉ 80.3%  │ UTL: ██████████████ 93%  │
╘════════════════╧═══════════════════════════╧══════════════════════════╛
[ CPU: █▌ 6.8%   UPTIME: 66.5 days ]  ( Load Average: 10.06  9.96  9.64 )
[ MEM: █▊ 7.7%      USED: 77.58GiB ]  [ SWP: ██████████████▌ 65.8%      ]

50 columns, a GPU with a fan, --ascii:

Fri Oct 16 03:41:57 2026        (h: help, q: quit)
+================================================+
| NVITOP 1.7.2.dev8  Driver: 617.42  CUDA: 13.4  |
|--------------------+-------------+-------------|
| GPU Fan Temp   Pwr | Memory      | GPU-Util    |
|====================+=============+=============|
|   0  0%  41C   36W | MEM: | 16%  | UTL: | 6%   |
+====================+=============+=============+
[ CPU: | 0.2%  UPTIME: 1.4 days ]  ( Load:  1.31 )
[ MEM: |||| 20.2%               ]  [ SWP: | 0.0% ]

The signal confirmation at 50 columns:

 ╒═════════════════════════════════════════════╕
 │                                             │
 │    Send signal to process 1234567(foo)?     │
 │                                             │
 │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
 │ │SIGTERM │ │SIGKILL │ │ SIGINT │ │ Cancel │ │
 │ └────────┘ └────────┘ └────────┘ └────────┘ │
 ╘═════════════════════════════════════════════╛

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/XuehaiPan/nvitop/pull/230 **Author:** [@Windyskr](https://github.com/Windyskr) **Created:** 10/15/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `mobile-layout` --- ### 📝 Commits (1) - [`e7598e2`](https://github.com/XuehaiPan/nvitop/commit/e7598e2fc4b1eda254c30226887a680f6f48d809) feat(tui): add a narrow layout for terminals with fewer than 79 columns ### 📊 Changes **13 files changed** (+568 additions, -91 deletions) <details> <summary>View changed files</summary> 📝 `CHANGELOG.md` (+1 -0) 📝 `nvitop/tui/library/__init__.py` (+4 -0) 📝 `nvitop/tui/library/libcurses.py` (+10 -0) 📝 `nvitop/tui/library/messagebox.py` (+52 -20) 📝 `nvitop/tui/library/utils.py` (+7 -0) 📝 `nvitop/tui/screens/help.py` (+140 -2) 📝 `nvitop/tui/screens/main/__init__.py` (+3 -1) 📝 `nvitop/tui/screens/main/panels/device.py` (+230 -34) 📝 `nvitop/tui/screens/main/panels/host.py` (+25 -4) 📝 `nvitop/tui/screens/main/panels/process.py` (+42 -13) 📝 `nvitop/tui/screens/metrics.py` (+26 -10) 📝 `nvitop/tui/screens/treeview.py` (+5 -2) 📝 `nvitop/tui/tui.py` (+23 -5) </details> ### 📄 Description #### Issue Type - Improvement/feature implementation #### Runtime Environment - Operating system and version: Ubuntu 22.04 LTS (8x A800-SXM4-80GB server), Ubuntu 26.04 LTS on WSL2 (GeForce RTX 5080) - Terminal emulator and version: SSH client app on a phone (73 columns in portrait mode); tmux 3.6 for the automated tests - Python version: `3.12.14` / `3.12.13` - NVML version (driver version): `590.48.01` / `617.42` - `nvitop` version or commit: `main@4796321` + this PR - `nvidia-ml-py` version: `13.615.71` - Locale: `en_US.UTF-8` #### Description Terminals from 50 to 78 columns now get a narrow layout instead of the "needs at least a width of 79" message. **Layouts at 79 columns and above are unchanged.** - **Device panel**: one line per device (MIG devices included): index, fan, temperature and power, followed by memory and utilization bars that share the remaining width. The fan column is left out when no device reports a fan speed (e.g., passively cooled data center boards). An 8-GPU server fits in 15 rows. - **Host panel**: always uses the existing two-line compact view on narrow terminals, with a shorter load average when the bars would get too narrow. - **Process panel**: the fixed columns fit; the host columns (`%CPU`, `%MEM`, `TIME`, `COMMAND`) scroll with the arrow keys as before. The header row is cut to the panel so it no longer overwrites the right border. - **Message box**: when the box is wider than the terminal, the buttons use tighter spacing, so the signal confirmation fits in 50 columns (49 instead of 62). At 62 columns and above the box is unchanged. - **Process metrics screen**: the labels are cut to their column. - **Help screen**: a single-column layout built from the existing `HELP_TEMPLATE`, with no duplicated text. The header is wrapped to the width, and every keybinding keeps its color and the `--readonly` dimming. When the help is taller than the terminal, the arrow keys, `j`/`k` and `PageUp`/`PageDown` scroll it, and any other key returns as before. The 79+ column help screen is unchanged. - **Superuser caution**: on narrow terminals it shares its row with the signal hint, so while the hint is shown it is shortened to the room left (`!SUPERUSER` or `!`), and it is restored when the selection is cleared. - The `user@host` context is cut so it doesn't overwrite the panel title. - `CursesShortcuts.addstr` clips strings at the right edge of the window instead of letting curses wrap them onto the next line, so screens without a dedicated narrow layout degrade gracefully. - `nvitop -1` keeps printing the standard 79+ column layout on narrow terminals. The minimum width (50) and the standard layout width (79) are the new `MIN_WIDTH` and `LAYOUT_WIDTH` constants in `nvitop/tui/library/utils.py`. #### Motivation and Context I often check my GPU server from my phone with a remote SSH app. In portrait mode the terminal is only 73 columns wide, so all nvitop shows is: ```text ╒════════════════════════════════════════════════════╕ │ │ │ nvitop needs at least a width of 79 to render, the │ │ current width is 73. │ │ │ ╘════════════════════════════════════════════════════╛ ``` I couldn't see the GPUs at all unless I rotated the phone and shrank the font until it was unreadable. With this change the same 73-column terminal shows all 8 GPUs, the host and the processes on one screen. #### Testing - `pre-commit run --all-files`: ruff, ruff-format, codespell, mypy and pylint pass on the changed files. The only pylint messages are the existing `E1120` ones in `nvitop/api/libnvml.py`, which show up on `main` as well with my local `nvidia-ml-py`. xdoctest passes. - **Wide layouts unchanged**: I captured screens from `main` and from this branch side by side in tmux, with digits and bar glyphs masked. They are identical at 79x20, 79x40, 100x40 and 140x50 in auto, compact (`c`) and full (`f`) modes, the help screen (including pressing `Down` and other keys there) and the tree view, apart from live data in the history graphs and the version string. `nvitop -1` output is identical at `COLUMNS` = 50, 60, 73, 79 and 120. - **Automated matrix, 212 cases, run on both machines (a single GPU with a fan, and 8 GPUs without fans running real workloads)**: - Sizes: widths 50, 52, 56, 57, 60, 66, 73, 78, 79, 80, 99, 100 and 140, at heights 24 and 45. - Keys at 50, 73 and 79 columns: `a`, `c`, `f`, `h`, `t`, `e`, `r`, `PageDown`, process selection, horizontal scrolling, tagging, sort order (`.` and `/`), the metrics screen (`Enter`), live resizes to 45, 60 and 140 columns, and scrolling the help screen. - Options: `--ascii`, `--colorful`, `--light`, `-m compact`, `-m full`, `--only 0` and `--readonly`. - Every case checks that each frame line is closed, that nothing is wider than the terminal, that there is no traceback, and that `q` exits with status 0. Widths 40 and 49 still show the "needs at least a width of 50" message. - **Manual**: on the 8-GPU server, at 50, 55, 61, 62 and 73 columns, I selected a GPU process I own, opened the `T` confirmation and closed it with `Esc`. At 73 I sent `SIGTERM`; at 50 I moved to `SIGKILL` with the arrow keys and confirmed. In both cases the highlight covered the selected button and the process received the signal. - **MIG, simulated**: I have no MIG-enabled GPU, so a small wrapper inserts two MIG instances after GPU 0. They are built from the real GPU 0 snapshot plus the MIG-specific fields. With the same wrapper, `main` and this branch render identically at 79, 100 and 140 columns in auto, compact and full modes. At 50 and 73 columns the narrow rows show `0:0` / `0:1`, the instance name and the memory / utilization bars, including an `N/A` utilization. - **Superuser, simulated**: with `IS_SUPERUSER` patched to `True` on the 8-GPU server, at 50, 60, 73, 78, 79 and 100 columns: the caution is shown, then shortened next to the signal hint while a process is selected (only below 78 columns), then restored after `Esc`. 79 and 100 columns match `main`. #### Images / Videos 73 columns, 8x A800 (the process list is omitted here because it shows other users' jobs): ```text Fri Oct 16 03:41:26 2026 (Press h for help or q to quit) ╒═══════════════════════════════════════════════════════════════════════╕ │ NVITOP 1.7.1 Driver: 590.48.01 CUDA: 13.1 │ ├────────────────┬───────────────────────────┬──────────────────────────┤ │ GPU Temp Pwr │ Memory-Usage │ GPU-Util │ ╞════════════════╪═══════════════════════════╪══════════════════════════╡ │ 0 26C 61W │ MEM: █▊ 8740MiB 10.7% │ UTL: ▏ 0% @ 1140MHz │ │ 1 42C 79W │ MEM: ████████████▉ 80.5% │ UTL: █████▌ 37% │ │ 2 37C 67W │ MEM: █ 5217MiB 6.4% │ UTL: ▏ 0% @ 1140MHz │ │ 3 46C 267W │ MEM: ████████████▉ 80.2% │ UTL: ███████████▊ 78% │ │ 4 46C 285W │ MEM: ████████████▉ 80.7% │ UTL: ██████████████▏ 94% │ │ 5 50C 234W │ MEM: ████████████▉ 80.5% │ UTL: █████████████▊ 92% │ │ 6 59C 299W │ MEM: ████████████▊ 80.0% │ UTL: █████████████▌ 90% │ │ 7 44C 253W │ MEM: ████████████▉ 80.3% │ UTL: ██████████████ 93% │ ╘════════════════╧═══════════════════════════╧══════════════════════════╛ [ CPU: █▌ 6.8% UPTIME: 66.5 days ] ( Load Average: 10.06 9.96 9.64 ) [ MEM: █▊ 7.7% USED: 77.58GiB ] [ SWP: ██████████████▌ 65.8% ] ``` 50 columns, a GPU with a fan, `--ascii`: ```text Fri Oct 16 03:41:57 2026 (h: help, q: quit) +================================================+ | NVITOP 1.7.2.dev8 Driver: 617.42 CUDA: 13.4 | |--------------------+-------------+-------------| | GPU Fan Temp Pwr | Memory | GPU-Util | |====================+=============+=============| | 0 0% 41C 36W | MEM: | 16% | UTL: | 6% | +====================+=============+=============+ [ CPU: | 0.2% UPTIME: 1.4 days ] ( Load: 1.31 ) [ MEM: |||| 20.2% ] [ SWP: | 0.0% ] ``` The signal confirmation at 50 columns: ```text ╒═════════════════════════════════════════════╕ │ │ │ Send signal to process 1234567(foo)? │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │SIGTERM │ │SIGKILL │ │ SIGINT │ │ Cancel │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ ╘═════════════════════════════════════════════╛ ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
gitea-mirror 2026-10-23 22:05:40 -06:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
github-starred/nvitop#231
No description provided.