Skip to content

Commit

Permalink
Merge pull request #4 from joshlay/linting_varlength
Browse files Browse the repository at this point in the history
shorten "CARDS" var; fix some linting
  • Loading branch information
joshlay authored Mar 3, 2024
2 parents 479d00c + 988076b commit bbe2ea6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 49 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Introduction:
```python
In [1]: import amdgpu_stats.utils

In [2]: amdgpu_stats.utils.AMDGPU_CARDS
In [2]: amdgpu_stats.utils.CARDS
Out[2]: {'card0': '/sys/class/drm/card0/device/hwmon/hwmon9'}

In [3]: amdgpu_stats.utils.get_core_stats('card0')
Expand Down
4 changes: 2 additions & 2 deletions src/amdgpu_stats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
from .tui import app
from .utils import (
AMDGPU_CARDS,
CARDS,
)


Expand All @@ -12,7 +12,7 @@ def check_for_cards() -> bool:
Returns:
bool: If any AMD cards found or not"""
if len(AMDGPU_CARDS) > 0:
if len(CARDS) > 0:
return True
return False

Expand Down
36 changes: 22 additions & 14 deletions src/amdgpu_stats/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
)

from .utils import (
AMDGPU_CARDS,
CARDS,
get_fan_rpm,
get_power_stats,
get_temp_stat,
Expand All @@ -46,6 +46,7 @@
# rich markup reference:
# https://rich.readthedocs.io/en/stable/markup.html


class GPUStatsWidget(Static):
"""The main stats widget."""

Expand Down Expand Up @@ -89,7 +90,7 @@ def get_column_data_mapping(self, card: Optional[str] = None) -> dict:
"Card": "",
"Core clock": "",
"Memory clock": "",
"Utilization": "",
"Usage": "",
"Voltage": "",
"Power": "",
"Limit": "",
Expand All @@ -104,7 +105,7 @@ def get_column_data_mapping(self, card: Optional[str] = None) -> dict:
"Card": card,
"Core clock": get_clock('core', card=card, format_freq=True),
"Memory clock": get_clock('memory', card=card, format_freq=True),
"Utilization": f'{get_gpu_usage(card=card)}%',
"Usage": f'{get_gpu_usage(card=card)}%',
"Voltage": f'{get_voltage(card=card)}V',
"Power": power_stats['usage'],
"Limit": power_stats['lim'],
Expand Down Expand Up @@ -162,7 +163,7 @@ def compose(self) -> ComposeResult:
with TabPane("Stats", id="tab_stats"):
yield self.stats_table
with TabPane("Graphs", id="tab_graphs", classes="tab_graphs"):
for card in AMDGPU_CARDS:
for card in CARDS:
yield Vertical(
Label(f'[bold]{card}'),
Label('Core:'),
Expand All @@ -189,9 +190,7 @@ async def get_stats(self):
'''Function to fetch stats / update the table for each AMD GPU found'''
for card in self.cards:
self.data = self.get_column_data_mapping(card)
# Update usage bars
if self.data['Utilization'] is not None:
self.query_one(f'#bar_{card}_util').update(total=100, progress=float(self.data['Utilization'].replace('%', '')))

# handle the table data appopriately
# if needs populated anew or updated
if self.table_needs_init:
Expand All @@ -201,13 +200,22 @@ async def get_stats(self):
Text(str(cell), style="normal", justify="right") for cell in self.data.values()
]
self.stats_table.add_row(*styled_row, key=card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
self.update_log(f"Added row for '{card}', stats dir: '{hwmon_dir}'")
else:
# Update existing rows, retaining styling/justification
# Update existing table rows, retaining styling/justification
for column, value in self.data.items():
styled_cell = Text(str(value), style="normal", justify="right")
self.stats_table.update_cell(card, column, styled_cell)
self.stats_table.update_cell(card,
column,
Text(str(value),
style="normal",
justify="right"))

# Update usage bars
if self.data['Usage'] is not None:
self.query_one(f'#bar_{card}_util').update(total=100,
progress=float(self.data['Usage'].replace('%', '')))

if self.table_needs_init:
# if this is the first time updating the table, mark it initialized
self.table_needs_init = False
Expand All @@ -224,7 +232,7 @@ class app(App): # pylint: disable=invalid-name
TITLE = 'AMD GPU Stats'

# set a default subtitle, will change with the active tab
SUB_TITLE = f'cards: {list(AMDGPU_CARDS)}'
SUB_TITLE = f'cards: {list(CARDS)}'

# setup keybinds
BINDINGS = [
Expand All @@ -239,7 +247,7 @@ class app(App): # pylint: disable=invalid-name
]

# create an instance of the stats widget with all cards
stats_widget = GPUStatsWidget(cards=AMDGPU_CARDS,
stats_widget = GPUStatsWidget(cards=CARDS,
name="stats_widget")

def compose(self) -> ComposeResult:
Expand Down Expand Up @@ -322,4 +330,4 @@ def on_tabbed_content_tab_activated(self, event: TabbedContent.TabActivated):
if active_tab == "logs":
self.sub_title = active_tab # pylint: disable=attribute-defined-outside-init
elif active_tab == "stats":
self.sub_title = f'cards: {list(AMDGPU_CARDS)}' # pylint: disable=attribute-defined-outside-init
self.sub_title = f'cards: {list(CARDS)}' # pylint: disable=attribute-defined-outside-init
63 changes: 31 additions & 32 deletions src/amdgpu_stats/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
This module contains utility functions/variables used throughout the 'amdgpu-stats' TUI
Variables:
- AMDGPU_CARDS (dict): discovered AMD GPUs and their `hwmon` stats directories
- CARDS (dict): discovered AMD GPUs and their `hwmon` stats directories
- Example: `{'card0': '/sys/class/drm/card0/device/hwmon/hwmon9'}`
- If no *AMD* cards are found, this will be empty.
- CLOCK_DOMAINS (tuple): supported clock domains, ie: `('core', 'memory')`
Expand Down Expand Up @@ -44,7 +44,7 @@ def find_cards() -> dict:


# discover all available AMD GPUs
AMDGPU_CARDS = find_cards()
CARDS = find_cards()
# supported clock domains by 'get_clock' func
# is concatenated with 'clock_' to index SRC_FILES for the relevant data file
CLOCK_DOMAINS = ('core', 'memory')
Expand All @@ -53,7 +53,7 @@ def find_cards() -> dict:

def validate_card(card: Optional[str] = None) -> str:
"""
Checks the provided `card` identifier -- if present in `AMDGPU_CARDS`
Checks the provided `card` identifier -- if present in `CARDS`
If `card` is not provided, will yield the first AMD GPU *(if any installed)*
Expand All @@ -62,24 +62,24 @@ def validate_card(card: Optional[str] = None) -> str:
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
str: Validated card identifier.
If `card` is provided and valid: original identifier `card` is returned
If `card` is omitted: the first AMD GPU identifier is returned
"""
if card in AMDGPU_CARDS:
if card in CARDS:
# card was provided and checks out, send it back
return card
if card is None:
# if no card provided and we know some, send the first one we know back
if len(AMDGPU_CARDS) > 0:
return list(AMDGPU_CARDS.keys())[0]
if len(CARDS) > 0:
return list(CARDS.keys())[0]
# if no AMD cards found, toss an errror
raise ValueError("No AMD GPUs or hwmon directories found")
# if 'card' was specified (not None) but invalid (not in 'AMDGPU_CARDS'), raise a helpful error
raise ValueError(f"Invalid card: '{card}'. Must be one of: {list(AMDGPU_CARDS.keys())}")
# if 'card' was specified (not None) but invalid (not in 'CARDS'), raise a helpful error
raise ValueError(f"Invalid card: '{card}'. Must be one of: {list(CARDS.keys())}")


def read_stat(file: str, stat_type: Optional[str] = None) -> str:
Expand All @@ -104,7 +104,6 @@ def read_stat(file: str, stat_type: Optional[str] = None) -> str:
return None



def format_frequency(frequency_hz: int) -> str:
"""
Takes a frequency (in Hz) and normalizes it: `Hz`, `MHz`, or `GHz`
Expand All @@ -122,11 +121,11 @@ def format_frequency(frequency_hz: int) -> str:
def get_power_stats(card: Optional[str] = None) -> dict:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
dict: A dictionary of current GPU *power* related statistics.
Expand All @@ -135,7 +134,7 @@ def get_power_stats(card: Optional[str] = None) -> dict:
`{'limit': int, 'average': int, 'capability': int, 'default': int}`
"""
card = validate_card(card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
_pwr = {"limit": read_stat(path.join(hwmon_dir, "power1_cap"), stat_type='power'),
"limit_pct": 0,
"average": read_stat(path.join(hwmon_dir, "power1_average"), stat_type='power'),
Expand All @@ -151,11 +150,11 @@ def get_power_stats(card: Optional[str] = None) -> dict:
def get_core_stats(card: Optional[str] = None) -> dict:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
dict: A dictionary of current GPU *core/memory* related statistics.
Expand All @@ -179,14 +178,14 @@ def get_clock(domain: str, card: Optional[str] = None, format_freq: Optional[boo
domain (str): The GPU part of interest RE: clock speed.
Must be either 'core' or 'memory'
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
format_freq (bool, optional): If True, a formatted string will be returned instead of an int.
Defaults to False.
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
Union[int, str]: The clock value for the specified domain.
Expand All @@ -197,7 +196,7 @@ def get_clock(domain: str, card: Optional[str] = None, format_freq: Optional[boo
"""
# verify card -- is it AMD, do we know the hwmon directory?
card = validate_card(card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
if domain not in CLOCK_DOMAINS:
raise ValueError(f"Invalid clock domain: '{domain}'. Must be one of: {CLOCK_DOMAINS}")
# set the clock file based on requested domain
Expand All @@ -217,29 +216,29 @@ def get_clock(domain: str, card: Optional[str] = None, format_freq: Optional[boo
def get_voltage(card: Optional[str] = None) -> float:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
float: The current GPU core voltage
"""
# verify card -- is it AMD, do we know the hwmon directory?
card = validate_card(card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
return round(int(read_stat(path.join(hwmon_dir, "in0_input"))) / 1000.0, 2)


def get_fan_rpm(card: Optional[str] = None) -> int:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
Expand All @@ -248,7 +247,7 @@ def get_fan_rpm(card: Optional[str] = None) -> int:
"""
# verify card -- is it AMD, do we know the hwmon directory?
card = validate_card(card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
_val = read_stat(path.join(hwmon_dir, "fan1_input"))
if _val is not None:
_val = int(_val)
Expand All @@ -258,29 +257,29 @@ def get_fan_rpm(card: Optional[str] = None) -> int:
def get_fan_target(card: Optional[str] = None) -> int:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
int: The *target* fan RPM
"""
# verify card -- is it AMD, do we know the hwmon directory?
card = validate_card(card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
return int(read_stat(path.join(hwmon_dir, "fan1_target")))


def get_gpu_usage(card: Optional[str] = None) -> int:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Determined with `AMDGPU_CARDS`
Determined with `CARDS`
Returns:
int: The current GPU usage/utilization as a percentage
Expand All @@ -293,7 +292,7 @@ def get_gpu_usage(card: Optional[str] = None) -> int:
def get_available_temps(card: Optional[str] = None) -> dict:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
Raises:
ValueError: If *no* AMD cards are found, or `card` is not one of them.
Expand All @@ -307,7 +306,7 @@ def get_available_temps(card: Optional[str] = None) -> dict:
`{'edge': '/.../temp1_input', 'junction': '/.../temp2_input', 'mem': '/.../temp3_input'}`
"""
card = validate_card(card)
hwmon_dir = AMDGPU_CARDS[card]
hwmon_dir = CARDS[card]
# determine temperature nodes/types, construct a dict to store them
temp_files = {}
temp_node_labels = glob.glob(path.join(hwmon_dir, "temp*_label"))
Expand All @@ -326,7 +325,7 @@ def get_available_temps(card: Optional[str] = None) -> dict:
def get_temp_stat(name: str, card: Optional[str] = None) -> dict:
"""
Args:
card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()`
card (str, optional): ie: `card0`. See `CARDS` or `find_cards()`
name (str): temperature *name*, ie: `edge`, `junction`, or `mem`
Raises:
Expand Down

0 comments on commit bbe2ea6

Please sign in to comment.