improve layout: decouple labels from values
This commit is contained in:
parent
34019e3c19
commit
7e8e4a4ff2
3 changed files with 52 additions and 28 deletions
75
gpustats.py
75
gpustats.py
|
@ -1,14 +1,18 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
"""Pretty Textual-based stats for AMD GPUs
|
"""Pretty Textual-based stats for AMD GPUs
|
||||||
|
|
||||||
TODO: restore argparse / --card, in case detection fails"""
|
TODO: restore argparse / --card, in case detection fails
|
||||||
|
|
||||||
|
rich markup reference:
|
||||||
|
https://rich.readthedocs.io/en/stable/markup.html
|
||||||
|
"""
|
||||||
from os import path
|
from os import path
|
||||||
import glob
|
import glob
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# from textual import events
|
# from textual import events
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Container
|
from textual.containers import Container, Horizontal
|
||||||
from textual.reactive import reactive
|
from textual.reactive import reactive
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Header, Footer, Static, TextLog, Label
|
from textual.widgets import Header, Footer, Static, TextLog, Label
|
||||||
|
@ -143,7 +147,10 @@ class MiscDisplay(Static):
|
||||||
self.timer_misc = None
|
self.timer_misc = None
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Label("Misc:", classes="statlabel")
|
yield Horizontal(Label("Utilization:",), Label("", id="util_pct", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("Temperature:",), Label("", id="temp_c", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("[underline]Current[/] fan RPM:",), Label("", id="fan_rpm", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("[underline]Target[/] fan RPM:",), Label("", id="fan_rpm_target", classes="statvalue"))
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""Event handler called when widget is added to the app."""
|
"""Event handler called when widget is added to the app."""
|
||||||
|
@ -151,7 +158,9 @@ class MiscDisplay(Static):
|
||||||
|
|
||||||
def update_misc_stats(self) -> None:
|
def update_misc_stats(self) -> None:
|
||||||
"""Method to update the 'misc' values to current measurements.
|
"""Method to update the 'misc' values to current measurements.
|
||||||
Utilization % and temperature (C)"""
|
Utilization % and temperature (C)
|
||||||
|
|
||||||
|
Run by a timer created 'on_mount'"""
|
||||||
self.misc_stats = {
|
self.misc_stats = {
|
||||||
"util_pct": read_stat(src_files['busy_pct']),
|
"util_pct": read_stat(src_files['busy_pct']),
|
||||||
"temp": int(int(read_stat(src_files['temp_c'])) / 1000),
|
"temp": int(int(read_stat(src_files['temp_c'])) / 1000),
|
||||||
|
@ -160,17 +169,17 @@ class MiscDisplay(Static):
|
||||||
}
|
}
|
||||||
|
|
||||||
def watch_misc_stats(self, misc_stats: dict) -> None:
|
def watch_misc_stats(self, misc_stats: dict) -> None:
|
||||||
"""Called when the clocks attribute changes."""
|
"""Called when the clocks attribute changes.
|
||||||
output = f"""Utilization: {misc_stats['util_pct']}%
|
- Updates label values
|
||||||
Temperature: {misc_stats['temp']}C
|
- Casting inputs to string to avoid type problems w/ int/None"""
|
||||||
Fan RPM: {misc_stats['fan_rpm']}
|
self.query_one("#util_pct", Static).update(f"{misc_stats['util_pct']}%")
|
||||||
Target: {misc_stats['fan_rpm_target']}"""
|
self.query_one("#temp_c", Static).update(f"{misc_stats['temp']}C")
|
||||||
self.update(output)
|
self.query_one("#fan_rpm", Static).update(f"{misc_stats['fan_rpm']}")
|
||||||
|
self.query_one("#fan_rpm_target", Static).update(f"{misc_stats['fan_rpm_target']}")
|
||||||
|
|
||||||
|
|
||||||
class ClockDisplay(Static):
|
class ClockDisplay(Static):
|
||||||
"""A widget to display GPU power stats."""
|
"""A widget to display GPU power stats."""
|
||||||
|
|
||||||
clocks = reactive({"sclk": 0, "mclk": 0, "core_voltage": 0})
|
clocks = reactive({"sclk": 0, "mclk": 0, "core_voltage": 0})
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -178,14 +187,18 @@ class ClockDisplay(Static):
|
||||||
self.timer_clocks = None
|
self.timer_clocks = None
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Label("Clocks:", classes="statlabel")
|
yield Horizontal(Label("Core clock:",), Label("", id="clk_core_val", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("Core voltage:",), Label("", id="clk_voltage_val", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("Memory clock:"), Label("", id="clk_memory_val", classes="statvalue"))
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""Event handler called when widget is added to the app."""
|
"""Event handler called when widget is added to the app."""
|
||||||
self.timer_clocks = self.set_interval(1, self.update_clocks)
|
self.timer_clocks = self.set_interval(1, self.update_clocks)
|
||||||
|
|
||||||
def update_clocks(self) -> None:
|
def update_clocks(self) -> None:
|
||||||
"""Method to update GPU clock values to the current measurements."""
|
"""Method to update GPU clock values to the current measurements.
|
||||||
|
Run by a timer created 'on_mount'"""
|
||||||
|
|
||||||
self.clocks = {
|
self.clocks = {
|
||||||
"sclk": format_frequency(read_stat(src_files['core_clock'])),
|
"sclk": format_frequency(read_stat(src_files['core_clock'])),
|
||||||
"mclk": format_frequency(read_stat(src_files['memory_clock'])),
|
"mclk": format_frequency(read_stat(src_files['memory_clock'])),
|
||||||
|
@ -195,10 +208,12 @@ class ClockDisplay(Static):
|
||||||
}
|
}
|
||||||
|
|
||||||
def watch_clocks(self, clocks: dict) -> None:
|
def watch_clocks(self, clocks: dict) -> None:
|
||||||
"""Called when the clocks attribute changes."""
|
"""Called when the clocks attribute changes
|
||||||
output = f"""Core: {clocks['sclk']} @ {clocks['core_voltage']}V
|
- Updates label values
|
||||||
Memory: {clocks['mclk']}"""
|
- Casting inputs to string to avoid type problems w/ int/None"""
|
||||||
self.update(output)
|
self.query_one("#clk_core_val", Static).update(f"{clocks['sclk']}")
|
||||||
|
self.query_one("#clk_voltage_val", Static).update(f"{clocks['core_voltage']}V")
|
||||||
|
self.query_one("#clk_memory_val", Static).update(f"{clocks['mclk']}")
|
||||||
|
|
||||||
|
|
||||||
class PowerDisplay(Static):
|
class PowerDisplay(Static):
|
||||||
|
@ -214,14 +229,23 @@ class PowerDisplay(Static):
|
||||||
self.timer_micro_watts = None
|
self.timer_micro_watts = None
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Label("Power:", classes="statlabel")
|
yield Horizontal(Label("Power usage:",),
|
||||||
|
Label("", id="pwr_avg_val", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("Power limit:",),
|
||||||
|
Label("", id="pwr_lim_val", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("[underline]Default[/] limit:",),
|
||||||
|
Label("", id="pwr_def_val", classes="statvalue"))
|
||||||
|
yield Horizontal(Label("Board capability:",),
|
||||||
|
Label("", id="pwr_cap_val", classes="statvalue"))
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""Event handler called when widget is added to the app."""
|
"""Event handler called when widget is added to the app."""
|
||||||
self.timer_micro_watts = self.set_interval(1, self.update_micro_watts)
|
self.timer_micro_watts = self.set_interval(1, self.update_micro_watts)
|
||||||
|
|
||||||
def update_micro_watts(self) -> None:
|
def update_micro_watts(self) -> None:
|
||||||
"""Method to update GPU power values to current measurements."""
|
"""Method to update GPU power values to current measurements.
|
||||||
|
|
||||||
|
Run by a timer created 'on_mount'"""
|
||||||
self.micro_watts = {
|
self.micro_watts = {
|
||||||
"limit": int(int(read_stat(src_files['pwr_limit'])) / 1000000),
|
"limit": int(int(read_stat(src_files['pwr_limit'])) / 1000000),
|
||||||
"average": int(int(read_stat(src_files['pwr_average'])) / 1000000),
|
"average": int(int(read_stat(src_files['pwr_average'])) / 1000000),
|
||||||
|
@ -230,12 +254,13 @@ class PowerDisplay(Static):
|
||||||
}
|
}
|
||||||
|
|
||||||
def watch_micro_watts(self, micro_watts: dict) -> None:
|
def watch_micro_watts(self, micro_watts: dict) -> None:
|
||||||
"""Called when the micro_watts attributes change."""
|
"""Called when the micro_watts attributes change.
|
||||||
output = f"""Using: {micro_watts['average']}W
|
- Updates label values
|
||||||
Set limit: {micro_watts['limit']}W
|
- Casting inputs to string to avoid type problems w/ int/None"""
|
||||||
Default: {micro_watts['default']}W
|
self.query_one("#pwr_avg_val", Static).update(f"{micro_watts['average']}W")
|
||||||
Board cap: {micro_watts['capability']}W"""
|
self.query_one("#pwr_lim_val", Static).update(f"{micro_watts['limit']}W")
|
||||||
self.update(output)
|
self.query_one("#pwr_def_val", Static).update(f"{micro_watts['default']}W")
|
||||||
|
self.query_one("#pwr_cap_val", Static).update(f"{micro_watts['capability']}W")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
BIN
screens/main.png
BIN
screens/main.png
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
|
@ -19,7 +19,6 @@ GPUStatsWidget {
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statlabel {
|
.statvalue {
|
||||||
padding: 1;
|
dock: right;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue