feat: ruff integration

This commit is contained in:
Xuehai Pan 2023-03-15 09:18:14 +00:00
parent c5ce570c72
commit 80b9d6f75c
32 changed files with 524 additions and 312 deletions

View file

@ -20,6 +20,9 @@ per-file-ignores =
# F401: module imported but unused
# intentionally unused imports
__init__.py: F401
# SIM113: use enumarate
# false positive
nvitop/gui/screens/main/process.py: SIM113
exclude =
.git,
.vscode,

View file

@ -23,6 +23,12 @@ repos:
- id: detect-private-key
- id: debug-statements
- id: double-quote-string-fixer
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.256
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit, push, manual]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:

View file

@ -173,7 +173,7 @@ def take_snapshots(
else:
leaf_devices = devices = list(devices)
gpu_processes = list(
itertools.chain.from_iterable(device.processes().values() for device in leaf_devices)
itertools.chain.from_iterable(device.processes().values() for device in leaf_devices),
)
devices = [device.as_snapshot() for device in devices]
@ -430,7 +430,9 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
self._tags = set()
self._daemon = threading.Thread(
name='gpu_metric_collector_daemon', target=self._target, daemon=True
name='gpu_metric_collector_daemon',
target=self._target,
daemon=True,
)
self._daemon_running = threading.Event()
@ -489,7 +491,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
tag = self._metric_buffer.tag
elif tag not in self._tags:
raise RuntimeError(
f'Resource metric collector has not been started with tag "{tag}".'
f'Resource metric collector has not been started with tag "{tag}".',
)
buffer = self._metric_buffer
@ -568,7 +570,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
tag = self._metric_buffer.tag
elif tag not in self._tags:
raise RuntimeError(
f'Resource metric collector has not been started with tag "{tag}".'
f'Resource metric collector has not been started with tag "{tag}".',
)
buffer = self._metric_buffer
@ -707,7 +709,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
'host/memory_percent (%)': host.memory_percent(),
'host/swap_percent (%)': host.swap_percent(),
'host/memory_used (GiB)': host.virtual_memory().used / GiB,
}
},
)
load_average = host.load_average()
if load_average is not None:
@ -716,7 +718,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
'host/load_average (%) (1 min)': load_average[0],
'host/load_average (%) (5 min)': load_average[1],
'host/load_average (%) (15 min)': load_average[2],
}
},
)
device_identifiers = {}
@ -755,7 +757,10 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
class _MetricBuffer: # pylint: disable=missing-class-docstring,missing-function-docstring,too-many-instance-attributes
def __init__(
self, tag: str, collector: ResourceMetricCollector, prev: _MetricBuffer | None = None
self,
tag: str,
collector: ResourceMetricCollector,
prev: _MetricBuffer | None = None,
) -> None:
self.collector = collector
self.prev = prev
@ -800,9 +805,7 @@ class _MetricBuffer: # pylint: disable=missing-class-docstring,missing-function
if key.endswith('host/running_time (min)/max'):
metrics[key[:-4]] = metrics[key]
del metrics[key]
elif key.endswith('host/running_time (min)/mean') or key.endswith(
'host/running_time (min)/min'
):
elif key.endswith(('host/running_time (min)/mean', 'host/running_time (min)/min')):
del metrics[key]
metrics[f'{self.key_prefix}/duration (s)'] = timer() - self.start_timestamp
metrics[f'{self.key_prefix}/timestamp'] = time.time()

View file

