diff --git a/projects/rocprofiler-compute/CHANGELOG.md b/projects/rocprofiler-compute/CHANGELOG.md index 19c3a53a65..689f1f92d7 100644 --- a/projects/rocprofiler-compute/CHANGELOG.md +++ b/projects/rocprofiler-compute/CHANGELOG.md @@ -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 diff --git a/projects/rocprofiler-compute/docs/how-to/profile/mode.rst b/projects/rocprofiler-compute/docs/how-to/profile/mode.rst index d9b4b044e4..74640f6b45 100644 --- a/projects/rocprofiler-compute/docs/how-to/profile/mode.rst +++ b/projects/rocprofiler-compute/docs/how-to/profile/mode.rst @@ -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 diff --git a/projects/rocprofiler-compute/src/argparser.py b/projects/rocprofiler-compute/src/argparser.py index 8959270113..8a51decaba 100644 --- a/projects/rocprofiler-compute/src/argparser.py +++ b/projects/rocprofiler-compute/src/argparser.py @@ -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", diff --git a/projects/rocprofiler-compute/src/rocprof_compute_soc/soc_base.py b/projects/rocprofiler-compute/src/rocprof_compute_soc/soc_base.py index c28fac059f..efd36ef2ea 100644 --- a/projects/rocprofiler-compute/src/rocprof_compute_soc/soc_base.py +++ b/projects/rocprofiler-compute/src/rocprof_compute_soc/soc_base.py @@ -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): diff --git a/projects/rocprofiler-compute/src/rocprof_compute_tui/utils/tui_utils.py b/projects/rocprofiler-compute/src/rocprof_compute_tui/utils/tui_utils.py index 44021a1cf3..de56c56607 100644 --- a/projects/rocprofiler-compute/src/rocprof_compute_tui/utils/tui_utils.py +++ b/projects/rocprofiler-compute/src/rocprof_compute_tui/utils/tui_utils.py @@ -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}") diff --git a/projects/rocprofiler-compute/src/utils/tty.py b/projects/rocprofiler-compute/src/utils/tty.py index a9fa977b5e..ee73cf79cc 100644 --- a/projects/rocprofiler-compute/src/utils/tty.py +++ b/projects/rocprofiler-compute/src/utils/tty.py @@ -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 diff --git a/projects/rocprofiler-compute/src/utils/utils.py b/projects/rocprofiler-compute/src/utils/utils.py index c12611405b..cf1c1c5f5e 100644 --- a/projects/rocprofiler-compute/src/utils/utils.py +++ b/projects/rocprofiler-compute/src/utils/utils.py @@ -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): diff --git a/projects/rocprofiler-compute/tests/test_utils.py b/projects/rocprofiler-compute/tests/test_utils.py index b28e797761..386a0acd0d 100644 --- a/projects/rocprofiler-compute/tests/test_utils.py +++ b/projects/rocprofiler-compute/tests/test_utils.py @@ -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") # =============================================================================