Archived
1
1
Fork 0

Merge pull request #29 from joshlay/async_improvements

Async improvements
This commit is contained in:
Josh Lay 2023-05-07 19:09:59 -05:00 committed by GitHub
commit 30f1f1f247
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 71 deletions

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "amdgpu-stats" name = "amdgpu-stats"
version = "0.1.15" version = "0.1.16"
description = "A module/TUI for AMD GPU statistics" description = "A module/TUI for AMD GPU statistics"
authors = ["Josh Lay <pypi@jlay.io>"] authors = ["Josh Lay <pypi@jlay.io>"]
repository = "https://github.com/joshlay/amdgpu_stats" repository = "https://github.com/joshlay/amdgpu_stats"
@ -10,7 +10,7 @@ documentation = "https://amdgpu-stats.readthedocs.io/en/latest/"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" python = "^3.9"
textual = ">=0.16.0" textual = ">=0.18.0"
humanfriendly = ">=10.0" humanfriendly = ">=10.0"
[tool.poetry.scripts] [tool.poetry.scripts]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -18,6 +18,7 @@ from datetime import datetime
from typing import Optional from typing import Optional
from rich.text import Text from rich.text import Text
from textual import work
from textual.binding import Binding from textual.binding import Binding
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Container from textual.containers import Container
@ -109,7 +110,6 @@ class GPUStatsWidget(Static):
def __init__(self, *args, cards=None, **kwargs): def __init__(self, *args, cards=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.cards = cards self.cards = cards
self.text_log = TextLog(highlight=True, self.text_log = TextLog(highlight=True,
markup=True, markup=True,
@ -121,12 +121,16 @@ class GPUStatsWidget(Static):
classes='stat_table') classes='stat_table')
self.tabbed_container = TabbedContent() self.tabbed_container = TabbedContent()
async def on_mount(self) -> None: def on_mount(self) -> None:
'''Fires when stats widget 'mounted', behaves like on first showing''' '''Fires when stats widget 'mounted', behaves like on first showing'''
self.update_log("[bold green]App started, logging begin!")
self.update_log(f"[bold]Discovered AMD GPUs: [/]{list(AMDGPU_CARDS)}")
# construct the table columns # construct the table columns
columns = list(self.get_column_data_mapping(None).keys()) columns = list(self.get_column_data_mapping(None).keys())
self.update_log('[bold]Stats table columns:')
for column in columns: for column in columns:
self.stats_table.add_column(label=column, key=column) self.stats_table.add_column(label=column, key=column)
self.update_log(f' - "{column}"')
# do a one-off stat collection, populate table before the interval # do a one-off stat collection, populate table before the interval
self.get_stats() self.get_stats()
# stand up the stat-collecting interval, twice per second # stand up the stat-collecting interval, twice per second
@ -134,13 +138,9 @@ class GPUStatsWidget(Static):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""Create child widgets.""" """Create child widgets."""
self.update_log("[bold green]App started, logging begin!")
self.update_log(f"[bold]Discovered AMD GPUs: [/]{list(AMDGPU_CARDS)}")
# Add the TabbedContent widget
with self.tabbed_container: with self.tabbed_container:
with TabPane("Stats", id="tab_stats"): with TabPane("Stats", id="tab_stats"):
yield self.stats_table yield self.stats_table
self.update_log('[bold]App: [/]created stats table')
with TabPane("Logs", id="tab_logs"): with TabPane("Logs", id="tab_logs"):
yield self.text_log yield self.text_log
@ -148,13 +148,10 @@ class GPUStatsWidget(Static):
"""Update the TextLog widget with a new message.""" """Update the TextLog widget with a new message."""
self.text_log.write(message) self.text_log.write(message)
def get_stats(self): @work(exclusive=True)
async def get_stats(self):
'''Function to fetch stats / update the table for each AMD GPU found''' '''Function to fetch stats / update the table for each AMD GPU found'''
for card in self.cards: for card in self.cards:
# annoyingly, must retain the styling used w/ the cols above
# otherwise stats won't update
# noticed when fiddling 'style' below between new/update 'Text'
# should store their IDs on creation and map those instead
self.data = self.get_column_data_mapping(card) self.data = self.get_column_data_mapping(card)
# handle the table data appopriately # handle the table data appopriately
# if needs populated anew or updated # if needs populated anew or updated
@ -166,7 +163,7 @@ class GPUStatsWidget(Static):
] ]
self.stats_table.add_row(*styled_row, key=card) self.stats_table.add_row(*styled_row, key=card)
hwmon_dir = AMDGPU_CARDS[card] hwmon_dir = AMDGPU_CARDS[card]
self.update_log(f"[bold]Table: [/]added row for '{card}', info dir: '{hwmon_dir}'") self.update_log(f"[bold]Stats table: [/]added row for '{card}', info dir: '{hwmon_dir}'")
else: else:
# Update existing rows, retaining styling/justification # Update existing rows, retaining styling/justification
for column, value in self.data.items(): for column, value in self.data.items():
@ -186,6 +183,7 @@ class app(App): # pylint: disable=invalid-name
# set the title - same as the class, but with spaces # set the title - same as the class, but with spaces
TITLE = 'AMD GPU Stats' TITLE = 'AMD GPU Stats'
SUB_TITLE = ''
# setup keybinds # setup keybinds
BINDINGS = [ BINDINGS = [
@ -205,7 +203,8 @@ class app(App): # pylint: disable=invalid-name
yield Container(self.stats_widget) yield Container(self.stats_widget)
yield Footer() yield Footer()
def action_custom_dark(self) -> None: @work(exclusive=True)
async def action_custom_dark(self) -> None:
"""An action to toggle dark mode. """An action to toggle dark mode.
Wraps 'action_toggle_dark' with our logging""" Wraps 'action_toggle_dark' with our logging"""
@ -225,13 +224,28 @@ class app(App): # pylint: disable=invalid-name
self.screen.mount(Notification(message)) self.screen.mount(Notification(message))
self.update_log(message) self.update_log(message)
def action_custom_tab(self) -> None:
"""Toggle between the 'Stats' and 'Logs' tabs"""
if self.stats_widget.tabbed_container.active == "tab_stats":
self.stats_widget.tabbed_container.active = 'tab_logs'
else:
self.stats_widget.tabbed_container.active = 'tab_stats'
def update_log(self, message: str) -> None: def update_log(self, message: str) -> None:
"""Update the TextLog widget with a new message.""" """Update the TextLog widget with a new message."""
self.stats_widget.text_log.write(message) self.stats_widget.text_log.write(message)
def action_custom_tab(self) -> None:
"""Toggle between the 'Stats' and 'Logs' tabs"""
if self.stats_widget.tabbed_container.active == "tab_stats":
new_tab = 'tab_logs'
else:
new_tab = 'tab_stats'
self.stats_widget.tabbed_container.active = new_tab
# craft a 'tab activated' (changed) event
# used to set the subtitle via event handling
event = TabbedContent.TabActivated(tabbed_content=self.stats_widget.tabbed_container,
tab=new_tab)
self.on_tabbed_content_tab_activated(event)
def on_tabbed_content_tab_activated(self, event: TabbedContent.TabActivated):
"""Listens to 'TabActivated' event, sets subtitle"""
# if self.tabbed_container.active == "tab_logs":
active_title = event.tabbed_content.active.title().replace('Tab_', '')
if event.tabbed_content.active == "tab_logs":
self.sub_title = active_title # pylint: disable=attribute-defined-outside-init
elif event.tabbed_content.active == "tab_stats":
self.sub_title = f'{active_title}: {list(AMDGPU_CARDS)}' # pylint: disable=attribute-defined-outside-init