From f323e4defeae94372e08b79ae3c68a90380a6580 Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Sun, 30 Apr 2023 18:54:52 -0500 Subject: [PATCH 1/6] power: report usage as a percentage of limit --- src/amdgpu_stats/tui.py | 3 ++- src/amdgpu_stats/utils.py | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/amdgpu_stats/tui.py b/src/amdgpu_stats/tui.py index b5e1925..9eb517f 100644 --- a/src/amdgpu_stats/tui.py +++ b/src/amdgpu_stats/tui.py @@ -277,6 +277,7 @@ class PowerDisplay(Static): """A widget to display GPU power stats.""" watts = reactive({"limit": 0, + "limit_pct": 0, "average": 0, "capability": 0, "default": 0}) @@ -318,7 +319,7 @@ class PowerDisplay(Static): - Updates label values - Casting inputs to string to avoid type problems w/ int/None""" self.query_one("#pwr_avg_val", - Static).update(f"{watts['average']}W") + Static).update(f"{watts['limit_pct']}%, {watts['average']}W") self.query_one("#pwr_lim_val", Static).update(f"{watts['limit']}W") self.query_one("#pwr_def_val", diff --git a/src/amdgpu_stats/utils.py b/src/amdgpu_stats/utils.py index 767e994..01e680a 100644 --- a/src/amdgpu_stats/utils.py +++ b/src/amdgpu_stats/utils.py @@ -132,12 +132,17 @@ def get_power_stats(card: Optional[str] = None) -> dict: """ card = validate_card(card) hwmon_dir = AMDGPU_CARDS[card] - - return {"limit": read_stat(path.join(hwmon_dir, "power1_cap"), stat_type='power'), + _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'), "capability": read_stat(path.join(hwmon_dir, "power1_cap_max"), stat_type='power'), "default": read_stat(path.join(hwmon_dir, "power1_cap_default"), stat_type='power')} + if _pwr['limit'] != 0: + _pwr['limit_pct'] = round((_pwr['average'] / _pwr['limit']) * 100, 1) + + return _pwr + def get_core_stats(card: Optional[str] = None) -> dict: """ From 91f80e62ae1f23b587b551faca6e138db6b9c90d Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Mon, 1 May 2023 21:26:47 -0500 Subject: [PATCH 2/6] tui: move from swarm of labels to DataTable --- src/amdgpu_stats/style.css | 29 +--- src/amdgpu_stats/tui.py | 288 +++++++++++-------------------------- src/amdgpu_stats/utils.py | 55 ++++--- 3 files changed, 125 insertions(+), 247 deletions(-) diff --git a/src/amdgpu_stats/style.css b/src/amdgpu_stats/style.css index b7b5d41..0d9c138 100644 --- a/src/amdgpu_stats/style.css +++ b/src/amdgpu_stats/style.css @@ -3,36 +3,15 @@ Header { } GPUStatsWidget { - layout: grid; - grid-size: 3; - grid-gutter: 2 8; box-sizing: content-box; background: $panel; - height: 10; + height: 100%; + width: 100%; min-width: 50; } /* for colors, see: https://textual.textualize.io/guide/design/#theme-reference */ -.widgetheader { - background: $panel; - border: solid $primary-background; - content-align: center middle; -} - -.box { - height: 100%; -/* border: ascii $primary-background;*/ - content-align: center middle; - padding: 0 1; -} - -Horizontal { - padding: 0; - margin: 0; - box-sizing: content-box; -} - -.statvalue { - dock: right; +DataTable { + width: 100%; } diff --git a/src/amdgpu_stats/tui.py b/src/amdgpu_stats/tui.py index 9eb517f..1d7f592 100644 --- a/src/amdgpu_stats/tui.py +++ b/src/amdgpu_stats/tui.py @@ -22,14 +22,15 @@ import sys from datetime import datetime from os import path +from rich.text import Text from textual.binding import Binding from textual.app import App, ComposeResult -from textual.containers import Container, Horizontal -from textual.reactive import reactive +from textual.containers import Container from textual.screen import Screen -from textual.widgets import Header, Footer, Static, TextLog, Label +from textual.widgets import Header, Footer, Static, TextLog, DataTable -from .utils import AMDGPU_CARDS, format_frequency, get_core_stats, get_fan_rpm, get_fan_target, get_power_stats, get_temp_stats # pylint: disable=line-too-long +from .utils import AMDGPU_CARDS, get_fan_rpm, get_power_stats, get_temp_stat, get_clock, get_gpu_usage, get_voltage +# pylint: disable=line-too-long # rich markup reference: # https://rich.readthedocs.io/en/stable/markup.html @@ -40,7 +41,7 @@ class LogScreen(Screen): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.text_log = TextLog(highlight=True, markup=True) + self.text_log = TextLog(highlight=True, markup=True, name='log_gpu') def on_mount(self) -> None: """Event handler called when widget is first added @@ -48,7 +49,7 @@ class LogScreen(Screen): def compose(self) -> ComposeResult: yield Header(show_clock=True) - yield Container(self.text_log) + yield self.text_log yield Footer() # def on_key(self, event: events.Key) -> None: @@ -59,25 +60,88 @@ class LogScreen(Screen): class GPUStatsWidget(Static): """The main stats widget.""" - def __init__(self, *args, card=None, **kwargs): + columns = ["card", + "core clock", + "memory clock", + "utilization", + "voltage", + "power usage", + "set limit", + "default limit", + "capability", + "fan rpm", + "edge temp", + "junction temp", + "memory temp"] + timer_stats = None + table = None + table_needs_init = True + data = {} + + def __init__(self, *args, cards=None, **kwargs): super().__init__(*args, **kwargs) # Instance variables - self.card = card - self.hwmon_dir = AMDGPU_CARDS[self.card] + self.cards = cards + + async def on_mount(self) -> None: + '''Fires when stats widget first shown''' + self.table = self.query_one(DataTable) + for column in self.columns: + self.table.add_column(label=column, key=column) + # self.table.add_columns(*self.columns) + self.table_needs_init = True + self.timer_stats = self.set_interval(1, self.get_stats) def compose(self) -> ComposeResult: """Create child widgets.""" - yield ClockDisplay(classes="box", card=self.card, hwmon_dir=self.hwmon_dir) - yield PowerDisplay(classes="box", card=self.card, hwmon_dir=self.hwmon_dir) - yield MiscDisplay(classes="box", card=self.card) - _msg = f'''[bold]App:[/] creating stat widgets for [green]{self.card}[/], stats directory: {self.hwmon_dir}''' - self.update_log(_msg) + stats_table = DataTable(zebra_stripes=True, show_cursor=False, name='stats_table') + yield stats_table + self.update_log('[bold]App:[/] created stats table') def update_log(self, message: str) -> None: """Update the TextLog widget with a new message.""" log_screen = AMDGPUStats.SCREENS["logs"] log_screen.text_log.write(message) + def get_stats(self): + '''Function to fetch stats / update the table''' + for card in self.cards: + power_stats = get_power_stats(card=card) + self.data = { + "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)}%', + "voltage": f'{get_voltage(card=card)}V', + "power usage": f'{power_stats["average"]}W', + "set limit": f'{power_stats["limit"]}W', + "default limit": f'{power_stats["default"]}W', + "capability": f'{power_stats["capability"]}W', + "fan rpm": f'{get_fan_rpm(card=card)}', + "edge temp": f"{get_temp_stat(name='edge', card=card)}C", + "junction temp": f"{get_temp_stat(name='junction', card=card)}C", + "memory temp": f"{get_temp_stat(name='mem', card=card)}C"} + # handle the table data appopriately + # if needs populated anew or updated + if self.table_needs_init: + # Add rows for the first time + # Adding styled and justified `Text` objects instead of plain strings. + styled_row = [ + Text(str(cell), style="italic", justify="right") for cell in self.data.values() + ] + self.table.add_row(*styled_row, key=card) + hwmon_dir = AMDGPU_CARDS[card] + self.update_log(f'[bold]stats:[/] added row for [bold green]{card}[/], info dir: {hwmon_dir}') + else: + # Update existing rows + for column, value in self.data.items(): + styled_cell = Text(str(value), style="italic", justify="right") + self.table.update_cell(card, column, styled_cell) + if self.table_needs_init: + # if this is the first time updating the table, mark it initialized + self.table_needs_init = False + self.table.refresh() + class AMDGPUStats(App): """Textual-based tool to show AMDGPU statistics.""" @@ -95,19 +159,17 @@ class AMDGPUStats(App): # Binding("l", "push_screen('logs')", "Toggle logs", priority=True), BINDINGS = [ Binding("c", "custom_dark", "Colors"), - Binding("l", "toggle_log", "Logs"), - Binding("s", "screenshot_wrapper", "Screenshot"), + Binding("l", "custom_log", "Logs"), + Binding("s", "custom_screenshot", "Screenshot"), Binding("q", "quit", "Quit") ] def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Header(show_clock=True) - for card in AMDGPU_CARDS: - stat_widget_name = "stats_" + card - yield Label(card, expand=True, classes='widgetheader') - widget = Container(GPUStatsWidget(card=card, id=stat_widget_name)) - yield widget + stats_widget = Container(GPUStatsWidget(cards=AMDGPU_CARDS, + name="stats_widget")) + yield stats_widget self.update_log("[bold green]App started, logging begin!") self.update_log(f"[bold]Discovered AMD GPUs:[/] {list(AMDGPU_CARDS)}") # nice-to-have: account for not storing these in dicts, but resolved in funcs @@ -126,7 +188,7 @@ class AMDGPUStats(App): self.refresh() # self.dark = not self.dark - def action_screenshot_wrapper(self, screen_dir: str = '/tmp') -> None: + def action_custom_screenshot(self, screen_dir: str = '/tmp') -> None: """Action that fires when the user presses 's' for a screenshot""" # construct the screenshot elements + path timestamp = datetime.now().isoformat().replace(":", "_") @@ -135,7 +197,7 @@ class AMDGPUStats(App): self.action_screenshot(path=screen_dir, filename=screen_name) self.update_log(f'[bold]Screenshot taken: [italic]{screen_path}') - def action_toggle_log(self) -> None: + def action_custom_log(self) -> None: """Toggle between the main screen and the LogScreen.""" if isinstance(self.screen, LogScreen): self.pop_screen() @@ -148,186 +210,6 @@ class AMDGPUStats(App): log_screen.text_log.write(message) -class MiscDisplay(Static): - """A widget to display misc. GPU stats.""" - # construct the misc. stats dict; appended by discovered temperature nodes - # used to make a 'reactive' object - fan_rpm = reactive(0) - fan_rpm_target = reactive(0) - # do some dancing to craft the UI; initialize the reactive obj with data - # dynamic object for temperature updates - # populated / looped in '__init__' to get proper labels - temp_stats = reactive({}) - # default to 'not composed', once labels are made - become true - # avoids a race condition between discovering temperature nodes/stats - # ... and making labels for them - composed = False - - def __init__(self, card: str, *args, **kwargs): - super().__init__(*args, **kwargs) - self.timer_misc = None - self.card = card - self.temp_stats = get_temp_stats(self.card) - - def compose(self) -> ComposeResult: - yield Horizontal(Label("[underline]Temperatures"), - Label("", classes="statvalue")) - for temp_node in self.temp_stats: - # capitalize the first letter for display - caption = temp_node[0].upper() + temp_node[1:] - yield Horizontal(Label(f' {caption}:',), - Label("", id="temp_" + temp_node, classes="statvalue")) - # padding to split groups - yield Horizontal() - yield Horizontal(Label("[underline]Fan RPM"), - Label("", classes="statvalue")) - yield Horizontal(Label(" Current:",), - Label("", id="fan_rpm", classes="statvalue")) - yield Horizontal(Label(" Target:",), - Label("", id="fan_rpm_target", classes="statvalue")) - self.composed = True - - def on_mount(self) -> None: - """Event handler called when widget is added to the app.""" - self.timer_misc = self.set_interval(1, self.update_misc_stats) - - def update_misc_stats(self) -> None: - """Method to update the temp/fan values to current measurements. - - Run by a timer created 'on_mount'""" - self.fan_rpm = get_fan_rpm(self.card) - self.fan_rpm_target = get_fan_target(self.card) - self.temp_stats = get_temp_stats(self.card) - - def watch_fan_rpm(self, fan_rpm: int) -> None: - """Called when the 'fan_rpm' reactive attr changes. - - - Updates label values - - Casting inputs to string to avoid type problems w/ int/None""" - self.query_one("#fan_rpm", Static).update(f"{fan_rpm}") - - def watch_fan_rpm_target(self, fan_rpm_target: int) -> None: - """Called when the 'fan_rpm_target' reactive attr changes. - - - Updates label values - - Casting inputs to string to avoid type problems w/ int/None""" - self.query_one("#fan_rpm_target", Static).update(f"{fan_rpm_target}") - - def watch_temp_stats(self, temp_stats: dict) -> None: - """Called when the temp_stats reactive attr changes, updates labels""" - # try to avoid racing - if not self.composed: - return - for temp_node in temp_stats: - item_val = self.temp_stats[temp_node] - self.query_one("#temp_" + temp_node, Static).update(f'{item_val}C') - - -class ClockDisplay(Static): - """A widget to display GPU power stats.""" - core_vals = reactive({"sclk": 0, "mclk": 0, "voltage": 0, "util_pct": 0}) - - def __init__(self, card: str, hwmon_dir: str, *args, **kwargs): - super().__init__(*args, **kwargs) - self.timer_clocks = None - self.card = card - self.hwmon_dir = hwmon_dir - - def compose(self) -> ComposeResult: - yield Horizontal(Label("[underline]Performance"), - Label("", classes="statvalue")) - yield Horizontal(Label(" Core clock:",), - Label("", id="clk_core_val", classes="statvalue")) - yield Horizontal(Label(" Memory clock:"), - Label("", id="clk_memory_val", classes="statvalue")) - yield Horizontal(Label(" Utilization:",), - Label("", id="util_pct", classes="statvalue")) - yield Horizontal(Label(" Voltage:",), - Label("", id="clk_voltage_val", classes="statvalue")) - # padding underneath, don't let them space out vertically - yield Horizontal() - yield Horizontal() - yield Horizontal() - yield Horizontal() - - def on_mount(self) -> None: - """Event handler called when widget is added to the app.""" - self.timer_clocks = self.set_interval(1, self.update_core_vals) - - def update_core_vals(self) -> None: - """Method to update GPU clock values to the current measurements. - Run by a timer created 'on_mount'""" - self.core_vals = get_core_stats(self.card) - - def watch_core_vals(self, core_vals: dict) -> None: - """Called when the clocks attribute changes - - Updates label values - - Casting inputs to string to avoid type problems w/ int/None""" - self.query_one("#clk_core_val", - Static).update(f"{format_frequency(core_vals['sclk'])}") - self.query_one("#util_pct", - Static).update(f"{core_vals['util_pct']}%") - self.query_one("#clk_voltage_val", - Static).update(f"{core_vals['voltage']}V") - self.query_one("#clk_memory_val", - Static).update(f"{format_frequency(core_vals['mclk'])}") - - -class PowerDisplay(Static): - """A widget to display GPU power stats.""" - - watts = reactive({"limit": 0, - "limit_pct": 0, - "average": 0, - "capability": 0, - "default": 0}) - - def __init__(self, card: str, hwmon_dir: str, *args, **kwargs): - super().__init__(*args, **kwargs) - self.timer_watts = None - self.card = card - self.hwmon_dir = hwmon_dir - - def compose(self) -> ComposeResult: - yield Horizontal(Label("[underline]Power"), - Label("", classes="statvalue")) - yield Horizontal(Label(" Usage:",), - Label("", id="pwr_avg_val", classes="statvalue")) - yield Horizontal(Label(" Set Limit:",), - Label("", id="pwr_lim_val", classes="statvalue")) - yield Horizontal(Label(" Default Limit:",), - Label("", id="pwr_def_val", classes="statvalue")) - yield Horizontal(Label(" Capability:",), - Label("", id="pwr_cap_val", classes="statvalue")) - yield Horizontal() - yield Horizontal() - yield Horizontal() - yield Horizontal() - - def on_mount(self) -> None: - """Event handler called when widget is added to the app.""" - self.timer_watts = self.set_interval(1, self.update_watts) - - def update_watts(self) -> None: - """Method to update GPU power values to current measurements. - - Run by a timer created 'on_mount'""" - self.watts = get_power_stats(self.card) - - def watch_watts(self, watts: dict) -> None: - """Called when the 'watts' reactive attribute (var) changes. - - Updates label values - - Casting inputs to string to avoid type problems w/ int/None""" - self.query_one("#pwr_avg_val", - Static).update(f"{watts['limit_pct']}%, {watts['average']}W") - self.query_one("#pwr_lim_val", - Static).update(f"{watts['limit']}W") - self.query_one("#pwr_def_val", - Static).update(f"{watts['default']}W") - self.query_one("#pwr_cap_val", - Static).update(f"{watts['capability']}W") - - def start() -> None: '''Spawns the textual UI only during CLI invocation / after argparse''' if len(AMDGPU_CARDS) > 0: diff --git a/src/amdgpu_stats/utils.py b/src/amdgpu_stats/utils.py index 01e680a..62c8b38 100644 --- a/src/amdgpu_stats/utils.py +++ b/src/amdgpu_stats/utils.py @@ -276,31 +276,25 @@ def get_gpu_usage(card: Optional[str] = None) -> int: return int(read_stat(stat_file)) -def get_temp_stats(card: Optional[str] = None) -> dict: +def get_available_temps(card: Optional[str] = None) -> dict: """ Args: card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()` Raises: ValueError: If *no* AMD cards are found, or `card` is not one of them. - Determined with `AMDGPU_CARDS` Returns: - dict: A dictionary of current GPU *temperature* related statistics + dict: Discovered temperature `nodes` and paths to their value files + + If none are found, will be empty. Example: - `{'name_temp_node_1': int, 'name_temp_node_2': int, 'name_temp_node_3': int}` - - Dictionary keys (temp nodes/names) are constructed through discovery. - - Driver provides temperatures in *millidegrees* C - - Returned values are converted to 'C' as integers for simple comparison + `{'edge': '/.../temp1_input', 'junction': '/.../temp2_input', 'mem': '/.../temp3_input'}` """ card = validate_card(card) hwmon_dir = AMDGPU_CARDS[card] - # determine temperature nodes, construct a dict to store them - # interface will iterate over these, creating labels as needed + # determine temperature nodes/types, construct a dict to store them temp_files = {} temp_node_labels = glob.glob(path.join(hwmon_dir, "temp*_label")) for temp_node_label_file in temp_node_labels: @@ -312,11 +306,34 @@ def get_temp_stats(card: Optional[str] = None) -> dict: temp_node_name = _node.read().strip() # add the node name/type and the corresponding temp file to the dict temp_files[temp_node_name] = temp_node_value_file + return temp_files - temp_update = {} - for temp_node, temp_file in temp_files.items(): - # iterate through the discovered temperature nodes - # ... updating the dictionary with new stats - _temperature = int(int(read_stat(temp_file)) // 1000) - temp_update[temp_node] = _temperature - return temp_update + +def get_temp_stat(name: str, card: Optional[str] = None) -> dict: + """ + Args: + card (str, optional): ie: `card0`. See `AMDGPU_CARDS` or `find_cards()` + name (str): temperature *name*, ie: `edge`, `junction`, or `mem` + + Raises: + ValueError: If *no* AMD cards are found, or `card` is not one of them. + *Or* Invalid temperature name is provided. + + Returns: + int: Requested GPU temperature (type, by `name`). + Either the first AMD card, or one specified with `card=`. + + Driver provides temperatures in *millidegrees* C + + Returned values are converted to 'C' as integers for simple comparison + """ + card = validate_card(card) + # determine temperature nodes/types, construct a dict to store them + temp_files = get_available_temps(card=card) + + # now that we know the temperature nodes/types for 'card', check request + if name not in temp_files: + raise ValueError(f'{name} does not appear to be valid, temp nodes: {list(temp_files.keys())}') + + # if the requested temperature node was found, read it / convert to C + return int(int(read_stat(temp_files[name])) // 1000) From 0832fcd811a44eda8a0d07e7cf4ce5a274133557 Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Mon, 1 May 2023 21:34:47 -0500 Subject: [PATCH 3/6] v0.1.12: bump for TUI updates --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 986b460..5d3cdb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "amdgpu-stats" -version = "0.1.11" +version = "0.1.12" description = "A module/TUI for AMD GPU statistics" authors = ["Josh Lay "] repository = "https://github.com/joshlay/amdgpu_stats" From 5564d8da41839e5abf6cccfb4685851d78c20350 Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Mon, 1 May 2023 21:39:09 -0500 Subject: [PATCH 4/6] screen: reflect DataTable updates --- screens/main.png | Bin 32369 -> 22212 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screens/main.png b/screens/main.png index ec3f6f063abe97c999506abaf7925aabb40e0b8c..70f51fe4bcef93de0eaac726bfe147659e66ea89 100644 GIT binary patch literal 22212 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 JPDHLkV1kcEy7&M9 literal 32369 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%0A74x!A-~g9aOTz>Dc~(gddbab9ft_ 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 H4PX90H#-s$ From 16764bed3056506650125adb53501122a900bda1 Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Mon, 1 May 2023 21:55:06 -0500 Subject: [PATCH 5/6] project: bump textual dep for DataTable usage --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5d3cdb3..6fe718b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://amdgpu-stats.readthedocs.io/en/latest/" [tool.poetry.dependencies] python = "^3.9" -textual = ">=0.10" +textual = ">=0.11" humanfriendly = ">=10.0" [tool.poetry.scripts] From 4e0f5879057c3921884ae11247e5aeff679dc07e Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Mon, 1 May 2023 22:01:10 -0500 Subject: [PATCH 6/6] readme/screen: use svg --- README.md | 4 +- screens/main.svg | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 screens/main.svg diff --git a/README.md b/README.md index bbeb358..9fe7aea 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ A Python module/TUI for AMD GPU statistics -![Screenshot of main screen](https://raw.githubusercontent.com/joshlay/amdgpu_stats/master/screens/main.png "Main screen") - -Supported GPUs and temperature nodes (`edge`/`junction`/etc.) are discovered automatically. +![Screenshot of main screen](https://raw.githubusercontent.com/joshlay/amdgpu_stats/master/screens/main.svg "Main screen") Tested _only_ on `RX6000` series cards; APUs and more _may_ be supported. Please file an issue if finding incompatibility! diff --git a/screens/main.svg b/screens/main.svg new file mode 100644 index 0000000..837307a --- /dev/null +++ b/screens/main.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AMDGPUStats + + + + + + + + + + AMDGPUStats21:57:16 + card   core clock  memory clock  utilization  voltage  power usage  set limit  default limit  capability  fan rpm  edge temp  junction temp  memory temp  +card1   500 MHz       1 GHz         0%  0.78V        35W     281W         281W      323W      0      33C          37C        42C + + + + + + + + + + + + C  Colors  L  Logs  S  Screenshot  Q  Quit  + + +