sot 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sot/__about__.py +4 -0
- sot/__init__.py +6 -0
- sot/_app.py +107 -0
- sot/_battery.py +62 -0
- sot/_cpu.py +346 -0
- sot/_disk.py +187 -0
- sot/_helpers.py +14 -0
- sot/_info.py +84 -0
- sot/_mem.py +106 -0
- sot/_net.py +223 -0
- sot/_procs_list.py +139 -0
- sot/_sot.py +37 -0
- sot/blockchar_stream.py +28 -0
- sot/braille_stream.py +149 -0
- sot-1.0.0.dist-info/LICENSE +22 -0
- sot-1.0.0.dist-info/METADATA +120 -0
- sot-1.0.0.dist-info/RECORD +20 -0
- sot-1.0.0.dist-info/WHEEL +5 -0
- sot-1.0.0.dist-info/entry_points.txt +2 -0
- sot-1.0.0.dist-info/top_level.txt +1 -0
sot/__about__.py
ADDED
sot/__init__.py
ADDED
sot/_app.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from sys import version_info
|
|
5
|
+
|
|
6
|
+
from textual.app import App
|
|
7
|
+
|
|
8
|
+
from .__about__ import __version__, __current_year__
|
|
9
|
+
from ._cpu import CPU
|
|
10
|
+
from ._disk import Disk
|
|
11
|
+
from ._info import InfoLine
|
|
12
|
+
from ._mem import Mem
|
|
13
|
+
from ._net import Net
|
|
14
|
+
from ._procs_list import ProcsList
|
|
15
|
+
# from ._sot import Sot
|
|
16
|
+
|
|
17
|
+
def run(argv=None):
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
description="Command-line System Obervation Tool ≈",
|
|
20
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
21
|
+
add_help=False
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--help",
|
|
26
|
+
"-H",
|
|
27
|
+
action="help",
|
|
28
|
+
default=argparse.SUPPRESS,
|
|
29
|
+
help="Show this help message and exit."
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--version",
|
|
34
|
+
"-V",
|
|
35
|
+
action="version",
|
|
36
|
+
version=_get_version_text(),
|
|
37
|
+
help="Display version information",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--log",
|
|
42
|
+
"-L",
|
|
43
|
+
type=str,
|
|
44
|
+
default=None,
|
|
45
|
+
help="Debug log file",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--net",
|
|
50
|
+
"-N",
|
|
51
|
+
type=str,
|
|
52
|
+
default=None,
|
|
53
|
+
help="Network interface to display (default: auto)",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
args = parser.parse_args(argv)
|
|
57
|
+
|
|
58
|
+
# with a grid
|
|
59
|
+
class sotApp(App):
|
|
60
|
+
async def on_mount(self) -> None:
|
|
61
|
+
grid = await self.view.dock_grid(edge="left")
|
|
62
|
+
|
|
63
|
+
grid.add_column(fraction=36, name="left")
|
|
64
|
+
# grid.add_column(fraction=15, name="middle")
|
|
65
|
+
# grid.add_column(fraction=36, name="right")
|
|
66
|
+
grid.add_column(fraction=45, name="right")
|
|
67
|
+
|
|
68
|
+
grid.add_row(size=1, name="r0")
|
|
69
|
+
grid.add_row(fraction=1, name="r1")
|
|
70
|
+
grid.add_row(fraction=1, name="r2")
|
|
71
|
+
grid.add_row(fraction=1, name="r3")
|
|
72
|
+
grid.add_areas(
|
|
73
|
+
area0="left-start|right-end,r0",
|
|
74
|
+
area1="left,r1",
|
|
75
|
+
# area2a="middle,r1-start|r1-end",
|
|
76
|
+
# area2b="middle,r2-start|r2-end",
|
|
77
|
+
# area2c="middle,r3-start|r3-end",
|
|
78
|
+
area3a="right,r1",
|
|
79
|
+
area3b="right,r2",
|
|
80
|
+
area3c="right,r3",
|
|
81
|
+
area4="left,r2-start|r3-end",
|
|
82
|
+
)
|
|
83
|
+
grid.place(
|
|
84
|
+
area0=InfoLine(),
|
|
85
|
+
area1=CPU(),
|
|
86
|
+
# area2b=Sot(),
|
|
87
|
+
area3a=Mem(),
|
|
88
|
+
area3b=Disk(),
|
|
89
|
+
area3c=Net(args.net),
|
|
90
|
+
area4=ProcsList(),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async def on_load(self, _):
|
|
94
|
+
await self.bind("q", "quit", "quit")
|
|
95
|
+
|
|
96
|
+
sotApp.run(log=args.log)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_version_text():
|
|
100
|
+
python_version = f"{version_info.major}.{version_info.minor}.{version_info.micro}"
|
|
101
|
+
|
|
102
|
+
return "\n".join(
|
|
103
|
+
[
|
|
104
|
+
f"sot {__version__} [Python {python_version}]",
|
|
105
|
+
f"Copyright (c) 2024-{__current_year__} Kumar Anirudha",
|
|
106
|
+
]
|
|
107
|
+
)
|
sot/_battery.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
from rich import box
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
from textual.widget import Widget
|
|
6
|
+
|
|
7
|
+
from .braille_stream import BrailleStream
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Battery(Widget):
|
|
11
|
+
def on_mount(self):
|
|
12
|
+
self.bat_stream = BrailleStream(40, 3, 0.0, 100.0)
|
|
13
|
+
|
|
14
|
+
self.panel = Panel(
|
|
15
|
+
"",
|
|
16
|
+
title="",
|
|
17
|
+
title_align="left",
|
|
18
|
+
# border_style="yellow",
|
|
19
|
+
border_style="bright_black",
|
|
20
|
+
box=box.SQUARE,
|
|
21
|
+
)
|
|
22
|
+
self.collect_data()
|
|
23
|
+
# update frequency: 1 min
|
|
24
|
+
self.set_interval(60.0, self.collect_data)
|
|
25
|
+
|
|
26
|
+
def collect_data(self):
|
|
27
|
+
bat = psutil.sensors_battery()
|
|
28
|
+
assert bat is not None
|
|
29
|
+
|
|
30
|
+
self.bat_stream.add_value(bat.percent)
|
|
31
|
+
self.refresh_graph()
|
|
32
|
+
|
|
33
|
+
if bat.power_plugged:
|
|
34
|
+
status = "charging"
|
|
35
|
+
else:
|
|
36
|
+
mm = bat.secsleft // 60
|
|
37
|
+
hh, mm = divmod(mm, 60)
|
|
38
|
+
time_left_str = []
|
|
39
|
+
if hh > 0:
|
|
40
|
+
time_left_str.append(f"{hh}h")
|
|
41
|
+
if mm > 0:
|
|
42
|
+
time_left_str.append(f"{mm}min")
|
|
43
|
+
status = " ".join(time_left_str) + " left"
|
|
44
|
+
|
|
45
|
+
title = f"[b]battery[/] - {self.bat_stream.values[-1]:.1f}% - {status}"
|
|
46
|
+
if bat.percent < 15 and not bat.power_plugged:
|
|
47
|
+
title = "[red3 reverse bold]" + title + "[/]"
|
|
48
|
+
|
|
49
|
+
self.panel.title = title
|
|
50
|
+
|
|
51
|
+
self.refresh()
|
|
52
|
+
|
|
53
|
+
def refresh_graph(self):
|
|
54
|
+
self.panel.renderable = Text("\n".join(self.bat_stream.graph), style="yellow")
|
|
55
|
+
|
|
56
|
+
def render(self) -> Panel:
|
|
57
|
+
return self.panel
|
|
58
|
+
|
|
59
|
+
async def on_resize(self, event):
|
|
60
|
+
self.bat_stream.reset_width(event.width - 4)
|
|
61
|
+
self.bat_stream.reset_height(event.height - 2)
|
|
62
|
+
self.refresh_graph()
|
sot/_cpu.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import psutil
|
|
5
|
+
from rich import box
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
from textual.widget import Widget
|
|
10
|
+
|
|
11
|
+
from .braille_stream import BrailleStream
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def val_to_color(val: float, minval: float, maxval: float) -> str:
|
|
15
|
+
t = (val - minval) / (maxval - minval)
|
|
16
|
+
k = round(t * 3)
|
|
17
|
+
return {0: "yellow", 1: "dark_orange", 2: "sky_blue3", 3: "aquamarine3"}[k]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# https://stackoverflow.com/a/312464/353337
|
|
21
|
+
def chunks(lst, n):
|
|
22
|
+
for i in range(0, len(lst), n):
|
|
23
|
+
yield lst[i : i + n]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# https://stackoverflow.com/a/6473724/353337
|
|
27
|
+
def transpose(lst):
|
|
28
|
+
return list(map(list, zip(*lst)))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def flatten(lst):
|
|
32
|
+
return [item for sublist in lst for item in sublist]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_cpu_model():
|
|
36
|
+
# cpuinfo does computation in addition to reading data, see
|
|
37
|
+
# <https://github.com/workhorsy/py-cpuinfo/issues/155#issuecomment-678923252>.
|
|
38
|
+
# Try to work around this.
|
|
39
|
+
# TODO keep an eye on the above bug.
|
|
40
|
+
try:
|
|
41
|
+
# On Linux, the file contains lines like
|
|
42
|
+
# ```
|
|
43
|
+
# model name : Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
|
|
44
|
+
# ```
|
|
45
|
+
with open("/proc/cpuinfo") as f:
|
|
46
|
+
content = f.read()
|
|
47
|
+
m = re.search(r"model name\t: (.*)", content)
|
|
48
|
+
model_name = m.group(1)
|
|
49
|
+
except Exception:
|
|
50
|
+
import cpuinfo
|
|
51
|
+
|
|
52
|
+
model_name = cpuinfo.get_cpu_info()["brand_raw"]
|
|
53
|
+
return model_name
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_current_temps():
|
|
57
|
+
# First try manually reading the temperatures. (pyutil is slow.)
|
|
58
|
+
for key in ["coretemp", "k10temp"]:
|
|
59
|
+
path = Path(f"/sys/devices/platform/{key}.0/hwmon/hwmon6/")
|
|
60
|
+
if not path.is_dir():
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
k = 1
|
|
64
|
+
temps = []
|
|
65
|
+
while True:
|
|
66
|
+
file = path / f"temp{k}_input"
|
|
67
|
+
if not file.exists():
|
|
68
|
+
break
|
|
69
|
+
with open(file) as f:
|
|
70
|
+
content = f.read()
|
|
71
|
+
temps.append(int(content) / 1000)
|
|
72
|
+
k += 1
|
|
73
|
+
|
|
74
|
+
return temps
|
|
75
|
+
|
|
76
|
+
# Not try psutil.sensors_temperatures().
|
|
77
|
+
# Slow, see <https://github.com/giampaolo/psutil/issues/2082>.
|
|
78
|
+
try:
|
|
79
|
+
temps = psutil.sensors_temperatures()
|
|
80
|
+
except AttributeError:
|
|
81
|
+
return None
|
|
82
|
+
else:
|
|
83
|
+
# coretemp: intel, k10temp: amd
|
|
84
|
+
# <https://github.com/nschloe/tiptop/issues/37>
|
|
85
|
+
for key in ["coretemp", "k10temp"]:
|
|
86
|
+
if key not in temps:
|
|
87
|
+
continue
|
|
88
|
+
return [t.current for t in temps[key]]
|
|
89
|
+
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_current_freq():
|
|
94
|
+
# psutil.cpu_freq() is slow, so first try something else:
|
|
95
|
+
# <https://github.com/nschloe/tiptop/issues/37>
|
|
96
|
+
candidates = [
|
|
97
|
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq",
|
|
98
|
+
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq",
|
|
99
|
+
]
|
|
100
|
+
for candidate in candidates:
|
|
101
|
+
if Path(candidate).exists():
|
|
102
|
+
with open(candidate) as f:
|
|
103
|
+
content = f.read()
|
|
104
|
+
return int(content) / 1000
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
cpu_freq = psutil.cpu_freq().current
|
|
108
|
+
except Exception:
|
|
109
|
+
# https://github.com/nschloe/tiptop/issues/25#issuecomment-1061390966
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
if psutil.__version__ == "5.9.0" and cpu_freq < 10:
|
|
113
|
+
# Work around <https://github.com/giampaolo/psutil/issues/2049>
|
|
114
|
+
cpu_freq *= 1000
|
|
115
|
+
return cpu_freq
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class CPU(Widget):
|
|
119
|
+
def on_mount(self):
|
|
120
|
+
self.width = 0
|
|
121
|
+
self.height = 0
|
|
122
|
+
|
|
123
|
+
# self.max_graph_width = 200
|
|
124
|
+
|
|
125
|
+
self.num_cores = psutil.cpu_count(logical=False)
|
|
126
|
+
num_threads = psutil.cpu_count(logical=True)
|
|
127
|
+
|
|
128
|
+
# 8 threads, 4 cores -> [[0, 4], [1, 5], [2, 6], [3, 7]]
|
|
129
|
+
assert num_threads % self.num_cores == 0
|
|
130
|
+
self.core_threads = transpose(list(chunks(range(num_threads), self.num_cores)))
|
|
131
|
+
|
|
132
|
+
self.cpu_total_stream = BrailleStream(50, 7, 0.0, 100.0)
|
|
133
|
+
|
|
134
|
+
self.thread_load_streams = [
|
|
135
|
+
BrailleStream(10, 1, 0.0, 100.0)
|
|
136
|
+
for _ in range(num_threads)
|
|
137
|
+
# BlockCharStream(10, 1, 0.0, 100.0) for _ in range(num_threads)
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
temps = get_current_temps()
|
|
141
|
+
|
|
142
|
+
if temps is None:
|
|
143
|
+
self.has_cpu_temp = False
|
|
144
|
+
self.has_core_temps = False
|
|
145
|
+
else:
|
|
146
|
+
self.has_cpu_temp = len(temps) > 0
|
|
147
|
+
self.has_core_temps = len(temps) == 1 + self.num_cores
|
|
148
|
+
|
|
149
|
+
temp_low = 30.0
|
|
150
|
+
# TODO read from file
|
|
151
|
+
temp_high = 100.0
|
|
152
|
+
|
|
153
|
+
if self.has_cpu_temp:
|
|
154
|
+
self.temp_total_stream = BrailleStream(
|
|
155
|
+
50, 7, temp_low, temp_high, flipud=True
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if self.has_core_temps:
|
|
159
|
+
self.core_temp_streams = [
|
|
160
|
+
BrailleStream(5, 1, temp_low, temp_high)
|
|
161
|
+
for _ in range(self.num_cores)
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
self.has_fan_rpm = False
|
|
165
|
+
try:
|
|
166
|
+
fan_current = list(psutil.sensors_fans().values())[0][0].current
|
|
167
|
+
except (AttributeError, IndexError):
|
|
168
|
+
pass
|
|
169
|
+
else:
|
|
170
|
+
self.has_fan_rpm = True
|
|
171
|
+
fan_low = 0
|
|
172
|
+
# Sometimes, psutil/computers will incorrectly report a fan
|
|
173
|
+
# speed of 2 ** 16 - 1; dismiss that.
|
|
174
|
+
if fan_current == 65535:
|
|
175
|
+
fan_current = 1
|
|
176
|
+
fan_high = max(fan_current, 1)
|
|
177
|
+
self.fan_stream = BrailleStream(50, 1, fan_low, fan_high)
|
|
178
|
+
|
|
179
|
+
box_title = ", ".join(
|
|
180
|
+
[
|
|
181
|
+
f"{self.num_cores} core" + ("s" if self.num_cores > 1 else ""),
|
|
182
|
+
f"{num_threads} thread" + ("s" if num_threads > 1 else ""),
|
|
183
|
+
]
|
|
184
|
+
)
|
|
185
|
+
self.info_box = Panel(
|
|
186
|
+
"",
|
|
187
|
+
title=box_title,
|
|
188
|
+
title_align="left",
|
|
189
|
+
subtitle=None,
|
|
190
|
+
subtitle_align="left",
|
|
191
|
+
border_style="bright_black",
|
|
192
|
+
box=box.SQUARE,
|
|
193
|
+
expand=False,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
self.panel = Panel(
|
|
197
|
+
"",
|
|
198
|
+
title=f"[b]CPU[/] - {get_cpu_model()}",
|
|
199
|
+
title_align="left",
|
|
200
|
+
border_style="bright_black",
|
|
201
|
+
box=box.SQUARE,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# immediately collect data to refresh info_box_width
|
|
205
|
+
self.collect_data()
|
|
206
|
+
self.set_interval(2.0, self.collect_data)
|
|
207
|
+
|
|
208
|
+
def collect_data(self):
|
|
209
|
+
# CPU loads
|
|
210
|
+
self.cpu_total_stream.add_value(psutil.cpu_percent())
|
|
211
|
+
#
|
|
212
|
+
load_per_thread = psutil.cpu_percent(percpu=True)
|
|
213
|
+
assert isinstance(load_per_thread, list)
|
|
214
|
+
for stream, load in zip(self.thread_load_streams, load_per_thread):
|
|
215
|
+
stream.add_value(load)
|
|
216
|
+
|
|
217
|
+
# CPU temperatures
|
|
218
|
+
if self.has_cpu_temp or self.has_core_temps:
|
|
219
|
+
temps = get_current_temps()
|
|
220
|
+
assert temps is not None
|
|
221
|
+
|
|
222
|
+
if self.has_cpu_temp:
|
|
223
|
+
self.temp_total_stream.add_value(temps[0])
|
|
224
|
+
|
|
225
|
+
if self.has_core_temps:
|
|
226
|
+
for stream, temp in zip(self.core_temp_streams, temps[1:]):
|
|
227
|
+
stream.add_value(temp)
|
|
228
|
+
|
|
229
|
+
lines_cpu = self.cpu_total_stream.graph
|
|
230
|
+
current_val_string = f"{self.cpu_total_stream.values[-1]:5.1f}%"
|
|
231
|
+
lines0 = lines_cpu[0][: -len(current_val_string)] + current_val_string
|
|
232
|
+
lines_cpu = [lines0] + lines_cpu[1:]
|
|
233
|
+
#
|
|
234
|
+
cpu_total_graph = "[yellow]" + "\n".join(lines_cpu) + "[/]\n"
|
|
235
|
+
#
|
|
236
|
+
if self.has_cpu_temp:
|
|
237
|
+
lines_temp = self.temp_total_stream.graph
|
|
238
|
+
current_val_string = f"{round(self.temp_total_stream.values[-1]):3d}°C"
|
|
239
|
+
lines0 = lines_temp[-1][: -len(current_val_string)] + current_val_string
|
|
240
|
+
lines_temp = lines_temp[:-1] + [lines0]
|
|
241
|
+
cpu_total_graph += "[slate_blue1]" + "\n".join(lines_temp) + "[/]"
|
|
242
|
+
|
|
243
|
+
# construct right info box
|
|
244
|
+
self._refresh_info_box(load_per_thread)
|
|
245
|
+
|
|
246
|
+
t = Table(expand=True, show_header=False, padding=0, box=None)
|
|
247
|
+
# Add ratio 1 to expand that column as much as possible
|
|
248
|
+
t.add_column("graph", no_wrap=True, ratio=1)
|
|
249
|
+
t.add_column("box", no_wrap=True, justify="left", vertical="middle")
|
|
250
|
+
t.add_row(cpu_total_graph, self.info_box)
|
|
251
|
+
|
|
252
|
+
if self.has_fan_rpm:
|
|
253
|
+
fan_current = list(psutil.sensors_fans().values())[0][0].current
|
|
254
|
+
|
|
255
|
+
# See comment above
|
|
256
|
+
if fan_current == 65535:
|
|
257
|
+
fan_current = self.fan_stream.maxval
|
|
258
|
+
|
|
259
|
+
if fan_current > self.fan_stream.maxval:
|
|
260
|
+
# TODO There should be a method that also rewrites all previous values
|
|
261
|
+
self.fan_stream.maxval = fan_current
|
|
262
|
+
|
|
263
|
+
self.fan_stream.add_value(fan_current)
|
|
264
|
+
string = f" {fan_current}rpm"
|
|
265
|
+
graph = Text(
|
|
266
|
+
self.fan_stream.graph[-1][: -len(string)] + string,
|
|
267
|
+
style="dark_orange",
|
|
268
|
+
)
|
|
269
|
+
t.add_row(graph, "")
|
|
270
|
+
|
|
271
|
+
self.panel.renderable = t
|
|
272
|
+
|
|
273
|
+
# textual method
|
|
274
|
+
self.refresh()
|
|
275
|
+
|
|
276
|
+
def _refresh_info_box(self, load_per_thread):
|
|
277
|
+
lines = []
|
|
278
|
+
for core_id, thread_ids in enumerate(self.core_threads):
|
|
279
|
+
line = []
|
|
280
|
+
for i in thread_ids:
|
|
281
|
+
color = val_to_color(load_per_thread[i], 0.0, 100.0)
|
|
282
|
+
line.append(
|
|
283
|
+
f"[{color}]"
|
|
284
|
+
+ f"{self.thread_load_streams[i].graph[0]}"
|
|
285
|
+
+ f"{round(self.thread_load_streams[i].values[-1]):3d}%"
|
|
286
|
+
+ "[/]"
|
|
287
|
+
)
|
|
288
|
+
if self.has_core_temps:
|
|
289
|
+
stream = self.core_temp_streams[core_id]
|
|
290
|
+
val = stream.values[-1]
|
|
291
|
+
color = "slate_blue1" if val < 70.0 else "red3"
|
|
292
|
+
line.append(
|
|
293
|
+
f"[{color}]{stream.graph[0]} {round(stream.values[-1])}°C[/]"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
lines.append(" ".join(line))
|
|
297
|
+
|
|
298
|
+
self.info_box.renderable = "\n".join(lines)
|
|
299
|
+
|
|
300
|
+
cpu_freq = get_current_freq()
|
|
301
|
+
|
|
302
|
+
if cpu_freq is None:
|
|
303
|
+
# https://github.com/nschloe/tiptop/issues/25
|
|
304
|
+
self.info_box.subtitle = None
|
|
305
|
+
else:
|
|
306
|
+
self.info_box.subtitle = f"{round(cpu_freq):4d} MHz"
|
|
307
|
+
|
|
308
|
+
# https://github.com/willmcgugan/rich/discussions/1559#discussioncomment-1459008
|
|
309
|
+
self.info_box_width = 4 + len(Text.from_markup(lines[0]))
|
|
310
|
+
|
|
311
|
+
def render(self):
|
|
312
|
+
return self.panel
|
|
313
|
+
|
|
314
|
+
async def on_resize(self, event):
|
|
315
|
+
self.width = event.width
|
|
316
|
+
self.height = event.height
|
|
317
|
+
|
|
318
|
+
# reset graph widths
|
|
319
|
+
graph_width = self.width - self.info_box_width - 5
|
|
320
|
+
self.cpu_total_stream.reset_width(graph_width)
|
|
321
|
+
if self.has_cpu_temp:
|
|
322
|
+
self.temp_total_stream.reset_width(graph_width)
|
|
323
|
+
if self.has_fan_rpm:
|
|
324
|
+
self.fan_stream.reset_width(graph_width)
|
|
325
|
+
|
|
326
|
+
# reset graph heights
|
|
327
|
+
# subtract border
|
|
328
|
+
total_height = self.height - 2
|
|
329
|
+
if self.has_cpu_temp:
|
|
330
|
+
# cpu total stream height: divide by two and round _down_
|
|
331
|
+
self.cpu_total_stream.reset_height(total_height // 2)
|
|
332
|
+
#
|
|
333
|
+
# temp total stream height: divide by two and round _up_. Subtract
|
|
334
|
+
# one if a fan stream is present.
|
|
335
|
+
temp_stream_height = -((-total_height) // 2)
|
|
336
|
+
if self.has_fan_rpm:
|
|
337
|
+
temp_stream_height -= 1
|
|
338
|
+
|
|
339
|
+
self.temp_total_stream.reset_height(temp_stream_height)
|
|
340
|
+
else:
|
|
341
|
+
# full size cpu stream
|
|
342
|
+
cpu_stream_height = total_height
|
|
343
|
+
if self.has_fan_rpm:
|
|
344
|
+
cpu_stream_height -= 1
|
|
345
|
+
|
|
346
|
+
self.cpu_total_stream.reset_height(cpu_stream_height)
|