Unified configuration for metrics (#726)

* Show description of metrics during analysis
    * Use --include-cols Description show the Description column in analyze mode (this is hidden by default)
    * Remove tips field from analysis config

* Align metric names in analysis config and documentation

* Add unified config utils/unified_config.yaml

* Add python script utils/split_config.py to auto generate analysis configuration and documentation metrics description
   * Add test case to ensure unified config is older than auto-generated config
   * Auto generate analysis config and documentation metrics description

* Update CONTRIBUTING.md to add instructions to build documentation assets
    * Add docker image and compose file to build documentation

* Update CHANGELOG and Documentation

* Use jinja template instead of hardcoding metric tables in documentation

[ROCm/rocprofiler-compute commit: bb44e90b2d]
This commit is contained in:
vedithal-amd
2025-07-25 14:01:34 -04:00
committed by GitHub
parent dcdadfd37d
commit 354fe5f52c
232 changed files with 44409 additions and 22480 deletions
+10 -6
View File
@@ -49,12 +49,16 @@ def filter_df(column, df, filt):
def multi_bar_chart(table_id, display_df):
if table_id == 1604:
nested_bar = {"NC": {}, "UC": {}, "RW": {}, "CC": {}}
nested_bar = {}
for index, row in display_df.iterrows():
if not row["Coherency"] in nested_bar:
nested_bar[row["Coherency"]] = {}
nested_bar[row["Coherency"]][row["Xfer"]] = row["Avg"]
if table_id == 1705: # L2 - Fabric Interface Stalls
nested_bar = {"Read": {}, "Write": {}}
nested_bar = {}
for index, row in display_df.iterrows():
if not row["Transaction"] in nested_bar:
nested_bar[row["Transaction"]] = {}
nested_bar[row["Transaction"]][row["Type"]] = row["Avg"]
return nested_bar
@@ -307,14 +311,14 @@ def build_table_chart(
else:
formatted_columns.append(dict(id=col, name=col, type="text"))
# tooltip shows only on the 1st col for now if 'Tips' available
# tooltip shows only on the 1st col for now if 'Metric Description' available
table_tooltip = (
[
{
column: {
"value": (
str(row["Tips"])
if column == display_columns[0] and row["Tips"]
str(row["Description"])
if column == display_columns[0] and row["Description"]
else ""
),
"type": "markdown",
@@ -323,7 +327,7 @@ def build_table_chart(
}
for row in original_df.to_dict("records")
]
if "Tips" in original_df.columns.values.tolist()
if "Description" in original_df.columns.values.tolist()
else None
)
@@ -25,7 +25,6 @@
from dash import html
from dash_svg import G, Path, Rect, Svg, Text
from config import HIDDEN_COLUMNS
from utils.logger import console_error
@@ -312,7 +311,7 @@ def insert_chart_data(mem_data, base_data):
id="sl1_rd",
fill="#FFFFFF",
fontSize="12px",
children=format_value_for_display(memchart_values["VL1D Rd"]),
children=memchart_values["sL1D Rd"],
),
Text(
x="838",
@@ -320,7 +319,7 @@ def insert_chart_data(mem_data, base_data):
id="sl1_hit",
fill="rgb(0, 0, 0)",
fontSize="12px",
children=memchart_values["VL1D Hit"],
children=memchart_values["sL1D Hit"],
),
Text(
x="838",
@@ -328,7 +327,7 @@ def insert_chart_data(mem_data, base_data):
id="sl1_lat",
fill="rgb(0, 0, 0)",
fontSize="12px",
children=memchart_values["VL1D Lat"],
children=memchart_values["sL1D Lat"],
),
Text(
x="1000",
@@ -336,7 +335,7 @@ def insert_chart_data(mem_data, base_data):
id="sl1_l2_rd",
fill="#FFFFFF",
fontSize="12px",
children=format_value_for_display(memchart_values["VL1D_L2 Rd"]),
children=memchart_values["sL1D_L2 Rd"],
),
Text(
x="1000",
@@ -344,7 +343,7 @@ def insert_chart_data(mem_data, base_data):
id="sl1_l2_wr",
fill="#FFFFFF",
fontSize="12px",
children=format_value_for_display(memchart_values["VL1D_L2 Wr"]),
children=memchart_values["sL1D_L2 Wr"],
),
Text(
x="1008",
@@ -352,7 +351,7 @@ def insert_chart_data(mem_data, base_data):
id="sl1_l2_atom",
fill="#FFFFFF",
fontSize="12px",
children=memchart_values["VL1D_L2 Atomic"],
children=memchart_values["sL1D_L2 Atomic"],
),
# ----------------------------------------
# Instr L1 Cache Block
@@ -1079,7 +1079,7 @@ class MemChart:
wires_E_GLV.vl1_rd = metric_dict["VL1 Rd"]
wires_E_GLV.vl1_wr = metric_dict["VL1 Wr"]
wires_E_GLV.vl1_atomic = metric_dict["VL1 Atomic"]
wires_E_GLV.sl1_rd = metric_dict["VL1D Rd"]
wires_E_GLV.sl1_rd = metric_dict["sL1D Rd"]
wires_E_GLV.draw(canvas)
@@ -1146,8 +1146,8 @@ class MemChart:
block_const_L1.y_max = block_vector_L1.y_min - 3
block_const_L1.y_min = block_const_L1.y_max - 5
block_const_L1.hit = metric_dict["VL1D Hit"]
block_const_L1.latency = metric_dict["VL1D Lat"]
block_const_L1.hit = metric_dict["sL1D Hit"]
block_const_L1.latency = metric_dict["sL1D Lat"]
block_const_L1.draw(canvas)
@@ -1174,9 +1174,9 @@ class MemChart:
wires_L1_L2.vl1_l2_rd = metric_dict["VL1_L2 Rd"]
wires_L1_L2.vl1_l2_wr = metric_dict["VL1_L2 Wr"]
wires_L1_L2.vl1_l2_atomic = metric_dict["VL1_L2 Atomic"]
wires_L1_L2.sl1_l2_rd = metric_dict["VL1D_L2 Rd"]
wires_L1_L2.sl1_l2_wr = metric_dict["VL1D_L2 Wr"]
wires_L1_L2.sl1_l2_atomic = metric_dict["VL1D_L2 Atomic"]
wires_L1_L2.sl1_l2_rd = metric_dict["sL1D_L2 Rd"]
wires_L1_L2.sl1_l2_wr = metric_dict["sL1D_L2 Wr"]
wires_L1_L2.sl1_l2_atomic = metric_dict["sL1D_L2 Atomic"]
wires_L1_L2.il1_l2_req = metric_dict["IL1_L2 Rd"]
wires_L1_L2.draw(canvas)
@@ -1331,9 +1331,9 @@ if __name__ == "__main__":
metric_dict["VL1 Coalesce"] = 27
metric_dict["VL1 Stall"] = 28
metric_dict["VL1D Rd"] = 29
metric_dict["VL1D Hit"] = 30
metric_dict["VL1D Lat"] = 31
metric_dict["sL1D Rd"] = 29
metric_dict["sL1D Hit"] = 30
metric_dict["sL1D Lat"] = 31
metric_dict["IL1 Fetch"] = 32
metric_dict["IL1 Hit"] = 33
@@ -1344,9 +1344,9 @@ if __name__ == "__main__":
metric_dict["VL1_L2 Wr"] = 37
metric_dict["VL1_L2 Atomic"] = 38
metric_dict["VL1D_L2 Rd"] = 39
metric_dict["VL1D_L2 Wr"] = 40
metric_dict["VL1D_L2 Atomic"] = 41
metric_dict["sL1D_L2 Rd"] = 39
metric_dict["sL1D_L2 Wr"] = 40
metric_dict["sL1D_L2 Atomic"] = 41
metric_dict["IL1_L2 Rd"] = 42
metric_dict["L2 Hit"] = 43
@@ -228,7 +228,7 @@ class MIGPUSpecs:
gpu_arch_lower = gpu_arch_.lower()
# Handle gfx942 with chip_id mapping
if gpu_arch_lower not in ("gfx906", "gfx908", "gfx90a"):
if gpu_arch_lower not in ("gfx908", "gfx90a"):
if chip_id_ and int(chip_id_) in cls._chip_id_dict:
gpu_model = cls._chip_id_dict.get(int(chip_id_))
else:
@@ -283,7 +283,7 @@ class MIGPUSpecs:
4. Default settings (last resort)
"""
# Constants for legacy GPUs that don't support compute partitions
LEGACY_ARCHS = {"gfx906", "gfx908", "gfx90a"}
LEGACY_ARCHS = {"gfx908", "gfx90a"}
LEGACY_MODELS = {"mi50", "mi60", "mi100", "mi210", "mi250", "mi250x"}
# Normalize inputs to lowercase for consistent comparison
@@ -509,17 +509,19 @@ def build_dfs(archConfigs, filter_metrics, sys_info):
headers.append(k)
for key, tile in data_config["header"].items():
if key != "metric" and key != "tips" and key != "expr":
if key != "metric" and key != "expr":
headers.append(tile)
else:
headers.append(data_config["header"]["metric"])
for key, tile in data_config["header"].items():
if key != "tips":
if key != "metric":
headers.append(tile)
# do we always need one?
headers.append("coll_level")
if "tips" in data_config["header"].keys():
headers.append(data_config["header"]["tips"])
# Only add Metrics Description column if it is defined in the panel
if "metrics_description" in panel:
headers.append("Description")
df = pd.DataFrame(columns=headers)
@@ -563,16 +565,12 @@ def build_dfs(archConfigs, filter_metrics, sys_info):
for bk, bv in simple_box.items():
values.append(bv[0] + v + bv[1])
else:
if (
k != "tips"
and k != "coll_level"
and k != "alias"
):
if k != "coll_level" and k != "alias":
values.append(v)
else:
for k, v in entries.items():
if k != "tips" and k != "coll_level" and k != "alias":
if k != "coll_level" and k != "alias":
values.append(v)
eqn_content.append(v)
@@ -584,8 +582,11 @@ def build_dfs(archConfigs, filter_metrics, sys_info):
else:
values.append(schema.pmc_perf_file_prefix)
if "tips" in entries.keys():
values.append(entries["tips"])
if "metrics_description" in panel:
if key in panel["metrics_description"]:
values.append(panel["metrics_description"][key])
else:
values.append("")
# print(headers, values)
# print(key, entries)
@@ -1459,7 +1460,14 @@ def build_comparable_columns(time_unit):
Build comparable columns/headers for display
"""
comparable_columns = schema.supported_field
top_stat_base = ["Count", "Sum", "Mean", "Median", "Standard Deviation"]
top_stat_base = [
"Count",
"Sum",
"Mean",
"Median",
"Standard Deviation",
"Description",
]
for h in top_stat_base:
comparable_columns.append(h + "(" + time_unit + ")")
+26 -7
View File
@@ -23,6 +23,7 @@
##############################################################################el
import copy
import textwrap
from pathlib import Path
import pandas as pd
@@ -51,8 +52,21 @@ def string_multiple_lines(source, width, max_rows):
def get_table_string(df, transpose=False, decimal=2):
"""
Convert DataFrame to a formatted table string, wrapping specified columns.
"""
df_to_show = df.transpose() if transpose else df
wrap_columns = ["Description"]
wrap_width = 40
for col in wrap_columns:
if col in df_to_show.columns:
df_to_show[col] = (
df_to_show[col]
.astype(str)
.apply(lambda x: textwrap.fill(x, width=wrap_width))
)
return tabulate(
df.transpose() if transpose else df,
df_to_show,
headers="keys",
tablefmt="fancy_grid",
floatfmt="." + str(decimal) + "f",
@@ -118,6 +132,10 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
int(convert_metric_id_to_panel_info(metric_id)[0])
for metric_id in filter_panel_ids
]
if args.include_cols:
hidden_cols = list(set(config.HIDDEN_COLUMNS_CLI) - set(args.include_cols))
else:
hidden_cols = config.HIDDEN_COLUMNS_CLI
for panel_id, panel in archConfigs.panel_configs.items():
# Skip panels that don't support baseline comparison
@@ -196,12 +214,14 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
df = pd.DataFrame(index=base_df.index)
for header in list(base_df.keys()):
# For raw csv table, columns cannot be filtered
# If columns are filtered, then skip the headers not in filtered columns
if (
(not args.cols)
or (args.cols and base_df.columns.get_loc(header) in args.cols)
or (type == "raw_csv_table")
type == "raw_csv_table"
or not args.cols
or base_df.columns.get_loc(header) in args.cols
):
if header in config.HIDDEN_COLUMNS:
if header in hidden_cols:
pass
elif header not in comparable_columns:
if (
@@ -236,8 +256,7 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
cur_df = convert_time_columns(cur_df, args.time_unit)
if (type == "raw_csv_table") or (
type == "metric_table"
and (not header in config.HIDDEN_COLUMNS)
type == "metric_table" and (not header in hidden_cols)
):
if run != base_run:
# calc percentage over the baseline