summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNico Schlömer <nico.schloemer@gmail.com>2022-03-07 14:20:35 +0100
committerGitHub <noreply@github.com>2022-03-07 14:20:35 +0100
commit6e77c746c6f8e533e60fd35ee41b6244a97c14f6 (patch)
tree71c582a5cc7a4c5aa9f5fe8ae76497c5e689b428
parent9727e7267fb6964709f42b1480e415dfbe879a2d (diff)
parent6de8201fac267c9d3b254f8139883de742b2bdc0 (diff)
Merge pull request #69 from nschloe/diskv0.2.0
Disk, relayout
-rw-r--r--src/tiptop/__about__.py2
-rw-r--r--src/tiptop/_app.py35
-rw-r--r--src/tiptop/_battery.py6
-rw-r--r--src/tiptop/_cpu.py13
-rw-r--r--src/tiptop/_disk.py163
-rw-r--r--src/tiptop/_helpers.py2
-rw-r--r--src/tiptop/_mem.py6
-rw-r--r--src/tiptop/_net.py9
-rw-r--r--src/tiptop/_procs_list.py43
9 files changed, 234 insertions, 45 deletions
diff --git a/src/tiptop/__about__.py b/src/tiptop/__about__.py
index 9cb17e7..d3ec452 100644
--- a/src/tiptop/__about__.py
+++ b/src/tiptop/__about__.py
@@ -1 +1 @@
-__version__ = "0.1.8"
+__version__ = "0.2.0"
diff --git a/src/tiptop/_app.py b/src/tiptop/_app.py
index e0bfc1c..2a53505 100644
--- a/src/tiptop/_app.py
+++ b/src/tiptop/_app.py
@@ -9,6 +9,7 @@ from textual.app import App
from .__about__ import __version__
from ._battery import Battery
from ._cpu import CPU
+from ._disk import Disk
from ._info import InfoLine
from ._mem import Mem
from ._net import Net
@@ -71,8 +72,8 @@ def run(argv=None):
# 34/55: approx golden ratio. See
# <https://gist.github.com/nschloe/ab6c3c90b4a6bc02c40405803fa8fa35>
# for the error.
- grid.add_column(fraction=34, name="left")
- grid.add_column(fraction=55, name="right")
+ grid.add_column(fraction=55, name="left")
+ grid.add_column(fraction=34, name="right")
if psutil.sensors_battery() is None:
grid.add_row(size=1, name="topline")
@@ -94,25 +95,27 @@ def run(argv=None):
area3=ProcsList(),
)
else:
- grid.add_row(size=1, name="topline")
- grid.add_row(fraction=3, name="top")
- grid.add_row(fraction=1, name="center1")
- grid.add_row(fraction=3, name="center2")
- grid.add_row(fraction=3, name="bottom")
+ grid.add_row(size=1, name="r0")
+ grid.add_row(fraction=17, name="r1")
+ grid.add_row(fraction=4, name="r2")
+ grid.add_row(fraction=15, name="r3")
+ grid.add_row(fraction=15, name="r4")
grid.add_areas(
- area0="left-start|right-end,topline",
- area1="left-start|right-end,top",
- area2a="left,center1",
- area2b="left,center2",
- area2c="left,bottom",
- area3="right,center1-start|bottom-end",
+ area0="left-start|right-end,r0",
+ area1="left,r1",
+ area2a="right,r1",
+ area2b="right,r2",
+ area2c="right,r3",
+ area2d="right,r4",
+ area3="left,r2-start|r4-end",
)
grid.place(
area0=InfoLine(),
area1=CPU(),
- area2a=Battery(),
- area2b=Mem(),
- area2c=Net(args.net),
+ area2a=Mem(),
+ area2b=Battery(),
+ area2c=Disk(),
+ area2d=Net(args.net),
area3=ProcsList(),
)
diff --git a/src/tiptop/_battery.py b/src/tiptop/_battery.py
index 68dbaf0..2d6ed96 100644
--- a/src/tiptop/_battery.py
+++ b/src/tiptop/_battery.py
@@ -14,7 +14,8 @@ class Battery(Widget):
"",
title="",
title_align="left",
- border_style="yellow",
+ # border_style="yellow",
+ border_style="white",
box=box.SQUARE,
)
self.collect_data()
@@ -23,6 +24,7 @@ class Battery(Widget):
def collect_data(self):
bat = psutil.sensors_battery()
+ assert bat is not None
self.bat_stream.add_value(bat.percent)
@@ -40,7 +42,7 @@ class Battery(Widget):
time_left_str.append(f"{mm}min")
status = " ".join(time_left_str) + " left"
- title = f"battery - {self.bat_stream.values[-1]:.1f}% - {status}"
+ title = f"[b]battery[/] - {self.bat_stream.values[-1]:.1f}% - {status}"
if bat.percent < 15 and not bat.power_plugged:
title = "[red reverse bold]" + title + "[/]"
diff --git a/src/tiptop/_cpu.py b/src/tiptop/_cpu.py
index 75c7f75..93c8198 100644
--- a/src/tiptop/_cpu.py
+++ b/src/tiptop/_cpu.py
@@ -8,8 +8,6 @@ from textual.widget import Widget
from .braille_stream import BrailleStream
-# from textual import log
-
def val_to_color(val: float, minval: float, maxval: float) -> str:
t = (val - minval) / (maxval - minval)
@@ -125,17 +123,18 @@ class CPU(Widget):
box=box.SQUARE,
expand=False,
)
- self.info_box_width = 0
brand_raw = cpuinfo.get_cpu_info()["brand_raw"]
self.panel = Panel(
"",
- title=f"cpu - {brand_raw}",
+ title=f"[b]cpu[/] - {brand_raw}",
title_align="left",
- border_style="blue",
+ border_style="white",
box=box.SQUARE,
)
+ # immediately collect data to refresh info_box_width
+ self.collect_data()
self.set_interval(2.0, self.collect_data)
def collect_data(self):
@@ -238,6 +237,10 @@ class CPU(Widget):
# https://github.com/nschloe/tiptop/issues/25
self.info_box.subtitle = None
else:
+ if psutil.__version__ == "5.9.0":
+ # Work around
+ # https://github.com/giampaolo/psutil/issues/2049
+ cpu_freq *= 1000
self.info_box.subtitle = f"{round(cpu_freq):4d} MHz"
# https://github.com/willmcgugan/rich/discussions/1559#discussioncomment-1459008
diff --git a/src/tiptop/_disk.py b/src/tiptop/_disk.py
new file mode 100644
index 0000000..40bbf9e
--- /dev/null
+++ b/src/tiptop/_disk.py
@@ -0,0 +1,163 @@
+from __future__ import annotations
+
+import psutil
+from rich import box
+from rich.console import Group
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from textual.widget import Widget
+
+from ._helpers import sizeof_fmt
+from .braille_stream import BrailleStream
+
+
+class Disk(Widget):
+ def __init__(self):
+ super().__init__()
+
+ def on_mount(self):
+ self.down_box = Panel(
+ "",
+ title="read",
+ title_align="left",
+ style="green",
+ width=20,
+ box=box.SQUARE,
+ )
+ self.up_box = Panel(
+ "",
+ title="write",
+ title_align="left",
+ style="blue",
+ width=20,
+ box=box.SQUARE,
+ )
+ self.table = Table(expand=True, show_header=False, padding=0, box=None)
+ # Add ratio 1 to expand that column as much as possible
+ self.table.add_column("graph", no_wrap=True, ratio=1)
+ self.table.add_column("box", no_wrap=True, width=20)
+ self.table.add_row("", self.down_box)
+ self.table.add_row("", self.up_box)
+
+ # kick out /dev/loop* devices
+ self.mountpoints = [
+ item.mountpoint
+ for item in psutil.disk_partitions()
+ if not item.device.startswith("/dev/loop")
+ ]
+ self.total = [
+ sizeof_fmt(psutil.disk_usage(mp).total, fmt=".1f")
+ for mp in self.mountpoints
+ ]
+
+ self.group = Group(self.table, "")
+ self.panel = Panel(
+ self.group,
+ title="[b]disk[/]",
+ # border_style="magenta",
+ border_style="white",
+ title_align="left",
+ box=box.SQUARE,
+ )
+
+ self.last_io = None
+ self.max_read_bytes_s = 0
+ self.max_read_bytes_s_str = ""
+ self.max_write_bytes_s = 0
+ self.max_write_bytes_s_str = ""
+
+ self.read_stream = BrailleStream(20, 5, 0.0, 1.0e6)
+ self.write_stream = BrailleStream(20, 5, 0.0, 1.0e6, flipud=True)
+
+ self.refresh_panel()
+
+ self.interval_s = 2.0
+ self.set_interval(self.interval_s, self.refresh_panel)
+
+ def refresh_panel(self):
+ io = psutil.disk_io_counters()
+
+ if self.last_io is None:
+ read_bytes_s_string = ""
+ write_bytes_s_string = ""
+ else:
+ read_bytes_s = (io.read_bytes - self.last_io.read_bytes) / self.interval_s
+ read_bytes_s_string = sizeof_fmt(read_bytes_s, fmt=".1f") + "/s"
+ write_bytes_s = (
+ io.write_bytes - self.last_io.write_bytes
+ ) / self.interval_s
+ write_bytes_s_string = sizeof_fmt(write_bytes_s, fmt=".1f") + "/s"
+
+ if read_bytes_s > self.max_read_bytes_s:
+ self.max_read_bytes_s = read_bytes_s
+ self.max_read_bytes_s_str = sizeof_fmt(read_bytes_s, fmt=".1f") + "/s"
+
+ if write_bytes_s > self.max_write_bytes_s:
+ self.max_write_bytes_s = write_bytes_s
+ self.max_write_bytes_s_str = sizeof_fmt(write_bytes_s, fmt=".1f") + "/s"
+
+ self.read_stream.add_value(read_bytes_s)
+ self.write_stream.add_value(write_bytes_s)
+
+ self.last_io = io
+
+ total_read_string = sizeof_fmt(io.read_bytes, sep=" ", fmt=".1f")
+ total_write_string = sizeof_fmt(io.write_bytes, sep=" ", fmt=".1f")
+
+ self.down_box.renderable = "\n".join(
+ [
+ f"{read_bytes_s_string}",
+ f"max {self.max_read_bytes_s_str}",
+ f"total {total_read_string}",
+ ]
+ )
+ self.up_box.renderable = "\n".join(
+ [
+ f"{write_bytes_s_string}",
+ f"max {self.max_write_bytes_s_str}",
+ f"total {total_write_string}",
+ ]
+ )
+
+ self.table.columns[0]._cells[0] = (
+ "[green]" + "\n".join(self.read_stream.graph) + "[/]"
+ )
+ self.table.columns[0]._cells[1] = (
+ "[blue]" + "\n".join(self.write_stream.graph) + "[/]"
+ )
+
+ table = Table(box=None, expand=False, padding=(0, 1), show_header=True)
+ table.add_column("", justify="left", no_wrap=True)
+ table.add_column(Text("free", justify="left"), justify="right", no_wrap=True)
+ table.add_column(Text("used", justify="left"), justify="right", no_wrap=True)
+ table.add_column(Text("total", justify="left"), justify="right", no_wrap=True)
+ table.add_column("", justify="right", no_wrap=True)
+
+ for mp, total in zip(self.mountpoints, self.total):
+ du = psutil.disk_usage(mp)
+
+ style = None
+ if du.percent > 99:
+ style = "red reverse bold"
+ elif du.percent > 95:
+ style = "yellow"
+
+ table.add_row(
+ f"[b]{mp}[/]",
+ sizeof_fmt(du.free, fmt=".1f"),
+ sizeof_fmt(du.used, fmt=".1f"),
+ total,
+ f"({du.percent:.1f}%)",
+ style=style,
+ )
+ self.group.renderables[1] = table
+
+ self.refresh()
+
+ def render(self):
+ return self.panel
+
+ async def on_resize(self, event):
+ self.read_stream.reset_width(event.width - 25)
+ self.write_stream.reset_width(event.width - 25)
diff --git a/src/tiptop/_helpers.py b/src/tiptop/_helpers.py
index 9f996d2..5a58475 100644
--- a/src/tiptop/_helpers.py
+++ b/src/tiptop/_helpers.py
@@ -1,5 +1,5 @@
# https://stackoverflow.com/a/1094933/353337
-def sizeof_fmt(num, suffix: str = "iB", sep=" ", fmt=".0f"):
+def sizeof_fmt(num, fmt=".0f", suffix: str = "iB", sep=" "):
assert num >= 0
for unit in ["B", "K", "M", "G", "T", "P", "E", "Z"]:
# actually 1024, but be economical with the return string size:
diff --git a/src/tiptop/_mem.py b/src/tiptop/_mem.py
index c3bae8a..7c009ef 100644
--- a/src/tiptop/_mem.py
+++ b/src/tiptop/_mem.py
@@ -45,12 +45,14 @@ class Mem(Widget):
mem_total_string = sizeof_fmt(self.mem_total_bytes, fmt=".2f")
self.panel = Panel(
self.table,
- title=f"mem - {mem_total_string}",
+ title=f"[b]mem[/] - {mem_total_string}",
title_align="left",
- border_style="green",
+ # border_style="green",
+ border_style="white",
box=box.SQUARE,
)
+ self.refresh_table()
self.set_interval(2.0, self.refresh_table)
def refresh_table(self):
diff --git a/src/tiptop/_net.py b/src/tiptop/_net.py
index b216444..6234b98 100644
--- a/src/tiptop/_net.py
+++ b/src/tiptop/_net.py
@@ -9,6 +9,7 @@ from rich.panel import Panel
from rich.table import Table
from textual.widget import Widget
+from .__about__ import __version__
from ._helpers import sizeof_fmt
from .braille_stream import BrailleStream
@@ -50,6 +51,7 @@ def _autoselect_interface():
class Net(Widget):
def __init__(self, interface: str | None = None):
self.interface = _autoselect_interface() if interface is None else interface
+ self.tiptop_string = f"tiptop v{__version__}"
super().__init__()
def on_mount(self):
@@ -79,10 +81,13 @@ class Net(Widget):
self.group = Group(self.table, "", "")
self.panel = Panel(
self.group,
- title=f"net - {self.interface}",
- border_style="red",
+ title=f"[b]net[/] - {self.interface}",
+ # border_style="red",
+ border_style="white",
title_align="left",
box=box.SQUARE,
+ subtitle=self.tiptop_string,
+ subtitle_align="right",
)
self.last_net = None
diff --git a/src/tiptop/_procs_list.py b/src/tiptop/_procs_list.py
index fd0a92e..8c69052 100644
--- a/src/tiptop/_procs_list.py
+++ b/src/tiptop/_procs_list.py
@@ -2,15 +2,14 @@ import psutil
from rich import box
from rich.panel import Panel
from rich.table import Table
+from rich.text import Text
from textual.widget import Widget
-from .__about__ import __version__
from ._helpers import sizeof_fmt
class ProcsList(Widget):
def on_mount(self):
- self.tiptop_string = f"tiptop v{__version__}"
self.max_num_procs = 100
self.collect_data()
self.set_interval(6.0, self.collect_data)
@@ -51,21 +50,34 @@ class ProcsList(Widget):
show_header=True,
header_style="bold",
box=None,
- padding=0,
+ padding=(0, 1),
expand=True,
)
- table.add_column("pid", min_width=6, no_wrap=True)
- table.add_column("program", max_width=10, style="green", no_wrap=True)
- table.add_column("args", max_width=20, no_wrap=True)
- table.add_column("thr", width=3, style="green", no_wrap=True)
+ # set ration=1 on all columns that should be expanded
+ # <https://github.com/Textualize/rich/issues/2030>
+ table.add_column(Text("pid", justify="left"), no_wrap=True, justify="right")
+ table.add_column("program", style="green", no_wrap=True, ratio=1)
+ table.add_column("args", no_wrap=True, ratio=2)
+ table.add_column(
+ Text("thr", justify="left"),
+ style="green",
+ no_wrap=True,
+ justify="right",
+ )
table.add_column("user", no_wrap=True)
- table.add_column("mem", style="green", no_wrap=True)
- table.add_column("[u]cpu%[/]", width=5, no_wrap=True)
+ table.add_column(
+ Text("mem", justify="left"), style="green", no_wrap=True, justify="right"
+ )
+ table.add_column(
+ Text("cpu%", style="u", justify="left"),
+ no_wrap=True,
+ justify="right",
+ )
for p in processes[: self.max_num_procs]:
# Everything can be None here, see comment above
pid = p.info["pid"]
- pid = "" if pid is None else f"{pid:6d}"
+ pid = "" if pid is None else str(pid)
#
name = p.info["name"]
if name is None:
@@ -75,7 +87,7 @@ class ProcsList(Widget):
cmdline = "" if cmdline is None else " ".join(p.info["cmdline"][1:])
#
num_threads = p.info["num_threads"]
- num_threads = "" if num_threads is None else f"{num_threads:3d}"
+ num_threads = "" if num_threads is None else str(num_threads)
#
username = p.info["username"]
if username is None:
@@ -87,7 +99,7 @@ class ProcsList(Widget):
)
#
cpu_percent = p.info["cpu_percent"]
- cpu_percent = "" if cpu_percent is None else f"{cpu_percent:5.1f}"
+ cpu_percent = "" if cpu_percent is None else f"{cpu_percent:.1f}"
table.add_row(
pid, name, cmdline, num_threads, username, mem_info, cpu_percent
)
@@ -97,11 +109,10 @@ class ProcsList(Widget):
self.panel = Panel(
table,
- title=f"proc - {len(processes)} ({total_num_threads} thr), {num_sleep} slp",
+ title=f"[b]proc[/] - {len(processes)} ({total_num_threads} thr), {num_sleep} slp",
title_align="left",
- subtitle=self.tiptop_string,
- subtitle_align="right",
- border_style="cyan",
+ # border_style="cyan",
+ border_style="white",
box=box.SQUARE,
)