diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c922d2d..7ea334e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: debug-statements - id: double-quote-string-fixer - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.9.0 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/nvitop-exporter/nvitop_exporter/exporter.py b/nvitop-exporter/nvitop_exporter/exporter.py index ceaeb6c..ec49b99 100644 --- a/nvitop-exporter/nvitop_exporter/exporter.py +++ b/nvitop-exporter/nvitop_exporter/exporter.py @@ -604,9 +604,9 @@ class PrometheusExporter: # pylint: disable=too-many-instance-attributes username = process.username() alive_pids.add((pid, username)) if (pid, username) not in host_snapshots: # noqa: SIM401,RUF100 - host_snapshot = host_snapshots[(pid, username)] = process.host_snapshot() + host_snapshot = host_snapshots[pid, username] = process.host_snapshot() else: - host_snapshot = host_snapshots[(pid, username)] + host_snapshot = host_snapshots[pid, username] self.process_info.labels( hostname=self.hostname, index=index, diff --git a/nvitop/api/collector.py b/nvitop/api/collector.py index 9083127..8878c9e 100644 --- a/nvitop/api/collector.py +++ b/nvitop/api/collector.py @@ -25,7 +25,7 @@ import os import threading import time from collections import OrderedDict, defaultdict -from typing import Callable, ClassVar, Generator, Iterable, NamedTuple, TypeVar +from typing import TYPE_CHECKING, ClassVar, NamedTuple, TypeVar from weakref import WeakSet from nvitop.api import host @@ -34,6 +34,10 @@ from nvitop.api.process import GpuProcess, HostProcess from nvitop.api.utils import GiB, MiB, Snapshot +if TYPE_CHECKING: + from collections.abc import Callable, Generator, Iterable + + __all__ = ['take_snapshots', 'collect_in_background', 'ResourceMetricCollector'] diff --git a/nvitop/api/device.py b/nvitop/api/device.py index ef66e7f..ae728c3 100644 --- a/nvitop/api/device.py +++ b/nvitop/api/device.py @@ -115,7 +115,7 @@ import textwrap import threading import time from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generator, Iterable, NamedTuple, overload +from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, overload from nvitop.api import libcuda, libcudart, libnvml from nvitop.api.process import GpuProcess @@ -131,7 +131,7 @@ from nvitop.api.utils import ( if TYPE_CHECKING: - from collections.abc import Hashable + from collections.abc import Callable, Generator, Hashable, Iterable from typing_extensions import ( Literal, # Python 3.8+ Self, # Python 3.11+ @@ -458,9 +458,7 @@ 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. @@ -1528,10 +1526,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me return self._nvlink_link_count # type: ignore[return-value] @memoize_when_activated - def nvlink_throughput( - self, - interval: float | None = None, - ) -> list[ThroughputInfo]: # in KiB/s + def nvlink_throughput(self, interval: float | None = None) -> list[ThroughputInfo]: # in KiB/s """The current NVLink throughput for each NVLink in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1606,10 +1601,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me for tx, rx in zip(throughputs[:nvlink_link_count], throughputs[nvlink_link_count:]) ] - def nvlink_total_throughput( - self, - interval: float | None = None, - ) -> ThroughputInfo: # in KiB/s + def nvlink_total_throughput(self, interval: float | None = None) -> ThroughputInfo: # in KiB/s """The total NVLink throughput for all NVLinks in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1639,10 +1631,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me rx=sum(rx_throughputs) if rx_throughputs else NA, ) - def nvlink_mean_throughput( - self, - interval: float | None = None, - ) -> ThroughputInfo: # in KiB/s + def nvlink_mean_throughput(self, interval: float | None = None) -> ThroughputInfo: # in KiB/s """The mean NVLink throughput for all NVLinks in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1672,10 +1661,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me rx=round(sum(rx_throughputs) / len(rx_throughputs)) if rx_throughputs else NA, ) - def nvlink_tx_throughput( - self, - interval: float | None = None, - ) -> list[int | NaType]: # in KiB/s + def nvlink_tx_throughput(self, interval: float | None = None) -> list[int | NaType]: # in KiB/s """The current NVLink transmit data throughput in KiB/s for each NVLink. This function is querying data counters between methods calls and thus is the NVLink @@ -1695,10 +1681,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me """ return [tx for tx, _ in self.nvlink_throughput(interval=interval)] - def nvlink_mean_tx_throughput( - self, - interval: float | None = None, - ) -> int | NaType: # in KiB/s + def nvlink_mean_tx_throughput(self, interval: float | None = None) -> int | NaType: # in KiB/s """The mean NVLink transmit data throughput for all NVLinks in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1718,10 +1701,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me """ return self.nvlink_mean_throughput(interval=interval).tx - def nvlink_total_tx_throughput( - self, - interval: float | None = None, - ) -> int | NaType: # in KiB/s + def nvlink_total_tx_throughput(self, interval: float | None = None) -> int | NaType: # in KiB/s """The total NVLink transmit data throughput for all NVLinks in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1741,10 +1721,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me """ return self.nvlink_total_throughput(interval=interval).tx - def nvlink_rx_throughput( - self, - interval: float | None = None, - ) -> list[int | NaType]: # in KiB/s + def nvlink_rx_throughput(self, interval: float | None = None) -> list[int | NaType]: # in KiB/s """The current NVLink receive data throughput for each NVLink in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1764,10 +1741,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me """ return [rx for _, rx in self.nvlink_throughput(interval=interval)] - def nvlink_mean_rx_throughput( - self, - interval: float | None = None, - ) -> int | NaType: # in KiB/s + def nvlink_mean_rx_throughput(self, interval: float | None = None) -> int | NaType: # in KiB/s """The mean NVLink receive data throughput for all NVLinks in KiB/s. This function is querying data counters between methods calls and thus is the NVLink @@ -1787,10 +1761,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me """ return self.nvlink_mean_throughput(interval=interval).rx - def nvlink_total_rx_throughput( - self, - interval: float | None = None, - ) -> int | NaType: # in KiB/s + def nvlink_total_rx_throughput(self, interval: float | None = None) -> int | NaType: # in KiB/s """The total NVLink receive data throughput for all NVLinks in KiB/s. This function is querying data counters between methods calls and thus is the NVLink diff --git a/nvitop/api/host.py b/nvitop/api/host.py index 0f3ad4b..d401fe3 100644 --- a/nvitop/api/host.py +++ b/nvitop/api/host.py @@ -23,12 +23,16 @@ utilization (CPU, memory, disks, network, sensors) in Python. from __future__ import annotations import os as _os -from typing import Callable as _Callable +from typing import TYPE_CHECKING as _TYPE_CHECKING import psutil as _psutil from psutil import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin +if _TYPE_CHECKING: + from collections.abc import Callable as _Callable + + __all__ = [name for name in _psutil.__all__ if not name.startswith('_')] + [ 'getuser', 'hostname', diff --git a/nvitop/api/libcuda.py b/nvitop/api/libcuda.py index 50a5a13..7f1e6f8 100644 --- a/nvitop/api/libcuda.py +++ b/nvitop/api/libcuda.py @@ -28,11 +28,11 @@ import sys as _sys import threading as _threading from typing import TYPE_CHECKING as _TYPE_CHECKING from typing import Any as _Any -from typing import Callable as _Callable from typing import ClassVar as _ClassVar if _TYPE_CHECKING: + from collections.abc import Callable as _Callable from typing_extensions import Self as _Self # Python 3.11+ from typing_extensions import TypeAlias as _TypeAlias # Python 3.10+ diff --git a/nvitop/api/libcudart.py b/nvitop/api/libcudart.py index fb748d7..2eea0a0 100644 --- a/nvitop/api/libcudart.py +++ b/nvitop/api/libcudart.py @@ -28,11 +28,11 @@ import sys as _sys import threading as _threading from typing import TYPE_CHECKING as _TYPE_CHECKING from typing import Any as _Any -from typing import Callable as _Callable from typing import ClassVar as _ClassVar if _TYPE_CHECKING: + from collections.abc import Callable as _Callable from typing_extensions import Self as _Self # Python 3.11+ diff --git a/nvitop/api/libnvml.py b/nvitop/api/libnvml.py index 0ee818c..e1efe37 100644 --- a/nvitop/api/libnvml.py +++ b/nvitop/api/libnvml.py @@ -33,7 +33,6 @@ from types import FunctionType as _FunctionType from types import ModuleType as _ModuleType from typing import TYPE_CHECKING as _TYPE_CHECKING from typing import Any as _Any -from typing import Callable as _Callable from typing import ClassVar as _ClassVar # Python Bindings for the NVIDIA Management Library (NVML) @@ -47,6 +46,7 @@ from nvitop.api.utils import colored as __colored if _TYPE_CHECKING: + from collections.abc import Callable as _Callable from typing_extensions import Self as _Self # Python 3.11+ from typing_extensions import TypeAlias as _TypeAlias # Python 3.10+ @@ -513,10 +513,7 @@ def nvmlQueryFieldValues( return values_with_timestamps -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 @@ -595,12 +592,12 @@ if not _pynvml_installation_corrupted: if __get_running_processes_version_suffix is None: # pylint: disable-next=protected-access,no-member - _nvmlGetFunctionPointer = _pynvml._nvmlGetFunctionPointer + nvmlGetFunctionPointer = _pynvml._nvmlGetFunctionPointer __get_running_processes_version_suffix = '_v3' def lookup(symbol: str) -> _Any | None: try: - ptr = _nvmlGetFunctionPointer(symbol) + ptr = nvmlGetFunctionPointer(symbol) except NVMLError_FunctionNotFound: LOGGER.debug('Failed to found symbol `%s`.', symbol) return None @@ -711,10 +708,7 @@ if not _pynvml_installation_corrupted: NVMLError_Unknown: On any unexpected error. """ - return __nvml_device_get_running_processes( - 'nvmlDeviceGetComputeRunningProcesses', - handle, - ) + return __nvml_device_get_running_processes('nvmlDeviceGetComputeRunningProcesses', handle) def nvmlDeviceGetGraphicsRunningProcesses( # pylint: disable=function-redefined handle: c_nvmlDevice_t, @@ -738,10 +732,7 @@ if not _pynvml_installation_corrupted: NVMLError_Unknown: On any unexpected error. """ - return __nvml_device_get_running_processes( - 'nvmlDeviceGetGraphicsRunningProcesses', - handle, - ) + return __nvml_device_get_running_processes('nvmlDeviceGetGraphicsRunningProcesses', handle) def nvmlDeviceGetMPSComputeRunningProcesses( # pylint: disable=function-redefined handle: c_nvmlDevice_t, @@ -819,10 +810,10 @@ if not _pynvml_installation_corrupted: if __get_memory_info_version_suffix is None: # pylint: disable-next=protected-access,no-member - _nvmlGetFunctionPointer = _pynvml._nvmlGetFunctionPointer + nvml_get_function_pointer = _pynvml._nvmlGetFunctionPointer __get_memory_info_version_suffix = '_v2' try: - _nvmlGetFunctionPointer('nvmlDeviceGetMemoryInfo_v2') + nvml_get_function_pointer('nvmlDeviceGetMemoryInfo_v2') except NVMLError_FunctionNotFound: LOGGER.debug('Failed to found symbol `nvmlDeviceGetMemoryInfo_v2`.') c_nvmlMemory_t = c_nvmlMemory_v1_t diff --git a/nvitop/api/process.py b/nvitop/api/process.py index d06b56f..c860d5a 100644 --- a/nvitop/api/process.py +++ b/nvitop/api/process.py @@ -25,9 +25,9 @@ import datetime import functools import os import threading -from abc import ABCMeta +from abc import ABC from types import FunctionType -from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable +from typing import TYPE_CHECKING, Any from weakref import WeakValueDictionary from nvitop.api import host, libnvml @@ -43,6 +43,7 @@ from nvitop.api.utils import ( if TYPE_CHECKING: + from collections.abc import Callable, Generator, Iterable from typing_extensions import Self # Python 3.11+ from nvitop.api.device import Device @@ -122,7 +123,7 @@ def auto_garbage_clean( except host.PsutilError as ex: try: with GpuProcess.INSTANCE_LOCK: - del GpuProcess.INSTANCES[(self.pid, self.device)] + del GpuProcess.INSTANCES[self.pid, self.device] except (KeyError, AttributeError): pass try: @@ -144,7 +145,7 @@ def auto_garbage_clean( return wrapper -class HostProcess(host.Process, metaclass=ABCMeta): +class HostProcess(host.Process, ABC): """Represent an OS process with the given PID. If PID is omitted current process PID (:func:`os.getpid`) is used. The instance will be cache @@ -476,7 +477,7 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi with cls.INSTANCE_LOCK: try: - instance = cls.INSTANCES[(pid, device)] + instance = cls.INSTANCES[pid, device] if instance.is_running(): return instance # type: ignore[return-value] except KeyError: @@ -492,7 +493,7 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi instance._hash = None instance._username = None - cls.INSTANCES[(pid, device)] = instance + cls.INSTANCES[pid, device] = instance return instance diff --git a/nvitop/api/utils.py b/nvitop/api/utils.py index 6f19ce9..9f44f73 100644 --- a/nvitop/api/utils.py +++ b/nvitop/api/utils.py @@ -29,11 +29,15 @@ import re import sys import time from collections.abc import KeysView -from typing import Any, Callable, Generator, Iterable, Iterator, TypeVar +from typing import TYPE_CHECKING, Any, Callable, TypeVar from psutil import WINDOWS +if TYPE_CHECKING: + from collections.abc import Generator, Iterable, Iterator + + __all__ = [ 'NA', 'NaType', @@ -479,9 +483,10 @@ NotApplicableType = NaType # NA == 'N/A' -> True # NA is NaType() -> True (`NaType` is a singleton class) NA = NaType() -NA.__doc__ = """The singleton instance of :class:`NaType`. The actual value is :const:`str: 'N/A'`.""" # pylint: disable=attribute-defined-outside-init +"""The singleton instance of :class:`NaType`. The actual value is :const:`str: 'N/A'`.""" NotApplicable = NA +"""The singleton instance of :class:`NaType`. The actual value is :const:`str: 'N/A'`.""" UINT_MAX: int = ctypes.c_uint(-1).value # 0xFFFFFFFF """The maximum value of :class:`ctypes.c_uint`.""" diff --git a/nvitop/gui/library/history.py b/nvitop/gui/library/history.py index 02d4fb2..583a61d 100644 --- a/nvitop/gui/library/history.py +++ b/nvitop/gui/library/history.py @@ -33,11 +33,11 @@ VALUE2SYMBOL_DOWN = { SYMBOL2VALUE_UP = {v: k for k, v in VALUE2SYMBOL_UP.items()} SYMBOL2VALUE_DOWN = {v: k for k, v in VALUE2SYMBOL_DOWN.items()} PAIR2SYMBOL_UP = { - (s1, s2): VALUE2SYMBOL_UP[(SYMBOL2VALUE_UP[s1][-1], SYMBOL2VALUE_UP[s2][0])] + (s1, s2): VALUE2SYMBOL_UP[SYMBOL2VALUE_UP[s1][-1], SYMBOL2VALUE_UP[s2][0]] for s1, s2 in itertools.product(SYMBOL2VALUE_UP, repeat=2) } PAIR2SYMBOL_DOWN = { - (s1, s2): VALUE2SYMBOL_DOWN[(SYMBOL2VALUE_DOWN[s1][-1], SYMBOL2VALUE_DOWN[s2][0])] + (s1, s2): VALUE2SYMBOL_DOWN[SYMBOL2VALUE_DOWN[s1][-1], SYMBOL2VALUE_DOWN[s2][0]] for s1, s2 in itertools.product(SYMBOL2VALUE_DOWN, repeat=2) } GRAPH_SYMBOLS = ''.join( @@ -269,7 +269,7 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes for h in range(self.height): s1 = min(max(round(5 * (value1 - h)), 0), 4) s2 = min(max(round(5 * (value2 - h)), 0), 4) - bar.append(self.value2symbol[(s1, s2)]) + bar.append(self.value2symbol[s1, s2]) if not self.upsidedown: bar.reverse() return bar diff --git a/nvitop/gui/library/keybinding.py b/nvitop/gui/library/keybinding.py index 02578fb..9d8d392 100644 --- a/nvitop/gui/library/keybinding.py +++ b/nvitop/gui/library/keybinding.py @@ -7,10 +7,11 @@ import copy import curses import curses.ascii +import string from collections import OrderedDict -DIGITS = set(map(ord, '0123456789')) +DIGITS = set(map(ord, string.digits)) # Arbitrary numbers which are not used with curses.KEY_XYZ ANYKEY, PASSIVE_ACTION, ALT_KEY, QUANT_KEY = range(9001, 9005) @@ -54,7 +55,7 @@ VERY_SPECIAL_KEYS = { } -def _uncase_special_key(string): +def _uncase_special_key(key_string): """Uncase a special key. >>> _uncase_special_key('Esc') @@ -70,9 +71,9 @@ def _uncase_special_key(string): >>> _uncase_special_key('A-x') 'a-x' """ - uncased = string.lower() + uncased = key_string.lower() if len(uncased) == 3 and (uncased.startswith(('a-', 'm-'))): - uncased = f'{uncased[0]}-{string[-1]}' + uncased = f'{uncased[0]}-{key_string[-1]}' return uncased @@ -139,13 +140,13 @@ def parse_keybinding(obj): # pylint: disable=too-many-branches if in_brackets: if char == '>': in_brackets = False - string = ''.join(bracket_content) + key_string = ''.join(bracket_content) try: - keys = SPECIAL_KEYS_UNCASED[_uncase_special_key(string)] + keys = SPECIAL_KEYS_UNCASED[_uncase_special_key(key_string)] yield from keys except KeyError: - if string.isdigit(): - yield int(string) + if key_string.isdigit(): + yield int(key_string) else: yield ord('<') for bracket_char in bracket_content: @@ -199,7 +200,7 @@ def construct_keybinding(keys): continue if alt_key_on: try: - strings.append(f'<{REVERSED_SPECIAL_KEYS[(ALT_KEY, key)]}>') + strings.append(f'<{REVERSED_SPECIAL_KEYS[ALT_KEY, key]}>') except KeyError: strings.extend(map(key_to_string, (ALT_KEY, key))) else: diff --git a/nvitop/gui/library/messagebox.py b/nvitop/gui/library/messagebox.py index f044a16..a0a7fb5 100644 --- a/nvitop/gui/library/messagebox.py +++ b/nvitop/gui/library/messagebox.py @@ -5,6 +5,7 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring import curses +import string import threading import time from functools import partial @@ -16,6 +17,9 @@ from nvitop.gui.library.utils import cut_string from nvitop.gui.library.widestring import WideString +DIGITS = set(string.digits) + + class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes class Option: # pylint: disable=too-few-public-methods # pylint: disable-next=too-many-arguments @@ -213,10 +217,7 @@ class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes keymaps.copy('messagebox', option.key, key) keymaps['messagebox'][keymaps.keybuffer.quantifier_key] = 'false' - if ( - len(set('0123456789').intersection(keymaps['messagebox'])) == 0 - and self.num_options <= 9 - ): + if len(DIGITS.intersection(keymaps['messagebox'])) == 0 and self.num_options <= 9: for key_n, option in zip('123456789', self.options): keymaps.copy('messagebox', option.key, key_n) diff --git a/nvitop/gui/screens/main/process.py b/nvitop/gui/screens/main/process.py index 294978e..d5e9c34 100644 --- a/nvitop/gui/screens/main/process.py +++ b/nvitop/gui/screens/main/process.py @@ -3,11 +3,13 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring +from __future__ import annotations + import itertools import threading import time from operator import attrgetter, xor -from typing import Any, Callable, NamedTuple +from typing import TYPE_CHECKING, Any, NamedTuple from cachetools.func import ttl_cache @@ -29,6 +31,10 @@ from nvitop.gui.library import ( ) +if TYPE_CHECKING: + from collections.abc import Callable + + class Order(NamedTuple): key: Callable[[Any], Any] reverse: bool diff --git a/nvitop/select.py b/nvitop/select.py index a080c5f..5dcc2bf 100644 --- a/nvitop/select.py +++ b/nvitop/select.py @@ -62,13 +62,14 @@ import math import os import sys import warnings -from typing import TYPE_CHECKING, Callable, Iterable, Sequence, overload +from typing import TYPE_CHECKING, overload from nvitop.api import Device, GpuProcess, Snapshot, colored, host, human2bytes, libnvml from nvitop.version import __version__ if TYPE_CHECKING: + from collections.abc import Callable, Iterable, Sequence from typing_extensions import Literal # Python 3.8+ diff --git a/pyproject.toml b/pyproject.toml index 8f1b559..539651a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -160,6 +160,9 @@ ignore = [ "W505", # ANN401: dynamically typed expressions (typing.Any) are disallowed "ANN401", + # FURB189: use the `UserDict`, `UserList`, and `UserString` instead + # internally subclassing `dict`, `list`, and `str` + "FURB189", # S101: use of `assert` detected # internal use and may never raise at runtime "S101",