@ -349,7 +349,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
@classmethod
def from_indices(
cls, indices: int | Iterable[int | tuple[int, int]] | None = None
cls,
indices: int | Iterable[int | tuple[int, int]] | None = None,
) -> list[PhysicalDevice | MigDevice]:
"""Return a list of devices of the given indices.
@ -412,7 +413,9 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
return cuda_devices
@staticmethod
def from_cuda_indices(cuda_indices: int | Iterable[int] | None = None) -> list[CudaDevice]:
def from_cuda_indices(
cuda_indices: int | Iterable[int] | None = None,
) -> list[CudaDevice]:
"""Return a list of CUDA devices of the given CUDA indices.
The CUDA ordinal will be enumerate from the ``CUDA_VISIBLE_DEVICES`` environment variable.
@ -541,7 +544,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
if (index, uuid, bus_id).count(None) != 2:
raise TypeError(
f'Device(index=None, uuid=None, bus_id=None) takes 1 non-None arguments '
f'but (index, uuid, bus_id) = {(index, uuid, bus_id)!r} were given'
f'but (index, uuid, bus_id) = {(index, uuid, bus_id)!r} were given',
)
if cls is not Device:
@ -560,14 +563,14 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
if not isinstance(index, tuple):
raise TypeError(
f'index must be an integer, or a tuple of two integers, or a valid UUID string, '
f'but index = {index!r} was given'
f'but index = {index!r} was given',
)
if not (
len(index) == 2 and isinstance(index[0], int) and isinstance(index[1], int)
):
raise TypeError(
f'index for MIG device must be a tuple of two integers '
f'but index = {index!r} was given'
f'but index = {index!r} was given',
)
return super().__new__(MigDevice)
elif uuid is not None and match is not None and match.group('MigMode') is not None:
@ -616,7 +619,9 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
self._nvml_index = index
try:
self._handle = libnvml.nvmlQuery(
'nvmlDeviceGetHandleByIndex', index, ignore_errors=False
'nvmlDeviceGetHandleByIndex',
index,
ignore_errors=False,
)
except libnvml.NVMLError_GpuIsLost:
self._handle = None
@ -628,11 +633,15 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
try:
if uuid is not None:
self._handle = libnvml.nvmlQuery(
'nvmlDeviceGetHandleByUUID', uuid, ignore_errors=False
'nvmlDeviceGetHandleByUUID',
uuid,
ignore_errors=False,
)
else:
self._handle = libnvml.nvmlQuery(
'nvmlDeviceGetHandleByPciBusId', bus_id, ignore_errors=False
'nvmlDeviceGetHandleByPciBusId',
bus_id,
ignore_errors=False,
)
except libnvml.NVMLError_GpuIsLost:
self._handle = None
@ -655,7 +664,10 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
def __str__(self) -> str:
"""Return a string representation of the device."""
return '{}(index={}, name="{}", total_memory={})'.format(
self.__class__.__name__, self.index, self.name(), self.memory_total_human()
self.__class__.__name__,
self.index,
self.name(),
self.memory_total_human(),
)
__repr__ = __str__
@ -721,10 +733,14 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
func = getattr(libnvml, 'nvmlDeviceGet' + pascal_case + suffix)
@ttl_cache(ttl=1.0)
def attribute(*args, **kwargs):
def attribute(*args: Any, **kwargs: Any) -> Any:
try:
return libnvml.nvmlQuery(
func, self._handle, *args, **kwargs, ignore_errors=False
func,
self._handle,
*args,
**kwargs,
ignore_errors=False,
)
except libnvml.NVMLError_NotSupported:
return NA
@ -788,7 +804,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
self._cuda_index = visible_device_indices.index(self.index)
except ValueError as ex:
raise RuntimeError(
f'CUDA Error: Device(index={self.index}) is not visible to CUDA applications'
f'CUDA Error: Device(index={self.index}) is not visible to CUDA applications',
) from ex
return self._cuda_index
@ -841,7 +857,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
"""
if self._bus_id is NA:
self._bus_id = libnvml.nvmlQuery(
lambda handle: libnvml.nvmlDeviceGetPciInfo(handle).busId, self.handle
lambda handle: libnvml.nvmlDeviceGetPciInfo(handle).busId,
self.handle,
)
return self._bus_id
@ -952,7 +969,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
"""
memory_info = self.memory_info()
if libnvml.nvmlCheckReturn(memory_info.used, int) and libnvml.nvmlCheckReturn(
memory_info.total, int
memory_info.total,
int,
):
return round(100.0 * memory_info.used / memory_info.total, 1)
return NA
@ -976,7 +994,9 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
memory_info = libnvml.nvmlQuery('nvmlDeviceGetBAR1MemoryInfo', self.handle)
if libnvml.nvmlCheckReturn(memory_info):
return MemoryInfo(
total=memory_info.bar1Total, free=memory_info.bar1Free, used=memory_info.bar1Used
total=memory_info.bar1Total,
free=memory_info.bar1Free,
used=memory_info.bar1Used,
)
return MemoryInfo(total=NA, free=NA, used=NA)
@ -1036,7 +1056,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
""" # pylint: disable=line-too-long
memory_info = self.bar1_memory_info()
if libnvml.nvmlCheckReturn(memory_info.used, int) and libnvml.nvmlCheckReturn(
memory_info.total, int
memory_info.total,
int,
):
return round(100.0 * memory_info.used / memory_info.total, 1)
return NA
@ -1133,12 +1154,16 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
""" # pylint: disable=line-too-long
return ClockInfos(
graphics=libnvml.nvmlQuery(
'nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_GRAPHICS
'nvmlDeviceGetClockInfo',
self.handle,
libnvml.NVML_CLOCK_GRAPHICS,
),
sm=libnvml.nvmlQuery('nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_SM),
memory=libnvml.nvmlQuery('nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_MEM),
video=libnvml.nvmlQuery(
'nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_VIDEO
'nvmlDeviceGetClockInfo',
self.handle,
libnvml.NVML_CLOCK_VIDEO,
),
)
@ -1156,7 +1181,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
for name, clock in clock_infos.items():
if clock is NA:
clock_type = getattr(
libnvml, 'NVML_CLOCK_{}'.format(name.replace('memory', 'mem').upper())
libnvml,
'NVML_CLOCK_{}'.format(name.replace('memory', 'mem').upper()),
)
clock = libnvml.nvmlQuery('nvmlDeviceGetMaxClockInfo', self.handle, clock_type)
clock_infos[name] = clock
@ -1319,7 +1345,9 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=temperature.gpu
"""
return libnvml.nvmlQuery(
'nvmlDeviceGetTemperature', self.handle, libnvml.NVML_TEMPERATURE_GPU
'nvmlDeviceGetTemperature',
self.handle,
libnvml.NVML_TEMPERATURE_GPU,
)
@memoize_when_activated
@ -1391,7 +1419,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=display_active
""" # pylint: disable=line-too-long
return {0: 'Disabled', 1: 'Enabled'}.get(
libnvml.nvmlQuery('nvmlDeviceGetDisplayActive', self.handle), NA
libnvml.nvmlQuery('nvmlDeviceGetDisplayActive', self.handle),
NA,
)
@ttl_cache(ttl=60.0)
@ -1412,7 +1441,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=display_mode
""" # pylint: disable=line-too-long
return {0: 'Disabled', 1: 'Enabled'}.get(
libnvml.nvmlQuery('nvmlDeviceGetDisplayMode', self.handle), NA
libnvml.nvmlQuery('nvmlDeviceGetDisplayMode', self.handle),
NA,
)
@ttl_cache(ttl=60.0)
@ -1437,7 +1467,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=driver_model.current
"""
return {libnvml.NVML_DRIVER_WDDM: 'WDDM', libnvml.NVML_DRIVER_WDM: 'WDM'}.get(
libnvml.nvmlQuery('nvmlDeviceGetCurrentDriverModel', self.handle), NA
libnvml.nvmlQuery('nvmlDeviceGetCurrentDriverModel', self.handle),
NA,
)
driver_model = current_driver_model
@ -1462,7 +1493,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=persistence_mode
""" # pylint: disable=line-too-long
return {0: 'Disabled', 1: 'Enabled'}.get(
libnvml.nvmlQuery('nvmlDeviceGetPersistenceMode', self.handle), NA
libnvml.nvmlQuery('nvmlDeviceGetPersistenceMode', self.handle),
NA,
)
@ttl_cache(ttl=5.0)
@ -1541,7 +1573,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
"""
if self._cuda_compute_capability is None:
self._cuda_compute_capability = libnvml.nvmlQuery(
'nvmlDeviceGetCudaComputeCapability', self.handle
'nvmlDeviceGetCudaComputeCapability',
self.handle,
)
return self._cuda_compute_capability
@ -1575,9 +1608,12 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
if self.is_mig_device():
return NA
mig_mode = libnvml.nvmlQuery(
'nvmlDeviceGetMigMode', self.handle, default=(NA, NA), ignore_function_not_found=True
)[0]
mig_mode, *_ = libnvml.nvmlQuery(
'nvmlDeviceGetMigMode',
self.handle,
default=(NA, NA),
ignore_function_not_found=True,
)
return {0: 'Disabled', 1: 'Enabled'}.get(mig_mode, NA)
def is_mig_mode_enabled(self) -> bool:
@ -1652,9 +1688,12 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
if len(processes) > 0:
samples = libnvml.nvmlQuery(
'nvmlDeviceGetProcessUtilization', self.handle, self._timestamp, default=()
'nvmlDeviceGetProcessUtilization',
self.handle,
self._timestamp,
default=(),
)
self._timestamp = max(min((s.timeStamp for s in samples), default=0) - 2000000, 0)
self._timestamp = max(min((s.timeStamp for s in samples), default=0) - 2_000_000, 0)
for s in samples:
try:
processes[s.pid].set_gpu_utilization(s.smUtil, s.memUtil, s.encUtil, s.decUtil)
@ -1720,7 +1759,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
# Modified from psutil (https://github.com/giampaolo/psutil)
@contextlib.contextmanager
def oneshot(self):
def oneshot(self) -> contextlib.AbstractContextManager:
"""A utility context manager which considerably speeds up the retrieval of multiple device information at the same time.
Internally different device info (e.g. memory_info, utilization_rates, ...) may be fetched
@ -1804,7 +1843,10 @@ class PhysicalDevice(Device):
This method will return 0 if the device does not support MIG mode.
"""
return libnvml.nvmlQuery(
'nvmlDeviceGetMaxMigDeviceCount', self.handle, default=0, ignore_function_not_found=True
'nvmlDeviceGetMaxMigDeviceCount',
self.handle,
default=0,
ignore_function_not_found=True,
)
@ttl_cache(ttl=60.0)
@ -1859,7 +1901,8 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
@classmethod
def from_indices( # pylint: disable=signature-differs
cls, indices: Iterable[tuple[int, int]]
cls,
indices: Iterable[tuple[int, int]],
) -> list[MigDevice]:
"""Return a list of MIG devices of the given indices.
@ -1885,7 +1928,10 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
# pylint: disable-next=super-init-not-called
def __init__(
self, index: tuple[int, int] | str | None = None, *, uuid: str | None = None
self,
index: tuple[int, int] | str | None = None,
*,
uuid: str | None = None,
) -> None:
"""Initialize the instance created by :meth:`__new__()`.
@ -1941,10 +1987,14 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
else:
self._handle = libnvml.nvmlQuery('nvmlDeviceGetHandleByUUID', uuid, ignore_errors=False)
parent_handle = libnvml.nvmlQuery(
'nvmlDeviceGetDeviceHandleFromMigDeviceHandle', self.handle, ignore_errors=False
'nvmlDeviceGetDeviceHandleFromMigDeviceHandle',
self.handle,
ignore_errors=False,
)
parent_index = libnvml.nvmlQuery(
'nvmlDeviceGetIndex', parent_handle, ignore_errors=False
'nvmlDeviceGetIndex',
parent_handle,
ignore_errors=False,
)
self._parent = PhysicalDevice(index=parent_index)
for mig_device in self.parent.mig_devices():
@ -1989,7 +2039,9 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
"""
if self._gpu_instance_id is NA:
self._gpu_instance_id = libnvml.nvmlQuery(
'nvmlDeviceGetGpuInstanceId', self.handle, default=0xFFFFFFFF
'nvmlDeviceGetGpuInstanceId',
self.handle,
default=0xFFFFFFFF,
)
if self._gpu_instance_id == 0xFFFFFFFF:
self._gpu_instance_id = NA
@ -2003,7 +2055,9 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
"""
if self._compute_instance_id is NA:
self._compute_instance_id = libnvml.nvmlQuery(
'nvmlDeviceGetComputeInstanceId', self.handle, default=0xFFFFFFFF
'nvmlDeviceGetComputeInstanceId',
self.handle,
default=0xFFFFFFFF,
)
if self._compute_instance_id == 0xFFFFFFFF:
self._compute_instance_id = NA
@ -2114,7 +2168,10 @@ class CudaDevice(Device):
return cls.from_indices()
@classmethod
def from_indices(cls, indices: int | Iterable[int] | None = None) -> list[CudaDevice]:
def from_indices(
cls,
indices: int | Iterable[int] | None = None,
) -> list[CudaDevice]:
"""Return a list of CUDA devices of the given CUDA indices.
The CUDA ordinal will be enumerate from the ``CUDA_VISIBLE_DEVICES`` environment variable.
@ -2415,7 +2472,7 @@ def _get_all_physical_device_attrs() -> dict[str, _PhysicalDeviceAttrs]:
),
)
for device in PhysicalDevice.all()
]
],
)
return _PHYSICAL_DEVICE_ATTRS
@ -2598,7 +2655,9 @@ def _parse_cuda_visible_devices_to_uuids(
def _cuda_visible_devices_parser(
cuda_visible_devices: str, queue: mp.SimpleQueue, verbose: bool = True
cuda_visible_devices: str,
queue: mp.SimpleQueue,
verbose: bool = True,
) -> None:
try:
if cuda_visible_devices is not None:
@ -2622,7 +2681,7 @@ def _cuda_visible_devices_parser(
uuids = list(map(libcuda.cuDeviceGetUuid, map(libcuda.cuDeviceGet, range(count))))
queue.put(uuids)
return
except Exception as ex: # pylint: disable=broad-except
except Exception as ex: # noqa: BLE001 # pylint: disable=broad-except
queue.put(ex)
if verbose:
raise ex

View file

@ -20,7 +20,10 @@
utilization (CPU, memory, disks, network, sensors) in Python.
"""
from __future__ import annotations
import os as _os
from typing import Callable as _Callable
import psutil as _psutil
from cachetools.func import ttl_cache as _ttl_cache
@ -49,30 +52,32 @@ swap_memory = _ttl_cache(ttl=0.25)(_psutil.swap_memory)
try:
load_average = _ttl_cache(ttl=2.0)(_psutil.getloadavg)
load_average: _Callable[[], tuple[float, float, float]] = _ttl_cache(ttl=2.0)(
_psutil.getloadavg,
)
load_average.__doc__ = """Get the system load average."""
except AttributeError:
def load_average():
def load_average() -> None:
"""Get the system load average."""
return None
return
def memory_percent():
def memory_percent() -> float:
"""The percentage usage of virtual memory, calculated as ``(total - available) / total * 100``."""
return virtual_memory().percent
def swap_percent():
def swap_percent() -> float:
"""The percentage usage of virtual memory, calculated as ``used / total * 100``."""
return swap_memory().percent
ppid_map = _psutil._ppid_map # pylint: disable=protected-access
ppid_map: _Callable[[], dict[int, int]] = _psutil._ppid_map # pylint: disable=protected-access
"""Obtain a ``{pid: ppid, ...}`` dict for all running processes in one shot."""
def reverse_ppid_map(): # pylint: disable=function-redefined
def reverse_ppid_map() -> dict[int, list[int]]: # pylint: disable=function-redefined
"""Obtain a ``{ppid: [pid, ...], ...}`` dict for all running processes in one shot."""
from collections import defaultdict # pylint: disable=import-outside-toplevel
@ -91,6 +96,3 @@ else:
WSL = None
WINDOWS_SUBSYSTEM_FOR_LINUX = WSL
"""The Linux distribution name of the Windows Subsystem for Linux."""
del _os, _ttl_cache

View file

@ -244,7 +244,7 @@ class CUDAError(Exception):
try:
if self.value not in CUDAError._errcode_to_string:
CUDAError._errcode_to_string[self.value] = '{}.'.format(
cuGetErrorString(self.value).rstrip('.').capitalize()
cuGetErrorString(self.value).rstrip('.').capitalize(),
)
if self.value not in CUDAError._errcode_to_name:
CUDAError._errcode_to_name[self.value] = cuGetErrorName(self.value)
@ -299,8 +299,7 @@ def _extract_cuda_errors_as_classes() -> None:
def gen_new(value):
def new(cls):
obj = CUDAError.__new__(cls, value)
return obj
return CUDAError.__new__(cls, value)
return new
@ -314,9 +313,7 @@ def _extract_cuda_errors_as_classes() -> None:
err_val,
)
else:
new_error_class.__doc__ = 'CUDA Error with code :data:`{}` ({})'.format(
err_name, err_val
)
new_error_class.__doc__ = f'CUDA Error with code :data:`{err_name}` ({err_val})'
setattr(this_module, class_name, new_error_class)
CUDAError._value_class_mapping[err_val] = new_error_class
CUDAError._errcode_to_name[err_val] = err_name
@ -395,7 +392,7 @@ def __LoadCudaLibrary() -> None:
]
# Also add libraries with version suffix `.1`
lib_filenames = list(
_itertools.chain.from_iterable((f'{lib}.1', lib) for lib in lib_filenames)
_itertools.chain.from_iterable((f'{lib}.1', lib) for lib in lib_filenames),
)
elif system == 'Windows':
bits = _platform.architecture()[0].replace('bit', '') # e.g., '64' or '32'

