diff --git a/.flake8 b/.flake8 index a992307..bee04d3 100644 --- a/.flake8 +++ b/.flake8 @@ -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, diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c757a82..125d942 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -75,7 +75,12 @@ jobs: python -m pip install --upgrade pip setuptools pre-commit pylint[spelling] python -m pip install -r requirements.txt && python -m pre_commit install --install-hooks && - python -m pre_commit run --all-files + python -m pre_commit run --all-files && + python -c 'import nvitop' && + python -m nvitop --version && + python -m nvitop --help && + python -m nvitop.select --version && + python -m nvitop.select --help ) - name: Test docker build diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ca63f56..e39846a 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -49,6 +49,14 @@ jobs: run: | python -m pip install -r requirements.txt + - name: Import tests + run: | + python -c 'import nvitop' + python -m nvitop --version + python -m nvitop --help + python -m nvitop.select --version + python -m nvitop.select --help + - name: Install linters run: | python -m pip install --upgrade pre-commit pylint[spelling] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1cf91b4..8b1694c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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: diff --git a/nvitop/api/collector.py b/nvitop/api/collector.py index bfd504b..87d93b2 100644 --- a/nvitop/api/collector.py +++ b/nvitop/api/collector.py @@ -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() diff --git a/nvitop/api/device.py b/nvitop/api/device.py index 3b44cb7..0728931 100644 --- a/nvitop/api/device.py +++ b/nvitop/api/device.py @@ -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= --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= --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= --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= --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= --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 diff --git a/nvitop/api/host.py b/nvitop/api/host.py index f487293..f80c191 100644 --- a/nvitop/api/host.py +++ b/nvitop/api/host.py @@ -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 diff --git a/nvitop/api/libcuda.py b/nvitop/api/libcuda.py index 6618d15..959c930 100644 --- a/nvitop/api/libcuda.py +++ b/nvitop/api/libcuda.py @@ -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' diff --git a/nvitop/api/libcudart.py b/nvitop/api/libcudart.py index 5542016..969e935 100644 --- a/nvitop/api/libcudart.py +++ b/nvitop/api/libcudart.py @@ -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: diff --git a/nvitop/api/libnvml.py b/nvitop/api/libnvml.py index c8c4c78..839b2f7 100644 --- a/nvitop/api/libnvml.py +++ b/nvitop/api/libnvml.py @@ -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: diff --git a/nvitop/api/process.py b/nvitop/api/process.py index 0296b80..b0e02bf 100644 --- a/nvitop/api/process.py +++ b/nvitop/api/process.py @@ -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: diff --git a/nvitop/api/utils.py b/nvitop/api/utils.py index 64cefa4..cea58dd 100644 --- a/nvitop/api/utils.py +++ b/nvitop/api/utils.py @@ -491,7 +491,8 @@ SIZE_UNITS = { } """Units of storage and memory measurements.""" SIZE_PATTERN = re.compile( - r'^\s*\+?\s*(?P\d+(?:\.\d+)?)\s*(?P[KMGTP]i?B?|B?)\s*$', flags=re.IGNORECASE + r'^\s*\+?\s*(?P\d+(?:\.\d+)?)\s*(?P[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 diff --git a/nvitop/callbacks/keras.py b/nvitop/callbacks/keras.py index d716384..c480399 100644 --- a/nvitop/callbacks/keras.py +++ b/nvitop/callbacks/keras.py @@ -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 diff --git a/nvitop/callbacks/pytorch_lightning.py b/nvitop/callbacks/pytorch_lightning.py index 77182f8..a37fa0c 100644 --- a/nvitop/callbacks/pytorch_lightning.py +++ b/nvitop/callbacks/pytorch_lightning.py @@ -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: diff --git a/nvitop/cli.py b/nvitop/cli.py index 8334399..b3fe85a 100644 --- a/nvitop/cli.py +++ b/nvitop/cli.py @@ -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 diff --git a/nvitop/gui/library/displayable.py b/nvitop/gui/library/displayable.py index 78a8fa3..452e537 100644 --- a/nvitop/gui/library/displayable.py +++ b/nvitop/gui/library/displayable.py @@ -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: diff --git a/nvitop/gui/library/history.py b/nvitop/gui/library/history.py index 51f61bd..6396ec3 100644 --- a/nvitop/gui/library/history.py +++ b/nvitop/gui/library/history.py @@ -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 diff --git a/nvitop/gui/library/keybinding.py b/nvitop/gui/library/keybinding.py index c3ba5ec..f51e6ca 100644 --- a/nvitop/gui/library/keybinding.py +++ b/nvitop/gui/library/keybinding.py @@ -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')) 'lol' @@ -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') 'lol' @@ -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)) diff --git a/nvitop/gui/library/libcurses.py b/nvitop/gui/library/libcurses.py index e1c3e1d..e892440 100644 --- a/nvitop/gui/library/libcurses.py +++ b/nvitop/gui/library/libcurses.py @@ -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(): diff --git a/nvitop/gui/library/messagebox.py b/nvitop/gui/library/messagebox.py index bae30ef..bec309f 100644 --- a/nvitop/gui/library/messagebox.py +++ b/nvitop/gui/library/messagebox.py @@ -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 diff --git a/nvitop/gui/library/mouse.py b/nvitop/gui/library/mouse.py index 8a5f3c9..e6d6b13 100644 --- a/nvitop/gui/library/mouse.py +++ b/nvitop/gui/library/mouse.py @@ -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. diff --git a/nvitop/gui/library/selection.py b/nvitop/gui/library/selection.py index fdf71fd..609c439 100644 --- a/nvitop/gui/library/selection.py +++ b/nvitop/gui/library/selection.py @@ -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 diff --git a/nvitop/gui/library/widestring.py b/nvitop/gui/library/widestring.py index a2bac26..67600e0 100644 --- a/nvitop/gui/library/widestring.py +++ b/nvitop/gui/library/widestring.py @@ -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] """ - 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) """ - 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) """ - 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) """ - 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() """ - 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() """ - 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() """ - return WideString(self.string.rstrip(chars)) diff --git a/nvitop/gui/screens/environ.py b/nvitop/gui/screens/environ.py index 61f6411..c8c8011 100644 --- a/nvitop/gui/screens/environ.py +++ b/nvitop/gui/screens/environ.py @@ -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]) diff --git a/nvitop/gui/screens/help.py b/nvitop/gui/screens/help.py index 326eebf..75b2862 100644 --- a/nvitop/gui/screens/help.py +++ b/nvitop/gui/screens/help.py @@ -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 diff --git a/nvitop/gui/screens/main/__init__.py b/nvitop/gui/screens/main/__init__.py index e6bd807..c6c6f48 100644 --- a/nvitop/gui/screens/main/__init__.py +++ b/nvitop/gui/screens/main/__init__.py @@ -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), ) diff --git a/nvitop/gui/screens/main/device.py b/nvitop/gui/screens/main/device.py index 1a8ddd8..6e3ca46 100644 --- a/nvitop/gui/screens/main/device.py +++ b/nvitop/gui/screens/main/device.py @@ -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)} │' diff --git a/nvitop/gui/screens/main/host.py b/nvitop/gui/screens/main/host.py index 36caf33..30f43d9 100644 --- a/nvitop/gui/screens/main/host.py +++ b/nvitop/gui/screens/main/host.py @@ -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 diff --git a/nvitop/gui/screens/main/process.py b/nvitop/gui/screens/main/process.py index 3ab199d..b037115 100644 --- a/nvitop/gui/screens/main/process.py +++ b/nvitop/gui/screens/main/process.py @@ -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',)) diff --git a/nvitop/gui/screens/metrics.py b/nvitop/gui/screens/metrics.py index ac8afed..714dbc8 100644 --- a/nvitop/gui/screens/metrics.py +++ b/nvitop/gui/screens/metrics.py @@ -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, '│') diff --git a/nvitop/gui/screens/treeview.py b/nvitop/gui/screens/treeview.py index f07af72..694ef90 100644 --- a/nvitop/gui/screens/treeview.py +++ b/nvitop/gui/screens/treeview.py @@ -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): diff --git a/nvitop/gui/ui.py b/nvitop/gui/ui.py index 8c2b6b4..a29b00a 100644 --- a/nvitop/gui/ui.py +++ b/nvitop/gui/ui.py @@ -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): diff --git a/nvitop/select.py b/nvitop/select.py index 9774b38..5a62a58 100644 --- a/nvitop/select.py +++ b/nvitop/select.py @@ -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 free - ) + ), ) if any(device.is_mig_device for device in available_devices): # found MIG devices! diff --git a/pyproject.toml b/pyproject.toml index 37e43aa..5685bad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"