From 9ddee8de2efdb50938e5d18ff9fe3fe0f15c29ed Mon Sep 17 00:00:00 2001 From: xuchen-amd Date: Fri, 1 Aug 2025 11:31:43 -0400 Subject: [PATCH] TUI: Kernel Selection (#769) --- CHANGELOG.md | 3 + docs/data/analyze/tui_home.png | Bin 0 -> 79913 bytes docs/data/analyze/tui_kernel_selection.png | Bin 0 -> 144086 bytes src/config.py | 2 + src/rocprof_compute_tui/analysis_tui.py | 92 ++- src/rocprof_compute_tui/config.py | 1 - src/rocprof_compute_tui/tui_app.py | 8 +- .../utils/analyze_config.yaml | 51 -- .../utils/kernel_view_config.yaml | 35 ++ src/rocprof_compute_tui/utils/tui_utils.py | 574 +++--------------- src/rocprof_compute_tui/views/kernel_view.py | 203 +++++++ src/rocprof_compute_tui/views/main_view.py | 63 +- .../widgets/center_panel/analyze_view.py | 74 --- .../widgets/center_panel/center_area.py | 15 +- src/rocprof_compute_tui/widgets/charts.py | 80 +-- .../widgets/collapsibles.py | 18 +- .../widgets/directory_tree.py | 39 -- src/utils/mem_chart.py | 114 ++-- src/utils/parser.py | 29 +- tests/test_analyze_commands.py | 2 +- tests/test_db_connector.py | 6 +- 21 files changed, 520 insertions(+), 889 deletions(-) create mode 100644 docs/data/analyze/tui_home.png create mode 100644 docs/data/analyze/tui_kernel_selection.png delete mode 100644 src/rocprof_compute_tui/utils/analyze_config.yaml create mode 100644 src/rocprof_compute_tui/utils/kernel_view_config.yaml create mode 100644 src/rocprof_compute_tui/views/kernel_view.py delete mode 100644 src/rocprof_compute_tui/widgets/center_panel/analyze_view.py delete mode 100644 src/rocprof_compute_tui/widgets/directory_tree.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2f352dee..9f33653aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs. * CLI analysis mode baseline comparison will now only compare common metrics across workloads and will not show Metric ID * Remove metrics from analysis configuration files which are explicitly marked as empty or None +* Change the basic view of TUI from aggregated analysis data to individual kernel analysis data + ### Resolved issues * Fixed not detecting memory clock issue when using amd-smi @@ -42,6 +44,7 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs. * Usage of rocm-smi * Hardware IP block based filtering has been removed in favor of analysis report block based filtering +* Remove aggregated analysis view from TUI mode ## ROCm Compute Profiler 3.2.1 for ROCm 7.0.0 diff --git a/docs/data/analyze/tui_home.png b/docs/data/analyze/tui_home.png new file mode 100644 index 0000000000000000000000000000000000000000..24fde654f04950217fdf944401f33540ae9777dd GIT binary patch literal 79913 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"`7NNn|T@{W*B3o#JWM z_T=Sr%nec6$24q09l=U}XB_32$gnrAcBzh(B!f4%<`o)X3nNOick?W%V@e{cULxhu z2^*U%#Fc^b@A${Pg_A*;?$?6k>pfkvFi0CkN>4SEO8>Y|1rV$#duFaWa?$

Gv>g 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

wJs-H1eeDy4ll@!eW`~ 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 VVlnwz_=k3&lWu>;{OR&f{|kmbPx1f& literal 0 HcmV?d00001 diff --git a/docs/data/analyze/tui_kernel_selection.png b/docs/data/analyze/tui_kernel_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..0ea6204b9758585e45e6cc106d293d3bc8816114 GIT binary patch literal 144086 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≠C?zaLx*62~lGfL>PTjAWMx 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 MI200 - "gfx90a", - "gfx940", - "gfx941", - "gfx942", - "gfx950", - ]: - # add roofline plot to cli output - self.get_socs()[self.arch].analysis_setup( - roofline_parameters={ - "workload_dir": self.path, - "device_id": 0, - "sort_type": "kernels", - "mem_level": "ALL", - "include_kernel_names": False, - "is_standalone": False, - "roofline_data_type": "FP32", - } + def run_kernel_analysis(self): + self.kernel_dfs.clear() + for kernel_name, df in self.raw_dfs.items(): + self.kernel_dfs[kernel_name] = process_panels_to_dataframes( + self.get_args(), df, self._arch_configs[self.arch], roof_plot=None ) - roof_obj = self.get_socs()[self.arch].roofline_obj + return self.kernel_dfs - if roof_obj: - # NOTE: using default data type - roof_plot = roof_obj.cli_generate_plot(roof_obj.get_dtype()[0]) - - results = process_panels_to_dataframes( - self.get_args(), - self._runs, - self._arch_configs[self.arch], - self._profiling_config, - roof_plot=roof_plot, - ) - return results + @demarcate + def run_top_kernel(self): + return get_top_kernels_and_dispatch_ids(self._runs) diff --git a/src/rocprof_compute_tui/config.py b/src/rocprof_compute_tui/config.py index fc51effe2a..c7e1198e31 100644 --- a/src/rocprof_compute_tui/config.py +++ b/src/rocprof_compute_tui/config.py @@ -30,7 +30,6 @@ Central configuration for the application. # Application settings APP_TITLE = "ROCm Compute Profiler TUI" -VERSION = "3.2.0" # Widget configurations DEFAULT_COLLAPSIBLE_STATE = True # True = collapsed by default diff --git a/src/rocprof_compute_tui/tui_app.py b/src/rocprof_compute_tui/tui_app.py index 4f07be9605..4f23660bac 100644 --- a/src/rocprof_compute_tui/tui_app.py +++ b/src/rocprof_compute_tui/tui_app.py @@ -39,15 +39,18 @@ from textual.binding import Binding from textual.widgets import Button, Footer, Header from textual_fspicker import SelectDirectory -from rocprof_compute_tui.config import APP_TITLE, VERSION +import config +from rocprof_compute_tui.config import APP_TITLE from rocprof_compute_tui.views.main_view import MainView from rocprof_compute_tui.widgets.menu_bar.menu_bar import DropdownMenu from utils.specs import MachineSpecs, generate_machine_specs +from utils.utils import get_version class RocprofTUIApp(App): """Main application for the performance analysis tool.""" + VERSION = get_version(config.rocprof_compute_home)["version"] TITLE = f"{APP_TITLE} v{VERSION}" SUB_TITLE = "Workload Analysis Tool" @@ -55,7 +58,8 @@ class RocprofTUIApp(App): BINDINGS = [ Binding(key="q", action="quit", description="Quit"), Binding(key="r", action="refresh", description="Refresh"), - Binding(key="a", action="analyze", description="Analyze"), + # TODO + # Binding(key="a", action="analyze", description="Analyze"), ] def __init__( diff --git a/src/rocprof_compute_tui/utils/analyze_config.yaml b/src/rocprof_compute_tui/utils/analyze_config.yaml deleted file mode 100644 index daea744094..0000000000 --- a/src/rocprof_compute_tui/utils/analyze_config.yaml +++ /dev/null @@ -1,51 +0,0 @@ -sections: - - title: "📊 Summaries" - collapsed: true - class: "summary-section" - subsections: - - title: "Top Kernels" - data_path: ["0. Top Stats", "0.1 Top Kernels"] - collapsed: true - header_label: "Top Kernels by Duration (ns):" - header_class: "section-header" - - title: "Dispatch List" - data_path: ["0. Top Stats", "0.2 Dispatch List"] - collapsed: true - - title: "System Info" - data_path: ["1. System Info", "1.1"] - collapsed: true - - - title: "⚡ High Level Analysis" - collapsed: true - class: "sysinfo-section" - subsections: - - title: "System Speed-of-Light" - data_path: ["2. System Speed-of-Light", "2.1 Speed-of-Light"] - collapsed: true - - title: "Roofline" - collapsed: true - tui_style: "roofline" - widget_id: "roofline-plot" - - title: "Memory Chart" - data_path: ["3. Memory Chart", "3.1 Memory Chart"] - collapsed: true - tui_style: "mem_chart" - - - title: "🔍 Detailed Block Analysis" - collapsed: true - class: "kernels-section" - dynamic_sections: true - skip_sections: - - "0. Top Stats" - - "1. System Info" - - "2. System Speed-of-Light" - - "3. Memory Chart" - - "4. Roofline" - - - title: "🚧 Source Level Analysis" - collapsed: true - class: "source-section" - subsections: - - title: "PC Sampling" - data_path: ["21. PC Sampling", "21.1 PC Sampling"] - collapsed: true diff --git a/src/rocprof_compute_tui/utils/kernel_view_config.yaml b/src/rocprof_compute_tui/utils/kernel_view_config.yaml new file mode 100644 index 0000000000..a12e2f846b --- /dev/null +++ b/src/rocprof_compute_tui/utils/kernel_view_config.yaml @@ -0,0 +1,35 @@ +# TODO: add System Info +# - title: "System Info" +# data_path: ["1. System Info", "1.1"] +# collapsed: true +sections: + - title: "High Level Analysis" + collapsed: true + class: "sysinfo-section" + subsections: + - title: "System Speed-of-Light" + data_path: ["2. System Speed-of-Light", "2.1 System Speed-of-Light"] + collapsed: true + - title: "Memory Chart" + data_path: ["3. Memory Chart", "3.1 Memory Chart"] + collapsed: true + tui_style: "mem_chart" + + - title: "Detailed Block Analysis" + collapsed: true + class: "kernels-section" + dynamic_sections: true + skip_sections: + - "0. Top Stats" + - "1. System Info" + - "2. System Speed-of-Light" + - "3. Memory Chart" + - "4. Roofline" + + - title: "Source Level Analysis" + collapsed: true + class: "source-section" + subsections: + - title: "PC Sampling" + data_path: ["21. PC Sampling", "21.1 PC Sampling"] + collapsed: true diff --git a/src/rocprof_compute_tui/utils/tui_utils.py b/src/rocprof_compute_tui/utils/tui_utils.py index de56c56607..5f5f0ce0ed 100644 --- a/src/rocprof_compute_tui/utils/tui_utils.py +++ b/src/rocprof_compute_tui/utils/tui_utils.py @@ -1,87 +1,27 @@ -import copy import logging -import os -import re from collections import defaultdict from datetime import datetime from enum import Enum -from pathlib import Path +import numpy as np import pandas as pd -from config import HIDDEN_COLUMNS, HIDDEN_SECTIONS - -supported_field = [ - "Value", - "Minimum", - "Maximum", - "Average", - "Median", - "Min", - "Max", - "Avg", - "Pct of Peak", - "Peak", - "Count", - "Mean", - "Pct", - "Std Dev", - "Q1", - "Q3", - "Expression", - # Special keywords for L2 channel - "Channel", - "L2 Cache Hit Rate", - "Requests", - "L2 Read", - "L2 Write", - "L2 Atomic", - "L2-Fabric Requests", - "L2-Fabric Read", - "L2-Fabric Write and Atomic", - "L2-Fabric Atomic", - "L2 Read Req", - "L2 Write Req", - "L2 Atomic Req", - "L2-Fabric Read Req", - "L2-Fabric Write and Atomic Req", - "L2-Fabric Atomic Req", - "L2-Fabric Read Latency", - "L2-Fabric Write Latency", - "L2-Fabric Atomic Latency", - "L2-Fabric Read Stall (PCIe)", - "L2-Fabric Read Stall (Infinity Fabric™)", - "L2-Fabric Read Stall (HBM)", - "L2-Fabric Write Stall (PCIe)", - "L2-Fabric Write Stall (Infinity Fabric™)", - "L2-Fabric Write Stall (HBM)", - "L2-Fabric Write Starve", -] +import config class LogLevel(str, Enum): - """Log levels for consistent logging.""" - INFO = "info" WARNING = "warning" ERROR = "error" - SUCCESS = "success" # Maintained for UI compatibility + SUCCESS = "success" class Logger: - """Centralized logging handler for the application.""" - def __init__(self, output_area=None): - """ - Initialize the logger. - """ self.output_area = output_area self._setup_logger() def _setup_logger(self): - """ - Setup the Python logger with proper formatting. - """ self.logger = logging.getLogger("app") self.logger.setLevel(logging.INFO) @@ -94,15 +34,9 @@ class Logger: self.logger.addHandler(handler) def set_output_area(self, output_area): - """ - Set or update the output area for displaying logs. - """ self.output_area = output_area def log(self, message, level=LogLevel.INFO, update_ui=True): - """ - Log a message with the specified level. - """ level_map = { LogLevel.INFO: logging.INFO, LogLevel.SUCCESS: logging.INFO, @@ -145,151 +79,28 @@ class Logger: self.log(message, LogLevel.ERROR, update_ui) -def split_table_line(line): - """ - Splits a table row line into a list of cell strings (trimmed). For example: +def get_top_kernels_and_dispatch_ids(runs): + if not runs: + return None - │ │ Kernel_Name │ Count │ ... - """ + base_run = next(iter(runs.values())) + if not hasattr(base_run, "dfs"): + return None - cells = line.split("│") - if cells and cells[0] == "": - cells = cells[1:] - if cells and cells[-1] == "": - cells = cells[:-1] - return [cell.strip() for cell in cells] + top_kernel_df = base_run.dfs.get(1) + dispatch_id_df = base_run.dfs.get(2) + + if top_kernel_df is None or dispatch_id_df is None: + return None + + merged_df = pd.merge( + top_kernel_df, dispatch_id_df, on="Kernel_Name", how="outer" + ).sort_values("Pct", ascending=False) + + return merged_df.to_dict("records") -def parse_ascii_table(table_lines): - """ - Given a list of lines belonging to one ASCII table (including border rows), - return a tuple (header, data_rows) where header is a list of column names and - data_rows is a list of rows (each a list of cell strings). - - Skips border/separator lines and also checks for continuation - rows (which have an empty first cell). Continuation rows get merged into the previous row. - """ - - header = None - data_rows = [] - - for line in table_lines: - if re.match(r"^[╒╞╘├└─]+", line): - continue - if "│" not in line: - continue - - cells = split_table_line(line) - - if header is None: - header = cells - continue - - if cells and cells[0] == "": - if data_rows: # There should be at least one row already. - for i, cell in enumerate(cells): - if cell: - data_rows[-1][i] += " " + cell - else: - continue - else: - data_rows.append(cells) - return header, data_rows - - -def parse_file(filename): - """ - Returns nested structure: - { - "0. Top Stats": { - "0.1 Top Kernels": {header: [...], data: [...]}, - "0.2 Dispatch List": {header: [...], data: [...]} - }, - "1. System Info": { - "1.1 System Information": {header: [...], data: [...]} - }, - ... - } - """ - with open(filename, "r", encoding="utf-8") as f: - lines = f.readlines() - - sections = {} - current_section = None - current_subsection = None - table_lines = [] - in_table = False - - for line in lines: - line = line.rstrip("\n") - - # Skip separator lines - if line.startswith( - "--------------------------------------------------------------------------------" - ): - continue - - # Check for section header (e.g., "0. Top Stats") - section_match = re.match(r"^\s*(\d+\. .+)$", line) - if section_match: - current_section = section_match.group(1).strip() - sections[current_section] = {} - continue - - # Check for subsection header (e.g., "0.1 Top Kernels") - # FIXME: 1. System Info is an exception, no subsection - subsection_match = re.match(r"^\s*(\d+\.\d+ .+)$", line) - if subsection_match: - current_subsection = subsection_match.group(1).strip() - if current_section is None: - current_section = "Uncategorized" - sections[current_section] = {} - continue - - # Table parsing logic - if line.startswith("╒"): - in_table = True - table_lines = [line] - continue - - if in_table: - table_lines.append(line) - if line.startswith("╘"): - if current_section and current_subsection: - header, data = parse_ascii_table(table_lines) - sections[current_section][current_subsection] = { - "header": header, - "data": data, - } - in_table = False - table_lines = [] - - return sections - - -def get_table_dfs(): - filename = str(Path(os.getcwd()).joinpath("analyze_output.csv")) - sections_info = parse_file(filename) - - # Convert to DataFrames while maintaining nested structure - section_dfs = {} - for section_name, subsections in sections_info.items(): - section_dfs[section_name] = {} - for subsection_name, table_data in subsections.items(): - if table_data and table_data["data"]: - try: - df = pd.DataFrame(table_data["data"], columns=table_data["header"]) - section_dfs[section_name][subsection_name] = df - except Exception as e: - print(f"Error creating DataFrame for {subsection_name}: {e}") - continue - - return section_dfs - - -def process_panels_to_dataframes( - args, runs, archConfigs, profiling_config, roof_plot=None -): +def process_panels_to_dataframes(args, kernel_df, archConfigs, roof_plot=None): """ Process panel data into pandas DataFrames. Returns a nested dictionary structure with DataFrames and tui_style information. @@ -305,318 +116,87 @@ def process_panels_to_dataframes( } """ - comparable_columns = build_comparable_columns(args.time_unit) - filter_panel_ids = profiling_config.get("filter_blocks", []) - if isinstance(filter_panel_ids, dict): - # For backward compatibility - filter_panel_ids = [ - name for name, type in filter_panel_ids.items() if type == "metric_id" - ] - filter_panel_ids = [ - int(convert_metric_id_to_panel_info(metric_id)[0]) - for metric_id in filter_panel_ids - ] + # TODO: add individual kernel roofline logic + # TODO: implement args logic: + # args.filter_metrics + # args.cols + # args.max_stat_num + # args.df_file_dir - # Initialize the result structure result_structure = defaultdict(dict) + decimal_precision = getattr(args, "decimal", 2) if args else 2 + for panel_id, panel in archConfigs.panel_configs.items(): - # Skip panels that don't support baseline comparison - if panel_id in HIDDEN_SECTIONS: + if panel_id in config.HIDDEN_SECTIONS: continue - # Get section name (e.g., "0. Top Stats") section_name = f"{panel_id // 100}. {panel['title']}" for data_source in panel["data source"]: for type, table_config in data_source.items(): - # Check for filtering conditions - if ( - not args.filter_metrics - and filter_panel_ids - and table_config["id"] not in filter_panel_ids - and panel_id not in filter_panel_ids - and panel_id > 100 - ): - table_id_str = ( - str(table_config["id"] // 100) - + "." - + str(table_config["id"] % 100) - ) + table_id = table_config["id"] + + if table_id not in kernel_df: continue - # Process the data - base_run, base_data = next(iter(runs.items())) - base_df = base_data.dfs[table_config["id"]] + base_df = kernel_df[table_id] + + if base_df is None or base_df.empty: + continue df = pd.DataFrame(index=base_df.index) - # Process columns - for header in list(base_df.keys()): - if should_process_column(header, args, type): - if header in HIDDEN_COLUMNS: - pass - elif header not in comparable_columns: - df = process_non_comparable_column( - df, header, base_df, type, table_config, runs - ) - else: - df = process_comparable_column( - df, - header, - base_df, - table_config, - runs, - base_run, - type, - args, - HIDDEN_COLUMNS, - ) + for header in list(base_df.columns): + if header in config.HIDDEN_COLUMNS_TUI: + continue + else: + df[header] = base_df[header] - if not df.empty: - # Check for empty columns - is_empty_columns_exist = check_empty_columns(df) + df = apply_rounding_logic(df, decimal_precision) - if not is_empty_columns_exist: - # Get subsection name - table_id_str = ( - str(table_config["id"] // 100) - + "." - + str(table_config["id"] % 100) - ) - subsection_name = table_id_str - if "title" in table_config and table_config["title"]: - subsection_name += " " + table_config["title"] + subsection_name = ( + str(table_config["id"] // 100) + "." + str(table_config["id"] % 100) + ) + if "title" in table_config and table_config["title"]: + subsection_name += " " + table_config["title"] - # Handle special cases for top stats - if type == "raw_csv_table" and ( - table_config["source"] == "pmc_kernel_top.csv" - or table_config["source"] == "pmc_dispatch_info.csv" - ): - df = df.head(args.max_stat_num) + result_structure[section_name][subsection_name] = { + "df": df, + "tui_style": None, + } - # Check for transpose requirement - transpose = ( - type != "raw_csv_table" - and "columnwise" in table_config - and table_config.get("columnwise") == True - ) + if type == "metric_table" and "tui_style" in table_config: + result_structure[section_name][subsection_name]["tui_style"] = ( + table_config["tui_style"] + ) - if transpose: - df = df.T - - # Store the DataFrame with tui_style as separate keys - result_structure[section_name][subsection_name] = { - "df": df, - "tui_style": None, - } - - # Set tui_style if available - if type == "metric_table" and "tui_style" in table_config: - result_structure[section_name][subsection_name][ - "tui_style" - ] = table_config["tui_style"] - - # Save to CSV if requested - if args.df_file_dir: - save_dataframe_to_csv(df, table_id_str, table_config, args) - result_structure["4. Roofline"] = roof_plot return dict(result_structure) -def should_process_column(header, args, type): - """Check if a column should be processed based on arguments.""" - return ( - (not args.cols) - or ( - args.cols and header in args.cols - ) # Assuming args.cols is now a list of column names - or (type == "raw_csv_table") - ) +def apply_rounding_logic(df, decimal_precision): + df_copy = df.copy() + for column in df_copy.columns: + if column in ["Metric", "Tips", "coll_level", "Unit", "Kernel_Name", "Info"]: + continue -def process_non_comparable_column(df, header, base_df, type, table_config, runs): - """Process columns that are not comparable across runs.""" - if ( - type == "raw_csv_table" - and ( - table_config["source"] == "pmc_kernel_top.csv" - or table_config["source"] == "pmc_dispatch_info.csv" - ) - and header == "Kernel_Name" - ): - # Adjust kernel name width based on source - if table_config["source"] == "pmc_kernel_top.csv": - adjusted_name = base_df["Kernel_Name"].apply( - lambda x: string_multiple_lines(x, 40, 3) - ) + if df_copy[column].dtype in ["float64", "float32", "int64", "int32"]: + df_copy[column] = df_copy[column].round(decimal_precision) else: - adjusted_name = base_df["Kernel_Name"].apply( - lambda x: string_multiple_lines(x, 80, 4) - ) - df = pd.concat([df, adjusted_name], axis=1) - elif type == "raw_csv_table" and header == "Info": - for run, data in runs.items(): - cur_df = data.dfs[table_config["id"]] - df = pd.concat([df, cur_df[header]], axis=1) - else: - df = pd.concat([df, base_df[header]], axis=1) + try: + numeric_series = pd.to_numeric(df_copy[column], errors="coerce") + if not numeric_series.isna().all(): + rounded_series = numeric_series.round(decimal_precision) - return df + if df_copy[column].dtype == "object": + df_copy[column] = df_copy[column].combine( + rounded_series, + lambda orig, rounded: rounded if pd.notna(rounded) else orig, + ) + else: + df_copy[column] = rounded_series + except (ValueError, TypeError): + continue - -def process_comparable_column( - df, header, base_df, table_config, runs, base_run, type, args, hidden_columns -): - """Process columns that can be compared across runs.""" - for run, data in runs.items(): - cur_df = data.dfs[table_config["id"]] - if (type == "raw_csv_table") or ( - type == "metric_table" and (header not in hidden_columns) - ): - if run != base_run: - # Calculate percentage over the baseline - base_values = [float(x) if x != "" else float(0) for x in base_df[header]] - cur_values = [float(x) if x != "" else float(0) for x in cur_df[header]] - - base_df[header] = base_values - cur_df[header] = cur_values - - t_df = pd.concat( - [base_df[header], cur_df[header]], - axis=1, - ) - absolute_diff = (t_df.iloc[:, 1] - t_df.iloc[:, 0]).round(args.decimal) - t_df = absolute_diff / t_df.iloc[:, 0].replace(0, 1) - - t_df_pretty = t_df.astype(float).mul(100).round(args.decimal) - - # Show value + percentage - t_df = ( - cur_df[header].astype(float).round(args.decimal).map(str).astype(str) - + " (" - + t_df_pretty.map(str) - + "%)" - ) - df = pd.concat([df, t_df], axis=1) - - # Check for threshold violations - if ( - header in ["Value", "Count", "Avg"] - and t_df_pretty.abs().gt(args.report_diff).any() - ): - df["Abs Diff"] = absolute_diff - if args.report_diff: - violation_idx = t_df_pretty.index[ - t_df_pretty.abs() > args.report_diff - ] - else: - cur_df_copy = copy.deepcopy(cur_df) - cur_df_copy[header] = [ - (round(float(x), args.decimal) if x != "" else x) - for x in base_df[header] - ] - df = pd.concat([df, cur_df_copy[header]], axis=1) - - return df - - -def check_empty_columns(df): - """Check if any column in the DataFrame is empty.""" - return any( - [ - df.columns[col_idx] - for col_idx in range(len(df.columns)) - if df.replace("", None).iloc[:, col_idx].isnull().all() - ] - ) - - -def save_dataframe_to_csv(df, table_id_str, table_config, args): - """Save DataFrame to CSV file if directory is specified.""" - p = Path(args.df_file_dir) - if not p.exists(): - p.mkdir() - if p.is_dir(): - filename = table_id_str - if "title" in table_config and table_config["title"]: - filename += "_" + table_config["title"] - df.to_csv( - p.joinpath(filename.replace(" ", "_") + ".csv"), - index=False, - ) - - -def string_multiple_lines(source, width, max_rows): - """ - Adjust string with multiple lines by inserting '\n' - """ - idx = 0 - lines = [] - while idx < len(source) and len(lines) < max_rows: - lines.append(source[idx : idx + width]) - idx += width - - if idx < len(source): - last = lines[-1] - lines[-1] = last[0:-3] + "..." - return "\n".join(lines) - - -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 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}") - - -def build_comparable_columns(time_unit): - """ - Build comparable columns/headers for display - """ - comparable_columns = supported_field - top_stat_base = ["Count", "Sum", "Mean", "Median", "Standard Deviation"] - - for h in top_stat_base: - comparable_columns.append(h + "(" + time_unit + ")") - - return comparable_columns + return df_copy diff --git a/src/rocprof_compute_tui/views/kernel_view.py b/src/rocprof_compute_tui/views/kernel_view.py new file mode 100644 index 0000000000..61e3957b30 --- /dev/null +++ b/src/rocprof_compute_tui/views/kernel_view.py @@ -0,0 +1,203 @@ +""" +Panel Widget Modules +------------------- +Contains the panel widgets used in the main layout. +""" + +from typing import Optional + +from textual import on +from textual.containers import Container, VerticalScroll +from textual.widgets import Label, RadioButton, RadioSet + +from config import rocprof_compute_home +from rocprof_compute_tui.widgets.collapsibles import build_all_sections + + +class KernelView(Container): + """Center panel with analysis results split into two scrollable sections.""" + + DEFAULT_CSS = """ + KernelView { + layout: vertical; + } + + #top-container { + height: 1fr; + border: none; + margin-top: 1; + } + + #bottom-container { + height: 4fr; + border: none; + margin-top: 2; + } + + .kernel-table-header { + background: $primary; + color: $text; + text-style: bold; + padding: 0 1; + offset: 5 0; + margin-top: 1; + } + + .kernel-row { + padding: 0 1; + border-bottom: solid $border; + } + + RadioSet { + border: solid $border; + } + """ + + def __init__(self, config_path: Optional[str] = None): + super().__init__(id="kernel-view") + self.status_label = None + self.dfs = {} + self.top_kernel = [] + + if rocprof_compute_home: + config_path = ( + rocprof_compute_home + / "rocprof_compute_tui" + / "utils" + / "kernel_view_config.yaml" + ) + self.config_path = config_path + + self.keys = None + self.current_selection = None + + def compose(self): + """ + Compose the split panel layout with two scrollable containers. + """ + with VerticalScroll(id="top-container"): + yield Label( + "Open a workload directory to run analysis and view individual kernel analysis results.", + classes="placeholder", + ) + + with VerticalScroll(id="bottom-container"): + # empty on init + pass + + def update_results(self, per_kernel_dfs, top_kernels) -> None: + self.dfs = per_kernel_dfs + self.top_kernel = top_kernels + + top_container = self.query_one("#top-container", VerticalScroll) + top_container.remove_children() + + if self.top_kernel: + try: + header = self.build_header() + top_container.mount(header) + selector = self.build_selector() + top_container.mount(selector) + except Exception as e: + top_container.mount( + Label(f"Error displaying kernel list: {str(e)}", classes="error") + ) + else: + top_container.mount(Label("No kernels available", classes="placeholder")) + + self.current_selection = self.top_kernel[0]["Kernel_Name"] + self._update_bottom_content() + + def update_view(self, message: str, log_level: str) -> None: + """ + Update the view with a status message. + """ + if self.status_label is None: + self.status_label = Label(f"{message}", classes=log_level) + self.mount(self.status_label) + else: + self.status_label.update(f"{message}") + self.status_label.set_classes(log_level) + + def reload_config(self, config_path: str = None) -> None: + if config_path: + self.config_path = config_path + + if self.dfs and self.top_kernel: + self.update_results() + + def build_header(self): + all_keys = set() + + for kernel in self.top_kernel: + all_keys.update(kernel.keys()) + + self.keys = sorted(all_keys) + + if "Kernel_Name" in self.keys: + self.keys.remove("Kernel_Name") + self.keys.insert(0, "Kernel_Name") + + header_text = " | ".join(f"{key:25}" for key in self.keys) + header_label = Label(header_text, classes="kernel-table-header") + + return header_label + + def build_selector(self): + radio_buttons = [] + + for i, kernel in enumerate(self.top_kernel): + row_data = [] + for key in self.keys: + value = str(kernel.get(key, "N/A")) + if len(value) > 18: + value = value[:15] + "..." + row_data.append(f"{value:25}") + + row_text = " | ".join(row_data) + radio_button = RadioButton(row_text, id=f"kernel-{i}") + radio_button.kernel_data = kernel + radio_buttons.append(radio_button) + + selector = RadioSet(*radio_buttons) + + return selector + + @on(RadioSet.Changed) + def on_radio_changed(self, event: RadioSet.Changed) -> None: + if event.pressed: + kernel_data = getattr(event.pressed, "kernel_data", None) + if kernel_data and "Kernel_Name" in kernel_data: + selected_kernel = kernel_data["Kernel_Name"] + self.current_selection = selected_kernel + self._update_bottom_content() + + def _update_bottom_content(self): + bottom_container = self.query_one("#bottom-container", VerticalScroll) + bottom_container.remove_children() + + bottom_container.mount( + Label(f"Toggle kernel selection to view detailed analysis.") + ) + + if self.current_selection and self.current_selection in self.dfs: + bottom_container.mount( + Label(f"Current kernel selection: {self.current_selection}") + ) + filtered_dfs = self.dfs[self.current_selection] + + try: + sections = build_all_sections(filtered_dfs, self.config_path) + for section in sections: + bottom_container.mount(section) + except Exception as e: + bottom_container.mount( + Label(f"Error displaying results: {str(e)}", classes="error") + ) + else: + bottom_container.mount( + Label( + f"No data available for kernel: {self.current_selection}", + classes="error", + ) + ) diff --git a/src/rocprof_compute_tui/views/main_view.py b/src/rocprof_compute_tui/views/main_view.py index ba8ce495a7..aa33167e46 100644 --- a/src/rocprof_compute_tui/views/main_view.py +++ b/src/rocprof_compute_tui/views/main_view.py @@ -50,10 +50,10 @@ class MainView(Horizontal): """Main view layout for the application.""" selected_path = reactive(None) - dfs = reactive({}) + per_kernel_dfs = reactive({}) + top_kernels = reactive([]) def __init__(self): - """Initialize the main view.""" super().__init__(id="main-container") self.start_path = ( # NOTE: is cwd the best choice? @@ -70,7 +70,6 @@ class MainView(Horizontal): pass def compose(self) -> ComposeResult: - """Compose the main view layout.""" self.logger.info("Composing main view layout", update_ui=False) yield MenuBar() @@ -80,7 +79,6 @@ class MainView(Horizontal): # Center Panel - Analysis results display center_panel = CenterPanel() yield center_panel - self.center = center_panel # Bottom Panel - Output, terminal, and metric description @@ -91,7 +89,6 @@ class MainView(Horizontal): self.metric_description = tabs.description_area self.output = tabs.output_area - # Now set the output area for the logger self.logger.set_output_area(self.output) self.logger.info("Main view layout composed") @@ -107,8 +104,9 @@ class MainView(Horizontal): try: row_data = table.get_row_at(row_idx) - content = f"Selected Row {row_idx}:\n" - content += "\n".join(f"{val}" for val in row_data) + content = f"Selected Metric ID: {row_data[0]}\n" + content += f"Selected Metric: {row_data[1]}\n" + # content += f"Metric Description:\n\t{row_data[-1]}" self.metric_description.text = content self.logger.info(f"Row {row_idx} data displayed in metric_description") @@ -122,7 +120,8 @@ class MainView(Horizontal): @work(thread=True) def run_analysis(self) -> None: - self.dfs = {} + self.per_kernel_dfs = {} + self.top_kernels = [] if not self.selected_path: error_msg = "No directory selected for analysis" @@ -173,7 +172,6 @@ class MainView(Horizontal): self.logger.info( f"Step 3: sys_info_df shape = {sys_info_df.shape if hasattr(sys_info_df, 'shape') else 'No shape attribute'}" ) - self.logger.info(f"Step 3: sys_info_df = {sys_info_df}") except Exception as e: self.logger.error(f"Step 3 failed - Error loading sys_info: {str(e)}") @@ -196,7 +194,6 @@ class MainView(Horizontal): raise TypeError(f"Unexpected type for sys_info: {type(sys_info_df)}") self.logger.info(f"Step 4: sys_info converted = {sys_info}") - self.logger.info(f"Step 4: sys_info type = {type(sys_info)}") except Exception as e: self.logger.error(f"Step 4 failed - Error converting sys_info: {str(e)}") @@ -231,18 +228,19 @@ class MainView(Horizontal): # Step 8: Run analysis try: self.logger.info("Step 8: Running analysis") - self.dfs = analyzer.run_analysis() - if not self.dfs: - warning_msg = "Step 8: Analysis completed but no data was returned" + self.per_kernel_dfs = analyzer.run_kernel_analysis() + self.top_kernels = analyzer.run_top_kernel() + + # TODO: add per kernel Roofline support when available + + if not self.per_kernel_dfs or not self.top_kernels: + warning_msg = "Step 8: Per Kernel Analysis completed but not all data was returned" self._update_view(warning_msg, LogLevel.WARNING) self.logger.warning(warning_msg) else: self.app.call_from_thread(self.refresh_results) - self.logger.info("Step 8: Analysis completed successfully") - if self.dfs.get("4. Roofline"): - self.logger.info("Step 8: Roofline data available") - else: - self.logger.info("Step 8: Roofline data not available") + self.logger.info("Step 8: Kernel Analysis completed successfully") + # self.logger.info(f"{self.per_kernel_dfs}") except Exception as e: self.logger.error(f"Step 8 failed - Error running analysis: {str(e)}") raise @@ -257,17 +255,15 @@ class MainView(Horizontal): def _update_view(self, message: str, log_level: LogLevel) -> None: try: - # Use call_from_thread to safely update UI from background thread self.app.call_from_thread(self._safe_update_view, message, log_level) except Exception as e: - # Capture errors that might occur when scheduling the UI update self.logger.error(f"View update scheduling error: {str(e)}") def _safe_update_view(self, message: str, log_level: LogLevel) -> None: try: - analyze_view = self.query_one("#analyze-view") - if analyze_view: - analyze_view.update_view(message, log_level) + kernel_view = self.query_one("#kernel-view") + if kernel_view: + kernel_view.update_view(message, log_level) else: self.logger.warning("Analysis view not found when updating log") except Exception as e: @@ -275,24 +271,29 @@ class MainView(Horizontal): def refresh_results(self) -> None: try: - self.logger.info("Refreshing analysis results") - analyze_view = self.query_one("#analyze-view") - if not analyze_view: - self.logger.error("Analysis view not found") + self.logger.info("Refreshing kernel results") + kernel_view = self.query_one("#kernel-view") + if not kernel_view: + self.logger.error("Kernel view not found") return - if not hasattr(self, "dfs") or self.dfs is None: - self.logger.error("No analysis data available to display") + if ( + not hasattr(self, "per_kernel_dfs") + or self.per_kernel_dfs is None + or not hasattr(self, "top_kernels") + or self.top_kernels is None + ): + self.logger.error("No kernel analysis data available to display") return - analyze_view.update_results(self.dfs) + kernel_view.update_results(self.per_kernel_dfs, self.top_kernels) self.logger.success(f"Results displayed successfully.") except Exception as e: self.logger.error(f"Error refreshing results: {str(e)}") def refresh_view(self) -> None: self.logger.info("Refreshing view...") - if self.dfs: + if self.top_kernels: self.refresh_results() else: self.logger.warning("No data available for refresh") diff --git a/src/rocprof_compute_tui/widgets/center_panel/analyze_view.py b/src/rocprof_compute_tui/widgets/center_panel/analyze_view.py deleted file mode 100644 index c314c73c60..0000000000 --- a/src/rocprof_compute_tui/widgets/center_panel/analyze_view.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Panel Widget Modules -------------------- -Contains the panel widgets used in the main layout. -""" - -from importlib import resources -from typing import Any, Dict, Optional - -from textual.containers import ScrollableContainer -from textual.widgets import Label - -from rocprof_compute_tui.widgets.collapsibles import build_all_sections - - -class AnalyzeView(ScrollableContainer): - """Center panel with analysis results.""" - - def __init__(self, config_path: Optional[str] = None): - super().__init__(id="analyze-view") - self.dfs = {} - - if config_path is None: - config_path = ( - resources.files("rocprof_compute_tui.utils") / "analyze_config.yaml" - ) - - self.config_path = str(config_path) - - def compose(self): - """ - Compose the initial center panel state. - """ - yield Label( - "Open a workload directory to run analysis and view results", - classes="placeholder", - ) - - def update_results(self, dfs: Dict[str, Any]) -> None: - """ - Update the center panel with analysis results. - """ - self.dfs = dfs - self.remove_children() - - try: - sections = build_all_sections(self.dfs, self.config_path) - - # Mount all sections - for section in sections: - self.mount(section) - - except Exception as e: - self.mount(Label(f"Error displaying results: {str(e)}", classes="error")) - - def update_view(self, message: str, log_level: str) -> None: - """ - Update the view with a status message. - """ - self.remove_children() - try: - self.mount(Label(f"{message}", classes=log_level)) - except Exception as e: - self.mount(Label(f"Error displaying results: {str(e)}", classes="error")) - - def reload_config(self, config_path: str = None) -> None: - """ - Reload the configuration and update the view. - """ - if config_path: - self.config_path = config_path - - if self.dfs: - self.update_results(self.dfs) diff --git a/src/rocprof_compute_tui/widgets/center_panel/center_area.py b/src/rocprof_compute_tui/widgets/center_panel/center_area.py index b7d588f0ed..59d075ebe3 100644 --- a/src/rocprof_compute_tui/widgets/center_panel/center_area.py +++ b/src/rocprof_compute_tui/widgets/center_panel/center_area.py @@ -29,9 +29,9 @@ Contains the panel widgets used in the main layout. """ from textual.containers import Vertical -from textual.widgets import Label, TabPane +from textual.widgets import TabPane -from rocprof_compute_tui.widgets.center_panel.analyze_view import AnalyzeView +from rocprof_compute_tui.views.kernel_view import KernelView from rocprof_compute_tui.widgets.tabbed_content import TabsTabbedContent @@ -48,15 +48,12 @@ class CenterPanel(Vertical): super().__init__() self.default_tab = "center-analyze" - self.analyze_view = AnalyzeView() + self.kernel_view = KernelView() def compose(self): - with TabsTabbedContent(initial="tab-analyze"): - with TabPane("Basic View", id="tab-analyze"): - yield self.analyze_view - # TODO: - # with TabPane("placeholder (🚧)", id="tab-1"): - # yield Label("🚧 Under Construction") + with TabsTabbedContent(initial="tab-kernel"): + with TabPane("Basic View", id="tab-kernel"): + yield self.kernel_view def on_mount(self) -> None: self.add_class("section") diff --git a/src/rocprof_compute_tui/widgets/charts.py b/src/rocprof_compute_tui/widgets/charts.py index 3cb710f2e2..b80352abbf 100644 --- a/src/rocprof_compute_tui/widgets/charts.py +++ b/src/rocprof_compute_tui/widgets/charts.py @@ -68,7 +68,10 @@ def simple_bar(df, title=None): w *= 100 plt.simple_bar(list(metric_dict.keys()), list(metric_dict.values()), width=w) # plt.show() - return "\n" + plt.build() + "\n" + plot_content = plt.build() + if not plot_content or plot_content.strip() == "": + return None + return "\n" + plot_content + "\n" def simple_multiple_bar(df, title=None): @@ -100,10 +103,13 @@ def simple_multiple_bar(df, title=None): h *= 300 plt.plot_size(height=h) - plt.multiple_bar(labels, data, color=["blue", "blue+", 68, 63]) + plt.multiple_bar(labels, data) # plt.show() - return "\n" + plt.build() + "\n" + plot_content = plt.build() + if not plot_content or plot_content.strip() == "": + return None + return "\n" + plot_content + "\n" def simple_box(df, orientation="v", title=None): @@ -173,7 +179,10 @@ def simple_box(df, orientation="v", title=None): plt.theme("pro") # plt.show() - return "\n" + plt.build() + "\n" + plot_content = plt.build() + if not plot_content or plot_content.strip() == "": + return None + return "\n" + plot_content + "\n" def px_simple_bar(df, title: str = None, id=None, style: dict = None, orientation="h"): @@ -284,18 +293,8 @@ class RooflinePlot(Static): super().__init__("", classes="roofline", **kwargs) self.df = df - # Disable markup rendering - self._render_markup = False - try: - plot_str = "" - try: - result = self.df["4. Roofline"] - if result: - plot_str = str(result) - except: - plot_str = "No roofline data generated" - + plot_str = str(self.df.get("4. Roofline", "No roofline data generated")) self.update(plot_str) except Exception as e: error_message = f"Roofline plot error: {str(e)}\n{traceback.format_exc()}" @@ -319,41 +318,37 @@ class MemoryChart(Static): """ def __init__(self, df: pd.DataFrame, **kwargs): - """Initialize the memory chart.""" super().__init__("", classes="mem-chart", **kwargs) self.df = df - # Generate the chart content on initialization try: - # Prepare data - metric_dict = ( - self.df[["Metric", "Value"]].set_index("Metric").to_dict()["Value"] - ) + if self.df is None or self.df.empty: + self.update("No chart data generated") + return + + if not {"Metric", "Value"}.issubset(self.df.columns): + self.update("Error: Missing required columns") + return + + metric_dict = dict(zip(self.df["Metric"], self.df["Value"])) - # Capture stdout original_stdout = sys.stdout - string_buffer = StringIO() - sys.stdout = string_buffer - try: - # Generate the chart - result = plot_mem_chart("", "per_kernel", metric_dict) - stdout_output = string_buffer.getvalue() - - if stdout_output: - plot_str = stdout_output - elif result: - plot_str = str(result) - else: - plot_str = "No chart data generated" + with StringIO() as string_buffer: + sys.stdout = string_buffer + result = plot_mem_chart("", "per_kernel", metric_dict) + stdout_output = string_buffer.getvalue() finally: sys.stdout = original_stdout + plot_str = next( + (x for x in [stdout_output, str(result) if result else None] if x), + "No chart data generated", + ) self.update(plot_str) except Exception as e: - error_message = f"Memory chart error: {str(e)}\n{traceback.format_exc()}" - self.update(f"Error: {str(error_message)}") + self.update(f"Memory chart error: {str(e)}") class SimpleBar(Static): @@ -372,7 +367,6 @@ class SimpleBar(Static): """ def __init__(self, df: pd.DataFrame, **kwargs): - """Initialize the simple bar.""" super().__init__("", classes="simple-bar", **kwargs) self.df = df @@ -381,13 +375,8 @@ class SimpleBar(Static): if result: plot_str = str(result) - # Escape markup characters escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]") self.update(escaped_content) - - # Alternative - wrap in [pre] tags for preformatted text - # self.update(f"[pre]{plot_str}[/pre]") - else: self.update("No simple bar data generated") @@ -398,7 +387,6 @@ class SimpleBar(Static): class SimpleBox(Static): - """Simple Box visualization widget.""" DEFAULT_CSS = """ SimpleBox { @@ -413,7 +401,6 @@ class SimpleBox(Static): """ def __init__(self, df: pd.DataFrame, **kwargs): - """Initialize the simple box.""" super().__init__("", classes="simple-box", **kwargs) self.df = df @@ -422,7 +409,6 @@ class SimpleBox(Static): if result: plot_str = str(result) - # Escape markup characters escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]") self.update(escaped_content) else: @@ -450,7 +436,6 @@ class SimpleMultiBar(Static): """ def __init__(self, df: pd.DataFrame, **kwargs): - """Initialize the simple multiple bar.""" super().__init__("", classes="simple-multi-bar", **kwargs) self.df = df @@ -459,7 +444,6 @@ class SimpleMultiBar(Static): if result: plot_str = str(result) - # Escape markup characters escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]") self.update(escaped_content) else: diff --git a/src/rocprof_compute_tui/widgets/collapsibles.py b/src/rocprof_compute_tui/widgets/collapsibles.py index 1970354391..3ffb316273 100644 --- a/src/rocprof_compute_tui/widgets/collapsibles.py +++ b/src/rocprof_compute_tui/widgets/collapsibles.py @@ -26,7 +26,6 @@ from typing import Any, Dict, List, Optional import pandas as pd import yaml -from textual.containers import VerticalScroll from textual.widgets import Collapsible, DataTable, Label from rocprof_compute_tui.widgets.charts import ( @@ -41,12 +40,9 @@ from rocprof_compute_tui.widgets.charts import ( def create_table(df: pd.DataFrame) -> DataTable: table = DataTable(zebra_stripes=True) - # Clean the DataFrame - remove NaN and empty cells df = df.reset_index() - df = df.dropna(how="any") df = df[~df.apply(lambda row: row.astype(str).str.strip().eq("").any(), axis=1)] - # Add columns and rows str_columns = [str(col) for col in df.columns] table.add_columns(*str_columns) table.add_rows([tuple(str(x) for x in row) for row in df.itertuples(index=False)]) @@ -59,7 +55,9 @@ def load_config(config_path) -> Dict[str, Any]: with open(config_path, "r") as file: return yaml.safe_load(file) except FileNotFoundError: - raise FileNotFoundError(f"Configuration file {config_path} not found") + raise FileNotFoundError( + f"Configuration file {config_path} not found, \nplease populate the analysis_config.yaml file." + ) except yaml.YAMLError as e: raise ValueError(f"Error parsing YAML configuration: {e}") @@ -167,7 +165,7 @@ def build_subsection( return collapsible -def build_dynamic_kernel_sections( +def build_kernel_sections( dfs: Dict[str, Any], skip_sections: List[str] ) -> List[Collapsible]: children = [] @@ -198,9 +196,10 @@ def build_dynamic_kernel_sections( return None try: - df = data["df"] + if data["df"] is None or data["df"].empty: + return None tui_style = data.get("tui_style") - widget = create_widget_from_data(df, tui_style) + widget = create_widget_from_data(data["df"], tui_style) if widget is None: add_warning(f"Widget creation returned None for '{subsection_name}'") @@ -277,7 +276,7 @@ def build_section_from_config( # Handle dynamic sections (like kernel sections) elif section_config.get("dynamic_sections", False): skip_sections = section_config.get("skip_sections", []) - children = build_dynamic_kernel_sections(dfs, skip_sections) + children = build_kernel_sections(dfs, skip_sections) # Handle regular sections with subsections elif "subsections" in section_config: @@ -290,7 +289,6 @@ def build_section_from_config( except Exception as e: error_msg = f"{subsection_config.get('title', 'Unknown')} error: {str(e)}" children.append(Label(error_msg, classes="warning")) - else: children = [Label("No configuration provided for this section")] diff --git a/src/rocprof_compute_tui/widgets/directory_tree.py b/src/rocprof_compute_tui/widgets/directory_tree.py deleted file mode 100644 index e7dc44bc6a..0000000000 --- a/src/rocprof_compute_tui/widgets/directory_tree.py +++ /dev/null @@ -1,39 +0,0 @@ -##############################################################################bl -# MIT License -# -# Copyright (c) 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. -##############################################################################el - -""" -Specialized Widget Modules -------------------------- -Contains custom widget implementations for the application. -""" - -from textual.widgets import DirectoryTree - - -class FolderOnlyDirectory(DirectoryTree): - """Directory tree that only shows folders.""" - - def filter_paths(self, paths): - """Filter to only show directories.""" - return [path for path in paths if path.is_dir()] diff --git a/src/utils/mem_chart.py b/src/utils/mem_chart.py index 6d3211d0b5..8679210759 100644 --- a/src/utils/mem_chart.py +++ b/src/utils/mem_chart.py @@ -106,8 +106,8 @@ def format_text( ) key_str = ( "{key:{key_format}}".format(key=key, key_format=key_format) - if key is not None - else None + if key and isinstance(key, (int, float)) + else str(key) if key else None ) unit_string = post_description_with_space if not "N/A" in value_str else "" @@ -1013,8 +1013,8 @@ class MemChart: block_instr_buff.y_max = self.y_max - 5.0 block_instr_buff.y_min = block_instr_buff.y_max - 24.0 - block_instr_buff.wave_occupancy = metric_dict["Wavefront Occupancy"] - block_instr_buff.wave_life = metric_dict["Wave Life"] + block_instr_buff.wave_occupancy = metric_dict.get("Wavefront Occupancy", "n/a") + block_instr_buff.wave_life = metric_dict.get("Wave Life", "n/a") block_instr_buff.draw(canvas) @@ -1037,14 +1037,14 @@ class MemChart: block_instr_disp.y_max = block_instr_buff.y_max block_instr_disp.y_min = block_instr_buff.y_min - block_instr_disp.instrs["SALU"] = metric_dict["SALU"] - block_instr_disp.instrs["SMEM"] = metric_dict["SMEM"] - block_instr_disp.instrs["VALU"] = metric_dict["VALU"] - block_instr_disp.instrs["MFMA"] = metric_dict["MFMA"] - block_instr_disp.instrs["VMEM"] = metric_dict["VMEM"] - block_instr_disp.instrs["LDS"] = metric_dict["LDS"] - block_instr_disp.instrs["GWS"] = metric_dict["GWS"] - block_instr_disp.instrs["BRANCH"] = metric_dict["BR"] + block_instr_disp.instrs["SALU"] = metric_dict.get("SALU", "n/a") + block_instr_disp.instrs["SMEM"] = metric_dict.get("SMEM", "n/a") + block_instr_disp.instrs["VALU"] = metric_dict.get("VALU", "n/a") + block_instr_disp.instrs["MFMA"] = metric_dict.get("MFMA", "n/a") + block_instr_disp.instrs["VMEM"] = metric_dict.get("VMEM", "n/a") + block_instr_disp.instrs["LDS"] = metric_dict.get("LDS", "n/a") + block_instr_disp.instrs["GWS"] = metric_dict.get("GWS", "n/a") + block_instr_disp.instrs["BRANCH"] = metric_dict.get("BR", "n/a") block_instr_disp.draw(canvas) @@ -1056,14 +1056,14 @@ class MemChart: block_exec.y_min = block_instr_disp.y_min - 6 block_exec.y_max = block_instr_disp.y_max - block_exec.active_cus = metric_dict["Active CUs"] - block_exec.num_cus = metric_dict["Num CUs"] - block_exec.vgprs = metric_dict["VGPR"] - block_exec.sgprs = metric_dict["SGPR"] - block_exec.lds_alloc = metric_dict["LDS Allocation"] - block_exec.scratch_alloc = metric_dict["Scratch Allocation"] - block_exec.wavefronts = metric_dict["Wavefronts"] - block_exec.workgroups = metric_dict["Workgroups"] + block_exec.active_cus = metric_dict.get("Active CUs", "n/a") + block_exec.num_cus = metric_dict.get("Num CUs", "n/a") + block_exec.vgprs = metric_dict.get("VGPR", "n/a") + block_exec.sgprs = metric_dict.get("SGPR", "n/a") + block_exec.lds_alloc = metric_dict.get("LDS Allocation", "n/a") + block_exec.scratch_alloc = metric_dict.get("Scratch Allocation", "n/a") + block_exec.wavefronts = metric_dict.get("Wavefronts", "n/a") + block_exec.workgroups = metric_dict.get("Workgroups", "n/a") block_exec.draw(canvas) @@ -1075,11 +1075,11 @@ class MemChart: wires_E_GLV.y_min = block_instr_disp.y_min wires_E_GLV.y_max = block_instr_disp.y_max - wires_E_GLV.lds_req = metric_dict["LDS Req"] - 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["sL1D Rd"] + wires_E_GLV.lds_req = metric_dict.get("LDS Req", "n/a") + wires_E_GLV.vl1_rd = metric_dict.get("VL1 Rd", "n/a") + wires_E_GLV.vl1_wr = metric_dict.get("VL1 Wr", "n/a") + wires_E_GLV.vl1_atomic = metric_dict.get("VL1 Atomic", "n/a") + wires_E_GLV.sl1_rd = metric_dict.get("VL1D Rd", "n/a") wires_E_GLV.draw(canvas) @@ -1093,7 +1093,7 @@ class MemChart: y_max=block_instr_buff.y_min, ) - wire_InstrBuff_IL1Cache.il1_fetch = metric_dict["IL1 Fetch"] + wire_InstrBuff_IL1Cache.il1_fetch = metric_dict.get("IL1 Fetch", "n/a") wire_InstrBuff_IL1Cache.draw(canvas) @@ -1118,8 +1118,8 @@ class MemChart: block_lds.y_max = wires_E_GLV.y_max block_lds.y_min = block_lds.y_max - 5 - block_lds.util = metric_dict["LDS Util"] - block_lds.latency = metric_dict["LDS Latency"] + block_lds.util = metric_dict.get("LDS Util", "n/a") + block_lds.latency = metric_dict.get("LDS Latency", "n/a") block_lds.draw(canvas) @@ -1131,10 +1131,10 @@ class MemChart: block_vector_L1.y_max = block_lds.y_min - 3 block_vector_L1.y_min = block_vector_L1.y_max - 9 - block_vector_L1.hit = metric_dict["VL1 Hit"] - block_vector_L1.latency = metric_dict["VL1 Lat"] - block_vector_L1.coales = metric_dict["VL1 Coalesce"] - block_vector_L1.stall = metric_dict["VL1 Stall"] + block_vector_L1.hit = metric_dict.get("VL1 Hit", "n/a") + block_vector_L1.latency = metric_dict.get("VL1 Lat", "n/a") + block_vector_L1.coales = metric_dict.get("VL1 Coalesce", "n/a") + block_vector_L1.stall = metric_dict.get("VL1 Stall", "n/a") block_vector_L1.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["sL1D Hit"] - block_const_L1.latency = metric_dict["sL1D Lat"] + block_const_L1.hit = metric_dict.get("sL1D Hit", "n/a") + block_const_L1.latency = metric_dict.get("sL1D Lat", "n/a") block_const_L1.draw(canvas) @@ -1159,8 +1159,8 @@ class MemChart: block_instr_L1.y_max = block_const_L1.y_min - 3 block_instr_L1.y_min = block_instr_L1.y_max - 5 - block_instr_L1.hit = metric_dict["IL1 Hit"] - block_instr_L1.latency = metric_dict["IL1 Lat"] + block_instr_L1.hit = metric_dict.get("IL1 Hit", "n/a") + block_instr_L1.latency = metric_dict.get("IL1 Lat", "n/a") block_instr_L1.draw(canvas) @@ -1171,13 +1171,13 @@ class MemChart: wires_L1_L2.x_max = wires_L1_L2.x_min + 14 wires_L1_L2.y_min = block_instr_L1.y_min wires_L1_L2.y_max = block_vector_L1.y_max - 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["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.vl1_l2_rd = metric_dict.get("VL1_L2 Rd", "n/a") + wires_L1_L2.vl1_l2_wr = metric_dict.get("VL1_L2 Wr", "n/a") + wires_L1_L2.vl1_l2_atomic = metric_dict.get("VL1_L2 Atomic", "n/a") + wires_L1_L2.sl1_l2_rd = metric_dict.get("VL1D_L2 Rd", "n/a") + wires_L1_L2.sl1_l2_wr = metric_dict.get("VL1D_L2 Wr", "n/a") + wires_L1_L2.sl1_l2_atomic = metric_dict.get("VL1D_L2 Atomic", "n/a") + wires_L1_L2.il1_l2_req = metric_dict.get("IL1_L2 Rd", "n/a") wires_L1_L2.draw(canvas) @@ -1190,12 +1190,12 @@ class MemChart: block_L2.y_min = block_instr_L1.y_min block_L2.y_max = block_lds.y_max - block_L2.hit = metric_dict["L2 Hit"] - block_L2.rd = metric_dict["L2 Rd"] - block_L2.wr = metric_dict["L2 Wr"] - block_L2.atomic = metric_dict["L2 Atomic"] - block_L2.rd_lat = metric_dict["L2 Rd Lat"] - block_L2.wr_lat = metric_dict["L2 Wr Lat"] + block_L2.hit = metric_dict.get("L2 Hit", "n/a") + block_L2.rd = metric_dict.get("L2 Rd", "n/a") + block_L2.wr = metric_dict.get("L2 Wr", "n/a") + block_L2.atomic = metric_dict.get("L2 Atomic", "n/a") + block_L2.rd_lat = metric_dict.get("L2 Rd Lat", "n/a") + block_L2.wr_lat = metric_dict.get("L2 Wr Lat", "n/a") block_L2.draw(canvas) @@ -1209,9 +1209,9 @@ class MemChart: y_max=block_L2.y_max - 10, ) - wires_L2_Fabric.rd = metric_dict["Fabric_L2 Rd"] - wires_L2_Fabric.wr = metric_dict["Fabric_L2 Wr"] - wires_L2_Fabric.atomic = metric_dict["Fabric_L2 Atomic"] + wires_L2_Fabric.rd = metric_dict.get("Fabric_L2 Rd", "n/a") + wires_L2_Fabric.wr = metric_dict.get("Fabric_L2 Wr", "n/a") + wires_L2_Fabric.atomic = metric_dict.get("Fabric_L2 Atomic", "n/a") wires_L2_Fabric.draw(canvas) @@ -1236,9 +1236,9 @@ class MemChart: y_min=block_xgmi_pcie.y_min - 5 - 11, ) - block_fabric.lat["Rd"] = metric_dict["Fabric Rd Lat"] - block_fabric.lat["Wr"] = metric_dict["Fabric Wr Lat"] - block_fabric.lat["Atomic"] = metric_dict["Fabric Atomic Lat"] + block_fabric.lat["Rd"] = metric_dict.get("Fabric Rd Lat", "n/a") + block_fabric.lat["Wr"] = metric_dict.get("Fabric Wr Lat", "n/a") + block_fabric.lat["Atomic"] = metric_dict.get("Fabric Atomic Lat", "n/a") block_fabric.draw(canvas) @@ -1264,8 +1264,8 @@ class MemChart: y_max=block_fabric.y_max - 4, ) - wires_Fabric_HBM.rd = metric_dict["HBM Rd"] - wires_Fabric_HBM.wr = metric_dict["HBM Wr"] + wires_Fabric_HBM.rd = metric_dict.get("HBM Rd", "n/a") + wires_Fabric_HBM.wr = metric_dict.get("HBM Wr", "n/a") wires_Fabric_HBM.draw(canvas) diff --git a/src/utils/parser.py b/src/utils/parser.py index b8bfaa8519..09e8c73deb 100644 --- a/src/utils/parser.py +++ b/src/utils/parser.py @@ -137,14 +137,28 @@ def to_max(*args): def to_avg(a): if str(type(a)) == "": return np.nan - elif np.isnan(a).all(): - return np.nan - elif a.empty: - return np.nan elif isinstance(a, pd.core.series.Series): - return a.mean() + if a.empty: + return np.nan + elif np.isnan(a).all(): + return np.nan + else: + return a.mean() + elif isinstance(a, (np.ndarray, list)): + arr = np.array(a) + if arr.size == 0: + return np.nan + elif np.isnan(arr).all(): + return np.nan + else: + return np.nanmean(arr) + elif isinstance(a, (int, float, np.number)): + if np.isnan(a): + return np.nan + else: + return float(a) else: - raise Exception("to_avg: unsupported type.") + raise Exception(f"to_avg: unsupported type: {type(a)}") def to_median(a): @@ -313,6 +327,7 @@ def build_eval_string(equation, coll_level, config): s = re.sub(r"\'\]\[(\d+)\]", r"[\g<1>]']", s) # use .get() to catch any potential KeyErrors s = re.sub(r"raw_pmc_df\['(.*?)']", r'raw_pmc_df.get("\1")', s) + # print("--- intermediate string: ", s) # apply coll_level if config.get("format_rocprof_output") == "rocpd": # Replace SQ_ACCUM_PREV_HIRES with coll_level_ACCUM then ignore coll_level df @@ -1448,7 +1463,7 @@ def load_kernel_top(workload, dir, args): def load_table_data(workload, dir, is_gui, args, config, skipKernelTop=False): """ - Load data for all "raw_csv_table" - - Load dat for "pc_sampling_table" + - Load data for "pc_sampling_table" - Calculate mertric value for all "metric_table" """ if not skipKernelTop: diff --git a/tests/test_analyze_commands.py b/tests/test_analyze_commands.py index 0dbfd53d52..12e6a8fece 100644 --- a/tests/test_analyze_commands.py +++ b/tests/test_analyze_commands.py @@ -24,7 +24,7 @@ import os import shutil -from unittest.mock import Mock, patch +from unittest.mock import Mock import pandas as pd import pytest diff --git a/tests/test_db_connector.py b/tests/test_db_connector.py index ad48a87a77..5bd657bf2f 100644 --- a/tests/test_db_connector.py +++ b/tests/test_db_connector.py @@ -24,11 +24,7 @@ import logging -import shutil -import sys -import tempfile -from pathlib import Path -from unittest.mock import MagicMock, Mock, call, patch +from unittest.mock import MagicMock, Mock, patch import pandas as pd import pytest