Improve block filtering to accept metric ids (#821)

* Fix tests
* Update CHANGELOG and documentation

[ROCm/rocprofiler-compute commit: a70ae40ddc]
Tento commit je obsažen v:
vedithal-amd
2025-07-23 16:16:29 -04:00
odevzdal GitHub
rodič f6f3a6ed3e
revize d9da3feadf
8 změnil soubory, kde provedl 226 přidání a 127 odebrání
+2
Zobrazit soubor
@@ -81,6 +81,8 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs.
* Update Roofline binaries
* Rebuild using latest ROCm stack
* OS distribution support minimum for roofline feature is now Ubuntu22.04, RHEL9, and SLES15SP6
* Improve analysis block based filtering to accept metric id level filtering
* This can be used to collect individual metrics from various sections of analysis config
### Optimized
+30
Zobrazit soubor
@@ -294,6 +294,36 @@ for ``Compute Unit - Instruction Mix`` (block 10) and ``Wavefront Launch Statist
...
It is also possible to collect individual metrics from the analysis report by providing metric ids.
The following example only collects the counters required to calculate ``Total VALU FLOPs`` (metric id 11.1.0) and ``LDS Utilization`` (metric id 12.1.0).
.. code-block:: shell-session
$ rocprof-compute profile --name vcopy -b 11.1.1 12.1.1 -- ./vcopy -n 1048576 -b 256
__ _
_ __ ___ ___ _ __ _ __ ___ / _| ___ ___ _ __ ___ _ __ _ _| |_ ___
| '__/ _ \ / __| '_ \| '__/ _ \| |_ _____ / __/ _ \| '_ ` _ \| '_ \| | | | __/ _ \
| | | (_) | (__| |_) | | | (_) | _|_____| (_| (_) | | | | | | |_) | |_| | || __/
|_| \___/ \___| .__/|_| \___/|_| \___\___/|_| |_| |_| .__/ \__,_|\__\___|
|_| |_|
rocprofiler-compute version: 2.0.0
Profiler choice: rocprofv1
Path: /home/auser/repos/rocprofiler-compute/sample/workloads/vcopy/MI200
Target: MI200
Command: ./vcopy -n 1048576 -b 256
Kernel Selection: None
Dispatch Selection: None
Hardware Blocks: []
Report Sections: ['11.1.0', '12.1.0']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Collecting Performance Counters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
To see a list of available hardware report blocks, use the ``--list-metrics`` option.
.. code-block:: shell-session
+3 -4
Zobrazit soubor
@@ -179,9 +179,8 @@ Examples:
)
def validate_block(value):
# Metric id regex, for example, 10, 4, 4.3, 4.32
# Dont allow more than two digits after decimal point
metric_id_pattern = re.compile(r"^\d+$|^\d+\.\d$|^\d+\.\d\d$")
# Metric id is of the form I or I.I or I.I.I where I is two digit number.
metric_id_pattern = re.compile(r"^\d{1,2}(?:\.\d{1,2}){0,2}$")
if metric_id_pattern.match(value):
return value
raise argparse.ArgumentTypeError(f"Invalid metric id: {value}")
@@ -195,7 +194,7 @@ Examples:
nargs="+",
required=False,
default=[],
help="""\t\t\tSpecify metric id(s) from --list-metrics for filtering (e.g. 10, 4, 4.3).\n\t\t\tCan provide multiple space separated arguments.""",
help="""\t\t\tSpecify metric id(s) from --list-metrics for filtering (e.g. 12, 12.1, 12.1.1).\n\t\t\tCan provide multiple space separated arguments.""",
)
profile_group.add_argument(
"--list-metrics",
+59 -54
Zobrazit soubor
@@ -50,7 +50,7 @@ from utils.parser import build_in_vars, supported_denom
from utils.utils import (
add_counter_extra_config_input_yaml,
capture_subprocess_output,
convert_metric_id_to_panel_idx,
convert_metric_id_to_panel_info,
detect_rocprof,
get_submodules,
is_tcc_channel_counter,
@@ -261,66 +261,73 @@ class OmniSoC_Base:
Create a set of counters required for the selected report sections.
Parse analysis report configuration files based on the selected report sections to be filtered.
"""
counters = set()
config_filenames = {
filename: []
for filename in os.listdir(
Path(self.get_args().config_dir).joinpath(self.__arch)
)
if filename.endswith(".yaml")
# Read the analysis config files and filter
config_root_dir = f"{self.get_args().config_dir}/{self.__arch}"
# File id dict
config_filename_dict = {
Path(filename).name.split("_")[0]: filename
for filename in glob.glob(f"{config_root_dir}/*.yaml")
}
file_ids = []
for section in self.get_args().filter_blocks:
section_num = convert_metric_id_to_panel_idx(section)
file_id = str(section_num // 100)
# Convert "4" to "04"
if len(file_id) == 1:
file_id = f"0{file_id}"
file_ids.append(file_id)
# Apply sub section filtering
for config_filename in config_filenames:
# If first two characters of the config filename match the file_id
if config_filename[:2].startswith(file_id) and section_num % 100:
config_filenames[config_filename].append(section_num)
texts = list()
# Apply section filters only if metric ids have been provided for filtering
if self.get_args().filter_blocks:
# Identify yaml files corresponding to file_ids
config_filenames = {
filename: subsections
for filename, subsections in config_filenames.items()
if filename[:2].startswith(tuple(file_ids))
}
if not self.get_args().filter_blocks:
# Read all config files if no filter_blocks are specified
for filename in config_filename_dict.values():
with open(filename, "r") as stream:
texts.append(stream.read())
for config_filename, subsections in config_filenames.items():
# Read the yaml file
with open(
Path(self.get_args().config_dir).joinpath(self.__arch, config_filename),
"r",
) as stream:
section_config = yaml.safe_load(stream)
# Extract subsection if section is of the form 4.52
if subsections:
section_config_text = "\n".join(
[
# Convert yaml to string
yaml.dump(subsection, sort_keys=False)
for subsection in section_config["Panel Config"]["data source"]
if subsection["metric_table"]["id"] in subsections
]
for block_id in self.get_args().filter_blocks:
file_id, panel_id, metric_id = convert_metric_id_to_panel_info(block_id)
# File id filtering
if file_id not in config_filename_dict:
console_warning(
f"Skipping {block_id}: file id {file_id} not found in {config_root_dir}"
)
else:
# Convert yaml to string
section_config_text = yaml.dump(section_config, sort_keys=False)
counters = counters.union(self.parse_counters(section_config_text))
continue
with open(config_filename_dict[file_id], "r") as stream:
file_config = yaml.safe_load(stream)
if panel_id is None:
# If no panel id level filtering, then read the whole file
texts.append(yaml.dump(file_config, sort_keys=False))
continue
# Panel id filtering
panel_dict = {
section["metric_table"]["id"]: section["metric_table"]
for section in file_config["Panel Config"]["data source"]
if "metric_table" in section
}
if panel_id not in panel_dict:
console_warning(
f"Skipping {block_id}: metric table {panel_id} not found in {config_filename_dict[file_id]}"
)
continue
if metric_id is None:
# If no metric id level filtering, then read the whole panel
texts.append(yaml.dump(panel_dict[panel_id], sort_keys=False))
# Metric id filtering
metric_dict = {
id: panel_dict[panel_id]["metric"][metric]
for id, metric in enumerate(panel_dict[panel_id]["metric"].keys())
}
if metric_id not in metric_dict:
console_warning(
f"Skipping {block_id}: metric id {metric_id} not found in panel id {panel_id}"
)
continue
texts.append(yaml.dump(metric_dict[metric_id], sort_keys=False))
counters = self.parse_counters("\n".join(texts))
# Handle TCC channel counters: if hw_counter_matches has elements ending with '['
# Expand and interleve the TCC channel counters
# e.g. TCC_HIT[0] TCC_ATOMIC[0] ... TCC_HIT[1] TCC_ATOMIC[1] ...
num_xcd_for_pmc_file = 1
if using_v3():
num_xcd_for_pmc_file = int(self._mspec.num_xcd)
else:
num_xcd_for_pmc_file = 1
for counter_name in counters.copy():
if counter_name.startswith("TCC") and counter_name.endswith("["):
counters.remove(counter_name)
@@ -562,7 +569,7 @@ class OmniSoC_Base:
# Sanity check whether counters are supported by underlying rocprof tool
rocprof_counters = self.get_rocprof_supported_counters()
# rocprof does not support TCC channel counters, so remove channel suffix for comparison
# rocprof does not support TCC channel counters in the avail output, so remove channel suffix for comparison
not_supported_counters = {
counter.split("[")[0] if is_tcc_channel_counter(counter) else counter
for counter in counters
@@ -606,8 +613,6 @@ class OmniSoC_Base:
file_count = 0
# Store all channels for a TCC channel counter in the same file
tcc_channel_counter_file_map = dict()
# Store all pipes for SPI pipe counters in the same file
spi_pipe_counter_file_map = dict()
for ctr in counters:
# Store all channels for a TCC channel counter in the same file
if is_tcc_channel_counter(ctr):
+43 -9
Zobrazit soubor
@@ -313,7 +313,8 @@ def process_panels_to_dataframes(
name for name, type in filter_panel_ids.items() if type == "metric_id"
]
filter_panel_ids = [
convert_metric_id_to_panel_idx(section) for section in filter_panel_ids
int(convert_metric_id_to_panel_info(metric_id)[0])
for metric_id in filter_panel_ids
]
# Initialize the result structure
@@ -562,15 +563,48 @@ def string_multiple_lines(source, width, max_rows):
return "\n".join(lines)
def convert_metric_id_to_panel_idx(metric_id):
# "4.02" -> 402
# "4.23" -> 423
# "4" -> 400
def convert_metric_id_to_panel_info(metric_id):
"""
Convert metric id into panel information.
Output is a tuples of the form (file_id, panel_id, metric_id).
For example:
Input: "2"
Output: ("0200", None, None)
Input: "11"
Output: ("1100", None, None)
Input: "11.1"
Output: ("1100", 1101, None)
Input: "11.1.1"
Output: ("1100", 1101, 1)
Raises exception for invalid metric id.
"""
tokens = metric_id.split(".")
if len(tokens) == 1:
return int(tokens[0]) * 100
elif len(tokens) == 2:
return int(tokens[0]) * 100 + int(tokens[1])
if 0 < len(tokens) < 4:
# File id
file_id = str(int(tokens[0]))
# 4 -> 04
if len(file_id) < 2:
file_id = f"0{file_id}"
# Multiply integer by 100
file_id = f"{file_id}00"
# Panel id
if len(tokens) > 1:
panel_id = int(tokens[0]) * 100
panel_id += int(tokens[1])
else:
panel_id = None
# Metric id
if len(tokens) > 2:
metric_id = int(tokens[2])
else:
metric_id = None
return (file_id, panel_id, metric_id)
else:
raise Exception(f"Invalid metric id: {metric_id}")
+3 -3
Zobrazit soubor
@@ -31,7 +31,7 @@ from tabulate import tabulate
from config import HIDDEN_COLUMNS, HIDDEN_SECTIONS
from utils import mem_chart, parser
from utils.logger import console_error, console_log, console_warning
from utils.utils import convert_metric_id_to_panel_idx
from utils.utils import convert_metric_id_to_panel_info
def string_multiple_lines(source, width, max_rows):
@@ -71,9 +71,9 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
name for name, type in filter_panel_ids.items() if type == "metric_id"
]
filter_panel_ids = [
convert_metric_id_to_panel_idx(section) for section in filter_panel_ids
int(convert_metric_id_to_panel_info(metric_id)[0])
for metric_id in filter_panel_ids
]
comparable_columns = parser.build_comparable_columns(args.time_unit)
for panel_id, panel in archConfigs.panel_configs.items():
# Skip panels that don't support baseline comparison
+43 -9
Zobrazit soubor
@@ -1536,16 +1536,50 @@ def merge_counters_spatial_multiplex(df_multi_index):
return final_df
def convert_metric_id_to_panel_idx(metric_id):
# "4.02" -> 402
# "4.23" -> 423
# "4" -> 400
def convert_metric_id_to_panel_info(metric_id):
"""
Convert metric id into panel information.
Output is a tuples of the form (file_id, panel_id, metric_id).
For example:
Input: "2"
Output: ("0200", None, None)
Input: "11"
Output: ("1100", None, None)
Input: "11.1"
Output: ("1100", 1101, None)
Input: "11.1.1"
Output: ("1100", 1101, 1)
Raises exception for invalid metric id.
"""
tokens = metric_id.split(".")
if len(tokens) == 1:
return int(tokens[0]) * 100
if len(tokens) == 2:
return int(tokens[0]) * 100 + int(tokens[1])
raise Exception(f"Invalid metric id: {metric_id}")
if 0 < len(tokens) < 4:
# File id
file_id = str(int(tokens[0]))
# 4 -> 04
if len(file_id) == 1:
file_id = f"0{file_id}"
# Multiply integer by 100
file_id = f"{file_id}00"
# Panel id
if len(tokens) > 1:
panel_id = int(tokens[0]) * 100
panel_id += int(tokens[1])
else:
panel_id = None
# Metric id
if len(tokens) > 2:
metric_id = int(tokens[2])
else:
metric_id = None
return (file_id, panel_id, metric_id)
else:
raise Exception(f"Invalid metric id: {metric_id}")
def format_time(seconds):
+43 -48
Zobrazit soubor
@@ -8484,39 +8484,39 @@ def test_merge_counters_spatial_multiplex_timestamp_median_calculation():
# =============================================================================
# Tests for convert_metric_id_to_panel_idx function
# Tests for convert_metric_id_to_panel_info function
# ============================================================================
def test_convert_metric_id_to_panel_idx_zero_values():
"""Test convert_metric_id_to_panel_idx with zero values in different positions.
def test_convert_metric_id_to_panel_info_zero_values():
"""Test convert_metric_id_to_panel_info with zero values in different positions.
Args:
None
Returns:
None: Asserts that zero values are handled correctly in metric IDs.
"""
assert utils.convert_metric_id_to_panel_idx("0") == 0
assert utils.convert_metric_id_to_panel_idx("0.0") == 0
assert utils.convert_metric_id_to_panel_idx("5.0") == 500
assert utils.convert_metric_id_to_panel_idx("0.5") == 5
assert utils.convert_metric_id_to_panel_info("0") == ("0000", None, None)
assert utils.convert_metric_id_to_panel_info("0.0") == ("0000", 0, None)
assert utils.convert_metric_id_to_panel_info("5.0") == ("0500", 500, None)
assert utils.convert_metric_id_to_panel_info("0.5") == ("0000", 5, None)
def test_convert_metric_id_to_panel_idx_leading_zeros():
"""Test convert_metric_id_to_panel_idx with leading zeros in metric IDs.
def test_convert_metric_id_to_panel_info_leading_zeros():
"""Test convert_metric_id_to_panel_info with leading zeros in metric IDs.
Args:
None
Returns:
None: Asserts that leading zeros are handled correctly.
"""
assert utils.convert_metric_id_to_panel_idx("04") == 400
assert utils.convert_metric_id_to_panel_idx("4.02") == 402
assert utils.convert_metric_id_to_panel_idx("01.05") == 105
assert utils.convert_metric_id_to_panel_info("04") == ("0400", None, None)
assert utils.convert_metric_id_to_panel_info("4.02") == ("0400", 402, None)
assert utils.convert_metric_id_to_panel_info("01.05") == ("0100", 105, None)
def test_convert_metric_id_to_panel_idx_invalid_empty_string():
"""Test convert_metric_id_to_panel_idx with empty string raises exception.
def test_convert_metric_id_to_panel_info_invalid_empty_string():
"""Test convert_metric_id_to_panel_info with empty string raises exception.
Args:
None
@@ -8524,11 +8524,11 @@ def test_convert_metric_id_to_panel_idx_invalid_empty_string():
None: Asserts that empty string raises ValueError.
"""
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx("")
utils.convert_metric_id_to_panel_info("")
def test_convert_metric_id_to_panel_idx_invalid_too_many_parts():
"""Test convert_metric_id_to_panel_idx with more than two parts raises exception.
def test_convert_metric_id_to_panel_info_invalid_too_many_parts():
"""Test convert_metric_id_to_panel_info with more than two parts raises exception.
Args:
None
@@ -8536,17 +8536,17 @@ def test_convert_metric_id_to_panel_idx_invalid_too_many_parts():
None: Asserts that metric IDs with more than two parts raise Exception.
"""
with pytest.raises(Exception, match="Invalid metric id"):
utils.convert_metric_id_to_panel_idx("4.02.1")
utils.convert_metric_id_to_panel_info("4.02.1.5")
with pytest.raises(Exception, match="Invalid metric id"):
utils.convert_metric_id_to_panel_idx("1.2.3.4")
utils.convert_metric_id_to_panel_info("1.2.3.4")
with pytest.raises(Exception, match="Invalid metric id"):
utils.convert_metric_id_to_panel_idx("4.02.1.5")
utils.convert_metric_id_to_panel_info("4.02.1.5")
def test_convert_metric_id_to_panel_idx_invalid_non_numeric():
"""Test convert_metric_id_to_panel_idx with non-numeric values raises exception.
def test_convert_metric_id_to_panel_info_invalid_non_numeric():
"""Test convert_metric_id_to_panel_info with non-numeric values raises exception.
Args:
None
@@ -8554,68 +8554,63 @@ def test_convert_metric_id_to_panel_idx_invalid_non_numeric():
None: Asserts that non-numeric metric IDs raise ValueError.
"""
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx("abc")
utils.convert_metric_id_to_panel_info("abc")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx("4.abc")
utils.convert_metric_id_to_panel_info("4.abc")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx("abc.02")
utils.convert_metric_id_to_panel_info("abc.02")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx("4.02abc")
utils.convert_metric_id_to_panel_info("4.02abc")
def test_convert_metric_id_to_panel_idx_invalid_floating_point():
"""Test convert_metric_id_to_panel_idx with floating point numbers in unexpected format.
def test_convert_metric_id_to_panel_info_three_floating_point():
"""Test convert_metric_id_to_panel_info with floating point numbers in unexpected format.
Args:
None
Returns:
None: Asserts behavior with floating point representations.
"""
with pytest.raises(Exception, match="Invalid metric id"):
utils.convert_metric_id_to_panel_idx("4.0.2")
with pytest.raises(Exception, match="Invalid metric id"):
utils.convert_metric_id_to_panel_idx("4.2.0")
assert utils.convert_metric_id_to_panel_info("4.0.2") == ("0400", 400, 2)
assert utils.convert_metric_id_to_panel_info("4.2.0") == ("0400", 402, 0)
assert utils.convert_metric_id_to_panel_info("4.0.3") == ("0400", 400, 3)
def test_convert_metric_id_to_panel_idx_edge_case_whitespace():
"""Test convert_metric_id_to_panel_idx with whitespace in metric IDs.
def test_convert_metric_id_to_panel_info_edge_case_whitespace():
"""Test convert_metric_id_to_panel_info with whitespace in metric IDs.
Args:
None
Returns:
None: Asserts that whitespace is handled (int() strips whitespace).
"""
assert utils.convert_metric_id_to_panel_idx(" 4") == 400
assert utils.convert_metric_id_to_panel_idx("4 ") == 400
assert utils.convert_metric_id_to_panel_idx(" 4.02 ") == 402
assert utils.convert_metric_id_to_panel_idx("4. 02") == 402
assert utils.convert_metric_id_to_panel_idx(" 4 . 02 ") == 402
assert utils.convert_metric_id_to_panel_info(" 4") == ("0400", None, None)
assert utils.convert_metric_id_to_panel_info("4 ") == ("0400", None, None)
assert utils.convert_metric_id_to_panel_info("4 . 02") == ("0400", 402, None)
def test_convert_metric_id_to_panel_idx_edge_case_dot_only():
"""Test convert_metric_id_to_panel_idx with only dot character raises exception.
def test_convert_metric_id_to_panel_info_edge_case_dot_only():
"""Test convert_metric_id_to_panel_info with only dot character raises exception.
Args:
None
Returns:
None: Asserts that metric ID with only dot raises Exception.
"""
with pytest.raises(Exception, match="Invalid metric id"):
utils.convert_metric_id_to_panel_idx("..")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_info("..")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx(".")
utils.convert_metric_id_to_panel_info(".")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx("4.")
utils.convert_metric_id_to_panel_info("4.")
with pytest.raises(ValueError):
utils.convert_metric_id_to_panel_idx(".02")
utils.convert_metric_id_to_panel_info(".02")
# =============================================================================