682ae2d014
* Remove L2 channels from --list-metrics --list-metrics moved to general options List metrics for the current architecture Filter blocks for metrics Removed test for --list-metrics in profile mode Test the options don't throw error Fixed --config-dir error Test stdout for command line options Provide path list for loading panel configs Show L2 Cache (per) channel metrics Changed command line option names Can show two levels only Removed filtering blocks Moved blocks to original position Removed filter block tests Removed filtering Formaating fix Readability enhancement Test formatting Filter L2 channels without sysinfo Show avilable metrics for current arch Intermediate commit Fixed tests Added argument sanitization Added list_metrics to ctest merge iconflict resolution Updated test marker Updated changelog Fixed formatting * Updated docs
522 lines
18 KiB
Python
522 lines
18 KiB
Python
##############################################################################
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2021 - 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.
|
|
|
|
##############################################################################
|
|
|
|
import argparse
|
|
import importlib
|
|
import os
|
|
import socket
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
import config
|
|
from argparser import omniarg_parser
|
|
from utils import file_io, parser, schema
|
|
from utils.logger import (
|
|
console_debug,
|
|
console_error,
|
|
console_log,
|
|
console_warning,
|
|
demarcate,
|
|
setup_console_handler,
|
|
setup_file_handler,
|
|
setup_logging_priority,
|
|
)
|
|
from utils.mi_gpu_spec import mi_gpu_specs
|
|
from utils.specs import MachineSpecs, generate_machine_specs
|
|
from utils.utils import (
|
|
detect_rocprof,
|
|
get_submodules,
|
|
get_version,
|
|
get_version_display,
|
|
parse_sets_yaml,
|
|
set_locale_encoding,
|
|
)
|
|
|
|
|
|
class RocProfCompute:
|
|
def __init__(self):
|
|
self.__args = None
|
|
self.__profiler_mode = None
|
|
self.__analyze_mode = None
|
|
self.__soc_name = (
|
|
set()
|
|
) # gpu name, or in case of analyze mode, all loaded gpu name(s)
|
|
self.__soc = dict() # set of key, value pairs. Where arch->OmniSoc() obj
|
|
self.__version = {
|
|
"ver": None,
|
|
"ver_pretty": None,
|
|
}
|
|
self.__options = {}
|
|
self.__supported_archs = mi_gpu_specs.get_gpu_series_dict()
|
|
self.__mspec: MachineSpecs = None # to be initalized in load_soc_specs()
|
|
setup_console_handler()
|
|
self.set_version()
|
|
self.parse_args()
|
|
self.__mode = self.__args.mode
|
|
gui_value = getattr(self.__args, "gui", None)
|
|
self.__loglevel = setup_logging_priority(
|
|
self.__args.verbose, self.__args.quiet, self.__mode, gui_value
|
|
)
|
|
setattr(self.__args, "loglevel", self.__loglevel)
|
|
set_locale_encoding()
|
|
|
|
self.sanitize()
|
|
|
|
if self.__mode == "profile":
|
|
self.detect_profiler()
|
|
elif self.__mode == "analyze":
|
|
self.detect_analyze()
|
|
|
|
console_debug("Execution mode = %s" % self.__mode)
|
|
|
|
def print_graphic(self):
|
|
"""Log program name as ascii art to terminal."""
|
|
ascii_art = r"""
|
|
__ _
|
|
_ __ ___ ___ _ __ _ __ ___ / _| ___ ___ _ __ ___ _ __ _ _| |_ ___
|
|
| '__/ _ \ / __| '_ \| '__/ _ \| |_ _____ / __/ _ \| '_ ` _ \| '_ \| | | | __/ _ \
|
|
| | | (_) | (__| |_) | | | (_) | _|_____| (_| (_) | | | | | | |_) | |_| | || __/
|
|
|_| \___/ \___| .__/|_| \___/|_| \___\___/|_| |_| |_| .__/ \__,_|\__\___|
|
|
|_| |_|
|
|
"""
|
|
print(ascii_art)
|
|
|
|
def get_mode(self):
|
|
return self.__mode
|
|
|
|
def set_version(self):
|
|
vData = get_version(config.rocprof_compute_home)
|
|
self.__version["ver"] = vData["version"]
|
|
self.__version["ver_pretty"] = get_version_display(
|
|
vData["version"], vData["sha"], vData["mode"]
|
|
)
|
|
return
|
|
|
|
def detect_profiler(self):
|
|
profiler_mode = detect_rocprof(self.__args)
|
|
if str(profiler_mode).endswith("rocprof"):
|
|
self.__profiler_mode = "rocprofv1"
|
|
elif str(profiler_mode).endswith("rocprofv2"):
|
|
self.__profiler_mode = "rocprofv2"
|
|
elif str(profiler_mode).endswith("rocprofv3"):
|
|
self.__profiler_mode = "rocprofv3"
|
|
elif str(profiler_mode) == "rocprofiler-sdk":
|
|
self.__profiler_mode = "rocprofiler-sdk"
|
|
else:
|
|
console_error(
|
|
"Incompatible profiler: %s. Supported profilers include: %s"
|
|
% (profiler_mode, get_submodules("rocprof_compute_profile"))
|
|
)
|
|
return
|
|
|
|
def detect_analyze(self):
|
|
if self.__args.gui:
|
|
self.__analyze_mode = "web_ui"
|
|
elif self.__args.tui:
|
|
self.__analyze_mode = "tui"
|
|
elif self.__args.output_format == "db":
|
|
self.__analyze_mode = "db"
|
|
else:
|
|
self.__analyze_mode = "cli"
|
|
return
|
|
|
|
def sanitize(self):
|
|
block = False
|
|
if (hasattr(self.__args, "filter_metrics") and self.__args.filter_metrics) or (
|
|
hasattr(self.__args, "filter_blocks") and self.__args.filter_blocks
|
|
):
|
|
block = True
|
|
|
|
if self.__args.list_metrics is not None and block:
|
|
console_error("Cannot use --list-metrics with --blocks")
|
|
if (
|
|
hasattr(self.__args, "list_available_metrics")
|
|
and self.__args.list_available_metrics
|
|
) and block:
|
|
console_error("Cannot use --list-available-metrics with --blocks")
|
|
|
|
@demarcate
|
|
def load_soc_specs(self, sysinfo: dict = None):
|
|
"""Load OmniSoC instance for RocProfCompute run"""
|
|
self.__mspec = generate_machine_specs(self.__args, sysinfo)
|
|
if self.__args.specs:
|
|
print(self.__mspec)
|
|
sys.exit(0)
|
|
|
|
arch = self.__mspec.gpu_arch
|
|
|
|
soc_module = importlib.import_module("rocprof_compute_soc.soc_" + arch)
|
|
soc_class = getattr(soc_module, arch + "_soc")
|
|
self.__soc[arch] = soc_class(self.__args, self.__mspec)
|
|
return
|
|
|
|
def parse_args(self):
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"Command line interface for AMD's GPU profiler, ROCm Compute Profiler"
|
|
),
|
|
prog="tool",
|
|
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
|
|
prog, max_help_position=30
|
|
),
|
|
usage="rocprof-compute [mode] [options]",
|
|
)
|
|
omniarg_parser(
|
|
parser, config.rocprof_compute_home, self.__supported_archs, self.__version
|
|
)
|
|
self.__args = parser.parse_args()
|
|
|
|
if (
|
|
"format_rocprof_output" in self.__args
|
|
and self.__args.format_rocprof_output != "rocpd"
|
|
):
|
|
console_warning(
|
|
(
|
|
f"The option --format-rocprof-output currently set to "
|
|
f"{self.__args.format_rocprof_output} will default to rocpd "
|
|
"in a future release."
|
|
)
|
|
)
|
|
|
|
if self.__args.mode == None:
|
|
if self.__args.specs:
|
|
print(generate_machine_specs(self.__args))
|
|
sys.exit(0)
|
|
elif self.__args.list_metrics is not None:
|
|
self.list_metrics()
|
|
sys.exit(0)
|
|
elif self.__args.config_dir:
|
|
parser.print_help(sys.stderr)
|
|
console_error(
|
|
"rocprof-compute requires you to pass --list-metrics "
|
|
"with --config-dir."
|
|
)
|
|
parser.print_help(sys.stderr)
|
|
console_error(
|
|
"rocprof-compute requires you to pass a valid mode. Detected None."
|
|
)
|
|
elif self.__args.mode == "profile":
|
|
# FIXME:
|
|
# Might want to get host name from detected spec
|
|
if self.__args.subpath == "node_name":
|
|
self.__args.path = str(
|
|
Path(self.__args.path).joinpath(socket.gethostname())
|
|
)
|
|
elif self.__args.subpath == "gpu_model":
|
|
self.__args.path = str(
|
|
Path(self.__args.path).joinpath(self.__mspec.gpu_model)
|
|
)
|
|
|
|
p = Path(self.__args.path)
|
|
if not p.exists():
|
|
try:
|
|
p.mkdir(parents=True, exist_ok=False)
|
|
except FileExistsError:
|
|
console_error("Directory already exists.")
|
|
|
|
elif self.__args.mode == "analyze":
|
|
# block all filters during spatial-multiplexing
|
|
if self.__args.spatial_multiplexing:
|
|
self.__args.gpu_id = None
|
|
self.__args.gpu_kernel = None
|
|
self.__args.gpu_dispatch_id = None
|
|
self.__args.nodes = None
|
|
|
|
return
|
|
|
|
@demarcate
|
|
def list_metrics(self):
|
|
self.load_soc_specs()
|
|
|
|
for_current_arch = False
|
|
if (
|
|
hasattr(self.__args, "list_available_metrics")
|
|
and self.__args.list_available_metrics
|
|
):
|
|
for_current_arch = True
|
|
|
|
arch = (
|
|
self.__mspec.gpu_arch
|
|
if (for_current_arch or self.__args.list_metrics is None)
|
|
else self.__args.list_metrics
|
|
)
|
|
if arch in self.__supported_archs.keys():
|
|
ac = schema.ArchConfig()
|
|
config_dir = Path(self.__args.config_dir)
|
|
ac.panel_configs = file_io.load_panel_configs([config_dir.joinpath(arch)])
|
|
sys_info = (
|
|
self.__mspec.get_class_members().iloc[0] if for_current_arch else None
|
|
)
|
|
parser.build_dfs(archConfigs=ac, filter_metrics=[], sys_info=sys_info)
|
|
for key, value in ac.metric_list.items():
|
|
prefix = ""
|
|
if "." not in str(key):
|
|
prefix = ""
|
|
elif str(key).count(".") == 1:
|
|
prefix = "\t"
|
|
else:
|
|
prefix = "\t\t"
|
|
print(prefix + key, "->", value)
|
|
sys.exit(0)
|
|
else:
|
|
console_error("Unsupported arch")
|
|
|
|
@demarcate
|
|
def list_sets(self):
|
|
sets_info = parse_sets_yaml(self.__mspec.gpu_arch)
|
|
|
|
if not sets_info:
|
|
console_error("No sets configuration found.")
|
|
|
|
print("\nAvailable Sets:")
|
|
print("=" * 115)
|
|
|
|
# Print header
|
|
print(
|
|
f"{'Set Option':<35} {'Set Title':<35}"
|
|
f" {'Metric Name':<30} {'Metric ID':<10}"
|
|
)
|
|
print("-" * 115)
|
|
|
|
# Print data grouped by set
|
|
for set_option, set_data in sets_info.items():
|
|
title = set_data.get("title", set_option)
|
|
metrics = set_data.get("metric", [])
|
|
|
|
first_row = True
|
|
for metric in metrics:
|
|
if isinstance(metric, dict) and metric:
|
|
metric_id = next(iter(metric.keys()))
|
|
metric_name = next(iter(metric.values()))
|
|
|
|
# Only show set info on first row of each set
|
|
set_display = set_option if first_row else ""
|
|
title_display = title if first_row else ""
|
|
|
|
print(
|
|
f"{set_display:<35} {title_display:<35}"
|
|
f" {metric_name:<30} {metric_id:<10}"
|
|
)
|
|
first_row = False
|
|
# Empty line between sets
|
|
print()
|
|
|
|
print("Usage Examples:")
|
|
if sets_info:
|
|
first_set = next(iter(sets_info.keys()))
|
|
print(f" rocprof-compute profile --set {first_set} # Profile this set")
|
|
print(" rocprof-compute profile --list-sets # Show this help")
|
|
print()
|
|
|
|
sys.exit(0)
|
|
|
|
@demarcate
|
|
def run_profiler(self):
|
|
self.print_graphic()
|
|
self.load_soc_specs()
|
|
|
|
if self.__args.list_metrics is not None or self.__args.list_available_metrics:
|
|
self.list_metrics()
|
|
elif self.__args.list_sets:
|
|
self.list_sets()
|
|
elif self.__args.name is None:
|
|
sys.exit("Either --list-name or --name is required")
|
|
|
|
if self.__args.name.find("/") != -1:
|
|
console_error("'/' not permitted in profile name")
|
|
|
|
# FIXME:
|
|
# Changing default path should be done at the end of arg parsing stage,
|
|
# unless there is a specific reason to do here.
|
|
|
|
# Update default path
|
|
if self.__args.path == str(Path(os.getcwd()).joinpath("workloads")):
|
|
self.__args.path = str(
|
|
Path(self.__args.path).joinpath(
|
|
self.__args.name, self.__mspec.gpu_model
|
|
)
|
|
)
|
|
|
|
# instantiate desired profiler
|
|
if self.__profiler_mode == "rocprofv1":
|
|
from rocprof_compute_profile.profiler_rocprof_v1 import rocprof_v1_profiler
|
|
|
|
profiler = rocprof_v1_profiler(
|
|
self.__args,
|
|
self.__profiler_mode,
|
|
self.__soc[self.__mspec.gpu_arch],
|
|
self.__supported_archs,
|
|
)
|
|
elif self.__profiler_mode == "rocprofv2":
|
|
from rocprof_compute_profile.profiler_rocprof_v2 import rocprof_v2_profiler
|
|
|
|
profiler = rocprof_v2_profiler(
|
|
self.__args,
|
|
self.__profiler_mode,
|
|
self.__soc[self.__mspec.gpu_arch],
|
|
self.__supported_archs,
|
|
)
|
|
elif self.__profiler_mode == "rocprofv3":
|
|
from rocprof_compute_profile.profiler_rocprof_v3 import rocprof_v3_profiler
|
|
|
|
profiler = rocprof_v3_profiler(
|
|
self.__args,
|
|
self.__profiler_mode,
|
|
self.__soc[self.__mspec.gpu_arch],
|
|
self.__supported_archs,
|
|
)
|
|
elif self.__profiler_mode == "rocprofiler-sdk":
|
|
from rocprof_compute_profile.profiler_rocprofiler_sdk import (
|
|
rocprofiler_sdk_profiler,
|
|
)
|
|
|
|
profiler = rocprofiler_sdk_profiler(
|
|
self.__args,
|
|
self.__profiler_mode,
|
|
self.__soc[self.__mspec.gpu_arch],
|
|
self.__supported_archs,
|
|
)
|
|
else:
|
|
console_error("Unsupported profiler")
|
|
|
|
# -----------------------
|
|
# run profiling workflow
|
|
# -----------------------
|
|
|
|
self.__soc[self.__mspec.gpu_arch].profiling_setup()
|
|
# Write profiling configuration as yaml file
|
|
with open(Path(self.__args.path).joinpath("profiling_config.yaml"), "w") as f:
|
|
args_dict = vars(self.__args)
|
|
args_dict["config_dir"] = str(args_dict["config_dir"])
|
|
yaml.dump(args_dict, f)
|
|
# enable file-based logging
|
|
setup_file_handler(self.__args.loglevel, self.__args.path)
|
|
|
|
profiler.pre_processing()
|
|
console_debug('starting "run_profiling" and about to start rocprof\'s workload')
|
|
time_start_prof = time.time()
|
|
profiler.run_profiling(self.__version["ver"], config.PROJECT_NAME)
|
|
time_end_prof = time.time()
|
|
console_debug(
|
|
(
|
|
'finished "run_profiling" and finished rocprof\'s workload, '
|
|
"time taken was {} m {} sec"
|
|
).format(
|
|
int((time_end_prof - time_start_prof) / 60),
|
|
str((time_end_prof - time_start_prof) % 60),
|
|
)
|
|
)
|
|
profiler.post_processing()
|
|
time_end_post = time.time()
|
|
console_debug(
|
|
'time taken for "post_processing" was {} seconds'.format(
|
|
int(time_end_post - time_end_prof)
|
|
)
|
|
)
|
|
self.__soc[self.__mspec.gpu_arch].post_profiling()
|
|
|
|
return
|
|
|
|
@demarcate
|
|
def update_db(self):
|
|
self.print_graphic()
|
|
|
|
console_warning(
|
|
"Database update mode is deprecated and will "
|
|
"be removed in a future release "
|
|
"and no fixes will be made for this mode."
|
|
)
|
|
|
|
from utils.db_connector import DatabaseConnector
|
|
|
|
db_connection = DatabaseConnector(self.__args)
|
|
|
|
# -----------------------
|
|
# run database workflow
|
|
# -----------------------
|
|
db_connection.pre_processing()
|
|
if self.__args.upload:
|
|
db_connection.db_import()
|
|
else:
|
|
db_connection.db_remove()
|
|
|
|
return
|
|
|
|
@demarcate
|
|
def run_analysis(self):
|
|
self.print_graphic()
|
|
|
|
console_log("Analysis mode = %s" % self.__analyze_mode)
|
|
|
|
if self.__analyze_mode == "cli":
|
|
from rocprof_compute_analyze.analysis_cli import cli_analysis
|
|
|
|
analyzer = cli_analysis(self.__args, self.__supported_archs)
|
|
elif self.__analyze_mode == "web_ui":
|
|
from rocprof_compute_analyze.analysis_webui import webui_analysis
|
|
|
|
analyzer = webui_analysis(self.__args, self.__supported_archs)
|
|
elif self.__analyze_mode == "tui":
|
|
from rocprof_compute_tui.tui_app import run_tui
|
|
|
|
run_tui(self.__args, self.__supported_archs)
|
|
return
|
|
elif self.__analyze_mode == "db":
|
|
from rocprof_compute_analyze.analysis_db import db_analysis
|
|
|
|
analyzer = db_analysis(self.__args, self.__supported_archs)
|
|
else:
|
|
console_error("Unsupported analysis mode -> %s" % self.__analyze_mode)
|
|
|
|
# -----------------------
|
|
# run analysis workflow
|
|
# -----------------------
|
|
analyzer.sanitize()
|
|
|
|
# Load required SoC(s) from input
|
|
for d in analyzer.get_args().path:
|
|
# FIXME
|
|
# sys_info = pd.read_csv(Path(d[0], "sysinfo.csv"))
|
|
sysinfo_path = (
|
|
Path(d[0])
|
|
if analyzer.get_args().nodes is None
|
|
and analyzer.get_args().spatial_multiplexing is not True
|
|
else file_io.find_1st_sub_dir(d[0])
|
|
)
|
|
sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
|
|
|
|
sys_info = sys_info.to_dict("list")
|
|
sys_info = {key: value[0] for key, value in sys_info.items()}
|
|
self.load_soc_specs(sys_info)
|
|
|
|
analyzer.set_soc(self.__soc)
|
|
analyzer.pre_processing()
|
|
analyzer.run_analysis()
|
|
|
|
return
|