View file

@ -295,7 +295,7 @@ class cudaError(Exception):
try:
if self.value not in cudaError._errcode_to_string:
cudaError._errcode_to_string[self.value] = '{}.'.format(
cuGetErrorString(self.value).rstrip('.').capitalize()
cuGetErrorString(self.value).rstrip('.').capitalize(),
)
if self.value not in cudaError._errcode_to_name:
cudaError._errcode_to_name[self.value] = cudaGetErrorName(self.value)
@ -353,8 +353,7 @@ def _extract_cuda_errors_as_classes() -> None:
def gen_new(value):
def new(cls):
obj = cudaError.__new__(cls, value)
return obj
return cudaError.__new__(cls, value)
return new
@ -368,9 +367,7 @@ def _extract_cuda_errors_as_classes() -> None:
err_val,
)
else:
new_error_class.__doc__ = 'CUDA Error with code :data:`{}` ({})'.format(
err_name, err_val
)
new_error_class.__doc__ = f'CUDA Error with code :data:`{err_name}` ({err_val})'
setattr(this_module, class_name, new_error_class)
cudaError._value_class_mapping[err_val] = new_error_class
cudaError._errcode_to_name[err_val] = err_name
@ -464,7 +461,7 @@ def __LoadCudaLibrary() -> None: # pylint: disable=too-many-branches
[
_os.path.join(cuda_path, f'lib{bits}', lib_filename),
_os.path.join(cuda_path, 'lib', lib_filename),
]
],
)
else:
candidate_dirs = _os.getenv('PATH', '').split(_os.path.pathsep)
@ -476,18 +473,18 @@ def __LoadCudaLibrary() -> None: # pylint: disable=too-many-branches
_os.path.join(cuda_path, 'bin'),
_os.path.join(cuda_path, f'lib{bits}'),
_os.path.join(cuda_path, 'lib'),
]
],
)
for candidate_dir in candidate_dirs:
candidate_paths.extend(
_glob.iglob(_os.path.join(candidate_dir, f'cudart{bits}*.dll'))
_glob.iglob(_os.path.join(candidate_dir, f'cudart{bits}*.dll')),
)
# Normalize paths and remove duplicates
candidate_paths = list(
dict.fromkeys(
_os.path.normpath(_os.path.normcase(p)) for p in candidate_paths
)
),
)
for lib_filename in candidate_paths:
try:

View file

@ -56,7 +56,7 @@ __all__ = [ # will be updated in below
if not callable(getattr(_pynvml, 'nvmlInitWithFlags', None)):
raise ImportError(
'Your installed package `nvidia-ml-py` is corrupted. Please reinstall package '
'`nvidia-ml-py` via `pip3 install --force-reinstall nvidia-ml-py nvitop`.'
'`nvidia-ml-py` via `pip3 install --force-reinstall nvidia-ml-py nvitop`.',
)
@ -79,7 +79,7 @@ _errcode_to_string = NVMLError._errcode_to_string # pylint: disable=protected-a
for _name, _attr in _vars_pynvml.items():
if _name in ('nvmlInit', 'nvmlInitWithFlags', 'nvmlShutdown'):
continue
if _name.startswith('NVML_ERROR_') or _name.startswith('NVMLError_'):
if _name.startswith(('NVML_ERROR_', 'NVMLError_')):
__all__.append(_name)
if _name.startswith('NVML_ERROR_'):
_errcode_to_name[_attr] = _name
@ -101,7 +101,9 @@ _errcode = _reason = _subclass = None
for _errcode, _reason in _errcode_to_string.items():
_subclass = nvmlExceptionClass(_errcode)
_subclass.__doc__ = '{}. Code: :data:`{}` ({})'.format(
_reason.rstrip('.'), _errcode_to_name[_errcode], _errcode
_reason.rstrip('.'),
_errcode_to_name[_errcode],
_errcode,
)
# 4. Add undocumented constants into module docstring
@ -184,7 +186,7 @@ except (ValueError, TypeError):
pass
if not LOGGER.hasHandlers() and LOGGER.isEnabledFor(_logging.DEBUG):
_formatter = _logging.Formatter(
'[%(levelname)s] %(asctime)s %(name)s::%(funcName)s: %(message)s'
'[%(levelname)s] %(asctime)s %(name)s::%(funcName)s: %(message)s',
)
_stream_handler = _logging.StreamHandler()
_stream_handler.setFormatter(_formatter)
@ -332,11 +334,11 @@ def nvmlShutdown() -> None: # pylint: disable=function-redefined
def nvmlQuery(
func: _Callable[..., _Any] | str,
*args,
*args: _Any,
default: _Any = NA,
ignore_errors: bool = True,
ignore_function_not_found: bool = False,
**kwargs,
**kwargs: _Any,
) -> _Any:
"""Call a function with the given arguments from NVML.
@ -416,7 +418,10 @@ def nvmlQuery(
return retval
def nvmlCheckReturn(retval: _Any, types: type | tuple[type, ...] | None = None) -> bool:
def nvmlCheckReturn(
retval: _Any,
types: type | tuple[type, ...] | None = None,
) -> bool:
"""Check whether the return value is not :const:`nvitop.NA` and is one of the given types."""
if types is None:
return retval != NA
@ -433,7 +438,7 @@ def __patch_backward_compatibility_layers() -> None:
if __patched_backward_compatibility_layers:
return
function_name_mapping_lock = _threading.Lock() # noqa: F405
function_name_mapping_lock = _threading.Lock()
function_name_mapping = {}
def function_mapping_update(mapping):
@ -456,8 +461,8 @@ def __patch_backward_compatibility_layers() -> None:
_pynvml.__dict__.update( # need to use module.__dict__.__setitem__ because module.__setattr__ will not work
_nvmlGetFunctionPointer=wrapper(
_pynvml._nvmlGetFunctionPointer # pylint: disable=protected-access,no-member
)
_pynvml._nvmlGetFunctionPointer, # pylint: disable=protected-access,no-member
),
)
def patch_function_pointers_when_fail(names, callback):
@ -514,8 +519,12 @@ def __patch_backward_compatibility_layers() -> None:
}
def patch_process_info_callback(
name, names, exception, pynvml, modself
): # pylint: disable=unused-argument
name,
names, # pylint: disable=unused-argument
exception,
pynvml,
modself,
):
if name in nvmlDeviceGetRunningProcesses_v3_v2:
mapping = nvmlDeviceGetRunningProcesses_v3_v2
struct_type = c_nvmlProcessInfo_v2_t
@ -533,7 +542,8 @@ def __patch_backward_compatibility_layers() -> None:
for old_name, mapped_name in mapping.items():
LOGGER.debug(' Map NVML function `%s` to `%s`', old_name, mapped_name)
LOGGER.debug(
' Patch NVML struct `c_nvmlProcessInfo_t` to `%s`', struct_type.__name__
' Patch NVML struct `c_nvmlProcessInfo_t` to `%s`',
struct_type.__name__,
)
return mapping[name]
@ -547,9 +557,9 @@ def __patch_backward_compatibility_layers() -> None:
names=set(nvmlDeviceGetRunningProcesses_v2_v1),
callback=patch_process_info_callback,
)(
_pynvml._nvmlGetFunctionPointer # pylint: disable=protected-access,no-member
)
)
_pynvml._nvmlGetFunctionPointer, # pylint: disable=protected-access,no-member
),
),
)
with_mapped_function_name() # patch first and only for once
@ -570,7 +580,8 @@ _pynvml_get_memory_info_v2_available = _pynvml_memory_v2_available
_driver_get_memory_info_v2_available = None if not _pynvml_installation_corrupted else False
def nvmlDeviceGetMemoryInfo(handle): # pylint: disable=function-redefined,too-many-branches
# pylint: disable-next=function-redefined,too-many-branches
def nvmlDeviceGetMemoryInfo(handle: c_nvmlDevice_t) -> _pynvml.c_nvmlMemory_t:
"""Retrieve the amount of used, free, reserved and total memory available on the device, in bytes.
Note:
@ -620,7 +631,8 @@ def nvmlDeviceGetMemoryInfo(handle): # pylint: disable=function-redefined,too-m
with __lock:
_pynvml_get_memory_info_v2_available = False
LOGGER.debug(
'NVML memory info version 2 is not available due to incompatible `nvidia-ml-py` package.'
'NVML memory info version 2 is not available '
'due to incompatible `nvidia-ml-py` package.',
)
else:
# driver ✔ pynvml ? user ✘
@ -634,7 +646,8 @@ def nvmlDeviceGetMemoryInfo(handle): # pylint: disable=function-redefined,too-m
with __lock:
_pynvml_get_memory_info_v2_available = False
LOGGER.debug(
'NVML memory info version 2 is not available due to incompatible NVIDIA driver.'
'NVML memory info version 2 is not available '
'due to incompatible NVIDIA driver.',
)
else:
# driver ✔ pynvml ✔
@ -647,19 +660,19 @@ def nvmlDeviceGetMemoryInfo(handle): # pylint: disable=function-redefined,too-m
'your NVIDIA driver does support the NVML memory info version 2 APIs. NVML '
'memory info version 2 is not available due to the legacy dependencies. '
'Please consider upgrading your `nvidia-ml-py` package by running '
'`pip3 install --upgrade nvitop nvidia-ml-py`.'
'`pip3 install --upgrade nvitop nvidia-ml-py`.',
)
elif _pynvml_memory_v2_available:
# driver ✘ pynvml ?
LOGGER.debug(
'NVML memory info version 2 is not available due to incompatible NVIDIA driver.'
'NVML memory info version 2 is not available due to incompatible NVIDIA driver.',
)
else:
# driver ✘ pynvml ✘
LOGGER.debug(
'NVML constant `nvmlMemory_v2` not found in package `nvidia-ml-py`, and '
'your NVIDIA driver does not support the NVML memory info version 2 APIs. '
'NVML memory info version 2 is not available.'
'NVML memory info version 2 is not available.',
)
elif _pynvml_get_memory_info_v2_available:
@ -697,12 +710,8 @@ class _CustomModule(_ModuleType):
_lazy_init()
return self
def __exit__(self, *args, **kwargs) -> None:
def __exit__(self, *args: _Any, **kwargs: _Any) -> None:
"""Shutdown the NVML context in the context manager for ``with`` statement."""
self.__del__()
def __del__(self) -> None:
"""Automatically shutdown the NVML context on destruction."""
try:
nvmlShutdown()
except NVMLError:

View file

@ -62,7 +62,7 @@ if host.POSIX:
if "'" not in s and '\n' not in s:
return f"'{s}'"
return '"{}"'.format(
s.replace('\\', r'\\').replace('"', r'\"').replace('$', r'\$').replace('\n', r'\n')
s.replace('\\', r'\\').replace('"', r'\"').replace('$', r'\$').replace('\n', r'\n'),
)
elif host.WINDOWS:
@ -77,7 +77,7 @@ elif host.WINDOWS:
if '"' not in s:
return f'"{s}"'
return '"{}"'.format(
s.replace('^', '^^').replace('"', '^"').replace('%', '^%').replace('\n', r'\n')
s.replace('^', '^^').replace('"', '^"').replace('%', '^%').replace('\n', r'\n'),
)
else:
@ -113,7 +113,7 @@ def auto_garbage_clean(
def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
@functools.wraps(func)
def wrapped(self: GpuProcess, *args, **kwargs):
def wrapped(self: GpuProcess, *args: Any, **kwargs: Any) -> Any:
try:
return func(self, *args, **kwargs)
except host.PsutilError as ex:
@ -127,9 +127,8 @@ def auto_garbage_clean(
del HostProcess.INSTANCES[self.pid]
except KeyError:
pass
if fallback is _RAISE or not getattr(
_USE_FALLBACK_WHEN_RAISE, 'value', False # see also `GpuProcess.failsafe`
):
# See also `GpuProcess.failsafe`
if fallback is _RAISE or not getattr(_USE_FALLBACK_WHEN_RAISE, 'value', False):
raise ex
if isinstance(fallback, tuple):
if isinstance(ex, host.AccessDenied) and fallback == ('No Such Process',):
@ -381,7 +380,7 @@ class HostProcess(host.Process, metaclass=ABCMeta):
return [HostProcess(child.pid) for child in super().children(recursive)]
@contextlib.contextmanager
def oneshot(self):
def oneshot(self) -> contextlib.AbstractContextManager:
"""A utility context manager which considerably speeds up the retrieval of multiple process information at the same time.
Internally different process info (e.g. name, ppid, uids, gids, ...) may be fetched by using
@ -416,7 +415,9 @@ class HostProcess(host.Process, metaclass=ABCMeta):
self.running_time.cache_deactivate(self)
def as_snapshot(
self, attrs: Iterable[str] | None = None, ad_value: Any | None = None
self,
attrs: Iterable[str] | None = None,
ad_value: Any | None = None,
) -> Snapshot:
"""Return a onetime snapshot of the process."""
with self.oneshot():
@ -912,7 +913,7 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi
def host_snapshot(self) -> Snapshot:
"""Return a onetime snapshot of the host process."""
with self.host.oneshot():
host_snapshot = Snapshot(
return Snapshot(
real=self.host,
is_running=self.is_running(),
status=self.status(),
@ -929,11 +930,11 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi
running_time_in_seconds=self.running_time_in_seconds(),
)
return host_snapshot
@auto_garbage_clean(fallback=_RAISE)
def as_snapshot(
self, *, host_process_snapshot_cache: dict[int, Snapshot] | None = None
self,
*,
host_process_snapshot_cache: dict[int, Snapshot] | None = None,
) -> Snapshot:
"""Return a onetime snapshot of the process on the GPU device.
@ -983,7 +984,10 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi
@classmethod
def take_snapshots( # batched version of `as_snapshot`
cls, gpu_processes: Iterable[GpuProcess], *, failsafe: bool = False
cls,
gpu_processes: Iterable[GpuProcess],
*,
failsafe: bool = False,
) -> list[Snapshot]:
"""Take snapshots for a list of :class:`GpuProcess` instances.
@ -993,15 +997,13 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi
cache = {}
context = cls.failsafe if failsafe else contextlib.nullcontext
with context():
snapshots = [
return [
process.as_snapshot(host_process_snapshot_cache=cache) for process in gpu_processes
]
return snapshots
@classmethod
@contextlib.contextmanager
def failsafe(cls):
def failsafe(cls) -> contextlib.AbstractContextManager:
"""A context manager that enables fallback values for methods that fail.
Examples:

View file

@ -491,7 +491,8 @@ SIZE_UNITS = {
}
"""Units of storage and memory measurements."""
SIZE_PATTERN = re.compile(
r'^\s*\+?\s*(?P<size>\d+(?:\.\d+)?)\s*(?P<unit>[KMGTP]i?B?|B?)\s*$', flags=re.IGNORECASE
r'^\s*\+?\s*(?P<size>\d+(?:\.\d+)?)\s*(?P<unit>[KMGTP]i?B?|B?)\s*$',
flags=re.IGNORECASE,
)
"""The regex pattern for human readable size."""
@ -607,7 +608,7 @@ class Snapshot:
Missing attributes will be automatically fetched from the original object.
"""
def __init__(self, real: Any, **items) -> None:
def __init__(self, real: Any, **items: Any) -> None:
"""Initialize a new :class:`Snapshot` object with the given attributes."""
self.real = real
self.timestamp = time.time()
@ -626,7 +627,9 @@ class Snapshot:
keyval = keyval.replace('\n', '\n ') # extra indentation for nested snapshots
keyvals.append(keyval)
return '{}{}(\n {},\n)'.format(
self.real.__class__.__name__, self.__class__.__name__, ',\n '.join(keyvals)
self.real.__class__.__name__,
self.__class__.__name__,
',\n '.join(keyvals),
)
__repr__ = __str__
@ -686,7 +689,7 @@ def memoize_when_activated(method: Callable[[Any], Any]) -> Callable[[Any], Any]
"""
@functools.wraps(method)
def wrapped(self):
def wrapped(self): # noqa: ANN001,ANN202
try:
# case 1: we previously entered oneshot() ctx
ret = self._cache[method] # pylint: disable=protected-access
@ -705,7 +708,7 @@ def memoize_when_activated(method: Callable[[Any], Any]) -> Callable[[Any], Any]
pass
return ret
def cache_activate(self):
def cache_activate(self): # noqa: ANN001,ANN202
"""Activate cache.
Expects an instance. Cache will be stored as a "_cache" instance attribute.
@ -713,7 +716,7 @@ def memoize_when_activated(method: Callable[[Any], Any]) -> Callable[[Any], Any]
if not hasattr(self, '_cache'):
self._cache = {} # pylint: disable=protected-access
def cache_deactivate(self):
def cache_deactivate(self): # noqa: ANN001,ANN202
"""Deactivate and clear cache."""
try:
del self._cache # pylint: disable=protected-access

View file

@ -109,7 +109,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
libnvml.nvmlInit()
except libnvml.NVMLError as ex:
raise ValueError(
'Cannot use the GpuStatsLogger callback because the NVIDIA driver is not installed.'
'Cannot use the GpuStatsLogger callback because the NVIDIA driver is not installed.',
) from ex
if isinstance(gpus, (list, tuple)):
@ -126,7 +126,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
except (libnvml.NVMLError, RuntimeError) as ex:
raise ValueError(
f'Cannot use GpuStatsLogger callback because devices unavailable. '
f'Received: `gpus={gpu_ids}`'
f'Received: `gpus={gpu_ids}`',
) from ex
self._memory_utilization = memory_utilization

View file

@ -98,7 +98,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
libnvml.nvmlInit()
except libnvml.NVMLError as ex:
raise MisconfigurationException(
'Cannot use GpuStatsLogger callback because NVIDIA driver is not installed.'
'Cannot use GpuStatsLogger callback because NVIDIA driver is not installed.',
) from ex
self._memory_utilization = memory_utilization
@ -111,13 +111,13 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
def on_train_start(self, trainer, pl_module) -> None:
if not trainer.logger:
raise MisconfigurationException(
'Cannot use GpuStatsLogger callback with Trainer that has no logger.'
'Cannot use GpuStatsLogger callback with Trainer that has no logger.',
)
if trainer.strategy.root_device.type != 'cuda':
raise MisconfigurationException(
f'You are using GpuStatsLogger but are not running on GPU. '
f'The root device type is {trainer.strategy.root_device.type}.'
f'The root device type is {trainer.strategy.root_device.type}.',
)
device_ids = trainer.data_parallel_device_ids
@ -126,7 +126,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
except (libnvml.NVMLError, RuntimeError) as ex:
raise ValueError(
f'Cannot use GpuStatsLogger callback because devices unavailable. '
f'Received: `gpus={device_ids}`'
f'Received: `gpus={device_ids}`',
) from ex
def on_train_epoch_start(self, trainer, pl_module) -> None:

View file

@ -15,7 +15,10 @@ from nvitop.version import __version__
TTY = sys.stdin.isatty() and sys.stdout.isatty()
NVITOP_MONITOR_MODE = set(
map(str.strip, os.environ.get('NVITOP_MONITOR_MODE', '').lower().split(','))
map(
str.strip,
os.environ.get('NVITOP_MONITOR_MODE', '').lower().split(','),
),
)
@ -23,7 +26,9 @@ NVITOP_MONITOR_MODE = set(
def parse_arguments() -> argparse.Namespace:
"""Parse command-line arguments for ``nvtiop``."""
coloring_rules = '{} < th1 %% <= {} < th2 %% <= {}'.format(
colored('light', 'green'), colored('moderate', 'yellow'), colored('heavy', 'red')
colored('light', 'green'),
colored('moderate', 'yellow'),
colored('heavy', 'red'),
)
def posint(argstring: str) -> int:
@ -59,7 +64,11 @@ def parse_arguments() -> argparse.Namespace:
mode = parser.add_mutually_exclusive_group()
mode.add_argument(
'--once', '-1', dest='once', action='store_true', help='Report query data only once.'
'--once',
'-1',
dest='once',
action='store_true',
help='Report query data only once.',
)
mode.add_argument(
'--monitor',
@ -354,7 +363,8 @@ def main() -> None:
grandparent = parent.parent() if parent is not None else None
if grandparent is not None and parent.name() == 'sh' and grandparent.name() == 'watch':
messages.append(
'HINT: You are running `nvitop` under `watch` command. Please try `nvitop -m` directly.'
'HINT: You are running `nvitop` under `watch` command. '
'Please try `nvitop -m` directly.',
)
ui.print()
@ -364,7 +374,7 @@ def main() -> None:
unknown_function_messages = [
'ERROR: Some FunctionNotFound errors occurred while calling:'
if len(libnvml.UNKNOWN_FUNCTIONS) > 1
else 'ERROR: A FunctionNotFound error occurred while calling:'
else 'ERROR: A FunctionNotFound error occurred while calling:',
]
unknown_function_messages.extend(
f' nvmlQuery({func.__name__!r}, *args, **kwargs)'
@ -376,8 +386,8 @@ def main() -> None:
'You can check the release history of `nvidia-ml-py` and install the compatible version manually.\n'
'See {} for more information.'
).format(
colored('https://github.com/XuehaiPan/nvitop#installation', attrs=('underline',))
)
colored('https://github.com/XuehaiPan/nvitop#installation', attrs=('underline',)),
),
)
message = '\n'.join(unknown_function_messages)
if (
@ -393,7 +403,7 @@ def main() -> None:
'',
' pip3 install "nvitop[cuda10]"',
'',
)
),
).replace('@VERSION@', Device.driver_version())
messages.append(message)
@ -409,7 +419,7 @@ def main() -> None:
' pip3 install --upgrade pipx',
' pipx install nvitop',
'',
)
),
)
messages.append(message)
@ -422,7 +432,7 @@ def main() -> None:
'',
' pip3 install --upgrade nvitop nvidia-ml-py',
'',
)
),
)
messages.append(message)
@ -430,15 +440,21 @@ def main() -> None:
for message in messages:
if message.startswith('ERROR:'):
message = message.replace(
'ERROR:', colored('ERROR:', color='red', attrs=('bold',)), 1
'ERROR:',
colored('ERROR:', color='red', attrs=('bold',)),
1,
)
elif message.startswith('WARNING:'):
message = message.replace(
'WARNING:', colored('WARNING:', color='yellow', attrs=('bold',)), 1
'WARNING:',
colored('WARNING:', color='yellow', attrs=('bold',)),
1,
)
elif message.startswith('HINT:'):
message = message.replace(
'HINT:', colored('HINT:', color='green', attrs=('bold',)), 1
'HINT:',
colored('HINT:', color='green', attrs=('bold',)),
1,
)
print(message, file=sys.stderr)
return 1

View file

@ -55,7 +55,7 @@ class Displayable(CursesShortcuts): # pylint: disable=too-many-instance-attribu
self.parent = None
def __contains__(self, item):
"""Checks if item is inside the boundaries.
"""Check if item is inside the boundaries.
item can be an iterable like [y, x] or an object with x and y methods.
"""
@ -77,8 +77,7 @@ class Displayable(CursesShortcuts): # pylint: disable=too-many-instance-attribu
return (self.x <= x < self.x + self.width) and (self.y <= y < self.y + self.height)
def poke(self):
"""Called before drawing, even if invisible"""
"""Called before drawing, even if invisible."""
if self._old_visible != self.visible:
self._old_visible = self.visible
self.need_redraw = True
@ -92,7 +91,6 @@ class Displayable(CursesShortcuts): # pylint: disable=too-many-instance-attribu
Called on every main iteration if visible. Containers should call draw()
on their contained objects here. Override this!
"""
self.need_redraw = False
def finalize(self):
@ -100,12 +98,10 @@ class Displayable(CursesShortcuts): # pylint: disable=too-many-instance-attribu
Override this!
"""
self.need_redraw = False
def destroy(self):
"""Called when the object is destroyed."""
self.win = None
self.root = None
@ -184,15 +180,13 @@ class DisplayableContainer(Displayable):
# extended or overridden methods
def poke(self):
"""Recursively called on objects in container"""
"""Recursively called on objects in container."""
super().poke()
for displayable in self.container:
displayable.poke()
def draw(self):
"""Recursively called on visible objects in container"""
"""Recursively called on visible objects in container."""
for displayable in self.container:
if self.need_redraw:
displayable.need_redraw = True
@ -202,22 +196,19 @@ class DisplayableContainer(Displayable):
self.need_redraw = False
def finalize(self):
"""Recursively called on visible objects in container"""
"""Recursively called on visible objects in container."""
for displayable in self.container:
if displayable.visible:
displayable.finalize()
def destroy(self):
"""Recursively called on objects in container"""
"""Recursively called on objects in container."""
for displayable in self.container:
displayable.destroy()
super().destroy()
def press(self, key):
"""Recursively called on objects in container"""
"""Recursively called on objects in container."""
focused_obj = self.get_focused_obj()
if focused_obj:
@ -226,8 +217,7 @@ class DisplayableContainer(Displayable):
return False
def click(self, event):
"""Recursively called on objects in container"""
"""Recursively called on objects in container."""
focused_obj = self.get_focused_obj()
if focused_obj and focused_obj.click(event):
return True
@ -241,7 +231,6 @@ class DisplayableContainer(Displayable):
def add_child(self, obj):
"""Add the objects to the container."""
if obj.parent is not None:
obj.parent.remove_child(obj)
self.container.append(obj)
@ -250,14 +239,12 @@ class DisplayableContainer(Displayable):
def replace_child(self, old_obj, new_obj):
"""Replace the old object with the new instance in the container."""
self.container[self.container.index(old_obj)] = new_obj
new_obj.parent = self
new_obj.root = self.root
def remove_child(self, obj):
"""Remove the object from the container."""
try:
self.container.remove(obj)
except ValueError:

View file

@ -41,7 +41,7 @@ PAIR2SYMBOL_DOWN = {
for s1, s2 in itertools.product(SYMBOL2VALUE_DOWN, repeat=2)
}
GRAPH_SYMBOLS = ''.join(
sorted(set(itertools.chain(VALUE2SYMBOL_UP.values(), VALUE2SYMBOL_DOWN.values())))
sorted(set(itertools.chain(VALUE2SYMBOL_UP.values(), VALUE2SYMBOL_DOWN.values()))),
).replace(' ', '')
@ -92,7 +92,8 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
self.maxlen = 2 * self.width + 1
self.history = deque(
[self.baseline - 0.1] * (2 * self.MAX_WIDTH + 1), maxlen=(2 * self.MAX_WIDTH + 1)
[self.baseline - 0.1] * (2 * self.MAX_WIDTH + 1),
maxlen=(2 * self.MAX_WIDTH + 1),
)
self.reversed_history = deque([self.baseline - 0.1] * self.maxlen, maxlen=self.maxlen)
self._max_value_maintainer = deque([self.baseline - 0.1] * self.maxlen, maxlen=self.maxlen)
@ -118,18 +119,23 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
@width.setter
def width(self, value):
if self._width != value:
assert isinstance(value, int) and value >= 1
assert isinstance(value, int)
assert value >= 1
self._width = value
with self.write_lock:
self.maxlen = 2 * self.width + 1
self.reversed_history = deque(
(self.baseline - 0.1,) * self.maxlen, maxlen=self.maxlen
(self.baseline - 0.1,) * self.maxlen,
maxlen=self.maxlen,
)
self._max_value_maintainer = deque(
(self.baseline - 0.1,) * self.maxlen, maxlen=self.maxlen
(self.baseline - 0.1,) * self.maxlen,
maxlen=self.maxlen,
)
for history in itertools.islice(
self.history, max(0, self.history.maxlen - self.maxlen), self.history.maxlen
self.history,
max(0, self.history.maxlen - self.maxlen),
self.history.maxlen,
):
if self.reversed_history[-1] == self._max_value_maintainer[0]:
self._max_value_maintainer.popleft()
@ -149,7 +155,8 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
@height.setter
def height(self, value):
if self._height != value:
assert isinstance(value, int) and value >= 1
assert isinstance(value, int)
assert value >= 1
self._height = value
self.remake_graph()
@ -160,8 +167,10 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
@graph_size.setter
def graph_size(self, value):
width, height = value
assert isinstance(width, int) and width >= 1
assert isinstance(height, int) and height >= 1
assert isinstance(width, int)
assert width >= 1
assert isinstance(height, int)
assert height >= 1
self._height = height
self._width = width - 1 # trigger force remake
self.width = width

View file

@ -41,7 +41,7 @@ SPECIAL_KEYS = OrderedDict(
('S-Tab', curses.KEY_BTAB),
('lt', ord('<')),
('gt', ord('>')),
]
],
)
NAMED_SPECIAL_KEYS = tuple(SPECIAL_KEYS.keys())
@ -55,7 +55,7 @@ VERY_SPECIAL_KEYS = {
def _uncase_special_key(string):
"""Uncase a special key
"""Uncase a special key.
>>> _uncase_special_key('Esc')
'esc'
@ -71,7 +71,7 @@ def _uncase_special_key(string):
'a-x'
"""
uncased = string.lower()
if len(uncased) == 3 and (uncased.startswith('a-') or uncased.startswith('m-')):
if len(uncased) == 3 and (uncased.startswith(('a-', 'm-'))):
uncased = f'{uncased[0]}-{string[-1]}'
return uncased
@ -176,7 +176,7 @@ def key_to_string(key):
def construct_keybinding(keys):
"""Does the reverse of parse_keybinding
"""Do the reverse of parse_keybinding.
>>> construct_keybinding(parse_keybinding('lol<CR>'))
'lol<Enter>'
@ -211,7 +211,7 @@ def construct_keybinding(keys):
def normalize_keybinding(keybinding):
"""Normalize a keybinding to a string
"""Normalize a keybinding to a string.
>>> normalize_keybinding('lol<CR>')
'lol<Enter>'
@ -275,7 +275,7 @@ class KeyMaps(dict):
pointer = pointer[key]
except KeyError as ex:
raise KeyError(
f'Tried to copy the keybinding `{source}`, but it was not found.'
f'Tried to copy the keybinding `{source}`, but it was not found.',
) from ex
try:
self.bind(context, target, copy.deepcopy(pointer))

View file

@ -36,7 +36,7 @@ TRUE_COLORS = dict(
('bright cyan', 14),
('bright white', 15),
]
+ [(f'preserved {i:02d}', i) for i in range(16, 64)]
+ [(f'preserved {i:02d}', i) for i in range(16, 64)],
)
@ -44,8 +44,7 @@ BASE_ATTR = 0
def _init_color_theme(light_theme=False):
"""Sets the default fg/bg colors."""
"""Set the default fg/bg colors."""
global LIGHT_THEME, DEFAULT_FOREGROUND, DEFAULT_BACKGROUND # pylint: disable=global-statement
LIGHT_THEME = light_theme
@ -76,8 +75,7 @@ def _get_true_color(rgb):
def _get_color(fg, bg):
"""Returns the curses color pair for the given fg/bg combination."""
"""Return the curses color pair for the given fg/bg combination."""
global COLOR_PAIRS # pylint: disable=global-statement,global-variable-not-assigned
if isinstance(fg, str):
@ -185,8 +183,8 @@ class CursesShortcuts:
"""
ASCII_TRANSTABLE = str.maketrans(
'' + '─╴' + '╒╤╕╪╘╧╛┌┬┐┼└┴┘' + '│╞╡├┤▏▎▍▌▋▊▉█░' + '▲▼' + '' + GRAPH_SYMBOLS,
'=' + '--' + '++++++++++++++' + '||||||||||||||' + '^v' + '?' + '=' * len(GRAPH_SYMBOLS),
'─╴╒╤╕╪╘╧╛┌┬┐┼└┴┘│╞╡├┤▏▎▍▌▋▊▉█░▲▼' + GRAPH_SYMBOLS,
'=--++++++++++++++||||||||||||||^v?' + '=' * len(GRAPH_SYMBOLS),
)
TERM_256COLOR = False
@ -232,17 +230,14 @@ class CursesShortcuts:
def color(self, fg=-1, bg=-1, attr=0):
"""Change the colors from now on."""
return self.set_fg_bg_attr(fg, bg, attr)
def color_reset(self):
"""Change the colors to the default colors"""
"""Change the colors to the default colors."""
return self.color()
def color_at(self, y, x, width, *args, **kwargs):
"""Change the colors at the specified position"""
"""Change the colors at the specified position."""
try:
self.win.chgat(y, x, width, self.get_fg_bg_attr(*args, **kwargs))
except curses.error:
@ -250,8 +245,7 @@ class CursesShortcuts:
@staticmethod
def get_fg_bg_attr(fg=-1, bg=-1, attr=0):
"""Returns the curses attribute for the given fg/bg/attr combination."""
"""Return the curses attribute for the given fg/bg/attr combination."""
if fg == -1 and bg == -1 and attr == 0:
return BASE_ATTR
@ -283,10 +277,11 @@ class CursesShortcuts:
return attr
def update_size(self, termsize=None):
if termsize is None:
self.update_lines_cols()
termsize = self.win.getmaxyx()
return termsize
if termsize is not None:
return termsize
self.update_lines_cols()
return self.win.getmaxyx()
@staticmethod
def update_lines_cols():

View file

@ -42,7 +42,8 @@ class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes
self.options = options
self.num_options = len(self.options)
assert cancel is not None and self.num_options >= 2
assert cancel is not None
assert self.num_options >= 2
assert 0 <= no < self.num_options
assert 0 <= cancel < self.num_options
assert 0 <= default < self.num_options

View file

@ -39,7 +39,7 @@ class MouseEvent:
CTRL_SCROLLWHEEL_MULTIPLIER = 5
def __init__(self, state):
"""Creates a MouseEvent object from the result of win.getmouse()"""
"""Create a MouseEvent object from the result of win.getmouse()."""
_, self.x, self.y, _, self.bstate = state
# x-values above ~220 suddenly became negative, apparently
@ -51,35 +51,35 @@ class MouseEvent:
self.y += 0xFF
def pressed(self, n):
"""Returns whether the mouse key n is pressed"""
"""Return whether the mouse key n is pressed."""
try:
return (self.bstate & MouseEvent.PRESSED[n]) != 0
except IndexError:
return False
def released(self, n):
"""Returns whether the mouse key n is released"""
"""Return whether the mouse key n is released."""
try:
return (self.bstate & MouseEvent.RELEASED[n]) != 0
except IndexError:
return False
def clicked(self, n):
"""Returns whether the mouse key n is clicked"""
"""Return whether the mouse key n is clicked."""
try:
return (self.bstate & MouseEvent.CLICKED[n]) != 0
except IndexError:
return False
def double_clicked(self, n):
"""Returns whether the mouse key n is double clicked"""
"""Return whether the mouse key n is double clicked."""
try:
return (self.bstate & MouseEvent.DOUBLE_CLICKED[n]) != 0
except IndexError:
return False
def wheel_direction(self):
"""Returns the direction of the scroll action, 0 if there was none"""
"""Return the direction of the scroll action, 0 if there was none."""
# If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button.
# I interpret invalid buttons as "scroll down" because all tested
# systems have a broken curses implementation and this is a workaround.

View file

@ -124,7 +124,7 @@ class Selection: # pylint: disable=too-many-instance-attributes
self.send_signal(
signal.SIGINT
if not host.WINDOWS
else signal.CTRL_C_EVENT # pylint: disable=no-member
else signal.CTRL_C_EVENT, # pylint: disable=no-member
)
except SystemError:
pass

View file

@ -14,16 +14,14 @@ WIDE_SYMBOLS = set('WF')
def utf_char_width(string):
"""Return the width of a single character"""
"""Return the width of a single character."""
if east_asian_width(string) in WIDE_SYMBOLS:
return WIDE
return NARROW
def string_to_charlist(string):
"""Return a list of characters with extra empty strings after wide chars"""
"""Return a list of characters with extra empty strings after wide chars."""
if ASCIIONLY.issuperset(string):
return list(string)
result = []
@ -35,8 +33,7 @@ def string_to_charlist(string):
def wcslen(string):
"""Return the length of a string with wide chars"""
"""Return the length of a string with wide chars."""
return len(WideString(string))
@ -63,7 +60,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> (WideString('afd') + 'bc').chars
['a', 'f', 'd', 'b', 'c']
"""
if isinstance(other, str):
return WideString(self.string + other)
if isinstance(other, WideString):
@ -75,7 +71,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> ('bc' + WideString('afd')).chars
['b', 'c', 'a', 'f', 'd']
"""
if isinstance(other, str):
return WideString(other + self.string)
if isinstance(other, WideString):
@ -131,7 +126,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString('aモ')[0:1]
<WideString 'a'>
"""
if isinstance(item, slice):
assert item.step is None or item.step == 1
start, stop = item.start, item.stop
@ -166,7 +160,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> len(WideString('モヒカン'))
8
"""
return len(self.chars)
def ljust(self, width, fillchar=' '):
@ -178,7 +171,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString('モヒカン').ljust(10)
<WideString 'モヒカン '>
"""
if width > len(self):
return WideString(self.string + fillchar * width)[:width]
return self
@ -192,7 +184,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString('モヒカン').rljust(10)
<WideString ' モヒカン'>
"""
if width > len(self):
return WideString(fillchar * width + self.string)[-width:]
return self
@ -206,7 +197,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString('モヒカン').center(10)
<WideString ' モヒカン '>
"""
if width > len(self):
left_width = (width - len(self)) // 2
right_width = width - left_width
@ -220,7 +210,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString(' モヒカン ').strip()
<WideString 'モヒカン'>
"""
return WideString(self.string.strip(chars))
def lstrip(self, chars=None):
@ -230,7 +219,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString(' モヒカン ').lstrip()
<WideString 'モヒカン '>
"""
return WideString(self.string.lstrip(chars))
def rstrip(self, chars=None):
@ -240,5 +228,4 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
>>> WideString(' モヒカン ').rstrip()
<WideString ' モヒカン'>
"""
return WideString(self.string.rstrip(chars))

View file

@ -127,7 +127,8 @@ class EnvironScreen(Displayable): # pylint: disable=too-many-instance-attribute
n_items = len(self.environ)
old_scroll_offset = self.scroll_offset
self.scroll_offset = max(
0, min(self.scroll_offset + direction, n_items - self.display_height)
0,
min(self.scroll_offset + direction, n_items - self.display_height),
)
direction -= self.scroll_offset - old_scroll_offset
self._y_offset += self.scroll_offset - old_scroll_offset
@ -146,12 +147,13 @@ class EnvironScreen(Displayable): # pylint: disable=too-many-instance-attribute
if isinstance(self.process, GpuProcess):
process_type = 'GPU: ' + self.process.type.replace('C', 'Compute').replace(
'G', 'Graphics'
'G',
'Graphics',
)
else:
process_type = 'Host'
header_prefix = WideString(
f'Environment of process {self.process.pid} ({self.username}@{process_type}): '
f'Environment of process {self.process.pid} ({self.username}@{process_type}): ',
)
offset = max(0, min(self.x_offset, len(self.command) + len(header_prefix) - self.width))
header = str((header_prefix + self.command[offset:]).ljust(self.width)[: self.width])

View file

@ -7,7 +7,7 @@ from nvitop.gui.library import Device, Displayable, MouseEvent
from nvitop.version import __version__
HELP_TEMPLATE = '''nvitop {} - (C) Xuehai Pan, 2021-2023.
HELP_TEMPLATE = """nvitop {} - (C) Xuehai Pan, 2021-2023.
Released under the GNU GPLv3 License.
GPU Process Type: C: Compute, G: Graphics, X: Mixed.
@ -38,7 +38,7 @@ Device coloring rules by loading intensity:
, .: select sort column /: invert sort order
Press any key to return.
'''
"""
class HelpScreen(Displayable): # pylint: disable=too-many-instance-attributes

View file

@ -12,7 +12,7 @@ from nvitop.gui.screens.main.host import HostPanel
from nvitop.gui.screens.main.process import ProcessPanel
class BreakLoop(Exception):
class BreakLoop(Exception): # noqa: N818
pass
@ -44,7 +44,11 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att
self.add_child(self.host_panel)
self.process_panel = ProcessPanel(
self.device_panel.leaf_devices, compact, filters, win=win, root=root
self.device_panel.leaf_devices,
compact,
filters,
win=win,
root=root,
)
self.process_panel.focused = False
self.add_child(self.process_panel)
@ -265,8 +269,12 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att
keymaps.bind('main', '/', order_reverse)
for order in ProcessPanel.ORDERS:
keymaps.bind(
'main', 'o' + order[:1].lower(), partial(sort_by, order=order, reverse=False)
'main',
'o' + order[:1].lower(),
partial(sort_by, order=order, reverse=False),
)
keymaps.bind(
'main', 'o' + order[:1].upper(), partial(sort_by, order=order, reverse=True)
'main',
'o' + order[:1].upper(),
partial(sort_by, order=order, reverse=True),
)

View file

@ -60,14 +60,16 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
self.snapshot_lock = threading.Lock()
self.snapshots = self.take_snapshots()
self._snapshot_daemon = threading.Thread(
name='device-snapshot-daemon', target=self._snapshot_target, daemon=True
name='device-snapshot-daemon',
target=self._snapshot_target,
daemon=True,
)
self._daemon_running = threading.Event()
self.formats_compact = [
'{physical_index:>3} {fan_speed_string:>3} {temperature_string:>4} '
'{performance_state:>3} {power_status:>12} '
'{memory_usage:>20}{gpu_utilization_string:>7} {compute_mode:>11}'
'{memory_usage:>20}{gpu_utilization_string:>7} {compute_mode:>11}',
]
self.formats_full = [
'{physical_index:>3} {name:<18} {persistence_mode:<4} '
@ -78,12 +80,13 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
self.mig_formats = [
'{physical_index:>2}:{mig_index:<2}{name:>12} @ GI/CI:{gpu_instance_id:>2}/{compute_instance_id:<2}'
'{memory_usage:>20} │ BAR1: {bar1_memory_used_human:>8} / {bar1_memory_percent_string:>3}'
'{memory_usage:>20} │ BAR1: {bar1_memory_used_human:>8} / {bar1_memory_percent_string:>3}',
]
if host.WINDOWS:
self.formats_full[0] = self.formats_full[0].replace(
'persistence_mode', 'current_driver_model'
'persistence_mode',
'current_driver_model',
)
self.support_mig = any('N/A' not in device.mig_mode for device in self.snapshots)
@ -143,10 +146,12 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
device.name = cut_string(device.name, maxlen=18, padstr='..', align='right')
device.current_driver_model = device.current_driver_model.replace('WDM', 'TCC')
device.display_active = device.display_active.replace('Enabled', 'On').replace(
'Disabled', 'Off'
'Disabled',
'Off',
)
device.persistence_mode = device.persistence_mode.replace('Enabled', 'On').replace(
'Disabled', 'Off'
'Disabled',
'Off',
)
device.mig_mode = device.mig_mode.replace('N/A', 'N/A ')
device.compute_mode = device.compute_mode.replace('Exclusive', 'E.')
@ -183,25 +188,25 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
]
if self.device_count > 0:
header.append(
'├───────────────────────────────┬──────────────────────┬──────────────────────┤'
'├───────────────────────────────┬──────────────────────┬──────────────────────┤',
)
if compact:
header.append(
'│ GPU Fan Temp Perf Pwr:Usg/Cap │ Memory-Usage │ GPU-Util Compute M. │'
'│ GPU Fan Temp Perf Pwr:Usg/Cap │ Memory-Usage │ GPU-Util Compute M. │',
)
else:
header.extend(
(
'│ GPU Name Persistence-M│ Bus-Id Disp.A │ Volatile Uncorr. ECC │',
'│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │',
)
),
)
if host.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')
header.append(
'╞═══════════════════════════════╪══════════════════════╪══════════════════════╡'
'╞═══════════════════════════════╪══════════════════════╪══════════════════════╡',
)
else:
header.extend(
@ -209,7 +214,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
'╞═════════════════════════════════════════════════════════════════════════════╡',
'│ No visible devices found │',
'╘═════════════════════════════════════════════════════════════════════════════╛',
)
),
)
return header
@ -241,7 +246,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
frame.pop()
frame.append(
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛'
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛',
)
if self.width >= 100:
frame[5 - int(compact)] = (
@ -273,10 +278,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
self.addstr(self.y, self.x, cut_string(time.strftime('%a %b %d %H:%M:%S %Y'), maxlen=32))
if self.compact:
formats = self.formats_compact
else:
formats = self.formats_full
formats = self.formats_compact if self.compact else self.formats_full
remaining_width = self.width - 79
draw_bars = self.width >= 100
@ -375,7 +377,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
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)
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:
@ -404,7 +406,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
or prev_device_index[0] != device.tuple_index[0]
):
lines.append(
'├───────────────────────────────┼──────────────────────┼──────────────────────┤'
'├───────────────────────────────┼──────────────────────┼──────────────────────┤',
)
def colorize(s):
@ -421,7 +423,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
prev_device_index = device.tuple_index
lines.append(
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛'
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛',
)
if self.width >= 100:
@ -458,7 +460,9 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
matrix.pop()
for y, (prefix, utilization, color) in enumerate(matrix, start=y_start):
bar = make_bar( # pylint: disable=disallowed-name
prefix, utilization, width=remaining_width - 3
prefix,
utilization,
width=remaining_width - 3,
)
lines[y] += f' {colored(bar, color)}'

View file

@ -43,7 +43,9 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
self.memory_percent = None
self.swap_percent = None
self._snapshot_daemon = threading.Thread(
name='host-snapshot-daemon', target=self._snapshot_target, daemon=True
name='host-snapshot-daemon',
target=self._snapshot_target,
daemon=True,
)
self._daemon_running = threading.Event()
@ -262,7 +264,11 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
self.color_at(self.y + 1, self.x, width=width_left, fg='magenta', attr='bold')
self.color_at(self.y, self.x + width_left + 2, width=width_right, attr='bold')
self.color_at(
self.y + 1, self.x + width_left + 2, width=width_right, fg='blue', attr='bold'
self.y + 1,
self.x + width_left + 2,
width=width_right,
fg='blue',
attr='bold',
)
return

View file

@ -142,7 +142,9 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
self._snapshots = []
self.snapshot_lock = threading.Lock()
self._snapshot_daemon = threading.Thread(
name='process-snapshot-daemon', target=self._snapshot_target, daemon=True
name='process-snapshot-daemon',
target=self._snapshot_target,
daemon=True,
)
self._daemon_running = threading.Event()
@ -168,7 +170,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
self._compact = value
processes = self.snapshots
n_processes, n_devices = len(processes), len(
{p.device.physical_index for p in processes}
{p.device.physical_index for p in processes},
)
self.full_height = 1 + max(6, 5 + n_processes + n_devices - 1)
self.compact_height = 1 + max(6, 5 + n_processes)
@ -242,7 +244,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
cls.SNAPSHOT_INTERVAL = min(interval / 3.0, 1.0)
cls.take_snapshots = ttl_cache(ttl=interval)(
cls.take_snapshots.__wrapped__ # pylint: disable=no-member
cls.take_snapshots.__wrapped__, # pylint: disable=no-member
)
def ensure_snapshots(self):
@ -265,7 +267,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
' ' * (time_length - len(snapshot.running_time_human))
+ snapshot.running_time_human,
snapshot.command,
)
),
)
with self.snapshot_lock:
@ -284,7 +286,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
'' + '' * (self.width - 2) + '',
'{}'.format('Processes:'.ljust(self.width - 4)),
'│ GPU PID USER GPU-MEM %SM {}'.format(
' '.join(self.host_headers).ljust(self.width - 40)
' '.join(self.host_headers).ljust(self.width - 40),
),
'' + '' * (self.width - 2) + '',
]
@ -294,14 +296,14 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
else:
message = ' Gathering process status...'
header.extend(
[f'{message.ljust(self.width - 4)}', '' + '' * (self.width - 2) + '']
[f'{message.ljust(self.width - 4)}', '' + '' * (self.width - 2) + ''],
)
return header
@property
def processes(self):
return list(
itertools.chain.from_iterable(device.processes().values() for device in self.devices)
itertools.chain.from_iterable(device.processes().values() for device in self.devices),
)
def poke(self):
@ -313,7 +315,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
self.selection.within_window = False
if len(self.snapshots) > 0 and self.selection.is_set():
y = self.y + 5 # noqa: SIM113
y = self.y + 5
prev_device_index = None
for process in self.snapshots:
device_index = process.device.physical_index
@ -397,7 +399,10 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
if offset > 38 or host_offset == 0:
self.addstr(self.y + 3, self.x + offset - 1, column + indicator)
self.color_at(
self.y + 3, self.x + offset - 1, width=column_width, attr='bold | underline'
self.y + 3,
self.x + offset - 1,
width=column_width,
attr='bold | underline',
)
elif offset <= 38 < offset + column_width:
self.addstr(self.y + 3, self.x + 38, (column + indicator)[39 - offset :])
@ -415,7 +420,10 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
else:
self.addstr(self.y + 3, self.x + offset - 1, column + indicator)
self.color_at(
self.y + 3, self.x + offset - 1, width=column_width, attr='bold | underline'
self.y + 3,
self.x + offset - 1,
width=column_width,
attr='bold | underline',
)
self.color_at(self.y + 3, self.x + offset + column_width - 1, width=1, attr='bold')
@ -426,7 +434,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
self.selection.within_window = False
if len(self.snapshots) > 0:
y = self.y + 5 # noqa: SIM113
y = self.y + 5
prev_device_index = None
prev_device_display_index = None
color = -1
@ -457,7 +465,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
cut_string(process.pid, maxlen=7, padstr='.'),
process.type,
str(
WideString(cut_string(process.username, maxlen=7, padstr='+')).rjust(7)
WideString(cut_string(process.username, maxlen=7, padstr='+')).rjust(7),
),
process.gpu_memory_human,
process.gpu_sm_utilization_string.replace('%', ''),
@ -542,7 +550,8 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
def print_width(self):
self.ensure_snapshots()
return min(
self.width, max((39 + len(process.host_info) for process in self.snapshots), default=79)
self.width,
max((39 + len(process.host_info) for process in self.snapshots), default=79),
)
def print(self):
@ -556,7 +565,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
colored('@', attrs=('bold',)),
colored(HOSTNAME, color='green', attrs=('bold',)),
lines[2][-2:],
)
),
)
if len(self.snapshots) > 0:
@ -591,7 +600,8 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
if process.username != USERNAME and not SUPERUSER:
info = (colored(item, attrs=('dark',)) for item in info)
info = colored(
process.command, color=('red' if process.is_gone else 'yellow')
process.command,
color=('red' if process.is_gone else 'yellow'),
).join(info)
elif process.username != USERNAME and not SUPERUSER:
info = colored(info, attrs=('dark',))

View file

@ -58,10 +58,7 @@ def get_yticks(history, y_offset): # pylint: disable=too-many-branches,too-many
if len(h2p) >= 2:
(hm1, pm1), (h2, p2) = h2p[-2:]
if height < 12:
if h2e[hm1] < h2e[h2]:
ticks = [(hm1, pm1)]
else:
ticks = [(h2, p2)]
ticks = [(hm1, pm1)] if h2e[hm1] < h2e[h2] else [(h2, p2)]
else:
ticks = [(h2, p2)]
if p2 % 2 == 0:
@ -96,7 +93,9 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.enabled = False
self.snapshot_lock = threading.Lock()
self._snapshot_daemon = threading.Thread(
name='process-metrics-snapshot-daemon', target=self._snapshot_target, daemon=True
name='process-metrics-snapshot-daemon',
target=self._snapshot_target,
daemon=True,
)
self._daemon_running = threading.Event()
@ -313,7 +312,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
def frame_lines(self):
line = '' + ' ' * self.left_width + '' + ' ' * self.right_width + ''
frame = [
return [
'' + '' * (self.width - 2) + '',
'{}'.format('Process:'.ljust(self.width - 4)),
'{}'.format('GPU'.ljust(self.width - 4)),
@ -325,7 +324,6 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
*([line] * self.lower_height),
'' + '' * self.left_width + '' + '' * self.right_width + '',
]
return frame
def poke(self):
if self.visible and not self._daemon_running.is_set():
@ -403,7 +401,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
WideString(process.username).rjust(4),
maxlen=32,
padstr='+',
)
),
),
),
(' GPU-MEM', process.gpu_memory_human.rjust(8)),
@ -414,7 +412,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
(' %CPU', process.cpu_percent_string.rjust(6)),
(' %MEM', process.memory_percent_string.rjust(5)),
(' TIME', (' ' + process.running_time_human).rjust(5)),
]
],
)
x = self.x + 1
@ -438,7 +436,10 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.addstr(self.y + 2, self.x + 1, header.ljust(self.width - 2))
self.addstr(self.y + 4, self.x + 1, str(fields.ljust(self.width - 2)))
self.color_at(
self.y + 4, self.x + 1, width=4, fg=self.process.device.snapshot.display_color
self.y + 4,
self.x + 1,
width=4,
fg=self.process.device.snapshot.display_color,
)
if no_break:
@ -448,7 +449,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.y + 2,
x,
cut_string('COMMAND', self.width - x - 2, padstr='..').ljust(
self.width - x - 2
self.width - x - 2,
),
)
if process.is_zombie or process.no_permissions:
@ -471,7 +472,8 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.color(fg='magenta')
for y, line in enumerate(
self.used_host_memory.graph, start=self.y + self.upper_height + 7
self.used_host_memory.graph,
start=self.y + self.upper_height + 7,
):
self.addstr(y, self.x + 1, line)
@ -480,7 +482,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.upper_height - 1
)
for i, (y, line) in enumerate(
enumerate(self.used_gpu_memory.graph, start=self.y + 6)
enumerate(self.used_gpu_memory.graph, start=self.y + 6),
):
self.addstr(
y,
@ -493,7 +495,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.lower_height - 1
)
for i, (y, line) in enumerate(
enumerate(self.gpu_sm_utilization.graph, start=self.y + self.upper_height + 7)
enumerate(self.gpu_sm_utilization.graph, start=self.y + self.upper_height + 7),
):
self.addstr(
y,
@ -508,7 +510,8 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.color(fg=self.process.device.snapshot.gpu_display_color)
for y, line in enumerate(
self.gpu_sm_utilization.graph, start=self.y + self.upper_height + 7
self.gpu_sm_utilization.graph,
start=self.y + self.upper_height + 7,
):
self.addstr(y, self.x + self.left_width + 2, line)
@ -528,7 +531,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.used_host_memory.max_value_string(),
maxlen=self.left_width - 2,
padstr='..',
)
),
),
)
self.addstr(
@ -539,7 +542,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.used_gpu_memory.max_value_string(),
maxlen=self.right_width - 2,
padstr='..',
)
),
),
)
self.addstr(self.y + 7, self.x + self.left_width + 6, f' {self.used_gpu_memory} ')
@ -558,7 +561,8 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
self.addstr(y, self.x, '')
self.addstr(y, self.x + self.left_width + 1, '')
for y in range(
self.y + self.upper_height + 7, self.y + self.upper_height + self.lower_height + 7
self.y + self.upper_height + 7,
self.y + self.upper_height + self.lower_height + 7,
):
self.addstr(y, self.x, '')
self.addstr(y, self.x + self.left_width + 1, '')

View file

@ -129,7 +129,7 @@ class TreeNode: # pylint: disable=too-many-instance-attributes
node._gone, # pylint: disable=protected-access
node.username,
node.pid,
)
),
)
for child in self.children:
child.is_last = False
@ -193,9 +193,7 @@ class TreeNode: # pylint: disable=too-many-instance-attributes
nodes[cpid] = child = cls(HostProcess(cpid))
node.add(child)
roots = sorted(filter(lambda node: node.is_root, nodes.values()), key=lambda node: node.pid)
return roots
return sorted(filter(lambda node: node.is_root, nodes.values()), key=lambda node: node.pid)
@staticmethod
def freeze(roots):
@ -231,7 +229,9 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
self._snapshots = []
self.snapshot_lock = threading.Lock()
self._snapshot_daemon = threading.Thread(
name='treeview-snapshot-daemon', target=self._snapshot_target, daemon=True
name='treeview-snapshot-daemon',
target=self._snapshot_target,
daemon=True,
)
self._daemon_running = threading.Event()
@ -289,7 +289,7 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
cls.SNAPSHOT_INTERVAL = min(interval / 3.0, 1.0)
cls.take_snapshots = ttl_cache(ttl=interval)(
cls.take_snapshots.__wrapped__ # pylint: disable=no-member
cls.take_snapshots.__wrapped__, # pylint: disable=no-member
)
@ttl_cache(ttl=2.0)
@ -354,7 +354,8 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
elif y >= self.y + self.height:
self.scroll_offset += y - self.y - self.height + 1
self.scroll_offset = max(
min(len(self.snapshots) - self.display_height, self.scroll_offset), 0
min(len(self.snapshots) - self.display_height, self.scroll_offset),
0,
)
break
else:
@ -367,14 +368,17 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
pid_width = max(3, max((len(str(process.pid)) for process in self.snapshots), default=3))
username_width = max(
4, max((len(process.username) for process in self.snapshots), default=4)
4,
max((len(process.username) for process in self.snapshots), default=4),
)
device_width = max(6, max((len(process.devices) for process in self.snapshots), default=6))
num_threads_width = max(
4, max((len(str(process.num_threads)) for process in self.snapshots), default=4)
4,
max((len(str(process.num_threads)) for process in self.snapshots), default=4),
)
time_width = max(
4, max((len(process.running_time_human) for process in self.snapshots), default=4)
4,
max((len(process.running_time_human) for process in self.snapshots), default=4),
)
header = ' '.join(
@ -387,12 +391,14 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
'%MEM',
'TIME'.rjust(time_width),
'COMMAND',
]
],
)
command_offset = len(header) - 7
if self.x_offset < command_offset:
self.addstr(
self.y, self.x, header[self.x_offset : self.x_offset + self.width].ljust(self.width)
self.y,
self.x,
header[self.x_offset : self.x_offset + self.width].ljust(self.width),
)
else:
self.addstr(self.y, self.x, 'COMMAND'.ljust(self.width))
@ -413,7 +419,9 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
self.selection.within_window = False
processes = islice(
self.snapshots, self.scroll_offset, self.scroll_offset + self.display_height
self.snapshots,
self.scroll_offset,
self.scroll_offset + self.display_height,
)
for y, process in enumerate(processes, start=self.y + 1):
prefix_length = len(process.prefix)
@ -485,7 +493,12 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
attr='bold | italic | reverse',
)
self.color_at(
self.y, text_offset + 10, width=3, fg='cyan', bg='red', attr='bold | reverse'
self.y,
text_offset + 10,
width=3,
fg='cyan',
bg='red',
attr='bold | reverse',
)
self.color_at(
self.y,
@ -496,7 +509,12 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
attr='bold | italic | reverse',
)
self.color_at(
self.y, text_offset + 17, width=4, fg='cyan', bg='red', attr='bold | reverse'
self.y,
text_offset + 17,
width=4,
fg='cyan',
bg='red',
attr='bold | reverse',
)
self.color_at(
self.y,
@ -507,7 +525,12 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
attr='bold | italic | reverse',
)
self.color_at(
self.y, text_offset + 25, width=4, fg='cyan', bg='red', attr='bold | reverse'
self.y,
text_offset + 25,
width=4,
fg='cyan',
bg='red',
attr='bold | reverse',
)
def finalize(self):

View file

@ -41,7 +41,12 @@ class UI(DisplayableContainer): # pylint: disable=too-many-instance-attributes
self.device_count = len(self.devices)
self.main_screen = MainScreen(
self.devices, filters, ascii=ascii, mode=mode, win=win, root=self
self.devices,
filters,
ascii=ascii,
mode=mode,
win=win,
root=self,
)
self.main_screen.visible = True
self.main_screen.focused = False
@ -193,8 +198,7 @@ class UI(DisplayableContainer): # pylint: disable=too-many-instance-attributes
self.main_screen.print()
def handle_mouse(self):
"""Handles mouse input"""
"""Handle mouse input."""
try:
event = MouseEvent(curses.getmouse())
except curses.error:
@ -202,8 +206,7 @@ class UI(DisplayableContainer): # pylint: disable=too-many-instance-attributes
super().click(event)
def handle_key(self, key):
"""Handles key input"""
"""Handle key input."""
if key < 0:
self.keybuffer.clear()
elif not super().press(key):

View file

@ -245,7 +245,7 @@ def select_devices( # pylint: disable=too-many-branches,too-many-statements,too
(not math.isnan(device.gpu_utilization), device.gpu_utilization), # ascending
(not math.isnan(device.memory_utilization), device.memory_utilization), # ascending
-device.physical_index, # descending to keep <GPU 0> free
)
),
)
if any(device.is_mig_device for device in available_devices): # found MIG devices!

View file

@ -85,3 +85,79 @@ multi_line_output = 3
[tool.pydocstyle]
convention = "google"
match-dir = '^(?!(gui|callbacks|docs))[^\.].*'
[tool.ruff]
target-version = "py37"
line-length = 100
show-source = true
src = ["nvitop"]
select = [
"E", "W", # pycodestyle
"F", # pyflakes
"N", # pep8-naming
"UP", # pyupgrade
"ANN", # flake8-annotations
"S", # flake8-bandit
"BLE", # flake8-blind-except
"B", # flake8-bugbear
"COM", # flake8-commas
"C4", # flake8-comprehensions
"EXE", # flake8-executable
"ISC", # flake8-implicit-str-concat
"PIE", # flake8-pie
"PYI", # flake8-pyi
"Q", # flake8-quotes
"RSE", # flake8-raise
"RET", # flake8-return
"SIM", # flake8-simplify
"TID", # flake8-tidy-imports
"RUF", # ruff
]
ignore = [
# E501: line too long
# W505: doc line too long
# too long docstring due to long example blocks
"E501",
"W505",
# ANN101: missing type annotation for `self` in method
# ANN102: missing type annotation for `cls` in classmethod
"ANN101",
"ANN102",
# ANN401: dynamically typed expressions (typing.Any) are disallowed
"ANN401",
# S101: use of `assert` detected
# internal use and may never raise at runtime
"S101",
# SIM105: use `contextlib.suppress(...)` instead of try-except-pass
# reduce unnessary function call
"SIM105",
]
[tool.ruff.per-file-ignores]
"__init__.py" = [
"F401", # unused-import
]
"setup.py" = [
"ANN", # flake8-annotations
]
"nvitop/api/lib*.py" = [
"N", # pep8-naming
"ANN", # flake8-annotations
]
"nvitop/callbacks/*.py" = [
"ANN", # flake8-annotations
]
"nvitop/gui/**/*.py" = [
"ANN", # flake8-annotations
]
[tool.ruff.flake8-annotations]
allow-star-arg-any = true
[tool.ruff.flake8-quotes]
docstring-quotes = "double"
multiline-quotes = "double"
inline-quotes = "single"
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "all"