diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ea513..301c790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `nvidia-ml-py` 13.595.45 to support list. - Add support for open kernel-module driver packages (e.g., `nvidia-driver-595-open`) in `install-nvidia-driver.sh` with new `--proprietary` and `--open` flags by [@XuehaiPan](https://github.com/XuehaiPan). - Add TLS and mutual TLS (mTLS) support for `nvitop-exporter` via new `--certfile`, `--keyfile`, `--client-cafile`, `--client-capath`, and `--client-auth-required` CLI flags by [@XuehaiPan](https://github.com/XuehaiPan) in [#213](https://github.com/XuehaiPan/nvitop/pull/213). Issued by [@StefanSander3](https://github.com/StefanSander3) in [#131](https://github.com/XuehaiPan/nvitop/issues/131). +- Add `--readonly` CLI flag (and equivalent `NVITOP_MONITOR_MODE="readonly"` env token) for monitor mode that disables all process-mutating shortcuts (`Ctrl-c` / `T` / `K` / `I` / `k`) by [@XuehaiPan](https://github.com/XuehaiPan). ### Changed diff --git a/README.md b/README.md index dd04997..78bb917 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,8 @@ You can configure the default monitor mode with the `NVITOP_MONITOR_MODE` enviro In monitor mode, you can use Ctrl-c / T / K keys to interrupt / terminate / kill a process. And it's recommended to *terminate* or *kill* a process in the **tree-view screen** (shortcut: t). For normal users, `nvitop` will shallow other users' processes (in low-intensity colors). For **system administrators**, you can use `sudo nvitop` to terminate other users' processes. +To run `nvitop` as a viewer only and disable all process-mutating shortcuts, pass `--readonly` (or set `NVITOP_MONITOR_MODE="readonly"`). The signal keys above become no-ops, the on-screen "Press ^C(INT)/T(TERM)/K(KILL) to send signals" hint is hidden, and the corresponding rows in the help screen are dimmed. Use this when sharing a session over SSH, demoing on a multi-tenant box, or wrapping `nvitop` in a non-admin alias. + Also, to enter the process metrics screen, select a process and then press the Enter / Return key . `nvitop` dynamically displays the process metrics with live graphs.
@@ -343,11 +345,11 @@ Type `nvitop --help` for more command options:
```text
usage: nvitop [--help] [--version] [--once | --monitor [{auto,full,compact}]]
- [--interval SEC] [--ascii] [--colorful] [--force-color] [--light]
- [--gpu-util-thresh th1 th2] [--mem-util-thresh th1 th2]
- [--only INDEX [INDEX ...]] [--only-visible]
- [--compute] [--only-compute] [--graphics] [--only-graphics]
- [--user [USERNAME ...]] [--pid PID [PID ...]]
+ [--interval SEC] [--no-unicode] [--readonly] [--colorful]
+ [--force-color] [--light] [--gpu-util-thresh th1 th2]
+ [--mem-util-thresh th1 th2] [--only INDEX [INDEX ...]]
+ [--only-visible] [--compute] [--only-compute] [--graphics]
+ [--only-graphics] [--user [USERNAME ...]] [--pid PID [PID ...]]
An interactive NVIDIA-GPU process viewer.
@@ -355,19 +357,22 @@ options:
--help, -h Show this help message and exit.
--version, -V Show nvitop's version number and exit.
--once, -1 Report query data only once.
- --monitor [{auto,full,compact}], -m [{auto,full,compact}]
+ --monitor, -m [{auto,full,compact}]
Run as a resource monitor. Continuously report query data and handle user inputs.
If the argument is omitted, the value from `NVITOP_MONITOR_MODE` will be used.
(default fallback mode: auto)
--interval SEC Process status update interval in seconds. (default: 2)
- --ascii, --no-unicode, -U
+ --no-unicode, --ascii, -U
Use ASCII characters only, which is useful for terminals without Unicode support.
+ --readonly Disable all system and process changing features (e.g., terminating processes).
+ Set variable `NVITOP_MONITOR_MODE="readonly"` for convenience.
coloring:
- --colorful Use gradient colors to get spectrum-like bar charts. This option is only available
- when the terminal supports 256 colors. You may need to set environment variable
- `TERM="xterm-256color"`. Note that the terminal multiplexer, such as `tmux`, may
- override the `TERM` variable.
+ --colorful Use gradient colors to get spectrum-like bar charts.
+ Set variable `NVITOP_MONITOR_MODE="colorful"` for convenience.
+ This option is only available when the terminal supports 256 colors.
+ You may need to set environment variable `TERM="xterm-256color"`. Note that the
+ terminal multiplexer, such as `tmux`, may override the `TERM` variable.
--force-color Force colorize even when `stdout` is not a TTY terminal.
--light Tweak visual results for light theme terminals in monitor mode.
Set variable `NVITOP_MONITOR_MODE="light"` on light terminals for convenience.
@@ -381,7 +386,7 @@ coloring:
( 1 <= th1 < th2 <= 99, defaults: 10 80 )
device filtering:
- --only INDEX [INDEX ...], -o INDEX [INDEX ...]
+ --only, -o INDEX [INDEX ...]
Only show the specified devices, suppress option `--only-visible`.
--only-visible, -ov Only show devices in the `CUDA_VISIBLE_DEVICES` environment variable.
@@ -390,9 +395,9 @@ process filtering:
--only-compute, -C Only show GPU processes exactly with the compute context. (type: 'C' only)
--graphics, -g Only show GPU processes with the graphics context. (type: 'G' or 'C+G')
--only-graphics, -G Only show GPU processes exactly with the graphics context. (type: 'G' only)
- --user [USERNAME ...], -u [USERNAME ...]
+ --user, -u [USERNAME ...]
Only show processes of the given users (or `$USER` for no argument).
- --pid PID [PID ...], -p PID [PID ...]
+ --pid, -p PID [PID ...]
Only show processes of the given PIDs.
```
@@ -400,7 +405,7 @@ process filtering:
| Name | Description | Valid Values | Default Value |
| -------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | ----------------- |
-| `NVITOP_MONITOR_MODE` | The default display mode (a comma-separated string) | `auto` / `full` / `compact`
`plain` / `colorful`
`dark` / `light` | `auto,plain,dark` |
+| `NVITOP_MONITOR_MODE` | The default display mode (a comma-separated string) | `auto` / `full` / `compact`
`plain` / `colorful`
`dark` / `light`
`readonly` (disables process-mutating shortcuts) | `auto,plain,dark` |
| `NVITOP_GPU_UTILIZATION_THRESHOLDS` | Thresholds of GPU utilization | `10,75` , `1,99`, ... | `10,75` |
| `NVITOP_MEMORY_UTILIZATION_THRESHOLDS` | Thresholds of GPU memory percent | `10,80` , `1,99`, ... | `10,80` |
| `LOGLEVEL` | Log level for log messages | `DEBUG` , `INFO`, `WARNING`, ... | `WARNING` |
@@ -450,9 +455,9 @@ echo 'set -gx NVITOP_MONITOR_MODE "full"' >> ~/.config/fish/config.fish
| | |
| `
`I` | Send `signal.SIGINT` to the selected process (interrupt). |
-| `T` | Send `signal.SIGTERM` to the selected process (terminate). |
-| `K` | Send `signal.SIGKILL` to the selected process (kill). |
+| `
`I` | Send `signal.SIGINT` to the selected process (interrupt). *(disabled under `--readonly`)* |
+| `T` | Send `signal.SIGTERM` to the selected process (terminate). *(disabled under `--readonly`)* |
+| `K` | Send `signal.SIGKILL` to the selected process (kill). *(disabled under `--readonly`)* |
| | |
| `e` | Show process environment. |
| `t` | Toggle tree-view screen. |
diff --git a/nvitop/cli.py b/nvitop/cli.py
index 5a8da80..31c296d 100644
--- a/nvitop/cli.py
+++ b/nvitop/cli.py
@@ -102,6 +102,15 @@ def parse_arguments() -> argparse.Namespace:
action='store_true',
help='Use ASCII characters only, which is useful for terminals without Unicode support.',
)
+ parser.add_argument(
+ '--readonly',
+ dest='readonly',
+ action='store_true',
+ help=(
+ 'Disable all system and process changing features (e.g., terminating processes).\n'
+ 'Set variable `NVITOP_MONITOR_MODE="readonly"` for convenience.'
+ ),
+ )
coloring = parser.add_argument_group('coloring')
coloring.add_argument(
@@ -109,10 +118,11 @@ def parse_arguments() -> argparse.Namespace:
dest='colorful',
action='store_true',
help=(
- 'Use gradient colors to get spectrum-like bar charts. This option is only available\n'
- 'when the terminal supports 256 colors. You may need to set environment variable\n'
- '`TERM="xterm-256color"`. Note that the terminal multiplexer, such as `tmux`, may\n'
- 'override the `TERM` variable.'
+ 'Use gradient colors to get spectrum-like bar charts.\n'
+ 'Set variable `NVITOP_MONITOR_MODE="colorful"` for convenience.\n'
+ 'This option is only available when the terminal supports 256 colors.\n'
+ 'You may need to set environment variable `TERM="xterm-256color"`. Note that the\n'
+ 'terminal multiplexer, such as `tmux`, may override the `TERM` variable.'
),
)
coloring.add_argument(
@@ -234,6 +244,8 @@ def parse_arguments() -> argparse.Namespace:
args.colorful = 'colorful' in NVITOP_MONITOR_MODE and 'plain' not in NVITOP_MONITOR_MODE
if not args.light:
args.light = 'light' in NVITOP_MONITOR_MODE and 'dark' not in NVITOP_MONITOR_MODE
+ if not args.readonly:
+ args.readonly = 'readonly' in NVITOP_MONITOR_MODE
if args.user is not None and len(args.user) == 0:
args.user.append(USERNAME)
if args.gpu_util_thresh is None:
@@ -355,6 +367,7 @@ def main() -> int:
no_unicode=args.no_unicode,
mode=args.monitor,
interval=args.interval,
+ readonly=args.readonly,
win=win,
)
tui.loop()
@@ -364,7 +377,7 @@ def main() -> int:
messages.append(f'ERROR: Failed to initialize `curses` ({ex})')
if tui is None:
- tui = TUI(devices, filters, no_unicode=args.no_unicode)
+ tui = TUI(devices, filters, no_unicode=args.no_unicode, readonly=args.readonly)
if not sys.stdout.isatty():
parent = HostProcess().parent()
if parent is not None:
diff --git a/nvitop/tui/library/__init__.py b/nvitop/tui/library/__init__.py
index 4227a73..ea70712 100644
--- a/nvitop/tui/library/__init__.py
+++ b/nvitop/tui/library/__init__.py
@@ -18,7 +18,12 @@ from nvitop.tui.library.keybinding import (
normalize_keybinding,
)
from nvitop.tui.library.libcurses import libcurses, setlocale_utf8
-from nvitop.tui.library.messagebox import MessageBox
+from nvitop.tui.library.messagebox import (
+ SIGNAL_HINT_BLANK,
+ SIGNAL_HINT_KEY_SPANS,
+ SIGNAL_HINT_TEXT,
+ MessageBox,
+)
from nvitop.tui.library.mouse import MouseEvent
from nvitop.tui.library.process import GpuProcess, HostProcess
from nvitop.tui.library.selection import Selection
@@ -58,6 +63,9 @@ __all__ = [
'NA',
'PASSIVE_ACTION',
'QUANT_KEY',
+ 'SIGNAL_HINT_BLANK',
+ 'SIGNAL_HINT_KEY_SPANS',
+ 'SIGNAL_HINT_TEXT',
'SPECIAL_KEYS',
'USERNAME',
'USER_CONTEXT',
diff --git a/nvitop/tui/library/messagebox.py b/nvitop/tui/library/messagebox.py
index 13d0c83..c7306a4 100644
--- a/nvitop/tui/library/messagebox.py
+++ b/nvitop/tui/library/messagebox.py
@@ -11,7 +11,7 @@ import string
import threading
import time
from functools import partial
-from typing import TYPE_CHECKING, Literal
+from typing import TYPE_CHECKING, ClassVar, Literal
from nvitop.tui.library import host
from nvitop.tui.library.displayable import Displayable
@@ -28,11 +28,30 @@ if TYPE_CHECKING:
from nvitop.tui.tui import TUI
-__all__ = ['MessageBox']
+__all__ = ['SIGNAL_HINT_BLANK', 'SIGNAL_HINT_KEY_SPANS', 'SIGNAL_HINT_TEXT', 'MessageBox']
DIGITS: frozenset[str] = frozenset(string.digits)
+SignalHintSpanKind = Literal['key', 'label']
+
+# Single source of truth for the on-screen hint that surfaces the kill/terminate/interrupt
+# bindings. The process panel and the tree-view screen both render this string with
+# screen-specific coloring; they share these positions and widths so the two layouts stay in
+# lockstep when the wording is updated.
+SIGNAL_HINT_TEXT: str = '(Press ^C(INT)/T(TERM)/K(KILL) to send signals)'
+SIGNAL_HINT_BLANK: str = ' ' * len(SIGNAL_HINT_TEXT)
+# (kind, offset_within_text, width) — ``kind`` is ``'key'`` for the shortcut tokens
+# (``^C``, ``T``, ``K``) and ``'label'`` for the signal names (``INT``, ``TERM``, ``KILL``).
+SIGNAL_HINT_KEY_SPANS: tuple[tuple[SignalHintSpanKind, int, int], ...] = (
+ ('key', 7, 2),
+ ('label', 10, 3),
+ ('key', 15, 1),
+ ('label', 17, 4),
+ ('key', 23, 1),
+ ('label', 25, 4),
+)
+
class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes
class Option: # pylint: disable=too-few-public-methods
@@ -302,12 +321,45 @@ class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes
keymaps.alias('messagebox', '