mirror of
https://github.com/XuehaiPan/nvitop.git
synced 2026-05-15 14:15:55 -06:00
fix(tui/host): ignore errors when collecting host metrics (#163)
This commit is contained in:
parent
57b48e6a3a
commit
dfb4e3bf55
12 changed files with 174 additions and 71 deletions
|
|
@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
-
|
||||
- Ignore errors when collecting host metrics for host panel by [@XuehaiPan](https://github.com/XuehaiPan) in [#163](https://github.com/XuehaiPan/nvitop/pull/163).
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
# pylint: disable=missing-module-docstring
|
||||
|
||||
from nvitop.tui.library.device import NA, Device
|
||||
from nvitop.tui.library import host
|
||||
from nvitop.tui.library.device import Device
|
||||
from nvitop.tui.library.displayable import Displayable, DisplayableContainer
|
||||
from nvitop.tui.library.history import BufferedHistoryGraph, HistoryGraph
|
||||
from nvitop.tui.library.keybinding import (
|
||||
|
|
@ -19,26 +20,25 @@ from nvitop.tui.library.keybinding import (
|
|||
from nvitop.tui.library.libcurses import libcurses, setlocale_utf8
|
||||
from nvitop.tui.library.messagebox import MessageBox, send_signal
|
||||
from nvitop.tui.library.mouse import MouseEvent
|
||||
from nvitop.tui.library.process import (
|
||||
GiB,
|
||||
GpuProcess,
|
||||
HostProcess,
|
||||
Snapshot,
|
||||
bytes2human,
|
||||
host,
|
||||
timedelta2human,
|
||||
)
|
||||
from nvitop.tui.library.process import GpuProcess, HostProcess
|
||||
from nvitop.tui.library.selection import Selection
|
||||
from nvitop.tui.library.utils import (
|
||||
HOSTNAME,
|
||||
LARGE_INTEGER,
|
||||
NA,
|
||||
SUPERUSER,
|
||||
USERCONTEXT,
|
||||
USER_CONTEXT,
|
||||
USERNAME,
|
||||
WINDOWS,
|
||||
WSL,
|
||||
GiB,
|
||||
Snapshot,
|
||||
bytes2human,
|
||||
colored,
|
||||
cut_string,
|
||||
make_bar,
|
||||
set_color,
|
||||
timedelta2human,
|
||||
ttl_cache,
|
||||
)
|
||||
from nvitop.tui.library.widestring import WideString, wcslen
|
||||
|
|
|
|||
101
nvitop/tui/library/host.py
Normal file
101
nvitop/tui/library/host.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# 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
|
||||
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, NamedTuple
|
||||
|
||||
from nvitop.api import NA, host
|
||||
from nvitop.api.host import WINDOWS, WSL, AccessDenied, PsutilError
|
||||
|
||||
|
||||
__all__ = [
|
||||
'WINDOWS',
|
||||
'WSL',
|
||||
'AccessDenied',
|
||||
'PsutilError',
|
||||
'cpu_percent',
|
||||
'getuser',
|
||||
'hostname',
|
||||
'load_average',
|
||||
'reverse_ppid_map',
|
||||
'swap_memory',
|
||||
'uptime',
|
||||
'virtual_memory',
|
||||
]
|
||||
|
||||
|
||||
def ignore_error(*, fallback):
|
||||
"""Ignore errors in the function."""
|
||||
|
||||
def wrapper(func):
|
||||
def wrapped(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception: # noqa: BLE001 # pylint: disable=broad-exception-caught
|
||||
return fallback
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class VirtualMemory(NamedTuple): # pylint: disable=missing-class-docstring
|
||||
total: int
|
||||
available: int
|
||||
percent: int
|
||||
used: int
|
||||
free: int
|
||||
|
||||
|
||||
@ignore_error(fallback=VirtualMemory(NA, NA, NA, NA, NA))
|
||||
def virtual_memory():
|
||||
vm = host.virtual_memory()
|
||||
return VirtualMemory(
|
||||
total=vm.total,
|
||||
available=vm.available,
|
||||
percent=vm.percent,
|
||||
used=vm.used,
|
||||
free=vm.free,
|
||||
)
|
||||
|
||||
|
||||
class SwapMemory(NamedTuple): # pylint: disable=missing-class-docstring
|
||||
total: int
|
||||
used: int
|
||||
free: int
|
||||
percent: float
|
||||
sin: int
|
||||
sout: int
|
||||
|
||||
|
||||
@ignore_error(fallback=SwapMemory(NA, NA, NA, NA, NA, NA))
|
||||
def swap_memory():
|
||||
sm = host.swap_memory()
|
||||
return SwapMemory(
|
||||
total=sm.total,
|
||||
used=sm.used,
|
||||
free=sm.free,
|
||||
percent=sm.percent,
|
||||
sin=sm.sin,
|
||||
sout=sm.sout,
|
||||
)
|
||||
|
||||
|
||||
@ignore_error(fallback=(NA, NA, NA))
|
||||
def load_average():
|
||||
la = host.load_average()
|
||||
if la is None:
|
||||
return (NA, NA, NA)
|
||||
return la
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nvitop.api.host import cpu_percent, getuser, hostname, reverse_ppid_map, uptime
|
||||
else:
|
||||
cpu_percent = ignore_error(fallback=NA)(host.cpu_percent)
|
||||
getuser = ignore_error(fallback=NA)(host.getuser)
|
||||
hostname = ignore_error(fallback=NA)(host.hostname)
|
||||
uptime = ignore_error(fallback=NA)(host.uptime)
|
||||
reverse_ppid_map = ignore_error(fallback=MappingProxyType({}))(host.reverse_ppid_map)
|
||||
|
|
@ -10,9 +10,9 @@ import threading
|
|||
import time
|
||||
from functools import partial
|
||||
|
||||
from nvitop.tui.library import host
|
||||
from nvitop.tui.library.displayable import Displayable
|
||||
from nvitop.tui.library.keybinding import normalize_keybinding
|
||||
from nvitop.tui.library.process import host
|
||||
from nvitop.tui.library.utils import cut_string
|
||||
from nvitop.tui.library.widestring import WideString
|
||||
|
||||
|
|
|
|||
|
|
@ -3,29 +3,12 @@
|
|||
|
||||
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
|
||||
|
||||
from nvitop.api import (
|
||||
NA,
|
||||
GiB,
|
||||
HostProcess,
|
||||
Snapshot,
|
||||
bytes2human,
|
||||
host,
|
||||
timedelta2human,
|
||||
utilization2string,
|
||||
)
|
||||
from nvitop.api import NA, HostProcess, Snapshot, utilization2string
|
||||
from nvitop.api import GpuProcess as GpuProcessBase
|
||||
from nvitop.api.host import WINDOWS, WSL
|
||||
|
||||
|
||||
__all__ = [
|
||||
'host',
|
||||
'HostProcess',
|
||||
'GpuProcess',
|
||||
'NA',
|
||||
'Snapshot',
|
||||
'bytes2human',
|
||||
'GiB',
|
||||
'timedelta2human',
|
||||
]
|
||||
__all__ = ['HostProcess', 'GpuProcess']
|
||||
|
||||
|
||||
class GpuProcess(GpuProcessBase):
|
||||
|
|
@ -63,7 +46,7 @@ class GpuProcess(GpuProcessBase):
|
|||
snapshot = super().as_snapshot(host_process_snapshot_cache=host_process_snapshot_cache)
|
||||
|
||||
snapshot.type = snapshot.type.replace('C+G', 'X')
|
||||
if snapshot.gpu_memory_human is NA and (host.WINDOWS or host.WSL):
|
||||
if snapshot.gpu_memory_human is NA and (WINDOWS or WSL):
|
||||
snapshot.gpu_memory_human = 'WDDM:N/A'
|
||||
|
||||
snapshot.cpu_percent_string = snapshot.host.cpu_percent_string
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import signal
|
|||
import time
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from nvitop.api import NA, Snapshot, host
|
||||
from nvitop.api import NA, Snapshot
|
||||
from nvitop.tui.library import host
|
||||
from nvitop.tui.library.utils import LARGE_INTEGER, SUPERUSER, USERNAME
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,32 +7,44 @@ import contextlib
|
|||
import math
|
||||
import os
|
||||
|
||||
from nvitop.api import NA, colored, host, set_color, ttl_cache
|
||||
from nvitop.api import (
|
||||
NA,
|
||||
GiB,
|
||||
Snapshot,
|
||||
bytes2human,
|
||||
colored,
|
||||
set_color,
|
||||
timedelta2human,
|
||||
ttl_cache,
|
||||
)
|
||||
from nvitop.tui.library.host import WINDOWS, WSL, getuser, hostname
|
||||
from nvitop.tui.library.widestring import WideString
|
||||
|
||||
|
||||
__all__ = [
|
||||
'NA',
|
||||
'USERNAME',
|
||||
'HOSTNAME',
|
||||
'SUPERUSER',
|
||||
'USERCONTEXT',
|
||||
'LARGE_INTEGER',
|
||||
'ttl_cache',
|
||||
'NA',
|
||||
'SUPERUSER',
|
||||
'USER_CONTEXT',
|
||||
'USERNAME',
|
||||
'GiB',
|
||||
'Snapshot',
|
||||
'bytes2human',
|
||||
'colored',
|
||||
'set_color',
|
||||
'cut_string',
|
||||
'make_bar',
|
||||
'set_color',
|
||||
'timedelta2human',
|
||||
'ttl_cache',
|
||||
]
|
||||
|
||||
|
||||
USERNAME = 'N/A'
|
||||
with contextlib.suppress(ImportError, OSError):
|
||||
USERNAME = host.getuser()
|
||||
USERNAME = getuser()
|
||||
|
||||
SUPERUSER = False
|
||||
with contextlib.suppress(AttributeError, OSError):
|
||||
if host.WINDOWS:
|
||||
if WINDOWS:
|
||||
import ctypes
|
||||
|
||||
SUPERUSER = bool(ctypes.windll.shell32.IsUserAnAdmin())
|
||||
|
|
@ -42,11 +54,11 @@ with contextlib.suppress(AttributeError, OSError):
|
|||
except AttributeError:
|
||||
SUPERUSER = os.getuid() == 0
|
||||
|
||||
HOSTNAME = host.hostname()
|
||||
if host.WSL:
|
||||
HOSTNAME = hostname()
|
||||
if WSL:
|
||||
HOSTNAME = f'{HOSTNAME} (WSL)'
|
||||
|
||||
USERCONTEXT = f'{USERNAME}@{HOSTNAME}'
|
||||
USER_CONTEXT = f'{USERNAME}@{HOSTNAME}'
|
||||
|
||||
|
||||
LARGE_INTEGER = 65536
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import time
|
|||
|
||||
from nvitop.tui.library import (
|
||||
NA,
|
||||
WINDOWS,
|
||||
Device,
|
||||
Displayable,
|
||||
colored,
|
||||
cut_string,
|
||||
host,
|
||||
make_bar,
|
||||
ttl_cache,
|
||||
)
|
||||
|
|
@ -90,7 +90,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
'│ {memory_usage:>20} │ BAR1: {bar1_memory_used_human:>8} / {bar1_memory_percent_string:>3} │',
|
||||
]
|
||||
|
||||
if host.WINDOWS:
|
||||
if WINDOWS:
|
||||
self.formats_full[0] = self.formats_full[0].replace(
|
||||
'persistence_mode',
|
||||
'current_driver_model',
|
||||
|
|
@ -218,7 +218,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
'│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │',
|
||||
),
|
||||
)
|
||||
if host.WINDOWS:
|
||||
if WINDOWS:
|
||||
header[-2] = header[-2].replace('Persistence-M', ' TCC/WDDM ')
|
||||
if self.support_mig:
|
||||
header[-2] = header[-2].replace('Volatile Uncorr. ECC', 'MIG M. Uncorr. ECC')
|
||||
|
|
|
|||
|
|
@ -254,13 +254,9 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
def draw(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
||||
self.color_reset()
|
||||
|
||||
if self.load_average is not None:
|
||||
load_average = tuple(
|
||||
f'{value:5.2f}'[:5] if value < 10000.0 else '9999+' for value in self.load_average
|
||||
)
|
||||
else:
|
||||
load_average = (NA,) * 3
|
||||
load_average = 'Load Average: {} {} {}'.format(*load_average)
|
||||
load_average = 'Load Average: {} {} {}'.format(
|
||||
*(f'{value:5.2f}'[:5] if value < 10000.0 else '9999+' for value in self.load_average),
|
||||
)
|
||||
|
||||
if self.compact:
|
||||
width_right = len(load_average) + 4
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ from nvitop.tui.library import (
|
|||
HOSTNAME,
|
||||
LARGE_INTEGER,
|
||||
SUPERUSER,
|
||||
USERCONTEXT,
|
||||
USER_CONTEXT,
|
||||
USERNAME,
|
||||
WINDOWS,
|
||||
WSL,
|
||||
Displayable,
|
||||
GpuProcess,
|
||||
MouseEvent,
|
||||
|
|
@ -24,7 +26,6 @@ from nvitop.tui.library import (
|
|||
WideString,
|
||||
colored,
|
||||
cut_string,
|
||||
host,
|
||||
ttl_cache,
|
||||
wcslen,
|
||||
)
|
||||
|
|
@ -330,7 +331,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
]
|
||||
if len(self.snapshots) == 0:
|
||||
if self.has_snapshots:
|
||||
message = ' No running processes found{} '.format(' (in WSL)' if host.WSL else '')
|
||||
message = ' No running processes found{} '.format(' (in WSL)' if WSL else '')
|
||||
else:
|
||||
message = ' Gathering process status...'
|
||||
header.extend(
|
||||
|
|
@ -390,13 +391,13 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
for y, line in enumerate(self.header_lines(), start=self.y + 1):
|
||||
self.addstr(y, self.x, line)
|
||||
|
||||
context_width = wcslen(USERCONTEXT)
|
||||
if not host.WINDOWS or len(USERCONTEXT) == context_width:
|
||||
context_width = wcslen(USER_CONTEXT)
|
||||
if not WINDOWS or len(USER_CONTEXT) == context_width:
|
||||
# Do not support windows-curses with wide characters
|
||||
username_width = wcslen(USERNAME)
|
||||
hostname_width = wcslen(HOSTNAME)
|
||||
offset = self.x + self.width - context_width - 2
|
||||
self.addstr(self.y + 2, self.x + offset, USERCONTEXT)
|
||||
self.addstr(self.y + 2, self.x + offset, USER_CONTEXT)
|
||||
self.color_at(self.y + 2, self.x + offset, width=context_width, attr='bold')
|
||||
self.color_at(
|
||||
self.y + 2,
|
||||
|
|
@ -561,7 +562,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
self.selection.clear()
|
||||
|
||||
elif self.has_snapshots:
|
||||
message = ' No running processes found{} '.format(' (in WSL)' if host.WSL else '')
|
||||
message = ' No running processes found{} '.format(' (in WSL)' if WSL else '')
|
||||
self.addstr(self.y + 5, self.x, f'│ {message.ljust(self.width - 4)} │')
|
||||
|
||||
text_offset = self.x + self.width - 47
|
||||
|
|
@ -599,7 +600,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
lines = ['', *self.header_lines()]
|
||||
lines[2] = ''.join(
|
||||
(
|
||||
lines[2][: -2 - wcslen(USERCONTEXT)],
|
||||
lines[2][: -2 - wcslen(USER_CONTEXT)],
|
||||
colored(USERNAME, color=('yellow' if SUPERUSER else 'magenta'), attrs=('bold',)),
|
||||
colored('@', attrs=('bold',)),
|
||||
colored(HOSTNAME, color='green', attrs=('bold',)),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from nvitop.tui.library import (
|
|||
HOSTNAME,
|
||||
NA,
|
||||
SUPERUSER,
|
||||
USERCONTEXT,
|
||||
USER_CONTEXT,
|
||||
USERNAME,
|
||||
BufferedHistoryGraph,
|
||||
Displayable,
|
||||
|
|
@ -350,13 +350,13 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
|||
for y, line in enumerate(self.frame_lines(), start=self.y):
|
||||
self.addstr(y, self.x, line)
|
||||
|
||||
context_width = wcslen(USERCONTEXT)
|
||||
if not host.WINDOWS or len(USERCONTEXT) == context_width:
|
||||
context_width = wcslen(USER_CONTEXT)
|
||||
if not host.WINDOWS or len(USER_CONTEXT) == context_width:
|
||||
# Do not support windows-curses with wide characters
|
||||
username_width = wcslen(USERNAME)
|
||||
hostname_width = wcslen(HOSTNAME)
|
||||
offset = self.x + self.width - context_width - 2
|
||||
self.addstr(self.y + 1, self.x + offset, USERCONTEXT)
|
||||
self.addstr(self.y + 1, self.x + offset, USER_CONTEXT)
|
||||
self.color_at(self.y + 1, self.x + offset, width=context_width, attr='bold')
|
||||
self.color_at(
|
||||
self.y + 1,
|
||||
|
|
|
|||
|
|
@ -197,6 +197,12 @@ ignore = [
|
|||
"ANN", # flake8-annotations
|
||||
"RUF012", # mutable-class-default
|
||||
]
|
||||
"!nvitop/tui/**/*.py" = [
|
||||
"TID251", # banned-api
|
||||
]
|
||||
"nvitop/tui/library/*.py" = [
|
||||
"TID251", # banned-api
|
||||
]
|
||||
"docs/source/conf.py" = [
|
||||
"D", # pydocstyle
|
||||
"INP001", # flake8-no-pep420
|
||||
|
|
@ -220,3 +226,6 @@ inline-quotes = "single"
|
|||
|
||||
[tool.ruff.lint.flake8-tidy-imports]
|
||||
ban-relative-imports = "all"
|
||||
|
||||
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||
"nvitop.api".msg = "Use `nvitop.tui.library` instead of `nvitop.api` in `nvitop.tui`."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue