mirror of
https://github.com/XuehaiPan/nvitop.git
synced 2026-05-21 06:45:24 -06:00
feat(gui/host): show more metrics
This commit is contained in:
parent
20313d08bd
commit
e00582a8f2
8 changed files with 113 additions and 44 deletions
|
|
@ -137,3 +137,4 @@ submodule
|
|||
submodules
|
||||
namespace
|
||||
noqa
|
||||
uptime
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ utilization (CPU, memory, disks, network, sensors) in Python.
|
|||
from __future__ import annotations
|
||||
|
||||
import os as _os
|
||||
import time as _time
|
||||
from typing import Callable as _Callable
|
||||
|
||||
import psutil as _psutil
|
||||
|
|
@ -32,6 +33,7 @@ from psutil import * # noqa: F403 # pylint: disable=wildcard-import,unused-wild
|
|||
|
||||
__all__ = [name for name in _psutil.__all__ if not name.startswith('_')] + [
|
||||
'load_average',
|
||||
'uptime',
|
||||
'memory_percent',
|
||||
'swap_percent',
|
||||
'ppid_map',
|
||||
|
|
@ -63,6 +65,11 @@ except AttributeError:
|
|||
return
|
||||
|
||||
|
||||
def uptime() -> float:
|
||||
"""Get the system uptime."""
|
||||
return _time.time() - _psutil.boot_time()
|
||||
|
||||
|
||||
def memory_percent() -> float:
|
||||
"""The percentage usage of virtual memory, calculated as ``(total - available) / total * 100``."""
|
||||
return virtual_memory().percent
|
||||
|
|
|
|||
|
|
@ -497,7 +497,12 @@ SIZE_PATTERN = re.compile(
|
|||
"""The regex pattern for human readable size."""
|
||||
|
||||
|
||||
def bytes2human(b: int | float | NaType) -> str: # pylint: disable=too-many-return-statements
|
||||
# pylint: disable-next=too-many-return-statements
|
||||
def bytes2human(
|
||||
b: int | float | NaType,
|
||||
*,
|
||||
min_unit: int = 1,
|
||||
) -> str:
|
||||
"""Convert bytes to a human readable string."""
|
||||
if b == NA:
|
||||
return NA
|
||||
|
|
@ -508,19 +513,19 @@ def bytes2human(b: int | float | NaType) -> str: # pylint: disable=too-many-ret
|
|||
except ValueError:
|
||||
return NA
|
||||
|
||||
if b < KiB:
|
||||
if b < KiB and min_unit < KiB:
|
||||
return f'{b}B'
|
||||
if b < MiB:
|
||||
if b < MiB and min_unit <= KiB:
|
||||
return f'{round(b / KiB)}KiB'
|
||||
if b <= 20 * GiB:
|
||||
if b <= 20 * GiB and min_unit <= MiB:
|
||||
return f'{round(b / MiB)}MiB'
|
||||
if b < 100 * GiB:
|
||||
if b < 100 * GiB and min_unit <= GiB:
|
||||
return f'{round(b / GiB, 2):.2f}GiB'
|
||||
if b < 1000 * GiB:
|
||||
if b < 1000 * GiB and min_unit <= GiB:
|
||||
return f'{round(b / GiB, 1):.1f}GiB'
|
||||
if b < 100 * TiB:
|
||||
if b < 100 * TiB and min_unit <= TiB:
|
||||
return f'{round(b / TiB, 2):.2f}TiB'
|
||||
if b < 1000 * TiB:
|
||||
if b < 1000 * TiB and min_unit <= TiB:
|
||||
return f'{round(b / TiB, 1):.1f}TiB'
|
||||
if b < 100 * PiB:
|
||||
return f'{round(b / PiB, 2):.2f}PiB'
|
||||
|
|
@ -561,7 +566,11 @@ def human2bytes(s: int | str) -> int:
|
|||
return int(float(size) * SIZE_UNITS[unit])
|
||||
|
||||
|
||||
def timedelta2human(dt: int | float | datetime.timedelta | NaType) -> str:
|
||||
def timedelta2human(
|
||||
dt: int | float | datetime.timedelta | NaType,
|
||||
*,
|
||||
round: bool = False, # pylint: disable=redefined-builtin
|
||||
) -> str:
|
||||
"""Convert a number in seconds or a :class:`datetime.timedelta` instance to a human readable string."""
|
||||
if isinstance(dt, (int, float)):
|
||||
dt = datetime.timedelta(seconds=dt)
|
||||
|
|
@ -569,7 +578,7 @@ def timedelta2human(dt: int | float | datetime.timedelta | NaType) -> str:
|
|||
if not isinstance(dt, datetime.timedelta):
|
||||
return NA
|
||||
|
||||
if dt.days >= 4:
|
||||
if dt.days >= 4 or (round and dt.days >= 1):
|
||||
return f'{dt.days + dt.seconds / 86400:.1f} days'
|
||||
|
||||
hours, seconds = divmod(86400 * dt.days + dt.seconds, 3600)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,15 @@ from nvitop.gui.library.keybinding import (
|
|||
from nvitop.gui.library.libcurses import libcurses, setlocale_utf8
|
||||
from nvitop.gui.library.messagebox import MessageBox, send_signal
|
||||
from nvitop.gui.library.mouse import MouseEvent
|
||||
from nvitop.gui.library.process import GpuProcess, HostProcess, Snapshot, bytes2human, host
|
||||
from nvitop.gui.library.process import (
|
||||
GiB,
|
||||
GpuProcess,
|
||||
HostProcess,
|
||||
Snapshot,
|
||||
bytes2human,
|
||||
host,
|
||||
timedelta2human,
|
||||
)
|
||||
from nvitop.gui.library.selection import Selection
|
||||
from nvitop.gui.library.utils import (
|
||||
HOSTNAME,
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
|
|||
)
|
||||
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.last_retval = None
|
||||
|
||||
self.graph = []
|
||||
self.last_graph = []
|
||||
|
|
@ -281,7 +282,7 @@ class HistoryGraph: # pylint: disable=too-many-instance-attributes
|
|||
def hook(self, func, get_value=None):
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
retval = value = func(*args, **kwargs)
|
||||
self.last_retval = retval = value = func(*args, **kwargs)
|
||||
if get_value is not None:
|
||||
value = get_value(retval)
|
||||
self.add(value)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,21 @@
|
|||
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
|
||||
|
||||
|
||||
from nvitop.api import NA
|
||||
from nvitop.api import NA, GiB
|
||||
from nvitop.api import GpuProcess as GpuProcessBase
|
||||
from nvitop.api import HostProcess, Snapshot, bytes2human, host, utilization2string
|
||||
from nvitop.api import HostProcess, Snapshot, bytes2human, host, timedelta2human, utilization2string
|
||||
|
||||
|
||||
__all__ = ['host', 'HostProcess', 'GpuProcess', 'NA', 'Snapshot', 'bytes2human']
|
||||
__all__ = [
|
||||
'host',
|
||||
'HostProcess',
|
||||
'GpuProcess',
|
||||
'NA',
|
||||
'Snapshot',
|
||||
'bytes2human',
|
||||
'GiB',
|
||||
'timedelta2human',
|
||||
]
|
||||
|
||||
|
||||
class GpuProcess(GpuProcessBase):
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def cut_string(s, maxlen, padstr='...', align='left'):
|
|||
|
||||
|
||||
# pylint: disable=disallowed-name
|
||||
def make_bar(prefix, percent, width):
|
||||
def make_bar(prefix, percent, width, *, extra_text=''):
|
||||
bar = f'{prefix}: '
|
||||
if percent != NA and not (isinstance(percent, float) and not math.isfinite(percent)):
|
||||
if isinstance(percent, str) and percent.endswith('%'):
|
||||
|
|
@ -45,12 +45,15 @@ def make_bar(prefix, percent, width):
|
|||
if remainder > 0:
|
||||
bar += ' ▏▎▍▌▋▊▉'[remainder]
|
||||
if isinstance(percent, float) and len(f'{bar} {percent:.1f}%') <= width:
|
||||
bar += f' {percent:.1f}%'
|
||||
text = f'{percent:.1f}%'
|
||||
else:
|
||||
bar += f' {min(round(percent), 100):d}%'.replace('100%', 'MAX')
|
||||
text = f'{min(round(percent), 100):d}%'.replace('100%', 'MAX')
|
||||
else:
|
||||
bar += '░' * (width - len(bar) - 4) + ' N/A'
|
||||
return bar.ljust(width)
|
||||
bar += '░' * (width - len(bar) - 4)
|
||||
text = 'N/A'
|
||||
if extra_text and len(f'{bar} {text} {extra_text}') <= width:
|
||||
return f'{bar} {text}'.ljust(width - len(extra_text) - 1) + f' {extra_text}'
|
||||
return f'{bar} {text}'.ljust(width)
|
||||
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@ from nvitop.gui.library import (
|
|||
BufferedHistoryGraph,
|
||||
Device,
|
||||
Displayable,
|
||||
GiB,
|
||||
bytes2human,
|
||||
colored,
|
||||
host,
|
||||
make_bar,
|
||||
timedelta2human,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -40,8 +43,8 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
|
||||
self.cpu_percent = None
|
||||
self.load_average = None
|
||||
self.memory_percent = None
|
||||
self.swap_percent = None
|
||||
self.virtual_memory = None
|
||||
self.swap_memory = None
|
||||
self._snapshot_daemon = threading.Thread(
|
||||
name='host-snapshot-daemon',
|
||||
target=self._snapshot_target,
|
||||
|
|
@ -91,7 +94,7 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
dynamic_bound=False,
|
||||
format='CPU: {:.1f}%'.format,
|
||||
)(host.cpu_percent)
|
||||
host.memory_percent = BufferedHistoryGraph(
|
||||
host.virtual_memory = BufferedHistoryGraph(
|
||||
interval=1.0,
|
||||
width=77,
|
||||
height=4,
|
||||
|
|
@ -99,9 +102,9 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
baseline=0.0,
|
||||
upperbound=100.0,
|
||||
dynamic_bound=False,
|
||||
format='MEM: {:.1f}%'.format,
|
||||
)(host.memory_percent)
|
||||
host.swap_percent = BufferedHistoryGraph(
|
||||
format='{:.1f}%'.format,
|
||||
)(host.virtual_memory, get_value=lambda vm: vm.percent)
|
||||
host.swap_memory = BufferedHistoryGraph(
|
||||
interval=1.0,
|
||||
width=77,
|
||||
height=1,
|
||||
|
|
@ -109,8 +112,8 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
baseline=0.0,
|
||||
upperbound=100.0,
|
||||
dynamic_bound=False,
|
||||
format='SWP: {:.1f}%'.format,
|
||||
)(host.swap_percent)
|
||||
format='{:.1f}%'.format,
|
||||
)(host.swap_memory, get_value=lambda sm: sm.percent)
|
||||
|
||||
def percentage(x):
|
||||
return f'{x:.1f}%' if x is not NA else NA
|
||||
|
|
@ -164,13 +167,13 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
|
||||
def take_snapshots(self):
|
||||
host.cpu_percent()
|
||||
host.memory_percent()
|
||||
host.swap_percent()
|
||||
host.virtual_memory()
|
||||
host.swap_memory()
|
||||
self.load_average = host.load_average()
|
||||
|
||||
self.cpu_percent = host.cpu_percent.history.last_value
|
||||
self.memory_percent = host.memory_percent.history.last_value
|
||||
self.swap_percent = host.swap_percent.history.last_value
|
||||
self.virtual_memory = host.virtual_memory.history.last_retval
|
||||
self.swap_memory = host.swap_memory.history.last_retval
|
||||
|
||||
total_memory_used = 0
|
||||
total_memory_total = 0
|
||||
|
|
@ -255,9 +258,23 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
if self.compact:
|
||||
width_right = len(load_average) + 4
|
||||
width_left = self.width - 2 - width_right
|
||||
cpu_bar = '[ {} ]'.format(make_bar('CPU', self.cpu_percent, width_left - 4))
|
||||
memory_bar = '[ {} ]'.format(make_bar('MEM', self.memory_percent, width_left - 4))
|
||||
swap_bar = '[ {} ]'.format(make_bar('SWP', self.swap_percent, width_right - 4))
|
||||
cpu_bar = '[ {} ]'.format(
|
||||
make_bar(
|
||||
'CPU',
|
||||
self.cpu_percent,
|
||||
width_left - 4,
|
||||
extra_text=f' UPTIME: {timedelta2human(host.uptime(), round=True)}',
|
||||
),
|
||||
)
|
||||
memory_bar = '[ {} ]'.format(
|
||||
make_bar(
|
||||
'MEM',
|
||||
self.virtual_memory.percent,
|
||||
width_left - 4,
|
||||
extra_text=f' USED: {bytes2human(self.virtual_memory.used, min_unit=GiB)}',
|
||||
),
|
||||
)
|
||||
swap_bar = '[ {} ]'.format(make_bar('SWP', self.swap_memory.percent, width_right - 4))
|
||||
self.addstr(self.y, self.x, f'{cpu_bar} ( {load_average} )')
|
||||
self.addstr(self.y + 1, self.x, f'{memory_bar} {swap_bar}')
|
||||
self.color_at(self.y, self.x, width=len(cpu_bar), fg='cyan', attr='bold')
|
||||
|
|
@ -305,11 +322,11 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
self.addstr(y, self.x + 1, line)
|
||||
|
||||
self.color(fg='magenta')
|
||||
for y, line in enumerate(host.memory_percent.history.graph, start=self.y + 6):
|
||||
for y, line in enumerate(host.virtual_memory.history.graph, start=self.y + 6):
|
||||
self.addstr(y, self.x + 1, line)
|
||||
|
||||
self.color(fg='blue')
|
||||
for y, line in enumerate(host.swap_percent.history.graph, start=self.y + 10):
|
||||
for y, line in enumerate(host.swap_memory.history.graph, start=self.y + 10):
|
||||
self.addstr(y, self.x + 1, line)
|
||||
|
||||
if self.width >= 100:
|
||||
|
|
@ -342,12 +359,12 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
self.addstr(
|
||||
self.y + 9,
|
||||
self.x + 1,
|
||||
f' {host.memory_percent.history} ',
|
||||
f' MEM: {bytes2human(self.virtual_memory.used, min_unit=GiB)} ({host.virtual_memory.history}) ',
|
||||
)
|
||||
self.addstr(
|
||||
self.y + 10,
|
||||
self.x + 1,
|
||||
f' {host.swap_percent.history} ',
|
||||
f' SWP: {bytes2human(self.swap_memory.used, min_unit=GiB)} ({host.swap_memory.history}) ',
|
||||
)
|
||||
if self.width >= 100:
|
||||
self.addstr(self.y, self.x + 79, f' {gpu_memory_percent} ')
|
||||
|
|
@ -364,8 +381,8 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
|
||||
def print(self):
|
||||
self.cpu_percent = host.cpu_percent()
|
||||
self.memory_percent = host.memory_percent()
|
||||
self.swap_percent = host.swap_percent()
|
||||
self.virtual_memory = host.virtual_memory()
|
||||
self.swap_memory = host.swap_memory()
|
||||
self.load_average = host.load_average()
|
||||
|
||||
if self.load_average is not None:
|
||||
|
|
@ -378,9 +395,23 @@ class HostPanel(Displayable): # pylint: disable=too-many-instance-attributes
|
|||
|
||||
width_right = len(load_average) + 4
|
||||
width_left = self.width - 2 - width_right
|
||||
cpu_bar = '[ {} ]'.format(make_bar('CPU', self.cpu_percent, width_left - 4))
|
||||
memory_bar = '[ {} ]'.format(make_bar('MEM', self.memory_percent, width_left - 4))
|
||||
swap_bar = '[ {} ]'.format(make_bar('SWP', self.swap_percent, width_right - 4))
|
||||
cpu_bar = '[ {} ]'.format(
|
||||
make_bar(
|
||||
'CPU',
|
||||
self.cpu_percent,
|
||||
width_left - 4,
|
||||
extra_text=f' UPTIME: {timedelta2human(host.uptime(), round=True)}',
|
||||
),
|
||||
)
|
||||
memory_bar = '[ {} ]'.format(
|
||||
make_bar(
|
||||
'MEM',
|
||||
self.virtual_memory.percent,
|
||||
width_left - 4,
|
||||
extra_text=f' USED: {bytes2human(self.virtual_memory.used, min_unit=GiB)}',
|
||||
),
|
||||
)
|
||||
swap_bar = '[ {} ]'.format(make_bar('SWP', self.swap_memory.percent, width_right - 4))
|
||||
|
||||
lines = [
|
||||
'{} {}'.format(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue