Files
rocm-systems/projects/rocprofiler-compute/src/utils/gui.py
T
vedithal-amd 4a5cbbfba5 [rocprofiler-compute] Fix kernel/dispatch filtering (#2479)
* Fix kernel/dispatch fitlering in GUI

* Disallow --kernel and --dispatch filtering in analyze --gui mode since
  GUI frontend offers dropdown menu for kernel and dispatch filtering
    * Update CHANGELOG and documentation

* Gracefully handle N/A values

* Ensure workload path is valid before using it in GUI

* Ignore kernel filters if dispatch filters provided

* Add documentation for dispatch filtering overriding kernel filtering

* Fix typo

* Fix documentation

* remove unnecessary whitespace

* Address review comments

* Allow kernel/dispatch filtering with --gui

* Address review comments

* Address review comments

* Update CHANGELOG

* Fix formatting
2026-01-20 10:02:31 -05:00

326 lines
10 KiB
Python

##############################################################################
# MIT License
#
# Copyright (c) 2021 - 2025 Advanced Micro Devices, Inc. All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
##############################################################################
from typing import Any
import pandas as pd
import plotly.express as px # type: ignore
from dash import dash_table # type: ignore
from utils.logger import console_error
pd.set_option(
"mode.chained_assignment", None
) # ignore SettingWithCopyWarning pandas warning
####################
# GRAPHICAL ELEMENTS
####################
def multi_bar_chart(
table_id: int, display_df: pd.DataFrame
) -> dict[str, dict[str, Any]]:
nested_bar: dict[str, dict[str, Any]] = {}
if table_id == 1604:
for _, row in display_df.iterrows():
coherency = row["Coherency"]
if coherency not in nested_bar:
nested_bar[coherency] = {}
nested_bar[coherency][row["Xfer"]] = row["Avg"]
elif table_id == 1705: # L2 - Fabric Interface Stalls
for _, row in display_df.iterrows():
transaction = row["Transaction"]
if transaction not in nested_bar:
nested_bar[transaction] = {}
nested_bar[transaction][row["Type"]] = row["Avg"]
return nested_bar
def create_instruction_mix_bar_chart(display_df: pd.DataFrame, df_unit: str) -> px.bar:
display_df = display_df.copy()
display_df["Avg"] = display_df["Avg"].apply(lambda x: int(x) if x != "N/A" else 0)
return px.bar(
display_df,
x="Avg",
y="Metric",
color="Avg",
labels={"Avg": f"# of {df_unit.lower()}"},
height=400,
orientation="h",
)
def create_multi_bar_charts(
display_df: pd.DataFrame, table_id: int, df_unit: str
) -> list[px.bar]:
display_df = display_df.copy()
display_df["Avg"] = display_df["Avg"].apply(lambda x: int(x) if x != "N/A" else 0)
nested_bar = multi_bar_chart(table_id, display_df)
charts = []
for group, metric in nested_bar.items():
chart = px.bar(
title=group,
x=list(metric.values()),
y=list(metric.keys()),
labels={"x": df_unit, "y": ""},
text=list(metric.values()),
orientation="h",
height=200,
)
chart.update_xaxes(showgrid=False, rangemode="nonnegative")
chart.update_yaxes(showgrid=False)
chart.update_layout(title_x=0.5)
charts.append(chart)
return charts
def create_sol_charts(display_df: pd.DataFrame, table_id: int) -> list[px.bar]:
display_df = display_df.copy()
display_df["Avg"] = display_df["Avg"].apply(
lambda x: float(x) if x != "N/A" else 0.0
)
charts = []
if table_id == 1701:
# Special layout for L2 Cache SOL
pct_data = display_df[display_df["Unit"] == "Pct"]
charts.append(
px.bar(
pct_data,
x="Avg",
y="Metric",
color="Avg",
range_color=[0, 100],
labels={"Avg": "%"},
height=220,
orientation="h",
).update_xaxes(range=[0, 110], ticks="inside", title="%")
)
# HBM Bandwidth chart
hbm_row = display_df[display_df["Metric"] == "HBM Bandwidth"]
if not hbm_row.empty:
hbm_bw = float(hbm_row["Avg"].iloc[0])
gb_data = display_df[display_df["Unit"] == "Gb/s"]
charts.append(
px.bar(
gb_data,
x="Avg",
y="Metric",
color="Avg",
range_color=[0, hbm_bw],
labels={"Avg": "GB/s"},
height=220,
orientation="h",
).update_xaxes(range=[0, hbm_bw])
)
elif table_id == 1101:
# Special formatting reference 'Pct of Peak' value
display_df["Pct of Peak"] = display_df["Pct of Peak"].apply(
lambda x: float(x) if x != "N/A" else 0.0
)
charts.append(
px.bar(
display_df,
x="Pct of Peak",
y="Metric",
color="Pct of Peak",
range_color=[0, 100],
labels={"Avg": "%"},
height=400,
orientation="h",
).update_xaxes(range=[0, 110])
)
else:
charts.append(
px.bar(
display_df,
x="Avg",
y="Metric",
color="Avg",
range_color=[0, 100],
labels={"Avg": "%"},
height=400,
orientation="h",
).update_xaxes(range=[0, 110])
)
return charts
def build_bar_chart(
display_df: pd.DataFrame,
table_config: dict[str, Any],
barchart_elements: dict[str, Any],
) -> list:
"""
Read data into a bar chart. ID will determine which subtype of barchart.
"""
table_id = table_config["id"]
charts: list[px.bar] = []
# Get unit from first row if available
df_unit = display_df["Unit"].iloc[0] if "Unit" in display_df.columns else ""
# Instruction Mix bar chart
if table_id in barchart_elements["instr_mix"]:
charts.append(create_instruction_mix_bar_chart(display_df, df_unit))
# Multi bar chart
elif table_id in barchart_elements["multi_bar"]:
charts.extend(create_multi_bar_charts(display_df, table_id, df_unit))
# Speed-of-light bar chart
elif table_id in barchart_elements["sol"]:
charts.extend(create_sol_charts(display_df, table_id))
else:
console_error(
f"Table id {table_id}. Cannot determine barchart type.", exit=False
)
return []
# Apply consistent styling to all charts
for fig in charts:
fig.update_layout(
margin=dict(l=50, r=50, b=50, t=50, pad=4),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font={"color": "#ffffff"},
)
return charts
def get_dark_mode_styles() -> tuple[
dict[str, Any], dict[str, Any], list[dict[str, Any]]
]:
style_header = {
"backgroundColor": "rgb(30, 30, 30)",
"color": "white",
"fontWeight": "bold",
}
style_data = {
"backgroundColor": "rgb(50, 50, 50)",
"color": "white",
"whiteSpace": "normal",
"height": "auto",
}
style_data_conditional = [
{"if": {"row_index": "odd"}, "backgroundColor": "rgb(60, 60, 60)"}
]
return style_header, style_data, style_data_conditional
def build_table_chart(
display_df: pd.DataFrame,
table_config: dict[str, Any],
original_df: pd.DataFrame,
display_columns: list[str],
comparable_columns: list[str],
decimal: int,
) -> list[dash_table.DataTable]:
"""
Read data into a DashTable
"""
d_figs = []
# build comlumns/header with formatting
formatted_columns = []
for col in display_df.columns:
col_lower = str(col).lower()
if col_lower in {"pct", "pop", "percentage"}:
formatted_columns.append({
"id": col,
"name": col,
"type": "numeric",
"format": {"specifier": f".{decimal}f"},
})
elif col in comparable_columns:
formatted_columns.append({
"id": col,
"name": col,
"type": "numeric",
"format": {"specifier": f".{decimal}f"},
})
else:
formatted_columns.append({"id": col, "name": col, "type": "text"})
# tooltip shows only on the 1st col for now if 'Metric Description' available
table_tooltip = (
[
{
column: {
"value": (
str(row["Description"])
if column == display_columns[0] and row["Description"]
else ""
),
"type": "markdown",
}
for column in row.keys()
}
for row in original_df.to_dict("records")
]
if "Description" in original_df.columns.values.tolist()
else None
)
# Get styling based on dark mode
style_header, style_data, style_data_conditional = get_dark_mode_styles()
# build data table with columns, tooltip, df and other properties
d_t = dash_table.DataTable(
id=str(table_config["id"]),
sort_action="native",
sort_mode="multi",
columns=formatted_columns,
tooltip_data=table_tooltip,
# left-aligning the text of the 1st col
style_cell_conditional=[
{"if": {"column_id": display_columns[0]}, "textAlign": "left"}
],
# style cell
style_cell={"maxWidth": "500px"},
# display style
style_header=style_header,
style_data=style_data,
style_data_conditional=style_data_conditional,
# the df to display
data=display_df.to_dict("records"),
)
d_figs.append(d_t)
return d_figs