mirror of
https://github.com/XuehaiPan/nvitop.git
synced 2026-05-15 14:15:55 -06:00
Merge c869a835cb into 4e814c52a6
This commit is contained in:
commit
d22f9db20a
22 changed files with 3183 additions and 56 deletions
|
|
@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Removed
|
||||
|
||||
-
|
||||
- Remove third-party dependency `windows-curses` for Windows support by [@XuehaiPan](https://github.com/XuehaiPan) in [#149](https://github.com/XuehaiPan/nvitop/pull/149).
|
||||
|
||||
------
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ bash install-nvidia-driver.sh --latest --open # install the latest
|
|||
|
||||
Run `bash install-nvidia-driver.sh --help` for more information.
|
||||
|
||||
<a name="curses">*</a> The `curses` library is a built-in module of Python on Unix-like systems, and it is supported by a third-party package called `windows-curses` on Windows using PDCurses. Inconsistent behavior of `nvitop` may occur on different terminal emulators on Windows, such as missing mouse support.
|
||||
<a name="curses">*</a> The `curses` library is a built-in module of Python on Unix-like systems, and `nvitop` supports Windows using ANSI escape codes. Inconsistent behavior of `nvitop` may occur on different terminal emulators on Windows, such as missing mouse support.
|
||||
|
||||
------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,30 @@
|
|||
accessdenied
|
||||
acs
|
||||
addch
|
||||
addnstr
|
||||
addstr
|
||||
api
|
||||
args
|
||||
ascii
|
||||
attr
|
||||
bel
|
||||
bg
|
||||
bool
|
||||
boolean
|
||||
bstate
|
||||
cbreak
|
||||
chgat
|
||||
chtype
|
||||
cli
|
||||
cmdline
|
||||
codepoint
|
||||
colorama
|
||||
colorscheme
|
||||
compat
|
||||
conf
|
||||
const
|
||||
cpython
|
||||
csi
|
||||
csv
|
||||
ctrl
|
||||
ctx
|
||||
|
|
@ -27,6 +39,7 @@ divmod
|
|||
docstring
|
||||
doctest
|
||||
ecc
|
||||
endwin
|
||||
enum
|
||||
env
|
||||
environ
|
||||
|
|
@ -34,6 +47,7 @@ esc
|
|||
failsafe
|
||||
fallbacks
|
||||
fg
|
||||
fileno
|
||||
fmt
|
||||
func
|
||||
getch
|
||||
|
|
@ -49,6 +63,8 @@ gpuprocesssnapshot
|
|||
gpus
|
||||
gpustatslogger
|
||||
hostname
|
||||
ignorable
|
||||
initscr
|
||||
ints
|
||||
ipython
|
||||
isinstance
|
||||
|
|
@ -57,6 +73,7 @@ keras
|
|||
kib
|
||||
kmd
|
||||
kwargs
|
||||
leaveok
|
||||
len
|
||||
libcuda
|
||||
libcudart
|
||||
|
|
@ -71,6 +88,7 @@ mig
|
|||
migdevice
|
||||
milliwatts
|
||||
mps
|
||||
msvcrt
|
||||
mypy
|
||||
namespace
|
||||
nan
|
||||
|
|
@ -78,6 +96,7 @@ noheader
|
|||
noqa
|
||||
nosuchprocess
|
||||
nounits
|
||||
num
|
||||
nvidia
|
||||
nvidia-smi
|
||||
nvisel
|
||||
|
|
@ -86,6 +105,7 @@ nvml
|
|||
nvmlerror
|
||||
oneshot
|
||||
ord
|
||||
ored
|
||||
os
|
||||
ot
|
||||
pan
|
||||
|
|
@ -103,16 +123,28 @@ pytorch
|
|||
redhat
|
||||
reentrant
|
||||
resourcemetriccollector
|
||||
rgb
|
||||
rlist
|
||||
rss
|
||||
rstrip
|
||||
rtx
|
||||
runtime
|
||||
rw
|
||||
rx
|
||||
rxvt
|
||||
sanitization
|
||||
scrollok
|
||||
selectable
|
||||
setupterm
|
||||
sgi's
|
||||
sgr
|
||||
shader
|
||||
sm
|
||||
smi
|
||||
stderr
|
||||
stdin
|
||||
stdout
|
||||
stdscr
|
||||
str
|
||||
struct
|
||||
subclasses
|
||||
|
|
@ -123,6 +155,7 @@ superset
|
|||
sys
|
||||
tcc
|
||||
tensorflow
|
||||
termios
|
||||
throughputinfo
|
||||
toml
|
||||
traceback
|
||||
|
|
@ -134,10 +167,13 @@ uid
|
|||
uids
|
||||
unallocated
|
||||
uncase
|
||||
ungetch
|
||||
unicode
|
||||
unicodedata
|
||||
uptime
|
||||
utils
|
||||
uuid
|
||||
wcwidth
|
||||
wddm
|
||||
wdm
|
||||
widestring
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ from nvitop.version import __version__
|
|||
__all__ = [*api.__all__, 'select_devices']
|
||||
|
||||
# Add submodules to the top-level namespace
|
||||
submodule = api
|
||||
for submodule in (
|
||||
caching,
|
||||
collector,
|
||||
|
|
@ -57,4 +58,4 @@ for submodule in (
|
|||
# Required for `python -m nvitop.select` to work properly
|
||||
sys.modules.pop(f'{__name__}.select', None)
|
||||
|
||||
del sys
|
||||
del sys, submodule
|
||||
|
|
|
|||
|
|
@ -104,13 +104,12 @@ if TYPE_CHECKING:
|
|||
__all__ = ['colored', 'cprint']
|
||||
|
||||
|
||||
if os.name == 'nt': # Windows
|
||||
try:
|
||||
from colorama import init
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
init()
|
||||
try:
|
||||
from colorama import just_fix_windows_console
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
just_fix_windows_console()
|
||||
|
||||
|
||||
ATTRIBUTES: dict[Attribute, int] = {
|
||||
|
|
@ -122,7 +121,6 @@ ATTRIBUTES: dict[Attribute, int] = {
|
|||
'concealed': 8,
|
||||
'strike': 9,
|
||||
}
|
||||
|
||||
HIGHLIGHTS: dict[Highlight, int] = {
|
||||
'on_black': 40,
|
||||
'on_grey': 40, # Actually black but kept for backwards compatibility
|
||||
|
|
@ -142,7 +140,6 @@ HIGHLIGHTS: dict[Highlight, int] = {
|
|||
'on_light_cyan': 106,
|
||||
'on_white': 107,
|
||||
}
|
||||
|
||||
COLORS: dict[Color, int] = {
|
||||
'black': 30,
|
||||
'grey': 30, # Actually black but kept for backwards compatibility
|
||||
|
|
@ -162,8 +159,6 @@ COLORS: dict[Color, int] = {
|
|||
'light_cyan': 96,
|
||||
'white': 97,
|
||||
}
|
||||
|
||||
|
||||
RESET = '\033[0m'
|
||||
|
||||
|
||||
|
|
@ -239,19 +234,15 @@ def colored(
|
|||
if not _can_do_color(no_color=no_color, force_color=force_color):
|
||||
return result
|
||||
|
||||
fmt_str = '\033[%dm%s'
|
||||
sequence = []
|
||||
if color is not None:
|
||||
result = fmt_str % (COLORS[color], result)
|
||||
|
||||
sequence.append(COLORS[color])
|
||||
if on_color is not None:
|
||||
result = fmt_str % (HIGHLIGHTS[on_color], result)
|
||||
|
||||
sequence.append(HIGHLIGHTS[on_color])
|
||||
if attrs is not None:
|
||||
for attr in attrs:
|
||||
result = fmt_str % (ATTRIBUTES[attr], result)
|
||||
|
||||
result += RESET
|
||||
|
||||
sequence.extend(ATTRIBUTES[attr] for attr in attrs)
|
||||
if sequence:
|
||||
return f'\033[{";".join(map(str, sequence))}m{result}{RESET}'
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@
|
|||
"""The interactive NVIDIA-GPU process viewer."""
|
||||
|
||||
import argparse
|
||||
import curses
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from nvitop.api import HostProcess, libnvml
|
||||
from nvitop.tui import TUI, USERNAME, Device, colored, libcurses, set_color, setlocale_utf8
|
||||
from nvitop.tui import TUI, USERNAME, Device, colored, curses, libcurses, set_color, setlocale_utf8
|
||||
from nvitop.version import __version__
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from nvitop.tui.library import (
|
|||
USERNAME,
|
||||
Device,
|
||||
colored,
|
||||
curses,
|
||||
libcurses,
|
||||
set_color,
|
||||
setlocale_utf8,
|
||||
|
|
@ -21,6 +22,7 @@ __all__ = [
|
|||
'USERNAME',
|
||||
'Device',
|
||||
'colored',
|
||||
'curses',
|
||||
'libcurses',
|
||||
'set_color',
|
||||
'setlocale_utf8',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
# pylint: disable=missing-module-docstring
|
||||
|
||||
from nvitop.tui.library import host
|
||||
from nvitop.tui.library import curses, host
|
||||
from nvitop.tui.library.device import Device, MigDevice
|
||||
from nvitop.tui.library.displayable import Displayable, DisplayableContainer
|
||||
from nvitop.tui.library.history import BufferedHistoryGraph, HistoryGraph
|
||||
|
|
@ -80,6 +80,7 @@ __all__ = [
|
|||
'WideString',
|
||||
'bytes2human',
|
||||
'colored',
|
||||
'curses',
|
||||
'cut_string',
|
||||
'host',
|
||||
'libcurses',
|
||||
|
|
|
|||
140
nvitop/tui/library/curses/__init__.py
Normal file
140
nvitop/tui/library/curses/__init__.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# This file is part of nvitop, the interactive NVIDIA-GPU process viewer.
|
||||
# License: GNU GPL version 3.
|
||||
|
||||
# pylint: disable=missing-module-docstring,missing-function-docstring,import-outside-toplevel,invalid-name
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
||||
|
||||
|
||||
HAS_CURSES_MODULE: bool = True
|
||||
try:
|
||||
import curses
|
||||
except ImportError:
|
||||
HAS_CURSES_MODULE = False
|
||||
else:
|
||||
del curses
|
||||
|
||||
from curses import * # noqa: F403 # pylint: disable=redefined-builtin
|
||||
from curses import ascii # pylint: disable=redefined-builtin
|
||||
|
||||
|
||||
if not HAS_CURSES_MODULE:
|
||||
# pylint: disable-next=redefined-builtin
|
||||
from nvitop.tui.library.curses import ascii # type: ignore[no-redef]
|
||||
from nvitop.tui.library.curses._curses import * # type: ignore[assignment] # noqa: F403
|
||||
|
||||
if _TYPE_CHECKING:
|
||||
from collections.abc import Callable as _Callable
|
||||
from typing import TypeVar as _TypeVar # pylint: disable=ungrouped-imports
|
||||
from typing_extensions import Concatenate as _Concatenate # Python 3.10+
|
||||
from typing_extensions import ParamSpec as _ParamSpec # Python 3.10+
|
||||
|
||||
# pylint: disable-next=ungrouped-imports
|
||||
from nvitop.tui.library.curses._curses import ( # type: ignore[assignment]
|
||||
CursesWindow as window, # noqa: N813
|
||||
)
|
||||
|
||||
_P = _ParamSpec('_P')
|
||||
_T = _TypeVar('_T')
|
||||
|
||||
# Copied from the CPython repository.
|
||||
# https://github.com/python/cpython/blob/HEAD/Lib/curses/__init__.py
|
||||
|
||||
# Some constants, most notably the ACS_* ones, are only added to the C
|
||||
# _curses module's dictionary after initscr() is called. (Some
|
||||
# versions of SGI's curses don't define values for those constants
|
||||
# until initscr() has been called.) This wrapper function calls the
|
||||
# underlying C initscr(), and then copies the constants from the
|
||||
# _curses module to the curses package's dictionary. Don't do 'from
|
||||
# curses import *' if you'll be needing the ACS_* constants.
|
||||
def initscr() -> window: # pylint: disable=function-redefined
|
||||
import os
|
||||
import sys
|
||||
|
||||
from nvitop.tui.library.curses import _curses
|
||||
|
||||
assert sys.__stdout__ is not None
|
||||
|
||||
# we call setupterm() here because it raises an error
|
||||
# instead of calling exit() in error cases.
|
||||
_curses.setupterm(term=os.getenv('TERM', 'unknown'), fd=sys.__stdout__.fileno())
|
||||
stdscr = _curses.initscr()
|
||||
globals().update(
|
||||
{
|
||||
key: value
|
||||
for key, value in vars(_curses).items()
|
||||
if key.startswith('ACS_') or key in ('LINES', 'COLS')
|
||||
},
|
||||
)
|
||||
|
||||
return stdscr # type: ignore[return-value]
|
||||
|
||||
# This is a similar wrapper for start_color(), which adds the COLORS and
|
||||
# COLOR_PAIRS variables which are only available after start_color() is
|
||||
# called.
|
||||
def start_color() -> None: # pylint: disable=function-redefined
|
||||
from nvitop.tui.library.curses import _curses
|
||||
|
||||
retval = _curses.start_color() # type: ignore[func-returns-value] # pylint: disable=assignment-from-no-return
|
||||
if hasattr(_curses, 'COLORS'):
|
||||
globals()['COLORS'] = _curses.COLORS
|
||||
if hasattr(_curses, 'COLOR_PAIRS'):
|
||||
globals()['COLOR_PAIRS'] = _curses.COLOR_PAIRS
|
||||
return retval
|
||||
|
||||
# Wrapper for the entire curses-based application. Runs a function which
|
||||
# should be the rest of your curses-based application. If the application
|
||||
# raises an exception, wrapper() will restore the terminal to a sane state so
|
||||
# you can read the resulting traceback.
|
||||
def wrapper( # pylint: disable=function-redefined
|
||||
func: _Callable[_Concatenate[window, _P], _T],
|
||||
/,
|
||||
*args: _P.args,
|
||||
**kwds: _P.kwargs,
|
||||
) -> _T:
|
||||
"""Wrapper function that initializes curses and calls another function,
|
||||
restoring normal keyboard/screen behavior on error.
|
||||
|
||||
The callable object 'func' is then passed the main window 'stdscr'
|
||||
as its first argument, followed by any other arguments passed to
|
||||
wrapper().
|
||||
"""
|
||||
from nvitop.tui.library.curses import _curses
|
||||
|
||||
try:
|
||||
# Initialize curses
|
||||
stdscr = initscr()
|
||||
|
||||
# Turn off echoing of keys, and enter cbreak mode,
|
||||
# where no buffering is performed on keyboard input
|
||||
_curses.noecho()
|
||||
_curses.cbreak()
|
||||
|
||||
# In keypad mode, escape sequences for special keys
|
||||
# (like the cursor keys) will be interpreted and
|
||||
# a special value like curses.KEY_LEFT will be returned
|
||||
stdscr.keypad(True)
|
||||
|
||||
# Start color, too. Harmless if the terminal doesn't have
|
||||
# color; user can test with has_color() later on. The try/catch
|
||||
# works around a minor bit of over-conscientiousness in the curses
|
||||
# module -- the error return from C start_color() is ignorable.
|
||||
try:
|
||||
start_color()
|
||||
except: # noqa: E722,S110,RUF100 # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
return func(stdscr, *args, **kwds)
|
||||
finally:
|
||||
# Set everything back to normal
|
||||
if 'stdscr' in locals():
|
||||
stdscr.keypad(False)
|
||||
_curses.echo()
|
||||
_curses.nocbreak()
|
||||
_curses.endwin()
|
||||
|
||||
else:
|
||||
if _TYPE_CHECKING:
|
||||
from curses import window # pylint: disable=ungrouped-imports
|
||||
2704
nvitop/tui/library/curses/_curses.py
Normal file
2704
nvitop/tui/library/curses/_curses.py
Normal file
File diff suppressed because it is too large
Load diff
213
nvitop/tui/library/curses/ascii.py
Normal file
213
nvitop/tui/library/curses/ascii.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
"""Constants and membership tests for ASCII characters"""
|
||||
|
||||
# Copied from the CPython repository.
|
||||
# https://github.com/python/cpython/blob/HEAD/Lib/curses/ascii.py
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import overload
|
||||
|
||||
|
||||
NUL = 0x00 # ^@
|
||||
SOH = 0x01 # ^A
|
||||
STX = 0x02 # ^B
|
||||
ETX = 0x03 # ^C
|
||||
EOT = 0x04 # ^D
|
||||
ENQ = 0x05 # ^E
|
||||
ACK = 0x06 # ^F
|
||||
BEL = 0x07 # ^G
|
||||
BS = 0x08 # ^H
|
||||
TAB = 0x09 # ^I
|
||||
HT = 0x09 # ^I
|
||||
LF = 0x0A # ^J
|
||||
NL = 0x0A # ^J
|
||||
VT = 0x0B # ^K
|
||||
FF = 0x0C # ^L
|
||||
CR = 0x0D # ^M
|
||||
SO = 0x0E # ^N
|
||||
SI = 0x0F # ^O
|
||||
DLE = 0x10 # ^P
|
||||
DC1 = 0x11 # ^Q
|
||||
DC2 = 0x12 # ^R
|
||||
DC3 = 0x13 # ^S
|
||||
DC4 = 0x14 # ^T
|
||||
NAK = 0x15 # ^U
|
||||
SYN = 0x16 # ^V
|
||||
ETB = 0x17 # ^W
|
||||
CAN = 0x18 # ^X
|
||||
EM = 0x19 # ^Y
|
||||
SUB = 0x1A # ^Z
|
||||
ESC = 0x1B # ^[
|
||||
FS = 0x1C # ^\
|
||||
GS = 0x1D # ^]
|
||||
RS = 0x1E # ^^
|
||||
US = 0x1F # ^_
|
||||
SP = 0x20 # space
|
||||
DEL = 0x7F # delete
|
||||
|
||||
controlnames = [
|
||||
'NUL',
|
||||
'SOH',
|
||||
'STX',
|
||||
'ETX',
|
||||
'EOT',
|
||||
'ENQ',
|
||||
'ACK',
|
||||
'BEL',
|
||||
'BS',
|
||||
'HT',
|
||||
'LF',
|
||||
'VT',
|
||||
'FF',
|
||||
'CR',
|
||||
'SO',
|
||||
'SI',
|
||||
'DLE',
|
||||
'DC1',
|
||||
'DC2',
|
||||
'DC3',
|
||||
'DC4',
|
||||
'NAK',
|
||||
'SYN',
|
||||
'ETB',
|
||||
'CAN',
|
||||
'EM',
|
||||
'SUB',
|
||||
'ESC',
|
||||
'FS',
|
||||
'GS',
|
||||
'RS',
|
||||
'US',
|
||||
'SP',
|
||||
]
|
||||
|
||||
|
||||
def _ctoi(c: int | str) -> int:
|
||||
if isinstance(c, str):
|
||||
return ord(c)
|
||||
return c
|
||||
|
||||
|
||||
def isalnum(c: int | str) -> bool:
|
||||
return isalpha(c) or isdigit(c)
|
||||
|
||||
|
||||
def isalpha(c: int | str) -> bool:
|
||||
return isupper(c) or islower(c)
|
||||
|
||||
|
||||
def isascii(c: int | str) -> bool:
|
||||
return 0 <= _ctoi(c) <= 127
|
||||
|
||||
|
||||
def isblank(c: int | str) -> bool:
|
||||
return _ctoi(c) in (9, 32)
|
||||
|
||||
|
||||
def iscntrl(c: int | str) -> bool:
|
||||
return 0 <= _ctoi(c) <= 31 or _ctoi(c) == 127
|
||||
|
||||
|
||||
def isdigit(c: int | str) -> bool:
|
||||
return 48 <= _ctoi(c) <= 57
|
||||
|
||||
|
||||
def isgraph(c: int | str) -> bool:
|
||||
return 33 <= _ctoi(c) <= 126
|
||||
|
||||
|
||||
def islower(c: int | str) -> bool:
|
||||
return 97 <= _ctoi(c) <= 122
|
||||
|
||||
|
||||
def isprint(c: int | str) -> bool:
|
||||
return 32 <= _ctoi(c) <= 126
|
||||
|
||||
|
||||
def ispunct(c: int | str) -> bool:
|
||||
return isgraph(c) and not isalnum(c)
|
||||
|
||||
|
||||
def isspace(c: int | str) -> bool:
|
||||
return _ctoi(c) in (9, 10, 11, 12, 13, 32)
|
||||
|
||||
|
||||
def isupper(c: int | str) -> bool:
|
||||
return 65 <= _ctoi(c) <= 90
|
||||
|
||||
|
||||
def isxdigit(c: int | str) -> bool:
|
||||
return isdigit(c) or (65 <= _ctoi(c) <= 70) or (97 <= _ctoi(c) <= 102)
|
||||
|
||||
|
||||
def isctrl(c: int | str) -> bool:
|
||||
return 0 <= _ctoi(c) < 32
|
||||
|
||||
|
||||
def ismeta(c: int | str) -> bool:
|
||||
return _ctoi(c) > 127
|
||||
|
||||
|
||||
@overload
|
||||
def ascii(c: int) -> int: ... # pylint: disable=redefined-builtin
|
||||
|
||||
|
||||
@overload
|
||||
def ascii(c: str) -> str: ...
|
||||
|
||||
|
||||
def ascii(c: int | str) -> int | str:
|
||||
if isinstance(c, str):
|
||||
return chr(_ctoi(c) & 0x7F)
|
||||
return _ctoi(c) & 0x7F
|
||||
|
||||
|
||||
@overload
|
||||
def ctrl(c: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def ctrl(c: str) -> str: ...
|
||||
|
||||
|
||||
def ctrl(c: int | str) -> int | str:
|
||||
if isinstance(c, str):
|
||||
return chr(_ctoi(c) & 0x1F)
|
||||
return _ctoi(c) & 0x1F
|
||||
|
||||
|
||||
@overload
|
||||
def alt(c: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def alt(c: str) -> str: ...
|
||||
|
||||
|
||||
def alt(c: int | str) -> int | str:
|
||||
if isinstance(c, str):
|
||||
return chr(_ctoi(c) | 0x80)
|
||||
return _ctoi(c) | 0x80
|
||||
|
||||
|
||||
@overload
|
||||
def unctrl(c: int) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
def unctrl(c: str) -> str: ...
|
||||
|
||||
|
||||
def unctrl(c: int | str) -> str:
|
||||
bits = _ctoi(c)
|
||||
if bits == 0x7F:
|
||||
rep = '^?'
|
||||
elif isprint(bits & 0x7F):
|
||||
rep = chr(bits & 0x7F)
|
||||
else:
|
||||
rep = '^' + chr(((bits & 0x7F) | 0x20) + 0x20)
|
||||
if bits & 0x80:
|
||||
return '!' + rep
|
||||
return rep
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import curses
|
||||
import curses.ascii
|
||||
import string
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Tuple, Union
|
||||
|
||||
from nvitop.tui.library import curses
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ from __future__ import annotations
|
|||
|
||||
import colorsys
|
||||
import contextlib
|
||||
import curses
|
||||
import locale
|
||||
import os
|
||||
import signal
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Tuple, Union
|
||||
|
||||
from nvitop.tui.library import curses
|
||||
from nvitop.tui.library.history import GRAPH_SYMBOLS
|
||||
|
||||
|
||||
|
|
@ -173,15 +173,16 @@ def libcurses(colorful: bool = False, light_theme: bool = False) -> Generator[cu
|
|||
|
||||
# Push a Ctrl+C (ascii value 3) to the curses getch stack
|
||||
def interrupt_handler(*_: Any) -> None: # pylint: disable=unused-argument
|
||||
curses.ungetch(3)
|
||||
curses.ungetch(curses.ascii.ETX)
|
||||
|
||||
# Simulate a ^C press in curses when an interrupt is caught
|
||||
signal.signal(signal.SIGINT, interrupt_handler)
|
||||
original_interrupt_handler = signal.signal(signal.SIGINT, interrupt_handler)
|
||||
|
||||
try:
|
||||
yield win
|
||||
finally:
|
||||
curses.endwin()
|
||||
signal.signal(signal.SIGINT, original_interrupt_handler)
|
||||
|
||||
|
||||
class CursesShortcuts:
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from nvitop.tui.library import host
|
||||
from nvitop.tui.library import curses, host
|
||||
from nvitop.tui.library.displayable import Displayable
|
||||
from nvitop.tui.library.keybinding import NAMED_SPECIAL_KEYS, normalize_keybinding
|
||||
from nvitop.tui.library.utils import cut_string
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
from nvitop.tui.library import curses
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self # Python 3.11+
|
||||
|
|
|
|||
|
|
@ -132,9 +132,14 @@ class Selection: # pylint: disable=too-many-instance-attributes
|
|||
self.foreach(lambda process: process.send_signal(sig))
|
||||
|
||||
def interrupt(self) -> None:
|
||||
try:
|
||||
sig = (
|
||||
signal.SIGINT
|
||||
if not IS_WINDOWS
|
||||
# pylint: disable-next=no-member
|
||||
self.send_signal(signal.SIGINT if not IS_WINDOWS else signal.CTRL_C_EVENT) # type: ignore[attr-defined]
|
||||
else signal.CTRL_C_EVENT # type: ignore[attr-defined,unused-ignore]
|
||||
)
|
||||
try:
|
||||
self.send_signal(sig)
|
||||
except SystemError:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ with contextlib.suppress(AttributeError, OSError):
|
|||
if IS_WINDOWS:
|
||||
import ctypes
|
||||
|
||||
IS_SUPERUSER = bool(ctypes.windll.shell32.IsUserAnAdmin()) # type: ignore[attr-defined]
|
||||
IS_SUPERUSER = bool(ctypes.windll.shell32.IsUserAnAdmin()) # type: ignore[attr-defined,unused-ignore]
|
||||
else:
|
||||
try:
|
||||
IS_SUPERUSER = os.geteuid() == 0
|
||||
IS_SUPERUSER = os.geteuid() == 0 # type: ignore[attr-defined,unused-ignore]
|
||||
except AttributeError:
|
||||
IS_SUPERUSER = os.getuid() == 0
|
||||
IS_SUPERUSER = os.getuid() == 0 # type: ignore[attr-defined,unused-ignore]
|
||||
|
||||
HOSTNAME: str = hostname()
|
||||
IS_WINDOWS_SUBSYSTEM_FOR_LINUX = IS_WSL = bool(WINDOWS_SUBSYSTEM_FOR_LINUX)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from unicodedata import east_asian_width
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -18,27 +18,50 @@ __all__ = ['WideString', 'wcslen']
|
|||
|
||||
|
||||
ASCIIONLY: frozenset[str] = frozenset(map(chr, range(1, 128)))
|
||||
COMBINING: Literal[0] = 0
|
||||
NARROW: Literal[1] = 1
|
||||
WIDE: Literal[2] = 2
|
||||
WIDE_SYMBOLS: frozenset[str] = frozenset('WF')
|
||||
|
||||
|
||||
def utf_char_width(string: str) -> Literal[1, 2]:
|
||||
"""Return the width of a single character."""
|
||||
if east_asian_width(string) in WIDE_SYMBOLS:
|
||||
return WIDE
|
||||
def utf_char_width(string: str) -> Literal[0, 1, 2]: # pylint: disable=too-many-return-statements
|
||||
"""Return the width of a single character (0 for combining, 2 for wide, 1 otherwise)."""
|
||||
try:
|
||||
import wcwidth # pylint: disable=import-outside-toplevel
|
||||
|
||||
w = wcwidth.wcwidth(string)
|
||||
if w < 0:
|
||||
return NARROW # control characters treated as width 1
|
||||
if w == 0:
|
||||
return COMBINING
|
||||
if w >= 2:
|
||||
return WIDE
|
||||
except ImportError:
|
||||
# Fallback to unicodedata
|
||||
if unicodedata.combining(string):
|
||||
return COMBINING
|
||||
if unicodedata.east_asian_width(string) in WIDE_SYMBOLS:
|
||||
return WIDE
|
||||
return NARROW
|
||||
|
||||
|
||||
def string_to_charlist(string: str) -> list[str]:
|
||||
"""Return a list of characters with extra empty strings after wide chars."""
|
||||
"""Return a list of characters with extra empty strings after wide chars.
|
||||
|
||||
Combining characters (width 0) are merged with the preceding character.
|
||||
"""
|
||||
if ASCIIONLY.issuperset(string):
|
||||
return list(string)
|
||||
result = []
|
||||
result: list[str] = []
|
||||
for char in string:
|
||||
result.append(char)
|
||||
if east_asian_width(char) in WIDE_SYMBOLS:
|
||||
result.append('')
|
||||
width = utf_char_width(char)
|
||||
if width == COMBINING and result:
|
||||
# Merge combining character with the previous character
|
||||
result[-1] += char
|
||||
else:
|
||||
result.append(char)
|
||||
if width == WIDE:
|
||||
result.append('')
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
import shutil
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Literal, Union
|
||||
|
|
@ -19,6 +18,7 @@ from nvitop.tui.library import (
|
|||
MessageBox,
|
||||
MouseEvent,
|
||||
Snapshot,
|
||||
curses,
|
||||
)
|
||||
from nvitop.tui.screens import (
|
||||
BaseScreen,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ if not __release__:
|
|||
|
||||
|
||||
# The package `nvidia-ml-py` is not backward compatible over releases. This may
|
||||
# cause problems with Old versions of NVIDIA drivers.
|
||||
# cause problems with old versions of NVIDIA drivers.
|
||||
# The ideal solution is to let the user install the best-fit version of `nvidia-ml-py`.
|
||||
PYNVML_VERSION_CANDIDATES = (
|
||||
# Sync with pyproject.toml and requirements.txt
|
||||
|
|
@ -127,3 +127,16 @@ Note:
|
|||
which are incompatible with some old NVIDIA drivers. ``nvitop`` may not display the processes
|
||||
correctly due to this incompatibility.
|
||||
"""
|
||||
|
||||
|
||||
# Check that PYNVML_VERSION_CANDIDATES is sorted.
|
||||
if not __release__:
|
||||
try:
|
||||
from packaging.version import Version as _Version
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
assert (
|
||||
tuple(sorted(PYNVML_VERSION_CANDIDATES, key=_Version)) == PYNVML_VERSION_CANDIDATES
|
||||
), 'PYNVML_VERSION_CANDIDATES is not sorted.'
|
||||
del _Version
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ dependencies = [
|
|||
# Sync with nvitop/version.py and requirements.txt
|
||||
"nvidia-ml-py >= 11.450.51, < 13.596.0a0",
|
||||
"psutil >= 5.6.6",
|
||||
"colorama >= 0.4.0; platform_system == 'Windows'",
|
||||
"windows-curses >= 2.2.0; platform_system == 'Windows'",
|
||||
"colorama >= 0.4.6; platform_system == 'Windows'",
|
||||
]
|
||||
dynamic = ["version", "optional-dependencies"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Sync with pyproject.toml and nvitop/version.py
|
||||
nvidia-ml-py >= 11.450.51, < 13.596.0a0
|
||||
psutil >= 5.6.6
|
||||
colorama >= 0.4.0; platform_system == 'Windows'
|
||||
windows-curses >= 2.2.0; platform_system == 'Windows'
|
||||
colorama >= 0.4.6; platform_system == 'Windows'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue