mirror of
https://github.com/XuehaiPan/nvitop.git
synced 2026-05-21 06:45:24 -06:00
feat: ruff integration
This commit is contained in:
parent
c5ce570c72
commit
80b9d6f75c
32 changed files with 524 additions and 312 deletions
3
.flake8
3
.flake8
|
|
@ -20,6 +20,9 @@ per-file-ignores =
|
||||||
# F401: module imported but unused
|
# F401: module imported but unused
|
||||||
# intentionally unused imports
|
# intentionally unused imports
|
||||||
__init__.py: F401
|
__init__.py: F401
|
||||||
|
# SIM113: use enumarate
|
||||||
|
# false positive
|
||||||
|
nvitop/gui/screens/main/process.py: SIM113
|
||||||
exclude =
|
exclude =
|
||||||
.git,
|
.git,
|
||||||
.vscode,
|
.vscode,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ repos:
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: double-quote-string-fixer
|
- 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
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 5.12.0
|
rev: 5.12.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ def take_snapshots(
|
||||||
else:
|
else:
|
||||||
leaf_devices = devices = list(devices)
|
leaf_devices = devices = list(devices)
|
||||||
gpu_processes = list(
|
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]
|
devices = [device.as_snapshot() for device in devices]
|
||||||
|
|
@ -430,7 +430,9 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
|
||||||
self._tags = set()
|
self._tags = set()
|
||||||
|
|
||||||
self._daemon = threading.Thread(
|
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()
|
self._daemon_running = threading.Event()
|
||||||
|
|
||||||
|
|
@ -489,7 +491,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
|
||||||
tag = self._metric_buffer.tag
|
tag = self._metric_buffer.tag
|
||||||
elif tag not in self._tags:
|
elif tag not in self._tags:
|
||||||
raise RuntimeError(
|
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
|
buffer = self._metric_buffer
|
||||||
|
|
@ -568,7 +570,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
|
||||||
tag = self._metric_buffer.tag
|
tag = self._metric_buffer.tag
|
||||||
elif tag not in self._tags:
|
elif tag not in self._tags:
|
||||||
raise RuntimeError(
|
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
|
buffer = self._metric_buffer
|
||||||
|
|
@ -707,7 +709,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
|
||||||
'host/memory_percent (%)': host.memory_percent(),
|
'host/memory_percent (%)': host.memory_percent(),
|
||||||
'host/swap_percent (%)': host.swap_percent(),
|
'host/swap_percent (%)': host.swap_percent(),
|
||||||
'host/memory_used (GiB)': host.virtual_memory().used / GiB,
|
'host/memory_used (GiB)': host.virtual_memory().used / GiB,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
load_average = host.load_average()
|
load_average = host.load_average()
|
||||||
if load_average is not None:
|
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 (%) (1 min)': load_average[0],
|
||||||
'host/load_average (%) (5 min)': load_average[1],
|
'host/load_average (%) (5 min)': load_average[1],
|
||||||
'host/load_average (%) (15 min)': load_average[2],
|
'host/load_average (%) (15 min)': load_average[2],
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
device_identifiers = {}
|
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
|
class _MetricBuffer: # pylint: disable=missing-class-docstring,missing-function-docstring,too-many-instance-attributes
|
||||||
def __init__(
|
def __init__(
|
||||||
self, tag: str, collector: ResourceMetricCollector, prev: _MetricBuffer | None = None
|
self,
|
||||||
|
tag: str,
|
||||||
|
collector: ResourceMetricCollector,
|
||||||
|
prev: _MetricBuffer | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.collector = collector
|
self.collector = collector
|
||||||
self.prev = prev
|
self.prev = prev
|
||||||
|
|
@ -800,9 +805,7 @@ class _MetricBuffer: # pylint: disable=missing-class-docstring,missing-function
|
||||||
if key.endswith('host/running_time (min)/max'):
|
if key.endswith('host/running_time (min)/max'):
|
||||||
metrics[key[:-4]] = metrics[key]
|
metrics[key[:-4]] = metrics[key]
|
||||||
del metrics[key]
|
del metrics[key]
|
||||||
elif key.endswith('host/running_time (min)/mean') or key.endswith(
|
elif key.endswith(('host/running_time (min)/mean', 'host/running_time (min)/min')):
|
||||||
'host/running_time (min)/min'
|
|
||||||
):
|
|
||||||
del metrics[key]
|
del metrics[key]
|
||||||
metrics[f'{self.key_prefix}/duration (s)'] = timer() - self.start_timestamp
|
metrics[f'{self.key_prefix}/duration (s)'] = timer() - self.start_timestamp
|
||||||
metrics[f'{self.key_prefix}/timestamp'] = time.time()
|
metrics[f'{self.key_prefix}/timestamp'] = time.time()
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_indices(
|
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]:
|
) -> list[PhysicalDevice | MigDevice]:
|
||||||
"""Return a list of devices of the given indices.
|
"""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
|
return cuda_devices
|
||||||
|
|
||||||
@staticmethod
|
@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.
|
"""Return a list of CUDA devices of the given CUDA indices.
|
||||||
|
|
||||||
The CUDA ordinal will be enumerate from the ``CUDA_VISIBLE_DEVICES`` environment variable.
|
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:
|
if (index, uuid, bus_id).count(None) != 2:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f'Device(index=None, uuid=None, bus_id=None) takes 1 non-None arguments '
|
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:
|
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):
|
if not isinstance(index, tuple):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f'index must be an integer, or a tuple of two integers, or a valid UUID string, '
|
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 (
|
if not (
|
||||||
len(index) == 2 and isinstance(index[0], int) and isinstance(index[1], int)
|
len(index) == 2 and isinstance(index[0], int) and isinstance(index[1], int)
|
||||||
):
|
):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f'index for MIG device must be a tuple of two integers '
|
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)
|
return super().__new__(MigDevice)
|
||||||
elif uuid is not None and match is not None and match.group('MigMode') is not None:
|
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
|
self._nvml_index = index
|
||||||
try:
|
try:
|
||||||
self._handle = libnvml.nvmlQuery(
|
self._handle = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetHandleByIndex', index, ignore_errors=False
|
'nvmlDeviceGetHandleByIndex',
|
||||||
|
index,
|
||||||
|
ignore_errors=False,
|
||||||
)
|
)
|
||||||
except libnvml.NVMLError_GpuIsLost:
|
except libnvml.NVMLError_GpuIsLost:
|
||||||
self._handle = None
|
self._handle = None
|
||||||
|
|
@ -628,11 +633,15 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
try:
|
try:
|
||||||
if uuid is not None:
|
if uuid is not None:
|
||||||
self._handle = libnvml.nvmlQuery(
|
self._handle = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetHandleByUUID', uuid, ignore_errors=False
|
'nvmlDeviceGetHandleByUUID',
|
||||||
|
uuid,
|
||||||
|
ignore_errors=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._handle = libnvml.nvmlQuery(
|
self._handle = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetHandleByPciBusId', bus_id, ignore_errors=False
|
'nvmlDeviceGetHandleByPciBusId',
|
||||||
|
bus_id,
|
||||||
|
ignore_errors=False,
|
||||||
)
|
)
|
||||||
except libnvml.NVMLError_GpuIsLost:
|
except libnvml.NVMLError_GpuIsLost:
|
||||||
self._handle = None
|
self._handle = None
|
||||||
|
|
@ -655,7 +664,10 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Return a string representation of the device."""
|
"""Return a string representation of the device."""
|
||||||
return '{}(index={}, name="{}", total_memory={})'.format(
|
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__
|
__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)
|
func = getattr(libnvml, 'nvmlDeviceGet' + pascal_case + suffix)
|
||||||
|
|
||||||
@ttl_cache(ttl=1.0)
|
@ttl_cache(ttl=1.0)
|
||||||
def attribute(*args, **kwargs):
|
def attribute(*args: Any, **kwargs: Any) -> Any:
|
||||||
try:
|
try:
|
||||||
return libnvml.nvmlQuery(
|
return libnvml.nvmlQuery(
|
||||||
func, self._handle, *args, **kwargs, ignore_errors=False
|
func,
|
||||||
|
self._handle,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
ignore_errors=False,
|
||||||
)
|
)
|
||||||
except libnvml.NVMLError_NotSupported:
|
except libnvml.NVMLError_NotSupported:
|
||||||
return NA
|
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)
|
self._cuda_index = visible_device_indices.index(self.index)
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
raise RuntimeError(
|
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
|
) from ex
|
||||||
|
|
||||||
return self._cuda_index
|
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:
|
if self._bus_id is NA:
|
||||||
self._bus_id = libnvml.nvmlQuery(
|
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
|
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()
|
memory_info = self.memory_info()
|
||||||
if libnvml.nvmlCheckReturn(memory_info.used, int) and libnvml.nvmlCheckReturn(
|
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 round(100.0 * memory_info.used / memory_info.total, 1)
|
||||||
return NA
|
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)
|
memory_info = libnvml.nvmlQuery('nvmlDeviceGetBAR1MemoryInfo', self.handle)
|
||||||
if libnvml.nvmlCheckReturn(memory_info):
|
if libnvml.nvmlCheckReturn(memory_info):
|
||||||
return MemoryInfo(
|
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)
|
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
|
""" # pylint: disable=line-too-long
|
||||||
memory_info = self.bar1_memory_info()
|
memory_info = self.bar1_memory_info()
|
||||||
if libnvml.nvmlCheckReturn(memory_info.used, int) and libnvml.nvmlCheckReturn(
|
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 round(100.0 * memory_info.used / memory_info.total, 1)
|
||||||
return NA
|
return NA
|
||||||
|
|
@ -1133,12 +1154,16 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
""" # pylint: disable=line-too-long
|
""" # pylint: disable=line-too-long
|
||||||
return ClockInfos(
|
return ClockInfos(
|
||||||
graphics=libnvml.nvmlQuery(
|
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),
|
sm=libnvml.nvmlQuery('nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_SM),
|
||||||
memory=libnvml.nvmlQuery('nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_MEM),
|
memory=libnvml.nvmlQuery('nvmlDeviceGetClockInfo', self.handle, libnvml.NVML_CLOCK_MEM),
|
||||||
video=libnvml.nvmlQuery(
|
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():
|
for name, clock in clock_infos.items():
|
||||||
if clock is NA:
|
if clock is NA:
|
||||||
clock_type = getattr(
|
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 = libnvml.nvmlQuery('nvmlDeviceGetMaxClockInfo', self.handle, clock_type)
|
||||||
clock_infos[name] = clock
|
clock_infos[name] = clock
|
||||||
|
|
@ -1319,7 +1345,9 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=temperature.gpu
|
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=temperature.gpu
|
||||||
"""
|
"""
|
||||||
return libnvml.nvmlQuery(
|
return libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetTemperature', self.handle, libnvml.NVML_TEMPERATURE_GPU
|
'nvmlDeviceGetTemperature',
|
||||||
|
self.handle,
|
||||||
|
libnvml.NVML_TEMPERATURE_GPU,
|
||||||
)
|
)
|
||||||
|
|
||||||
@memoize_when_activated
|
@memoize_when_activated
|
||||||
|
|
@ -1391,7 +1419,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=display_active
|
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=display_active
|
||||||
""" # pylint: disable=line-too-long
|
""" # pylint: disable=line-too-long
|
||||||
return {0: 'Disabled', 1: 'Enabled'}.get(
|
return {0: 'Disabled', 1: 'Enabled'}.get(
|
||||||
libnvml.nvmlQuery('nvmlDeviceGetDisplayActive', self.handle), NA
|
libnvml.nvmlQuery('nvmlDeviceGetDisplayActive', self.handle),
|
||||||
|
NA,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ttl_cache(ttl=60.0)
|
@ttl_cache(ttl=60.0)
|
||||||
|
|
@ -1412,7 +1441,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=display_mode
|
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=display_mode
|
||||||
""" # pylint: disable=line-too-long
|
""" # pylint: disable=line-too-long
|
||||||
return {0: 'Disabled', 1: 'Enabled'}.get(
|
return {0: 'Disabled', 1: 'Enabled'}.get(
|
||||||
libnvml.nvmlQuery('nvmlDeviceGetDisplayMode', self.handle), NA
|
libnvml.nvmlQuery('nvmlDeviceGetDisplayMode', self.handle),
|
||||||
|
NA,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ttl_cache(ttl=60.0)
|
@ttl_cache(ttl=60.0)
|
||||||
|
|
@ -1437,7 +1467,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=driver_model.current
|
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=driver_model.current
|
||||||
"""
|
"""
|
||||||
return {libnvml.NVML_DRIVER_WDDM: 'WDDM', libnvml.NVML_DRIVER_WDM: 'WDM'}.get(
|
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
|
driver_model = current_driver_model
|
||||||
|
|
@ -1462,7 +1493,8 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
|
||||||
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=persistence_mode
|
nvidia-smi --id=<IDENTIFIER> --format=csv,noheader,nounits --query-gpu=persistence_mode
|
||||||
""" # pylint: disable=line-too-long
|
""" # pylint: disable=line-too-long
|
||||||
return {0: 'Disabled', 1: 'Enabled'}.get(
|
return {0: 'Disabled', 1: 'Enabled'}.get(
|
||||||
libnvml.nvmlQuery('nvmlDeviceGetPersistenceMode', self.handle), NA
|
libnvml.nvmlQuery('nvmlDeviceGetPersistenceMode', self.handle),
|
||||||
|
NA,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ttl_cache(ttl=5.0)
|
@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:
|
if self._cuda_compute_capability is None:
|
||||||
self._cuda_compute_capability = libnvml.nvmlQuery(
|
self._cuda_compute_capability = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetCudaComputeCapability', self.handle
|
'nvmlDeviceGetCudaComputeCapability',
|
||||||
|
self.handle,
|
||||||
)
|
)
|
||||||
return self._cuda_compute_capability
|
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():
|
if self.is_mig_device():
|
||||||
return NA
|
return NA
|
||||||
|
|
||||||
mig_mode = libnvml.nvmlQuery(
|
mig_mode, *_ = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetMigMode', self.handle, default=(NA, NA), ignore_function_not_found=True
|
'nvmlDeviceGetMigMode',
|
||||||
)[0]
|
self.handle,
|
||||||
|
default=(NA, NA),
|
||||||
|
ignore_function_not_found=True,
|
||||||
|
)
|
||||||
return {0: 'Disabled', 1: 'Enabled'}.get(mig_mode, NA)
|
return {0: 'Disabled', 1: 'Enabled'}.get(mig_mode, NA)
|
||||||
|
|
||||||
def is_mig_mode_enabled(self) -> bool:
|
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:
|
if len(processes) > 0:
|
||||||
samples = libnvml.nvmlQuery(
|
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:
|
for s in samples:
|
||||||
try:
|
try:
|
||||||
processes[s.pid].set_gpu_utilization(s.smUtil, s.memUtil, s.encUtil, s.decUtil)
|
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)
|
# Modified from psutil (https://github.com/giampaolo/psutil)
|
||||||
@contextlib.contextmanager
|
@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.
|
"""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
|
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.
|
This method will return 0 if the device does not support MIG mode.
|
||||||
"""
|
"""
|
||||||
return libnvml.nvmlQuery(
|
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)
|
@ttl_cache(ttl=60.0)
|
||||||
|
|
@ -1859,7 +1901,8 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_indices( # pylint: disable=signature-differs
|
def from_indices( # pylint: disable=signature-differs
|
||||||
cls, indices: Iterable[tuple[int, int]]
|
cls,
|
||||||
|
indices: Iterable[tuple[int, int]],
|
||||||
) -> list[MigDevice]:
|
) -> list[MigDevice]:
|
||||||
"""Return a list of MIG devices of the given indices.
|
"""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
|
# pylint: disable-next=super-init-not-called
|
||||||
def __init__(
|
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:
|
) -> None:
|
||||||
"""Initialize the instance created by :meth:`__new__()`.
|
"""Initialize the instance created by :meth:`__new__()`.
|
||||||
|
|
||||||
|
|
@ -1941,10 +1987,14 @@ class MigDevice(Device): # pylint: disable=too-many-instance-attributes
|
||||||
else:
|
else:
|
||||||
self._handle = libnvml.nvmlQuery('nvmlDeviceGetHandleByUUID', uuid, ignore_errors=False)
|
self._handle = libnvml.nvmlQuery('nvmlDeviceGetHandleByUUID', uuid, ignore_errors=False)
|
||||||
parent_handle = libnvml.nvmlQuery(
|
parent_handle = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetDeviceHandleFromMigDeviceHandle', self.handle, ignore_errors=False
|
'nvmlDeviceGetDeviceHandleFromMigDeviceHandle',
|
||||||
|
self.handle,
|
||||||
|
ignore_errors=False,
|
||||||
)
|
)
|
||||||
parent_index = libnvml.nvmlQuery(
|
parent_index = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetIndex', parent_handle, ignore_errors=False
|
'nvmlDeviceGetIndex',
|
||||||
|
parent_handle,
|
||||||
|
ignore_errors=False,
|
||||||
)
|
)
|
||||||
self._parent = PhysicalDevice(index=parent_index)
|
self._parent = PhysicalDevice(index=parent_index)
|
||||||
for mig_device in self.parent.mig_devices():
|
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:
|
if self._gpu_instance_id is NA:
|
||||||
self._gpu_instance_id = libnvml.nvmlQuery(
|
self._gpu_instance_id = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetGpuInstanceId', self.handle, default=0xFFFFFFFF
|
'nvmlDeviceGetGpuInstanceId',
|
||||||
|
self.handle,
|
||||||
|
default=0xFFFFFFFF,
|
||||||
)
|
)
|
||||||
if self._gpu_instance_id == 0xFFFFFFFF:
|
if self._gpu_instance_id == 0xFFFFFFFF:
|
||||||
self._gpu_instance_id = NA
|
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:
|
if self._compute_instance_id is NA:
|
||||||
self._compute_instance_id = libnvml.nvmlQuery(
|
self._compute_instance_id = libnvml.nvmlQuery(
|
||||||
'nvmlDeviceGetComputeInstanceId', self.handle, default=0xFFFFFFFF
|
'nvmlDeviceGetComputeInstanceId',
|
||||||
|
self.handle,
|
||||||
|
default=0xFFFFFFFF,
|
||||||
)
|
)
|
||||||
if self._compute_instance_id == 0xFFFFFFFF:
|
if self._compute_instance_id == 0xFFFFFFFF:
|
||||||
self._compute_instance_id = NA
|
self._compute_instance_id = NA
|
||||||
|
|
@ -2114,7 +2168,10 @@ class CudaDevice(Device):
|
||||||
return cls.from_indices()
|
return cls.from_indices()
|
||||||
|
|
||||||
@classmethod
|
@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.
|
"""Return a list of CUDA devices of the given CUDA indices.
|
||||||
|
|
||||||
The CUDA ordinal will be enumerate from the ``CUDA_VISIBLE_DEVICES`` environment variable.
|
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()
|
for device in PhysicalDevice.all()
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
return _PHYSICAL_DEVICE_ATTRS
|
return _PHYSICAL_DEVICE_ATTRS
|
||||||
|
|
||||||
|
|
@ -2598,7 +2655,9 @@ def _parse_cuda_visible_devices_to_uuids(
|
||||||
|
|
||||||
|
|
||||||
def _cuda_visible_devices_parser(
|
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:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
if cuda_visible_devices is not None:
|
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))))
|
uuids = list(map(libcuda.cuDeviceGetUuid, map(libcuda.cuDeviceGet, range(count))))
|
||||||
queue.put(uuids)
|
queue.put(uuids)
|
||||||
return
|
return
|
||||||
except Exception as ex: # pylint: disable=broad-except
|
except Exception as ex: # noqa: BLE001 # pylint: disable=broad-except
|
||||||
queue.put(ex)
|
queue.put(ex)
|
||||||
if verbose:
|
if verbose:
|
||||||
raise ex
|
raise ex
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@
|
||||||
utilization (CPU, memory, disks, network, sensors) in Python.
|
utilization (CPU, memory, disks, network, sensors) in Python.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os as _os
|
import os as _os
|
||||||
|
from typing import Callable as _Callable
|
||||||
|
|
||||||
import psutil as _psutil
|
import psutil as _psutil
|
||||||
from cachetools.func import ttl_cache as _ttl_cache
|
from cachetools.func import ttl_cache as _ttl_cache
|
||||||
|
|
@ -49,30 +52,32 @@ swap_memory = _ttl_cache(ttl=0.25)(_psutil.swap_memory)
|
||||||
|
|
||||||
|
|
||||||
try:
|
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."""
|
load_average.__doc__ = """Get the system load average."""
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
||||||
def load_average():
|
def load_average() -> None:
|
||||||
"""Get the system load average."""
|
"""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``."""
|
"""The percentage usage of virtual memory, calculated as ``(total - available) / total * 100``."""
|
||||||
return virtual_memory().percent
|
return virtual_memory().percent
|
||||||
|
|
||||||
|
|
||||||
def swap_percent():
|
def swap_percent() -> float:
|
||||||
"""The percentage usage of virtual memory, calculated as ``used / total * 100``."""
|
"""The percentage usage of virtual memory, calculated as ``used / total * 100``."""
|
||||||
return swap_memory().percent
|
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."""
|
"""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."""
|
"""Obtain a ``{ppid: [pid, ...], ...}`` dict for all running processes in one shot."""
|
||||||
from collections import defaultdict # pylint: disable=import-outside-toplevel
|
from collections import defaultdict # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
|
@ -91,6 +96,3 @@ else:
|
||||||
WSL = None
|
WSL = None
|
||||||
WINDOWS_SUBSYSTEM_FOR_LINUX = WSL
|
WINDOWS_SUBSYSTEM_FOR_LINUX = WSL
|
||||||
"""The Linux distribution name of the Windows Subsystem for Linux."""
|
"""The Linux distribution name of the Windows Subsystem for Linux."""
|
||||||
|
|
||||||
|
|
||||||
del _os, _ttl_cache
|
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ class CUDAError(Exception):
|
||||||
try:
|
try:
|
||||||
if self.value not in CUDAError._errcode_to_string:
|
if self.value not in CUDAError._errcode_to_string:
|
||||||
CUDAError._errcode_to_string[self.value] = '{}.'.format(
|
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:
|
if self.value not in CUDAError._errcode_to_name:
|
||||||
CUDAError._errcode_to_name[self.value] = cuGetErrorName(self.value)
|
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 gen_new(value):
|
||||||
def new(cls):
|
def new(cls):
|
||||||
obj = CUDAError.__new__(cls, value)
|
return CUDAError.__new__(cls, value)
|
||||||
return obj
|
|
||||||
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
|
@ -314,9 +313,7 @@ def _extract_cuda_errors_as_classes() -> None:
|
||||||
err_val,
|
err_val,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
new_error_class.__doc__ = 'CUDA Error with code :data:`{}` ({})'.format(
|
new_error_class.__doc__ = f'CUDA Error with code :data:`{err_name}` ({err_val})'
|
||||||
err_name, err_val
|
|
||||||
)
|
|
||||||
setattr(this_module, class_name, new_error_class)
|
setattr(this_module, class_name, new_error_class)
|
||||||
CUDAError._value_class_mapping[err_val] = new_error_class
|
CUDAError._value_class_mapping[err_val] = new_error_class
|
||||||
CUDAError._errcode_to_name[err_val] = err_name
|
CUDAError._errcode_to_name[err_val] = err_name
|
||||||
|
|
@ -395,7 +392,7 @@ def __LoadCudaLibrary() -> None:
|
||||||
]
|
]
|
||||||
# Also add libraries with version suffix `.1`
|
# Also add libraries with version suffix `.1`
|
||||||
lib_filenames = list(
|
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':
|
elif system == 'Windows':
|
||||||
bits = _platform.architecture()[0].replace('bit', '') # e.g., '64' or '32'
|
bits = _platform.architecture()[0].replace('bit', '') # e.g., '64' or '32'
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ class cudaError(Exception):
|
||||||
try:
|
try:
|
||||||
if self.value not in cudaError._errcode_to_string:
|
if self.value not in cudaError._errcode_to_string:
|
||||||
cudaError._errcode_to_string[self.value] = '{}.'.format(
|
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:
|
if self.value not in cudaError._errcode_to_name:
|
||||||
cudaError._errcode_to_name[self.value] = cudaGetErrorName(self.value)
|
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 gen_new(value):
|
||||||
def new(cls):
|
def new(cls):
|
||||||
obj = cudaError.__new__(cls, value)
|
return cudaError.__new__(cls, value)
|
||||||
return obj
|
|
||||||
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
|
@ -368,9 +367,7 @@ def _extract_cuda_errors_as_classes() -> None:
|
||||||
err_val,
|
err_val,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
new_error_class.__doc__ = 'CUDA Error with code :data:`{}` ({})'.format(
|
new_error_class.__doc__ = f'CUDA Error with code :data:`{err_name}` ({err_val})'
|
||||||
err_name, err_val
|
|
||||||
)
|
|
||||||
setattr(this_module, class_name, new_error_class)
|
setattr(this_module, class_name, new_error_class)
|
||||||
cudaError._value_class_mapping[err_val] = new_error_class
|
cudaError._value_class_mapping[err_val] = new_error_class
|
||||||
cudaError._errcode_to_name[err_val] = err_name
|
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, f'lib{bits}', lib_filename),
|
||||||
_os.path.join(cuda_path, 'lib', lib_filename),
|
_os.path.join(cuda_path, 'lib', lib_filename),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
candidate_dirs = _os.getenv('PATH', '').split(_os.path.pathsep)
|
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, 'bin'),
|
||||||
_os.path.join(cuda_path, f'lib{bits}'),
|
_os.path.join(cuda_path, f'lib{bits}'),
|
||||||
_os.path.join(cuda_path, 'lib'),
|
_os.path.join(cuda_path, 'lib'),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
for candidate_dir in candidate_dirs:
|
for candidate_dir in candidate_dirs:
|
||||||
candidate_paths.extend(
|
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
|
# Normalize paths and remove duplicates
|
||||||
candidate_paths = list(
|
candidate_paths = list(
|
||||||
dict.fromkeys(
|
dict.fromkeys(
|
||||||
_os.path.normpath(_os.path.normcase(p)) for p in candidate_paths
|
_os.path.normpath(_os.path.normcase(p)) for p in candidate_paths
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
for lib_filename in candidate_paths:
|
for lib_filename in candidate_paths:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ __all__ = [ # will be updated in below
|
||||||
if not callable(getattr(_pynvml, 'nvmlInitWithFlags', None)):
|
if not callable(getattr(_pynvml, 'nvmlInitWithFlags', None)):
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
'Your installed package `nvidia-ml-py` is corrupted. Please reinstall package '
|
'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():
|
for _name, _attr in _vars_pynvml.items():
|
||||||
if _name in ('nvmlInit', 'nvmlInitWithFlags', 'nvmlShutdown'):
|
if _name in ('nvmlInit', 'nvmlInitWithFlags', 'nvmlShutdown'):
|
||||||
continue
|
continue
|
||||||
if _name.startswith('NVML_ERROR_') or _name.startswith('NVMLError_'):
|
if _name.startswith(('NVML_ERROR_', 'NVMLError_')):
|
||||||
__all__.append(_name)
|
__all__.append(_name)
|
||||||
if _name.startswith('NVML_ERROR_'):
|
if _name.startswith('NVML_ERROR_'):
|
||||||
_errcode_to_name[_attr] = _name
|
_errcode_to_name[_attr] = _name
|
||||||
|
|
@ -101,7 +101,9 @@ _errcode = _reason = _subclass = None
|
||||||
for _errcode, _reason in _errcode_to_string.items():
|
for _errcode, _reason in _errcode_to_string.items():
|
||||||
_subclass = nvmlExceptionClass(_errcode)
|
_subclass = nvmlExceptionClass(_errcode)
|
||||||
_subclass.__doc__ = '{}. Code: :data:`{}` ({})'.format(
|
_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
|
# 4. Add undocumented constants into module docstring
|
||||||
|
|
@ -184,7 +186,7 @@ except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
if not LOGGER.hasHandlers() and LOGGER.isEnabledFor(_logging.DEBUG):
|
if not LOGGER.hasHandlers() and LOGGER.isEnabledFor(_logging.DEBUG):
|
||||||
_formatter = _logging.Formatter(
|
_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 = _logging.StreamHandler()
|
||||||
_stream_handler.setFormatter(_formatter)
|
_stream_handler.setFormatter(_formatter)
|
||||||
|
|
@ -332,11 +334,11 @@ def nvmlShutdown() -> None: # pylint: disable=function-redefined
|
||||||
|
|
||||||
def nvmlQuery(
|
def nvmlQuery(
|
||||||
func: _Callable[..., _Any] | str,
|
func: _Callable[..., _Any] | str,
|
||||||
*args,
|
*args: _Any,
|
||||||
default: _Any = NA,
|
default: _Any = NA,
|
||||||
ignore_errors: bool = True,
|
ignore_errors: bool = True,
|
||||||
ignore_function_not_found: bool = False,
|
ignore_function_not_found: bool = False,
|
||||||
**kwargs,
|
**kwargs: _Any,
|
||||||
) -> _Any:
|
) -> _Any:
|
||||||
"""Call a function with the given arguments from NVML.
|
"""Call a function with the given arguments from NVML.
|
||||||
|
|
||||||
|
|
@ -416,7 +418,10 @@ def nvmlQuery(
|
||||||
return retval
|
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."""
|
"""Check whether the return value is not :const:`nvitop.NA` and is one of the given types."""
|
||||||
if types is None:
|
if types is None:
|
||||||
return retval != NA
|
return retval != NA
|
||||||
|
|
@ -433,7 +438,7 @@ def __patch_backward_compatibility_layers() -> None:
|
||||||
if __patched_backward_compatibility_layers:
|
if __patched_backward_compatibility_layers:
|
||||||
return
|
return
|
||||||
|
|
||||||
function_name_mapping_lock = _threading.Lock() # noqa: F405
|
function_name_mapping_lock = _threading.Lock()
|
||||||
function_name_mapping = {}
|
function_name_mapping = {}
|
||||||
|
|
||||||
def function_mapping_update(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
|
_pynvml.__dict__.update( # need to use module.__dict__.__setitem__ because module.__setattr__ will not work
|
||||||
_nvmlGetFunctionPointer=wrapper(
|
_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):
|
def patch_function_pointers_when_fail(names, callback):
|
||||||
|
|
@ -514,8 +519,12 @@ def __patch_backward_compatibility_layers() -> None:
|
||||||
}
|
}
|
||||||
|
|
||||||
def patch_process_info_callback(
|
def patch_process_info_callback(
|
||||||
name, names, exception, pynvml, modself
|
name,
|
||||||
): # pylint: disable=unused-argument
|
names, # pylint: disable=unused-argument
|
||||||
|
exception,
|
||||||
|
pynvml,
|
||||||
|
modself,
|
||||||
|
):
|
||||||
if name in nvmlDeviceGetRunningProcesses_v3_v2:
|
if name in nvmlDeviceGetRunningProcesses_v3_v2:
|
||||||
mapping = nvmlDeviceGetRunningProcesses_v3_v2
|
mapping = nvmlDeviceGetRunningProcesses_v3_v2
|
||||||
struct_type = c_nvmlProcessInfo_v2_t
|
struct_type = c_nvmlProcessInfo_v2_t
|
||||||
|
|
@ -533,7 +542,8 @@ def __patch_backward_compatibility_layers() -> None:
|
||||||
for old_name, mapped_name in mapping.items():
|
for old_name, mapped_name in mapping.items():
|
||||||
LOGGER.debug(' Map NVML function `%s` to `%s`', old_name, mapped_name)
|
LOGGER.debug(' Map NVML function `%s` to `%s`', old_name, mapped_name)
|
||||||
LOGGER.debug(
|
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]
|
return mapping[name]
|
||||||
|
|
||||||
|
|
@ -547,9 +557,9 @@ def __patch_backward_compatibility_layers() -> None:
|
||||||
names=set(nvmlDeviceGetRunningProcesses_v2_v1),
|
names=set(nvmlDeviceGetRunningProcesses_v2_v1),
|
||||||
callback=patch_process_info_callback,
|
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
|
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
|
_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.
|
"""Retrieve the amount of used, free, reserved and total memory available on the device, in bytes.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
@ -620,7 +631,8 @@ def nvmlDeviceGetMemoryInfo(handle): # pylint: disable=function-redefined,too-m
|
||||||
with __lock:
|
with __lock:
|
||||||
_pynvml_get_memory_info_v2_available = False
|
_pynvml_get_memory_info_v2_available = False
|
||||||
LOGGER.debug(
|
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:
|
else:
|
||||||
# driver ✔ pynvml ? user ✘
|
# driver ✔ pynvml ? user ✘
|
||||||
|
|
@ -634,7 +646,8 @@ def nvmlDeviceGetMemoryInfo(handle): # pylint: disable=function-redefined,too-m
|
||||||
with __lock:
|
with __lock:
|
||||||
_pynvml_get_memory_info_v2_available = False
|
_pynvml_get_memory_info_v2_available = False
|
||||||
LOGGER.debug(
|
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:
|
else:
|
||||||
# driver ✔ pynvml ✔
|
# 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 '
|
'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. '
|
'memory info version 2 is not available due to the legacy dependencies. '
|
||||||
'Please consider upgrading your `nvidia-ml-py` package by running '
|
'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:
|
elif _pynvml_memory_v2_available:
|
||||||
# driver ✘ pynvml ?
|
# driver ✘ pynvml ?
|
||||||
LOGGER.debug(
|
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:
|
else:
|
||||||
# driver ✘ pynvml ✘
|
# driver ✘ pynvml ✘
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
'NVML constant `nvmlMemory_v2` not found in package `nvidia-ml-py`, and '
|
'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. '
|
'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:
|
elif _pynvml_get_memory_info_v2_available:
|
||||||
|
|
@ -697,12 +710,8 @@ class _CustomModule(_ModuleType):
|
||||||
_lazy_init()
|
_lazy_init()
|
||||||
return self
|
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."""
|
"""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:
|
try:
|
||||||
nvmlShutdown()
|
nvmlShutdown()
|
||||||
except NVMLError:
|
except NVMLError:
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ if host.POSIX:
|
||||||
if "'" not in s and '\n' not in s:
|
if "'" not in s and '\n' not in s:
|
||||||
return f"'{s}'"
|
return f"'{s}'"
|
||||||
return '"{}"'.format(
|
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:
|
elif host.WINDOWS:
|
||||||
|
|
@ -77,7 +77,7 @@ elif host.WINDOWS:
|
||||||
if '"' not in s:
|
if '"' not in s:
|
||||||
return f'"{s}"'
|
return f'"{s}"'
|
||||||
return '"{}"'.format(
|
return '"{}"'.format(
|
||||||
s.replace('^', '^^').replace('"', '^"').replace('%', '^%').replace('\n', r'\n')
|
s.replace('^', '^^').replace('"', '^"').replace('%', '^%').replace('\n', r'\n'),
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -113,7 +113,7 @@ def auto_garbage_clean(
|
||||||
|
|
||||||
def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
|
def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapped(self: GpuProcess, *args, **kwargs):
|
def wrapped(self: GpuProcess, *args: Any, **kwargs: Any) -> Any:
|
||||||
try:
|
try:
|
||||||
return func(self, *args, **kwargs)
|
return func(self, *args, **kwargs)
|
||||||
except host.PsutilError as ex:
|
except host.PsutilError as ex:
|
||||||
|
|
@ -127,9 +127,8 @@ def auto_garbage_clean(
|
||||||
del HostProcess.INSTANCES[self.pid]
|
del HostProcess.INSTANCES[self.pid]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
if fallback is _RAISE or not getattr(
|
# See also `GpuProcess.failsafe`
|
||||||
_USE_FALLBACK_WHEN_RAISE, 'value', False # see also `GpuProcess.failsafe`
|
if fallback is _RAISE or not getattr(_USE_FALLBACK_WHEN_RAISE, 'value', False):
|
||||||
):
|
|
||||||
raise ex
|
raise ex
|
||||||
if isinstance(fallback, tuple):
|
if isinstance(fallback, tuple):
|
||||||
if isinstance(ex, host.AccessDenied) and fallback == ('No Such Process',):
|
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)]
|
return [HostProcess(child.pid) for child in super().children(recursive)]
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@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.
|
"""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
|
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)
|
self.running_time.cache_deactivate(self)
|
||||||
|
|
||||||
def as_snapshot(
|
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:
|
) -> Snapshot:
|
||||||
"""Return a onetime snapshot of the process."""
|
"""Return a onetime snapshot of the process."""
|
||||||
with self.oneshot():
|
with self.oneshot():
|
||||||
|
|
@ -912,7 +913,7 @@ class GpuProcess: # pylint: disable=too-many-instance-attributes,too-many-publi
|
||||||
def host_snapshot(self) -> Snapshot:
|
def host_snapshot(self) -> Snapshot:
|
||||||
"""Return a onetime snapshot of the host process."""
|
"""Return a onetime snapshot of the host process."""
|
||||||
with self.host.oneshot():
|
with self.host.oneshot():
|
||||||
host_snapshot = Snapshot(
|
return Snapshot(
|
||||||
real=self.host,
|
real=self.host,
|
||||||
is_running=self.is_running(),
|
is_running=self.is_running(),
|
||||||
status=self.status(),
|
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(),
|
running_time_in_seconds=self.running_time_in_seconds(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return host_snapshot
|
|
||||||
|
|
||||||
@auto_garbage_clean(fallback=_RAISE)
|
@auto_garbage_clean(fallback=_RAISE)
|
||||||
def as_snapshot(
|
def as_snapshot(
|
||||||
self, *, host_process_snapshot_cache: dict[int, Snapshot] | None = None
|
self,
|
||||||
|
*,
|
||||||
|
host_process_snapshot_cache: dict[int, Snapshot] | None = None,
|
||||||
) -> Snapshot:
|
) -> Snapshot:
|
||||||
"""Return a onetime snapshot of the process on the GPU device.
|
"""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
|
@classmethod
|
||||||
def take_snapshots( # batched version of `as_snapshot`
|
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]:
|
) -> list[Snapshot]:
|
||||||
"""Take snapshots for a list of :class:`GpuProcess` instances.
|
"""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 = {}
|
cache = {}
|
||||||
context = cls.failsafe if failsafe else contextlib.nullcontext
|
context = cls.failsafe if failsafe else contextlib.nullcontext
|
||||||
with context():
|
with context():
|
||||||
snapshots = [
|
return [
|
||||||
process.as_snapshot(host_process_snapshot_cache=cache) for process in gpu_processes
|
process.as_snapshot(host_process_snapshot_cache=cache) for process in gpu_processes
|
||||||
]
|
]
|
||||||
|
|
||||||
return snapshots
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def failsafe(cls):
|
def failsafe(cls) -> contextlib.AbstractContextManager:
|
||||||
"""A context manager that enables fallback values for methods that fail.
|
"""A context manager that enables fallback values for methods that fail.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
|
||||||
|
|
@ -491,7 +491,8 @@ SIZE_UNITS = {
|
||||||
}
|
}
|
||||||
"""Units of storage and memory measurements."""
|
"""Units of storage and memory measurements."""
|
||||||
SIZE_PATTERN = re.compile(
|
SIZE_PATTERN = re.compile(
|
||||||
r'^\s*\+?\s*(?P<size>\d+(?:\.\d+)?)\s*(?P<unit>[KMGTP]i?B?|B?)\s*$', flags=re.IGNORECASE
|
r'^\s*\+?\s*(?P<size>\d+(?:\.\d+)?)\s*(?P<unit>[KMGTP]i?B?|B?)\s*$',
|
||||||
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
"""The regex pattern for human readable size."""
|
"""The regex pattern for human readable size."""
|
||||||
|
|
||||||
|
|
@ -607,7 +608,7 @@ class Snapshot:
|
||||||
Missing attributes will be automatically fetched from the original object.
|
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."""
|
"""Initialize a new :class:`Snapshot` object with the given attributes."""
|
||||||
self.real = real
|
self.real = real
|
||||||
self.timestamp = time.time()
|
self.timestamp = time.time()
|
||||||
|
|
@ -626,7 +627,9 @@ class Snapshot:
|
||||||
keyval = keyval.replace('\n', '\n ') # extra indentation for nested snapshots
|
keyval = keyval.replace('\n', '\n ') # extra indentation for nested snapshots
|
||||||
keyvals.append(keyval)
|
keyvals.append(keyval)
|
||||||
return '{}{}(\n {},\n)'.format(
|
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__
|
__repr__ = __str__
|
||||||
|
|
@ -686,7 +689,7 @@ def memoize_when_activated(method: Callable[[Any], Any]) -> Callable[[Any], Any]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@functools.wraps(method)
|
@functools.wraps(method)
|
||||||
def wrapped(self):
|
def wrapped(self): # noqa: ANN001,ANN202
|
||||||
try:
|
try:
|
||||||
# case 1: we previously entered oneshot() ctx
|
# case 1: we previously entered oneshot() ctx
|
||||||
ret = self._cache[method] # pylint: disable=protected-access
|
ret = self._cache[method] # pylint: disable=protected-access
|
||||||
|
|
@ -705,7 +708,7 @@ def memoize_when_activated(method: Callable[[Any], Any]) -> Callable[[Any], Any]
|
||||||
pass
|
pass
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def cache_activate(self):
|
def cache_activate(self): # noqa: ANN001,ANN202
|
||||||
"""Activate cache.
|
"""Activate cache.
|
||||||
|
|
||||||
Expects an instance. Cache will be stored as a "_cache" instance attribute.
|
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'):
|
if not hasattr(self, '_cache'):
|
||||||
self._cache = {} # pylint: disable=protected-access
|
self._cache = {} # pylint: disable=protected-access
|
||||||
|
|
||||||
def cache_deactivate(self):
|
def cache_deactivate(self): # noqa: ANN001,ANN202
|
||||||
"""Deactivate and clear cache."""
|
"""Deactivate and clear cache."""
|
||||||
try:
|
try:
|
||||||
del self._cache # pylint: disable=protected-access
|
del self._cache # pylint: disable=protected-access
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
|
||||||
libnvml.nvmlInit()
|
libnvml.nvmlInit()
|
||||||
except libnvml.NVMLError as ex:
|
except libnvml.NVMLError as ex:
|
||||||
raise ValueError(
|
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
|
) from ex
|
||||||
|
|
||||||
if isinstance(gpus, (list, tuple)):
|
if isinstance(gpus, (list, tuple)):
|
||||||
|
|
@ -126,7 +126,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
|
||||||
except (libnvml.NVMLError, RuntimeError) as ex:
|
except (libnvml.NVMLError, RuntimeError) as ex:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'Cannot use GpuStatsLogger callback because devices unavailable. '
|
f'Cannot use GpuStatsLogger callback because devices unavailable. '
|
||||||
f'Received: `gpus={gpu_ids}`'
|
f'Received: `gpus={gpu_ids}`',
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
self._memory_utilization = memory_utilization
|
self._memory_utilization = memory_utilization
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class GpuStatsLogger(Callback): # pylint: disable=too-many-instance-attributes
|
||||||
libnvml.nvmlInit()
|
libnvml.nvmlInit()
|
||||||
except libnvml.NVMLError as ex:
|
except libnvml.NVMLError as ex:
|
||||||
raise MisconfigurationException(
|
raise MisconfigurationException(
|
||||||
'Cannot use GpuStatsLogger callback because NVIDIA driver is not installed.'
|
'Cannot use GpuStatsLogger callback because NVIDIA driver is not installed.',
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
self._memory_utilization = memory_utilization
|
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:
|
def on_train_start(self, trainer, pl_module) -> None:
|
||||||
if not trainer.logger:
|
if not trainer.logger:
|
||||||
raise MisconfigurationException(
|
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':
|
if trainer.strategy.root_device.type != 'cuda':
|
||||||
raise MisconfigurationException(
|
raise MisconfigurationException(
|
||||||
f'You are using GpuStatsLogger but are not running on GPU. '
|
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
|
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:
|
except (libnvml.NVMLError, RuntimeError) as ex:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'Cannot use GpuStatsLogger callback because devices unavailable. '
|
f'Cannot use GpuStatsLogger callback because devices unavailable. '
|
||||||
f'Received: `gpus={device_ids}`'
|
f'Received: `gpus={device_ids}`',
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
def on_train_epoch_start(self, trainer, pl_module) -> None:
|
def on_train_epoch_start(self, trainer, pl_module) -> None:
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ from nvitop.version import __version__
|
||||||
|
|
||||||
TTY = sys.stdin.isatty() and sys.stdout.isatty()
|
TTY = sys.stdin.isatty() and sys.stdout.isatty()
|
||||||
NVITOP_MONITOR_MODE = set(
|
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:
|
def parse_arguments() -> argparse.Namespace:
|
||||||
"""Parse command-line arguments for ``nvtiop``."""
|
"""Parse command-line arguments for ``nvtiop``."""
|
||||||
coloring_rules = '{} < th1 %% <= {} < th2 %% <= {}'.format(
|
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:
|
def posint(argstring: str) -> int:
|
||||||
|
|
@ -59,7 +64,11 @@ def parse_arguments() -> argparse.Namespace:
|
||||||
|
|
||||||
mode = parser.add_mutually_exclusive_group()
|
mode = parser.add_mutually_exclusive_group()
|
||||||
mode.add_argument(
|
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(
|
mode.add_argument(
|
||||||
'--monitor',
|
'--monitor',
|
||||||
|
|
@ -354,7 +363,8 @@ def main() -> None:
|
||||||
grandparent = parent.parent() if parent is not None else None
|
grandparent = parent.parent() if parent is not None else None
|
||||||
if grandparent is not None and parent.name() == 'sh' and grandparent.name() == 'watch':
|
if grandparent is not None and parent.name() == 'sh' and grandparent.name() == 'watch':
|
||||||
messages.append(
|
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()
|
ui.print()
|
||||||
|
|
@ -364,7 +374,7 @@ def main() -> None:
|
||||||
unknown_function_messages = [
|
unknown_function_messages = [
|
||||||
'ERROR: Some FunctionNotFound errors occurred while calling:'
|
'ERROR: Some FunctionNotFound errors occurred while calling:'
|
||||||
if len(libnvml.UNKNOWN_FUNCTIONS) > 1
|
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(
|
unknown_function_messages.extend(
|
||||||
f' nvmlQuery({func.__name__!r}, *args, **kwargs)'
|
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'
|
'You can check the release history of `nvidia-ml-py` and install the compatible version manually.\n'
|
||||||
'See {} for more information.'
|
'See {} for more information.'
|
||||||
).format(
|
).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)
|
message = '\n'.join(unknown_function_messages)
|
||||||
if (
|
if (
|
||||||
|
|
@ -393,7 +403,7 @@ def main() -> None:
|
||||||
'',
|
'',
|
||||||
' pip3 install "nvitop[cuda10]"',
|
' pip3 install "nvitop[cuda10]"',
|
||||||
'',
|
'',
|
||||||
)
|
),
|
||||||
).replace('@VERSION@', Device.driver_version())
|
).replace('@VERSION@', Device.driver_version())
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
|
|
@ -409,7 +419,7 @@ def main() -> None:
|
||||||
' pip3 install --upgrade pipx',
|
' pip3 install --upgrade pipx',
|
||||||
' pipx install nvitop',
|
' pipx install nvitop',
|
||||||
'',
|
'',
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
|
|
@ -422,7 +432,7 @@ def main() -> None:
|
||||||
'',
|
'',
|
||||||
' pip3 install --upgrade nvitop nvidia-ml-py',
|
' pip3 install --upgrade nvitop nvidia-ml-py',
|
||||||
'',
|
'',
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
|
|
@ -430,15 +440,21 @@ def main() -> None:
|
||||||
for message in messages:
|
for message in messages:
|
||||||
if message.startswith('ERROR:'):
|
if message.startswith('ERROR:'):
|
||||||
message = message.replace(
|
message = message.replace(
|
||||||
'ERROR:', colored('ERROR:', color='red', attrs=('bold',)), 1
|
'ERROR:',
|
||||||
|
colored('ERROR:', color='red', attrs=('bold',)),
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
elif message.startswith('WARNING:'):
|
elif message.startswith('WARNING:'):
|
||||||
message = message.replace(
|
message = message.replace(
|
||||||
'WARNING:', colored('WARNING:', color='yellow', attrs=('bold',)), 1
|
'WARNING:',
|
||||||
|
colored('WARNING:', color='yellow', attrs=('bold',)),
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
elif message.startswith('HINT:'):
|
elif message.startswith('HINT:'):
|
||||||
message = message.replace(
|
message = message.replace(
|
||||||
'HINT:', colored('HINT:', color='green', attrs=('bold',)), 1
|
'HINT:',
|
||||||
|
colored('HINT:', color='green', attrs=('bold',)),
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
print(message, file=sys.stderr)
|
print(message, file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class Displayable(CursesShortcuts): # pylint: disable=too-many-instance-attribu
|
||||||
self.parent = None
|
self.parent = None
|
||||||
|
|
||||||
def __contains__(self, item):
|
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.
|
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)
|
return (self.x <= x < self.x + self.width) and (self.y <= y < self.y + self.height)
|
||||||
|
|
||||||
def poke(self):
|
def poke(self):
|
||||||
"""Called before drawing, even if invisible"""
|
"""Called before drawing, even if invisible."""
|
||||||
|
|
||||||
if self._old_visible != self.visible:
|
if self._old_visible != self.visible:
|
||||||
self._old_visible = self.visible
|
self._old_visible = self.visible
|
||||||
self.need_redraw = True
|
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()
|
Called on every main iteration if visible. Containers should call draw()
|
||||||
on their contained objects here. Override this!
|
on their contained objects here. Override this!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.need_redraw = False
|
self.need_redraw = False
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
|
|
@ -100,12 +98,10 @@ class Displayable(CursesShortcuts): # pylint: disable=too-many-instance-attribu
|
||||||
|
|
||||||
Override this!
|
Override this!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.need_redraw = False
|
self.need_redraw = False
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Called when the object is destroyed."""
|
"""Called when the object is destroyed."""
|
||||||
|
|
||||||
self.win = None
|
self.win = None
|
||||||
self.root = None
|
self.root = None
|
||||||
|
|
||||||
|
|
@ -184,15 +180,13 @@ class DisplayableContainer(Displayable):
|
||||||
# extended or overridden methods
|
# extended or overridden methods
|
||||||
|
|
||||||
def poke(self):
|
def poke(self):
|
||||||
"""Recursively called on objects in container"""
|
"""Recursively called on objects in container."""
|
||||||
|
|
||||||
super().poke()
|
super().poke()
|
||||||
for displayable in self.container:
|
for displayable in self.container:
|
||||||
displayable.poke()
|
displayable.poke()
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
"""Recursively called on visible objects in container"""
|
"""Recursively called on visible objects in container."""
|
||||||
|
|
||||||
for displayable in self.container:
|
for displayable in self.container:
|
||||||
if self.need_redraw:
|
if self.need_redraw:
|
||||||
displayable.need_redraw = True
|
displayable.need_redraw = True
|
||||||
|
|
@ -202,22 +196,19 @@ class DisplayableContainer(Displayable):
|
||||||
self.need_redraw = False
|
self.need_redraw = False
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
"""Recursively called on visible objects in container"""
|
"""Recursively called on visible objects in container."""
|
||||||
|
|
||||||
for displayable in self.container:
|
for displayable in self.container:
|
||||||
if displayable.visible:
|
if displayable.visible:
|
||||||
displayable.finalize()
|
displayable.finalize()
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Recursively called on objects in container"""
|
"""Recursively called on objects in container."""
|
||||||
|
|
||||||
for displayable in self.container:
|
for displayable in self.container:
|
||||||
displayable.destroy()
|
displayable.destroy()
|
||||||
super().destroy()
|
super().destroy()
|
||||||
|
|
||||||
def press(self, key):
|
def press(self, key):
|
||||||
"""Recursively called on objects in container"""
|
"""Recursively called on objects in container."""
|
||||||
|
|
||||||
focused_obj = self.get_focused_obj()
|
focused_obj = self.get_focused_obj()
|
||||||
|
|
||||||
if focused_obj:
|
if focused_obj:
|
||||||
|
|
@ -226,8 +217,7 @@ class DisplayableContainer(Displayable):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def click(self, event):
|
def click(self, event):
|
||||||
"""Recursively called on objects in container"""
|
"""Recursively called on objects in container."""
|
||||||
|
|
||||||
focused_obj = self.get_focused_obj()
|
focused_obj = self.get_focused_obj()
|
||||||
if focused_obj and focused_obj.click(event):
|
if focused_obj and focused_obj.click(event):
|
||||||
return True
|
return True
|
||||||
|
|
@ -241,7 +231,6 @@ class DisplayableContainer(Displayable):
|
||||||
|
|
||||||
def add_child(self, obj):
|
def add_child(self, obj):
|
||||||
"""Add the objects to the container."""
|
"""Add the objects to the container."""
|
||||||
|
|
||||||
if obj.parent is not None:
|
if obj.parent is not None:
|
||||||
obj.parent.remove_child(obj)
|
obj.parent.remove_child(obj)
|
||||||
self.container.append(obj)
|
self.container.append(obj)
|
||||||
|
|
@ -250,14 +239,12 @@ class DisplayableContainer(Displayable):
|
||||||
|
|
||||||
def replace_child(self, old_obj, new_obj):
|
def replace_child(self, old_obj, new_obj):
|
||||||
"""Replace the old object with the new instance in the container."""
|
"""Replace the old object with the new instance in the container."""
|
||||||
|
|
||||||
self.container[self.container.index(old_obj)] = new_obj
|
self.container[self.container.index(old_obj)] = new_obj
|
||||||
new_obj.parent = self
|
new_obj.parent = self
|
||||||
new_obj.root = self.root
|
new_obj.root = self.root
|
||||||
|
|
||||||
def remove_child(self, obj):
|
def remove_child(self, obj):
|
||||||
"""Remove the object from the container."""
|
"""Remove the object from the container."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.container.remove(obj)
|
self.container.remove(obj)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ PAIR2SYMBOL_DOWN = {
|
||||||
for s1, s2 in itertools.product(SYMBOL2VALUE_DOWN, repeat=2)
|
for s1, s2 in itertools.product(SYMBOL2VALUE_DOWN, repeat=2)
|
||||||
}
|
}
|
||||||
GRAPH_SYMBOLS = ''.join(
|
GRAPH_SYMBOLS = ''.join(
|
||||||
sorted(set(itertools.chain(VALUE2SYMBOL_UP.values(), VALUE2SYMBOL_DOWN.values())))
|
sorted(set(itertools.chain(VALUE2SYMBOL_UP.values(), VALUE2SYMBOL_DOWN.values()))),
|
||||||
).replace(' ', '')
|
).replace(' ', '')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -92,7 +92,8 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
self.maxlen = 2 * self.width + 1
|
self.maxlen = 2 * self.width + 1
|
||||||
self.history = deque(
|
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.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)
|
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
|
@width.setter
|
||||||
def width(self, value):
|
def width(self, value):
|
||||||
if self._width != value:
|
if self._width != value:
|
||||||
assert isinstance(value, int) and value >= 1
|
assert isinstance(value, int)
|
||||||
|
assert value >= 1
|
||||||
self._width = value
|
self._width = value
|
||||||
with self.write_lock:
|
with self.write_lock:
|
||||||
self.maxlen = 2 * self.width + 1
|
self.maxlen = 2 * self.width + 1
|
||||||
self.reversed_history = deque(
|
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._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(
|
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]:
|
if self.reversed_history[-1] == self._max_value_maintainer[0]:
|
||||||
self._max_value_maintainer.popleft()
|
self._max_value_maintainer.popleft()
|
||||||
|
|
@ -149,7 +155,8 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
|
||||||
@height.setter
|
@height.setter
|
||||||
def height(self, value):
|
def height(self, value):
|
||||||
if self._height != value:
|
if self._height != value:
|
||||||
assert isinstance(value, int) and value >= 1
|
assert isinstance(value, int)
|
||||||
|
assert value >= 1
|
||||||
self._height = value
|
self._height = value
|
||||||
self.remake_graph()
|
self.remake_graph()
|
||||||
|
|
||||||
|
|
@ -160,8 +167,10 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
|
||||||
@graph_size.setter
|
@graph_size.setter
|
||||||
def graph_size(self, value):
|
def graph_size(self, value):
|
||||||
width, height = value
|
width, height = value
|
||||||
assert isinstance(width, int) and width >= 1
|
assert isinstance(width, int)
|
||||||
assert isinstance(height, int) and height >= 1
|
assert width >= 1
|
||||||
|
assert isinstance(height, int)
|
||||||
|
assert height >= 1
|
||||||
self._height = height
|
self._height = height
|
||||||
self._width = width - 1 # trigger force remake
|
self._width = width - 1 # trigger force remake
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ SPECIAL_KEYS = OrderedDict(
|
||||||
('S-Tab', curses.KEY_BTAB),
|
('S-Tab', curses.KEY_BTAB),
|
||||||
('lt', ord('<')),
|
('lt', ord('<')),
|
||||||
('gt', ord('>')),
|
('gt', ord('>')),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
NAMED_SPECIAL_KEYS = tuple(SPECIAL_KEYS.keys())
|
NAMED_SPECIAL_KEYS = tuple(SPECIAL_KEYS.keys())
|
||||||
|
|
@ -55,7 +55,7 @@ VERY_SPECIAL_KEYS = {
|
||||||
|
|
||||||
|
|
||||||
def _uncase_special_key(string):
|
def _uncase_special_key(string):
|
||||||
"""Uncase a special key
|
"""Uncase a special key.
|
||||||
|
|
||||||
>>> _uncase_special_key('Esc')
|
>>> _uncase_special_key('Esc')
|
||||||
'esc'
|
'esc'
|
||||||
|
|
@ -71,7 +71,7 @@ def _uncase_special_key(string):
|
||||||
'a-x'
|
'a-x'
|
||||||
"""
|
"""
|
||||||
uncased = string.lower()
|
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]}'
|
uncased = f'{uncased[0]}-{string[-1]}'
|
||||||
return uncased
|
return uncased
|
||||||
|
|
||||||
|
|
@ -176,7 +176,7 @@ def key_to_string(key):
|
||||||
|
|
||||||
|
|
||||||
def construct_keybinding(keys):
|
def construct_keybinding(keys):
|
||||||
"""Does the reverse of parse_keybinding
|
"""Do the reverse of parse_keybinding.
|
||||||
|
|
||||||
>>> construct_keybinding(parse_keybinding('lol<CR>'))
|
>>> construct_keybinding(parse_keybinding('lol<CR>'))
|
||||||
'lol<Enter>'
|
'lol<Enter>'
|
||||||
|
|
@ -211,7 +211,7 @@ def construct_keybinding(keys):
|
||||||
|
|
||||||
|
|
||||||
def normalize_keybinding(keybinding):
|
def normalize_keybinding(keybinding):
|
||||||
"""Normalize a keybinding to a string
|
"""Normalize a keybinding to a string.
|
||||||
|
|
||||||
>>> normalize_keybinding('lol<CR>')
|
>>> normalize_keybinding('lol<CR>')
|
||||||
'lol<Enter>'
|
'lol<Enter>'
|
||||||
|
|
@ -275,7 +275,7 @@ class KeyMaps(dict):
|
||||||
pointer = pointer[key]
|
pointer = pointer[key]
|
||||||
except KeyError as ex:
|
except KeyError as ex:
|
||||||
raise KeyError(
|
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
|
) from ex
|
||||||
try:
|
try:
|
||||||
self.bind(context, target, copy.deepcopy(pointer))
|
self.bind(context, target, copy.deepcopy(pointer))
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ TRUE_COLORS = dict(
|
||||||
('bright cyan', 14),
|
('bright cyan', 14),
|
||||||
('bright white', 15),
|
('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):
|
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
|
global LIGHT_THEME, DEFAULT_FOREGROUND, DEFAULT_BACKGROUND # pylint: disable=global-statement
|
||||||
|
|
||||||
LIGHT_THEME = light_theme
|
LIGHT_THEME = light_theme
|
||||||
|
|
@ -76,8 +75,7 @@ def _get_true_color(rgb):
|
||||||
|
|
||||||
|
|
||||||
def _get_color(fg, bg):
|
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
|
global COLOR_PAIRS # pylint: disable=global-statement,global-variable-not-assigned
|
||||||
|
|
||||||
if isinstance(fg, str):
|
if isinstance(fg, str):
|
||||||
|
|
@ -185,8 +183,8 @@ class CursesShortcuts:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ASCII_TRANSTABLE = str.maketrans(
|
ASCII_TRANSTABLE = str.maketrans(
|
||||||
'═' + '─╴' + '╒╤╕╪╘╧╛┌┬┐┼└┴┘' + '│╞╡├┤▏▎▍▌▋▊▉█░' + '▲▼' + '' + GRAPH_SYMBOLS,
|
'═─╴╒╤╕╪╘╧╛┌┬┐┼└┴┘│╞╡├┤▏▎▍▌▋▊▉█░▲▼' + GRAPH_SYMBOLS,
|
||||||
'=' + '--' + '++++++++++++++' + '||||||||||||||' + '^v' + '?' + '=' * len(GRAPH_SYMBOLS),
|
'=--++++++++++++++||||||||||||||^v?' + '=' * len(GRAPH_SYMBOLS),
|
||||||
)
|
)
|
||||||
TERM_256COLOR = False
|
TERM_256COLOR = False
|
||||||
|
|
||||||
|
|
@ -232,17 +230,14 @@ class CursesShortcuts:
|
||||||
|
|
||||||
def color(self, fg=-1, bg=-1, attr=0):
|
def color(self, fg=-1, bg=-1, attr=0):
|
||||||
"""Change the colors from now on."""
|
"""Change the colors from now on."""
|
||||||
|
|
||||||
return self.set_fg_bg_attr(fg, bg, attr)
|
return self.set_fg_bg_attr(fg, bg, attr)
|
||||||
|
|
||||||
def color_reset(self):
|
def color_reset(self):
|
||||||
"""Change the colors to the default colors"""
|
"""Change the colors to the default colors."""
|
||||||
|
|
||||||
return self.color()
|
return self.color()
|
||||||
|
|
||||||
def color_at(self, y, x, width, *args, **kwargs):
|
def color_at(self, y, x, width, *args, **kwargs):
|
||||||
"""Change the colors at the specified position"""
|
"""Change the colors at the specified position."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.win.chgat(y, x, width, self.get_fg_bg_attr(*args, **kwargs))
|
self.win.chgat(y, x, width, self.get_fg_bg_attr(*args, **kwargs))
|
||||||
except curses.error:
|
except curses.error:
|
||||||
|
|
@ -250,8 +245,7 @@ class CursesShortcuts:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_fg_bg_attr(fg=-1, bg=-1, attr=0):
|
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:
|
if fg == -1 and bg == -1 and attr == 0:
|
||||||
return BASE_ATTR
|
return BASE_ATTR
|
||||||
|
|
||||||
|
|
@ -283,10 +277,11 @@ class CursesShortcuts:
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def update_size(self, termsize=None):
|
def update_size(self, termsize=None):
|
||||||
if termsize is None:
|
if termsize is not None:
|
||||||
self.update_lines_cols()
|
return termsize
|
||||||
termsize = self.win.getmaxyx()
|
|
||||||
return termsize
|
self.update_lines_cols()
|
||||||
|
return self.win.getmaxyx()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_lines_cols():
|
def update_lines_cols():
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
self.options = options
|
self.options = options
|
||||||
self.num_options = len(self.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 <= no < self.num_options
|
||||||
assert 0 <= cancel < self.num_options
|
assert 0 <= cancel < self.num_options
|
||||||
assert 0 <= default < self.num_options
|
assert 0 <= default < self.num_options
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class MouseEvent:
|
||||||
CTRL_SCROLLWHEEL_MULTIPLIER = 5
|
CTRL_SCROLLWHEEL_MULTIPLIER = 5
|
||||||
|
|
||||||
def __init__(self, state):
|
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
|
_, self.x, self.y, _, self.bstate = state
|
||||||
|
|
||||||
# x-values above ~220 suddenly became negative, apparently
|
# x-values above ~220 suddenly became negative, apparently
|
||||||
|
|
@ -51,35 +51,35 @@ class MouseEvent:
|
||||||
self.y += 0xFF
|
self.y += 0xFF
|
||||||
|
|
||||||
def pressed(self, n):
|
def pressed(self, n):
|
||||||
"""Returns whether the mouse key n is pressed"""
|
"""Return whether the mouse key n is pressed."""
|
||||||
try:
|
try:
|
||||||
return (self.bstate & MouseEvent.PRESSED[n]) != 0
|
return (self.bstate & MouseEvent.PRESSED[n]) != 0
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def released(self, n):
|
def released(self, n):
|
||||||
"""Returns whether the mouse key n is released"""
|
"""Return whether the mouse key n is released."""
|
||||||
try:
|
try:
|
||||||
return (self.bstate & MouseEvent.RELEASED[n]) != 0
|
return (self.bstate & MouseEvent.RELEASED[n]) != 0
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def clicked(self, n):
|
def clicked(self, n):
|
||||||
"""Returns whether the mouse key n is clicked"""
|
"""Return whether the mouse key n is clicked."""
|
||||||
try:
|
try:
|
||||||
return (self.bstate & MouseEvent.CLICKED[n]) != 0
|
return (self.bstate & MouseEvent.CLICKED[n]) != 0
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def double_clicked(self, n):
|
def double_clicked(self, n):
|
||||||
"""Returns whether the mouse key n is double clicked"""
|
"""Return whether the mouse key n is double clicked."""
|
||||||
try:
|
try:
|
||||||
return (self.bstate & MouseEvent.DOUBLE_CLICKED[n]) != 0
|
return (self.bstate & MouseEvent.DOUBLE_CLICKED[n]) != 0
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def wheel_direction(self):
|
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.
|
# If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button.
|
||||||
# I interpret invalid buttons as "scroll down" because all tested
|
# I interpret invalid buttons as "scroll down" because all tested
|
||||||
# systems have a broken curses implementation and this is a workaround.
|
# systems have a broken curses implementation and this is a workaround.
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class Selection: # pylint: disable=too-many-instance-attributes
|
||||||
self.send_signal(
|
self.send_signal(
|
||||||
signal.SIGINT
|
signal.SIGINT
|
||||||
if not host.WINDOWS
|
if not host.WINDOWS
|
||||||
else signal.CTRL_C_EVENT # pylint: disable=no-member
|
else signal.CTRL_C_EVENT, # pylint: disable=no-member
|
||||||
)
|
)
|
||||||
except SystemError:
|
except SystemError:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,14 @@ WIDE_SYMBOLS = set('WF')
|
||||||
|
|
||||||
|
|
||||||
def utf_char_width(string):
|
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:
|
if east_asian_width(string) in WIDE_SYMBOLS:
|
||||||
return WIDE
|
return WIDE
|
||||||
return NARROW
|
return NARROW
|
||||||
|
|
||||||
|
|
||||||
def string_to_charlist(string):
|
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):
|
if ASCIIONLY.issuperset(string):
|
||||||
return list(string)
|
return list(string)
|
||||||
result = []
|
result = []
|
||||||
|
|
@ -35,8 +33,7 @@ def string_to_charlist(string):
|
||||||
|
|
||||||
|
|
||||||
def wcslen(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))
|
return len(WideString(string))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,7 +60,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> (WideString('afd') + 'bc').chars
|
>>> (WideString('afd') + 'bc').chars
|
||||||
['a', 'f', 'd', 'b', 'c']
|
['a', 'f', 'd', 'b', 'c']
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
return WideString(self.string + other)
|
return WideString(self.string + other)
|
||||||
if isinstance(other, WideString):
|
if isinstance(other, WideString):
|
||||||
|
|
@ -75,7 +71,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> ('bc' + WideString('afd')).chars
|
>>> ('bc' + WideString('afd')).chars
|
||||||
['b', 'c', 'a', 'f', 'd']
|
['b', 'c', 'a', 'f', 'd']
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
return WideString(other + self.string)
|
return WideString(other + self.string)
|
||||||
if isinstance(other, WideString):
|
if isinstance(other, WideString):
|
||||||
|
|
@ -131,7 +126,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString('aモ')[0:1]
|
>>> WideString('aモ')[0:1]
|
||||||
<WideString 'a'>
|
<WideString 'a'>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(item, slice):
|
if isinstance(item, slice):
|
||||||
assert item.step is None or item.step == 1
|
assert item.step is None or item.step == 1
|
||||||
start, stop = item.start, item.stop
|
start, stop = item.start, item.stop
|
||||||
|
|
@ -166,7 +160,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> len(WideString('モヒカン'))
|
>>> len(WideString('モヒカン'))
|
||||||
8
|
8
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return len(self.chars)
|
return len(self.chars)
|
||||||
|
|
||||||
def ljust(self, width, fillchar=' '):
|
def ljust(self, width, fillchar=' '):
|
||||||
|
|
@ -178,7 +171,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString('モヒカン').ljust(10)
|
>>> WideString('モヒカン').ljust(10)
|
||||||
<WideString 'モヒカン '>
|
<WideString 'モヒカン '>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if width > len(self):
|
if width > len(self):
|
||||||
return WideString(self.string + fillchar * width)[:width]
|
return WideString(self.string + fillchar * width)[:width]
|
||||||
return self
|
return self
|
||||||
|
|
@ -192,7 +184,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString('モヒカン').rljust(10)
|
>>> WideString('モヒカン').rljust(10)
|
||||||
<WideString ' モヒカン'>
|
<WideString ' モヒカン'>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if width > len(self):
|
if width > len(self):
|
||||||
return WideString(fillchar * width + self.string)[-width:]
|
return WideString(fillchar * width + self.string)[-width:]
|
||||||
return self
|
return self
|
||||||
|
|
@ -206,7 +197,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString('モヒカン').center(10)
|
>>> WideString('モヒカン').center(10)
|
||||||
<WideString ' モヒカン '>
|
<WideString ' モヒカン '>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if width > len(self):
|
if width > len(self):
|
||||||
left_width = (width - len(self)) // 2
|
left_width = (width - len(self)) // 2
|
||||||
right_width = width - left_width
|
right_width = width - left_width
|
||||||
|
|
@ -220,7 +210,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString(' モヒカン ').strip()
|
>>> WideString(' モヒカン ').strip()
|
||||||
<WideString 'モヒカン'>
|
<WideString 'モヒカン'>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return WideString(self.string.strip(chars))
|
return WideString(self.string.strip(chars))
|
||||||
|
|
||||||
def lstrip(self, chars=None):
|
def lstrip(self, chars=None):
|
||||||
|
|
@ -230,7 +219,6 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString(' モヒカン ').lstrip()
|
>>> WideString(' モヒカン ').lstrip()
|
||||||
<WideString 'モヒカン '>
|
<WideString 'モヒカン '>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return WideString(self.string.lstrip(chars))
|
return WideString(self.string.lstrip(chars))
|
||||||
|
|
||||||
def rstrip(self, chars=None):
|
def rstrip(self, chars=None):
|
||||||
|
|
@ -240,5 +228,4 @@ class WideString: # pylint: disable=too-few-public-methods,wrong-spelling-in-do
|
||||||
>>> WideString(' モヒカン ').rstrip()
|
>>> WideString(' モヒカン ').rstrip()
|
||||||
<WideString ' モヒカン'>
|
<WideString ' モヒカン'>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return WideString(self.string.rstrip(chars))
|
return WideString(self.string.rstrip(chars))
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,8 @@ class EnvironScreen(Displayable): # pylint: disable=too-many-instance-attribute
|
||||||
n_items = len(self.environ)
|
n_items = len(self.environ)
|
||||||
old_scroll_offset = self.scroll_offset
|
old_scroll_offset = self.scroll_offset
|
||||||
self.scroll_offset = max(
|
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
|
direction -= self.scroll_offset - old_scroll_offset
|
||||||
self._y_offset += 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):
|
if isinstance(self.process, GpuProcess):
|
||||||
process_type = 'GPU: ' + self.process.type.replace('C', 'Compute').replace(
|
process_type = 'GPU: ' + self.process.type.replace('C', 'Compute').replace(
|
||||||
'G', 'Graphics'
|
'G',
|
||||||
|
'Graphics',
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
process_type = 'Host'
|
process_type = 'Host'
|
||||||
header_prefix = WideString(
|
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))
|
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])
|
header = str((header_prefix + self.command[offset:]).ljust(self.width)[: self.width])
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from nvitop.gui.library import Device, Displayable, MouseEvent
|
||||||
from nvitop.version import __version__
|
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.
|
Released under the GNU GPLv3 License.
|
||||||
|
|
||||||
GPU Process Type: C: Compute, G: Graphics, X: Mixed.
|
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
|
, .: select sort column /: invert sort order
|
||||||
|
|
||||||
Press any key to return.
|
Press any key to return.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
class HelpScreen(Displayable): # pylint: disable=too-many-instance-attributes
|
class HelpScreen(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from nvitop.gui.screens.main.host import HostPanel
|
||||||
from nvitop.gui.screens.main.process import ProcessPanel
|
from nvitop.gui.screens.main.process import ProcessPanel
|
||||||
|
|
||||||
|
|
||||||
class BreakLoop(Exception):
|
class BreakLoop(Exception): # noqa: N818
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,7 +44,11 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att
|
||||||
self.add_child(self.host_panel)
|
self.add_child(self.host_panel)
|
||||||
|
|
||||||
self.process_panel = ProcessPanel(
|
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.process_panel.focused = False
|
||||||
self.add_child(self.process_panel)
|
self.add_child(self.process_panel)
|
||||||
|
|
@ -265,8 +269,12 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att
|
||||||
keymaps.bind('main', '/', order_reverse)
|
keymaps.bind('main', '/', order_reverse)
|
||||||
for order in ProcessPanel.ORDERS:
|
for order in ProcessPanel.ORDERS:
|
||||||
keymaps.bind(
|
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(
|
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),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,16 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
self.snapshot_lock = threading.Lock()
|
self.snapshot_lock = threading.Lock()
|
||||||
self.snapshots = self.take_snapshots()
|
self.snapshots = self.take_snapshots()
|
||||||
self._snapshot_daemon = threading.Thread(
|
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._daemon_running = threading.Event()
|
||||||
|
|
||||||
self.formats_compact = [
|
self.formats_compact = [
|
||||||
'│ {physical_index:>3} {fan_speed_string:>3} {temperature_string:>4} '
|
'│ {physical_index:>3} {fan_speed_string:>3} {temperature_string:>4} '
|
||||||
'{performance_state:>3} {power_status:>12} '
|
'{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 = [
|
self.formats_full = [
|
||||||
'│ {physical_index:>3} {name:<18} {persistence_mode:<4} '
|
'│ {physical_index:>3} {name:<18} {persistence_mode:<4} '
|
||||||
|
|
@ -78,12 +80,13 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
self.mig_formats = [
|
self.mig_formats = [
|
||||||
'│{physical_index:>2}:{mig_index:<2}{name:>12} @ GI/CI:{gpu_instance_id:>2}/{compute_instance_id:<2}'
|
'│{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:
|
if host.WINDOWS:
|
||||||
self.formats_full[0] = self.formats_full[0].replace(
|
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)
|
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.name = cut_string(device.name, maxlen=18, padstr='..', align='right')
|
||||||
device.current_driver_model = device.current_driver_model.replace('WDM', 'TCC')
|
device.current_driver_model = device.current_driver_model.replace('WDM', 'TCC')
|
||||||
device.display_active = device.display_active.replace('Enabled', 'On').replace(
|
device.display_active = device.display_active.replace('Enabled', 'On').replace(
|
||||||
'Disabled', 'Off'
|
'Disabled',
|
||||||
|
'Off',
|
||||||
)
|
)
|
||||||
device.persistence_mode = device.persistence_mode.replace('Enabled', 'On').replace(
|
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.mig_mode = device.mig_mode.replace('N/A', 'N/A ')
|
||||||
device.compute_mode = device.compute_mode.replace('Exclusive', 'E.')
|
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:
|
if self.device_count > 0:
|
||||||
header.append(
|
header.append(
|
||||||
'├───────────────────────────────┬──────────────────────┬──────────────────────┤'
|
'├───────────────────────────────┬──────────────────────┬──────────────────────┤',
|
||||||
)
|
)
|
||||||
if compact:
|
if compact:
|
||||||
header.append(
|
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:
|
else:
|
||||||
header.extend(
|
header.extend(
|
||||||
(
|
(
|
||||||
'│ GPU Name Persistence-M│ Bus-Id Disp.A │ Volatile Uncorr. ECC │',
|
'│ GPU Name Persistence-M│ Bus-Id Disp.A │ Volatile Uncorr. ECC │',
|
||||||
'│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │',
|
'│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │',
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
if host.WINDOWS:
|
if host.WINDOWS:
|
||||||
header[-2] = header[-2].replace('Persistence-M', ' TCC/WDDM ')
|
header[-2] = header[-2].replace('Persistence-M', ' TCC/WDDM ')
|
||||||
if self.support_mig:
|
if self.support_mig:
|
||||||
header[-2] = header[-2].replace('Volatile Uncorr. ECC', 'MIG M. Uncorr. ECC')
|
header[-2] = header[-2].replace('Volatile Uncorr. ECC', 'MIG M. Uncorr. ECC')
|
||||||
header.append(
|
header.append(
|
||||||
'╞═══════════════════════════════╪══════════════════════╪══════════════════════╡'
|
'╞═══════════════════════════════╪══════════════════════╪══════════════════════╡',
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
header.extend(
|
header.extend(
|
||||||
|
|
@ -209,7 +214,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
'╞═════════════════════════════════════════════════════════════════════════════╡',
|
'╞═════════════════════════════════════════════════════════════════════════════╡',
|
||||||
'│ No visible devices found │',
|
'│ No visible devices found │',
|
||||||
'╘═════════════════════════════════════════════════════════════════════════════╛',
|
'╘═════════════════════════════════════════════════════════════════════════════╛',
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
return header
|
return header
|
||||||
|
|
||||||
|
|
@ -241,7 +246,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
frame.pop()
|
frame.pop()
|
||||||
frame.append(
|
frame.append(
|
||||||
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛'
|
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛',
|
||||||
)
|
)
|
||||||
if self.width >= 100:
|
if self.width >= 100:
|
||||||
frame[5 - int(compact)] = (
|
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))
|
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 if self.compact else self.formats_full
|
||||||
formats = self.formats_compact
|
|
||||||
else:
|
|
||||||
formats = self.formats_full
|
|
||||||
|
|
||||||
remaining_width = self.width - 79
|
remaining_width = self.width - 79
|
||||||
draw_bars = self.width >= 100
|
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
|
full_bar_len = width - prefix_len - 5
|
||||||
self.color_at(y, x_offset, width=width, fg=float(bar_len / full_bar_len))
|
self.color_at(y, x_offset, width=width, fg=float(bar_len / full_bar_len))
|
||||||
for i, x in enumerate(
|
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))
|
self.color_at(y, x, width=1, fg=float(i / full_bar_len))
|
||||||
else:
|
else:
|
||||||
|
|
@ -404,7 +406,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
or prev_device_index[0] != device.tuple_index[0]
|
or prev_device_index[0] != device.tuple_index[0]
|
||||||
):
|
):
|
||||||
lines.append(
|
lines.append(
|
||||||
'├───────────────────────────────┼──────────────────────┼──────────────────────┤'
|
'├───────────────────────────────┼──────────────────────┼──────────────────────┤',
|
||||||
)
|
)
|
||||||
|
|
||||||
def colorize(s):
|
def colorize(s):
|
||||||
|
|
@ -421,7 +423,7 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
prev_device_index = device.tuple_index
|
prev_device_index = device.tuple_index
|
||||||
|
|
||||||
lines.append(
|
lines.append(
|
||||||
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛'
|
'╘═══════════════════════════════╧══════════════════════╧══════════════════════╛',
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.width >= 100:
|
if self.width >= 100:
|
||||||
|
|
@ -458,7 +460,9 @@ class DevicePanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
matrix.pop()
|
matrix.pop()
|
||||||
for y, (prefix, utilization, color) in enumerate(matrix, start=y_start):
|
for y, (prefix, utilization, color) in enumerate(matrix, start=y_start):
|
||||||
bar = make_bar( # pylint: disable=disallowed-name
|
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)} │'
|
lines[y] += f' {colored(bar, color)} │'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,9 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
self.memory_percent = None
|
self.memory_percent = None
|
||||||
self.swap_percent = None
|
self.swap_percent = None
|
||||||
self._snapshot_daemon = threading.Thread(
|
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()
|
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 + 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, self.x + width_left + 2, width=width_right, attr='bold')
|
||||||
self.color_at(
|
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
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,9 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
self._snapshots = []
|
self._snapshots = []
|
||||||
self.snapshot_lock = threading.Lock()
|
self.snapshot_lock = threading.Lock()
|
||||||
self._snapshot_daemon = threading.Thread(
|
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()
|
self._daemon_running = threading.Event()
|
||||||
|
|
||||||
|
|
@ -168,7 +170,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
self._compact = value
|
self._compact = value
|
||||||
processes = self.snapshots
|
processes = self.snapshots
|
||||||
n_processes, n_devices = len(processes), len(
|
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.full_height = 1 + max(6, 5 + n_processes + n_devices - 1)
|
||||||
self.compact_height = 1 + max(6, 5 + n_processes)
|
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.SNAPSHOT_INTERVAL = min(interval / 3.0, 1.0)
|
||||||
cls.take_snapshots = ttl_cache(ttl=interval)(
|
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):
|
def ensure_snapshots(self):
|
||||||
|
|
@ -265,7 +267,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
' ' * (time_length - len(snapshot.running_time_human))
|
' ' * (time_length - len(snapshot.running_time_human))
|
||||||
+ snapshot.running_time_human,
|
+ snapshot.running_time_human,
|
||||||
snapshot.command,
|
snapshot.command,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.snapshot_lock:
|
with self.snapshot_lock:
|
||||||
|
|
@ -284,7 +286,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
'╒' + '═' * (self.width - 2) + '╕',
|
'╒' + '═' * (self.width - 2) + '╕',
|
||||||
'│ {} │'.format('Processes:'.ljust(self.width - 4)),
|
'│ {} │'.format('Processes:'.ljust(self.width - 4)),
|
||||||
'│ GPU PID USER GPU-MEM %SM {} │'.format(
|
'│ 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) + '╡',
|
'╞' + '═' * (self.width - 2) + '╡',
|
||||||
]
|
]
|
||||||
|
|
@ -294,14 +296,14 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
else:
|
else:
|
||||||
message = ' Gathering process status...'
|
message = ' Gathering process status...'
|
||||||
header.extend(
|
header.extend(
|
||||||
[f'│ {message.ljust(self.width - 4)} │', '╘' + '═' * (self.width - 2) + '╛']
|
[f'│ {message.ljust(self.width - 4)} │', '╘' + '═' * (self.width - 2) + '╛'],
|
||||||
)
|
)
|
||||||
return header
|
return header
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def processes(self):
|
def processes(self):
|
||||||
return list(
|
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):
|
def poke(self):
|
||||||
|
|
@ -313,7 +315,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
self.selection.within_window = False
|
self.selection.within_window = False
|
||||||
if len(self.snapshots) > 0 and self.selection.is_set():
|
if len(self.snapshots) > 0 and self.selection.is_set():
|
||||||
y = self.y + 5 # noqa: SIM113
|
y = self.y + 5
|
||||||
prev_device_index = None
|
prev_device_index = None
|
||||||
for process in self.snapshots:
|
for process in self.snapshots:
|
||||||
device_index = process.device.physical_index
|
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:
|
if offset > 38 or host_offset == 0:
|
||||||
self.addstr(self.y + 3, self.x + offset - 1, column + indicator)
|
self.addstr(self.y + 3, self.x + offset - 1, column + indicator)
|
||||||
self.color_at(
|
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:
|
elif offset <= 38 < offset + column_width:
|
||||||
self.addstr(self.y + 3, self.x + 38, (column + indicator)[39 - offset :])
|
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:
|
else:
|
||||||
self.addstr(self.y + 3, self.x + offset - 1, column + indicator)
|
self.addstr(self.y + 3, self.x + offset - 1, column + indicator)
|
||||||
self.color_at(
|
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')
|
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
|
self.selection.within_window = False
|
||||||
if len(self.snapshots) > 0:
|
if len(self.snapshots) > 0:
|
||||||
y = self.y + 5 # noqa: SIM113
|
y = self.y + 5
|
||||||
prev_device_index = None
|
prev_device_index = None
|
||||||
prev_device_display_index = None
|
prev_device_display_index = None
|
||||||
color = -1
|
color = -1
|
||||||
|
|
@ -457,7 +465,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
cut_string(process.pid, maxlen=7, padstr='.'),
|
cut_string(process.pid, maxlen=7, padstr='.'),
|
||||||
process.type,
|
process.type,
|
||||||
str(
|
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_memory_human,
|
||||||
process.gpu_sm_utilization_string.replace('%', ''),
|
process.gpu_sm_utilization_string.replace('%', ''),
|
||||||
|
|
@ -542,7 +550,8 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
def print_width(self):
|
def print_width(self):
|
||||||
self.ensure_snapshots()
|
self.ensure_snapshots()
|
||||||
return min(
|
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):
|
def print(self):
|
||||||
|
|
@ -556,7 +565,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
||||||
colored('@', attrs=('bold',)),
|
colored('@', attrs=('bold',)),
|
||||||
colored(HOSTNAME, color='green', attrs=('bold',)),
|
colored(HOSTNAME, color='green', attrs=('bold',)),
|
||||||
lines[2][-2:],
|
lines[2][-2:],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(self.snapshots) > 0:
|
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:
|
if process.username != USERNAME and not SUPERUSER:
|
||||||
info = (colored(item, attrs=('dark',)) for item in info)
|
info = (colored(item, attrs=('dark',)) for item in info)
|
||||||
info = colored(
|
info = colored(
|
||||||
process.command, color=('red' if process.is_gone else 'yellow')
|
process.command,
|
||||||
|
color=('red' if process.is_gone else 'yellow'),
|
||||||
).join(info)
|
).join(info)
|
||||||
elif process.username != USERNAME and not SUPERUSER:
|
elif process.username != USERNAME and not SUPERUSER:
|
||||||
info = colored(info, attrs=('dark',))
|
info = colored(info, attrs=('dark',))
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,7 @@ def get_yticks(history, y_offset): # pylint: disable=too-many-branches,too-many
|
||||||
if len(h2p) >= 2:
|
if len(h2p) >= 2:
|
||||||
(hm1, pm1), (h2, p2) = h2p[-2:]
|
(hm1, pm1), (h2, p2) = h2p[-2:]
|
||||||
if height < 12:
|
if height < 12:
|
||||||
if h2e[hm1] < h2e[h2]:
|
ticks = [(hm1, pm1)] if h2e[hm1] < h2e[h2] else [(h2, p2)]
|
||||||
ticks = [(hm1, pm1)]
|
|
||||||
else:
|
|
||||||
ticks = [(h2, p2)]
|
|
||||||
else:
|
else:
|
||||||
ticks = [(h2, p2)]
|
ticks = [(h2, p2)]
|
||||||
if p2 % 2 == 0:
|
if p2 % 2 == 0:
|
||||||
|
|
@ -96,7 +93,9 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
self.enabled = False
|
self.enabled = False
|
||||||
self.snapshot_lock = threading.Lock()
|
self.snapshot_lock = threading.Lock()
|
||||||
self._snapshot_daemon = threading.Thread(
|
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()
|
self._daemon_running = threading.Event()
|
||||||
|
|
||||||
|
|
@ -313,7 +312,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
|
|
||||||
def frame_lines(self):
|
def frame_lines(self):
|
||||||
line = '│' + ' ' * self.left_width + '│' + ' ' * self.right_width + '│'
|
line = '│' + ' ' * self.left_width + '│' + ' ' * self.right_width + '│'
|
||||||
frame = [
|
return [
|
||||||
'╒' + '═' * (self.width - 2) + '╕',
|
'╒' + '═' * (self.width - 2) + '╕',
|
||||||
'│ {} │'.format('Process:'.ljust(self.width - 4)),
|
'│ {} │'.format('Process:'.ljust(self.width - 4)),
|
||||||
'│ {} │'.format('GPU'.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),
|
*([line] * self.lower_height),
|
||||||
'╘' + '═' * self.left_width + '╧' + '═' * self.right_width + '╛',
|
'╘' + '═' * self.left_width + '╧' + '═' * self.right_width + '╛',
|
||||||
]
|
]
|
||||||
return frame
|
|
||||||
|
|
||||||
def poke(self):
|
def poke(self):
|
||||||
if self.visible and not self._daemon_running.is_set():
|
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),
|
WideString(process.username).rjust(4),
|
||||||
maxlen=32,
|
maxlen=32,
|
||||||
padstr='+',
|
padstr='+',
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(' GPU-MEM', process.gpu_memory_human.rjust(8)),
|
(' 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)),
|
(' %CPU', process.cpu_percent_string.rjust(6)),
|
||||||
(' %MEM', process.memory_percent_string.rjust(5)),
|
(' %MEM', process.memory_percent_string.rjust(5)),
|
||||||
(' TIME', (' ' + process.running_time_human).rjust(5)),
|
(' TIME', (' ' + process.running_time_human).rjust(5)),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
x = self.x + 1
|
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 + 2, self.x + 1, header.ljust(self.width - 2))
|
||||||
self.addstr(self.y + 4, self.x + 1, str(fields.ljust(self.width - 2)))
|
self.addstr(self.y + 4, self.x + 1, str(fields.ljust(self.width - 2)))
|
||||||
self.color_at(
|
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:
|
if no_break:
|
||||||
|
|
@ -448,7 +449,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
self.y + 2,
|
self.y + 2,
|
||||||
x,
|
x,
|
||||||
cut_string('COMMAND', self.width - x - 2, padstr='..').ljust(
|
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:
|
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')
|
self.color(fg='magenta')
|
||||||
for y, line in enumerate(
|
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)
|
self.addstr(y, self.x + 1, line)
|
||||||
|
|
||||||
|
|
@ -480,7 +482,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
self.upper_height - 1
|
self.upper_height - 1
|
||||||
)
|
)
|
||||||
for i, (y, line) in enumerate(
|
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(
|
self.addstr(
|
||||||
y,
|
y,
|
||||||
|
|
@ -493,7 +495,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
self.lower_height - 1
|
self.lower_height - 1
|
||||||
)
|
)
|
||||||
for i, (y, line) in enumerate(
|
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(
|
self.addstr(
|
||||||
y,
|
y,
|
||||||
|
|
@ -508,7 +510,8 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
|
|
||||||
self.color(fg=self.process.device.snapshot.gpu_display_color)
|
self.color(fg=self.process.device.snapshot.gpu_display_color)
|
||||||
for y, line in enumerate(
|
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)
|
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(),
|
self.used_host_memory.max_value_string(),
|
||||||
maxlen=self.left_width - 2,
|
maxlen=self.left_width - 2,
|
||||||
padstr='..',
|
padstr='..',
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.addstr(
|
self.addstr(
|
||||||
|
|
@ -539,7 +542,7 @@ class ProcessMetricsScreen(Displayable): # pylint: disable=too-many-instance-at
|
||||||
self.used_gpu_memory.max_value_string(),
|
self.used_gpu_memory.max_value_string(),
|
||||||
maxlen=self.right_width - 2,
|
maxlen=self.right_width - 2,
|
||||||
padstr='..',
|
padstr='..',
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.addstr(self.y + 7, self.x + self.left_width + 6, f' {self.used_gpu_memory} ')
|
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.addstr(y, self.x + self.left_width + 1, '│')
|
self.addstr(y, self.x + self.left_width + 1, '│')
|
||||||
for y in range(
|
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.addstr(y, self.x + self.left_width + 1, '│')
|
self.addstr(y, self.x + self.left_width + 1, '│')
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ class TreeNode: # pylint: disable=too-many-instance-attributes
|
||||||
node._gone, # pylint: disable=protected-access
|
node._gone, # pylint: disable=protected-access
|
||||||
node.username,
|
node.username,
|
||||||
node.pid,
|
node.pid,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
child.is_last = False
|
child.is_last = False
|
||||||
|
|
@ -193,9 +193,7 @@ class TreeNode: # pylint: disable=too-many-instance-attributes
|
||||||
nodes[cpid] = child = cls(HostProcess(cpid))
|
nodes[cpid] = child = cls(HostProcess(cpid))
|
||||||
node.add(child)
|
node.add(child)
|
||||||
|
|
||||||
roots = sorted(filter(lambda node: node.is_root, nodes.values()), key=lambda node: node.pid)
|
return sorted(filter(lambda node: node.is_root, nodes.values()), key=lambda node: node.pid)
|
||||||
|
|
||||||
return roots
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def freeze(roots):
|
def freeze(roots):
|
||||||
|
|
@ -231,7 +229,9 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
|
||||||
self._snapshots = []
|
self._snapshots = []
|
||||||
self.snapshot_lock = threading.Lock()
|
self.snapshot_lock = threading.Lock()
|
||||||
self._snapshot_daemon = threading.Thread(
|
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()
|
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.SNAPSHOT_INTERVAL = min(interval / 3.0, 1.0)
|
||||||
cls.take_snapshots = ttl_cache(ttl=interval)(
|
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)
|
@ttl_cache(ttl=2.0)
|
||||||
|
|
@ -354,7 +354,8 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
|
||||||
elif y >= self.y + self.height:
|
elif y >= self.y + self.height:
|
||||||
self.scroll_offset += y - self.y - self.height + 1
|
self.scroll_offset += y - self.y - self.height + 1
|
||||||
self.scroll_offset = max(
|
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
|
break
|
||||||
else:
|
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))
|
pid_width = max(3, max((len(str(process.pid)) for process in self.snapshots), default=3))
|
||||||
username_width = max(
|
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))
|
device_width = max(6, max((len(process.devices) for process in self.snapshots), default=6))
|
||||||
num_threads_width = max(
|
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(
|
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(
|
header = ' '.join(
|
||||||
|
|
@ -387,12 +391,14 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
|
||||||
'%MEM',
|
'%MEM',
|
||||||
'TIME'.rjust(time_width),
|
'TIME'.rjust(time_width),
|
||||||
'COMMAND',
|
'COMMAND',
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
command_offset = len(header) - 7
|
command_offset = len(header) - 7
|
||||||
if self.x_offset < command_offset:
|
if self.x_offset < command_offset:
|
||||||
self.addstr(
|
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:
|
else:
|
||||||
self.addstr(self.y, self.x, 'COMMAND'.ljust(self.width))
|
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
|
self.selection.within_window = False
|
||||||
processes = islice(
|
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):
|
for y, process in enumerate(processes, start=self.y + 1):
|
||||||
prefix_length = len(process.prefix)
|
prefix_length = len(process.prefix)
|
||||||
|
|
@ -485,7 +493,12 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
|
||||||
attr='bold | italic | reverse',
|
attr='bold | italic | reverse',
|
||||||
)
|
)
|
||||||
self.color_at(
|
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.color_at(
|
||||||
self.y,
|
self.y,
|
||||||
|
|
@ -496,7 +509,12 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
|
||||||
attr='bold | italic | reverse',
|
attr='bold | italic | reverse',
|
||||||
)
|
)
|
||||||
self.color_at(
|
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.color_at(
|
||||||
self.y,
|
self.y,
|
||||||
|
|
@ -507,7 +525,12 @@ class TreeViewScreen(Displayable): # pylint: disable=too-many-instance-attribut
|
||||||
attr='bold | italic | reverse',
|
attr='bold | italic | reverse',
|
||||||
)
|
)
|
||||||
self.color_at(
|
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):
|
def finalize(self):
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,12 @@ class UI(DisplayableContainer): # pylint: disable=too-many-instance-attributes
|
||||||
self.device_count = len(self.devices)
|
self.device_count = len(self.devices)
|
||||||
|
|
||||||
self.main_screen = MainScreen(
|
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.visible = True
|
||||||
self.main_screen.focused = False
|
self.main_screen.focused = False
|
||||||
|
|
@ -193,8 +198,7 @@ class UI(DisplayableContainer): # pylint: disable=too-many-instance-attributes
|
||||||
self.main_screen.print()
|
self.main_screen.print()
|
||||||
|
|
||||||
def handle_mouse(self):
|
def handle_mouse(self):
|
||||||
"""Handles mouse input"""
|
"""Handle mouse input."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event = MouseEvent(curses.getmouse())
|
event = MouseEvent(curses.getmouse())
|
||||||
except curses.error:
|
except curses.error:
|
||||||
|
|
@ -202,8 +206,7 @@ class UI(DisplayableContainer): # pylint: disable=too-many-instance-attributes
|
||||||
super().click(event)
|
super().click(event)
|
||||||
|
|
||||||
def handle_key(self, key):
|
def handle_key(self, key):
|
||||||
"""Handles key input"""
|
"""Handle key input."""
|
||||||
|
|
||||||
if key < 0:
|
if key < 0:
|
||||||
self.keybuffer.clear()
|
self.keybuffer.clear()
|
||||||
elif not super().press(key):
|
elif not super().press(key):
|
||||||
|
|
|
||||||
|
|
@ -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.gpu_utilization), device.gpu_utilization), # ascending
|
||||||
(not math.isnan(device.memory_utilization), device.memory_utilization), # ascending
|
(not math.isnan(device.memory_utilization), device.memory_utilization), # ascending
|
||||||
-device.physical_index, # descending to keep <GPU 0> free
|
-device.physical_index, # descending to keep <GPU 0> free
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if any(device.is_mig_device for device in available_devices): # found MIG devices!
|
if any(device.is_mig_device for device in available_devices): # found MIG devices!
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,79 @@ multi_line_output = 3
|
||||||
[tool.pydocstyle]
|
[tool.pydocstyle]
|
||||||
convention = "google"
|
convention = "google"
|
||||||
match-dir = '^(?!(gui|callbacks|docs))[^\.].*'
|
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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue