[rocprofiler-compute][TUI] Updates and refactor. (#703)

This commit is contained in:
xuchen-amd
2025-08-22 11:02:32 -04:00
committed by GitHub
parent 59a8c8011d
commit ed877433f3
4 changed files with 108 additions and 150 deletions
@@ -230,7 +230,7 @@ class RocProfCompute:
if arch in self.__supported_archs.keys():
ac = schema.ArchConfig()
ac.panel_configs = file_io.load_panel_configs(
self.__args.config_dir.joinpath(arch)
[self.__args.config_dir.joinpath(arch)]
)
sys_info = self.__mspec.get_class_members().iloc[0]
parser.build_dfs(archConfigs=ac, filter_metrics=[], sys_info=sys_info)
@@ -26,6 +26,8 @@
import copy
from pathlib import Path
import pandas as pd
from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base
from rocprof_compute_tui.utils.tui_utils import (
get_top_kernels_and_dispatch_ids,
@@ -40,9 +42,8 @@ class tui_analysis(OmniAnalyze_Base):
def __init__(self, args, supported_archs, path):
super().__init__(args, supported_archs)
self.path = str(path)
self.arch = None
self.args = self.get_args()
self.raw_dfs = {}
self.kernel_dfs = {}
# -----------------------
# Required child methods
@@ -50,99 +51,108 @@ class tui_analysis(OmniAnalyze_Base):
@demarcate
def pre_processing(self):
self._profiling_config = file_io.load_profiling_config(self.path)
self._runs = self.initalize_runs()
if self.get_args().random_port:
if self.args.random_port:
console_error("--gui flag is required to enable --random-port")
self._runs[self.path].raw_pmc = file_io.create_df_pmc(
# Process PMC data
workload = self._runs[self.path]
workload.raw_pmc = file_io.create_df_pmc(
self.path,
self.get_args().nodes,
self.get_args().spatial_multiplexing,
self.get_args().kernel_verbose,
self.get_args().verbose,
self.args.nodes,
self.args.spatial_multiplexing,
self.args.kernel_verbose,
self.args.verbose,
self._profiling_config,
)
if self.get_args().spatial_multiplexing:
self._runs[self.path].raw_pmc = self.spatial_multiplex_merge_counters(
self._runs[self.path].raw_pmc
)
if self.args.spatial_multiplexing:
workload.raw_pmc = self.spatial_multiplex_merge_counters(workload.raw_pmc)
file_io.create_df_kernel_top_stats(
df_in=self._runs[self.path].raw_pmc,
df_in=workload.raw_pmc,
raw_data_dir=self.path,
filter_gpu_ids=self._runs[self.path].filter_gpu_ids,
filter_dispatch_ids=self._runs[self.path].filter_dispatch_ids,
filter_nodes=self._runs[self.path].filter_nodes,
time_unit=self.get_args().time_unit,
max_stat_num=self.get_args().max_stat_num,
kernel_verbose=self.get_args().kernel_verbose,
)
kernel_name_shortener(
self._runs[self.path].raw_pmc, self.get_args().kernel_verbose
filter_gpu_ids=workload.filter_gpu_ids,
filter_dispatch_ids=workload.filter_dispatch_ids,
filter_nodes=workload.filter_nodes,
time_unit=self.args.time_unit,
max_stat_num=self.args.max_stat_num,
kernel_verbose=self.args.kernel_verbose,
)
kernel_name_shortener(self._runs[self.path].raw_pmc, self.args.kernel_verbose)
# 1. load top kernel
parser.load_kernel_top(
workload=self._runs[self.path], dir=self.path, args=self.get_args()
workload=self._runs[self.path], dir=self.path, args=self.args
)
# 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]]
# 2. Generate kernel-specific dataframes
self.raw_dfs = {}
for idx in workload.raw_pmc.index:
kernel_df = workload.raw_pmc.loc[[idx]]
kernel_name = kernel_df.pmc_perf["Kernel_Name"].loc[idx]
this_dfs = copy.deepcopy(self._runs[self.path].dfs)
kernel_dfs = copy.deepcopy(workload.dfs)
parser.eval_metric(
this_dfs,
self._runs[self.path].dfs_type,
self._runs[self.path].sys_info.iloc[0],
kernel_dfs,
workload.dfs_type,
workload.sys_info.iloc[0],
workload.roofline_peaks,
kernel_df,
self.get_args().debug,
self.args.debug,
self._profiling_config,
)
self.raw_dfs[kernel_name] = this_dfs
self.raw_dfs[kernel_name] = kernel_dfs
def initalize_runs(self, normalization_filter=None):
sysinfo_path = Path(self.path)
sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
self.arch = sys_info.iloc[0]["gpu_arch"]
args = self.get_args()
# Load system info and configure
sys_info = file_io.load_sys_info(Path(self.path) / "sysinfo.csv")
arch = sys_info.iloc[0]["gpu_arch"]
self.generate_configs(
self.arch,
args.config_dir,
args.list_stats,
args.filter_metrics,
arch,
self.args.config_dir,
self.args.list_stats,
self.args.filter_metrics,
sys_info.iloc[0],
)
self.load_options(normalization_filter)
# Create workload with system and roofline data
w = schema.Workload()
w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
mspec = self.get_socs()[self.arch]._mspec
if args.specs_correction:
w.sys_info = parser.correct_sys_info(mspec, args.specs_correction)
w.avail_ips = w.sys_info["ip_blocks"].item().split("|")
w.dfs = copy.deepcopy(self._arch_configs[self.arch].dfs)
w.dfs_type = self._arch_configs[self.arch].dfs_type
self._runs[self.path] = w
w.sys_info = (
parser.correct_sys_info(
self.get_socs()[arch]._mspec, self.args.specs_correction
)
if self.args.specs_correction
else sys_info
)
roofline_path = Path(self.path) / "roofline.csv"
w.roofline_peaks = (
pd.read_csv(roofline_path)
if not getattr(self.args, "no_roof", False) and roofline_path.exists()
else pd.DataFrame()
)
w.avail_ips = w.sys_info["ip_blocks"].item().split("|")
w.dfs = copy.deepcopy(self._arch_configs[arch].dfs)
w.dfs_type = self._arch_configs[arch].dfs_type
self._runs[self.path] = w
return self._runs
@demarcate
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
arch = list(self._arch_configs.keys())[0]
return {
kernel_name: process_panels_to_dataframes(
self.args, df, self._arch_configs[arch], roof_plot=None
)
return self.kernel_dfs
for kernel_name, df in self.raw_dfs.items()
}
@demarcate
def run_top_kernel(self):
return get_top_kernels_and_dispatch_ids(self._runs)
@@ -29,12 +29,10 @@ ROCm Compute Profiler TUI - Main Application with Analysis Methods
import importlib
import json
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from textual import on, work
from textual.app import App, ComposeResult
from textual.app import App
from textual.binding import Binding
from textual.widgets import Button, Footer, Header
from textual_fspicker import SelectDirectory
@@ -43,7 +41,7 @@ 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.specs import generate_machine_specs
from utils.utils import get_version
@@ -62,123 +60,70 @@ class RocprofTUIApp(App):
# Binding(key="a", action="analyze", description="Analyze"),
]
def __init__(
self, args: Optional[Any] = None, supported_archs: Optional[Dict] = None
) -> None:
"""
Initialize the application.
"""
def __init__(self, args=None, supported_archs=None):
super().__init__()
self.main_view = MainView()
self.recent_dirs = self._load_recent_dirs()
self.recent_file = Path.home() / ".textual_browser_recent.json"
self.recent_dirs: List[str] = []
self.current_path = ""
self.load_recent_directories()
# Initialize analysis-related attributes
# Analysis attributes
self.args = args
self.supported_archs = supported_archs or {}
self.soc: Dict = {}
self.mspec: Optional[MachineSpecs] = None
self.soc = {}
self.mspec = None
def compose(self) -> ComposeResult:
"""Compose the application layout."""
def compose(self):
yield Header()
yield self.main_view
yield Footer()
def action_refresh(self) -> None:
"""Refresh the view."""
try:
self.main_view.refresh_view()
except Exception as e:
self.notify(f"Refresh failed: {str(e)}", severity="error")
def action_refresh(self):
self.main_view.refresh_view()
def load_soc_specs(self, sysinfo: dict = None) -> None:
"""
Load OmniSoC instance for analysis.
"""
def load_soc_specs(self, sysinfo=None):
self.mspec = generate_machine_specs(self.args, sysinfo)
if self.args and self.args.specs:
print(self.mspec)
return
arch = self.mspec.gpu_arch
# Dynamically import and instantiate the SoC class
soc_module = importlib.import_module("rocprof_compute_soc.soc_" + arch)
soc_class = getattr(soc_module, arch + "_soc")
soc_module = importlib.import_module(f"rocprof_compute_soc.soc_{arch}")
soc_class = getattr(soc_module, f"{arch}_soc")
self.soc[arch] = soc_class(self.args, self.mspec)
def get_soc(self) -> Dict:
"""Get the SoC dictionary."""
return self.soc
def _load_recent_dirs(self):
recent_file = Path.home() / ".textual_browser_recent.json"
if recent_file.exists():
with open(recent_file, "r") as f:
return json.load(f)
return []
def get_mspec(self) -> Optional[MachineSpecs]:
"""Get the machine specifications."""
return self.mspec
def _save_recent_dirs(self):
recent_file = Path.home() / ".textual_browser_recent.json"
with open(recent_file, "w") as f:
json.dump(self.recent_dirs, f, indent=2)
def load_recent_directories(self) -> None:
"""Load recent directories from file."""
try:
if self.recent_file.exists():
with open(self.recent_file, "r") as f:
self.recent_dirs = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
self.recent_dirs = []
def add_recent_dir(self, directory):
directory = str(Path(directory).absolute())
def save_recent_directories(self) -> None:
"""Save recent directories to file."""
try:
with open(self.recent_file, "w") as f:
json.dump(self.recent_dirs, f, indent=2)
except Exception as e:
self.notify(f"Failed to save recent directories: {e}", severity="error")
def add_to_recent(self, directory: str) -> None:
"""Add directory to recent list (FIFO, max 5 items)."""
directory = os.path.abspath(directory)
# Remove if already exists
# Remove if exists, add to front, keep max 5
if directory in self.recent_dirs:
self.recent_dirs.remove(directory)
# Add to front
# TODO: should we check to if workload dir can be successfully loaded?
self.recent_dirs.insert(0, directory)
# Keep only last 5
self.recent_dirs = self.recent_dirs[:5]
self._save_recent_dirs()
# Save to file
self.save_recent_directories()
def on_recent_selected(self, selected_dir: str) -> None:
def on_recent_selected(self, selected_dir):
if selected_dir:
self.main_view.selected_path = selected_dir
self.main_view.run_analysis()
@on(Button.Pressed, "#menu-open-workload")
@work
async def pick_a_directory(self) -> None:
async def pick_directory(self):
if opened := await self.push_screen_wait(SelectDirectory()):
self.add_to_recent(str(opened))
self.add_recent_dir(str(opened))
self.main_view.selected_path = opened
dropdown = self.query_one("#file-dropdown", DropdownMenu)
dropdown.add_class("hidden")
self.query_one("#file-dropdown", DropdownMenu).add_class("hidden")
self.main_view.run_analysis()
def run_tui(args: Optional[Any] = None, supported_archs: Optional[list] = None) -> None:
"""
Run the TUI application.
"""
try:
app = RocprofTUIApp(args, supported_archs)
app.run()
except KeyboardInterrupt:
pass
except Exception as e:
raise RuntimeError(f"Failed to run TUI application: {str(e)}") from e
def run_tui(args=None, supported_archs=None):
"""Run the TUI application."""
app = RocprofTUIApp(args, supported_archs)
app.run()
@@ -32,6 +32,9 @@ sections:
- "2. System Speed-of-Light"
- "3. Memory Chart"
- "4. Roofline"
- "32. GPU Speed-of-Light"
- "33. Compute Throughput"
- "34. Memory Throughput"
collapsed: true
- title: "Source Level Analysis"