[rocprofiler-compute][TUI] Updates and refactor. (#703)
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user