[ROCm/rocprofiler-compute commit: 9ddee8de2e]
Этот коммит содержится в:
xuchen-amd
2025-08-01 11:31:43 -04:00
коммит произвёл GitHub
родитель bf05f442b8
Коммит b6a7d72212
21 изменённых файлов: 520 добавлений и 889 удалений
+3
Просмотреть файл
@@ -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 * 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 * 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 ### Resolved issues
* Fixed not detecting memory clock issue when using amd-smi * 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 * Usage of rocm-smi
* Hardware IP block based filtering has been removed in favor of analysis report block based filtering * 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 ## ROCm Compute Profiler 3.2.1 for ROCm 7.0.0
Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 78 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 141 KiB

+2
Просмотреть файл
@@ -22,6 +22,7 @@
# SOFTWARE. # SOFTWARE.
##############################################################################el ##############################################################################el
import re
from pathlib import Path from pathlib import Path
# NB: Creating a new module to share global vars across modules # NB: Creating a new module to share global vars across modules
@@ -30,6 +31,7 @@ PROJECT_NAME = "rocprofiler-compute"
HIDDEN_COLUMNS = ["coll_level"] HIDDEN_COLUMNS = ["coll_level"]
HIDDEN_COLUMNS_CLI = ["Description", "coll_level"] HIDDEN_COLUMNS_CLI = ["Description", "coll_level"]
HIDDEN_COLUMNS_TUI = ["Description", "coll_level"]
HIDDEN_SECTIONS = [400, 1900, 2000] HIDDEN_SECTIONS = [400, 1900, 2000]
TIME_UNITS = {"s": 10**9, "ms": 10**6, "us": 10**3, "ns": 1} TIME_UNITS = {"s": 10**9, "ms": 10**6, "us": 10**3, "ns": 1}
+35 -57
Просмотреть файл
@@ -23,11 +23,13 @@
##############################################################################el ##############################################################################el
import copy import copy
import sys
from pathlib import Path from pathlib import Path
from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base
from rocprof_compute_tui.utils.tui_utils import process_panels_to_dataframes from rocprof_compute_tui.utils.tui_utils import (
get_top_kernels_and_dispatch_ids,
process_panels_to_dataframes,
)
from utils import file_io, parser, schema from utils import file_io, parser, schema
from utils.kernel_name_shortener import kernel_name_shortener from utils.kernel_name_shortener import kernel_name_shortener
from utils.logger import console_error, demarcate from utils.logger import console_error, demarcate
@@ -38,23 +40,21 @@ class tui_analysis(OmniAnalyze_Base):
super().__init__(args, supported_archs) super().__init__(args, supported_archs)
self.path = str(path) self.path = str(path)
self.arch = None self.arch = None
self.raw_dfs = {}
self.kernel_dfs = {}
# ----------------------- # -----------------------
# Required child methods # Required child methods
# ----------------------- # -----------------------
@demarcate @demarcate
def pre_processing(self): def pre_processing(self):
"""Perform any pre-processing steps prior to analysis."""
# Read profiling config
self._profiling_config = file_io.load_profiling_config(self.path) self._profiling_config = file_io.load_profiling_config(self.path)
# initalize runs
self._runs = self.initalize_runs() self._runs = self.initalize_runs()
if self.get_args().random_port: if self.get_args().random_port:
console_error("--gui flag is required to enable --random-port") console_error("--gui flag is required to enable --random-port")
# create 'mega dataframe'
self._runs[self.path].raw_pmc = file_io.create_df_pmc( self._runs[self.path].raw_pmc = file_io.create_df_pmc(
self.path, self.path,
self.get_args().nodes, self.get_args().nodes,
@@ -80,22 +80,33 @@ class tui_analysis(OmniAnalyze_Base):
kernel_verbose=self.get_args().kernel_verbose, kernel_verbose=self.get_args().kernel_verbose,
) )
# demangle and overwrite original 'Kernel_Name'
kernel_name_shortener( kernel_name_shortener(
self._runs[self.path].raw_pmc, self.get_args().kernel_verbose self._runs[self.path].raw_pmc, self.get_args().kernel_verbose
) )
# create the loaded table # 1. load top kernel
parser.load_table_data( parser.load_kernel_top(
workload=self._runs[self.path], workload=self._runs[self.path], dir=self.path, args=self.get_args()
dir=self.path,
is_gui=False,
args=self.get_args(),
config=self._profiling_config,
) )
# 2. load table data for each kernel
self.raw_dfs.clear()
for idx in self._runs[self.path].raw_pmc.index:
kernel_df = self._runs[self.path].raw_pmc.loc[[idx]]
kernel_name = kernel_df.pmc_perf["Kernel_Name"].loc[idx]
this_dfs = copy.deepcopy(self._runs[self.path].dfs)
parser.eval_metric(
this_dfs,
self._runs[self.path].dfs_type,
self._runs[self.path].sys_info.iloc[0],
kernel_df,
self.get_args().debug,
self._profiling_config,
)
self.raw_dfs[kernel_name] = this_dfs
def initalize_runs(self, normalization_filter=None): def initalize_runs(self, normalization_filter=None):
# load required configs
sysinfo_path = Path(self.path) sysinfo_path = Path(self.path)
sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv")) sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
self.arch = sys_info.iloc[0]["gpu_arch"] self.arch = sys_info.iloc[0]["gpu_arch"]
@@ -111,10 +122,6 @@ class tui_analysis(OmniAnalyze_Base):
self.load_options(normalization_filter) self.load_options(normalization_filter)
w = schema.Workload() w = schema.Workload()
# FIXME:
# For regular single node case, load sysinfo.csv directly
# For multi-node, either the default "all", or specified some,
# pick up the one in the 1st sub_dir. We could fix it properly later.
w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv")) w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
mspec = self.get_socs()[self.arch]._mspec mspec = self.get_socs()[self.arch]._mspec
if args.specs_correction: if args.specs_correction:
@@ -127,43 +134,14 @@ class tui_analysis(OmniAnalyze_Base):
return self._runs return self._runs
@demarcate @demarcate
def run_analysis(self): def run_kernel_analysis(self):
"""Run TUI analysis.""" self.kernel_dfs.clear()
super().run_analysis() for kernel_name, df in self.raw_dfs.items():
self.kernel_dfs[kernel_name] = process_panels_to_dataframes(
roof_plot = None self.get_args(), df, self._arch_configs[self.arch], roof_plot=None
# 1. check if not baseline && compatible soc:
if self.arch in [
# >= 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",
}
) )
roof_obj = self.get_socs()[self.arch].roofline_obj return self.kernel_dfs
if roof_obj: @demarcate
# NOTE: using default data type def run_top_kernel(self):
roof_plot = roof_obj.cli_generate_plot(roof_obj.get_dtype()[0]) return get_top_kernels_and_dispatch_ids(self._runs)
results = process_panels_to_dataframes(
self.get_args(),
self._runs,
self._arch_configs[self.arch],
self._profiling_config,
roof_plot=roof_plot,
)
return results
-1
Просмотреть файл
@@ -30,7 +30,6 @@ Central configuration for the application.
# Application settings # Application settings
APP_TITLE = "ROCm Compute Profiler TUI" APP_TITLE = "ROCm Compute Profiler TUI"
VERSION = "3.2.0"
# Widget configurations # Widget configurations
DEFAULT_COLLAPSIBLE_STATE = True # True = collapsed by default DEFAULT_COLLAPSIBLE_STATE = True # True = collapsed by default
+6 -2
Просмотреть файл
@@ -39,15 +39,18 @@ from textual.binding import Binding
from textual.widgets import Button, Footer, Header from textual.widgets import Button, Footer, Header
from textual_fspicker import SelectDirectory 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.views.main_view import MainView
from rocprof_compute_tui.widgets.menu_bar.menu_bar import DropdownMenu from rocprof_compute_tui.widgets.menu_bar.menu_bar import DropdownMenu
from utils.specs import MachineSpecs, generate_machine_specs from utils.specs import MachineSpecs, generate_machine_specs
from utils.utils import get_version
class RocprofTUIApp(App): class RocprofTUIApp(App):
"""Main application for the performance analysis tool.""" """Main application for the performance analysis tool."""
VERSION = get_version(config.rocprof_compute_home)["version"]
TITLE = f"{APP_TITLE} v{VERSION}" TITLE = f"{APP_TITLE} v{VERSION}"
SUB_TITLE = "Workload Analysis Tool" SUB_TITLE = "Workload Analysis Tool"
@@ -55,7 +58,8 @@ class RocprofTUIApp(App):
BINDINGS = [ BINDINGS = [
Binding(key="q", action="quit", description="Quit"), Binding(key="q", action="quit", description="Quit"),
Binding(key="r", action="refresh", description="Refresh"), Binding(key="r", action="refresh", description="Refresh"),
Binding(key="a", action="analyze", description="Analyze"), # TODO
# Binding(key="a", action="analyze", description="Analyze"),
] ]
def __init__( def __init__(
-51
Просмотреть файл
@@ -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
+35
Просмотреть файл
@@ -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
+77 -497
Просмотреть файл
@@ -1,87 +1,27 @@
import copy
import logging import logging
import os
import re
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from pathlib import Path
import numpy as np
import pandas as pd import pandas as pd
from config import HIDDEN_COLUMNS, HIDDEN_SECTIONS import config
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",
]
class LogLevel(str, Enum): class LogLevel(str, Enum):
"""Log levels for consistent logging."""
INFO = "info" INFO = "info"
WARNING = "warning" WARNING = "warning"
ERROR = "error" ERROR = "error"
SUCCESS = "success" # Maintained for UI compatibility SUCCESS = "success"
class Logger: class Logger:
"""Centralized logging handler for the application."""
def __init__(self, output_area=None): def __init__(self, output_area=None):
"""
Initialize the logger.
"""
self.output_area = output_area self.output_area = output_area
self._setup_logger() self._setup_logger()
def _setup_logger(self): def _setup_logger(self):
"""
Setup the Python logger with proper formatting.
"""
self.logger = logging.getLogger("app") self.logger = logging.getLogger("app")
self.logger.setLevel(logging.INFO) self.logger.setLevel(logging.INFO)
@@ -94,15 +34,9 @@ class Logger:
self.logger.addHandler(handler) self.logger.addHandler(handler)
def set_output_area(self, output_area): def set_output_area(self, output_area):
"""
Set or update the output area for displaying logs.
"""
self.output_area = output_area self.output_area = output_area
def log(self, message, level=LogLevel.INFO, update_ui=True): def log(self, message, level=LogLevel.INFO, update_ui=True):
"""
Log a message with the specified level.
"""
level_map = { level_map = {
LogLevel.INFO: logging.INFO, LogLevel.INFO: logging.INFO,
LogLevel.SUCCESS: logging.INFO, LogLevel.SUCCESS: logging.INFO,
@@ -145,151 +79,28 @@ class Logger:
self.log(message, LogLevel.ERROR, update_ui) self.log(message, LogLevel.ERROR, update_ui)
def split_table_line(line): def get_top_kernels_and_dispatch_ids(runs):
""" if not runs:
Splits a table row line into a list of cell strings (trimmed). For example: return None
│ │ Kernel_Name │ Count │ ... base_run = next(iter(runs.values()))
""" if not hasattr(base_run, "dfs"):
return None
cells = line.split("") top_kernel_df = base_run.dfs.get(1)
if cells and cells[0] == "": dispatch_id_df = base_run.dfs.get(2)
cells = cells[1:]
if cells and cells[-1] == "": if top_kernel_df is None or dispatch_id_df is None:
cells = cells[:-1] return None
return [cell.strip() for cell in cells]
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): def process_panels_to_dataframes(args, kernel_df, archConfigs, roof_plot=None):
"""
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
):
""" """
Process panel data into pandas DataFrames. Process panel data into pandas DataFrames.
Returns a nested dictionary structure with DataFrames and tui_style information. 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) # TODO: add individual kernel roofline logic
filter_panel_ids = profiling_config.get("filter_blocks", []) # TODO: implement args logic:
if isinstance(filter_panel_ids, dict): # args.filter_metrics
# For backward compatibility # args.cols
filter_panel_ids = [ # args.max_stat_num
name for name, type in filter_panel_ids.items() if type == "metric_id" # args.df_file_dir
]
filter_panel_ids = [
int(convert_metric_id_to_panel_info(metric_id)[0])
for metric_id in filter_panel_ids
]
# Initialize the result structure
result_structure = defaultdict(dict) result_structure = defaultdict(dict)
decimal_precision = getattr(args, "decimal", 2) if args else 2
for panel_id, panel in archConfigs.panel_configs.items(): for panel_id, panel in archConfigs.panel_configs.items():
# Skip panels that don't support baseline comparison if panel_id in config.HIDDEN_SECTIONS:
if panel_id in HIDDEN_SECTIONS:
continue continue
# Get section name (e.g., "0. Top Stats")
section_name = f"{panel_id // 100}. {panel['title']}" section_name = f"{panel_id // 100}. {panel['title']}"
for data_source in panel["data source"]: for data_source in panel["data source"]:
for type, table_config in data_source.items(): for type, table_config in data_source.items():
# Check for filtering conditions table_id = table_config["id"]
if (
not args.filter_metrics if table_id not in kernel_df:
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)
)
continue continue
# Process the data base_df = kernel_df[table_id]
base_run, base_data = next(iter(runs.items()))
base_df = base_data.dfs[table_config["id"]] if base_df is None or base_df.empty:
continue
df = pd.DataFrame(index=base_df.index) df = pd.DataFrame(index=base_df.index)
# Process columns for header in list(base_df.columns):
for header in list(base_df.keys()): if header in config.HIDDEN_COLUMNS_TUI:
if should_process_column(header, args, type): continue
if header in HIDDEN_COLUMNS: else:
pass df[header] = base_df[header]
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,
)
if not df.empty: df = apply_rounding_logic(df, decimal_precision)
# Check for empty columns
is_empty_columns_exist = check_empty_columns(df)
if not is_empty_columns_exist: subsection_name = (
# Get subsection name str(table_config["id"] // 100) + "." + str(table_config["id"] % 100)
table_id_str = ( )
str(table_config["id"] // 100) if "title" in table_config and table_config["title"]:
+ "." subsection_name += " " + table_config["title"]
+ str(table_config["id"] % 100)
)
subsection_name = table_id_str
if "title" in table_config and table_config["title"]:
subsection_name += " " + table_config["title"]
# Handle special cases for top stats result_structure[section_name][subsection_name] = {
if type == "raw_csv_table" and ( "df": df,
table_config["source"] == "pmc_kernel_top.csv" "tui_style": None,
or table_config["source"] == "pmc_dispatch_info.csv" }
):
df = df.head(args.max_stat_num)
# Check for transpose requirement if type == "metric_table" and "tui_style" in table_config:
transpose = ( result_structure[section_name][subsection_name]["tui_style"] = (
type != "raw_csv_table" table_config["tui_style"]
and "columnwise" in table_config )
and table_config.get("columnwise") == True
)
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) return dict(result_structure)
def should_process_column(header, args, type): def apply_rounding_logic(df, decimal_precision):
"""Check if a column should be processed based on arguments.""" df_copy = df.copy()
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")
)
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): if df_copy[column].dtype in ["float64", "float32", "int64", "int32"]:
"""Process columns that are not comparable across runs.""" df_copy[column] = df_copy[column].round(decimal_precision)
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)
)
else: else:
adjusted_name = base_df["Kernel_Name"].apply( try:
lambda x: string_multiple_lines(x, 80, 4) numeric_series = pd.to_numeric(df_copy[column], errors="coerce")
) if not numeric_series.isna().all():
df = pd.concat([df, adjusted_name], axis=1) rounded_series = numeric_series.round(decimal_precision)
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)
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
return df_copy
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
+203
Просмотреть файл
@@ -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",
)
)
+32 -31
Просмотреть файл
@@ -50,10 +50,10 @@ class MainView(Horizontal):
"""Main view layout for the application.""" """Main view layout for the application."""
selected_path = reactive(None) selected_path = reactive(None)
dfs = reactive({}) per_kernel_dfs = reactive({})
top_kernels = reactive([])
def __init__(self): def __init__(self):
"""Initialize the main view."""
super().__init__(id="main-container") super().__init__(id="main-container")
self.start_path = ( self.start_path = (
# NOTE: is cwd the best choice? # NOTE: is cwd the best choice?
@@ -70,7 +70,6 @@ class MainView(Horizontal):
pass pass
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""Compose the main view layout."""
self.logger.info("Composing main view layout", update_ui=False) self.logger.info("Composing main view layout", update_ui=False)
yield MenuBar() yield MenuBar()
@@ -80,7 +79,6 @@ class MainView(Horizontal):
# Center Panel - Analysis results display # Center Panel - Analysis results display
center_panel = CenterPanel() center_panel = CenterPanel()
yield center_panel yield center_panel
self.center = center_panel self.center = center_panel
# Bottom Panel - Output, terminal, and metric description # Bottom Panel - Output, terminal, and metric description
@@ -91,7 +89,6 @@ class MainView(Horizontal):
self.metric_description = tabs.description_area self.metric_description = tabs.description_area
self.output = tabs.output_area self.output = tabs.output_area
# Now set the output area for the logger
self.logger.set_output_area(self.output) self.logger.set_output_area(self.output)
self.logger.info("Main view layout composed") self.logger.info("Main view layout composed")
@@ -107,8 +104,9 @@ class MainView(Horizontal):
try: try:
row_data = table.get_row_at(row_idx) row_data = table.get_row_at(row_idx)
content = f"Selected Row {row_idx}:\n" content = f"Selected Metric ID: {row_data[0]}\n"
content += "\n".join(f"{val}" for val in row_data) content += f"Selected Metric: {row_data[1]}\n"
# content += f"Metric Description:\n\t{row_data[-1]}"
self.metric_description.text = content self.metric_description.text = content
self.logger.info(f"Row {row_idx} data displayed in metric_description") self.logger.info(f"Row {row_idx} data displayed in metric_description")
@@ -122,7 +120,8 @@ class MainView(Horizontal):
@work(thread=True) @work(thread=True)
def run_analysis(self) -> None: def run_analysis(self) -> None:
self.dfs = {} self.per_kernel_dfs = {}
self.top_kernels = []
if not self.selected_path: if not self.selected_path:
error_msg = "No directory selected for analysis" error_msg = "No directory selected for analysis"
@@ -173,7 +172,6 @@ class MainView(Horizontal):
self.logger.info( self.logger.info(
f"Step 3: sys_info_df shape = {sys_info_df.shape if hasattr(sys_info_df, 'shape') else 'No shape attribute'}" 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: except Exception as e:
self.logger.error(f"Step 3 failed - Error loading sys_info: {str(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)}") 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 converted = {sys_info}")
self.logger.info(f"Step 4: sys_info type = {type(sys_info)}")
except Exception as e: except Exception as e:
self.logger.error(f"Step 4 failed - Error converting sys_info: {str(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 # Step 8: Run analysis
try: try:
self.logger.info("Step 8: Running analysis") self.logger.info("Step 8: Running analysis")
self.dfs = analyzer.run_analysis() self.per_kernel_dfs = analyzer.run_kernel_analysis()
if not self.dfs: self.top_kernels = analyzer.run_top_kernel()
warning_msg = "Step 8: Analysis completed but no data was returned"
# 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._update_view(warning_msg, LogLevel.WARNING)
self.logger.warning(warning_msg) self.logger.warning(warning_msg)
else: else:
self.app.call_from_thread(self.refresh_results) self.app.call_from_thread(self.refresh_results)
self.logger.info("Step 8: Analysis completed successfully") self.logger.info("Step 8: Kernel Analysis completed successfully")
if self.dfs.get("4. Roofline"): # self.logger.info(f"{self.per_kernel_dfs}")
self.logger.info("Step 8: Roofline data available")
else:
self.logger.info("Step 8: Roofline data not available")
except Exception as e: except Exception as e:
self.logger.error(f"Step 8 failed - Error running analysis: {str(e)}") self.logger.error(f"Step 8 failed - Error running analysis: {str(e)}")
raise raise
@@ -257,17 +255,15 @@ class MainView(Horizontal):
def _update_view(self, message: str, log_level: LogLevel) -> None: def _update_view(self, message: str, log_level: LogLevel) -> None:
try: try:
# Use call_from_thread to safely update UI from background thread
self.app.call_from_thread(self._safe_update_view, message, log_level) self.app.call_from_thread(self._safe_update_view, message, log_level)
except Exception as e: except Exception as e:
# Capture errors that might occur when scheduling the UI update
self.logger.error(f"View update scheduling error: {str(e)}") self.logger.error(f"View update scheduling error: {str(e)}")
def _safe_update_view(self, message: str, log_level: LogLevel) -> None: def _safe_update_view(self, message: str, log_level: LogLevel) -> None:
try: try:
analyze_view = self.query_one("#analyze-view") kernel_view = self.query_one("#kernel-view")
if analyze_view: if kernel_view:
analyze_view.update_view(message, log_level) kernel_view.update_view(message, log_level)
else: else:
self.logger.warning("Analysis view not found when updating log") self.logger.warning("Analysis view not found when updating log")
except Exception as e: except Exception as e:
@@ -275,24 +271,29 @@ class MainView(Horizontal):
def refresh_results(self) -> None: def refresh_results(self) -> None:
try: try:
self.logger.info("Refreshing analysis results") self.logger.info("Refreshing kernel results")
analyze_view = self.query_one("#analyze-view") kernel_view = self.query_one("#kernel-view")
if not analyze_view: if not kernel_view:
self.logger.error("Analysis view not found") self.logger.error("Kernel view not found")
return return
if not hasattr(self, "dfs") or self.dfs is None: if (
self.logger.error("No analysis data available to display") 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 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.") self.logger.success(f"Results displayed successfully.")
except Exception as e: except Exception as e:
self.logger.error(f"Error refreshing results: {str(e)}") self.logger.error(f"Error refreshing results: {str(e)}")
def refresh_view(self) -> None: def refresh_view(self) -> None:
self.logger.info("Refreshing view...") self.logger.info("Refreshing view...")
if self.dfs: if self.top_kernels:
self.refresh_results() self.refresh_results()
else: else:
self.logger.warning("No data available for refresh") self.logger.warning("No data available for refresh")
-74
Просмотреть файл
@@ -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)
+6 -9
Просмотреть файл
@@ -29,9 +29,9 @@ Contains the panel widgets used in the main layout.
""" """
from textual.containers import Vertical 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 from rocprof_compute_tui.widgets.tabbed_content import TabsTabbedContent
@@ -48,15 +48,12 @@ class CenterPanel(Vertical):
super().__init__() super().__init__()
self.default_tab = "center-analyze" self.default_tab = "center-analyze"
self.analyze_view = AnalyzeView() self.kernel_view = KernelView()
def compose(self): def compose(self):
with TabsTabbedContent(initial="tab-analyze"): with TabsTabbedContent(initial="tab-kernel"):
with TabPane("Basic View", id="tab-analyze"): with TabPane("Basic View", id="tab-kernel"):
yield self.analyze_view yield self.kernel_view
# TODO:
# with TabPane("placeholder (🚧)", id="tab-1"):
# yield Label("🚧 Under Construction")
def on_mount(self) -> None: def on_mount(self) -> None:
self.add_class("section") self.add_class("section")
+32 -48
Просмотреть файл
@@ -68,7 +68,10 @@ def simple_bar(df, title=None):
w *= 100 w *= 100
plt.simple_bar(list(metric_dict.keys()), list(metric_dict.values()), width=w) plt.simple_bar(list(metric_dict.keys()), list(metric_dict.values()), width=w)
# plt.show() # 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): def simple_multiple_bar(df, title=None):
@@ -100,10 +103,13 @@ def simple_multiple_bar(df, title=None):
h *= 300 h *= 300
plt.plot_size(height=h) plt.plot_size(height=h)
plt.multiple_bar(labels, data, color=["blue", "blue+", 68, 63]) plt.multiple_bar(labels, data)
# plt.show() # 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): def simple_box(df, orientation="v", title=None):
@@ -173,7 +179,10 @@ def simple_box(df, orientation="v", title=None):
plt.theme("pro") plt.theme("pro")
# plt.show() # 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"): 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) super().__init__("", classes="roofline", **kwargs)
self.df = df self.df = df
# Disable markup rendering
self._render_markup = False
try: try:
plot_str = "" plot_str = str(self.df.get("4. Roofline", "No roofline data generated"))
try:
result = self.df["4. Roofline"]
if result:
plot_str = str(result)
except:
plot_str = "No roofline data generated"
self.update(plot_str) self.update(plot_str)
except Exception as e: except Exception as e:
error_message = f"Roofline plot error: {str(e)}\n{traceback.format_exc()}" 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): def __init__(self, df: pd.DataFrame, **kwargs):
"""Initialize the memory chart."""
super().__init__("", classes="mem-chart", **kwargs) super().__init__("", classes="mem-chart", **kwargs)
self.df = df self.df = df
# Generate the chart content on initialization
try: try:
# Prepare data if self.df is None or self.df.empty:
metric_dict = ( self.update("No chart data generated")
self.df[["Metric", "Value"]].set_index("Metric").to_dict()["Value"] 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 original_stdout = sys.stdout
string_buffer = StringIO()
sys.stdout = string_buffer
try: try:
# Generate the chart with StringIO() as string_buffer:
result = plot_mem_chart("", "per_kernel", metric_dict) sys.stdout = string_buffer
stdout_output = string_buffer.getvalue() 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"
finally: finally:
sys.stdout = original_stdout 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) self.update(plot_str)
except Exception as e: except Exception as e:
error_message = f"Memory chart error: {str(e)}\n{traceback.format_exc()}" self.update(f"Memory chart error: {str(e)}")
self.update(f"Error: {str(error_message)}")
class SimpleBar(Static): class SimpleBar(Static):
@@ -372,7 +367,6 @@ class SimpleBar(Static):
""" """
def __init__(self, df: pd.DataFrame, **kwargs): def __init__(self, df: pd.DataFrame, **kwargs):
"""Initialize the simple bar."""
super().__init__("", classes="simple-bar", **kwargs) super().__init__("", classes="simple-bar", **kwargs)
self.df = df self.df = df
@@ -381,13 +375,8 @@ class SimpleBar(Static):
if result: if result:
plot_str = str(result) plot_str = str(result)
# Escape markup characters
escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]") escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]")
self.update(escaped_content) self.update(escaped_content)
# Alternative - wrap in [pre] tags for preformatted text
# self.update(f"[pre]{plot_str}[/pre]")
else: else:
self.update("No simple bar data generated") self.update("No simple bar data generated")
@@ -398,7 +387,6 @@ class SimpleBar(Static):
class SimpleBox(Static): class SimpleBox(Static):
"""Simple Box visualization widget."""
DEFAULT_CSS = """ DEFAULT_CSS = """
SimpleBox { SimpleBox {
@@ -413,7 +401,6 @@ class SimpleBox(Static):
""" """
def __init__(self, df: pd.DataFrame, **kwargs): def __init__(self, df: pd.DataFrame, **kwargs):
"""Initialize the simple box."""
super().__init__("", classes="simple-box", **kwargs) super().__init__("", classes="simple-box", **kwargs)
self.df = df self.df = df
@@ -422,7 +409,6 @@ class SimpleBox(Static):
if result: if result:
plot_str = str(result) plot_str = str(result)
# Escape markup characters
escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]") escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]")
self.update(escaped_content) self.update(escaped_content)
else: else:
@@ -450,7 +436,6 @@ class SimpleMultiBar(Static):
""" """
def __init__(self, df: pd.DataFrame, **kwargs): def __init__(self, df: pd.DataFrame, **kwargs):
"""Initialize the simple multiple bar."""
super().__init__("", classes="simple-multi-bar", **kwargs) super().__init__("", classes="simple-multi-bar", **kwargs)
self.df = df self.df = df
@@ -459,7 +444,6 @@ class SimpleMultiBar(Static):
if result: if result:
plot_str = str(result) plot_str = str(result)
# Escape markup characters
escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]") escaped_content = plot_str.replace("[", r"\[").replace("]", r"\]")
self.update(escaped_content) self.update(escaped_content)
else: else:
+8 -10
Просмотреть файл
@@ -26,7 +26,6 @@ from typing import Any, Dict, List, Optional
import pandas as pd import pandas as pd
import yaml import yaml
from textual.containers import VerticalScroll
from textual.widgets import Collapsible, DataTable, Label from textual.widgets import Collapsible, DataTable, Label
from rocprof_compute_tui.widgets.charts import ( 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: def create_table(df: pd.DataFrame) -> DataTable:
table = DataTable(zebra_stripes=True) table = DataTable(zebra_stripes=True)
# Clean the DataFrame - remove NaN and empty cells
df = df.reset_index() df = df.reset_index()
df = df.dropna(how="any")
df = df[~df.apply(lambda row: row.astype(str).str.strip().eq("").any(), axis=1)] 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] str_columns = [str(col) for col in df.columns]
table.add_columns(*str_columns) table.add_columns(*str_columns)
table.add_rows([tuple(str(x) for x in row) for row in df.itertuples(index=False)]) 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: with open(config_path, "r") as file:
return yaml.safe_load(file) return yaml.safe_load(file)
except FileNotFoundError: 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: except yaml.YAMLError as e:
raise ValueError(f"Error parsing YAML configuration: {e}") raise ValueError(f"Error parsing YAML configuration: {e}")
@@ -167,7 +165,7 @@ def build_subsection(
return collapsible return collapsible
def build_dynamic_kernel_sections( def build_kernel_sections(
dfs: Dict[str, Any], skip_sections: List[str] dfs: Dict[str, Any], skip_sections: List[str]
) -> List[Collapsible]: ) -> List[Collapsible]:
children = [] children = []
@@ -198,9 +196,10 @@ def build_dynamic_kernel_sections(
return None return None
try: try:
df = data["df"] if data["df"] is None or data["df"].empty:
return None
tui_style = data.get("tui_style") 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: if widget is None:
add_warning(f"Widget creation returned None for '{subsection_name}'") 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) # Handle dynamic sections (like kernel sections)
elif section_config.get("dynamic_sections", False): elif section_config.get("dynamic_sections", False):
skip_sections = section_config.get("skip_sections", []) 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 # Handle regular sections with subsections
elif "subsections" in section_config: elif "subsections" in section_config:
@@ -290,7 +289,6 @@ def build_section_from_config(
except Exception as e: except Exception as e:
error_msg = f"{subsection_config.get('title', 'Unknown')} error: {str(e)}" error_msg = f"{subsection_config.get('title', 'Unknown')} error: {str(e)}"
children.append(Label(error_msg, classes="warning")) children.append(Label(error_msg, classes="warning"))
else: else:
children = [Label("No configuration provided for this section")] children = [Label("No configuration provided for this section")]
-39
Просмотреть файл
@@ -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()]
+57 -57
Просмотреть файл
@@ -106,8 +106,8 @@ def format_text(
) )
key_str = ( key_str = (
"{key:{key_format}}".format(key=key, key_format=key_format) "{key:{key_format}}".format(key=key, key_format=key_format)
if key is not None if key and isinstance(key, (int, float))
else None else str(key) if key else None
) )
unit_string = post_description_with_space if not "N/A" in value_str else "" 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_max = self.y_max - 5.0
block_instr_buff.y_min = block_instr_buff.y_max - 24.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_occupancy = metric_dict.get("Wavefront Occupancy", "n/a")
block_instr_buff.wave_life = metric_dict["Wave Life"] block_instr_buff.wave_life = metric_dict.get("Wave Life", "n/a")
block_instr_buff.draw(canvas) 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_max = block_instr_buff.y_max
block_instr_disp.y_min = block_instr_buff.y_min block_instr_disp.y_min = block_instr_buff.y_min
block_instr_disp.instrs["SALU"] = metric_dict["SALU"] block_instr_disp.instrs["SALU"] = metric_dict.get("SALU", "n/a")
block_instr_disp.instrs["SMEM"] = metric_dict["SMEM"] block_instr_disp.instrs["SMEM"] = metric_dict.get("SMEM", "n/a")
block_instr_disp.instrs["VALU"] = metric_dict["VALU"] block_instr_disp.instrs["VALU"] = metric_dict.get("VALU", "n/a")
block_instr_disp.instrs["MFMA"] = metric_dict["MFMA"] block_instr_disp.instrs["MFMA"] = metric_dict.get("MFMA", "n/a")
block_instr_disp.instrs["VMEM"] = metric_dict["VMEM"] block_instr_disp.instrs["VMEM"] = metric_dict.get("VMEM", "n/a")
block_instr_disp.instrs["LDS"] = metric_dict["LDS"] block_instr_disp.instrs["LDS"] = metric_dict.get("LDS", "n/a")
block_instr_disp.instrs["GWS"] = metric_dict["GWS"] block_instr_disp.instrs["GWS"] = metric_dict.get("GWS", "n/a")
block_instr_disp.instrs["BRANCH"] = metric_dict["BR"] block_instr_disp.instrs["BRANCH"] = metric_dict.get("BR", "n/a")
block_instr_disp.draw(canvas) block_instr_disp.draw(canvas)
@@ -1056,14 +1056,14 @@ class MemChart:
block_exec.y_min = block_instr_disp.y_min - 6 block_exec.y_min = block_instr_disp.y_min - 6
block_exec.y_max = block_instr_disp.y_max block_exec.y_max = block_instr_disp.y_max
block_exec.active_cus = metric_dict["Active CUs"] block_exec.active_cus = metric_dict.get("Active CUs", "n/a")
block_exec.num_cus = metric_dict["Num CUs"] block_exec.num_cus = metric_dict.get("Num CUs", "n/a")
block_exec.vgprs = metric_dict["VGPR"] block_exec.vgprs = metric_dict.get("VGPR", "n/a")
block_exec.sgprs = metric_dict["SGPR"] block_exec.sgprs = metric_dict.get("SGPR", "n/a")
block_exec.lds_alloc = metric_dict["LDS Allocation"] block_exec.lds_alloc = metric_dict.get("LDS Allocation", "n/a")
block_exec.scratch_alloc = metric_dict["Scratch Allocation"] block_exec.scratch_alloc = metric_dict.get("Scratch Allocation", "n/a")
block_exec.wavefronts = metric_dict["Wavefronts"] block_exec.wavefronts = metric_dict.get("Wavefronts", "n/a")
block_exec.workgroups = metric_dict["Workgroups"] block_exec.workgroups = metric_dict.get("Workgroups", "n/a")
block_exec.draw(canvas) block_exec.draw(canvas)
@@ -1075,11 +1075,11 @@ class MemChart:
wires_E_GLV.y_min = block_instr_disp.y_min wires_E_GLV.y_min = block_instr_disp.y_min
wires_E_GLV.y_max = block_instr_disp.y_max wires_E_GLV.y_max = block_instr_disp.y_max
wires_E_GLV.lds_req = metric_dict["LDS Req"] wires_E_GLV.lds_req = metric_dict.get("LDS Req", "n/a")
wires_E_GLV.vl1_rd = metric_dict["VL1 Rd"] wires_E_GLV.vl1_rd = metric_dict.get("VL1 Rd", "n/a")
wires_E_GLV.vl1_wr = metric_dict["VL1 Wr"] wires_E_GLV.vl1_wr = metric_dict.get("VL1 Wr", "n/a")
wires_E_GLV.vl1_atomic = metric_dict["VL1 Atomic"] wires_E_GLV.vl1_atomic = metric_dict.get("VL1 Atomic", "n/a")
wires_E_GLV.sl1_rd = metric_dict["sL1D Rd"] wires_E_GLV.sl1_rd = metric_dict.get("VL1D Rd", "n/a")
wires_E_GLV.draw(canvas) wires_E_GLV.draw(canvas)
@@ -1093,7 +1093,7 @@ class MemChart:
y_max=block_instr_buff.y_min, 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) wire_InstrBuff_IL1Cache.draw(canvas)
@@ -1118,8 +1118,8 @@ class MemChart:
block_lds.y_max = wires_E_GLV.y_max block_lds.y_max = wires_E_GLV.y_max
block_lds.y_min = block_lds.y_max - 5 block_lds.y_min = block_lds.y_max - 5
block_lds.util = metric_dict["LDS Util"] block_lds.util = metric_dict.get("LDS Util", "n/a")
block_lds.latency = metric_dict["LDS Latency"] block_lds.latency = metric_dict.get("LDS Latency", "n/a")
block_lds.draw(canvas) block_lds.draw(canvas)
@@ -1131,10 +1131,10 @@ class MemChart:
block_vector_L1.y_max = block_lds.y_min - 3 block_vector_L1.y_max = block_lds.y_min - 3
block_vector_L1.y_min = block_vector_L1.y_max - 9 block_vector_L1.y_min = block_vector_L1.y_max - 9
block_vector_L1.hit = metric_dict["VL1 Hit"] block_vector_L1.hit = metric_dict.get("VL1 Hit", "n/a")
block_vector_L1.latency = metric_dict["VL1 Lat"] block_vector_L1.latency = metric_dict.get("VL1 Lat", "n/a")
block_vector_L1.coales = metric_dict["VL1 Coalesce"] block_vector_L1.coales = metric_dict.get("VL1 Coalesce", "n/a")
block_vector_L1.stall = metric_dict["VL1 Stall"] block_vector_L1.stall = metric_dict.get("VL1 Stall", "n/a")
block_vector_L1.draw(canvas) 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_max = block_vector_L1.y_min - 3
block_const_L1.y_min = block_const_L1.y_max - 5 block_const_L1.y_min = block_const_L1.y_max - 5
block_const_L1.hit = metric_dict["sL1D Hit"] block_const_L1.hit = metric_dict.get("sL1D Hit", "n/a")
block_const_L1.latency = metric_dict["sL1D Lat"] block_const_L1.latency = metric_dict.get("sL1D Lat", "n/a")
block_const_L1.draw(canvas) 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_max = block_const_L1.y_min - 3
block_instr_L1.y_min = block_instr_L1.y_max - 5 block_instr_L1.y_min = block_instr_L1.y_max - 5
block_instr_L1.hit = metric_dict["IL1 Hit"] block_instr_L1.hit = metric_dict.get("IL1 Hit", "n/a")
block_instr_L1.latency = metric_dict["IL1 Lat"] block_instr_L1.latency = metric_dict.get("IL1 Lat", "n/a")
block_instr_L1.draw(canvas) 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.x_max = wires_L1_L2.x_min + 14
wires_L1_L2.y_min = block_instr_L1.y_min wires_L1_L2.y_min = block_instr_L1.y_min
wires_L1_L2.y_max = block_vector_L1.y_max 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_rd = metric_dict.get("VL1_L2 Rd", "n/a")
wires_L1_L2.vl1_l2_wr = metric_dict["VL1_L2 Wr"] wires_L1_L2.vl1_l2_wr = metric_dict.get("VL1_L2 Wr", "n/a")
wires_L1_L2.vl1_l2_atomic = metric_dict["VL1_L2 Atomic"] wires_L1_L2.vl1_l2_atomic = metric_dict.get("VL1_L2 Atomic", "n/a")
wires_L1_L2.sl1_l2_rd = metric_dict["sL1D_L2 Rd"] wires_L1_L2.sl1_l2_rd = metric_dict.get("VL1D_L2 Rd", "n/a")
wires_L1_L2.sl1_l2_wr = metric_dict["sL1D_L2 Wr"] wires_L1_L2.sl1_l2_wr = metric_dict.get("VL1D_L2 Wr", "n/a")
wires_L1_L2.sl1_l2_atomic = metric_dict["sL1D_L2 Atomic"] wires_L1_L2.sl1_l2_atomic = metric_dict.get("VL1D_L2 Atomic", "n/a")
wires_L1_L2.il1_l2_req = metric_dict["IL1_L2 Rd"] wires_L1_L2.il1_l2_req = metric_dict.get("IL1_L2 Rd", "n/a")
wires_L1_L2.draw(canvas) wires_L1_L2.draw(canvas)
@@ -1190,12 +1190,12 @@ class MemChart:
block_L2.y_min = block_instr_L1.y_min block_L2.y_min = block_instr_L1.y_min
block_L2.y_max = block_lds.y_max block_L2.y_max = block_lds.y_max
block_L2.hit = metric_dict["L2 Hit"] block_L2.hit = metric_dict.get("L2 Hit", "n/a")
block_L2.rd = metric_dict["L2 Rd"] block_L2.rd = metric_dict.get("L2 Rd", "n/a")
block_L2.wr = metric_dict["L2 Wr"] block_L2.wr = metric_dict.get("L2 Wr", "n/a")
block_L2.atomic = metric_dict["L2 Atomic"] block_L2.atomic = metric_dict.get("L2 Atomic", "n/a")
block_L2.rd_lat = metric_dict["L2 Rd Lat"] block_L2.rd_lat = metric_dict.get("L2 Rd Lat", "n/a")
block_L2.wr_lat = metric_dict["L2 Wr Lat"] block_L2.wr_lat = metric_dict.get("L2 Wr Lat", "n/a")
block_L2.draw(canvas) block_L2.draw(canvas)
@@ -1209,9 +1209,9 @@ class MemChart:
y_max=block_L2.y_max - 10, y_max=block_L2.y_max - 10,
) )
wires_L2_Fabric.rd = metric_dict["Fabric_L2 Rd"] wires_L2_Fabric.rd = metric_dict.get("Fabric_L2 Rd", "n/a")
wires_L2_Fabric.wr = metric_dict["Fabric_L2 Wr"] wires_L2_Fabric.wr = metric_dict.get("Fabric_L2 Wr", "n/a")
wires_L2_Fabric.atomic = metric_dict["Fabric_L2 Atomic"] wires_L2_Fabric.atomic = metric_dict.get("Fabric_L2 Atomic", "n/a")
wires_L2_Fabric.draw(canvas) wires_L2_Fabric.draw(canvas)
@@ -1236,9 +1236,9 @@ class MemChart:
y_min=block_xgmi_pcie.y_min - 5 - 11, y_min=block_xgmi_pcie.y_min - 5 - 11,
) )
block_fabric.lat["Rd"] = metric_dict["Fabric Rd Lat"] block_fabric.lat["Rd"] = metric_dict.get("Fabric Rd Lat", "n/a")
block_fabric.lat["Wr"] = metric_dict["Fabric Wr Lat"] block_fabric.lat["Wr"] = metric_dict.get("Fabric Wr Lat", "n/a")
block_fabric.lat["Atomic"] = metric_dict["Fabric Atomic Lat"] block_fabric.lat["Atomic"] = metric_dict.get("Fabric Atomic Lat", "n/a")
block_fabric.draw(canvas) block_fabric.draw(canvas)
@@ -1264,8 +1264,8 @@ class MemChart:
y_max=block_fabric.y_max - 4, y_max=block_fabric.y_max - 4,
) )
wires_Fabric_HBM.rd = metric_dict["HBM Rd"] wires_Fabric_HBM.rd = metric_dict.get("HBM Rd", "n/a")
wires_Fabric_HBM.wr = metric_dict["HBM Wr"] wires_Fabric_HBM.wr = metric_dict.get("HBM Wr", "n/a")
wires_Fabric_HBM.draw(canvas) wires_Fabric_HBM.draw(canvas)
+22 -7
Просмотреть файл
@@ -137,14 +137,28 @@ def to_max(*args):
def to_avg(a): def to_avg(a):
if str(type(a)) == "<class 'NoneType'>": if str(type(a)) == "<class 'NoneType'>":
return np.nan return np.nan
elif np.isnan(a).all():
return np.nan
elif a.empty:
return np.nan
elif isinstance(a, pd.core.series.Series): 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: else:
raise Exception("to_avg: unsupported type.") raise Exception(f"to_avg: unsupported type: {type(a)}")
def to_median(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) s = re.sub(r"\'\]\[(\d+)\]", r"[\g<1>]']", s)
# use .get() to catch any potential KeyErrors # use .get() to catch any potential KeyErrors
s = re.sub(r"raw_pmc_df\['(.*?)']", r'raw_pmc_df.get("\1")', s) s = re.sub(r"raw_pmc_df\['(.*?)']", r'raw_pmc_df.get("\1")', s)
# print("--- intermediate string: ", s)
# apply coll_level # apply coll_level
if config.get("format_rocprof_output") == "rocpd": if config.get("format_rocprof_output") == "rocpd":
# Replace SQ_ACCUM_PREV_HIRES with coll_level_ACCUM then ignore coll_level df # 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): def load_table_data(workload, dir, is_gui, args, config, skipKernelTop=False):
""" """
- Load data for all "raw_csv_table" - 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" - Calculate mertric value for all "metric_table"
""" """
if not skipKernelTop: if not skipKernelTop:
+1 -1
Просмотреть файл
@@ -24,7 +24,7 @@
import os import os
import shutil import shutil
from unittest.mock import Mock, patch from unittest.mock import Mock
import pandas as pd import pandas as pd
import pytest import pytest
+1 -5
Просмотреть файл
@@ -24,11 +24,7 @@
import logging import logging
import shutil from unittest.mock import MagicMock, Mock, patch
import sys
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, Mock, call, patch
import pandas as pd import pandas as pd
import pytest import pytest