[rocprofiler-compute] Refactor to add type annotation and misc (#787)
This commit is contained in:
@@ -11,7 +11,7 @@ repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version. Check https://github.com/astral-sh/ruff-pre-commit#version-compatibility,
|
||||
# for the latest ruff version supported by the hook.
|
||||
rev: v0.12.7
|
||||
rev: v0.12.12
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -82,6 +82,41 @@ ruff format .
|
||||
|
||||
-----
|
||||
|
||||
```markdown
|
||||
## Type Annotations
|
||||
|
||||
This project enforces type annotations using Ruff's `flake8-annotations` rules. All new code must include proper type annotations.
|
||||
|
||||
### Requirements
|
||||
|
||||
- All function arguments must have type annotations (except `self` and `cls`)
|
||||
- All function return types must be annotated
|
||||
- Class attributes should have type annotations where applicable
|
||||
|
||||
### Examples
|
||||
|
||||
```python
|
||||
# Good - properly annotated
|
||||
def process_kernel_data(kernel_name: str, metrics: list[float]) -> dict[str, Any]:
|
||||
"""Process kernel performance metrics."""
|
||||
return {"kernel": kernel_name, "avg": sum(metrics) / len(metrics)}
|
||||
|
||||
# Bad - missing annotations (will be caught by Ruff)
|
||||
def process_kernel_data(kernel_name, metrics):
|
||||
return {"kernel": kernel_name, "avg": sum(metrics) / len(metrics)}
|
||||
```
|
||||
|
||||
### Checking Type Annotations
|
||||
|
||||
To specifically check for missing type annotations:
|
||||
|
||||
```bash
|
||||
ruff check --select ANN .
|
||||
```
|
||||
|
||||
For existing code, we're gradually adding type annotations. When modifying existing functions, please add type annotations to any code you touch.
|
||||
```
|
||||
|
||||
## Disabling Formatting for Specific Sections
|
||||
|
||||
There may be instances where you need to disable Ruff's formatting on a specific block of code. You can do this using special comments:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
|
||||
@@ -60,7 +60,7 @@ exclude_patterns = ["archive", "*/includes"]
|
||||
html_static_path = ["sphinx/static/css"]
|
||||
html_css_files = ["o_custom.css"]
|
||||
|
||||
with open("data/metrics_description.yaml", "r") as f:
|
||||
with open("data/metrics_description.yaml") as f:
|
||||
metrics_data = yaml.safe_load(f)
|
||||
jinja_contexts = {
|
||||
"wavefront-launch-stats": {
|
||||
|
||||
@@ -21,15 +21,29 @@ extend-exclude = [
|
||||
".misc",
|
||||
"external",
|
||||
"build-rocprof_compute",
|
||||
# deprecated files
|
||||
"src/rocprof_compute_profile/profiler_rocprof_v1.py",
|
||||
"src/rocprof_compute_profile/profiler_rocprof_v2.py",
|
||||
"src/rocprof_compute_analyze/analysis_db.py",
|
||||
"src/utils/db_connector.py",
|
||||
# WIP files
|
||||
"src/rocprof_compute_tui/widgets/splitter.py"
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Enable Pyflakes (F), pycodestyle (E, W for PEP8), and isort (I) rules.
|
||||
select = ["E", "W", "F", "I"]
|
||||
ignore = ["E713", "E711"]
|
||||
# Enable Pyflakes (F), pycodestyle (E, W for PEP8), aisort (I),
|
||||
# type annotation (ANN), and f-string (UP) rules.
|
||||
select = ["E", "W", "F", "I", "ANN","UP"]
|
||||
ignore = ["E713", "E711", "ANN001", "ANN401", "UP045"]
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
[tool.ruff.lint.flake8-annotations]
|
||||
allow-star-arg-any = true #allow Any for *args
|
||||
ignore-fully-untyped = false #require type annotations
|
||||
suppress-dummy-args = true #don't require annotation for "_" arguments
|
||||
suppress-none-returning = false #require explicit None return types
|
||||
|
||||
[tool.ruff.format]
|
||||
preview = true
|
||||
|
||||
|
||||
@@ -27,18 +27,22 @@ import argparse
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def print_avail_arch(avail_arch: list):
|
||||
def print_avail_arch(avail_arch: list[str]) -> str:
|
||||
ret_str = "List all available metrics for analysis on specified arch:"
|
||||
for arch in avail_arch:
|
||||
ret_str += "\n {}".format(arch)
|
||||
ret_str += f"\n {arch}"
|
||||
return ret_str
|
||||
|
||||
|
||||
def add_general_group(
|
||||
parser, rocprof_compute_version, supported_archs, rocprof_compute_home
|
||||
):
|
||||
parser: argparse.ArgumentParser,
|
||||
rocprof_compute_home: Path,
|
||||
supported_archs: dict[str, str],
|
||||
rocprof_compute_version: dict[str, Optional[str]],
|
||||
) -> None:
|
||||
general_group = parser.add_argument_group("General Options")
|
||||
|
||||
general_group.add_argument(
|
||||
@@ -62,25 +66,28 @@ def add_general_group(
|
||||
dest="list_metrics",
|
||||
metavar="",
|
||||
choices=supported_archs.keys(), # ["gfx908", "gfx90a"],
|
||||
help=print_avail_arch(supported_archs.keys()),
|
||||
help=print_avail_arch(list(supported_archs.keys())),
|
||||
)
|
||||
general_group.add_argument(
|
||||
"--config-dir",
|
||||
dest="config_dir",
|
||||
metavar="",
|
||||
help="Specify the directory of customized report section configs.",
|
||||
default=rocprof_compute_home.joinpath("rocprof_compute_soc/analysis_configs/"),
|
||||
default=rocprof_compute_home / "rocprof_compute_soc/analysis_configs/",
|
||||
)
|
||||
# Nowhere to load specs from in db mode
|
||||
if "database" not in parser.usage:
|
||||
if parser.usage and "database" not in parser.usage:
|
||||
general_group.add_argument(
|
||||
"-s", "--specs", action="store_true", help="Print system specs and exit."
|
||||
)
|
||||
|
||||
|
||||
def omniarg_parser(
|
||||
parser, rocprof_compute_home, supported_archs, rocprof_compute_version
|
||||
):
|
||||
parser: argparse.ArgumentParser,
|
||||
rocprof_compute_home: Path,
|
||||
supported_archs: dict[str, str],
|
||||
rocprof_compute_version: dict[str, Optional[str]],
|
||||
) -> None:
|
||||
# -----------------------------------------
|
||||
# Parse arguments (dependent on mode)
|
||||
# -----------------------------------------
|
||||
@@ -88,7 +95,7 @@ def omniarg_parser(
|
||||
## General Command Line Options
|
||||
## ----------------------------
|
||||
add_general_group(
|
||||
parser, rocprof_compute_version, supported_archs, rocprof_compute_home
|
||||
parser, rocprof_compute_home, supported_archs, rocprof_compute_version
|
||||
)
|
||||
parser._positionals.title = "Modes"
|
||||
parser._optionals.title = "Help"
|
||||
@@ -104,8 +111,7 @@ def omniarg_parser(
|
||||
help="Profile the target application",
|
||||
usage="""
|
||||
|
||||
`rocprof-compute profile --name <workload_name>
|
||||
[profile options] [roofline options] -- <profile_cmd>`
|
||||
`rocprof-compute profile --name <workload_name> [profile options] [roofline options] -- <workload_cmd>`
|
||||
|
||||
---------------------------------------------------------------------------------
|
||||
Examples:
|
||||
@@ -115,7 +121,7 @@ Examples:
|
||||
\trocprof-compute profile -n vcopy_disp -d 0 -- ./vcopy -n 1048576 -b 256
|
||||
\trocprof-compute profile -n vcopy_roof --roof-only -- ./vcopy -n 1048576 -b 256
|
||||
---------------------------------------------------------------------------------
|
||||
""",
|
||||
""", # noqa: E501
|
||||
prog="tool",
|
||||
allow_abbrev=False,
|
||||
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
|
||||
@@ -125,7 +131,7 @@ Examples:
|
||||
profile_parser._optionals.title = "Help"
|
||||
|
||||
add_general_group(
|
||||
profile_parser, rocprof_compute_version, supported_archs, rocprof_compute_home
|
||||
profile_parser, rocprof_compute_home, supported_archs, rocprof_compute_version
|
||||
)
|
||||
profile_group = profile_parser.add_argument_group("Profile Options")
|
||||
roofline_group = profile_parser.add_argument_group("Standalone Roofline Options")
|
||||
@@ -147,11 +153,10 @@ Examples:
|
||||
metavar="",
|
||||
type=str,
|
||||
dest="path",
|
||||
default=str(Path(os.getcwd()).joinpath("workloads")),
|
||||
default=str(Path(os.getcwd()) / "workloads"),
|
||||
required=False,
|
||||
help=(
|
||||
"\t\t\tSpecify path to save workload.\n\t\t\t"
|
||||
"(DEFAULT: {}/workloads/<name>)".format(os.getcwd())
|
||||
f"\t\t\tSpecify path to save workload.\n\t\t\t(DEFAULT: {os.getcwd()}/workloads/<name>)" # noqa: E501
|
||||
),
|
||||
)
|
||||
profile_group.add_argument(
|
||||
@@ -207,10 +212,9 @@ Examples:
|
||||
help="\t\t\tDispatch ID filtering.",
|
||||
)
|
||||
|
||||
def validate_block(value):
|
||||
def validate_block(value: str) -> str:
|
||||
# Metric id is of the form I or I.I or I.I.I where I is two digit number.
|
||||
metric_id_pattern = re.compile(r"^\d{1,2}(?:\.\d{1,2}){0,2}$")
|
||||
if metric_id_pattern.match(value):
|
||||
if re.compile(r"^\d{1,2}(?:\.\d{1,2}){0,2}$").match(value):
|
||||
return value
|
||||
raise argparse.ArgumentTypeError(f"Invalid metric id: {value}")
|
||||
|
||||
@@ -252,7 +256,6 @@ Examples:
|
||||
"\t\t\tCannot be used with --block or --roof-only"
|
||||
),
|
||||
)
|
||||
|
||||
profile_group.add_argument(
|
||||
"--join-type",
|
||||
metavar="",
|
||||
@@ -298,7 +301,6 @@ Examples:
|
||||
default="csv",
|
||||
help="\t\t\tSet the format of output file of rocprof.",
|
||||
)
|
||||
|
||||
profile_group.add_argument(
|
||||
"--pc-sampling-method",
|
||||
required=False,
|
||||
@@ -310,7 +312,6 @@ Examples:
|
||||
"Support stochastic only >= MI300"
|
||||
),
|
||||
)
|
||||
|
||||
profile_group.add_argument(
|
||||
"--pc-sampling-interval",
|
||||
required=False,
|
||||
@@ -324,7 +325,6 @@ Examples:
|
||||
"(DEFAULT: 1048576)."
|
||||
),
|
||||
)
|
||||
|
||||
profile_group.add_argument(
|
||||
"--rocprofiler-sdk-library-path",
|
||||
type=str,
|
||||
@@ -400,7 +400,6 @@ Examples:
|
||||
action="store_true",
|
||||
help="\t\t\tInclude kernel names in roofline plot.",
|
||||
)
|
||||
|
||||
roofline_group.add_argument(
|
||||
"-R",
|
||||
"--roofline-data-type",
|
||||
@@ -468,12 +467,10 @@ Examples:
|
||||
|
||||
\n\n-------------------------------------------------------------------------------
|
||||
\nExamples:
|
||||
\n\trocprof-compute database --import -H pavii1 -u temp -t asw -w "
|
||||
"workloads/vcopy/mi200/"
|
||||
"\n\trocprof-compute database --remove -H pavii1 -u temp -w "
|
||||
"rocprofiler-compute_asw_sample_mi200"
|
||||
"\n-------------------------------------------------------------------------------\n"
|
||||
""",
|
||||
\n\trocprof-compute database --import -H pavii1 -u temp -t asw -w "workloads/vcopy/mi200/"
|
||||
\n\trocprof-compute database --remove -H pavii1 -u temp -w "rocprofiler-compute_asw_sample_mi200"
|
||||
\n-------------------------------------------------------------------------------\n
|
||||
""", # noqa: E501
|
||||
prog="tool",
|
||||
allow_abbrev=False,
|
||||
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
|
||||
@@ -483,7 +480,7 @@ Examples:
|
||||
db_parser._optionals.title = "Help"
|
||||
|
||||
add_general_group(
|
||||
db_parser, rocprof_compute_version, supported_archs, rocprof_compute_home
|
||||
db_parser, rocprof_compute_home, supported_archs, rocprof_compute_version
|
||||
)
|
||||
interaction_group = db_parser.add_argument_group("Interaction Type")
|
||||
connection_group = db_parser.add_argument_group("Connection Options")
|
||||
@@ -494,7 +491,7 @@ Examples:
|
||||
required=False,
|
||||
dest="upload",
|
||||
action="store_true",
|
||||
help="\t\t\t\tImport workload to rocprofiler-compute DB",
|
||||
help="\t\tImport workload to rocprofiler-compute DB",
|
||||
)
|
||||
interaction_group.add_argument(
|
||||
"-r",
|
||||
@@ -502,7 +499,7 @@ Examples:
|
||||
required=False,
|
||||
dest="remove",
|
||||
action="store_true",
|
||||
help="\t\t\t\tRemove a workload from rocprofiler-compute DB",
|
||||
help="\t\tRemove a workload from rocprofiler-compute DB",
|
||||
)
|
||||
|
||||
connection_group.add_argument(
|
||||
@@ -510,14 +507,14 @@ Examples:
|
||||
"--host",
|
||||
required=True,
|
||||
metavar="",
|
||||
help="\t\t\t\tName or IP address of the server host.",
|
||||
help="\t\tName or IP address of the server host.",
|
||||
)
|
||||
connection_group.add_argument(
|
||||
"-P",
|
||||
"--port",
|
||||
required=False,
|
||||
metavar="",
|
||||
help="\t\t\t\tTCP/IP Port. (DEFAULT: 27018)",
|
||||
help="\t\tTCP/IP Port. (DEFAULT: 27018)",
|
||||
default=27018,
|
||||
)
|
||||
connection_group.add_argument(
|
||||
@@ -525,17 +522,17 @@ Examples:
|
||||
"--username",
|
||||
required=True,
|
||||
metavar="",
|
||||
help="\t\t\t\tUsername for authentication.",
|
||||
help="\t\tUsername for authentication.",
|
||||
)
|
||||
connection_group.add_argument(
|
||||
"-p",
|
||||
"--password",
|
||||
metavar="",
|
||||
help="\t\t\t\tThe user's password. (will be requested later if it's not set)",
|
||||
help="\t\tThe user's password. (will be requested later if it's not set)",
|
||||
default="",
|
||||
)
|
||||
connection_group.add_argument(
|
||||
"-t", "--team", required=False, metavar="", help="\t\t\t\tSpecify Team prefix."
|
||||
"-t", "--team", required=False, metavar="", help="\t\tSpecify Team prefix."
|
||||
)
|
||||
connection_group.add_argument(
|
||||
"-w",
|
||||
@@ -544,8 +541,7 @@ Examples:
|
||||
metavar="",
|
||||
dest="workload",
|
||||
help=(
|
||||
"\t\t\t\tSpecify name of workload (to remove) or path to workload "
|
||||
"(to import)"
|
||||
"\t\tSpecify name of workload (to remove) or path to workload (to import)"
|
||||
),
|
||||
)
|
||||
connection_group.add_argument(
|
||||
@@ -585,7 +581,7 @@ Examples:
|
||||
analyze_parser._optionals.title = "Help"
|
||||
|
||||
add_general_group(
|
||||
analyze_parser, rocprof_compute_version, supported_archs, rocprof_compute_home
|
||||
analyze_parser, rocprof_compute_home, supported_archs, rocprof_compute_version
|
||||
)
|
||||
analyze_group = analyze_parser.add_argument_group("Analyze Options")
|
||||
analyze_advanced_group = analyze_parser.add_argument_group("Advanced Options")
|
||||
@@ -728,7 +724,6 @@ Examples:
|
||||
"\t\t\t I64\n\t\t\t "
|
||||
),
|
||||
)
|
||||
|
||||
analyze_group.add_argument(
|
||||
"--pc-sampling-sorting-type",
|
||||
required=False,
|
||||
@@ -806,7 +801,7 @@ Examples:
|
||||
nargs="+",
|
||||
help=(
|
||||
"\t\tSpecify which hidden column names should be included in cli output.\n"
|
||||
"\t\tFor example, to show 'Description' column which is hidden by "
|
||||
'\t\tFor example, to show "Description" column which is hidden by '
|
||||
"default in cli output,\n"
|
||||
"\t\tuse the option --include-cols Description."
|
||||
),
|
||||
@@ -837,8 +832,7 @@ Examples:
|
||||
type=str,
|
||||
metavar="",
|
||||
help="\t\tSpecify the specs to correct. e.g. "
|
||||
"--specs-correction='specname1:specvalue1,"
|
||||
"specname2:specvalue2'",
|
||||
'--specs-correction="specname1:specvalue1,specname2:specvalue2"',
|
||||
)
|
||||
analyze_advanced_group.add_argument(
|
||||
"--list-nodes",
|
||||
|
||||
@@ -25,11 +25,8 @@
|
||||
# SOFTWARE.
|
||||
##############################################################################el
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# import logging
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
@@ -38,112 +35,109 @@ try:
|
||||
from rocprof_compute_base import RocProfCompute
|
||||
from utils.logger import console_error
|
||||
except ImportError as e:
|
||||
print (f"{e}")
|
||||
# In wheel package, softlink is not supported.
|
||||
# rocprof-compute will get installed in bin and libexec/rocprofiler-compute.
|
||||
# When invoked from bin folder, the extra paths are required to import the dependent scripts
|
||||
# When invoked from bin folder, the extra paths are required to import the
|
||||
# dependent scripts
|
||||
|
||||
try:
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
additional_path = f"{current_path}/../libexec/rocprofiler-compute"
|
||||
sys.path.append(os.path.abspath(additional_path))
|
||||
current_path = Path(__file__).resolve().parent
|
||||
additional_path = current_path / "../libexec/rocprofiler-compute"
|
||||
sys.path.append(str(additional_path.resolve()))
|
||||
|
||||
from importlib import metadata
|
||||
|
||||
from rocprof_compute_base import RocProfCompute
|
||||
from utils.utils import console_error
|
||||
except ImportError as e:
|
||||
# print("Failed to import required modules: " + str(e))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def verify_deps_version(localVer, desiredVer, operator):
|
||||
def check_version(local_ver, desired_ver, operator) -> bool:
|
||||
"""Check package version strings with simple operators used in companion
|
||||
requirements.txt file"""
|
||||
if operator == "==":
|
||||
return localVer == desiredVer
|
||||
elif operator == ">=":
|
||||
return localVer >= desiredVer
|
||||
elif operator == "<=":
|
||||
return localVer <= desiredVer
|
||||
elif operator == ">":
|
||||
return localVer > desiredVer
|
||||
elif operator == "<":
|
||||
return localVer < desiredVer
|
||||
else:
|
||||
return True
|
||||
ops = {
|
||||
"==": lambda loc, des: loc == des,
|
||||
">=": lambda loc, des: loc >= des,
|
||||
"<=": lambda loc, des: loc <= des,
|
||||
">": lambda loc, des: loc > des,
|
||||
"<": lambda loc, des: loc < des,
|
||||
}
|
||||
return ops.get(operator, lambda loc, des: True)(local_ver, desired_ver)
|
||||
|
||||
|
||||
def verify_deps():
|
||||
def verify_deps() -> None:
|
||||
"""Utility to read library dependencies from requirements.txt and endeavor
|
||||
to load them within current execution environment.
|
||||
Used in top-level rocprofiler-compute to provide error messages if necessary
|
||||
dependencies are not available."""
|
||||
|
||||
# Check which version of python is being used
|
||||
if sys.version_info[0] < 3 or (sys.version_info[0] == 3
|
||||
and sys.version_info[1] < 8):
|
||||
print("[ERROR] Python 3.8 or higher is required to run rocprofiler-compute."
|
||||
f" The current version is {sys.version_info[0]}.{sys.version_info[1]}.")
|
||||
if sys.version_info < (3, 8):
|
||||
print(
|
||||
f"[ERROR] Python 3.8 or higher is required to run rocprofiler-compute. "
|
||||
f"The current version is {sys.version_info[0]}.{sys.version_info[1]}."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
bindir = str(Path(__file__).resolve().parent)
|
||||
depsLocation = ["requirements.txt", "../requirements.txt"]
|
||||
deps_locations = ["requirements.txt", "../requirements.txt"]
|
||||
|
||||
for location in depsLocation:
|
||||
checkFile = str(Path(bindir).joinpath(location))
|
||||
if Path(checkFile).exists():
|
||||
with open(checkFile, "r", encoding="utf-8") as file_in:
|
||||
dependencies = file_in.read().splitlines()
|
||||
for location in deps_locations:
|
||||
check_file = Path(bindir).joinpath(location)
|
||||
if check_file.exists():
|
||||
dependencies = check_file.read_text(encoding="utf-8").splitlines()
|
||||
|
||||
error = False
|
||||
version_pattern = r"^([^=<>]+)([=<>]+)(.*)$"
|
||||
version_pattern = re.compile(r"^([^=<>]+)([=<>]+)(.*)$")
|
||||
|
||||
for dependency in dependencies:
|
||||
desiredVersion = None
|
||||
match = re.match(version_pattern, dependency)
|
||||
match = version_pattern.match(dependency)
|
||||
if match:
|
||||
package = match.group(1)
|
||||
operator = match.group(2) or None
|
||||
desiredVersion = match.group(3) or None
|
||||
package, operator, desired_version = match.groups()
|
||||
else:
|
||||
package = dependency
|
||||
package, operator, desired_version = dependency, None, None
|
||||
|
||||
try:
|
||||
localVersion = metadata.distribution(package).version
|
||||
local_version = metadata.distribution(package).version
|
||||
except metadata.PackageNotFoundError:
|
||||
error = True
|
||||
print(f"[ERROR] The '{dependency}' package was not found "
|
||||
"in the current execution environment.")
|
||||
print(
|
||||
f"[ERROR] The '{dependency}' package was not found "
|
||||
"in the current execution environment."
|
||||
)
|
||||
continue
|
||||
|
||||
# check version requirement
|
||||
if not error:
|
||||
if desiredVersion:
|
||||
if not verify_deps_version(localVersion, desiredVersion,
|
||||
operator):
|
||||
print(
|
||||
f"[ERROR] the '{dependency}' distribution does "
|
||||
"not meet version requirements to use rocprofiler-compute."
|
||||
)
|
||||
print(" --> version installed :", localVersion)
|
||||
error = True
|
||||
# Check version requirement
|
||||
if desired_version and not check_version(
|
||||
local_version, desired_version, operator
|
||||
):
|
||||
print(
|
||||
f"[ERROR] the '{dependency}' distribution does not meet "
|
||||
"version requirements to use rocprofiler-compute."
|
||||
)
|
||||
print(f" --> version installed : {local_version}")
|
||||
error = True
|
||||
|
||||
if error:
|
||||
print("")
|
||||
print("Please verify all of the python dependencies called out "
|
||||
"in the requirements file")
|
||||
print(
|
||||
"\nPlease verify all of the python dependencies called out "
|
||||
"in the requirements file"
|
||||
)
|
||||
print("are installed locally prior to running rocprofiler-compute.")
|
||||
print("")
|
||||
print(f"See: {checkFile}")
|
||||
print(f"\nSee: {check_file}")
|
||||
sys.exit(1)
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
"""Main function for rocprofiler-compute"""
|
||||
|
||||
# verify required python dependencies
|
||||
verify_deps()
|
||||
|
||||
rocprof_compute = RocProfCompute()
|
||||
|
||||
mode = rocprof_compute.get_mode()
|
||||
|
||||
# major rocprofiler-compute execution modes
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import re
|
||||
import sys
|
||||
@@ -30,10 +31,12 @@ import textwrap
|
||||
from abc import abstractmethod
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TextIO
|
||||
|
||||
import pandas as pd
|
||||
|
||||
import config
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils import file_io, parser, schema
|
||||
from utils.logger import (
|
||||
console_debug,
|
||||
@@ -44,146 +47,104 @@ from utils.logger import (
|
||||
)
|
||||
from utils.utils import get_uuid, is_workload_empty, merge_counters_spatial_multiplex
|
||||
|
||||
# the build-in config to list kernel names purpose only
|
||||
TOP_STATS_BUILD_IN_CONFIG: OrderedDict[int, dict[str, Any]] = OrderedDict([
|
||||
(
|
||||
0,
|
||||
{
|
||||
"id": 0,
|
||||
"title": "Top Kernels",
|
||||
"data source": [
|
||||
{"raw_csv_table": {"id": 1, "source": "pmc_kernel_top.csv"}}
|
||||
],
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Dispatch List",
|
||||
"data source": [
|
||||
{"raw_csv_table": {"id": 2, "source": "pmc_dispatch_info.csv"}}
|
||||
],
|
||||
},
|
||||
),
|
||||
])
|
||||
|
||||
|
||||
class OmniAnalyze_Base:
|
||||
def __init__(self, args, supported_archs):
|
||||
def __init__(
|
||||
self, args: argparse.Namespace, supported_archs: dict[str, str]
|
||||
) -> None:
|
||||
self.__args = args
|
||||
self._runs = OrderedDict()
|
||||
self._arch_configs = {}
|
||||
self._profiling_config = dict()
|
||||
self._runs: OrderedDict[str, schema.Workload] = OrderedDict()
|
||||
self._arch_configs: dict[str, schema.ArchConfig] = {}
|
||||
self._profiling_config: dict[str, Any] = {}
|
||||
self.__supported_archs = supported_archs
|
||||
self._output = None
|
||||
self.__socs: dict = None # available OmniSoC objs
|
||||
self._output: Optional[TextIO] = None
|
||||
self.__socs: Optional[dict[str, OmniSoC_Base]] = None
|
||||
|
||||
def get_args(self):
|
||||
def get_args(self) -> argparse.Namespace:
|
||||
return self.__args
|
||||
|
||||
def set_soc(self, omni_socs):
|
||||
def set_soc(self, omni_socs: dict[str, OmniSoC_Base]) -> None:
|
||||
self.__socs = omni_socs
|
||||
|
||||
def get_socs(self):
|
||||
def get_socs(self) -> Optional[dict[str, OmniSoC_Base]]:
|
||||
return self.__socs
|
||||
|
||||
@demarcate
|
||||
def spatial_multiplex_merge_counters(self, df):
|
||||
def spatial_multiplex_merge_counters(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
return merge_counters_spatial_multiplex(df)
|
||||
|
||||
@demarcate
|
||||
def generate_configs(self, arch, config_dir, list_stats, filter_metrics, sys_info):
|
||||
def generate_configs(
|
||||
self,
|
||||
arch: str,
|
||||
config_dir: str,
|
||||
list_stats: bool,
|
||||
filter_metrics: Optional[list[str]],
|
||||
sys_info: pd.Series,
|
||||
) -> dict[str, schema.ArchConfig]:
|
||||
single_panel_config = file_io.is_single_panel_config(
|
||||
Path(config_dir), self.__supported_archs
|
||||
config_dir, self.__supported_archs
|
||||
)
|
||||
|
||||
ac = schema.ArchConfig()
|
||||
if list_stats:
|
||||
ac.panel_configs = file_io.top_stats_build_in_config
|
||||
ac.panel_configs = TOP_STATS_BUILD_IN_CONFIG
|
||||
else:
|
||||
arch_panel_config = [
|
||||
config_dir if single_panel_config else config_dir.joinpath(arch)
|
||||
config_dir if single_panel_config else str(f"{config_dir}/{arch}")
|
||||
]
|
||||
# Use restructured perf metrics in TUI analyze mode
|
||||
if self.__args.tui and arch in ["gfx942", "gfx950"]:
|
||||
if self.get_args().tui and arch in ["gfx942", "gfx950"]:
|
||||
arch_panel_config.append(
|
||||
f"{config.rocprof_compute_home}/rocprof_compute_tui/utils/{arch}"
|
||||
str(
|
||||
config.rocprof_compute_home
|
||||
/ "rocprof_compute_tui"
|
||||
/ "utils"
|
||||
/ arch
|
||||
)
|
||||
)
|
||||
ac.panel_configs = file_io.load_panel_configs(arch_panel_config)
|
||||
|
||||
# TODO: filter_metrics should/might be one per arch
|
||||
# print(ac)
|
||||
|
||||
parser.build_dfs(
|
||||
archConfigs=ac, filter_metrics=filter_metrics, sys_info=sys_info
|
||||
arch_configs=ac, filter_metrics=filter_metrics, sys_info=sys_info
|
||||
)
|
||||
self._arch_configs[arch] = ac
|
||||
return self._arch_configs
|
||||
|
||||
@demarcate
|
||||
def list_metrics(self):
|
||||
args = self.__args
|
||||
if args.list_metrics in self.__supported_archs.keys():
|
||||
arch = args.list_metrics
|
||||
if arch not in self._arch_configs.keys():
|
||||
sys_info = file_io.load_sys_info(
|
||||
Path(self.__args.path[0][0], "sysinfo.csv")
|
||||
)
|
||||
self.generate_configs(
|
||||
arch,
|
||||
args.config_dir,
|
||||
args.list_stats,
|
||||
args.filter_metrics,
|
||||
sys_info.iloc[0],
|
||||
)
|
||||
def list_metrics(self) -> None:
|
||||
args = self.get_args()
|
||||
arch = args.list_metrics
|
||||
|
||||
metric_descriptions = {
|
||||
k: v
|
||||
for dfs in self._arch_configs[args.list_metrics].dfs.values()
|
||||
for k, v in dfs.to_dict().get("Description", {}).items()
|
||||
}
|
||||
for key, value in self._arch_configs[args.list_metrics].metric_list.items():
|
||||
prefix = ""
|
||||
description = ""
|
||||
if "." not in str(key):
|
||||
prefix = ""
|
||||
elif str(key).count(".") == 1:
|
||||
prefix = "\t"
|
||||
else:
|
||||
prefix = "\t\t"
|
||||
description = metric_descriptions.get(key, "")
|
||||
print(prefix + key, "->", value + "\n")
|
||||
if description:
|
||||
print(
|
||||
prefix
|
||||
+ f"\n{prefix}".join(textwrap.wrap(description, width=40))
|
||||
+ "\n"
|
||||
)
|
||||
sys.exit(0)
|
||||
else:
|
||||
console_error("Unsupported arch")
|
||||
|
||||
@demarcate
|
||||
def load_options(self, normalization_filter):
|
||||
if not normalization_filter:
|
||||
for k, v in self._arch_configs.items():
|
||||
parser.build_metric_value_string(
|
||||
v.dfs, v.dfs_type, self.__args.normal_unit, self._profiling_config
|
||||
)
|
||||
else:
|
||||
for k, v in self._arch_configs.items():
|
||||
parser.build_metric_value_string(
|
||||
v.dfs, v.dfs_type, normalization_filter, self._profiling_config
|
||||
)
|
||||
|
||||
args = self.__args
|
||||
# Error checking for multiple runs and multiple kernel filters
|
||||
if args.gpu_kernel and (len(args.path) != len(args.gpu_kernel)):
|
||||
if len(args.gpu_kernel) == 1:
|
||||
for i in range(len(args.path) - 1):
|
||||
args.gpu_kernel.extend(args.gpu_kernel)
|
||||
else:
|
||||
console_error(
|
||||
"analysis"
|
||||
"The number of -k/--kernel doesn't match the number of --dir."
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def initalize_runs(self, normalization_filter=None):
|
||||
if self.__args.list_metrics:
|
||||
self.list_metrics()
|
||||
|
||||
def get_sysinfo_path(data_path):
|
||||
return (
|
||||
Path(data_path)
|
||||
if self.__args.nodes is None
|
||||
and self.__args.spatial_multiplexing is not True
|
||||
else file_io.find_1st_sub_dir(data_path)
|
||||
)
|
||||
|
||||
# load required configs
|
||||
for d in self.__args.path:
|
||||
sysinfo_path = get_sysinfo_path(d[0])
|
||||
sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
|
||||
arch = sys_info.iloc[0]["gpu_arch"]
|
||||
args = self.__args
|
||||
if arch not in self.__supported_archs:
|
||||
console_error("analysis", "Unsupported arch")
|
||||
if arch not in self._arch_configs:
|
||||
sys_info = file_io.load_sys_info(f"{args.path[0][0]}/sysinfo.csv")
|
||||
self.generate_configs(
|
||||
arch,
|
||||
args.config_dir,
|
||||
@@ -192,163 +153,244 @@ class OmniAnalyze_Base:
|
||||
sys_info.iloc[0],
|
||||
)
|
||||
|
||||
metric_descriptions = {
|
||||
k: v
|
||||
for dfs in self._arch_configs[arch].dfs.values()
|
||||
for k, v in dfs.to_dict().get("Description", {}).items()
|
||||
}
|
||||
for key, value in self._arch_configs[arch].metric_list.items():
|
||||
dot_count = str(key).count(".")
|
||||
if dot_count == 0:
|
||||
prefix = ""
|
||||
elif dot_count == 1:
|
||||
prefix = "\t"
|
||||
else:
|
||||
prefix = "\t\t"
|
||||
|
||||
description = metric_descriptions.get(key, "") if dot_count > 1 else ""
|
||||
|
||||
print(f"{prefix}{key} -> {value}\n")
|
||||
if description:
|
||||
formatted_desc = f"\n{prefix}".join(
|
||||
textwrap.wrap(description, width=40)
|
||||
)
|
||||
print(f"{prefix}{formatted_desc}\n")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
@demarcate
|
||||
def load_options(self, normalization_filter: Optional[str]) -> None:
|
||||
args = self.get_args()
|
||||
target_filter = normalization_filter or args.normal_unit
|
||||
|
||||
for arch_config in self._arch_configs.values():
|
||||
parser.build_metric_value_string(
|
||||
arch_config.dfs,
|
||||
arch_config.dfs_type,
|
||||
target_filter,
|
||||
self._profiling_config,
|
||||
)
|
||||
# Error checking for multiple runs and multiple kernel filters
|
||||
if args.gpu_kernel and (len(args.path) != len(args.gpu_kernel)):
|
||||
if len(args.gpu_kernel) == 1:
|
||||
args.gpu_kernel *= len(args.path)
|
||||
else:
|
||||
console_error(
|
||||
"analysis"
|
||||
"The number of -k/--kernel doesn't match the number of --dir."
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def initalize_runs(
|
||||
self, normalization_filter: Optional[str] = None
|
||||
) -> OrderedDict[str, schema.Workload]:
|
||||
args = self.get_args()
|
||||
if args.list_metrics:
|
||||
self.list_metrics()
|
||||
|
||||
def get_sysinfo_path(data_path: str) -> Optional[str]:
|
||||
return (
|
||||
data_path
|
||||
if args.nodes is None and not args.spatial_multiplexing
|
||||
else file_io.find_1st_sub_dir(data_path)
|
||||
)
|
||||
|
||||
# load required configs
|
||||
for path_info in args.path:
|
||||
sysinfo_path = get_sysinfo_path(path_info[0])
|
||||
if sysinfo_path:
|
||||
sys_info = file_io.load_sys_info(f"{sysinfo_path}/sysinfo.csv")
|
||||
arch = sys_info.iloc[0]["gpu_arch"]
|
||||
self.generate_configs(
|
||||
arch,
|
||||
args.config_dir,
|
||||
args.list_stats,
|
||||
args.filter_metrics,
|
||||
sys_info.iloc[0],
|
||||
)
|
||||
|
||||
self.load_options(normalization_filter)
|
||||
|
||||
for d in self.__args.path:
|
||||
w = schema.Workload()
|
||||
for path_info in args.path:
|
||||
# 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 = schema.Workload()
|
||||
sysinfo_path = get_sysinfo_path(d[0])
|
||||
w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
|
||||
|
||||
if not getattr(self.get_args(), "no_roof", False):
|
||||
try:
|
||||
roofline_csv_path = sysinfo_path / "roofline.csv"
|
||||
roofline_df = pd.read_csv(roofline_csv_path)
|
||||
w.roofline_peaks = roofline_df
|
||||
|
||||
except FileNotFoundError:
|
||||
console_warning("roofline.csv not found.")
|
||||
sysinfo_path = get_sysinfo_path(path_info[0])
|
||||
if sysinfo_path:
|
||||
w.sys_info = file_io.load_sys_info(f"{sysinfo_path}/sysinfo.csv")
|
||||
if not getattr(args, "no_roof", False):
|
||||
try:
|
||||
roofline_df = pd.read_csv(f"{sysinfo_path}/roofline.csv")
|
||||
w.roofline_peaks = roofline_df
|
||||
except FileNotFoundError:
|
||||
console_warning("roofline.csv not found.")
|
||||
w.roofline_peaks = pd.DataFrame()
|
||||
else:
|
||||
w.roofline_peaks = pd.DataFrame()
|
||||
else:
|
||||
w.roofline_peaks = pd.DataFrame()
|
||||
|
||||
arch = w.sys_info.iloc[0]["gpu_arch"]
|
||||
mspec = self.get_socs()[arch]._mspec
|
||||
if self.__args.specs_correction:
|
||||
w.sys_info = parser.correct_sys_info(
|
||||
mspec, self.__args.specs_correction
|
||||
)
|
||||
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[d[0]] = w
|
||||
arch = w.sys_info.iloc[0]["gpu_arch"]
|
||||
socs = self.get_socs()
|
||||
if socs and arch in socs:
|
||||
mspec = socs[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[arch].dfs)
|
||||
w.dfs_type = self._arch_configs[arch].dfs_type
|
||||
self._runs[path_info[0]] = w
|
||||
|
||||
return self._runs
|
||||
|
||||
@demarcate
|
||||
def sanitize(self):
|
||||
def sanitize(self) -> None:
|
||||
"""Perform sanitization of inputs"""
|
||||
if self.__args.tui:
|
||||
args = self.get_args()
|
||||
if args.tui:
|
||||
return
|
||||
if not self.__args.path:
|
||||
|
||||
if not args.path:
|
||||
console_error("The following arguments are required: -p/--path")
|
||||
|
||||
# verify not accessing parent directories
|
||||
if ".." in str(self.__args.path):
|
||||
if ".." in str(args.path):
|
||||
console_error(
|
||||
"Access denied. Cannot access parent directories in path (i.e. ../)"
|
||||
)
|
||||
|
||||
# ensure absolute path
|
||||
for dir in self.__args.path:
|
||||
full_path = str(Path(dir[0]).absolute().resolve())
|
||||
dir[0] = full_path
|
||||
if not Path(dir[0]).is_dir():
|
||||
console_error("Invalid directory {}\nPlease try again.".format(dir[0]))
|
||||
seen_paths: set[str] = set()
|
||||
for dir_info in args.path:
|
||||
full_path = Path(dir_info[0]).absolute().resolve()
|
||||
dir_info[0] = str(full_path)
|
||||
|
||||
if not full_path.is_dir():
|
||||
console_error(
|
||||
"analysis", f"Invalid directory {full_path}\nPlease try again."
|
||||
)
|
||||
# validate profiling data
|
||||
|
||||
# Todo: more err check
|
||||
if not (
|
||||
self.__args.nodes is not None
|
||||
or self.__args.list_nodes
|
||||
or self.__args.spatial_multiplexing
|
||||
):
|
||||
is_workload_empty(dir[0])
|
||||
# else:
|
||||
if dir_info[0] in seen_paths:
|
||||
console_error("analysis", "You cannot provide the same path twice.")
|
||||
seen_paths.add(dir_info[0])
|
||||
|
||||
# no using same paths
|
||||
occurances = set()
|
||||
for dir in self.__args.path:
|
||||
dir = dir[0]
|
||||
if dir in occurances:
|
||||
console_error("You cannot provide the same path twice.")
|
||||
else:
|
||||
occurances.add(dir)
|
||||
if not any([
|
||||
args.nodes,
|
||||
args.list_nodes,
|
||||
args.spatial_multiplexing,
|
||||
]):
|
||||
is_workload_empty(dir_info[0])
|
||||
|
||||
# FIXME:
|
||||
# The proper location of this func should be in pre_processing().
|
||||
# However, because of reading soc depends on sys spec, and sys
|
||||
# spec depends on sys_info. And we read sys_info too early so we
|
||||
# . can not do it now. There should be a way to make it simpler.
|
||||
if self.__args.list_nodes:
|
||||
nodes = []
|
||||
if args.list_nodes:
|
||||
# NB:
|
||||
# There are 2 ways to do it: one is doing like the below, checking
|
||||
# sub dirs only as we assume the profiling stage generate sub dirs
|
||||
# with node name. The 2nd way would be checkign host name in each
|
||||
# sub dir and very those.
|
||||
for subdir in Path(self.__args.path[0][0]).iterdir():
|
||||
if subdir.is_dir():
|
||||
nodes.append(str(subdir.name))
|
||||
nodes = [
|
||||
subdir.name
|
||||
for subdir in Path(args.path[0][0]).iterdir()
|
||||
if subdir.is_dir()
|
||||
]
|
||||
print("Node list:", " ".join(nodes))
|
||||
sys.exit(0)
|
||||
|
||||
# Ensure analysis output does not overwrite existing files
|
||||
if self.__args.output_name:
|
||||
if not re.match(r"^[A-Za-z0-9_-]+$", self.__args.output_name):
|
||||
console_error(
|
||||
"Analysis output file/folder name must "
|
||||
"contain only alphanumeric characters "
|
||||
"or underscores (_), hyphens (-)."
|
||||
)
|
||||
path_to_check = self.__args.output_name
|
||||
if self.__args.output_format in ("txt", "db"):
|
||||
path_to_check += f".{self.__args.output_format}"
|
||||
if Path(path_to_check).exists():
|
||||
console_error(
|
||||
f"Analysis output file/folder {path_to_check} already exists. "
|
||||
"Please choose a different name."
|
||||
)
|
||||
if not args.output_name:
|
||||
return
|
||||
|
||||
if not re.match(r"^[A-Za-z0-9_-]+$", args.output_name):
|
||||
console_error(
|
||||
"analysis",
|
||||
"Analysis output file/folder name must "
|
||||
"contain only alphanumeric characters "
|
||||
"or underscores (_), hyphens (-).",
|
||||
)
|
||||
|
||||
path_to_check = args.output_name
|
||||
if args.output_format in ("txt", "db"):
|
||||
path_to_check += f".{args.output_format}"
|
||||
|
||||
if Path(path_to_check).exists():
|
||||
console_error(
|
||||
f"Analysis output file/folder {path_to_check} already exists. "
|
||||
"Please choose a different name."
|
||||
)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Required methods to be implemented by child classes
|
||||
# ----------------------------------------------------
|
||||
@abstractmethod
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
"""Perform initialization prior to analysis."""
|
||||
console_debug("analysis", "prepping to do some analysis")
|
||||
console_log("analysis", "deriving rocprofiler-compute metrics...")
|
||||
args = self.get_args()
|
||||
|
||||
# initalize output file
|
||||
if self.__args.output_format == "txt":
|
||||
output_filename = self.__args.output_name or f"rocprof_compute_{get_uuid()}"
|
||||
if args.output_format == "txt":
|
||||
output_filename = args.output_name or f"rocprof_compute_{get_uuid()}"
|
||||
output_filename += ".txt"
|
||||
self._output = open(output_filename, "w+")
|
||||
console_warning(f"Created file: {output_filename}")
|
||||
elif self.__args.output_format == "stdout":
|
||||
console_warning("analysis", f"Created file: {output_filename}")
|
||||
elif args.output_format == "stdout":
|
||||
self._output = sys.stdout
|
||||
|
||||
# Read profiling config
|
||||
self._profiling_config = file_io.load_profiling_config(self.__args.path[0][0])
|
||||
self._profiling_config = file_io.load_profiling_config(args.path[0][0])
|
||||
|
||||
# initalize runs
|
||||
self._runs = self.initalize_runs()
|
||||
|
||||
# set filters
|
||||
if self.__args.gpu_kernel:
|
||||
for d, gk in zip(self.__args.path, self.__args.gpu_kernel):
|
||||
self._runs[d[0]].filter_kernel_ids = gk
|
||||
if self.__args.gpu_id:
|
||||
if len(self.__args.gpu_id) == 1 and len(self.__args.path) != 1:
|
||||
for i in range(len(self.__args.path) - 1):
|
||||
self.__args.gpu_id.extend(self.__args.gpu_id)
|
||||
for d, gi in zip(self.__args.path, self.__args.gpu_id):
|
||||
self._runs[d[0]].filter_gpu_ids = gi
|
||||
if self.__args.gpu_dispatch_id:
|
||||
if len(self.__args.gpu_dispatch_id) == 1 and len(self.__args.path) != 1:
|
||||
for i in range(len(self.__args.path) - 1):
|
||||
self.__args.gpu_dispatch_id.extend(self.__args.gpu_dispatch_id)
|
||||
for d, gd in zip(self.__args.path, self.__args.gpu_dispatch_id):
|
||||
self._runs[d[0]].filter_dispatch_ids = gd
|
||||
if self.__args.nodes:
|
||||
if len(self.__args.nodes) == 1 and len(self.__args.path) != 1:
|
||||
for i in range(len(self.__args.path) - 1):
|
||||
self.__args.nodes.extend(self.__args.nodes)
|
||||
for d, gd in zip(self.__args.path, self.__args.nodes):
|
||||
self._runs[d[0]].nodes = gd
|
||||
filter_configs = [
|
||||
(args.gpu_kernel, "filter_kernel_ids"),
|
||||
(args.gpu_id, "filter_gpu_ids"),
|
||||
(args.gpu_dispatch_id, "filter_dispatch_ids"),
|
||||
(args.nodes, "nodes"),
|
||||
]
|
||||
|
||||
for filter_list, attr_name in filter_configs:
|
||||
if not filter_list:
|
||||
continue
|
||||
|
||||
# Extend single filter to match all paths
|
||||
if len(filter_list) == 1 and len(args.path) > 1:
|
||||
filter_list *= len(args.path)
|
||||
|
||||
# Apply filters to workloads
|
||||
for path_info, filter_value in zip(args.path, filter_list):
|
||||
setattr(self._runs[path_info[0]], attr_name, filter_value)
|
||||
|
||||
@abstractmethod
|
||||
def run_analysis(self):
|
||||
def run_analysis(self) -> None:
|
||||
"""Run analysis."""
|
||||
console_debug("analysis", "generating analysis")
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
|
||||
from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base
|
||||
from utils import file_io, parser, tty
|
||||
from utils.kernel_name_shortener import kernel_name_shortener
|
||||
@@ -34,89 +35,97 @@ class cli_analysis(OmniAnalyze_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
"""Perform any pre-processing steps prior to analysis."""
|
||||
super().pre_processing()
|
||||
if self.get_args().random_port:
|
||||
args = self.get_args()
|
||||
|
||||
if args.random_port:
|
||||
console_error("--gui flag is required to enable --random-port")
|
||||
for d in self.get_args().path:
|
||||
workload = self._runs[d[0]]
|
||||
|
||||
for path_info in args.path:
|
||||
workload = self._runs[path_info[0]]
|
||||
|
||||
# create 'mega dataframe'
|
||||
workload.raw_pmc = file_io.create_df_pmc(
|
||||
d[0],
|
||||
self.get_args().nodes,
|
||||
self.get_args().spatial_multiplexing,
|
||||
self.get_args().kernel_verbose,
|
||||
self.get_args().verbose,
|
||||
path_info[0],
|
||||
args.nodes,
|
||||
args.spatial_multiplexing,
|
||||
args.kernel_verbose,
|
||||
args.verbose,
|
||||
self._profiling_config,
|
||||
)
|
||||
|
||||
if self.get_args().spatial_multiplexing:
|
||||
if args.spatial_multiplexing:
|
||||
workload.raw_pmc = self.spatial_multiplex_merge_counters(
|
||||
workload.raw_pmc
|
||||
)
|
||||
|
||||
file_io.create_df_kernel_top_stats(
|
||||
df_in=workload.raw_pmc,
|
||||
raw_data_dir=d[0],
|
||||
raw_data_dir=path_info[0],
|
||||
filter_gpu_ids=workload.filter_gpu_ids,
|
||||
filter_dispatch_ids=workload.filter_dispatch_ids,
|
||||
filter_nodes=workload.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,
|
||||
time_unit=args.time_unit,
|
||||
kernel_verbose=args.kernel_verbose,
|
||||
)
|
||||
|
||||
# demangle and overwrite original 'Kernel_Name'
|
||||
kernel_name_shortener(workload.raw_pmc, self.get_args().kernel_verbose)
|
||||
kernel_name_shortener(workload.raw_pmc, args.kernel_verbose)
|
||||
|
||||
# create the loaded table
|
||||
parser.load_table_data(
|
||||
workload=workload,
|
||||
dir=d[0],
|
||||
dir_path=path_info[0],
|
||||
is_gui=False,
|
||||
args=self.get_args(),
|
||||
args=args,
|
||||
config=self._profiling_config,
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def run_analysis(self):
|
||||
def run_analysis(self) -> None:
|
||||
"""Run CLI analysis."""
|
||||
super().run_analysis()
|
||||
|
||||
workload_path = self.get_args().path[0][0]
|
||||
args = self.get_args()
|
||||
|
||||
workload_path = args.path[0][0]
|
||||
workload = self._runs[workload_path]
|
||||
gpu_arch = workload.sys_info.iloc[0]["gpu_arch"]
|
||||
arch_config = self._arch_configs[gpu_arch]
|
||||
|
||||
if self.get_args().list_stats:
|
||||
if args.list_stats:
|
||||
tty.show_kernel_stats(
|
||||
self.get_args(),
|
||||
args,
|
||||
self._runs,
|
||||
arch_config,
|
||||
self._output,
|
||||
)
|
||||
else:
|
||||
roof_plot = None
|
||||
# 1. check if not baseline && compatible soc:
|
||||
if (len(self.get_args().path)) == 1:
|
||||
|
||||
# Generate roofline plot for single-path, compatible architectures
|
||||
if (len(args.path)) == 1:
|
||||
if gpu_arch in ["gfx90a", "gfx940", "gfx941", "gfx942", "gfx950"]:
|
||||
roof_obj = self.get_socs()[gpu_arch].roofline_obj
|
||||
soc = self.get_socs()
|
||||
if soc and gpu_arch in soc:
|
||||
roof_obj = soc[gpu_arch].roofline_obj
|
||||
|
||||
if roof_obj:
|
||||
# store path in workload for calc_ai_analyze
|
||||
workload.path = workload_path
|
||||
if roof_obj:
|
||||
# store path in workload for calc_ai_analyze
|
||||
workload.path = workload_path
|
||||
|
||||
# NOTE: using default data type
|
||||
roof_plot = roof_obj.cli_generate_plot(
|
||||
dtype=roof_obj.get_dtype()[0],
|
||||
workload=workload,
|
||||
config=self._profiling_config,
|
||||
arch_config=arch_config,
|
||||
)
|
||||
# NOTE: using default data type
|
||||
roof_plot = roof_obj.cli_generate_plot(
|
||||
dtype=roof_obj.get_dtype()[0],
|
||||
workload=workload,
|
||||
config=self._profiling_config,
|
||||
arch_config=arch_config,
|
||||
)
|
||||
|
||||
tty.show_all(
|
||||
self.get_args(),
|
||||
args,
|
||||
self._runs,
|
||||
arch_config,
|
||||
self._output,
|
||||
|
||||
@@ -39,7 +39,7 @@ from utils.logger import console_debug, console_error, console_warning, demarcat
|
||||
from utils.parser import (
|
||||
PC_SAMPLING_NOT_ISSUE_PREFIX,
|
||||
CodeTransformer,
|
||||
build_in_vars,
|
||||
BUILD_IN_VARS,
|
||||
to_avg,
|
||||
to_concat,
|
||||
to_int,
|
||||
@@ -409,14 +409,14 @@ class db_analysis(OmniAnalyze_Base):
|
||||
sys_info[f"{key}_empirical_peak"] = value
|
||||
|
||||
# Calculate PER_XCD variables first
|
||||
for key, value in build_in_vars.items():
|
||||
for key, value in BUILD_IN_VARS.items():
|
||||
if "PER_XCD" in key:
|
||||
sys_info[key] = db_analysis.evaluate(
|
||||
key, value, pmc_df, sys_info, parse=True
|
||||
)
|
||||
|
||||
# variable dependent on PER_XCD variables
|
||||
for key, value in build_in_vars.items():
|
||||
for key, value in BUILD_IN_VARS.items():
|
||||
if "PER_XCD" not in key:
|
||||
sys_info[key] = db_analysis.evaluate(
|
||||
key, value, pmc_df, sys_info, parse=True
|
||||
|
||||
@@ -23,35 +23,41 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import random
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import dash
|
||||
import dash_bootstrap_components as dbc
|
||||
import pandas as pd
|
||||
from dash import dcc, html
|
||||
from dash.dependencies import Input, Output, State
|
||||
|
||||
from config import HIDDEN_COLUMNS, PROJECT_NAME
|
||||
from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base
|
||||
from utils import file_io, parser
|
||||
from utils import file_io, parser, schema
|
||||
from utils.gui import build_bar_chart, build_table_chart
|
||||
from utils.logger import console_debug, console_error, console_warning, demarcate
|
||||
|
||||
|
||||
class webui_analysis(OmniAnalyze_Base):
|
||||
def __init__(self, args, supported_archs):
|
||||
def __init__(
|
||||
self, args: argparse.Namespace, supported_archs: dict[str, str]
|
||||
) -> None:
|
||||
super().__init__(args, supported_archs)
|
||||
self.app = dash.Dash(
|
||||
__name__, title=PROJECT_NAME, external_stylesheets=[dbc.themes.CYBORG]
|
||||
)
|
||||
self.dest_dir = str(Path(args.path[0][0]).absolute().resolve())
|
||||
self.arch = None
|
||||
self.arch: Optional[str] = None
|
||||
|
||||
self.__hidden_sections = ["Memory Chart"]
|
||||
self.__hidden_columns = HIDDEN_COLUMNS
|
||||
|
||||
# define different types of bar charts
|
||||
self.__barchart_elements = {
|
||||
self.__barchart_elements: dict[str, list[int]] = {
|
||||
"instr_mix": [1001, 1002],
|
||||
# 1604: L1D - L2 Transactions
|
||||
# 1705: L2 - Fabric Interface Stalls
|
||||
@@ -59,27 +65,33 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
"sol": [1101, 1201, 1301, 1401, 1601, 1701],
|
||||
# "l2_cache_per_chan": [1802, 1803]
|
||||
}
|
||||
# define any elements which will have full width
|
||||
self.__full_width_elements = {1801}
|
||||
|
||||
# define any elements which will have full width
|
||||
self.__full_width_elements: set[int] = {1801}
|
||||
self.__roofline_data_type = args.roofline_data_type
|
||||
|
||||
@demarcate
|
||||
def build_layout(self, input_filters, arch_configs):
|
||||
def build_layout(
|
||||
self, input_filters: dict[str, Any], arch_configs: schema.ArchConfig
|
||||
) -> None:
|
||||
"""
|
||||
Build gui layout
|
||||
"""
|
||||
from utils.gui_components.header import get_header
|
||||
from utils.gui_components.memchart import get_memchart
|
||||
args = self.get_args()
|
||||
|
||||
comparable_columns = parser.build_comparable_columns(self.get_args().time_unit)
|
||||
comparable_columns = parser.build_comparable_columns(args.time_unit)
|
||||
base_run, base_data = next(iter(self._runs.items()))
|
||||
|
||||
self.app.layout = html.Div(style={"backgroundColor": "rgb(50, 50, 50)"})
|
||||
|
||||
filt_kernel_names = []
|
||||
# get filtered kernel names from kernel ids
|
||||
filt_kernel_names: list[str] = []
|
||||
kernel_top_df = base_data.dfs[1]
|
||||
for kernel_id in base_data.filter_kernel_ids:
|
||||
filt_kernel_names.append(kernel_top_df.loc[kernel_id, "Kernel_Name"])
|
||||
filt_kernel_names.append(str(kernel_top_df.loc[kernel_id, "Kernel_Name"]))
|
||||
|
||||
# setup app layout
|
||||
from utils.gui_components.header import get_header
|
||||
|
||||
self.app.layout.children = html.Div(
|
||||
children=[
|
||||
@@ -105,36 +117,43 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
[State("container", "children")],
|
||||
)
|
||||
def generate_from_filter(
|
||||
disp_filt, kernel_filter, gcd_filter, norm_filt, top_n_filt, div_children
|
||||
):
|
||||
console_debug("analysis", "gui normalization is %s" % norm_filt)
|
||||
disp_filt: str,
|
||||
kernel_filter: str,
|
||||
gcd_filter: str,
|
||||
norm_filt: str,
|
||||
top_n_filt: str,
|
||||
div_children: list[html.Section],
|
||||
) -> list[html.Section]:
|
||||
console_debug("analysis", f"gui normalization is {norm_filt}")
|
||||
|
||||
# Re-initalizes everything
|
||||
base_data = self.initalize_runs(normalization_filter=norm_filt)
|
||||
|
||||
panel_configs = copy.deepcopy(arch_configs.panel_configs)
|
||||
|
||||
# Generate original raw df
|
||||
base_data[base_run].raw_pmc = file_io.create_df_pmc(
|
||||
self.dest_dir,
|
||||
self.get_args().nodes,
|
||||
self.get_args().spatial_multiplexing,
|
||||
self.get_args().kernel_verbose,
|
||||
self.get_args().verbose,
|
||||
args.nodes,
|
||||
args.spatial_multiplexing,
|
||||
args.kernel_verbose,
|
||||
args.verbose,
|
||||
self._profiling_config,
|
||||
)
|
||||
|
||||
if self.get_args().spatial_multiplexing:
|
||||
if args.spatial_multiplexing:
|
||||
base_data[base_run].raw_pmc = self.spatial_multiplex_merge_counters(
|
||||
base_data[base_run].raw_pmc
|
||||
)
|
||||
|
||||
console_debug("analysis", "gui dispatch filter is %s" % disp_filt)
|
||||
console_debug("analysis", "gui kernel filter is %s" % kernel_filter)
|
||||
console_debug("analysis", "gui gpu filter is %s" % gcd_filter)
|
||||
console_debug("analysis", "gui top-n filter is %s" % top_n_filt)
|
||||
base_data[base_run].filter_kernel_ids = kernel_filter
|
||||
base_data[base_run].filter_gpu_ids = gcd_filter
|
||||
base_data[base_run].filter_dispatch_ids = disp_filt
|
||||
# Apply filters to workload data
|
||||
console_debug("analysis", f"gui dispatch filter is {disp_filt}")
|
||||
console_debug("analysis", f"gui kernel filter is {kernel_filter}")
|
||||
console_debug("analysis", f"gui gpu filter is {gcd_filter}")
|
||||
console_debug("analysis", f"gui top-n filter is {top_n_filt}")
|
||||
|
||||
base_data[base_run].filter_kernel_ids = [int(kernel_filter)]
|
||||
base_data[base_run].filter_gpu_ids = [int(gcd_filter)]
|
||||
base_data[base_run].filter_dispatch_ids = [int(disp_filt)]
|
||||
base_data[base_run].filter_top_n = top_n_filt
|
||||
|
||||
# Reload the pmc_kernel_top.csv for Top Stats panel
|
||||
@@ -144,31 +163,35 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
filter_gpu_ids=base_data[base_run].filter_gpu_ids,
|
||||
filter_dispatch_ids=base_data[base_run].filter_dispatch_ids,
|
||||
filter_nodes=self._runs[self.dest_dir].filter_nodes,
|
||||
time_unit=self.get_args().time_unit,
|
||||
max_stat_num=base_data[base_run].filter_top_n,
|
||||
kernel_verbose=self.get_args().kernel_verbose,
|
||||
time_unit=args.time_unit,
|
||||
kernel_verbose=args.kernel_verbose,
|
||||
)
|
||||
|
||||
# Only display basic metrics if no filters are applied
|
||||
if not (disp_filt or kernel_filter or gcd_filter):
|
||||
temp = {}
|
||||
keep = [1, 2, 101, 201, 301, 401, 402]
|
||||
for key in base_data[base_run].dfs:
|
||||
if keep.count(key) != 0:
|
||||
temp[key] = base_data[base_run].dfs[key]
|
||||
basic_dfs_keep = [1, 2, 101, 201, 301, 401, 402]
|
||||
basic_panels_keep = [0, 100, 200, 300, 400]
|
||||
|
||||
# Filter dataframes
|
||||
filtered_dfs = {
|
||||
key: base_data[base_run].dfs[key]
|
||||
for key in base_data[base_run].dfs
|
||||
if key in basic_dfs_keep
|
||||
}
|
||||
base_data[base_run].dfs = filtered_dfs
|
||||
|
||||
panel_configs = {
|
||||
key: panel_configs[key]
|
||||
for key in panel_configs
|
||||
if key in basic_panels_keep
|
||||
}
|
||||
|
||||
base_data[base_run].dfs = temp
|
||||
temp = {}
|
||||
keep = [0, 100, 200, 300, 400]
|
||||
for key in panel_configs:
|
||||
if keep.count(key) != 0:
|
||||
temp[key] = panel_configs[key]
|
||||
panel_configs = temp
|
||||
# All filtering will occur here
|
||||
parser.load_table_data(
|
||||
workload=base_data[base_run],
|
||||
dir=self.dest_dir,
|
||||
dir_path=self.dest_dir,
|
||||
is_gui=True,
|
||||
args=self.get_args(),
|
||||
args=args,
|
||||
config=self._profiling_config,
|
||||
)
|
||||
|
||||
@@ -178,39 +201,46 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
div_children = []
|
||||
|
||||
# Append memory chart and roofline
|
||||
from utils.gui_components.memchart import get_memchart
|
||||
|
||||
div_children.append(
|
||||
get_memchart(panel_configs[300]["data source"], base_data[base_run])
|
||||
)
|
||||
has_roofline = Path(self.dest_dir).joinpath("roofline.csv").is_file()
|
||||
if has_roofline and hasattr(self.get_socs()[self.arch], "roofline_obj"):
|
||||
# update roofline for visualization in GUI
|
||||
self.get_socs()[self.arch].analysis_setup(
|
||||
roofline_parameters={
|
||||
"workload_dir": self.dest_dir,
|
||||
"device_id": 0,
|
||||
"sort_type": "kernels",
|
||||
"mem_level": "ALL",
|
||||
"include_kernel_names": False,
|
||||
"is_standalone": False,
|
||||
"roofline_data_type": self.__roofline_data_type,
|
||||
"kernel_filter": False,
|
||||
}
|
||||
)
|
||||
roof_obj = self.get_socs()[self.arch].roofline_obj
|
||||
div_children.append(
|
||||
roof_obj.empirical_roofline(
|
||||
ret_df=parser.apply_filters(
|
||||
workload=base_data[base_run],
|
||||
dir=self.dest_dir,
|
||||
is_gui=True,
|
||||
debug=self.get_args().debug,
|
||||
|
||||
has_roofline = (Path(self.dest_dir) / "roofline.csv").is_file()
|
||||
soc = self.get_socs()
|
||||
if soc and self.arch in soc:
|
||||
if has_roofline and hasattr(soc[self.arch], "roofline_obj"):
|
||||
# update roofline for visualization in GUI
|
||||
soc[self.arch].analysis_setup(
|
||||
roofline_parameters={
|
||||
"workload_dir": self.dest_dir,
|
||||
"device_id": 0,
|
||||
"sort_type": "kernels",
|
||||
"mem_level": "ALL",
|
||||
"include_kernel_names": False,
|
||||
"is_standalone": False,
|
||||
"roofline_data_type": self.__roofline_data_type,
|
||||
"kernel_filter": False,
|
||||
}
|
||||
)
|
||||
roof_obj = soc[self.arch].roofline_obj
|
||||
div_children.append(
|
||||
roof_obj.empirical_roofline(
|
||||
ret_df=parser.apply_filters(
|
||||
workload=base_data[base_run],
|
||||
dir=self.dest_dir,
|
||||
is_gui=True,
|
||||
debug=args.debug,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Iterate over each section as defined in panel configs
|
||||
for panel_id, panel in panel_configs.items():
|
||||
title = str(panel_id // 100) + ". " + panel["title"]
|
||||
if panel["title"] in self.__hidden_sections:
|
||||
continue
|
||||
title = f"{panel_id // 100}. {panel['title']}"
|
||||
section_title = (
|
||||
panel["title"]
|
||||
.replace("(", "")
|
||||
@@ -219,43 +249,40 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
.replace(" ", "_")
|
||||
.lower()
|
||||
)
|
||||
|
||||
# Build content for a single panel
|
||||
html_section = []
|
||||
if panel["title"] not in self.__hidden_sections:
|
||||
# Iterate over each table per section
|
||||
for data_source in panel["data source"]:
|
||||
for t_type, table_config in data_source.items():
|
||||
original_df = base_data[base_run].dfs[table_config["id"]]
|
||||
# The sys info table need to add index back
|
||||
if (
|
||||
t_type == "raw_csv_table"
|
||||
and "Info" in original_df.keys()
|
||||
):
|
||||
original_df.reset_index(inplace=True)
|
||||
# Iterate over each table per section
|
||||
for data_source in panel["data source"]:
|
||||
for t_type, table_config in data_source.items():
|
||||
original_df = base_data[base_run].dfs[table_config["id"]]
|
||||
|
||||
content = determine_chart_type(
|
||||
original_df=original_df,
|
||||
table_config=table_config,
|
||||
hidden_columns=self.__hidden_columns,
|
||||
barchart_elements=self.__barchart_elements,
|
||||
norm_filt=norm_filt,
|
||||
comparable_columns=comparable_columns,
|
||||
decimal=self.get_args().decimal,
|
||||
# The sys info table need to add index back
|
||||
if t_type == "raw_csv_table" and "Info" in original_df.keys():
|
||||
original_df.reset_index(inplace=True)
|
||||
|
||||
content = determine_chart_type(
|
||||
original_df=original_df,
|
||||
table_config=table_config,
|
||||
hidden_columns=self.__hidden_columns,
|
||||
barchart_elements=self.__barchart_elements,
|
||||
comparable_columns=comparable_columns,
|
||||
decimal=args.decimal,
|
||||
)
|
||||
|
||||
# Update content for this section
|
||||
div_style = (
|
||||
{"width": "100%"}
|
||||
if table_config["id"] in self.__full_width_elements
|
||||
else {}
|
||||
)
|
||||
html_section.append(
|
||||
html.Div(
|
||||
className="float-child",
|
||||
children=content,
|
||||
style=div_style,
|
||||
)
|
||||
|
||||
# Update content for this section
|
||||
if table_config["id"] in self.__full_width_elements:
|
||||
# Optionally override default (50%) width
|
||||
html_section.append(
|
||||
html.Div(
|
||||
className="float-child",
|
||||
children=content,
|
||||
style={"width": "100%"},
|
||||
)
|
||||
)
|
||||
else:
|
||||
html_section.append(
|
||||
html.Div(className="float-child", children=content)
|
||||
)
|
||||
)
|
||||
|
||||
# Append the new section with all of it's contents
|
||||
div_children.append(
|
||||
@@ -298,56 +325,54 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
"""Perform any pre-processing steps prior to analysis."""
|
||||
super().pre_processing()
|
||||
if len(self._runs) == 1:
|
||||
args = self.get_args()
|
||||
|
||||
# create 'mega dataframe'
|
||||
self._runs[self.dest_dir].raw_pmc = file_io.create_df_pmc(
|
||||
self.dest_dir,
|
||||
self.get_args().nodes,
|
||||
self.get_args().spatial_multiplexing,
|
||||
self.get_args().kernel_verbose,
|
||||
args.verbose,
|
||||
self._profiling_config,
|
||||
)
|
||||
|
||||
if self.get_args().spatial_multiplexing:
|
||||
self._runs[
|
||||
self.dest_dir
|
||||
].raw_pmc = self.spatial_multiplex_merge_counters(
|
||||
self._runs[self.dest_dir].raw_pmc
|
||||
)
|
||||
|
||||
file_io.create_df_kernel_top_stats(
|
||||
df_in=self._runs[self.dest_dir].raw_pmc,
|
||||
raw_data_dir=self.dest_dir,
|
||||
filter_gpu_ids=self._runs[self.dest_dir].filter_gpu_ids,
|
||||
filter_dispatch_ids=self._runs[self.dest_dir].filter_dispatch_ids,
|
||||
filter_nodes=self._runs[self.dest_dir].filter_nodes,
|
||||
time_unit=args.time_unit,
|
||||
max_stat_num=args.max_stat_num,
|
||||
kernel_verbose=self.get_args().kernel_verbose,
|
||||
)
|
||||
# create the loaded kernel stats
|
||||
parser.load_kernel_top(
|
||||
self._runs[self.dest_dir], self.dest_dir, self.get_args()
|
||||
)
|
||||
# set architecture
|
||||
self.arch = self._runs[self.dest_dir].sys_info.iloc[0]["gpu_arch"]
|
||||
|
||||
else:
|
||||
if len(self._runs) != 1:
|
||||
console_error(
|
||||
"Multiple runs not yet supported in GUI. Retry without --gui flag."
|
||||
"analysis",
|
||||
"Multiple runs not yet supported in GUI. Retry without --gui flag.",
|
||||
)
|
||||
|
||||
args = self.get_args()
|
||||
|
||||
# create 'mega dataframe'
|
||||
self._runs[self.dest_dir].raw_pmc = file_io.create_df_pmc(
|
||||
self.dest_dir,
|
||||
args.nodes,
|
||||
args.spatial_multiplexing,
|
||||
args.kernel_verbose,
|
||||
args.verbose,
|
||||
self._profiling_config,
|
||||
)
|
||||
|
||||
if args.spatial_multiplexing:
|
||||
self._runs[self.dest_dir].raw_pmc = self.spatial_multiplex_merge_counters(
|
||||
self._runs[self.dest_dir].raw_pmc
|
||||
)
|
||||
|
||||
file_io.create_df_kernel_top_stats(
|
||||
df_in=self._runs[self.dest_dir].raw_pmc,
|
||||
raw_data_dir=self.dest_dir,
|
||||
filter_gpu_ids=self._runs[self.dest_dir].filter_gpu_ids,
|
||||
filter_dispatch_ids=self._runs[self.dest_dir].filter_dispatch_ids,
|
||||
filter_nodes=self._runs[self.dest_dir].filter_nodes,
|
||||
time_unit=args.time_unit,
|
||||
kernel_verbose=args.kernel_verbose,
|
||||
)
|
||||
# create the loaded kernel stats
|
||||
parser.load_non_mertrics_table(self._runs[self.dest_dir], self.dest_dir, args)
|
||||
# set architecture
|
||||
self.arch = self._runs[self.dest_dir].sys_info.iloc[0]["gpu_arch"]
|
||||
|
||||
@demarcate
|
||||
def run_analysis(self):
|
||||
"""Run CLI analysis."""
|
||||
def run_analysis(self) -> None:
|
||||
"""Run webui analysis."""
|
||||
super().run_analysis()
|
||||
|
||||
args = self.get_args()
|
||||
|
||||
input_filters = {
|
||||
"kernel": self._runs[self.dest_dir].filter_kernel_ids,
|
||||
"gpu": self._runs[self.dest_dir].filter_gpu_ids,
|
||||
@@ -356,63 +381,60 @@ class webui_analysis(OmniAnalyze_Base):
|
||||
"top_n": args.max_stat_num,
|
||||
}
|
||||
|
||||
self.build_layout(
|
||||
input_filters,
|
||||
self._arch_configs[self.arch],
|
||||
)
|
||||
if args.random_port:
|
||||
self.app.run(debug=False, host="0.0.0.0", port=random.randint(1024, 49151))
|
||||
else:
|
||||
self.app.run(debug=False, host="0.0.0.0", port=args.gui)
|
||||
if self.arch and self.arch in self._arch_configs:
|
||||
self.build_layout(
|
||||
input_filters,
|
||||
self._arch_configs[self.arch],
|
||||
)
|
||||
|
||||
port = random.randint(1024, 49151) if args.random_port else args.gui
|
||||
self.app.run(debug=False, host="0.0.0.0", port=port)
|
||||
|
||||
|
||||
@demarcate
|
||||
def determine_chart_type(
|
||||
original_df,
|
||||
table_config,
|
||||
hidden_columns,
|
||||
barchart_elements,
|
||||
norm_filt,
|
||||
comparable_columns,
|
||||
decimal,
|
||||
):
|
||||
original_df: pd.DataFrame,
|
||||
table_config: dict[str, Any],
|
||||
hidden_columns: list[str],
|
||||
barchart_elements: dict[str, list[int]],
|
||||
comparable_columns: list[str],
|
||||
decimal: int,
|
||||
) -> list[html.Div]:
|
||||
content = []
|
||||
|
||||
display_columns = original_df.columns.values.tolist().copy()
|
||||
# Remove hidden columns. Better way to do it?
|
||||
for col in hidden_columns:
|
||||
if col in display_columns:
|
||||
display_columns.remove(col)
|
||||
if original_df.empty:
|
||||
console_warning(
|
||||
"analysis",
|
||||
f"The dataframe with id={table_config['id']} is empty! Not displaying it.",
|
||||
)
|
||||
return content
|
||||
|
||||
display_columns = [
|
||||
col for col in original_df.columns.values.tolist() if col not in hidden_columns
|
||||
]
|
||||
display_df = original_df[display_columns]
|
||||
|
||||
# Determine chart type:
|
||||
# a) Barchart
|
||||
if original_df.empty:
|
||||
console_warning(
|
||||
f"The dataframe with id={table_config['id']} is empty! Not displaying it."
|
||||
)
|
||||
elif table_config["id"] in [x for i in barchart_elements.values() for x in i]:
|
||||
d_figs = build_bar_chart(display_df, table_config, barchart_elements, norm_filt)
|
||||
if table_config["id"] in [x for i in barchart_elements.values() for x in i]:
|
||||
d_figs = build_bar_chart(display_df, table_config, barchart_elements)
|
||||
# Smaller formatting if barchart yeilds several graphs
|
||||
if (
|
||||
len(d_figs) > 2
|
||||
# and not table_config["id"]
|
||||
# in barchart_elements["l2_cache_per_chan"]
|
||||
):
|
||||
temp_obj = []
|
||||
for fig in d_figs:
|
||||
temp_obj.append(
|
||||
html.Div(
|
||||
className="float-child",
|
||||
children=[dcc.Graph(figure=fig, style={"margin": "2%"})],
|
||||
)
|
||||
if len(d_figs) > 2:
|
||||
temp_obj = [
|
||||
html.Div(
|
||||
className="float-child",
|
||||
children=[dcc.Graph(figure=fig, style={"margin": "2%"})],
|
||||
)
|
||||
for fig in d_figs
|
||||
]
|
||||
content.append(html.Div(className="float-container", children=temp_obj))
|
||||
# Normal formatting if < 2 graphs
|
||||
else:
|
||||
for fig in d_figs:
|
||||
content.append(dcc.Graph(figure=fig, style={"margin": "2%"}))
|
||||
# B) Tablechart
|
||||
content.extend([
|
||||
dcc.Graph(figure=fig, style={"margin": "2%"}) for fig in d_figs
|
||||
])
|
||||
|
||||
# b) Tablechart
|
||||
else:
|
||||
d_figs = build_table_chart(
|
||||
display_df,
|
||||
@@ -422,18 +444,13 @@ def determine_chart_type(
|
||||
comparable_columns,
|
||||
decimal,
|
||||
)
|
||||
for fig in d_figs:
|
||||
content.append(html.Div([fig], style={"margin": "2%"}))
|
||||
content.extend([html.Div([fig], style={"margin": "2%"}) for fig in d_figs])
|
||||
|
||||
# subtitle for each table in a panel if existing
|
||||
if "title" in table_config and table_config["title"]:
|
||||
if table_config.get("title"):
|
||||
subtitle = (
|
||||
str(table_config["id"] // 100)
|
||||
+ "."
|
||||
+ str(table_config["id"] % 100)
|
||||
+ " "
|
||||
+ table_config["title"]
|
||||
+ "\n"
|
||||
f"{table_config['id'] // 100}.{table_config['id'] % 100} "
|
||||
f"{table_config['title']}\n"
|
||||
)
|
||||
|
||||
content.insert(
|
||||
|
||||
@@ -30,9 +30,11 @@ import socket
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import config
|
||||
from argparser import omniarg_parser
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils import file_io, parser, schema
|
||||
from utils.logger import (
|
||||
console_debug,
|
||||
@@ -57,25 +59,21 @@ from utils.utils import (
|
||||
|
||||
|
||||
class RocProfCompute:
|
||||
def __init__(self):
|
||||
self.__args = None
|
||||
def __init__(self) -> None:
|
||||
self.__args: Optional[argparse.Namespace] = 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.__soc: dict[str, OmniSoC_Base] = {}
|
||||
self.__version: dict[str, Optional[str]] = {"ver": None, "ver_pretty": None}
|
||||
self.__supported_archs = mi_gpu_specs.get_gpu_series_dict()
|
||||
self.__mspec: MachineSpecs = None # to be initalized in load_soc_specs()
|
||||
self.__mspec: MachineSpecs # to be initialized in load_soc_specs()
|
||||
|
||||
setup_console_handler()
|
||||
self.set_version()
|
||||
self.parse_args()
|
||||
assert self.__args is not None
|
||||
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
|
||||
@@ -90,11 +88,11 @@ class RocProfCompute:
|
||||
elif self.__mode == "analyze":
|
||||
self.detect_analyze()
|
||||
|
||||
console_debug("Execution mode = %s" % self.__mode)
|
||||
console_debug(f"Execution mode = {self.__mode}")
|
||||
|
||||
def print_graphic(self):
|
||||
"""Log program name as ascii art to terminal."""
|
||||
ascii_art = r"""
|
||||
def print_graphic(self) -> None:
|
||||
print(
|
||||
r"""
|
||||
__ _
|
||||
_ __ ___ ___ _ __ _ __ ___ / _| ___ ___ _ __ ___ _ __ _ _| |_ ___
|
||||
| '__/ _ \ / __| '_ \| '__/ _ \| |_ _____ / __/ _ \| '_ ` _ \| '_ \| | | | __/ _ \
|
||||
@@ -102,20 +100,19 @@ class RocProfCompute:
|
||||
|_| \___/ \___| .__/|_| \___/|_| \___\___/|_| |_| |_| .__/ \__,_|\__\___|
|
||||
|_| |_|
|
||||
"""
|
||||
print(ascii_art)
|
||||
)
|
||||
|
||||
def get_mode(self):
|
||||
def get_mode(self) -> Optional[str]:
|
||||
return self.__mode
|
||||
|
||||
def set_version(self):
|
||||
def set_version(self) -> None:
|
||||
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):
|
||||
def detect_profiler(self) -> None:
|
||||
profiler_mode = detect_rocprof(self.__args)
|
||||
if str(profiler_mode).endswith("rocprof"):
|
||||
self.__profiler_mode = "rocprofv1"
|
||||
@@ -127,12 +124,11 @@ class RocProfCompute:
|
||||
self.__profiler_mode = "rocprofiler-sdk"
|
||||
else:
|
||||
console_error(
|
||||
"Incompatible profiler: %s. Supported profilers include: %s"
|
||||
% (profiler_mode, get_submodules("rocprof_compute_profile"))
|
||||
f"Incompatible profiler: {profiler_mode}. Supported profilers "
|
||||
f"include: {get_submodules('rocprof_compute_profile')}"
|
||||
)
|
||||
return
|
||||
|
||||
def detect_analyze(self):
|
||||
def detect_analyze(self) -> None:
|
||||
if self.__args.gui:
|
||||
self.__analyze_mode = "web_ui"
|
||||
elif self.__args.tui:
|
||||
@@ -141,9 +137,8 @@ class RocProfCompute:
|
||||
self.__analyze_mode = "db"
|
||||
else:
|
||||
self.__analyze_mode = "cli"
|
||||
return
|
||||
|
||||
def sanitize(self):
|
||||
def sanitize(self) -> None:
|
||||
block = False
|
||||
if (hasattr(self.__args, "filter_metrics") and self.__args.filter_metrics) or (
|
||||
hasattr(self.__args, "filter_blocks") and self.__args.filter_blocks
|
||||
@@ -159,21 +154,19 @@ class RocProfCompute:
|
||||
console_error("Cannot use --list-available-metrics with --blocks")
|
||||
|
||||
@demarcate
|
||||
def load_soc_specs(self, sysinfo: dict = None):
|
||||
def load_soc_specs(self, sysinfo: Optional[dict] = None) -> None:
|
||||
"""Load OmniSoC instance for RocProfCompute run"""
|
||||
self.__mspec = generate_machine_specs(self.__args, sysinfo)
|
||||
if self.__args.specs:
|
||||
if self.__args and 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")
|
||||
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)
|
||||
return
|
||||
|
||||
def parse_args(self):
|
||||
def parse_args(self) -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Command line interface for AMD's GPU profiler, ROCm Compute Profiler"
|
||||
@@ -190,18 +183,16 @@ class RocProfCompute:
|
||||
self.__args = parser.parse_args()
|
||||
|
||||
if (
|
||||
"format_rocprof_output" in self.__args
|
||||
hasattr(self.__args, "format_rocprof_output")
|
||||
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."
|
||||
)
|
||||
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.mode is None:
|
||||
if self.__args.specs:
|
||||
print(generate_machine_specs(self.__args))
|
||||
sys.exit(0)
|
||||
@@ -219,36 +210,42 @@ class RocProfCompute:
|
||||
"rocprof-compute requires you to pass a valid mode. Detected None."
|
||||
)
|
||||
elif self.__args.mode == "profile":
|
||||
# Add --name to workload path if --path is not given
|
||||
if self.__args.path == str(Path(os.getcwd()) / "workloads"):
|
||||
self.__args.path = str(Path(self.__args.path) / self.__args.name)
|
||||
# Add node name to workload path
|
||||
if self.__args.subpath == "node_name":
|
||||
self.__args.path = str(Path(self.__args.path) / socket.gethostname())
|
||||
# Or, add gpu model name to workload path
|
||||
elif self.__args.subpath == "gpu_model":
|
||||
self.__args.path = str(Path(self.__args.path) / self.__mspec.gpu_model)
|
||||
|
||||
# Create workload directory if it does not exist
|
||||
p = Path(self.__args.path)
|
||||
if not p.exists():
|
||||
try:
|
||||
p.mkdir(parents=True, exist_ok=False)
|
||||
except FileExistsError:
|
||||
console_error("Directory already exists.")
|
||||
|
||||
self.handle_profile_args()
|
||||
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
|
||||
self.handle_analyze_args()
|
||||
|
||||
return
|
||||
def handle_profile_args(self) -> None:
|
||||
# Add --name to workload path if --path is not given
|
||||
if self.__args.path == str(Path(os.getcwd()) / "workloads"):
|
||||
if not hasattr(self.__args, "name") or not self.__args.name:
|
||||
console_error("-n/--name is required")
|
||||
self.__args.path = str(Path(self.__args.path) / self.__args.name)
|
||||
# Add node name to workload path
|
||||
if self.__args.subpath == "node_name":
|
||||
self.__args.path = str(Path(self.__args.path) / socket.gethostname())
|
||||
# Or, add gpu model name to workload path
|
||||
elif self.__args.subpath == "gpu_model":
|
||||
self.__args.path = str(Path(self.__args.path) / self.__mspec.gpu_model)
|
||||
|
||||
# Create workload directory if it does not exist
|
||||
p = Path(self.__args.path)
|
||||
if not p.exists():
|
||||
try:
|
||||
p.mkdir(parents=True, exist_ok=False)
|
||||
except FileExistsError:
|
||||
console_error("Directory already exists.")
|
||||
|
||||
def handle_analyze_args(self) -> None:
|
||||
"""Handle analyze-specific argument processing"""
|
||||
# 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
|
||||
|
||||
@demarcate
|
||||
def list_metrics(self):
|
||||
def list_metrics(self) -> None:
|
||||
self.load_soc_specs()
|
||||
|
||||
for_current_arch = False
|
||||
@@ -271,7 +268,7 @@ class RocProfCompute:
|
||||
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)
|
||||
parser.build_dfs(arch_configs=ac, filter_metrics=[], sys_info=sys_info)
|
||||
for key, value in ac.metric_list.items():
|
||||
prefix = ""
|
||||
if "." not in str(key):
|
||||
@@ -286,7 +283,7 @@ class RocProfCompute:
|
||||
console_error("Unsupported arch")
|
||||
|
||||
@demarcate
|
||||
def list_sets(self):
|
||||
def list_sets(self) -> None:
|
||||
sets_info = parse_sets_yaml(self.__mspec.gpu_arch)
|
||||
|
||||
if not sets_info:
|
||||
@@ -334,8 +331,41 @@ class RocProfCompute:
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def create_profiler(self) -> object:
|
||||
profiler_classes = {
|
||||
"rocprofv1": (
|
||||
"rocprof_compute_profile.profiler_rocprof_v1",
|
||||
"rocprof_v1_profiler",
|
||||
),
|
||||
"rocprofv2": (
|
||||
"rocprof_compute_profile.profiler_rocprof_v2",
|
||||
"rocprof_v2_profiler",
|
||||
),
|
||||
"rocprofv3": (
|
||||
"rocprof_compute_profile.profiler_rocprof_v3",
|
||||
"rocprof_v3_profiler",
|
||||
),
|
||||
"rocprofiler-sdk": (
|
||||
"rocprof_compute_profile.profiler_rocprofiler_sdk",
|
||||
"rocprofiler_sdk_profiler",
|
||||
),
|
||||
}
|
||||
|
||||
if self.__profiler_mode not in profiler_classes:
|
||||
console_error("Unsupported profiler")
|
||||
|
||||
module_name, class_name = profiler_classes[self.__profiler_mode]
|
||||
module = importlib.import_module(module_name)
|
||||
profiler_class = getattr(module, class_name)
|
||||
|
||||
return profiler_class(
|
||||
self.__args,
|
||||
self.__profiler_mode,
|
||||
self.__soc[self.__mspec.gpu_arch],
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def run_profiler(self):
|
||||
def run_profiler(self) -> None:
|
||||
self.print_graphic()
|
||||
self.load_soc_specs()
|
||||
|
||||
@@ -346,51 +376,15 @@ class RocProfCompute:
|
||||
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")
|
||||
if "/" in self.__args.name:
|
||||
console_error('"/" is not permitted in profile name')
|
||||
|
||||
# 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],
|
||||
)
|
||||
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],
|
||||
)
|
||||
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],
|
||||
)
|
||||
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],
|
||||
)
|
||||
else:
|
||||
console_error("Unsupported profiler")
|
||||
profiler = self.create_profiler()
|
||||
|
||||
# -----------------------
|
||||
# run profiling workflow
|
||||
# -----------------------
|
||||
|
||||
profiler.sanitize()
|
||||
|
||||
# enable file-based logging
|
||||
@@ -398,28 +392,26 @@ class RocProfCompute:
|
||||
|
||||
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()
|
||||
|
||||
prof_duration = time_end_prof - time_start_prof
|
||||
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)
|
||||
)
|
||||
f'finished "run_profiling" and finished rocprof\'s workload, '
|
||||
f"time taken was {int(prof_duration / 60)} m {prof_duration % 60} sec"
|
||||
)
|
||||
|
||||
profiler.post_processing()
|
||||
time_end_post = time.time()
|
||||
|
||||
post_duration = int(time_end_post - time_end_prof)
|
||||
console_debug(f'time taken for "post_processing" was {post_duration} seconds')
|
||||
self.__soc[self.__mspec.gpu_arch].post_profiling()
|
||||
|
||||
@demarcate
|
||||
def update_db(self):
|
||||
def update_db(self) -> None:
|
||||
self.print_graphic()
|
||||
|
||||
console_warning(
|
||||
@@ -444,10 +436,9 @@ class RocProfCompute:
|
||||
return
|
||||
|
||||
@demarcate
|
||||
def run_analysis(self):
|
||||
def run_analysis(self) -> None:
|
||||
self.print_graphic()
|
||||
|
||||
console_log("Analysis mode = %s" % self.__analyze_mode)
|
||||
console_log(f"Analysis mode = {self.__analyze_mode}")
|
||||
|
||||
if self.__analyze_mode == "cli":
|
||||
from rocprof_compute_analyze.analysis_cli import cli_analysis
|
||||
@@ -467,7 +458,7 @@ class RocProfCompute:
|
||||
|
||||
analyzer = db_analysis(self.__args, self.__supported_archs)
|
||||
else:
|
||||
console_error("Unsupported analysis mode -> %s" % self.__analyze_mode)
|
||||
console_error(f"Unsupported analysis mode -> {self.__analyze_mode}")
|
||||
|
||||
# -----------------------
|
||||
# run analysis workflow
|
||||
@@ -476,12 +467,10 @@ class RocProfCompute:
|
||||
|
||||
# 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
|
||||
and not analyzer.get_args().spatial_multiplexing
|
||||
else file_io.find_1st_sub_dir(d[0])
|
||||
)
|
||||
sys_info = file_io.load_sys_info(sysinfo_path / "sysinfo.csv")
|
||||
@@ -493,5 +482,3 @@ class RocProfCompute:
|
||||
analyzer.set_soc(self.__soc)
|
||||
analyzer.pre_processing()
|
||||
analyzer.run_analysis()
|
||||
|
||||
return
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import glob
|
||||
import os
|
||||
@@ -32,10 +33,13 @@ import shutil
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import (
|
||||
console_debug,
|
||||
console_error,
|
||||
@@ -54,21 +58,28 @@ from utils.utils import (
|
||||
|
||||
|
||||
class RocProfCompute_Base:
|
||||
def __init__(self, args, profiler_mode, soc):
|
||||
def __init__(
|
||||
self,
|
||||
args: argparse.Namespace,
|
||||
profiler_mode: str,
|
||||
soc: OmniSoC_Base,
|
||||
) -> None:
|
||||
self.__args = args
|
||||
self.__profiler = profiler_mode
|
||||
self._soc = soc # OmniSoC obj
|
||||
|
||||
def get_args(self):
|
||||
def get_args(self) -> argparse.Namespace:
|
||||
return self.__args
|
||||
|
||||
def get_profiler_options(self, fname, soc):
|
||||
def get_profiler_options(
|
||||
self, fname: str, soc: OmniSoC_Base
|
||||
) -> Union[list[str], dict[str, Any]]:
|
||||
"""Fetch any version specific arguments required by profiler"""
|
||||
# assume no SoC specific options and return empty list by default
|
||||
return []
|
||||
|
||||
@demarcate
|
||||
def sanitize(self):
|
||||
def sanitize(self) -> None:
|
||||
"""Perform sanitization of inputs"""
|
||||
args = self.get_args()
|
||||
|
||||
@@ -103,26 +114,27 @@ class RocProfCompute_Base:
|
||||
args.remaining = " ".join(args.remaining)
|
||||
else:
|
||||
console_error(
|
||||
(
|
||||
"Profiling command required. Pass application executable after -- "
|
||||
"at the end of options.\n"
|
||||
"\t\ti.e. rocprof-compute profile -n vcopy -- "
|
||||
"./vcopy -n 1048576 -b 256"
|
||||
)
|
||||
"Profiling command required. Pass application executable after -- "
|
||||
"at the end of options.\n"
|
||||
"\ti.e. rocprof-compute profile -n vcopy -- "
|
||||
"./vcopy -n 1048576 -b 256"
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def join_prof(self, out=None):
|
||||
def join_prof(self, out: Optional[str] = None) -> Optional[pd.DataFrame]:
|
||||
"""Manually join separated rocprof runs"""
|
||||
if self.get_args().format_rocprof_output == "rocpd":
|
||||
args = self.get_args()
|
||||
output_file = out or f"{args.path}/pmc_perf.csv"
|
||||
|
||||
# handle rocpd format
|
||||
if args.format_rocprof_output == "rocpd":
|
||||
# Vertically concat (by rows) results_*.csv into pmc_perf.csv
|
||||
result_files = glob.glob(self.get_args().path + "/results_*.csv")
|
||||
if out is None:
|
||||
out = self.__args.path + "/pmc_perf.csv"
|
||||
with open(out, "w", newline="") as outfile:
|
||||
result_files = glob.glob(f"{args.path}/results_*.csv")
|
||||
|
||||
with open(output_file, "w", newline="") as outfile:
|
||||
writer = None
|
||||
for file in result_files:
|
||||
with open(file, "r", newline="") as infile:
|
||||
with open(file, newline="") as infile:
|
||||
reader = csv.reader(infile)
|
||||
header = next(reader)
|
||||
# Write header only once
|
||||
@@ -131,7 +143,9 @@ class RocProfCompute_Base:
|
||||
writer.writerow(header)
|
||||
for row in reader:
|
||||
writer.writerow(row)
|
||||
console_debug(f"Created file: {out}")
|
||||
|
||||
console_debug(f"Created file: {output_file}")
|
||||
|
||||
# Delete results_*.csv files
|
||||
for file in result_files:
|
||||
os.remove(file)
|
||||
@@ -139,14 +153,15 @@ class RocProfCompute_Base:
|
||||
return
|
||||
|
||||
# Set default output directory if not specified
|
||||
if isinstance(self.__args.path, str):
|
||||
if out is None:
|
||||
out = self.__args.path + "/pmc_perf.csv"
|
||||
files = glob.glob(self.__args.path + "/" + "pmc_perf_*.csv")
|
||||
files.extend(glob.glob(self.__args.path + "/" + "SQ_*.csv"))
|
||||
files.extend(glob.glob(self.__args.path + "/" + "SQC_*.csv"))
|
||||
if isinstance(args.path, str):
|
||||
csv_patterns = ["pmc_perf_*.csv", "SQ_*.csv", "SQC_*.csv"]
|
||||
files = [
|
||||
file
|
||||
for pattern in csv_patterns
|
||||
for file in glob.glob(f"{args.path}/{pattern}")
|
||||
]
|
||||
|
||||
if self.get_args().hip_trace:
|
||||
if args.hip_trace:
|
||||
# remove hip api trace ouputs from this list
|
||||
files = [
|
||||
f
|
||||
@@ -156,7 +171,7 @@ class RocProfCompute_Base:
|
||||
)
|
||||
]
|
||||
|
||||
if self.get_args().kokkos_trace:
|
||||
if args.kokkos_trace:
|
||||
# remove marker api trace ouputs from this list
|
||||
files = [
|
||||
f
|
||||
@@ -165,39 +180,39 @@ class RocProfCompute_Base:
|
||||
os.path.basename(f)
|
||||
)
|
||||
]
|
||||
elif isinstance(self.__args.path, list):
|
||||
files = self.__args.path
|
||||
elif isinstance(args.path, list):
|
||||
files = args.path
|
||||
else:
|
||||
console_error(
|
||||
"Invalid workload directory. Cannot resolve %s" % self.__args.path
|
||||
)
|
||||
console_error(f"Invalid workload directory. Cannot resolve {args.path}")
|
||||
|
||||
# Process files and create joined dataframe
|
||||
df = None
|
||||
for i, file in enumerate(files):
|
||||
_df = pd.read_csv(file) if isinstance(self.__args.path, str) else file
|
||||
if self.__args.join_type == "kernel":
|
||||
key = _df.groupby("Kernel_Name").cumcount()
|
||||
_df["key"] = _df.Kernel_Name + " - " + key.astype(str)
|
||||
elif self.__args.join_type == "grid":
|
||||
key = _df.groupby(["Kernel_Name", "Grid_Size"]).cumcount()
|
||||
_df["key"] = (
|
||||
_df["Kernel_Name"]
|
||||
current_df = pd.read_csv(file)
|
||||
if args.join_type == "kernel":
|
||||
key = current_df.groupby("Kernel_Name").cumcount()
|
||||
current_df["key"] = current_df.Kernel_Name + " - " + key.astype(str)
|
||||
elif args.join_type == "grid":
|
||||
key = current_df.groupby(["Kernel_Name", "Grid_Size"]).cumcount()
|
||||
current_df["key"] = (
|
||||
current_df["Kernel_Name"].astype(str)
|
||||
+ " - "
|
||||
+ _df["Grid_Size"].astype(str)
|
||||
+ current_df["Grid_Size"].astype(str)
|
||||
+ " - "
|
||||
+ key.astype(str)
|
||||
)
|
||||
else:
|
||||
console_error(
|
||||
"%s is an unrecognized option for --join-type"
|
||||
% self.__args.join_type
|
||||
f"{args.join_type} is an unrecognized option for --join-type"
|
||||
)
|
||||
|
||||
if df is None:
|
||||
df = _df
|
||||
df = current_df
|
||||
else:
|
||||
# join by unique index of kernel
|
||||
df = pd.merge(df, _df, how="inner", on="key", suffixes=("", f"_{i}"))
|
||||
df = pd.merge(
|
||||
df, current_df, how="inner", on="key", suffixes=("", f"_{i}")
|
||||
)
|
||||
|
||||
if df is None or df.empty:
|
||||
return
|
||||
@@ -217,6 +232,7 @@ class RocProfCompute_Base:
|
||||
],
|
||||
"SGPR": [col for col in df.columns if col.startswith("SGPR")],
|
||||
}
|
||||
|
||||
# Check for vgpr counter in ROCm < 5.3
|
||||
if "vgpr" in df.columns:
|
||||
duplicate_cols["vgpr"] = [
|
||||
@@ -230,108 +246,106 @@ class RocProfCompute_Base:
|
||||
duplicate_cols["Accum_VGPR"] = [
|
||||
col for col in df.columns if col.startswith("Accum_VGPR")
|
||||
]
|
||||
|
||||
for key, cols in duplicate_cols.items():
|
||||
_df = df[cols]
|
||||
if not test_df_column_equality(_df):
|
||||
msg = "Detected differing {} values while joining pmc_perf.csv".format(
|
||||
key
|
||||
current_df = df[cols]
|
||||
if not test_df_column_equality(current_df):
|
||||
console_warning(
|
||||
f"Detected differing {key} values while joining pmc_perf.csv"
|
||||
)
|
||||
console_warning(msg)
|
||||
else:
|
||||
msg = "Successfully joined {} in pmc_perf.csv".format(key)
|
||||
console_debug(msg)
|
||||
console_debug(f"Successfully joined {key} in pmc_perf.csv")
|
||||
|
||||
# now, we can:
|
||||
# A) throw away any of the "boring" duplicates
|
||||
columns_to_remove = [
|
||||
# rocprofv2 headers
|
||||
"GPU_ID_",
|
||||
"Grid_Size_",
|
||||
"Workgroup_Size_",
|
||||
"LDS_Per_Workgroup_",
|
||||
"Scratch_Per_Workitem_",
|
||||
"vgpr_",
|
||||
"Arch_VGPR_",
|
||||
"Accum_VGPR_",
|
||||
"SGPR_",
|
||||
"Dispatch_ID_",
|
||||
"Queue_ID",
|
||||
"Queue_Index",
|
||||
"PID",
|
||||
"TID",
|
||||
"SIG",
|
||||
"OBJ",
|
||||
"Correlation_ID_",
|
||||
"Wave_Size_",
|
||||
# rocscope specific merged counters, keep original
|
||||
"dispatch_",
|
||||
# extras
|
||||
"sig",
|
||||
"queue-id",
|
||||
"queue-index",
|
||||
"pid",
|
||||
"tid",
|
||||
"fbar",
|
||||
]
|
||||
|
||||
df = df[
|
||||
[
|
||||
k
|
||||
for k in df.keys()
|
||||
if not any(
|
||||
k.startswith(check)
|
||||
for check in [
|
||||
# rocprofv2 headers
|
||||
"GPU_ID_",
|
||||
"Grid_Size_",
|
||||
"Workgroup_Size_",
|
||||
"LDS_Per_Workgroup_",
|
||||
"Scratch_Per_Workitem_",
|
||||
"vgpr_",
|
||||
"Arch_VGPR_",
|
||||
"Accum_VGPR_",
|
||||
"SGPR_",
|
||||
"Dispatch_ID_",
|
||||
"Queue_ID",
|
||||
"Queue_Index",
|
||||
"PID",
|
||||
"TID",
|
||||
"SIG",
|
||||
"OBJ",
|
||||
"Correlation_ID_",
|
||||
"Wave_Size_",
|
||||
# rocscope specific merged counters, keep original
|
||||
"dispatch_",
|
||||
# extras
|
||||
"sig",
|
||||
"queue-id",
|
||||
"queue-index",
|
||||
"pid",
|
||||
"tid",
|
||||
"fbar",
|
||||
]
|
||||
)
|
||||
col
|
||||
for col in df.columns
|
||||
if not any(col.startswith(prefix) for prefix in columns_to_remove)
|
||||
]
|
||||
]
|
||||
|
||||
# B) any timestamps that are _not_ the duration,
|
||||
# which is the one we care about
|
||||
timestamp_patterns = ["DispatchNs", "CompleteNs", "HostDuration"]
|
||||
|
||||
df = df[
|
||||
[
|
||||
k
|
||||
for k in df.keys()
|
||||
if not any(
|
||||
check in k
|
||||
for check in [
|
||||
"DispatchNs",
|
||||
"CompleteNs",
|
||||
# rocscope specific timestamp
|
||||
"HostDuration",
|
||||
]
|
||||
)
|
||||
col
|
||||
for col in df.columns
|
||||
if not any(pattern in col for pattern in timestamp_patterns)
|
||||
]
|
||||
]
|
||||
|
||||
# C) sanity check the name and key
|
||||
namekeys = [k for k in df.keys() if "Kernel_Name" in k]
|
||||
assert len(namekeys)
|
||||
for k in namekeys[1:]:
|
||||
assert (df[namekeys[0]] == df[k]).all()
|
||||
df = df.drop(columns=namekeys[1:])
|
||||
name_cols = [col for col in df.columns if "Kernel_Name" in col]
|
||||
if not name_cols:
|
||||
return df
|
||||
|
||||
for col in name_cols[1:]:
|
||||
assert (df[name_cols[0]] == df[col]).all()
|
||||
|
||||
df = df.drop(columns=name_cols[1:])
|
||||
|
||||
# now take the median of the durations
|
||||
bkeys = []
|
||||
ekeys = []
|
||||
for k in df.keys():
|
||||
if "Start_Timestamp" in k:
|
||||
bkeys.append(k)
|
||||
if "End_Timestamp" in k:
|
||||
ekeys.append(k)
|
||||
# compute mean begin and end timestamps
|
||||
endNs = df[ekeys].mean(axis=1)
|
||||
beginNs = df[bkeys].mean(axis=1)
|
||||
# and replace
|
||||
df = df.drop(columns=bkeys)
|
||||
df = df.drop(columns=ekeys)
|
||||
df["Start_Timestamp"] = beginNs
|
||||
df["End_Timestamp"] = endNs
|
||||
start_cols = [col for col in df.columns if "Start_Timestamp" in col]
|
||||
end_cols = [col for col in df.columns if "End_Timestamp" in col]
|
||||
|
||||
# compute mean mean timestamps
|
||||
if start_cols and end_cols:
|
||||
mean_start = df[start_cols].mean(axis=1)
|
||||
mean_end = df[end_cols].mean(axis=1)
|
||||
|
||||
# Replace with consolidated timestamps
|
||||
df = df.drop(columns=start_cols + end_cols)
|
||||
df["Start_Timestamp"] = mean_start
|
||||
df["End_Timestamp"] = mean_end
|
||||
|
||||
# finally, join the drop key
|
||||
df = df.drop(columns=["key"])
|
||||
|
||||
# save to file and delete old file(s)
|
||||
# skip if we're being called outside of rocprof-compute
|
||||
if isinstance(self.__args.path, str):
|
||||
df.to_csv(out, index=False)
|
||||
if not self.__args.verbose:
|
||||
if isinstance(args.path, str):
|
||||
df.to_csv(output_file, index=False)
|
||||
if not args.verbose:
|
||||
for file in files:
|
||||
# Do not remove accumulate counter files
|
||||
if "SQ_" not in file or "SQC_" not in file:
|
||||
os.remove(file)
|
||||
return None
|
||||
else:
|
||||
return df
|
||||
|
||||
@@ -339,14 +353,15 @@ class RocProfCompute_Base:
|
||||
# Required methods to be implemented by child classes
|
||||
# ----------------------------------------------------
|
||||
@abstractmethod
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
"""Perform any pre-processing steps prior to profiling."""
|
||||
console_debug("profiling", "pre-processing using %s profiler" % self.__profiler)
|
||||
args = self.get_args()
|
||||
console_debug("profiling", f"pre-processing using {self.__profiler} profiler")
|
||||
|
||||
self._filter_blocks = self._soc.profiling_setup()
|
||||
|
||||
# Write profiling configuration as yaml file
|
||||
with open(Path(self.__args.path).joinpath("profiling_config.yaml"), "w") as f:
|
||||
with open(f"{self.__args.path}/profiling_config.yaml", "w") as f:
|
||||
args_dict = vars(self.__args)
|
||||
# Override filter_blocks when writing profiling config yaml
|
||||
args_dict["filter_blocks"] = self._filter_blocks
|
||||
@@ -356,63 +371,56 @@ class RocProfCompute_Base:
|
||||
# verify soc compatibility
|
||||
if self.__profiler not in self._soc.get_compatible_profilers():
|
||||
console_error(
|
||||
"%s is not enabled in %s. Available profilers include: %s"
|
||||
% (
|
||||
self._soc.get_arch(),
|
||||
self.__profiler,
|
||||
self._soc.get_compatible_profilers(),
|
||||
)
|
||||
f"{self._soc.get_arch()} is not enabled in {self.__profiler}. "
|
||||
f"Available profilers include: {self._soc.get_compatible_profilers()}"
|
||||
)
|
||||
|
||||
gen_sysinfo(
|
||||
workload_name=self.__args.name,
|
||||
workload_dir=self.get_args().path,
|
||||
app_cmd=self.__args.remaining,
|
||||
skip_roof=self.__args.no_roof,
|
||||
workload_name=args.name,
|
||||
workload_dir=args.path,
|
||||
app_cmd=args.remaining,
|
||||
skip_roof=args.no_roof,
|
||||
mspec=self._soc._mspec,
|
||||
soc=self._soc,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def run_profiling(self, version: str, prog: str):
|
||||
def run_profiling(self, version: str, prog: str) -> None:
|
||||
"""Run profiling."""
|
||||
console_debug(
|
||||
"profiling", "performing profiling using %s profiler" % self.__profiler
|
||||
"profiling", f"performing profiling using {self.__profiler} profiler"
|
||||
)
|
||||
args = self.get_args()
|
||||
|
||||
# log basic info
|
||||
console_log(str(prog).title() + " version: " + str(version))
|
||||
console_log("Profiler choice: %s" % self.__profiler)
|
||||
console_log("Path: " + str(Path(self.__args.path).absolute().resolve()))
|
||||
console_log("Target: " + str(self._soc._mspec.gpu_model))
|
||||
console_log("Command: " + str(self.__args.remaining))
|
||||
console_log("Kernel Selection: " + str(self.__args.kernel))
|
||||
console_log("Dispatch Selection: " + str(self.__args.dispatch))
|
||||
console_log(f"{str(prog).title()} version: {version}")
|
||||
console_log(f"Profiler choice: {self.__profiler}")
|
||||
console_log(f"Path: {Path(self.__args.path).absolute().resolve()}")
|
||||
console_log(f"Target: {self._soc._mspec.gpu_model}")
|
||||
console_log(f"Command: {args.remaining}")
|
||||
console_log(f"Kernel Selection: {args.kernel}")
|
||||
console_log(f"Dispatch Selection: {args.dispatch}")
|
||||
if self._filter_blocks:
|
||||
console_log(f"Filtered sections: {str(self._filter_blocks)}")
|
||||
else:
|
||||
console_log("Filtered sections: All")
|
||||
|
||||
msg = "Collecting Performance Counters"
|
||||
(
|
||||
print_status(msg)
|
||||
if not self.__args.roof_only
|
||||
else print_status(msg + " (Roofline Only)")
|
||||
)
|
||||
status_msg = f"{msg} (Roofline Only)" if self.__args.roof_only else msg
|
||||
print_status(status_msg)
|
||||
|
||||
# Run profiling on each input file
|
||||
input_files = glob.glob(self.get_args().path + "/perfmon/*.txt")
|
||||
input_files.sort()
|
||||
|
||||
input_files = sorted(glob.glob(f"{args.path}/perfmon/*.txt"))
|
||||
total_runs = len(input_files)
|
||||
total_profiling_time_so_far = 0
|
||||
avg_profiling_time = 0
|
||||
total_profiling_time = 0.0
|
||||
|
||||
for i, fname in enumerate(input_files):
|
||||
run_number = i + 1
|
||||
|
||||
# Log progress and time estimation
|
||||
if i > 0:
|
||||
avg_profiling_time = total_profiling_time_so_far / i
|
||||
time_left_seconds = (total_runs - run_number) * avg_profiling_time
|
||||
avg_time = total_profiling_time / i
|
||||
time_left_seconds = (total_runs - run_number) * avg_time
|
||||
time_left = format_time(time_left_seconds)
|
||||
console_log(
|
||||
f"[Run {run_number}/{total_runs}]"
|
||||
@@ -426,15 +434,12 @@ class RocProfCompute_Base:
|
||||
)
|
||||
|
||||
# Kernel filtering (in-place replacement)
|
||||
if not self.__args.kernel == None:
|
||||
if not args.kernel == None:
|
||||
success, output = capture_subprocess_output([
|
||||
"sed",
|
||||
"-i",
|
||||
"-r",
|
||||
"s%^(kernel:).*%"
|
||||
+ "kernel: "
|
||||
+ ",".join(self.__args.kernel)
|
||||
+ "%g",
|
||||
f"s%^(kernel:).*%kernel: {','.join(self.__args.kernel)}%g",
|
||||
fname,
|
||||
])
|
||||
# log output from profile filtering
|
||||
@@ -444,15 +449,12 @@ class RocProfCompute_Base:
|
||||
console_debug(output)
|
||||
|
||||
# Dispatch filtering (inplace replacement)
|
||||
if not self.__args.dispatch == None:
|
||||
if not args.dispatch == None:
|
||||
success, output = capture_subprocess_output([
|
||||
"sed",
|
||||
"-i",
|
||||
"-r",
|
||||
"s%^(range:).*%"
|
||||
+ "range: "
|
||||
+ " ".join(self.__args.dispatch)
|
||||
+ "%g",
|
||||
f"s%^(range:).*%range: {' '.join(self.__args.dispatch)}%g",
|
||||
fname,
|
||||
])
|
||||
# log output from profile filtering
|
||||
@@ -460,73 +462,76 @@ class RocProfCompute_Base:
|
||||
console_error(output)
|
||||
else:
|
||||
console_debug(output)
|
||||
console_log("profiling", "Current input file: %s" % fname)
|
||||
|
||||
options = self.get_profiler_options(fname, self._soc)
|
||||
if (
|
||||
self.__profiler == "rocprofv1"
|
||||
or self.__profiler == "rocprofv2"
|
||||
or self.__profiler == "rocprofv3"
|
||||
or self.__profiler == "rocprofiler-sdk"
|
||||
console_log("profiling", f"Current input file: {fname}")
|
||||
|
||||
if self.__profiler in (
|
||||
"rocprofv1",
|
||||
"rocprofv2",
|
||||
"rocprofv3",
|
||||
"rocprofiler-sdk",
|
||||
):
|
||||
start_run_prof = time.time()
|
||||
options = self.get_profiler_options(fname, self._soc)
|
||||
start_time = time.time()
|
||||
run_prof(
|
||||
fname=fname,
|
||||
profiler_options=options,
|
||||
workload_dir=self.get_args().path,
|
||||
workload_dir=args.path,
|
||||
mspec=self._soc._mspec,
|
||||
loglevel=self.get_args().loglevel,
|
||||
format_rocprof_output=self.get_args().format_rocprof_output,
|
||||
retain_rocpd_output=self.get_args().retain_rocpd_output,
|
||||
loglevel=args.loglevel,
|
||||
format_rocprof_output=args.format_rocprof_output,
|
||||
retain_rocpd_output=args.retain_rocpd_output,
|
||||
)
|
||||
end_run_prof = time.time()
|
||||
actual_profiling_duration = end_run_prof - start_run_prof
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
total_profiling_time += duration
|
||||
|
||||
console_debug(
|
||||
"The time of run_prof of {} is {} m {} sec".format(
|
||||
fname,
|
||||
int((end_run_prof - start_run_prof) / 60),
|
||||
str((end_run_prof - start_run_prof) % 60),
|
||||
)
|
||||
f"The time of run_prof of {fname} is {int(duration / 60)} min"
|
||||
f" {duration % 60} sec"
|
||||
)
|
||||
else:
|
||||
console_error("Profiler not supported")
|
||||
total_profiling_time_so_far += actual_profiling_duration
|
||||
# PC sampling data is only collected when block "21" is specified
|
||||
if "21" in self.get_args().filter_blocks and self.__profiler in (
|
||||
"rocprofv3",
|
||||
"rocprofiler-sdk",
|
||||
):
|
||||
console_log(
|
||||
f"[Run {total_runs + 1}/{total_runs + 1}][PC sampling profile run]"
|
||||
)
|
||||
start_run_prof = time.time()
|
||||
pc_sampling_prof(
|
||||
method=self.get_args().pc_sampling_method,
|
||||
interval=self.get_args().pc_sampling_interval,
|
||||
workload_dir=self.get_args().path,
|
||||
appcmd=shlex.split(
|
||||
self.get_args().remaining
|
||||
), # FIXME: the right solution is applying it when argparsing once!
|
||||
rocprofiler_sdk_library_path=self.get_args().rocprofiler_sdk_library_path,
|
||||
)
|
||||
end_run_prof = time.time()
|
||||
pc_sampling_duration = end_run_prof - start_run_prof
|
||||
console_debug(
|
||||
"The time of pc sampling profiling is {} m {} sec".format(
|
||||
int((pc_sampling_duration) / 60),
|
||||
str((pc_sampling_duration) % 60),
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def post_processing(self):
|
||||
"""Perform any post-processing steps prior to profiling."""
|
||||
# PC sampling data is only collected when block "21" is specified
|
||||
if not (
|
||||
"21" in args.filter_blocks
|
||||
and self.__profiler in ("rocprofv3", "rocprofiler-sdk")
|
||||
):
|
||||
return
|
||||
|
||||
input_files = glob.glob(f"{args.path}/perfmon/*.txt")
|
||||
total_runs = len(input_files)
|
||||
|
||||
console_log(f"[Run {total_runs + 1}/{total_runs + 1}][PC sampling profile run]")
|
||||
|
||||
start_time = time.time()
|
||||
pc_sampling_prof(
|
||||
method=args.pc_sampling_method,
|
||||
interval=args.pc_sampling_interval,
|
||||
workload_dir=args.path,
|
||||
appcmd=shlex.split(
|
||||
args.remaining
|
||||
), # FIXME: the right solution is applying it when argparsing once!
|
||||
rocprofiler_sdk_library_path=args.rocprofiler_sdk_library_path,
|
||||
)
|
||||
end_time = time.time()
|
||||
|
||||
duration = end_time - start_time
|
||||
console_debug(
|
||||
"profiling",
|
||||
"performing post-processing using %s profiler" % self.__profiler,
|
||||
f"The time of pc sampling profiling is {int(duration / 60)} m "
|
||||
f"{duration % 60} sec",
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def post_processing(self) -> None:
|
||||
"""Perform any post-processing steps prior to profiling."""
|
||||
console_debug(
|
||||
"profiling", f"performing post-processing using {self.__profiler} profiler"
|
||||
)
|
||||
self._soc.post_profiling()
|
||||
|
||||
|
||||
def test_df_column_equality(df):
|
||||
def test_df_column_equality(df: pd.DataFrame) -> np.bool:
|
||||
return df.eq(df.iloc[:, 0], axis=0).all(1).all()
|
||||
|
||||
@@ -23,26 +23,34 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
|
||||
from rocprof_compute_profile.profiler_base import RocProfCompute_Base
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import console_error, console_log, demarcate
|
||||
|
||||
|
||||
class rocprof_v3_profiler(RocProfCompute_Base):
|
||||
def __init__(self, profiling_args, profiler_mode, soc):
|
||||
def __init__(
|
||||
self,
|
||||
profiling_args: argparse.Namespace,
|
||||
profiler_mode: str,
|
||||
soc: OmniSoC_Base,
|
||||
) -> None:
|
||||
super().__init__(profiling_args, profiler_mode, soc)
|
||||
self.ready_to_profile = (
|
||||
self.get_args().roof_only
|
||||
and not Path(self.get_args().path).joinpath("pmc_perf.csv").is_file()
|
||||
and not (Path(self.get_args().path) / "pmc_perf.csv").is_file()
|
||||
or not self.get_args().roof_only
|
||||
)
|
||||
|
||||
def get_profiler_options(self, fname, soc):
|
||||
app_cmd = shlex.split(self.get_args().remaining)
|
||||
trace_option = "--kernel-trace"
|
||||
if self.get_args().kokkos_trace:
|
||||
def get_profiler_options(self, fname: str, soc: OmniSoC_Base) -> list[str]:
|
||||
args = self.get_args()
|
||||
app_cmd = shlex.split(args.remaining)
|
||||
|
||||
if args.kokkos_trace:
|
||||
trace_option = "--kokkos-trace"
|
||||
# NOTE: --kokkos-trace feature is incomplete and is disabled for now.
|
||||
console_error(
|
||||
@@ -50,61 +58,69 @@ class rocprof_v3_profiler(RocProfCompute_Base):
|
||||
"version of rocprof-compute. This functionality is planned for a "
|
||||
"future release. Please adjust your profiling options accordingly."
|
||||
)
|
||||
if self.get_args().hip_trace:
|
||||
elif args.hip_trace:
|
||||
trace_option = "--hip-trace"
|
||||
else:
|
||||
trace_option = "--kernel-trace"
|
||||
|
||||
args = [
|
||||
profiling_options = [
|
||||
# v3 requires output directory argument
|
||||
"-d",
|
||||
self.get_args().path + "/" + "out",
|
||||
f"{self.get_args().path}/out",
|
||||
trace_option,
|
||||
"--output-format",
|
||||
self.get_args().format_rocprof_output,
|
||||
args.format_rocprof_output,
|
||||
]
|
||||
|
||||
# Kernel filtering
|
||||
if self.get_args().kernel:
|
||||
args.extend(["--kernel-include-regex", "|".join(self.get_args().kernel)])
|
||||
if args.kernel:
|
||||
profiling_options.extend(["--kernel-include-regex", "|".join(args.kernel)])
|
||||
|
||||
# Dispatch filtering
|
||||
dispatch = []
|
||||
# rocprofv3 dispatch indexing is inclusive and starts from 1
|
||||
if self.get_args().dispatch:
|
||||
for dispatch_id in self.get_args().dispatch:
|
||||
if args.dispatch:
|
||||
for dispatch_id in args.dispatch:
|
||||
if ":" in dispatch_id:
|
||||
tokens = dispatch_id.split(":")
|
||||
# 4:7 -> 5-7
|
||||
dispatch.append(f"{int(tokens[0]) + 1}-{tokens[1]}")
|
||||
start, end = dispatch_id.split(":")
|
||||
dispatch.append(f"{int(start) + 1}-{end}")
|
||||
else:
|
||||
# 4 -> 5
|
||||
dispatch.append(f"{int(dispatch_id) + 1}")
|
||||
if dispatch:
|
||||
args.extend(["--kernel-iteration-range", f"[{','.join(dispatch)}]"])
|
||||
args.append("--")
|
||||
args.extend(app_cmd)
|
||||
return args
|
||||
profiling_options.extend([
|
||||
"--kernel-iteration-range",
|
||||
f"[{','.join(dispatch)}]",
|
||||
])
|
||||
|
||||
profiling_options.append("--")
|
||||
profiling_options.extend(app_cmd)
|
||||
return profiling_options
|
||||
|
||||
# -----------------------
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
"""Perform any pre-processing steps prior to profiling."""
|
||||
super().pre_processing()
|
||||
|
||||
@demarcate
|
||||
def run_profiling(self, version, prog):
|
||||
def run_profiling(self, version: str, prog: str) -> None:
|
||||
"""Run profiling."""
|
||||
if self.ready_to_profile:
|
||||
if self.get_args().roof_only:
|
||||
console_log(
|
||||
"roofline", "Generating pmc_perf.csv (roofline counters only)."
|
||||
)
|
||||
# Log profiling options and setup filtering
|
||||
super().run_profiling(version, prog)
|
||||
else:
|
||||
if not self.ready_to_profile:
|
||||
console_log("roofline", "Detected existing pmc_perf.csv")
|
||||
return
|
||||
|
||||
if self.get_args().roof_only:
|
||||
console_log("roofline", "Generating pmc_perf.csv (roofline counters only).")
|
||||
|
||||
# Log profiling options and setup filtering
|
||||
super().run_profiling(version, prog)
|
||||
|
||||
@demarcate
|
||||
def post_processing(self):
|
||||
def post_processing(self) -> None:
|
||||
"""Perform any post-processing steps prior to profiling."""
|
||||
if self.ready_to_profile:
|
||||
# Manually join each pmc_perf*.csv output
|
||||
|
||||
+42
-30
@@ -23,31 +23,43 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from rocprof_compute_profile.profiler_base import RocProfCompute_Base
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import console_error, console_log, demarcate
|
||||
|
||||
|
||||
class rocprofiler_sdk_profiler(RocProfCompute_Base):
|
||||
def __init__(self, profiling_args, profiler_mode, soc):
|
||||
def __init__(
|
||||
self,
|
||||
profiling_args: argparse.Namespace,
|
||||
profiler_mode: str,
|
||||
soc: OmniSoC_Base,
|
||||
) -> None:
|
||||
super().__init__(profiling_args, profiler_mode, soc)
|
||||
self.ready_to_profile = (
|
||||
self.get_args().roof_only
|
||||
and not Path(self.get_args().path).joinpath("pmc_perf.csv").is_file()
|
||||
and not (Path(self.get_args().path) / "pmc_perf.csv").is_file()
|
||||
or not self.get_args().roof_only
|
||||
)
|
||||
|
||||
def get_profiler_options(self, fname, soc):
|
||||
app_cmd = shlex.split(self.get_args().remaining)
|
||||
rocm_libdir = str(Path(self.get_args().rocprofiler_sdk_library_path).parent)
|
||||
def get_profiler_options(
|
||||
self, fname: str, soc: OmniSoC_Base
|
||||
) -> dict[str, Union[str, list[str]]]:
|
||||
args = self.get_args()
|
||||
app_cmd = shlex.split(args.remaining)
|
||||
|
||||
rocm_libdir = Path(args.rocprofiler_sdk_library_path).parent
|
||||
rocprofiler_sdk_tool_path = str(
|
||||
Path(rocm_libdir).joinpath("rocprofiler-sdk/librocprofiler-sdk-tool.so")
|
||||
rocm_libdir / "rocprofiler-sdk" / "librocprofiler-sdk-tool.so"
|
||||
)
|
||||
ld_preload = [
|
||||
rocprofiler_sdk_tool_path,
|
||||
self.get_args().rocprofiler_sdk_library_path,
|
||||
args.rocprofiler_sdk_library_path,
|
||||
]
|
||||
options = {
|
||||
"ROCPROFILER_LIBRARY_CTOR": "1",
|
||||
@@ -55,38 +67,38 @@ class rocprofiler_sdk_profiler(RocProfCompute_Base):
|
||||
"ROCP_TOOL_LIBRARIES": rocprofiler_sdk_tool_path,
|
||||
"LD_LIBRARY_PATH": rocm_libdir,
|
||||
"ROCPROF_KERNEL_TRACE": "1",
|
||||
"ROCPROF_OUTPUT_FORMAT": self.get_args().format_rocprof_output,
|
||||
"ROCPROF_OUTPUT_PATH": self.get_args().path + "/out/pmc_1",
|
||||
"ROCPROF_OUTPUT_FORMAT": args.format_rocprof_output,
|
||||
"ROCPROF_OUTPUT_PATH": f"{args.path}/out/pmc_1",
|
||||
}
|
||||
|
||||
if self.get_args().kokkos_trace:
|
||||
if args.kokkos_trace:
|
||||
# NOTE: --kokkos-trace feature is incomplete and is disabled for now.
|
||||
console_error(
|
||||
"The option '--kokkos-trace' is not supported in the current "
|
||||
"version of rocprof-compute. This functionality is planned for a "
|
||||
"future release. Please adjust your profiling options accordingly."
|
||||
)
|
||||
if self.get_args().hip_trace:
|
||||
if args.hip_trace:
|
||||
options["ROCPROF_HIP_COMPILER_API_TRACE"] = "1"
|
||||
options["ROCPROF_HIP_RUNTIME_API_TRACE"] = "1"
|
||||
|
||||
# Kernel filtering
|
||||
if self.get_args().kernel:
|
||||
options["ROCPROF_KERNEL_FILTER_INCLUDE_REGEX"] = "|".join(
|
||||
self.get_args().kernel
|
||||
)
|
||||
if args.kernel:
|
||||
options["ROCPROF_KERNEL_FILTER_INCLUDE_REGEX"] = "|".join(args.kernel)
|
||||
|
||||
# Dispatch filtering
|
||||
dispatch = []
|
||||
# rocprof sdk dispatch indexing is inclusive and starts from 1
|
||||
if self.get_args().dispatch:
|
||||
for dispatch_id in self.get_args().dispatch:
|
||||
if args.dispatch:
|
||||
for dispatch_id in args.dispatch:
|
||||
if ":" in dispatch_id:
|
||||
tokens = dispatch_id.split(":")
|
||||
# 4:7 -> 5-7
|
||||
dispatch.append(f"{int(tokens[0]) + 1}-{tokens[1]}")
|
||||
start, end = dispatch_id.split(":")
|
||||
dispatch.append(f"{int(start) + 1}-{end}")
|
||||
else:
|
||||
# 4 -> 5
|
||||
dispatch.append(f"{int(dispatch_id) + 1}")
|
||||
|
||||
if dispatch:
|
||||
options["ROCPROF_KERNEL_FILTER_RANGE"] = f"[{','.join(dispatch)}]"
|
||||
options["APP_CMD"] = app_cmd
|
||||
@@ -96,25 +108,25 @@ class rocprofiler_sdk_profiler(RocProfCompute_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
"""Perform any pre-processing steps prior to profiling."""
|
||||
super().pre_processing()
|
||||
|
||||
@demarcate
|
||||
def run_profiling(self, version, prog):
|
||||
def run_profiling(self, version: str, prog: str) -> None:
|
||||
"""Run profiling."""
|
||||
if self.ready_to_profile:
|
||||
if self.get_args().roof_only:
|
||||
console_log(
|
||||
"roofline", "Generating pmc_perf.csv (roofline counters only)."
|
||||
)
|
||||
# Log profiling options and setup filtering
|
||||
super().run_profiling(version, prog)
|
||||
else:
|
||||
if not self.ready_to_profile:
|
||||
console_log("roofline", "Detected existing pmc_perf.csv")
|
||||
return
|
||||
|
||||
if self.get_args().roof_only:
|
||||
console_log("roofline", "Generating pmc_perf.csv (roofline counters only).")
|
||||
|
||||
# Log profiling options and setup filtering
|
||||
super().run_profiling(version, prog)
|
||||
|
||||
@demarcate
|
||||
def post_processing(self):
|
||||
def post_processing(self) -> None:
|
||||
"""Perform any post-processing steps prior to profiling."""
|
||||
if self.ready_to_profile:
|
||||
# Manually join each pmc_perf*.csv output
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import math
|
||||
@@ -31,6 +32,7 @@ import re
|
||||
import sys
|
||||
from abc import abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -44,7 +46,8 @@ from utils.logger import (
|
||||
demarcate,
|
||||
)
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.parser import build_in_vars, supported_denom
|
||||
from utils.parser import BUILD_IN_VARS, SUPPORTED_DENOM
|
||||
from utils.specs import MachineSpecs
|
||||
from utils.utils import (
|
||||
add_counter_extra_config_input_yaml,
|
||||
capture_subprocess_output,
|
||||
@@ -59,111 +62,111 @@ from utils.utils import (
|
||||
|
||||
|
||||
class OmniSoC_Base:
|
||||
def __init__(
|
||||
self, args, mspec
|
||||
): # new info field will contain rocminfo or sysinfo to populate properties
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
# new info field will contain rocminfo or sysinfo to populate properties
|
||||
console_debug("[omnisoc init]")
|
||||
self.__args = args
|
||||
self.__arch = None
|
||||
self.__arch: Optional[str] = None
|
||||
self._mspec = mspec
|
||||
# Per IP block, max number of simultaneous counters. GFX IP Blocks.
|
||||
self.__perfmon_config = {}
|
||||
self.__soc_params = {} # SoC specifications
|
||||
self.__compatible_profilers = [] # Store profilers compatible with SoC
|
||||
self.__perfmon_config: dict[str, int] = {}
|
||||
self.__compatible_profilers: list[str] = [] # Store SoC compatible profilers
|
||||
self.populate_mspec()
|
||||
# Create roofline object if mode is provided; skip for --specs
|
||||
if hasattr(self.__args, "mode") and self.__args.mode:
|
||||
self.roofline_obj = Roofline(args, self._mspec)
|
||||
|
||||
def __hash__(self):
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.__arch)
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, type(self)):
|
||||
return NotImplemented
|
||||
return self.__arch == other.get_soc()
|
||||
|
||||
def set_perfmon_config(self, config: dict):
|
||||
def set_perfmon_config(self, config: dict[str, int]) -> None:
|
||||
self.__perfmon_config = config
|
||||
|
||||
def get_soc_param(self):
|
||||
return self.__soc_params
|
||||
|
||||
def set_arch(self, arch: str):
|
||||
def set_arch(self, arch: str) -> None:
|
||||
self.__arch = arch
|
||||
|
||||
def get_arch(self):
|
||||
return self.__arch
|
||||
|
||||
def get_args(self):
|
||||
return self.__args
|
||||
|
||||
def set_compatible_profilers(self, profiler_names: list):
|
||||
def set_compatible_profilers(self, profiler_names: list[str]) -> None:
|
||||
self.__compatible_profilers = profiler_names
|
||||
|
||||
def get_compatible_profilers(self):
|
||||
def get_arch(self) -> Optional[str]:
|
||||
return self.__arch
|
||||
|
||||
def get_args(self) -> argparse.Namespace:
|
||||
return self.__args
|
||||
|
||||
def get_compatible_profilers(self) -> list[str]:
|
||||
return self.__compatible_profilers
|
||||
|
||||
def populate_mspec(self):
|
||||
def populate_mspec(self) -> None:
|
||||
from utils.specs import run, search, total_sqc
|
||||
|
||||
if not hasattr(self._mspec, "_rocminfo") or self._mspec._rocminfo is None:
|
||||
if (
|
||||
not hasattr(self._mspec, "rocminfo_lines")
|
||||
or self._mspec.rocminfo_lines is None
|
||||
):
|
||||
return
|
||||
|
||||
# load stats from rocminfo
|
||||
self._mspec.gpu_l1 = ""
|
||||
self._mspec.gpu_l2 = ""
|
||||
for idx2, linetext in enumerate(self._mspec._rocminfo):
|
||||
|
||||
for linetext in self._mspec.rocminfo_lines:
|
||||
key = search(r"^\s*L1:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.gpu_l1 = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*L2:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.gpu_l2 = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*Max Clock Freq\. \(MHz\):\s+([0-9]+)", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.max_sclk = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*Compute Unit:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.cu_per_gpu = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*SIMDs per CU:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.simd_per_cu = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*Shader Engines:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.se_per_gpu = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*Wavefront Size:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.wave_size = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*Workgroup Max Size:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.workgroup_max_size = key
|
||||
continue
|
||||
|
||||
key = search(r"^\s*Max Waves Per CU:\s+ ([a-zA-Z0-9]+)\s*", linetext)
|
||||
if key != None:
|
||||
if key is not None:
|
||||
self._mspec.max_waves_per_cu = key
|
||||
break
|
||||
|
||||
self._mspec.sqc_per_gpu = str(
|
||||
total_sqc(
|
||||
self._mspec.gpu_arch, self._mspec.cu_per_gpu, self._mspec.se_per_gpu
|
||||
if self._mspec.gpu_arch and self._mspec.cu_per_gpu and self._mspec.se_per_gpu:
|
||||
self._mspec.sqc_per_gpu = str(
|
||||
total_sqc(
|
||||
self._mspec.gpu_arch, self._mspec.cu_per_gpu, self._mspec.se_per_gpu
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Parse json from amd-smi static --clock
|
||||
static_data = json.loads(
|
||||
@@ -187,14 +190,15 @@ class OmniSoC_Base:
|
||||
# 100 Mhz -> 100
|
||||
self._mspec.max_mclk = amd_smi_mclk.split()[0]
|
||||
|
||||
console_debug("max mem clock is {}".format(self._mspec.max_mclk))
|
||||
console_debug(f"max mem clock is {self._mspec.max_mclk}")
|
||||
|
||||
# These are just max values now, because the parsing was broken and this was
|
||||
# inconsistent with how we use the clocks elsewhere (all max, all the time)
|
||||
self._mspec.cur_sclk = self._mspec.max_sclk
|
||||
self._mspec.cur_mclk = self._mspec.max_mclk
|
||||
|
||||
self._mspec.gpu_series = mi_gpu_specs.get_gpu_series(self._mspec.gpu_arch)
|
||||
if self._mspec.gpu_arch:
|
||||
self._mspec.gpu_series = mi_gpu_specs.get_gpu_series(self._mspec.gpu_arch)
|
||||
# specify gpu model name for gfx942 hardware
|
||||
self._mspec.gpu_model = mi_gpu_specs.get_gpu_model(
|
||||
self._mspec.gpu_arch, self._mspec.gpu_chip_id
|
||||
@@ -212,7 +216,7 @@ class OmniSoC_Base:
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def detect_gpu_model(self, gpu_arch):
|
||||
def detect_gpu_model(self, gpu_arch: str) -> Optional[str]:
|
||||
"""
|
||||
Detects the GPU model using various identifiers from 'amd-smi static'.
|
||||
Falls back through multiple methods if the primary method fails.
|
||||
@@ -225,11 +229,15 @@ class OmniSoC_Base:
|
||||
static_data = run(
|
||||
["amd-smi", "static", "--gpu=0", "--json"], exit_on_error=True
|
||||
)
|
||||
gpu_list = (
|
||||
static_data
|
||||
if isinstance(static_data, list)
|
||||
else static_data.get("gpu_data", [])
|
||||
)
|
||||
try:
|
||||
parsed_data = json.loads(static_data)
|
||||
gpu_list = (
|
||||
parsed_data
|
||||
if isinstance(parsed_data, list)
|
||||
else parsed_data.get("gpu_data", [])
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
gpu_list = []
|
||||
gpu_data = gpu_list[0] if gpu_list else {}
|
||||
|
||||
# Try detection methods until we find a match
|
||||
@@ -244,7 +252,7 @@ class OmniSoC_Base:
|
||||
detected_name = gpu_data.get(section, {}).get(field, "").lower()
|
||||
for model in mi_gpu_specs.get_all_gpu_models():
|
||||
if model in detected_name:
|
||||
console_log(f"GPU model '{model}' detected using {section}.{field}")
|
||||
console_log(f'GPU model "{model}" detected using {section}.{field}')
|
||||
gpu_model = model
|
||||
break
|
||||
|
||||
@@ -255,12 +263,12 @@ class OmniSoC_Base:
|
||||
gpu_model = self._adjust_mi300_model(gpu_model.lower(), gpu_arch.lower())
|
||||
|
||||
if gpu_model.lower() not in mi_gpu_specs.get_num_xcds_dict().keys():
|
||||
console_warning(f"Unknown GPU model detected: '{gpu_model}'.")
|
||||
console_warning(f'Unknown GPU model detected: "{gpu_model}".')
|
||||
return
|
||||
|
||||
return gpu_model.upper()
|
||||
|
||||
def _adjust_mi300_model(self, gpu_model, gpu_arch):
|
||||
def _adjust_mi300_model(self, gpu_model: str, gpu_arch: str) -> str:
|
||||
"""
|
||||
Applies specific adjustments for MI300 series GPU models based on architecture.
|
||||
"""
|
||||
@@ -274,7 +282,7 @@ class OmniSoC_Base:
|
||||
return gpu_model
|
||||
|
||||
@demarcate
|
||||
def detect_counters(self):
|
||||
def detect_counters(self) -> tuple[set[str], list[str]]:
|
||||
"""
|
||||
Create a set of counters required for the selected report sections.
|
||||
Parse analysis report configuration files based on the selected report
|
||||
@@ -290,11 +298,11 @@ class OmniSoC_Base:
|
||||
}
|
||||
|
||||
filter_blocks = args.filter_blocks
|
||||
if args.set_selected:
|
||||
if args.set_selected and self.__arch:
|
||||
sets_info = parse_sets_yaml(self.__arch)
|
||||
if args.set_selected not in set(sets_info.keys()):
|
||||
console_error(
|
||||
f"argument --set: invalid choice: '{args.set_selected}' "
|
||||
f'argument --set: invalid choice: "{args.set_selected}" '
|
||||
f"(choose from {sets_info.keys()})"
|
||||
)
|
||||
filter_blocks = [
|
||||
@@ -304,25 +312,24 @@ class OmniSoC_Base:
|
||||
elif args.roof_only:
|
||||
filter_blocks = ["4"]
|
||||
|
||||
texts = list()
|
||||
texts: list[str] = []
|
||||
if not filter_blocks:
|
||||
# Select all sections by default
|
||||
for filename in config_filename_dict.values():
|
||||
with open(filename, "r") as stream:
|
||||
with open(filename) as stream:
|
||||
texts.append(stream.read())
|
||||
|
||||
for block_id in filter_blocks:
|
||||
file_id, panel_id, metric_id = convert_metric_id_to_panel_info(block_id)
|
||||
|
||||
# File id filtering
|
||||
if file_id not in config_filename_dict:
|
||||
console_warning(
|
||||
(
|
||||
f"Skipping {block_id}: file id {file_id} not found in "
|
||||
f"{config_root_dir}"
|
||||
)
|
||||
f"Skipping {block_id}: file id {file_id} not found in "
|
||||
f"{config_root_dir}"
|
||||
)
|
||||
continue
|
||||
with open(config_filename_dict[file_id], "r") as stream:
|
||||
with open(config_filename_dict[file_id]) as stream:
|
||||
file_config = yaml.safe_load(stream)
|
||||
if panel_id is None:
|
||||
# If no panel id level filtering, then read the whole file
|
||||
@@ -337,10 +344,8 @@ class OmniSoC_Base:
|
||||
}
|
||||
if panel_id not in panel_dict:
|
||||
console_warning(
|
||||
(
|
||||
f"Skipping {block_id}: metric table {panel_id} not found in "
|
||||
f"{config_filename_dict[file_id]}"
|
||||
)
|
||||
f"Skipping {block_id}: metric table {panel_id} not found in "
|
||||
f"{config_filename_dict[file_id]}"
|
||||
)
|
||||
continue
|
||||
if metric_id is None:
|
||||
@@ -355,10 +360,8 @@ class OmniSoC_Base:
|
||||
}
|
||||
if metric_id not in metric_dict:
|
||||
console_warning(
|
||||
(
|
||||
f"Skipping {block_id}: metric id {metric_id} not found in "
|
||||
f"panel id {panel_id}"
|
||||
)
|
||||
f"Skipping {block_id}: metric id {metric_id} not found in "
|
||||
f"panel id {panel_id}"
|
||||
)
|
||||
continue
|
||||
texts.append(yaml.dump(metric_dict[metric_id], sort_keys=False))
|
||||
@@ -368,23 +371,21 @@ class OmniSoC_Base:
|
||||
# Handle TCC channel counters: if hw_counter_matches has elems ending with '['
|
||||
# Expand and interleve the TCC channel counters
|
||||
# e.g. TCC_HIT[0] TCC_ATOMIC[0] ... TCC_HIT[1] TCC_ATOMIC[1] ...
|
||||
if using_v3():
|
||||
num_xcd_for_pmc_file = int(self._mspec.num_xcd)
|
||||
else:
|
||||
num_xcd_for_pmc_file = 1
|
||||
num_xcd_for_pmc_file = int(self._mspec.num_xcd) if using_v3() else 1
|
||||
|
||||
for counter_name in counters.copy():
|
||||
if counter_name.startswith("TCC") and counter_name.endswith("["):
|
||||
counters.remove(counter_name)
|
||||
counter_name = counter_name.split("[")[0]
|
||||
counters = counters.union({
|
||||
f"{counter_name}[{i}]"
|
||||
for i in range(num_xcd_for_pmc_file * int(self._mspec._l2_banks))
|
||||
for i in range(num_xcd_for_pmc_file * int(self._mspec.l2_banks))
|
||||
})
|
||||
|
||||
return counters, filter_blocks
|
||||
|
||||
@demarcate
|
||||
def perfmon_filter(self):
|
||||
def perfmon_filter(self) -> list[str]:
|
||||
"""Filter default performance counter set based on user arguments"""
|
||||
counters, filter_blocks = self.detect_counters()
|
||||
|
||||
@@ -413,7 +414,7 @@ class OmniSoC_Base:
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def parse_counters(self, config_text):
|
||||
def parse_counters(self, config_text: str) -> set[str]:
|
||||
"""
|
||||
Create a set of all hardware counters mentioned in the given config file
|
||||
content string.
|
||||
@@ -421,7 +422,7 @@ class OmniSoC_Base:
|
||||
hw_counter_matches, variable_matches = self.parse_counters_text(config_text)
|
||||
|
||||
# get hw counters and variables for all supported denominators
|
||||
for formula in supported_denom.values():
|
||||
for formula in SUPPORTED_DENOM.values():
|
||||
hw_counter_matches_denom, variable_matches_denom = self.parse_counters_text(
|
||||
formula
|
||||
)
|
||||
@@ -430,13 +431,13 @@ class OmniSoC_Base:
|
||||
|
||||
# get hw counters corresponding to variables recursively
|
||||
while variable_matches:
|
||||
subvariable_matches = set()
|
||||
subvariable_matches: set[str] = set()
|
||||
for var in variable_matches:
|
||||
if var in build_in_vars:
|
||||
if var in BUILD_IN_VARS:
|
||||
(
|
||||
hw_counter_matches_vars,
|
||||
variable_matches_vars,
|
||||
) = self.parse_counters_text(build_in_vars[var])
|
||||
) = self.parse_counters_text(BUILD_IN_VARS[var])
|
||||
hw_counter_matches.update(hw_counter_matches_vars)
|
||||
subvariable_matches.update(variable_matches_vars)
|
||||
# process new found variables
|
||||
@@ -444,7 +445,7 @@ class OmniSoC_Base:
|
||||
|
||||
return hw_counter_matches
|
||||
|
||||
def parse_counters_text(self, text):
|
||||
def parse_counters_text(self, text: str) -> tuple[set[str], set[str]]:
|
||||
"""Parse out hardware counters and variables from given text"""
|
||||
# hw counter name should start with ip block name
|
||||
# hw counter name should have all capital letters or digits
|
||||
@@ -461,8 +462,9 @@ class OmniSoC_Base:
|
||||
hw_counter_matches = hw_counter_matches - variable_matches
|
||||
return hw_counter_matches, variable_matches
|
||||
|
||||
def get_rocprof_supported_counters(self):
|
||||
rocprof_cmd = detect_rocprof(self.get_args())
|
||||
def get_rocprof_supported_counters(self) -> set[str]:
|
||||
args = self.get_args()
|
||||
rocprof_cmd = detect_rocprof(args)
|
||||
|
||||
if rocprof_cmd != "rocprofiler-sdk":
|
||||
console_warning(
|
||||
@@ -473,67 +475,55 @@ class OmniSoC_Base:
|
||||
"--rocprofiler-sdk-library-path."
|
||||
)
|
||||
|
||||
rocprof_counters = set()
|
||||
rocprof_counters: set[str] = set()
|
||||
|
||||
if str(rocprof_cmd).endswith("rocprof"):
|
||||
command = [rocprof_cmd, "--list-basic"]
|
||||
success, output = capture_subprocess_output(command, enable_logging=False)
|
||||
# return code should be 1 so success should be False
|
||||
if success:
|
||||
console_error(
|
||||
"Failed to list rocprof supported counters using command: %s"
|
||||
% command
|
||||
if rocprof_cmd.endswith("rocprof"):
|
||||
for list_type in ["--list-basic", "--list-derived"]:
|
||||
command = [rocprof_cmd, list_type]
|
||||
success, output = capture_subprocess_output(
|
||||
command, enable_logging=False
|
||||
)
|
||||
for line in output.splitlines():
|
||||
if "gpu-agent" in line:
|
||||
counters, _ = self.parse_counters_text(line.split(":")[1].strip())
|
||||
rocprof_counters.update(counters)
|
||||
# return code should be 1 so success should be False
|
||||
if success:
|
||||
console_error(
|
||||
"Failed to list rocprof supported counters using command: "
|
||||
f"{command}"
|
||||
)
|
||||
|
||||
command = [rocprof_cmd, "--list-derived"]
|
||||
success, output = capture_subprocess_output(command, enable_logging=False)
|
||||
# return code should be 1 so success should be False
|
||||
if success:
|
||||
console_error(
|
||||
"Failed to list rocprof supported counters using command: %s"
|
||||
% command
|
||||
)
|
||||
for line in output.splitlines():
|
||||
if "gpu-agent" in line:
|
||||
counters, _ = self.parse_counters_text(line.split(":")[1].strip())
|
||||
rocprof_counters.update(counters)
|
||||
|
||||
elif str(rocprof_cmd).endswith("rocprofv2"):
|
||||
for line in output.splitlines():
|
||||
if "gpu-agent" in line:
|
||||
counters, _ = self.parse_counters_text(
|
||||
line.split(":")[1].strip()
|
||||
)
|
||||
rocprof_counters.update(counters)
|
||||
elif rocprof_cmd.endswith("rocprofv2"):
|
||||
command = [rocprof_cmd, "--list-counters"]
|
||||
success, output = capture_subprocess_output(command, enable_logging=False)
|
||||
# return code should be 1 so success should be False
|
||||
if success:
|
||||
console_error(
|
||||
"Failed to list rocprof supported counters using command: %s"
|
||||
% command
|
||||
"Failed to list rocprof supported counters using command: "
|
||||
f"{command}"
|
||||
)
|
||||
|
||||
for line in output.splitlines():
|
||||
if "gfx" in line:
|
||||
counters, _ = self.parse_counters_text(line.split(":")[2].strip())
|
||||
rocprof_counters.update(counters)
|
||||
elif (
|
||||
str(rocprof_cmd).endswith("rocprofv3")
|
||||
or str(rocprof_cmd) == "rocprofiler-sdk"
|
||||
):
|
||||
elif rocprof_cmd.endswith("rocprofv3") or rocprof_cmd == "rocprofiler-sdk":
|
||||
# Point to counter definition
|
||||
old_rocprofiler_metrics_path = os.environ.get("ROCPROFILER_METRICS_PATH")
|
||||
os.environ["ROCPROFILER_METRICS_PATH"] = str(
|
||||
config.rocprof_compute_home / "rocprof_compute_soc" / "profile_configs"
|
||||
)
|
||||
sys.path.append(
|
||||
str(
|
||||
Path(self.get_args().rocprofiler_sdk_library_path).parent.parent
|
||||
/ "bin"
|
||||
)
|
||||
str(Path(args.rocprofiler_sdk_library_path).parent.parent / "bin")
|
||||
)
|
||||
|
||||
from rocprofv3_avail_module import avail
|
||||
|
||||
avail.loadLibrary.libname = str(
|
||||
Path(self.get_args().rocprofiler_sdk_library_path).parent.parent
|
||||
Path(args.rocprofiler_sdk_library_path).parent.parent
|
||||
/ "lib"
|
||||
/ "rocprofiler-sdk"
|
||||
/ "librocprofv3-list-avail.so"
|
||||
@@ -552,20 +542,20 @@ class OmniSoC_Base:
|
||||
|
||||
else:
|
||||
console_error(
|
||||
"Incompatible profiler: %s. Supported profilers include: %s"
|
||||
% (rocprof_cmd, get_submodules("rocprof_compute_profile"))
|
||||
f"Incompatible profiler: {rocprof_cmd}. Supported profilers include: "
|
||||
f"{get_submodules('rocprof_compute_profile')}"
|
||||
)
|
||||
|
||||
return rocprof_counters
|
||||
|
||||
@demarcate
|
||||
def perfmon_coalesce(self, counters):
|
||||
def perfmon_coalesce(self, counters: set[str]) -> None:
|
||||
"""
|
||||
Sort and bucket all related performance counters to minimize required
|
||||
application passes
|
||||
"""
|
||||
workload_perfmon_dir = self.get_args().path + "/perfmon"
|
||||
Path(workload_perfmon_dir).mkdir(parents=True, exist_ok=True)
|
||||
workload_perfmon_dir = Path(self.get_args().path) / "perfmon"
|
||||
workload_perfmon_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Sanity check whether counters are supported by underlying rocprof tool
|
||||
rocprof_counters = self.get_rocprof_supported_counters()
|
||||
@@ -575,27 +565,27 @@ class OmniSoC_Base:
|
||||
counter.split("[")[0] if is_tcc_channel_counter(counter) else counter
|
||||
for counter in counters
|
||||
} - rocprof_counters
|
||||
|
||||
if not_supported_counters:
|
||||
console_warning(
|
||||
"Following counters might not be supported by rocprof: %s"
|
||||
% ", ".join(not_supported_counters)
|
||||
"Following counters might not be supported by rocprof: "
|
||||
f"{', '.join(not_supported_counters)}"
|
||||
)
|
||||
|
||||
# We might be providing definitions of unsupported counters, so still try to
|
||||
# collect them
|
||||
if not counters:
|
||||
console_error(
|
||||
"profiling",
|
||||
(
|
||||
"No performance counters to collect, "
|
||||
"please check the provided profiling filters"
|
||||
),
|
||||
"No performance counters to collect, "
|
||||
"please check the provided profiling filters",
|
||||
)
|
||||
else:
|
||||
console_debug(f"Collecting following counters: {', '.join(counters)} ")
|
||||
|
||||
output_files = []
|
||||
console_debug(f"Collecting following counters: {', '.join(counters)} ")
|
||||
|
||||
output_files: list[CounterFile] = []
|
||||
accu_file_count = 0
|
||||
|
||||
# Create separate perfmon file for LEVEL counters without _sum suffix
|
||||
# TCC LEVEL counters are handled channel wise, so ignore them
|
||||
for counter in counters.copy():
|
||||
@@ -609,6 +599,7 @@ class OmniSoC_Base:
|
||||
CounterFile(counter + ".txt", self.__perfmon_config)
|
||||
)
|
||||
output_files[-1].add(counter)
|
||||
|
||||
if using_v3():
|
||||
# v3 does not support SQ_ACCUM_PREV_HIRES. Use custom counters
|
||||
# defined in counter_defs.yaml that utilize accumulate(),
|
||||
@@ -620,7 +611,8 @@ class OmniSoC_Base:
|
||||
|
||||
file_count = 0
|
||||
# Store all channels for a TCC channel counter in the same file
|
||||
tcc_channel_counter_file_map = dict()
|
||||
tcc_channel_counter_file_map: dict[str, CounterFile] = {}
|
||||
|
||||
for ctr in counters:
|
||||
# Store all channels for a TCC channel counter in the same file
|
||||
if is_tcc_channel_counter(ctr):
|
||||
@@ -628,29 +620,26 @@ class OmniSoC_Base:
|
||||
if output_file:
|
||||
output_file.add(ctr)
|
||||
continue
|
||||
|
||||
# Add counter to first file that has room
|
||||
added = False
|
||||
for i in range(len(output_files)):
|
||||
if output_files[i].add(ctr):
|
||||
for output_file in output_files:
|
||||
if output_file.add(ctr):
|
||||
added = True
|
||||
# Store all channels for a TCC channel counter in the same file
|
||||
if is_tcc_channel_counter(ctr):
|
||||
tcc_channel_counter_file_map[ctr.split("[")[0]] = output_files[
|
||||
i
|
||||
]
|
||||
tcc_channel_counter_file_map[ctr.split("[")[0]] = output_file
|
||||
break
|
||||
|
||||
# All files are full, create a new file
|
||||
if not added:
|
||||
output_files.append(
|
||||
CounterFile(
|
||||
"pmc_perf_{}.txt".format(file_count), self.__perfmon_config
|
||||
)
|
||||
CounterFile(f"pmc_perf_{file_count}.txt", self.__perfmon_config)
|
||||
)
|
||||
file_count += 1
|
||||
output_files[-1].add(ctr)
|
||||
|
||||
console_debug("profiling", "perfmon_coalesce file_count %s" % file_count)
|
||||
console_debug("profiling", f"perfmon_coalesce file_count {file_count}")
|
||||
|
||||
# TODO: rewrite the above logic for spatial_multiplexing later
|
||||
if self.get_args().spatial_multiplexing:
|
||||
@@ -661,9 +650,9 @@ class OmniSoC_Base:
|
||||
"multiplexing need provide node_idx node_count and gpu_count",
|
||||
)
|
||||
|
||||
node_idx = int(self.get_args().spatial_multiplexing[0])
|
||||
node_count = int(self.get_args().spatial_multiplexing[1])
|
||||
gpu_count = int(self.get_args().spatial_multiplexing[2])
|
||||
node_idx, node_count, gpu_count = map(
|
||||
int, self.get_args().spatial_multiplexing
|
||||
)
|
||||
|
||||
old_group_num = file_count + accu_file_count
|
||||
new_bucket_count = node_count * gpu_count
|
||||
@@ -677,34 +666,17 @@ class OmniSoC_Base:
|
||||
|
||||
console_debug(
|
||||
"profiling",
|
||||
(
|
||||
"spatial_multiplexing node_idx %s, node_count %s, gpu_count: %s,\n"
|
||||
"old_group_num %s, new_bucket_count %s, groups_per_bucket %s,\n"
|
||||
"max_groups_per_node %s, group_start %s, group_end %s"
|
||||
)
|
||||
% (
|
||||
node_idx,
|
||||
node_count,
|
||||
gpu_count,
|
||||
old_group_num,
|
||||
new_bucket_count,
|
||||
groups_per_bucket,
|
||||
max_groups_per_node,
|
||||
group_start,
|
||||
group_end,
|
||||
),
|
||||
f"spatial_multiplexing node_idx {node_idx}, node_count {node_count}, "
|
||||
f"gpu_count: {gpu_count},\n"
|
||||
f"old_group_num {old_group_num}, new_bucket_count {new_bucket_count}, "
|
||||
f"groups_per_bucket {groups_per_bucket},\n"
|
||||
f"max_groups_per_node {max_groups_per_node}, "
|
||||
f"group_start {group_start}, group_end {group_end}",
|
||||
)
|
||||
|
||||
for f_idx in range(groups_per_bucket):
|
||||
file_name = str(
|
||||
Path(workload_perfmon_dir).joinpath(
|
||||
"pmc_perf_"
|
||||
+ "node_"
|
||||
+ str(node_idx)
|
||||
+ "_"
|
||||
+ str(f_idx)
|
||||
+ ".txt"
|
||||
)
|
||||
file_name = (
|
||||
Path(workload_perfmon_dir) / f"pmc_perf_node_{node_idx}_{f_idx}.txt"
|
||||
)
|
||||
|
||||
pmc = []
|
||||
@@ -715,65 +687,55 @@ class OmniSoC_Base:
|
||||
gpu_idx = g_idx % gpu_count
|
||||
for block_name in output_files[g_idx].blocks.keys():
|
||||
for ctr in output_files[g_idx].blocks[block_name].elements:
|
||||
pmc.append(ctr + ":device=" + str(gpu_idx))
|
||||
|
||||
stext = "pmc: " + " ".join(pmc)
|
||||
pmc.append(f"{ctr}:device={gpu_idx}")
|
||||
|
||||
# Write counters to file
|
||||
fd = open(file_name, "w")
|
||||
fd.write(stext + "\n\n")
|
||||
fd.close()
|
||||
|
||||
with open(file_name, "w") as fd:
|
||||
fd.write(f"pmc: {' '.join(pmc)}\n\n")
|
||||
else:
|
||||
# Output to files
|
||||
for f in output_files:
|
||||
file_name_txt = str(
|
||||
Path(workload_perfmon_dir).joinpath(f.file_name_txt)
|
||||
)
|
||||
file_name_yaml = str(
|
||||
Path(workload_perfmon_dir).joinpath(f.file_name_yaml)
|
||||
)
|
||||
file_name_txt = workload_perfmon_dir / f.file_name_txt
|
||||
file_name_yaml = workload_perfmon_dir / f.file_name_yaml
|
||||
|
||||
pmc = []
|
||||
counter_def = dict()
|
||||
counter_def: dict[str, Any] = {}
|
||||
|
||||
for ctr in [
|
||||
ctr
|
||||
for block_name in f.blocks
|
||||
for ctr in f.blocks[block_name].elements
|
||||
]:
|
||||
pmc.append(ctr)
|
||||
if using_v3():
|
||||
# Add TCC channel counters definitions
|
||||
if is_tcc_channel_counter(ctr):
|
||||
counter_name = ctr.split("[")[0]
|
||||
idx = int(ctr.split("[")[1].split("]")[0])
|
||||
xcd_idx = idx // int(self._mspec._l2_banks)
|
||||
channel_idx = idx % int(self._mspec._l2_banks)
|
||||
expression = (
|
||||
f"select({counter_name},"
|
||||
f"[DIMENSION_XCC=[{xcd_idx}], "
|
||||
f"DIMENSION_INSTANCE=[{channel_idx}]])"
|
||||
)
|
||||
description = (
|
||||
f"{counter_name} on {xcd_idx}th XCC and "
|
||||
f"{channel_idx}th channel"
|
||||
)
|
||||
counter_def = add_counter_extra_config_input_yaml(
|
||||
counter_def,
|
||||
ctr,
|
||||
description,
|
||||
expression,
|
||||
[self.__arch],
|
||||
)
|
||||
if using_v3() and is_tcc_channel_counter(ctr):
|
||||
counter_name = ctr.split("[")[0]
|
||||
idx = int(ctr.split("[")[1].split("]")[0])
|
||||
xcd_idx = idx // int(self._mspec.l2_banks)
|
||||
channel_idx = idx % int(self._mspec.l2_banks)
|
||||
|
||||
expression = (
|
||||
f"select({counter_name},"
|
||||
f"[DIMENSION_XCC=[{xcd_idx}], "
|
||||
f"DIMENSION_INSTANCE=[{channel_idx}]])"
|
||||
)
|
||||
description = (
|
||||
f"{counter_name} on {xcd_idx}th XCC and "
|
||||
f"{channel_idx}th channel"
|
||||
)
|
||||
counter_def = add_counter_extra_config_input_yaml(
|
||||
counter_def,
|
||||
ctr,
|
||||
description,
|
||||
expression,
|
||||
[self.__arch],
|
||||
)
|
||||
|
||||
stext = "pmc: " + " ".join(pmc)
|
||||
# Write counters to file
|
||||
fd = open(file_name_txt, "w")
|
||||
fd.write(stext + "\n\n")
|
||||
fd.write("gpu:\n")
|
||||
fd.write("range:\n")
|
||||
fd.write("kernel:\n")
|
||||
fd.close()
|
||||
with open(file_name_txt, "w") as fd:
|
||||
fd.write(f"pmc: {' '.join(pmc)}\n\n")
|
||||
fd.write("gpu:\n")
|
||||
fd.write("range:\n")
|
||||
fd.write("kernel:\n")
|
||||
|
||||
# Write counter definitions to file
|
||||
if counter_def:
|
||||
@@ -783,25 +745,26 @@ class OmniSoC_Base:
|
||||
# Add a timestamp file
|
||||
# TODO: Does v3 need this?
|
||||
if not using_v3():
|
||||
fd = open(str(Path(workload_perfmon_dir).joinpath("timestamps.txt")), "w")
|
||||
fd.write("pmc:\n\n")
|
||||
fd.write("gpu:\n")
|
||||
fd.write("range:\n")
|
||||
fd.write("kernel:\n")
|
||||
fd.close()
|
||||
timestamp_file = workload_perfmon_dir / "timestamps.txt"
|
||||
with open(timestamp_file, "w") as fd:
|
||||
fd.write("pmc:\n\n")
|
||||
fd.write("gpu:\n")
|
||||
fd.write("range:\n")
|
||||
fd.write("kernel:\n")
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Required methods to be implemented by child classes
|
||||
# ----------------------------------------------------
|
||||
@abstractmethod
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> Optional[list[str]]:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
console_debug("profiling", "perform SoC profiling setup for %s" % self.__arch)
|
||||
console_debug("profiling", f"perform SoC profiling setup for {self.__arch}")
|
||||
|
||||
@abstractmethod
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
console_debug("profiling", "perform SoC post processing for %s" % self.__arch)
|
||||
console_debug("profiling", f"perform SoC post processing for {self.__arch}")
|
||||
|
||||
# Roofline can be skipped via --no-roof
|
||||
# Roofline not supported on MI 100
|
||||
# If --filter-blocks is provided, roofline block (block 4) should be mentioned
|
||||
@@ -815,23 +778,23 @@ class OmniSoC_Base:
|
||||
):
|
||||
console_log("roofline", "Skipping roofline")
|
||||
else:
|
||||
pmc_path = str(Path(self.get_args().path).joinpath("pmc_perf.csv"))
|
||||
if not Path(pmc_path).is_file():
|
||||
pmc_path = Path(self.get_args().path) / "pmc_perf.csv"
|
||||
if not pmc_path.is_file():
|
||||
console_warning(
|
||||
"Incomplete or missing profiling data. Skipping roofline."
|
||||
)
|
||||
return
|
||||
console_log(
|
||||
"roofline", "Checking for roofline.csv in " + str(self.get_args().path)
|
||||
"roofline", f"Checking for roofline.csv in {self.get_args().path}"
|
||||
)
|
||||
if not Path(self.get_args().path).joinpath("roofline.csv").is_file():
|
||||
if not (Path(self.get_args().path) / "roofline.csv").is_file():
|
||||
mibench(self.get_args(), self._mspec)
|
||||
self.roofline_obj.post_processing()
|
||||
|
||||
@abstractmethod
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(self, roofline_parameters: Optional[dict[str, Any]]) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
console_debug("analysis", "perform SoC analysis setup for %s" % self.__arch)
|
||||
console_debug("analysis", f"perform SoC analysis setup for {self.__arch}")
|
||||
if roofline_parameters:
|
||||
self.roofline_obj = Roofline(
|
||||
self.get_args(), self._mspec, roofline_parameters
|
||||
@@ -840,20 +803,20 @@ class OmniSoC_Base:
|
||||
|
||||
# Set with limited size
|
||||
class LimitedSet:
|
||||
def __init__(self, maxsize) -> None:
|
||||
self.avail = maxsize
|
||||
self.elements = []
|
||||
def __init__(self, maxsize: int) -> None:
|
||||
self.avail: int = maxsize
|
||||
self.elements: list[str] = []
|
||||
|
||||
def add(self, e) -> None:
|
||||
if e in self.elements:
|
||||
def add(self, element: str) -> bool:
|
||||
if element in self.elements:
|
||||
return True
|
||||
# Store all channels for a TCC channel counter in the same file
|
||||
if e.split("[")[0] in {element.split("[")[0] for element in self.elements}:
|
||||
self.elements.append(e)
|
||||
if element.split("[")[0] in {elem.split("[")[0] for elem in self.elements}:
|
||||
self.elements.append(element)
|
||||
return True
|
||||
if self.avail > 0:
|
||||
self.avail -= 1
|
||||
self.elements.append(e)
|
||||
self.elements.append(element)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -861,13 +824,15 @@ class LimitedSet:
|
||||
# Represents a file that lists PMC counters. Number of counters for each
|
||||
# block limited according to perfmon config.
|
||||
class CounterFile:
|
||||
def __init__(self, name, perfmon_config) -> None:
|
||||
def __init__(self, name: str, perfmon_config: dict[str, int]) -> None:
|
||||
name_no_extension = name.split(".")[0]
|
||||
self.file_name_txt = name_no_extension + ".txt"
|
||||
self.file_name_yaml = name_no_extension + ".yaml"
|
||||
self.blocks = {b: LimitedSet(v) for b, v in perfmon_config.items()}
|
||||
self.file_name_txt: str = name_no_extension + ".txt"
|
||||
self.file_name_yaml: str = name_no_extension + ".yaml"
|
||||
self.blocks: dict[str, LimitedSet] = {
|
||||
block: LimitedSet(capacity) for block, capacity in perfmon_config.items()
|
||||
}
|
||||
|
||||
def add(self, counter) -> bool:
|
||||
def add(self, counter: str) -> bool:
|
||||
block = counter.split("_")[0]
|
||||
|
||||
# SQ and SQC belong to the same IP block
|
||||
|
||||
@@ -22,22 +22,26 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
import argparse
|
||||
from typing import Any, Optional
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import console_error, demarcate
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
|
||||
class gfx908_soc(OmniSoC_Base):
|
||||
def __init__(self, args, mspec):
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
super().__init__(args, mspec)
|
||||
self.set_arch("gfx908")
|
||||
|
||||
self.set_compatible_profilers(["rocprofv1", "rocprofv3", "rocprofiler-sdk"])
|
||||
# Per IP block max number of simultaneous counters. GFX IP Blocks
|
||||
self.set_perfmon_config(mi_gpu_specs.get_perfmon_config("gfx908"))
|
||||
|
||||
# Set arch specific specs
|
||||
self._mspec._l2_banks = 32
|
||||
self._mspec.l2_banks = 32
|
||||
self._mspec.lds_banks_per_cu = 32
|
||||
self._mspec.pipes_per_gpu = 4
|
||||
|
||||
@@ -45,21 +49,24 @@ class gfx908_soc(OmniSoC_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> Optional[list[str]]:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
super().profiling_setup()
|
||||
if self.get_args().roof_only:
|
||||
console_error("%s does not support roofline analysis" % self.get_arch())
|
||||
console_error(f"{self.get_arch()} does not support roofline analysis")
|
||||
|
||||
# Perfmon filtering
|
||||
filter_blocks = self.perfmon_filter()
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
super().post_profiling()
|
||||
|
||||
@demarcate
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(
|
||||
self, roofline_parameters: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
super().analysis_setup(roofline_parameters=roofline_parameters)
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
from typing import Any, Optional
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import demarcate
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
|
||||
class gfx90a_soc(OmniSoC_Base):
|
||||
def __init__(self, args, mspec):
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
super().__init__(args, mspec)
|
||||
self.set_arch("gfx90a")
|
||||
self.set_compatible_profilers([
|
||||
@@ -43,7 +45,7 @@ class gfx90a_soc(OmniSoC_Base):
|
||||
self.set_perfmon_config(mi_gpu_specs.get_perfmon_config("gfx90a"))
|
||||
|
||||
# Set arch specific specs
|
||||
self._mspec._l2_banks = 32
|
||||
self._mspec.l2_banks = 32
|
||||
self._mspec.lds_banks_per_cu = 32
|
||||
self._mspec.pipes_per_gpu = 4
|
||||
|
||||
@@ -51,7 +53,7 @@ class gfx90a_soc(OmniSoC_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> None:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
super().profiling_setup()
|
||||
# Performance counter filtering
|
||||
@@ -59,11 +61,13 @@ class gfx90a_soc(OmniSoC_Base):
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
super().post_profiling()
|
||||
|
||||
@demarcate
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(
|
||||
self, roofline_parameters: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
super().analysis_setup(roofline_parameters=roofline_parameters)
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
from typing import Any, Optional
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import demarcate
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
|
||||
class gfx940_soc(OmniSoC_Base):
|
||||
def __init__(self, args, mspec):
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
super().__init__(args, mspec)
|
||||
self.set_arch("gfx940")
|
||||
self.set_compatible_profilers([
|
||||
@@ -43,7 +45,7 @@ class gfx940_soc(OmniSoC_Base):
|
||||
self.set_perfmon_config(mi_gpu_specs.get_perfmon_config("gfx940"))
|
||||
|
||||
# Set arch specific specs
|
||||
self._mspec._l2_banks = 16
|
||||
self._mspec.l2_banks = 16
|
||||
self._mspec.lds_banks_per_cu = 32
|
||||
self._mspec.pipes_per_gpu = 4
|
||||
|
||||
@@ -51,7 +53,7 @@ class gfx940_soc(OmniSoC_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> Optional[list[str]]:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
super().profiling_setup()
|
||||
# Performance counter filtering
|
||||
@@ -59,11 +61,13 @@ class gfx940_soc(OmniSoC_Base):
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
super().post_profiling()
|
||||
|
||||
@demarcate
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(
|
||||
self, roofline_parameters: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
super().analysis_setup(roofline_parameters=roofline_parameters)
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
from typing import Any, Optional
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import demarcate
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
|
||||
class gfx941_soc(OmniSoC_Base):
|
||||
def __init__(self, args, mspec):
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
super().__init__(args, mspec)
|
||||
self.set_arch("gfx941")
|
||||
self.set_compatible_profilers([
|
||||
@@ -43,7 +45,7 @@ class gfx941_soc(OmniSoC_Base):
|
||||
self.set_perfmon_config(mi_gpu_specs.get_perfmon_config("gfx941"))
|
||||
|
||||
# Set arch specific specs
|
||||
self._mspec._l2_banks = 16
|
||||
self._mspec.l2_banks = 16
|
||||
self._mspec.lds_banks_per_cu = 32
|
||||
self._mspec.pipes_per_gpu = 4
|
||||
|
||||
@@ -51,7 +53,7 @@ class gfx941_soc(OmniSoC_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> Optional[list[str]]:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
super().profiling_setup()
|
||||
# Performance counter filtering
|
||||
@@ -59,11 +61,13 @@ class gfx941_soc(OmniSoC_Base):
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
super().post_profiling()
|
||||
|
||||
@demarcate
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(
|
||||
self, roofline_parameters: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
super().analysis_setup(roofline_parameters=roofline_parameters)
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
from typing import Any, Optional
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import demarcate
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
|
||||
class gfx942_soc(OmniSoC_Base):
|
||||
def __init__(self, args, mspec):
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
super().__init__(args, mspec)
|
||||
self.set_arch("gfx942")
|
||||
self.set_compatible_profilers([
|
||||
@@ -43,7 +45,7 @@ class gfx942_soc(OmniSoC_Base):
|
||||
self.set_perfmon_config(mi_gpu_specs.get_perfmon_config("gfx942"))
|
||||
|
||||
# Set arch specific specs
|
||||
self._mspec._l2_banks = 16
|
||||
self._mspec.l2_banks = 16
|
||||
self._mspec.lds_banks_per_cu = 32
|
||||
self._mspec.pipes_per_gpu = 4
|
||||
|
||||
@@ -51,7 +53,7 @@ class gfx942_soc(OmniSoC_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> Optional[list[str]]:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
super().profiling_setup()
|
||||
# Performance counter filtering
|
||||
@@ -59,11 +61,13 @@ class gfx942_soc(OmniSoC_Base):
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
super().post_profiling()
|
||||
|
||||
@demarcate
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(
|
||||
self, roofline_parameters: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
super().analysis_setup(roofline_parameters=roofline_parameters)
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
from typing import Any, Optional
|
||||
|
||||
from rocprof_compute_soc.soc_base import OmniSoC_Base
|
||||
from utils.logger import demarcate
|
||||
from utils.mi_gpu_spec import mi_gpu_specs
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
|
||||
class gfx950_soc(OmniSoC_Base):
|
||||
def __init__(self, args, mspec):
|
||||
def __init__(self, args: argparse.Namespace, mspec: MachineSpecs) -> None:
|
||||
super().__init__(args, mspec)
|
||||
self.set_arch("gfx950")
|
||||
self.set_compatible_profilers(["rocprofv3", "rocprofiler-sdk"])
|
||||
@@ -38,7 +40,7 @@ class gfx950_soc(OmniSoC_Base):
|
||||
self.set_perfmon_config(mi_gpu_specs.get_perfmon_config("gfx950"))
|
||||
|
||||
# Set arch specific specs
|
||||
self._mspec._l2_banks = 16
|
||||
self._mspec.l2_banks = 16
|
||||
self._mspec.lds_banks_per_cu = 32
|
||||
self._mspec.pipes_per_gpu = 4
|
||||
|
||||
@@ -46,7 +48,7 @@ class gfx950_soc(OmniSoC_Base):
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def profiling_setup(self):
|
||||
def profiling_setup(self) -> Optional[list[str]]:
|
||||
"""Perform any SoC-specific setup prior to profiling."""
|
||||
super().profiling_setup()
|
||||
# Performance counter filtering
|
||||
@@ -54,11 +56,13 @@ class gfx950_soc(OmniSoC_Base):
|
||||
return filter_blocks
|
||||
|
||||
@demarcate
|
||||
def post_profiling(self):
|
||||
def post_profiling(self) -> None:
|
||||
"""Perform any SoC-specific post profiling activities."""
|
||||
super().post_profiling()
|
||||
|
||||
@demarcate
|
||||
def analysis_setup(self, roofline_parameters=None):
|
||||
def analysis_setup(
|
||||
self, roofline_parameters: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Perform any SoC-specific setup prior to analysis."""
|
||||
super().analysis_setup(roofline_parameters=roofline_parameters)
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
from pathlib import Path
|
||||
from typing import Any, Hashable, Optional, OrderedDict
|
||||
|
||||
import pandas as pd
|
||||
|
||||
@@ -39,17 +41,19 @@ from utils.logger import console_error, demarcate
|
||||
|
||||
|
||||
class tui_analysis(OmniAnalyze_Base):
|
||||
def __init__(self, args, supported_archs, path):
|
||||
def __init__(
|
||||
self, args: argparse.Namespace, supported_archs: dict[str, str], path: Path
|
||||
) -> None:
|
||||
super().__init__(args, supported_archs)
|
||||
self.path = str(path)
|
||||
self.args = self.get_args()
|
||||
self.raw_dfs = {}
|
||||
self.raw_dfs: dict[str, dict] = {}
|
||||
|
||||
# -----------------------
|
||||
# Required child methods
|
||||
# -----------------------
|
||||
@demarcate
|
||||
def pre_processing(self):
|
||||
def pre_processing(self) -> None:
|
||||
self._profiling_config = file_io.load_profiling_config(self.path)
|
||||
self._runs = self.initalize_runs()
|
||||
|
||||
@@ -78,13 +82,12 @@ class tui_analysis(OmniAnalyze_Base):
|
||||
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(
|
||||
parser.load_non_mertrics_table(
|
||||
workload=self._runs[self.path], dir=self.path, args=self.args
|
||||
)
|
||||
|
||||
@@ -92,7 +95,7 @@ class tui_analysis(OmniAnalyze_Base):
|
||||
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]
|
||||
kernel_name = str(kernel_df.pmc_perf["Kernel_Name"].loc[idx])
|
||||
kernel_dfs = copy.deepcopy(workload.dfs)
|
||||
|
||||
parser.eval_metric(
|
||||
@@ -107,9 +110,11 @@ class tui_analysis(OmniAnalyze_Base):
|
||||
|
||||
self.raw_dfs[kernel_name] = kernel_dfs
|
||||
|
||||
def initalize_runs(self, normalization_filter=None):
|
||||
def initalize_runs(
|
||||
self, normalization_filter: Optional[str] = None
|
||||
) -> OrderedDict[str, schema.Workload]:
|
||||
# Load system info and configure
|
||||
sys_info = file_io.load_sys_info(Path(self.path) / "sysinfo.csv")
|
||||
sys_info = file_io.load_sys_info(str(Path(self.path) / "sysinfo.csv"))
|
||||
arch = sys_info.iloc[0]["gpu_arch"]
|
||||
|
||||
self.generate_configs(
|
||||
@@ -132,11 +137,8 @@ class tui_analysis(OmniAnalyze_Base):
|
||||
)
|
||||
|
||||
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()
|
||||
)
|
||||
if roofline_path.is_file() and not getattr(self.args, "no_roof", False):
|
||||
w.roofline_peaks = pd.read_csv(roofline_path)
|
||||
|
||||
w.avail_ips = w.sys_info["ip_blocks"].item().split("|")
|
||||
w.dfs = copy.deepcopy(self._arch_configs[arch].dfs)
|
||||
@@ -145,7 +147,7 @@ class tui_analysis(OmniAnalyze_Base):
|
||||
self._runs[self.path] = w
|
||||
return self._runs
|
||||
|
||||
def run_kernel_analysis(self):
|
||||
def run_kernel_analysis(self) -> dict[str, Any]:
|
||||
arch = list(self._arch_configs.keys())[0]
|
||||
return {
|
||||
kernel_name: process_panels_to_dataframes(
|
||||
@@ -154,5 +156,5 @@ class tui_analysis(OmniAnalyze_Base):
|
||||
for kernel_name, df in self.raw_dfs.items()
|
||||
}
|
||||
|
||||
def run_top_kernel(self):
|
||||
def run_top_kernel(self) -> Optional[list[dict[Hashable, Any]]]:
|
||||
return get_top_kernels_and_dispatch_ids(self._runs)
|
||||
|
||||
@@ -27,12 +27,14 @@ ROCm Compute Profiler TUI - Main Application with Analysis Methods
|
||||
----------------------------------------------------------------
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from textual import on, work
|
||||
from textual.app import App
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.widgets import Button, Footer, Header
|
||||
from textual_fspicker import SelectDirectory
|
||||
@@ -60,7 +62,11 @@ class RocprofTUIApp(App):
|
||||
# Binding(key="a", action="analyze", description="Analyze"),
|
||||
]
|
||||
|
||||
def __init__(self, args=None, supported_archs=None):
|
||||
def __init__(
|
||||
self,
|
||||
args: argparse.Namespace,
|
||||
supported_archs: Optional[dict[str, Any]] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.main_view = MainView()
|
||||
self.recent_dirs = self._load_recent_dirs()
|
||||
@@ -68,37 +74,37 @@ class RocprofTUIApp(App):
|
||||
# Analysis attributes
|
||||
self.args = args
|
||||
self.supported_archs = supported_archs or {}
|
||||
self.soc = {}
|
||||
self.mspec = None
|
||||
self.soc: dict[str, Any] = {}
|
||||
self.mspec: Optional[Any] = None
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield self.main_view
|
||||
yield Footer()
|
||||
|
||||
def action_refresh(self):
|
||||
def action_refresh(self) -> None:
|
||||
self.main_view.refresh_view()
|
||||
|
||||
def load_soc_specs(self, sysinfo=None):
|
||||
def load_soc_specs(self, sysinfo: Optional[dict] = None) -> None:
|
||||
self.mspec = generate_machine_specs(self.args, sysinfo)
|
||||
arch = self.mspec.gpu_arch
|
||||
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 _load_recent_dirs(self):
|
||||
def _load_recent_dirs(self) -> list[str]:
|
||||
recent_file = Path.home() / ".textual_browser_recent.json"
|
||||
if recent_file.exists():
|
||||
with open(recent_file, "r") as f:
|
||||
with open(recent_file) as f:
|
||||
return json.load(f)
|
||||
return []
|
||||
|
||||
def _save_recent_dirs(self):
|
||||
def _save_recent_dirs(self) -> None:
|
||||
recent_file = Path.home() / ".textual_browser_recent.json"
|
||||
with open(recent_file, "w") as f:
|
||||
json.dump(self.recent_dirs, f, indent=2)
|
||||
|
||||
def add_recent_dir(self, directory):
|
||||
def add_recent_dir(self, directory: str) -> None:
|
||||
directory = str(Path(directory).absolute())
|
||||
|
||||
# Remove if exists, add to front, keep max 5
|
||||
@@ -108,14 +114,14 @@ class RocprofTUIApp(App):
|
||||
self.recent_dirs = self.recent_dirs[:5]
|
||||
self._save_recent_dirs()
|
||||
|
||||
def on_recent_selected(self, selected_dir):
|
||||
def on_recent_selected(self, selected_dir: Optional[str]) -> None:
|
||||
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_directory(self):
|
||||
async def pick_directory(self) -> None:
|
||||
if opened := await self.push_screen_wait(SelectDirectory()):
|
||||
self.add_recent_dir(str(opened))
|
||||
self.main_view.selected_path = opened
|
||||
@@ -123,7 +129,9 @@ class RocprofTUIApp(App):
|
||||
self.main_view.run_analysis()
|
||||
|
||||
|
||||
def run_tui(args=None, supported_archs=None):
|
||||
def run_tui(
|
||||
args: argparse.Namespace, supported_archs: Optional[dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Run the TUI application."""
|
||||
app = RocprofTUIApp(args, supported_archs)
|
||||
app.run()
|
||||
|
||||
@@ -22,14 +22,17 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Hashable, Optional
|
||||
|
||||
import pandas as pd
|
||||
from textual.widgets import TextArea
|
||||
|
||||
import config
|
||||
from utils import schema
|
||||
|
||||
|
||||
class LogLevel(str, Enum):
|
||||
@@ -40,11 +43,11 @@ class LogLevel(str, Enum):
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, output_area=None):
|
||||
def __init__(self, output_area: Optional[TextArea] = None) -> None:
|
||||
self.output_area = output_area
|
||||
self._setup_logger()
|
||||
|
||||
def _setup_logger(self):
|
||||
def _setup_logger(self) -> None:
|
||||
self.logger = logging.getLogger("app")
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
@@ -56,21 +59,23 @@ class Logger:
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def set_output_area(self, output_area):
|
||||
def set_output_area(self, output_area: TextArea) -> None:
|
||||
self.output_area = output_area
|
||||
|
||||
def log(self, message, level="INFO", update_ui=True):
|
||||
def log(
|
||||
self, message: str, log_level: str = "INFO", update_ui: bool = True
|
||||
) -> None:
|
||||
level_map = {
|
||||
"INFO": logging.INFO,
|
||||
"SUCCESS": logging.INFO,
|
||||
"WARNING": logging.WARNING,
|
||||
"ERROR": logging.ERROR,
|
||||
}
|
||||
self.logger.log(level_map[level], message)
|
||||
self.logger.log(level_map[log_level], message)
|
||||
|
||||
if update_ui and self.output_area and hasattr(self.output_area, "text"):
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
formatted_msg = f"[{timestamp}] [{level}] {message}"
|
||||
formatted_msg = f"[{timestamp}] [{log_level}] {message}"
|
||||
self.output_area.text = (
|
||||
f"{self.output_area.text}\n{formatted_msg}"
|
||||
if self.output_area.text
|
||||
@@ -78,20 +83,22 @@ class Logger:
|
||||
)
|
||||
self.output_area.cursor_location = (999999, 0)
|
||||
|
||||
def info(self, message, update_ui=True):
|
||||
def info(self, message: str, update_ui: bool = True) -> None:
|
||||
self.log(message, "INFO", update_ui)
|
||||
|
||||
def success(self, message, update_ui=True):
|
||||
def success(self, message: str, update_ui: bool = True) -> None:
|
||||
self.log(message, "SUCCESS", update_ui)
|
||||
|
||||
def warning(self, message, update_ui=True):
|
||||
def warning(self, message: str, update_ui: bool = True) -> None:
|
||||
self.log(message, "WARNING", update_ui)
|
||||
|
||||
def error(self, message, update_ui=True):
|
||||
def error(self, message: str, update_ui: bool = True) -> None:
|
||||
self.log(message, "ERROR", update_ui)
|
||||
|
||||
|
||||
def get_top_kernels_and_dispatch_ids(runs):
|
||||
def get_top_kernels_and_dispatch_ids(
|
||||
runs: dict[str, Any],
|
||||
) -> Optional[list[dict[Hashable, Any]]]:
|
||||
if not runs:
|
||||
return None
|
||||
|
||||
@@ -113,7 +120,12 @@ def get_top_kernels_and_dispatch_ids(runs):
|
||||
return merged_df.to_dict("records")
|
||||
|
||||
|
||||
def process_panels_to_dataframes(args, kernel_df, archConfigs, roof_plot=None):
|
||||
def process_panels_to_dataframes(
|
||||
args: argparse.Namespace,
|
||||
kernel_df: dict[int, pd.DataFrame],
|
||||
arch_configs: schema.ArchConfig,
|
||||
roof_plot: Optional[str] = None,
|
||||
) -> dict[str, dict[str, dict[str, Any]]]:
|
||||
"""
|
||||
Process panel data into pandas DataFrames.
|
||||
Returns a nested dictionary structure with DataFrames and tui_style information.
|
||||
@@ -139,7 +151,7 @@ def process_panels_to_dataframes(args, kernel_df, archConfigs, roof_plot=None):
|
||||
result_structure = {}
|
||||
decimal_precision = getattr(args, "decimal", 2) if args else 2
|
||||
|
||||
for panel_id, panel in archConfigs.panel_configs.items():
|
||||
for panel_id, panel in arch_configs.panel_configs.items():
|
||||
if panel_id in config.HIDDEN_SECTIONS:
|
||||
continue
|
||||
|
||||
@@ -173,7 +185,7 @@ def process_panels_to_dataframes(args, kernel_df, archConfigs, roof_plot=None):
|
||||
f"{table_config['id'] // 100}.{table_config['id'] % 100}"
|
||||
)
|
||||
if table_config.get("title"):
|
||||
subsection_name += " " + table_config["title"]
|
||||
subsection_name += f" {table_config['title']}"
|
||||
|
||||
section_data[subsection_name] = {
|
||||
"df": df,
|
||||
@@ -190,7 +202,7 @@ def process_panels_to_dataframes(args, kernel_df, archConfigs, roof_plot=None):
|
||||
return result_structure
|
||||
|
||||
|
||||
def apply_rounding_logic(df, decimal_precision):
|
||||
def apply_rounding_logic(df: pd.DataFrame, decimal_precision: int) -> pd.DataFrame:
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
@@ -200,8 +212,8 @@ def apply_rounding_logic(df, decimal_precision):
|
||||
if len(float_cols) > 0:
|
||||
df_rounded[float_cols] = df_rounded[float_cols].round(decimal_precision)
|
||||
|
||||
other_cols = df_rounded.select_dtypes(exclude=["float"]).columns
|
||||
for col in other_cols:
|
||||
non_float_cols = df_rounded.select_dtypes(exclude=["float"]).columns
|
||||
for col in non_float_cols:
|
||||
numeric_series = pd.to_numeric(df_rounded[col], errors="coerce")
|
||||
if numeric_series.notna().any():
|
||||
df_rounded[col] = numeric_series.round(decimal_precision)
|
||||
|
||||
@@ -29,9 +29,10 @@ Panel Widget Modules
|
||||
Contains the panel widgets used in the main layout.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from textual import on
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container, VerticalScroll
|
||||
from textual.widgets import Label, RadioButton, RadioSet
|
||||
|
||||
@@ -78,13 +79,14 @@ class KernelView(Container):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, config_path: Optional[str] = None):
|
||||
def __init__(self, config_path: Optional[str] = None) -> None:
|
||||
super().__init__(id="kernel-view")
|
||||
self.kernel_to_df_dict = {}
|
||||
self.top_kernel_to_df_list = []
|
||||
self.current_selection = None
|
||||
self.kernel_to_df_dict: dict[str, dict[str, Any]] = {}
|
||||
self.top_kernel_to_df_list: list[dict[str, Any]] = []
|
||||
self.current_selection: Optional[str] = None
|
||||
self.status_label: Optional[Label] = None
|
||||
|
||||
self.config_path = config_path or (
|
||||
self.config_path = config_path or str(
|
||||
rocprof_compute_home
|
||||
/ "rocprof_compute_tui"
|
||||
/ "utils"
|
||||
@@ -93,7 +95,7 @@ class KernelView(Container):
|
||||
else None
|
||||
)
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
"""
|
||||
Compose the split panel layout with two scrollable containers.
|
||||
"""
|
||||
@@ -110,7 +112,11 @@ class KernelView(Container):
|
||||
# empty on init
|
||||
pass
|
||||
|
||||
def update_results(self, kernel_to_df_dict, top_kernel_to_df_list) -> None:
|
||||
def update_results(
|
||||
self,
|
||||
kernel_to_df_dict: dict[str, dict[str, Any]],
|
||||
top_kernel_to_df_list: list[dict[str, Any]],
|
||||
) -> None:
|
||||
self.kernel_to_df_dict = kernel_to_df_dict
|
||||
self.top_kernel_to_df_list = top_kernel_to_df_list
|
||||
|
||||
@@ -151,7 +157,7 @@ class KernelView(Container):
|
||||
self.status_label.update(message)
|
||||
self.status_label.set_classes(log_level)
|
||||
|
||||
def new_perf_metric(self):
|
||||
def new_perf_metric(self) -> None:
|
||||
new_metrics = ["VGPRs", "Grid Size", "Workgroup Size"]
|
||||
for new_metric in new_metrics:
|
||||
for i, kernel in enumerate(self.top_kernel_to_df_list):
|
||||
@@ -171,7 +177,7 @@ class KernelView(Container):
|
||||
self.current_selection = kernel_data["Kernel_Name"]
|
||||
self.update_bottom_content()
|
||||
|
||||
def update_bottom_content(self):
|
||||
def update_bottom_content(self) -> None:
|
||||
bottom_container = self.query_one("#bottom-container", VerticalScroll)
|
||||
bottom_container.remove_children()
|
||||
|
||||
|
||||
@@ -29,8 +29,10 @@ Contains the main view layout and organization for the application.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from textual import on, work
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Horizontal, Vertical
|
||||
from textual.reactive import reactive
|
||||
from textual.widgets import DataTable
|
||||
@@ -48,21 +50,21 @@ from utils import file_io
|
||||
class MainView(Horizontal):
|
||||
"""Main view layout for the application."""
|
||||
|
||||
selected_path = reactive(None)
|
||||
kernel_to_df_dict = reactive({})
|
||||
top_kernel_to_df_list = reactive([])
|
||||
selected_path: reactive[Optional[Path]] = reactive(None)
|
||||
kernel_to_df_dict: reactive[dict[str, dict[str, Any]]] = reactive({})
|
||||
top_kernel_to_df_list: reactive[list[dict[str, Any]]] = reactive([])
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(id="main-container")
|
||||
self.start_path = Path(DEFAULT_START_PATH) if DEFAULT_START_PATH else Path.cwd()
|
||||
self.logger = Logger()
|
||||
self.logger.info("MainView initialized", update_ui=False)
|
||||
|
||||
def flush(self):
|
||||
def flush(self) -> None:
|
||||
"""Required for stdout compatibility."""
|
||||
pass
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
self.logger.info("Composing main view layout", update_ui=False)
|
||||
yield MenuBar()
|
||||
|
||||
@@ -87,25 +89,31 @@ class MainView(Horizontal):
|
||||
yield RightPanel()
|
||||
|
||||
@on(DataTable.CellSelected)
|
||||
def on_data_table_cell_selected(self, event):
|
||||
def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None:
|
||||
table = event.data_table
|
||||
row_idx = event.coordinate.row
|
||||
|
||||
visible_data = table.get_row_at(row_idx)
|
||||
description = (
|
||||
table._df.iloc[row_idx].get("Description", "No description")
|
||||
if hasattr(table, "_df")
|
||||
else "N/A"
|
||||
)
|
||||
description = self._get_row_description(table, row_idx)
|
||||
|
||||
self.metric_description.text = (
|
||||
f"Selected Metric ID: {visible_data[0]}\n"
|
||||
f"Selected Metric: {visible_data[1]}\n"
|
||||
f"Description: {description}"
|
||||
)
|
||||
if self.metric_description is not None:
|
||||
self.metric_description.text = (
|
||||
f"Selected Metric ID: {visible_data[0]}\n"
|
||||
f"Selected Metric: {visible_data[1]}\n"
|
||||
f"Description: {description}"
|
||||
)
|
||||
|
||||
def _get_row_description(self, table: DataTable, row_idx: int) -> str:
|
||||
"""Get description for a table row with safe attribute access."""
|
||||
try:
|
||||
if hasattr(table, "_df") and table._df is not None:
|
||||
return str(table._df.iloc[row_idx].get("Description", "No description"))
|
||||
except (IndexError, AttributeError, KeyError):
|
||||
pass
|
||||
return "N/A"
|
||||
|
||||
@work(thread=True)
|
||||
def run_analysis(self):
|
||||
def run_analysis(self) -> None:
|
||||
self.kernel_to_df_dict = {}
|
||||
self.top_kernel_to_df_list = []
|
||||
|
||||
@@ -158,12 +166,12 @@ class MainView(Horizontal):
|
||||
self.logger.error(f"{error_msg}\n{traceback.format_exc()}")
|
||||
self._update_kernel_view(error_msg, LogLevel.ERROR)
|
||||
|
||||
def _update_kernel_view(self, message, log_level):
|
||||
def _update_kernel_view(self, message: str, log_level: LogLevel) -> None:
|
||||
self.app.call_from_thread(
|
||||
lambda: self.query_one("#kernel-view").update_view(message, log_level)
|
||||
)
|
||||
|
||||
def refresh_results(self):
|
||||
def refresh_results(self) -> None:
|
||||
kernel_view = self.query_one("#kernel-view")
|
||||
if kernel_view:
|
||||
kernel_view.update_results(
|
||||
@@ -173,7 +181,7 @@ class MainView(Horizontal):
|
||||
else:
|
||||
self.logger.error("Kernel view not found or no data available")
|
||||
|
||||
def refresh_view(self):
|
||||
def refresh_view(self) -> None:
|
||||
if self.kernel_to_df_dict and self.top_kernel_to_df_list:
|
||||
self.refresh_results()
|
||||
else:
|
||||
|
||||
+3
-2
@@ -28,6 +28,7 @@ Panel Widget Modules
|
||||
Contains the panel widgets used in the main layout.
|
||||
"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import TabPane
|
||||
|
||||
@@ -44,13 +45,13 @@ class CenterPanel(Vertical):
|
||||
"border-title-status",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.default_tab = "center-analyze"
|
||||
self.kernel_view = KernelView()
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
with TabsTabbedContent(initial="tab-kernel"):
|
||||
with TabPane("Basic View", id="tab-kernel"):
|
||||
yield self.kernel_view
|
||||
|
||||
@@ -29,16 +29,30 @@ import math
|
||||
import sys
|
||||
import traceback
|
||||
from io import StringIO
|
||||
from typing import Any, Optional
|
||||
|
||||
import pandas as pd
|
||||
import plotext as plt
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from textual.widgets import Static
|
||||
|
||||
from utils.mem_chart import plot_mem_chart
|
||||
|
||||
# Constants
|
||||
MIN_PLOT_WIDTH = 20
|
||||
WIDTH_MULTIPLIER_SMALL = 3
|
||||
WIDTH_MULTIPLIER_TINY = 100
|
||||
HEIGHT_MULTIPLIER_SMALL = 10
|
||||
HEIGHT_MULTIPLIER_TINY = 300
|
||||
WIDTH_THRESHOLD_SMALL = 20
|
||||
WIDTH_THRESHOLD_TINY = 1
|
||||
HEIGHT_THRESHOLD_SMALL = 20
|
||||
HEIGHT_THRESHOLD_TINY = 0.5
|
||||
DEFAULT_WIDTH_OFFSET = 40
|
||||
|
||||
def simple_bar(df, title=None):
|
||||
|
||||
def simple_bar(df: pd.DataFrame, title: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
Plot data with simple bar chart
|
||||
"""
|
||||
@@ -63,20 +77,20 @@ def simple_bar(df, title=None):
|
||||
plt.clear_figure()
|
||||
|
||||
# adjust plot size along x axis based on the max value
|
||||
w = max(list(metric_dict.values())) - 40
|
||||
if w < 20 and w > 1:
|
||||
w *= 3
|
||||
elif w < 1:
|
||||
w *= 100
|
||||
w = max(list(metric_dict.values())) - DEFAULT_WIDTH_OFFSET
|
||||
if w < WIDTH_THRESHOLD_SMALL and w > WIDTH_THRESHOLD_TINY:
|
||||
w *= WIDTH_MULTIPLIER_SMALL
|
||||
elif w < WIDTH_THRESHOLD_TINY:
|
||||
w *= WIDTH_MULTIPLIER_TINY
|
||||
plt.simple_bar(list(metric_dict.keys()), list(metric_dict.values()), width=w)
|
||||
# plt.show()
|
||||
|
||||
plot_content = plt.build()
|
||||
if not plot_content or plot_content.strip() == "":
|
||||
return None
|
||||
return "\n" + plot_content + "\n"
|
||||
return f"\n{plot_content}\n"
|
||||
|
||||
|
||||
def simple_multiple_bar(df, title=None):
|
||||
def simple_multiple_bar(df: pd.DataFrame, title: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
Plot data with simple multiple bar chart
|
||||
"""
|
||||
@@ -92,51 +106,35 @@ def simple_multiple_bar(df, title=None):
|
||||
data = t_df.transpose().to_dict("split")["data"]
|
||||
labels = data.pop(0)
|
||||
|
||||
# plt.simple_multiple_bar(labels, data, labels = sub_labels) #, width=w)
|
||||
|
||||
# print(data)
|
||||
plt.theme("pro")
|
||||
# adjust plot size along y axis based on the max value
|
||||
h = max(max(y) for y in data)
|
||||
# print(h)
|
||||
if h < 20 and h > 0.5:
|
||||
h *= 10
|
||||
elif h < 0.5 or math.isclose(h, 0.5):
|
||||
h *= 300
|
||||
|
||||
if h < HEIGHT_THRESHOLD_SMALL and h > HEIGHT_THRESHOLD_TINY:
|
||||
h *= HEIGHT_MULTIPLIER_SMALL
|
||||
elif h < HEIGHT_THRESHOLD_TINY or math.isclose(h, HEIGHT_THRESHOLD_TINY):
|
||||
h *= HEIGHT_MULTIPLIER_TINY
|
||||
|
||||
plt.plot_size(height=h)
|
||||
plt.multiple_bar(labels, data)
|
||||
|
||||
# plt.show()
|
||||
plot_content = plt.build()
|
||||
if not plot_content or plot_content.strip() == "":
|
||||
return None
|
||||
return "\n" + plot_content + "\n"
|
||||
return f"\n{plot_content}\n"
|
||||
|
||||
|
||||
def simple_box(df, orientation="v", title=None):
|
||||
def simple_box(
|
||||
df: pd.DataFrame, orientation: str = "v", title: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Plot data with simple box/whisker chart.
|
||||
Accept pre-calculated data only for now.
|
||||
"""
|
||||
|
||||
# Example:
|
||||
# labels = ["apple", "bee", "cat", "dog"]
|
||||
# datas = [
|
||||
# # max, q3, q2, q1, min
|
||||
# [10, 7, 5, 3, 1.5],
|
||||
# [19, 12.3, 9, 7, 4],
|
||||
# [15, 14, 11, 9, 8],
|
||||
# [13, 12, 11, 10, 6]]
|
||||
|
||||
# plt.box(labels, datas, width=0.1, hint='hint')
|
||||
# plt.theme("pro")
|
||||
# plt.title("Most Favored Pizzas in the World")
|
||||
# plt.show()
|
||||
|
||||
plt.clear_figure()
|
||||
labels = []
|
||||
data = []
|
||||
labels: list[str] = []
|
||||
data: list[list[float]] = []
|
||||
|
||||
# TODO:
|
||||
# handle Nan and None properly
|
||||
@@ -147,7 +145,7 @@ def simple_box(df, orientation="v", title=None):
|
||||
t_df = (
|
||||
df.fillna(0).replace("", 0).replace(float("inf"), -1).replace(float("-inf"), -1)
|
||||
)
|
||||
for index, row in t_df.iterrows():
|
||||
for _, row in t_df.iterrows():
|
||||
column_name = row.get("Metric") or row.get("Channel")
|
||||
|
||||
if column_name is None:
|
||||
@@ -159,14 +157,6 @@ def simple_box(df, orientation="v", title=None):
|
||||
data.append([row["Max"], row["Q3"], row["Median"], row["Q1"], row["Min"]])
|
||||
|
||||
# TODO: need better fix for horizontal overflow
|
||||
# labels_length *= 0.80
|
||||
# print("~~~~~~~~~~~~~~~~~~~~")
|
||||
# print(labels)
|
||||
# print(labels_length)
|
||||
# print(data)
|
||||
# print("~~~~~~~~~~~~~~~~~~~~")
|
||||
# print(plt.bar.__doc__)
|
||||
|
||||
if orientation == "v":
|
||||
# adjust plot size along x axis based on total labels length
|
||||
plt.plot_size(labels_length, 30)
|
||||
@@ -180,14 +170,19 @@ def simple_box(df, orientation="v", title=None):
|
||||
)
|
||||
plt.theme("pro")
|
||||
|
||||
# plt.show()
|
||||
plot_content = plt.build()
|
||||
if not plot_content or plot_content.strip() == "":
|
||||
return None
|
||||
return "\n" + plot_content + "\n"
|
||||
return f"\n{plot_content}\n"
|
||||
|
||||
|
||||
def px_simple_bar(df, title: str = None, id=None, style: dict = None, orientation="h"):
|
||||
def px_simple_bar(
|
||||
df: pd.DataFrame,
|
||||
title: Optional[str] = None,
|
||||
id: Optional[int] = None,
|
||||
style: Optional[dict[str, Any]] = None,
|
||||
orientation: str = "h",
|
||||
) -> go.Figure:
|
||||
"""
|
||||
Plot data with simple bar chart
|
||||
"""
|
||||
@@ -196,15 +191,15 @@ def px_simple_bar(df, title: str = None, id=None, style: dict = None, orientatio
|
||||
if "Metric" in df.columns and ("Count" in df.columns or "Value" in df.columns):
|
||||
detected_label = "Count" if "Count" in df.columns else "Value"
|
||||
df[detected_label] = [
|
||||
x.astype(int) if x != "" else int(0) for x in df[detected_label]
|
||||
x.astype(int) if x != "" else 0 for x in df[detected_label]
|
||||
]
|
||||
else:
|
||||
raise NameError("simple_bar: No Metric or Count in df columns!")
|
||||
|
||||
# Assign figure characteristics
|
||||
range_color = style.get("range_color", None)
|
||||
label_txt = style.get("label_txt", None)
|
||||
xrange = style.get("xrange", None)
|
||||
range_color = style.get("range_color", None) if style else None
|
||||
label_txt = style.get("label_txt", None) if style else None
|
||||
xrange = style.get("xrange", None) if style else None
|
||||
if label_txt is not None:
|
||||
label_txt = label_txt.strip("()")
|
||||
try:
|
||||
@@ -236,27 +231,29 @@ def px_simple_bar(df, title: str = None, id=None, style: dict = None, orientatio
|
||||
return fig
|
||||
|
||||
|
||||
def px_simple_multi_bar(df, title=None, id=None):
|
||||
def px_simple_multi_bar(
|
||||
df: pd.DataFrame, title: Optional[str] = None, id: Optional[int] = None
|
||||
) -> list[go.Figure]:
|
||||
"""
|
||||
Plot data with simple multiple bar chart
|
||||
"""
|
||||
|
||||
# TODO: handle Nan and None properly
|
||||
if "Metric" in df.columns and "Avg" in df.columns:
|
||||
df["Avg"] = [x.astype(int) if x != "" else int(0) for x in df["Avg"]]
|
||||
df["Avg"] = [x.astype(int) if x != "" else 0 for x in df["Avg"]]
|
||||
else:
|
||||
raise NameError("simple_multi_bar: No Metric or Count in df columns!")
|
||||
|
||||
dfigs = []
|
||||
nested_bar = {}
|
||||
dfigs: list[go.Figure] = []
|
||||
nested_bar: dict[str, dict[str, Any]] = {}
|
||||
df_unit = df["Unit"][0]
|
||||
if id == 1604:
|
||||
nested_bar = {"NC": {}, "UC": {}, "RW": {}, "CC": {}}
|
||||
for index, row in df.iterrows():
|
||||
for _, row in df.iterrows():
|
||||
nested_bar[row["Coherency"]][row["Xfer"]] = row["Avg"]
|
||||
if id == 1704:
|
||||
nested_bar = {"Read": {}, "Write": {}}
|
||||
for index, row in df.iterrows():
|
||||
for _, row in df.iterrows():
|
||||
nested_bar[row["Transaction"]][row["Type"]] = row["Avg"]
|
||||
|
||||
for group, metric in nested_bar.items():
|
||||
@@ -290,7 +287,7 @@ class RooflinePlot(Static):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, df: pd.DataFrame, **kwargs):
|
||||
def __init__(self, df: pd.DataFrame, **kwargs: Any) -> None:
|
||||
"""Initialize the roofline plot"""
|
||||
super().__init__("", classes="roofline", **kwargs)
|
||||
self.df = df
|
||||
@@ -319,7 +316,7 @@ class MemoryChart(Static):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, df: pd.DataFrame, **kwargs):
|
||||
def __init__(self, df: pd.DataFrame, **kwargs: Any) -> None:
|
||||
super().__init__("", classes="mem-chart", **kwargs)
|
||||
self.df = df
|
||||
|
||||
@@ -368,7 +365,7 @@ class SimpleBar(Static):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, df: pd.DataFrame, **kwargs):
|
||||
def __init__(self, df: pd.DataFrame, **kwargs: Any) -> None:
|
||||
super().__init__("", classes="simple-bar", **kwargs)
|
||||
self.df = df
|
||||
|
||||
@@ -401,7 +398,7 @@ class SimpleBox(Static):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, df: pd.DataFrame, **kwargs):
|
||||
def __init__(self, df: pd.DataFrame, **kwargs: Any) -> None:
|
||||
super().__init__("", classes="simple-box", **kwargs)
|
||||
self.df = df
|
||||
|
||||
@@ -436,7 +433,7 @@ class SimpleMultiBar(Static):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, df: pd.DataFrame, **kwargs):
|
||||
def __init__(self, df: pd.DataFrame, **kwargs: Any) -> None:
|
||||
super().__init__("", classes="simple-multi-bar", **kwargs)
|
||||
self.df = df
|
||||
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from textual.widgets import Collapsible, DataTable, Label
|
||||
from textual.widgets import Collapsible, DataTable, Label, Static
|
||||
|
||||
from rocprof_compute_tui.widgets.charts import (
|
||||
MemoryChart,
|
||||
@@ -35,7 +36,9 @@ from rocprof_compute_tui.widgets.charts import (
|
||||
)
|
||||
|
||||
|
||||
def create_table(df, hidden_columns=[]):
|
||||
def create_table(
|
||||
df: pd.DataFrame, hidden_columns: Optional[list[str]] = []
|
||||
) -> Union[DataTable, Label]:
|
||||
table = DataTable(zebra_stripes=True)
|
||||
|
||||
df = df.reset_index().dropna()
|
||||
@@ -44,8 +47,8 @@ def create_table(df, hidden_columns=[]):
|
||||
if df.empty:
|
||||
return Label("No table data generated")
|
||||
|
||||
table._df = df
|
||||
table._visible_cols = [col for col in df.columns if col not in hidden_columns]
|
||||
table._df = df # type: ignore[attr-defined]
|
||||
table._visible_cols = [col for col in df.columns if col not in hidden_columns] # type: ignore[attr-defined]
|
||||
|
||||
table.add_columns(*table._visible_cols)
|
||||
for _, row in df.iterrows():
|
||||
@@ -54,7 +57,9 @@ def create_table(df, hidden_columns=[]):
|
||||
return table
|
||||
|
||||
|
||||
def create_widget_from_data(df, tui_style=None, context=""):
|
||||
def create_widget_from_data(
|
||||
df: Optional[pd.DataFrame], tui_style: Optional[str] = None, context: str = ""
|
||||
) -> Union[Label, Static, DataTable]:
|
||||
if df is None or df.empty:
|
||||
return Label(
|
||||
f"Data not available{f' for {context}' if context else ''}",
|
||||
@@ -75,15 +80,17 @@ def create_widget_from_data(df, tui_style=None, context=""):
|
||||
return Label(f"Unknown display type: {tui_style}")
|
||||
|
||||
|
||||
def load_config(config_path):
|
||||
with open(config_path, "r") as file:
|
||||
def load_config(config_path: str) -> dict[str, Any]:
|
||||
with open(config_path) as file:
|
||||
return yaml.safe_load(file)
|
||||
|
||||
|
||||
def build_section_from_config(dfs, section_config):
|
||||
def build_section_from_config(
|
||||
dfs: dict[str, Any], section_config: dict[str, Any]
|
||||
) -> Collapsible:
|
||||
title = section_config["title"]
|
||||
collapsed = section_config.get("collapsed", True)
|
||||
children = []
|
||||
children: list[Collapsible] = []
|
||||
|
||||
for subsection_config in section_config["subsections"]:
|
||||
subsection_title = subsection_config.get("title", "Untitled")
|
||||
@@ -139,7 +146,7 @@ def build_section_from_config(dfs, section_config):
|
||||
return Collapsible(*children, title=title, collapsed=collapsed)
|
||||
|
||||
|
||||
def build_all_sections(dfs, config_path):
|
||||
def build_all_sections(dfs: dict[str, Any], config_path: str) -> list[Collapsible]:
|
||||
config = load_config(config_path)
|
||||
return [
|
||||
build_section_from_config(dfs, section_config)
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
from typing import Any, Optional
|
||||
|
||||
from textual import on
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container, Horizontal
|
||||
from textual.reactive import reactive
|
||||
from textual.widgets import Button
|
||||
@@ -32,7 +34,7 @@ from rocprof_compute_tui.widgets.recent_directories import RecentDirectoriesScre
|
||||
|
||||
|
||||
class DropdownMenu(Container):
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose the dropdown menu with menu items."""
|
||||
yield Button("Open Workload", id="menu-open-workload", classes="menu-item")
|
||||
yield Button("Open Recent", id="menu-open-recent", classes="menu-item")
|
||||
@@ -47,11 +49,11 @@ class DropdownMenu(Container):
|
||||
class MenuButton(Button):
|
||||
is_open = reactive(False)
|
||||
|
||||
def __init__(self, label, menu_id, *args, **kwargs):
|
||||
def __init__(self, label: str, menu_id: str, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(label, *args, **kwargs)
|
||||
self.menu_id = menu_id
|
||||
|
||||
def on_click(self):
|
||||
def on_click(self) -> None:
|
||||
self.is_open = not self.is_open
|
||||
dropdown = self.app.query_one(f"#{self.menu_id}", DropdownMenu)
|
||||
|
||||
@@ -64,7 +66,7 @@ class MenuButton(Button):
|
||||
class MenuBar(Container):
|
||||
"""A menu bar that spans the width of the app."""
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Horizontal(
|
||||
MenuButton("File", "file-dropdown", id="menu-file"), id="menu-buttons"
|
||||
)
|
||||
@@ -72,18 +74,18 @@ class MenuBar(Container):
|
||||
with Container(id="dropdown-container"):
|
||||
yield DropdownMenu(id="file-dropdown")
|
||||
|
||||
def on_mount(self):
|
||||
def on_mount(self) -> None:
|
||||
self.border_title = "MENU BAR"
|
||||
self.add_class("section")
|
||||
self.parent_main_view = self.screen.query_one("#main-container", Horizontal)
|
||||
|
||||
@on(Button.Pressed, "#menu-open-recent")
|
||||
def show_recent(self):
|
||||
def show_recent(self) -> None:
|
||||
if not self.app.recent_dirs:
|
||||
self.notify("No recent directories found", severity="warning")
|
||||
return
|
||||
|
||||
def on_recent_selected(selected_dir):
|
||||
def on_recent_selected(selected_dir: Optional[str]) -> None:
|
||||
if selected_dir:
|
||||
self.parent_main_view.selected_path = selected_dir
|
||||
self.query_one("#file-dropdown", DropdownMenu).add_class("hidden")
|
||||
@@ -94,5 +96,5 @@ class MenuBar(Container):
|
||||
)
|
||||
|
||||
@on(Button.Pressed, "#menu-exit")
|
||||
def exit_app(self):
|
||||
def exit_app(self) -> None:
|
||||
self.app.exit()
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
from typing import List
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container, Horizontal
|
||||
@@ -34,7 +33,7 @@ from textual.widgets import Button, Label, ListItem, ListView
|
||||
class RecentDirectoriesScreen(ModalScreen):
|
||||
"""Modal screen to display recent directories."""
|
||||
|
||||
def __init__(self, recent_dirs: List[str]) -> None:
|
||||
def __init__(self, recent_dirs: list[str]) -> None:
|
||||
super().__init__()
|
||||
self.recent_dirs = recent_dirs
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ Panel Widget Modules
|
||||
Contains the panel widgets used in the main layout.
|
||||
"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Label
|
||||
|
||||
@@ -35,14 +36,14 @@ from textual.widgets import Label
|
||||
class RightPanel(Vertical):
|
||||
"""Right panel for additional tools."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the right panel."""
|
||||
super().__init__()
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose the right panel."""
|
||||
yield Label("🚧 Under Construction")
|
||||
|
||||
def _on_mount(self):
|
||||
def _on_mount(self) -> None:
|
||||
self.border_title = "🚧 UNDER CONSTRUCTION"
|
||||
self.add_class("section")
|
||||
|
||||
@@ -28,6 +28,7 @@ Panel Widget Modules
|
||||
Contains the panel widgets used in the main layout.
|
||||
"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import TabPane, TextArea
|
||||
|
||||
@@ -44,7 +45,7 @@ class TabsArea(Vertical):
|
||||
"border-title-status",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the bottom panel."""
|
||||
|
||||
super().__init__()
|
||||
@@ -56,7 +57,7 @@ class TabsArea(Vertical):
|
||||
# Set initial tab
|
||||
self.default_tab = "tab-output"
|
||||
|
||||
def compose(self):
|
||||
def compose(self) -> ComposeResult:
|
||||
with TabsTabbedContent(initial="tab-output"):
|
||||
with TabPane("METRIC DESCRIPTION", id="tab-description"):
|
||||
yield (self.description_area)
|
||||
|
||||
+18
-18
@@ -41,23 +41,23 @@ class Terimnal(Container):
|
||||
name: Optional[str] = None,
|
||||
id: Optional[str] = None,
|
||||
classes: Optional[str] = None,
|
||||
):
|
||||
) -> None:
|
||||
super().__init__(name=name, id=id, classes=classes)
|
||||
self.current_directory = os.getcwd()
|
||||
self.output_text = ""
|
||||
self.input_text = ""
|
||||
self.input_prompt = ""
|
||||
self.has_focus = True
|
||||
self.output_text: str = ""
|
||||
self.input_text: str = ""
|
||||
self.input_prompt: str = ""
|
||||
self.has_focus: bool = True
|
||||
|
||||
# Command history
|
||||
self.command_history = []
|
||||
self.history_index = -1
|
||||
self.current_command = ""
|
||||
self.command_history: list[str] = []
|
||||
self.history_index: int = -1
|
||||
self.current_command: str = ""
|
||||
|
||||
# Tab completion
|
||||
self.tab_completions = []
|
||||
self.tab_index = -1
|
||||
self.tab_prefix = ""
|
||||
self.tab_completions: list[str] = []
|
||||
self.tab_index: int = -1
|
||||
self.tab_prefix: str = ""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# Output area with scroll wrapper
|
||||
@@ -94,8 +94,8 @@ class Terimnal(Container):
|
||||
def add_output(self, text: str) -> None:
|
||||
"""Add text to the terminal output."""
|
||||
self.output_text += text
|
||||
output = self.query_one("#terminal-output")
|
||||
output.update(Text.from_ansi(self.output_text))
|
||||
scroll = self.query_one("#term-output-scroll", VerticalScroll)
|
||||
scroll.scroll_end(animate=False)
|
||||
|
||||
# Ensure scroll to bottom
|
||||
scroll = self.query_one("#term-output-scroll")
|
||||
@@ -104,7 +104,7 @@ class Terimnal(Container):
|
||||
def action_clear(self) -> None:
|
||||
"""Clear the terminal output."""
|
||||
self.output_text = ""
|
||||
output = self.query_one("#terminal-output")
|
||||
output = self.query_one("#terminal-output", Static)
|
||||
output.update(Text.from_ansi(""))
|
||||
|
||||
def action_interrupt(self) -> None:
|
||||
@@ -125,7 +125,7 @@ class Terimnal(Container):
|
||||
else:
|
||||
# If no process is running, just show ^C and clear the input
|
||||
self.add_output("\n^C\n")
|
||||
self.query_one("#terminal-input").value = ""
|
||||
self.query_one("#terminal-input", Input).value = ""
|
||||
|
||||
def run_command(self, command: str) -> None:
|
||||
"""Run a system command and display its output."""
|
||||
@@ -137,7 +137,7 @@ class Terimnal(Container):
|
||||
self.history_index = len(self.command_history)
|
||||
|
||||
# Show the command in the output
|
||||
prompt = self.query_one("#terminal-input").placeholder
|
||||
prompt = self.query_one("#terminal-input", Input).placeholder
|
||||
self.add_output(f"{prompt}{command}\n")
|
||||
|
||||
if not command.strip():
|
||||
@@ -207,7 +207,7 @@ class Terimnal(Container):
|
||||
"""Handle key events for history navigation."""
|
||||
# Handle arrow keys for command history
|
||||
if event.key == "up" and self.command_history:
|
||||
input_widget = self.query_one("#terminal-input")
|
||||
input_widget = self.query_one("#terminal-input", Input)
|
||||
if self.history_index > 0:
|
||||
self.history_index -= 1
|
||||
input_widget.value = self.command_history[self.history_index]
|
||||
@@ -215,7 +215,7 @@ class Terimnal(Container):
|
||||
event.prevent_default()
|
||||
|
||||
elif event.key == "down" and self.command_history:
|
||||
input_widget = self.query_one("#terminal-input")
|
||||
input_widget = self.query_one("#terminal-input", Input)
|
||||
if self.history_index < len(self.command_history) - 1:
|
||||
self.history_index += 1
|
||||
input_widget.value = self.command_history[self.history_index]
|
||||
|
||||
@@ -23,12 +23,14 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import textwrap
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -36,7 +38,7 @@ import plotext as plt
|
||||
import plotly.graph_objects as go
|
||||
from dash import dcc, html
|
||||
|
||||
from utils import file_io, rocpd_data
|
||||
from utils import file_io, rocpd_data, schema
|
||||
from utils.logger import (
|
||||
console_debug,
|
||||
console_error,
|
||||
@@ -50,13 +52,14 @@ from utils.roofline_calc import (
|
||||
SUPPORTED_DATATYPES,
|
||||
calc_ai_analyze,
|
||||
calc_ai_profile,
|
||||
constuct_roof,
|
||||
construct_roof,
|
||||
)
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
SYMBOLS = [0, 1, 2, 3, 4, 5, 13, 17, 18, 20]
|
||||
|
||||
|
||||
def wrap_text(text, width=92):
|
||||
def wrap_text(text: str, width: int = 92) -> str:
|
||||
"""
|
||||
Wraps text using textwrap and joins lines with <br> for Plotly.
|
||||
"""
|
||||
@@ -68,8 +71,19 @@ def wrap_text(text, width=92):
|
||||
return "<br>".join(wrapped_lines)
|
||||
|
||||
|
||||
def to_int(value: Union[float, None]) -> Union[int, float]:
|
||||
if value is None:
|
||||
return np.nan
|
||||
return int(value)
|
||||
|
||||
|
||||
class Roofline:
|
||||
def __init__(self, args, mspec, run_parameters=None):
|
||||
def __init__(
|
||||
self,
|
||||
args: argparse.Namespace,
|
||||
mspec: MachineSpecs,
|
||||
run_parameters: Optional[dict[str, Any]] = None,
|
||||
) -> None:
|
||||
self.__args = args
|
||||
self.__mspec = mspec
|
||||
self.__run_parameters = (
|
||||
@@ -87,9 +101,10 @@ class Roofline:
|
||||
"kernel_filter": False,
|
||||
}
|
||||
)
|
||||
self.__ai_data = None
|
||||
self.__ceiling_data = None
|
||||
self.__ai_data: Optional[dict[str, Any]] = None
|
||||
self.__ceiling_data: Optional[dict[str, Any]] = None
|
||||
self.__figure = go.Figure()
|
||||
|
||||
# Set roofline run parameters from args
|
||||
if hasattr(self.__args, "path") and not run_parameters:
|
||||
self.__run_parameters["workload_dir"] = self.__args.path
|
||||
@@ -108,7 +123,10 @@ class Roofline:
|
||||
self.__run_parameters["kernel_filter"] = True
|
||||
self.validate_parameters()
|
||||
|
||||
def validate_parameters(self):
|
||||
def get_args(self) -> argparse.Namespace:
|
||||
return self.__args
|
||||
|
||||
def validate_parameters(self) -> None:
|
||||
if self.__run_parameters["include_kernel_names"] and (
|
||||
not self.__run_parameters["is_standalone"]
|
||||
):
|
||||
@@ -116,7 +134,7 @@ class Roofline:
|
||||
"--kernel-names is nonactionable when used with --no-roof option"
|
||||
)
|
||||
|
||||
def roof_setup(self):
|
||||
def roof_setup(self) -> None:
|
||||
# Setup the workload directory for roofline profiling.
|
||||
workload_dir_val = self.__run_parameters.get("workload_dir")
|
||||
|
||||
@@ -171,53 +189,72 @@ class Roofline:
|
||||
# Create the directory
|
||||
Path(final_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def validate_apply_kernel_filter(self, df, path=None):
|
||||
if self.__run_parameters["kernel_filter"] is True:
|
||||
if self.__args.mode == "profile":
|
||||
df_pmc = df["pmc_perf"]
|
||||
df_filtered = df_pmc.copy()
|
||||
df_list = (df_pmc.loc[:, "Kernel_Name"]).to_list()
|
||||
for idx in range(0, len(df_list)):
|
||||
if df_list[idx].split("(")[0] not in self.__args.kernel:
|
||||
# Drop row from dataframe if kernel has not been requested
|
||||
df_filtered.drop(index=idx, inplace=True)
|
||||
# Verify that final filtered kernel df matches the kernel list requested
|
||||
if len(df_filtered.drop_duplicates(subset=["Kernel_Name"])) != len(
|
||||
self.__args.kernel
|
||||
):
|
||||
console_debug(
|
||||
"Profiled kernels: {}\n`--kernel`: {}".format(
|
||||
df_list, self.__args.kernel
|
||||
)
|
||||
)
|
||||
console_error(
|
||||
"Roofline cannot profile - kernels requested with `--kernel` missing from profiling data!" # noqa: E501
|
||||
"\n\tRe-profile workload in full or specify subset of available kernels using `--kernel` option." # noqa: E501
|
||||
"\n\tComplete profiled kernels list can be found in pmc_perf file.", # noqa: E501
|
||||
exit=True,
|
||||
)
|
||||
# Fix df structure to resemble same df arg passed in
|
||||
df["pmc_perf"] = df_filtered
|
||||
elif self.__args.mode == "analyze":
|
||||
top_kernels_csv = Path(path).joinpath("pmc_kernel_top.csv")
|
||||
if not top_kernels_csv.is_file():
|
||||
console_error(
|
||||
"roofline", "{} does not exist".format(top_kernels_csv)
|
||||
)
|
||||
k_df = pd.read_csv(top_kernels_csv)
|
||||
k_df = k_df.loc[self.__args.gpu_kernel[0], "Kernel_Name"]
|
||||
def apply_profile_kernel_filter(
|
||||
self, df: dict[str, pd.DataFrame], args: argparse.Namespace
|
||||
) -> dict[str, pd.DataFrame]:
|
||||
"""Apply kernel filter for profile mode."""
|
||||
df_pmc = df["pmc_perf"]
|
||||
df_filtered = df_pmc.copy()
|
||||
df_list = df_pmc["Kernel_Name"].tolist()
|
||||
|
||||
df["pmc_perf"] = df["pmc_perf"][
|
||||
df["pmc_perf"]["Kernel_Name"].isin(k_df)
|
||||
]
|
||||
for idx in range(len(df_list)):
|
||||
if df_list[idx].split("(")[0] not in args.kernel:
|
||||
df_filtered.drop(index=idx, inplace=True)
|
||||
|
||||
# Verify that final filtered kernel df matches the kernel list requested
|
||||
unique_kernels = len(df_filtered.drop_duplicates(subset=["Kernel_Name"]))
|
||||
if unique_kernels != len(args.kernel):
|
||||
console_debug(f"Profiled kernels: {df_list}\n`--kernel`: {args.kernel}")
|
||||
console_error(
|
||||
"Roofline cannot profile - kernels requested with `--kernel` missing "
|
||||
"from profiling data!\n"
|
||||
"\tRe-profile workload in full or specify subset of available kernels "
|
||||
"using `--kernel` option.\n"
|
||||
"\tComplete profiled kernels list can be found in pmc_perf file.",
|
||||
exit=True,
|
||||
)
|
||||
|
||||
df["pmc_perf"] = df_filtered
|
||||
return df
|
||||
|
||||
def apply_analyze_kernel_filter(
|
||||
self,
|
||||
df: dict[str, pd.DataFrame],
|
||||
path_str: Optional[str],
|
||||
args: argparse.Namespace,
|
||||
) -> dict[str, pd.DataFrame]:
|
||||
"""Apply kernel filter for analyze mode."""
|
||||
if not path_str:
|
||||
console_error("roofline", "cannot locate pmc_kernel_top.csv")
|
||||
|
||||
top_kernels_csv = Path(path_str) / "pmc_kernel_top.csv"
|
||||
if not top_kernels_csv.is_file():
|
||||
console_error("roofline", f"{top_kernels_csv} does not exist")
|
||||
|
||||
k_df = pd.read_csv(top_kernels_csv)
|
||||
k_df = k_df.loc[args.gpu_kernel[0], "Kernel_Name"]
|
||||
|
||||
df["pmc_perf"] = df["pmc_perf"][df["pmc_perf"]["Kernel_Name"].isin(k_df)]
|
||||
return df
|
||||
|
||||
def validate_apply_kernel_filter(
|
||||
self, df: dict[str, pd.DataFrame], path_str: Optional[str] = None
|
||||
) -> dict[str, pd.DataFrame]:
|
||||
if not self.__run_parameters["kernel_filter"]:
|
||||
return df
|
||||
args = self.get_args()
|
||||
|
||||
if args.mode == "profile":
|
||||
return self.apply_profile_kernel_filter(df, args)
|
||||
elif args.mode == "analyze":
|
||||
return self.apply_analyze_kernel_filter(df, path_str, args)
|
||||
|
||||
return df
|
||||
|
||||
@demarcate
|
||||
def empirical_roofline(
|
||||
self,
|
||||
ret_df,
|
||||
):
|
||||
self, ret_df: dict[str, pd.DataFrame]
|
||||
) -> Optional[html.Section]:
|
||||
"""
|
||||
Generate a set of empirical roofline plots given a directory containing
|
||||
required profiling and benchmarking data.
|
||||
@@ -228,19 +265,20 @@ class Roofline:
|
||||
):
|
||||
self.roof_setup()
|
||||
|
||||
console_debug(
|
||||
"roofline", "Path: %s" % self.__run_parameters.get("workload_dir")
|
||||
)
|
||||
console_debug("roofline", f"Path: {self.__run_parameters.get('workload_dir')}")
|
||||
|
||||
# Verify kernels have been profiled and filter the df
|
||||
ret_df = self.validate_apply_kernel_filter(
|
||||
df=ret_df, path=self.__run_parameters.get("workload_dir")
|
||||
df=ret_df, path_str=self.__run_parameters.get("workload_dir")
|
||||
)
|
||||
|
||||
self.__ai_data = calc_ai_profile(
|
||||
self.__mspec, self.__run_parameters.get("sort_type"), ret_df
|
||||
)
|
||||
|
||||
msg = "AI at each mem level:"
|
||||
for i in self.__ai_data:
|
||||
msg += "\n\t%s -> %s" % (i, self.__ai_data[i])
|
||||
for key, value in self.__ai_data.items():
|
||||
msg += f"\n\t{key} -> {value}"
|
||||
console_debug(msg)
|
||||
|
||||
ops_figure = flops_figure = None
|
||||
@@ -254,35 +292,27 @@ class Roofline:
|
||||
or str(dt) not in SUPPORTED_DATATYPES[gpu_arch]
|
||||
):
|
||||
console_error(
|
||||
"{} is not a supported datatype for roofline profiling on {} "
|
||||
"(arch: {})".format(
|
||||
str(dt),
|
||||
getattr(self.__mspec, "gpu_model", "N/A"),
|
||||
gpu_arch,
|
||||
),
|
||||
f"{dt} is not a supported datatype for roofline profiling on "
|
||||
f"{getattr(self.__mspec, 'gpu_model', 'N/A')} (arch: {gpu_arch})",
|
||||
exit=False,
|
||||
)
|
||||
continue
|
||||
|
||||
ops_flops = "Ops" if (str(dt[:1]) == "I") else "Flops"
|
||||
ops_flops = "Ops" if str(dt).startswith("I") else "Flops"
|
||||
|
||||
if ops_flops == "Ops":
|
||||
if ops_figure:
|
||||
ops_combo_figure = self.generate_plot(
|
||||
ops_figure = self.generate_plot(
|
||||
dtype=str(dt),
|
||||
fig=ops_figure,
|
||||
)
|
||||
ops_figure = ops_combo_figure
|
||||
else:
|
||||
ops_figure = self.generate_plot(dtype=str(dt))
|
||||
ops_dt_list += "_" + str(dt)
|
||||
|
||||
if ops_flops == "Flops":
|
||||
if flops_figure:
|
||||
flops_combo_figure = self.generate_plot(
|
||||
dtype=str(dt),
|
||||
fig=flops_figure,
|
||||
)
|
||||
flops_figure = flops_combo_figure
|
||||
flops_figure = self.generate_plot(dtype=str(dt), fig=flops_figure)
|
||||
else:
|
||||
flops_figure = self.generate_plot(dtype=str(dt))
|
||||
flops_dt_list += "_" + str(dt)
|
||||
@@ -299,11 +329,11 @@ class Roofline:
|
||||
original_kernel_names = self.__ai_data.get("kernelNames", [])
|
||||
|
||||
num_kernels = len(original_kernel_names)
|
||||
|
||||
self.__figure.data = []
|
||||
self.__figure.layout = {}
|
||||
|
||||
if num_kernels == 0:
|
||||
# Create empty kernel names figure when no kernels are found
|
||||
console_log(
|
||||
"roofline",
|
||||
"No kernel names found to generate "
|
||||
@@ -328,15 +358,10 @@ class Roofline:
|
||||
width=400,
|
||||
)
|
||||
else:
|
||||
symbols_list = []
|
||||
kernel_names_list = []
|
||||
|
||||
for i in range(num_kernels):
|
||||
symbols_list.append(SYMBOLS[i % len(SYMBOLS)])
|
||||
kernel_names_list.append(original_kernel_names[i])
|
||||
# Create populated kernel names figure with symbols and names.
|
||||
symbols_list = [SYMBOLS[i % len(SYMBOLS)] for i in range(num_kernels)]
|
||||
|
||||
self.__figure = go.Figure()
|
||||
|
||||
self.__figure.add_trace(
|
||||
go.Scatter(
|
||||
x=[0.1] * num_kernels,
|
||||
@@ -353,7 +378,8 @@ class Roofline:
|
||||
)
|
||||
)
|
||||
|
||||
for i, kernel_name in enumerate(kernel_names_list):
|
||||
# Add kernel name annotations
|
||||
for i, kernel_name in enumerate(original_kernel_names):
|
||||
self.__figure.add_annotation(
|
||||
x=0.25,
|
||||
y=num_kernels - i,
|
||||
@@ -365,6 +391,7 @@ class Roofline:
|
||||
font=dict(size=11, color="black"),
|
||||
)
|
||||
|
||||
# Add formatting elements to kernel names figure.
|
||||
self.__figure.add_annotation(
|
||||
x=0.1,
|
||||
y=num_kernels + 1,
|
||||
@@ -384,6 +411,7 @@ class Roofline:
|
||||
font=dict(size=12, color="black"),
|
||||
)
|
||||
|
||||
# Add grid lines
|
||||
for i in range(num_kernels + 1):
|
||||
self.__figure.add_shape(
|
||||
type="line",
|
||||
@@ -427,51 +455,50 @@ class Roofline:
|
||||
kernel_list += "_" + name
|
||||
|
||||
# Re-save to remove loading MathJax pop up
|
||||
for i in range(2):
|
||||
for _ in range(2):
|
||||
if ops_figure:
|
||||
ops_figure.write_image(
|
||||
self.__run_parameters["workload_dir"]
|
||||
+ "/empirRoof_gpu-{}{}{}.pdf".format(
|
||||
dev_id, ops_dt_list, kernel_list
|
||||
)
|
||||
f"{self.__run_parameters['workload_dir']}/empirRoof_gpu-{dev_id}{ops_dt_list}{kernel_list}.pdf"
|
||||
)
|
||||
if flops_figure:
|
||||
flops_figure.write_image(
|
||||
self.__run_parameters["workload_dir"]
|
||||
+ "/empirRoof_gpu-{}{}{}.pdf".format(
|
||||
dev_id, flops_dt_list, kernel_list
|
||||
)
|
||||
f"{self.__run_parameters['workload_dir']}/empirRoof_gpu-{dev_id}{flops_dt_list}{kernel_list}.pdf"
|
||||
)
|
||||
|
||||
# only save a legend if kernel_names option is toggled
|
||||
if self.__run_parameters["include_kernel_names"]:
|
||||
self.__figure.write_image(
|
||||
self.__run_parameters["workload_dir"]
|
||||
+ "/kernelName_legend{}.pdf".format(kernel_list)
|
||||
f"{self.__run_parameters['workload_dir']}/kernelName_legend{kernel_list}.pdf"
|
||||
)
|
||||
time.sleep(1)
|
||||
|
||||
console_log("roofline", "Empirical Roofline PDFs saved!")
|
||||
else:
|
||||
if ops_figure:
|
||||
ops_graph = html.Div(
|
||||
# Create HTML output for GUI mode.
|
||||
ops_graph = (
|
||||
html.Div(
|
||||
className="float-child",
|
||||
children=[
|
||||
html.H3(children="Empirical Roofline Analysis (Ops)"),
|
||||
dcc.Graph(figure=ops_figure),
|
||||
],
|
||||
)
|
||||
else:
|
||||
ops_graph = None
|
||||
if flops_figure:
|
||||
flops_graph = html.Div(
|
||||
if ops_figure
|
||||
else None
|
||||
)
|
||||
|
||||
flops_graph = (
|
||||
html.Div(
|
||||
className="float-child",
|
||||
children=[
|
||||
html.H3(children="Empirical Roofline Analysis (Flops)"),
|
||||
dcc.Graph(figure=flops_figure),
|
||||
],
|
||||
)
|
||||
else:
|
||||
flops_graph = None
|
||||
if flops_figure
|
||||
else None
|
||||
)
|
||||
|
||||
return html.Section(
|
||||
id="roofline",
|
||||
children=[
|
||||
@@ -486,7 +513,7 @@ class Roofline:
|
||||
)
|
||||
|
||||
@demarcate
|
||||
def generate_plot(self, dtype, fig=None) -> go.Figure():
|
||||
def generate_plot(self, dtype: str, fig: Optional[go.Figure] = None) -> go.Figure:
|
||||
"""
|
||||
Create graph object from ai_data (coordinate points) and ceiling_data
|
||||
(peak FLOP and BW) data.
|
||||
@@ -498,12 +525,14 @@ class Roofline:
|
||||
skipAI = True # Don't repeat AI plotting
|
||||
|
||||
plot_mode = "lines+text" if self.__run_parameters["is_standalone"] else "lines"
|
||||
self.__ceiling_data = constuct_roof(
|
||||
|
||||
self.__ceiling_data = construct_roof(
|
||||
roofline_parameters=self.__run_parameters,
|
||||
dtype=dtype,
|
||||
)
|
||||
console_debug("roofline", "Ceiling data:\n%s" % self.__ceiling_data)
|
||||
ops_flops = "OP" if (dtype[:1] == "I") else "FLOP" # For printing purposes
|
||||
console_debug("roofline", f"Ceiling data:\n{self.__ceiling_data}")
|
||||
|
||||
ops_flops = "OP" if dtype.startswith("I") else "FLOP" # For printing purposes
|
||||
|
||||
#######################
|
||||
# Plot Application AI
|
||||
@@ -576,17 +605,21 @@ class Roofline:
|
||||
# Plot ceilings
|
||||
#######################
|
||||
mem_level_config = self.__run_parameters.get("mem_level", "ALL")
|
||||
if mem_level_config == "ALL":
|
||||
cache_hierarchy = ["HBM", "L2", "L1", "LDS"]
|
||||
else:
|
||||
cache_hierarchy = (
|
||||
|
||||
cache_hierarchy = (
|
||||
["HBM", "L2", "L1", "LDS"]
|
||||
if mem_level_config == "ALL"
|
||||
else (
|
||||
mem_level_config
|
||||
if isinstance(mem_level_config, list)
|
||||
else [mem_level_config]
|
||||
)
|
||||
)
|
||||
|
||||
# Plot peak BW ceiling(s)
|
||||
for cache_level in cache_hierarchy:
|
||||
cache_key = cache_level.lower()
|
||||
|
||||
if (
|
||||
not self.__ceiling_data
|
||||
or cache_level.lower() not in self.__ceiling_data
|
||||
@@ -606,19 +639,15 @@ class Roofline:
|
||||
go.Scatter(
|
||||
x=self.__ceiling_data[cache_level.lower()][0],
|
||||
y=self.__ceiling_data[cache_level.lower()][1],
|
||||
name="{}-{}".format(cache_level, dtype),
|
||||
name=f"{cache_level}-{dtype}",
|
||||
mode=plot_mode,
|
||||
hovertemplate="<b>%{text}</b>",
|
||||
text=[
|
||||
"{} GB/s".format(
|
||||
to_int(self.__ceiling_data[cache_level.lower()][2])
|
||||
),
|
||||
f"{to_int(self.__ceiling_data[cache_key][2])} GB/s",
|
||||
(
|
||||
None
|
||||
if self.__run_parameters.get("is_standalone")
|
||||
else "{} GB/s".format(
|
||||
to_int(self.__ceiling_data[cache_level.lower()][2])
|
||||
)
|
||||
else f"{to_int(self.__ceiling_data[cache_key][2])} GB/s"
|
||||
),
|
||||
],
|
||||
textposition="top right",
|
||||
@@ -631,20 +660,19 @@ class Roofline:
|
||||
go.Scatter(
|
||||
x=self.__ceiling_data["valu"][0],
|
||||
y=self.__ceiling_data["valu"][1],
|
||||
name="Peak VALU-{}".format(dtype),
|
||||
name=f"Peak VALU-{dtype}",
|
||||
mode=plot_mode,
|
||||
hovertemplate="<b>%{text}</b>",
|
||||
text=[
|
||||
(
|
||||
None
|
||||
if self.__run_parameters["is_standalone"]
|
||||
else "{} G{}/s".format(
|
||||
to_int(self.__ceiling_data["valu"][2]), ops_flops
|
||||
else (
|
||||
f"{to_int(self.__ceiling_data['valu'][2])} G"
|
||||
f"{ops_flops}/s"
|
||||
)
|
||||
),
|
||||
"{} G{}/s".format(
|
||||
to_int(self.__ceiling_data["valu"][2]), ops_flops
|
||||
),
|
||||
f"{to_int(self.__ceiling_data['valu'][2])} G{ops_flops}/s",
|
||||
],
|
||||
textposition="top left",
|
||||
)
|
||||
@@ -656,20 +684,19 @@ class Roofline:
|
||||
go.Scatter(
|
||||
x=self.__ceiling_data["mfma"][0],
|
||||
y=self.__ceiling_data["mfma"][1],
|
||||
name="Peak MFMA-{}".format(dtype),
|
||||
name=f"Peak MFMA-{dtype}",
|
||||
mode=plot_mode,
|
||||
hovertemplate="<b>%{text}</b>",
|
||||
text=[
|
||||
(
|
||||
None
|
||||
if self.__run_parameters["is_standalone"]
|
||||
else "{} G{}/s".format(
|
||||
to_int(self.__ceiling_data["mfma"][2]), ops_flops
|
||||
else (
|
||||
f"{to_int(self.__ceiling_data['mfma'][2])} "
|
||||
f"G{ops_flops}/s"
|
||||
)
|
||||
),
|
||||
"{} G{}/s".format(
|
||||
to_int(self.__ceiling_data["mfma"][2]), ops_flops
|
||||
),
|
||||
f"{to_int(self.__ceiling_data['mfma'][2])} G{ops_flops}/s",
|
||||
],
|
||||
textposition="top left",
|
||||
)
|
||||
@@ -680,7 +707,13 @@ class Roofline:
|
||||
|
||||
return fig
|
||||
|
||||
def cli_generate_plot(self, dtype, workload=None, config=None, arch_config=None):
|
||||
def cli_generate_plot(
|
||||
self,
|
||||
dtype: str,
|
||||
workload: Optional[schema.Workload] = None,
|
||||
config: Optional[dict[str, Any]] = None,
|
||||
arch_config: Optional[schema.ArchConfig] = None,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Plot CLI mode roofline analysis in terminal using plotext
|
||||
|
||||
@@ -692,11 +725,11 @@ class Roofline:
|
||||
"""
|
||||
console_debug("roofline", "Generating roofline plot for CLI")
|
||||
|
||||
if not (str(dtype) in SUPPORTED_DATATYPES[self.__mspec.gpu_arch]):
|
||||
if not (str(dtype) in SUPPORTED_DATATYPES[str(self.__mspec.gpu_arch)]):
|
||||
console_error(
|
||||
"{} is not a supported datatype for roofline profiling on {}".format(
|
||||
str(dtype), self.__mspec.gpu_model
|
||||
),
|
||||
f"{dtype} is not a supported datatype for roofline profiling on "
|
||||
f"{getattr(self.__mspec, 'gpu_model', 'N/A')} (arch: "
|
||||
f"{self.__mspec.gpu_arch})",
|
||||
exit=False,
|
||||
)
|
||||
return
|
||||
@@ -728,21 +761,20 @@ class Roofline:
|
||||
else:
|
||||
# workload_dir is a string
|
||||
base_dir = workload_dir
|
||||
# Convert to Path object for easier manipulation
|
||||
base_path = Path(base_dir)
|
||||
|
||||
base_path = Path(base_dir)
|
||||
roofline_csv = base_path / "roofline.csv"
|
||||
if not roofline_csv.is_file():
|
||||
console_log("roofline", "{} does not exist".format(roofline_csv))
|
||||
console_log("roofline", f"{roofline_csv} does not exist")
|
||||
return
|
||||
|
||||
# if workload is detected, utilize Roofline yamls.
|
||||
# If not, fallback to legacy calc_ai
|
||||
if workload is not None:
|
||||
if workload and config and arch_config:
|
||||
self.__ai_data = calc_ai_analyze(
|
||||
workload=workload,
|
||||
mspec=self.__mspec,
|
||||
sort_type=self.__run_parameters.get("sort_type"),
|
||||
sort_type=str(self.__run_parameters.get("sort_type")),
|
||||
config=config,
|
||||
arch_config=arch_config,
|
||||
)
|
||||
@@ -750,21 +782,24 @@ class Roofline:
|
||||
else:
|
||||
pmc_perf_csv = base_path / "pmc_perf.csv"
|
||||
if not pmc_perf_csv.is_file():
|
||||
console_error("roofline", "{} does not exist".format(pmc_perf_csv))
|
||||
console_error("roofline", f"{pmc_perf_csv} does not exist")
|
||||
|
||||
t_df = OrderedDict()
|
||||
t_df["pmc_perf"] = pd.read_csv(pmc_perf_csv)
|
||||
|
||||
profiling_config = file_io.load_profiling_config(self.__args.path[0][0])
|
||||
if profiling_config.get("format_rocprof_output") == "rocpd":
|
||||
t_df["pmc_perf"] = rocpd_data.process_rocpd_csv(t_df["pmc_perf"])
|
||||
|
||||
t_df = self.validate_apply_kernel_filter(df=t_df, path=base_path)
|
||||
t_df = self.validate_apply_kernel_filter(df=t_df, path_str=str(base_path))
|
||||
self.__ai_data = calc_ai_profile(
|
||||
self.__mspec, self.__run_parameters["sort_type"], t_df
|
||||
)
|
||||
|
||||
self.__ceiling_data = constuct_roof(
|
||||
self.__ceiling_data = construct_roof(
|
||||
roofline_parameters=self.__run_parameters, dtype=dtype
|
||||
)
|
||||
|
||||
console_debug(f"AI data: {self.__ai_data}")
|
||||
console_debug(f"Kernel names: {self.__ai_data.get('kernelNames', [])}")
|
||||
|
||||
@@ -800,40 +835,39 @@ class Roofline:
|
||||
plt.clf()
|
||||
plt.plotsize(plt.tw(), plt.th())
|
||||
|
||||
ops_flops = "OP" if (dtype[:1] == "I") else "FLOP" # For printing purposes
|
||||
ops_flops = "OP" if dtype.startswith("I") else "FLOP"
|
||||
|
||||
# Plot BW Lines
|
||||
if self.__run_parameters["mem_level"] == "ALL":
|
||||
cache_hierarchy = ["HBM", "L2", "L1", "LDS"]
|
||||
else:
|
||||
cache_hierarchy = self.__run_parameters["mem_level"]
|
||||
# Plot bandwidth lines
|
||||
cache_hierarchy = (
|
||||
["HBM", "L2", "L1", "LDS"]
|
||||
if self.__run_parameters["mem_level"] == "ALL"
|
||||
else self.__run_parameters["mem_level"]
|
||||
)
|
||||
|
||||
for cache_level in cache_hierarchy:
|
||||
cache_key = cache_level.lower()
|
||||
plt.plot(
|
||||
self.__ceiling_data[cache_level.lower()][0],
|
||||
self.__ceiling_data[cache_level.lower()][1],
|
||||
label="{}-{}".format(cache_level, dtype),
|
||||
self.__ceiling_data[cache_key][0],
|
||||
self.__ceiling_data[cache_key][1],
|
||||
label=f"{cache_level}-{dtype}",
|
||||
marker="braille",
|
||||
color=color_scheme[cache_level],
|
||||
)
|
||||
plt.text(
|
||||
str(round(self.__ceiling_data[cache_level.lower()][2])) + " GB/s",
|
||||
x=self.__ceiling_data[cache_level.lower()][0][0],
|
||||
y=self.__ceiling_data[cache_level.lower()][1][0],
|
||||
f"{round(self.__ceiling_data[cache_key][2])} GB/s",
|
||||
x=self.__ceiling_data[cache_key][0][0],
|
||||
y=self.__ceiling_data[cache_key][1][0],
|
||||
background="black",
|
||||
color="white",
|
||||
alignment="left",
|
||||
)
|
||||
console_debug(
|
||||
"roofline",
|
||||
cache_level
|
||||
+ ": [{},{}], [{},{}], {}".format(
|
||||
str(self.__ceiling_data[cache_level.lower()][0][0]),
|
||||
str(self.__ceiling_data[cache_level.lower()][0][1]),
|
||||
str(self.__ceiling_data[cache_level.lower()][1][0]),
|
||||
str(self.__ceiling_data[cache_level.lower()][1][1]),
|
||||
str(self.__ceiling_data[cache_level.lower()][2]),
|
||||
),
|
||||
f"{cache_level}: [{self.__ceiling_data[cache_key][0][0]},"
|
||||
f"{self.__ceiling_data[cache_key][0][1]}], "
|
||||
f"[{self.__ceiling_data[cache_key][1][0]},"
|
||||
f"{self.__ceiling_data[cache_key][1][1]}], "
|
||||
f"{self.__ceiling_data[cache_key][2]}",
|
||||
)
|
||||
|
||||
# Plot VALU and MFMA Peak
|
||||
@@ -844,12 +878,12 @@ class Roofline:
|
||||
self.__ceiling_data["valu"][1][0] - 0.1,
|
||||
self.__ceiling_data["valu"][1][1] - 0.1,
|
||||
],
|
||||
label="Peak VALU-{}".format(dtype),
|
||||
label=f"Peak VALU-{dtype}",
|
||||
marker="braille",
|
||||
color=color_scheme["VALU"],
|
||||
)
|
||||
plt.text(
|
||||
str(round(self.__ceiling_data["valu"][2])) + " G{}/s".format(ops_flops),
|
||||
f"{round(self.__ceiling_data['valu'][2])} G{ops_flops}/s",
|
||||
x=self.__ceiling_data["valu"][0][1] - 800,
|
||||
y=self.__ceiling_data["valu"][1][1],
|
||||
background="black",
|
||||
@@ -858,16 +892,14 @@ class Roofline:
|
||||
)
|
||||
console_debug(
|
||||
"roofline",
|
||||
"VALU: [{},{}], [{},{}], {}".format(
|
||||
str(self.__ceiling_data["valu"][0][0]),
|
||||
str(self.__ceiling_data["valu"][0][1]),
|
||||
str(self.__ceiling_data["valu"][1][0]),
|
||||
str(self.__ceiling_data["valu"][1][1]),
|
||||
str(self.__ceiling_data["valu"][2]),
|
||||
),
|
||||
f"VALU: [{self.__ceiling_data['valu'][0][0]},"
|
||||
f"{self.__ceiling_data['valu'][0][1]}], "
|
||||
f"[{self.__ceiling_data['valu'][1][0]},"
|
||||
f"{self.__ceiling_data['valu'][1][1]}], "
|
||||
f"{self.__ceiling_data['valu'][2]}",
|
||||
)
|
||||
else:
|
||||
console_warning("No PEAK measurement available for {}".format(dtype))
|
||||
console_warning(f"No PEAK measurement available for {dtype}")
|
||||
|
||||
if dtype in MFMA_DATATYPES:
|
||||
plt.plot(
|
||||
@@ -876,12 +908,12 @@ class Roofline:
|
||||
self.__ceiling_data["mfma"][1][0] - 0.1,
|
||||
self.__ceiling_data["mfma"][1][1] - 0.1,
|
||||
],
|
||||
label="Peak MFMA-{}".format(dtype),
|
||||
label=f"Peak MFMA-{dtype}",
|
||||
marker="braille",
|
||||
color=color_scheme["MFMA"],
|
||||
)
|
||||
plt.text(
|
||||
str(round(self.__ceiling_data["mfma"][2])) + " G{}/s".format(ops_flops),
|
||||
f"{round(self.__ceiling_data['mfma'][2])} G{ops_flops}/s",
|
||||
x=self.__ceiling_data["mfma"][0][1] - 800,
|
||||
y=self.__ceiling_data["mfma"][1][1],
|
||||
background="black",
|
||||
@@ -890,45 +922,46 @@ class Roofline:
|
||||
)
|
||||
console_debug(
|
||||
"roofline",
|
||||
"MFMA: [{},{}], [{},{}], {}".format(
|
||||
str(self.__ceiling_data["mfma"][0][0]),
|
||||
str(self.__ceiling_data["mfma"][0][1]),
|
||||
str(self.__ceiling_data["mfma"][1][0]),
|
||||
str(self.__ceiling_data["mfma"][1][1]),
|
||||
str(self.__ceiling_data["mfma"][2]),
|
||||
),
|
||||
f"MFMA: [{self.__ceiling_data['mfma'][0][0]},"
|
||||
f"{self.__ceiling_data['mfma'][0][1]}], "
|
||||
f"[{self.__ceiling_data['mfma'][1][0]},"
|
||||
f"{self.__ceiling_data['mfma'][1][1]}], "
|
||||
f"{self.__ceiling_data['mfma'][2]}",
|
||||
)
|
||||
else:
|
||||
console_warning("No MFMA measurement available for {}".format(dtype))
|
||||
console_warning(f"No MFMA measurement available for {dtype}")
|
||||
|
||||
# Plot Application AI
|
||||
for cache_level in cache_hierarchy:
|
||||
key = "ai_" + cache_level.lower()
|
||||
if key in self.__ai_data:
|
||||
for i in range(len(self.__ai_data["kernelNames"])):
|
||||
# Zero intensity level means no data reported for this cache level
|
||||
if self.__ai_data[key][0][i] > 0 and self.__ai_data[key][1][i] > 0:
|
||||
plt.plot(
|
||||
[self.__ai_data[key][0][i]],
|
||||
[self.__ai_data[key][1][i]],
|
||||
label="AI_"
|
||||
+ cache_level
|
||||
+ "_{}".format(self.__ai_data["kernelNames"][i]),
|
||||
color=color_scheme[cache_level],
|
||||
marker=kernel_markers[i % len(kernel_markers)],
|
||||
)
|
||||
console_debug(
|
||||
"roofline",
|
||||
"AI_{}: {}, {}".format(
|
||||
self.__ai_data["kernelNames"][i],
|
||||
self.__ai_data[key][0][i],
|
||||
self.__ai_data[key][1][i],
|
||||
),
|
||||
)
|
||||
key = f"ai_{cache_level.lower()}"
|
||||
if key not in self.__ai_data:
|
||||
continue
|
||||
|
||||
plt.xlabel("Arithmetic Intensity ({})s/Byte)".format(ops_flops))
|
||||
kernel_names = self.__ai_data.get("kernelNames", [])
|
||||
for i in range(len(self.__ai_data.get("kernelNames", []))):
|
||||
# Zero intensity level means no data reported for this cache level
|
||||
if self.__ai_data[key][0][i] > 0 and self.__ai_data[key][1][i] > 0:
|
||||
plt.plot(
|
||||
[self.__ai_data[key][0][i]],
|
||||
[self.__ai_data[key][1][i]],
|
||||
label=f"AI_{cache_level}_{kernel_names[i]}",
|
||||
color=color_scheme[cache_level],
|
||||
marker=kernel_markers[i % len(kernel_markers)],
|
||||
)
|
||||
val1 = (
|
||||
self.__ai_data[key][0][i]
|
||||
if i < len(self.__ai_data[key][0])
|
||||
else "N/A"
|
||||
)
|
||||
val2 = (
|
||||
self.__ai_data[key][1][i]
|
||||
if i < len(self.__ai_data[key][1])
|
||||
else "N/A"
|
||||
)
|
||||
console_debug("roofline", f"AI_{kernel_names[i]}: {val1}, {val2}")
|
||||
plt.xlabel(f"Arithmetic Intensity ({ops_flops}s/Byte)")
|
||||
plt.ylabel("Performance (GFLOP/sec)")
|
||||
plt.title("Roofline ({}) - {}".format(dtype, base_path))
|
||||
plt.title(f"Roofline ({dtype}) - {base_path}")
|
||||
|
||||
# Canvas config
|
||||
plt.theme("pro")
|
||||
@@ -940,7 +973,7 @@ class Roofline:
|
||||
return plt.build()
|
||||
|
||||
@demarcate
|
||||
def standalone_roofline(self):
|
||||
def standalone_roofline(self) -> None:
|
||||
if (
|
||||
not isinstance(self.__run_parameters["workload_dir"], list)
|
||||
and self.__run_parameters["workload_dir"] != None
|
||||
@@ -952,33 +985,26 @@ class Roofline:
|
||||
self.__run_parameters["mem_level"].remove("vL1D")
|
||||
self.__run_parameters["mem_level"].append("L1")
|
||||
|
||||
app_path = str(
|
||||
Path(self.__run_parameters["workload_dir"]).joinpath("pmc_perf.csv")
|
||||
)
|
||||
roofline_exists = Path(app_path).is_file()
|
||||
if not roofline_exists:
|
||||
console_error("roofline", "{} does not exist".format(app_path))
|
||||
app_path = Path(str(self.__run_parameters["workload_dir"])) / "pmc_perf.csv"
|
||||
if not app_path.is_file():
|
||||
console_error("roofline", f"{app_path} does not exist")
|
||||
|
||||
t_df = OrderedDict()
|
||||
t_df["pmc_perf"] = pd.read_csv(app_path)
|
||||
|
||||
profiling_config = file_io.load_profiling_config(self.__args.path)
|
||||
if profiling_config.get("format_rocprof_output") == "rocpd":
|
||||
t_df["pmc_perf"] = rocpd_data.process_rocpd_csv(t_df["pmc_perf"])
|
||||
|
||||
self.empirical_roofline(ret_df=t_df)
|
||||
|
||||
# NB: Currently the post_prossesing() method is the only one being used by
|
||||
# rocprofiler-compute, we include pre_processing() and profile() methods for
|
||||
# those who wish to borrow the roofline module
|
||||
@abstractmethod
|
||||
def post_processing(self):
|
||||
def post_processing(self) -> None:
|
||||
if self.__run_parameters["is_standalone"]:
|
||||
self.standalone_roofline()
|
||||
|
||||
def get_dtype(self):
|
||||
def get_dtype(self) -> list[str]:
|
||||
return self.__run_parameters["roofline_data_type"]
|
||||
|
||||
|
||||
def to_int(a):
|
||||
if str(type(a)) == "<class 'NoneType'>":
|
||||
return np.nan
|
||||
else:
|
||||
return int(a)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
##############################################################################el
|
||||
from typing import Any, Optional
|
||||
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
@@ -30,21 +31,25 @@ from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
TextClause,
|
||||
create_engine,
|
||||
func,
|
||||
select,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.orm import Session, declarative_base, relationship, sessionmaker
|
||||
from sqlalchemy.sql import Select
|
||||
|
||||
from utils.logger import console_debug, console_error
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
PREFIX = "compute_"
|
||||
SCHEMA_VERSION = "1.0.0"
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class Workload(Base):
|
||||
__tablename__ = f"{PREFIX}workload"
|
||||
|
||||
@@ -162,33 +167,38 @@ class Metadata(Base):
|
||||
|
||||
|
||||
class Database:
|
||||
_session = None
|
||||
_session: Optional[Session] = None
|
||||
_engine: Optional[Engine] = None
|
||||
|
||||
@classmethod
|
||||
def init(cls, db_name):
|
||||
engine = create_engine(f"sqlite:///{db_name}")
|
||||
Base.metadata.create_all(engine)
|
||||
cls._session = sessionmaker(bind=engine)()
|
||||
def init(cls, db_name: str) -> str:
|
||||
cls._engine = create_engine(f"sqlite:///{db_name}")
|
||||
Base.metadata.create_all(cls._engine)
|
||||
cls._session = sessionmaker(bind=cls._engine)()
|
||||
console_debug(f"SQLite database initialized with name: {db_name}")
|
||||
return db_name
|
||||
|
||||
@classmethod
|
||||
def get_session(cls):
|
||||
def get_session(cls) -> Optional[Session]:
|
||||
return cls._session
|
||||
|
||||
@classmethod
|
||||
def write(self):
|
||||
def write(cls) -> None:
|
||||
if cls._session is None:
|
||||
console_error("No active database session")
|
||||
|
||||
try:
|
||||
self._session.commit()
|
||||
cls._session.commit()
|
||||
except Exception as e:
|
||||
self._session.rollback()
|
||||
cls._session.rollback()
|
||||
console_error(f"Error writing analysis database: {e}")
|
||||
finally:
|
||||
self._session.close()
|
||||
cls._session.close()
|
||||
cls._session = None
|
||||
|
||||
|
||||
def get_views():
|
||||
views = {
|
||||
def get_views() -> list[TextClause]:
|
||||
views: dict[str, Select[Any]] = {
|
||||
"kernel_view": select(
|
||||
Dispatch.kernel_name,
|
||||
func.count(Dispatch.dispatch_id).label("dispatch_count"),
|
||||
@@ -207,6 +217,7 @@ def get_views():
|
||||
Value.value,
|
||||
).join(Value, Metric.metric_uuid == Value.metric_uuid),
|
||||
}
|
||||
|
||||
return [
|
||||
text(
|
||||
f"CREATE VIEW {PREFIX}{view_name} AS "
|
||||
|
||||
@@ -27,6 +27,7 @@ import os
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import pandas as pd
|
||||
import yaml
|
||||
@@ -39,90 +40,71 @@ from utils.logger import console_debug, console_error, console_log, demarcate
|
||||
# TODO: use pandas chunksize or dask to read really large csv file
|
||||
# from dask import dataframe as dd
|
||||
|
||||
# the build-in config to list kernel names purpose only
|
||||
top_stats_build_in_config = {
|
||||
0: {
|
||||
"id": 0,
|
||||
"title": "Top Kernels",
|
||||
"data source": [{"raw_csv_table": {"id": 1, "source": "pmc_kernel_top.csv"}}],
|
||||
},
|
||||
1: {
|
||||
"id": 1,
|
||||
"title": "Dispatch List",
|
||||
"data source": [
|
||||
{"raw_csv_table": {"id": 2, "source": "pmc_dispatch_info.csv"}}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def load_sys_info(f):
|
||||
def load_sys_info(f: str) -> pd.DataFrame:
|
||||
"""
|
||||
Load sys running info from csv file to a df.
|
||||
"""
|
||||
return pd.read_csv(f)
|
||||
|
||||
|
||||
def load_panel_configs(dirs):
|
||||
def load_panel_configs(
|
||||
dirs: list[str],
|
||||
) -> OrderedDict[int, dict[str, Any]]:
|
||||
"""
|
||||
Load all panel configs from yaml file.
|
||||
"""
|
||||
d = {}
|
||||
for dir in dirs:
|
||||
for root, _, files in os.walk(dir):
|
||||
for f in files:
|
||||
if f.endswith(".yaml"):
|
||||
with open(Path(root) / f) as file:
|
||||
configs: dict[int, dict[str, Any]] = {}
|
||||
for dir_path in dirs:
|
||||
for root, _, files in os.walk(dir_path):
|
||||
for file_name in files:
|
||||
if file_name.endswith(".yaml"):
|
||||
with open(Path(root) / file_name) as file:
|
||||
config_yml = yaml.safe_load(file)
|
||||
# metric key can be None due to some metric-
|
||||
# tables not having any metrics
|
||||
# metric key should be empty dict instead of None
|
||||
for data_source in config_yml["Panel Config"]["data source"]:
|
||||
panel_config = config_yml["Panel Config"]
|
||||
for data_source in panel_config["data source"]:
|
||||
metric_table = data_source.get("metric_table")
|
||||
if metric_table and metric_table["metric"] is None:
|
||||
metric_table["metric"] = {}
|
||||
d[config_yml["Panel Config"]["id"]] = config_yml["Panel Config"]
|
||||
configs[panel_config["id"]] = panel_config
|
||||
|
||||
# TODO: sort metrics as the header order in case they-
|
||||
# are not defined in the same order
|
||||
|
||||
od = OrderedDict(sorted(d.items()))
|
||||
# for key, value in od.items():
|
||||
# print(key, value)
|
||||
return od
|
||||
return OrderedDict(sorted(configs.items()))
|
||||
|
||||
|
||||
def load_profiling_config(config_dir):
|
||||
def load_profiling_config(config_dir: str) -> dict[str, Any]:
|
||||
"""
|
||||
Load profiling config from yaml file.
|
||||
"""
|
||||
config_path = Path(config_dir) / "profiling_config.yaml"
|
||||
try:
|
||||
with open(Path(config_dir).joinpath("profiling_config.yaml")) as file:
|
||||
prof_config = yaml.safe_load(file)
|
||||
return prof_config
|
||||
with open(config_path) as file:
|
||||
return yaml.safe_load(file) or {}
|
||||
except FileNotFoundError:
|
||||
console_log(f"Could not find profiling_config.yaml in {config_dir}")
|
||||
return dict()
|
||||
return {}
|
||||
|
||||
|
||||
@demarcate
|
||||
def create_df_kernel_top_stats(
|
||||
df_in,
|
||||
raw_data_dir,
|
||||
filter_gpu_ids,
|
||||
filter_dispatch_ids,
|
||||
filter_nodes,
|
||||
time_unit,
|
||||
max_stat_num,
|
||||
kernel_verbose,
|
||||
sortby="sum",
|
||||
):
|
||||
df_in: dict[str, pd.DataFrame],
|
||||
raw_data_dir: str,
|
||||
filter_gpu_ids: Optional[list[str]],
|
||||
filter_dispatch_ids: Optional[list[str]],
|
||||
filter_nodes: Optional[str],
|
||||
time_unit: str,
|
||||
kernel_verbose: int,
|
||||
sortby: str = "sum",
|
||||
) -> None:
|
||||
"""
|
||||
Create top stats info by grouping kernels with user's filters.
|
||||
"""
|
||||
|
||||
# NB: think about df = pd.DataFrame(df_in["pmc_perf"].copy())
|
||||
df = df_in["pmc_perf"]
|
||||
df = df_in["pmc_perf"].copy()
|
||||
|
||||
# Demangle original KernelNames
|
||||
kernel_name_shortener(df, kernel_verbose)
|
||||
@@ -139,87 +121,105 @@ def create_df_kernel_top_stats(
|
||||
if filter_dispatch_ids:
|
||||
# NB: support ignoring the 1st n dispatched execution by '> n'
|
||||
# The better way may be parsing python slice string
|
||||
if ">" in filter_dispatch_ids[0]:
|
||||
m = re.match(r"\> (\d+)", filter_dispatch_ids[0])
|
||||
df = df[df["Dispatch_ID"] > int(m.group(1))]
|
||||
first_filter = filter_dispatch_ids[0]
|
||||
if first_filter.startswith(">"):
|
||||
match = re.match(r">\s*(\d+)", first_filter)
|
||||
if match:
|
||||
threshold = int(match.group(1))
|
||||
df = df[df["Dispatch_ID"] > threshold]
|
||||
else:
|
||||
df = df.loc[df["Dispatch_ID"].astype(str).isin(filter_dispatch_ids)]
|
||||
|
||||
# First, create a dispatches file used to populate global vars
|
||||
dispatch_info = (
|
||||
df.loc[:, ["Node", "Dispatch_ID", "Kernel_Name", "GPU_ID"]]
|
||||
dispatch_columns = (
|
||||
["Node", "Dispatch_ID", "Kernel_Name", "GPU_ID"]
|
||||
if "Node" in df.columns
|
||||
else df.loc[:, ["Dispatch_ID", "Kernel_Name", "GPU_ID"]]
|
||||
)
|
||||
dispatch_info.to_csv(
|
||||
str(Path(raw_data_dir).joinpath("pmc_dispatch_info.csv")), index=False
|
||||
else ["Dispatch_ID", "Kernel_Name", "GPU_ID"]
|
||||
)
|
||||
dispatch_info = df[dispatch_columns]
|
||||
dispatch_output_path = Path(raw_data_dir) / "pmc_dispatch_info.csv"
|
||||
dispatch_info.to_csv(dispatch_output_path, index=False)
|
||||
|
||||
time_stats = pd.concat(
|
||||
[df["Kernel_Name"], (df["End_Timestamp"] - df["Start_Timestamp"])],
|
||||
keys=["Kernel_Name", "ExeTime"],
|
||||
axis=1,
|
||||
)
|
||||
|
||||
grouped = time_stats.groupby(by=["Kernel_Name"]).agg({
|
||||
"ExeTime": ["count", "sum", "mean", "median"]
|
||||
# Calculate execution times
|
||||
execution_times = df["End_Timestamp"] - df["Start_Timestamp"]
|
||||
time_stats = pd.DataFrame({
|
||||
"Kernel_Name": df["Kernel_Name"],
|
||||
"ExeTime": execution_times,
|
||||
})
|
||||
|
||||
time_unit_str = "(" + time_unit + ")"
|
||||
grouped.columns = [
|
||||
x.capitalize() + time_unit_str if x != "count" else x.capitalize()
|
||||
for x in grouped.columns.get_level_values(1)
|
||||
]
|
||||
grouped = time_stats.groupby("Kernel_Name")["ExeTime"].agg([
|
||||
"count",
|
||||
"sum",
|
||||
"mean",
|
||||
"median",
|
||||
])
|
||||
|
||||
key = "Sum" + time_unit_str
|
||||
grouped[key] = grouped[key].div(config.TIME_UNITS[time_unit])
|
||||
key = "Mean" + time_unit_str
|
||||
grouped[key] = grouped[key].div(config.TIME_UNITS[time_unit])
|
||||
key = "Median" + time_unit_str
|
||||
grouped[key] = grouped[key].div(config.TIME_UNITS[time_unit])
|
||||
# Rename columns with time unit
|
||||
time_unit_suffix = f"({time_unit})"
|
||||
column_mapping = {
|
||||
"count": "Count",
|
||||
"sum": f"Sum{time_unit_suffix}",
|
||||
"mean": f"Mean{time_unit_suffix}",
|
||||
"median": f"Median{time_unit_suffix}",
|
||||
}
|
||||
grouped = grouped.rename(columns=column_mapping)
|
||||
|
||||
grouped = grouped.reset_index() # Remove special group indexing
|
||||
# Convert time units
|
||||
time_divisor = config.TIME_UNITS[time_unit]
|
||||
for col in [
|
||||
f"Sum{time_unit_suffix}",
|
||||
f"Mean{time_unit_suffix}",
|
||||
f"Median{time_unit_suffix}",
|
||||
]:
|
||||
grouped[col] = grouped[col] / time_divisor
|
||||
|
||||
key = "Sum" + time_unit_str
|
||||
grouped["Pct"] = grouped[key] / grouped[key].sum() * 100
|
||||
grouped = grouped.reset_index()
|
||||
|
||||
# Calculate percentage
|
||||
sum_column = f"Sum{time_unit_suffix}"
|
||||
grouped["Pct"] = grouped[sum_column] / grouped[sum_column].sum() * 100
|
||||
|
||||
# NB:
|
||||
# Sort by total time as default.
|
||||
if sortby == "sum":
|
||||
grouped = grouped.sort_values(by=("Sum" + time_unit_str), ascending=False)
|
||||
grouped.to_csv(
|
||||
str(Path(raw_data_dir).joinpath("pmc_kernel_top.csv")), index=False
|
||||
)
|
||||
grouped = grouped.sort_values(sum_column, ascending=False)
|
||||
grouped.to_csv(str(Path(raw_data_dir) / "pmc_kernel_top.csv"), index=False)
|
||||
elif sortby == "kernel":
|
||||
grouped = grouped.sort_values("Kernel_Name")
|
||||
grouped.to_csv(
|
||||
str(Path(raw_data_dir).joinpath("pmc_kernel_top.csv")), index=False
|
||||
)
|
||||
grouped.to_csv(str(Path(raw_data_dir) / "pmc_kernel_top.csv"), index=False)
|
||||
|
||||
|
||||
@demarcate
|
||||
def create_df_pmc(
|
||||
raw_data_root_dir, nodes, spatial_multiplexing, kernel_verbose, verbose, config
|
||||
):
|
||||
raw_data_root_dir: str,
|
||||
nodes: Optional[list[str]],
|
||||
spatial_multiplexing: bool,
|
||||
kernel_verbose: int,
|
||||
verbose: int,
|
||||
config_dict: dict[str, Any],
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
Load all raw pmc counters and join into one df.
|
||||
"""
|
||||
|
||||
def create_single_df_pmc(raw_data_dir, node_name, kernel_verbose, verbose):
|
||||
dfs = []
|
||||
coll_levels = []
|
||||
def create_single_df_pmc(
|
||||
raw_data_dir: str, node_name: Optional[str], kernel_verbose: int, verbose: int
|
||||
) -> pd.DataFrame:
|
||||
dfs: list[pd.DataFrame] = []
|
||||
coll_levels: list[str] = []
|
||||
|
||||
df = pd.DataFrame() # noqa: F841
|
||||
new_df = pd.DataFrame() # noqa: F841
|
||||
for root, dirs, files in os.walk(raw_data_dir):
|
||||
for f in files:
|
||||
# print("file ", f)
|
||||
if (f.endswith(".csv") and f.startswith("SQ")) or (
|
||||
f == schema.pmc_perf_file_prefix + ".csv"
|
||||
):
|
||||
tmp_df = pd.read_csv(str(Path(root).joinpath(f)))
|
||||
if config.get("format_rocprof_output") == "rocpd":
|
||||
for root, _, files in os.walk(raw_data_dir):
|
||||
for file_name in files:
|
||||
# Process SQ*.csv or pmc_perf.csv files
|
||||
is_sq_file = file_name.endswith(".csv") and file_name.startswith("SQ")
|
||||
is_pmc_perf = file_name == f"{schema.PMC_PERF_FILE_PREFIX}.csv"
|
||||
|
||||
if is_sq_file or is_pmc_perf:
|
||||
file_path = Path(root) / file_name
|
||||
tmp_df = pd.read_csv(file_path)
|
||||
|
||||
if config_dict.get("format_rocprof_output") == "rocpd":
|
||||
tmp_df = rocpd_data.process_rocpd_csv(tmp_df)
|
||||
|
||||
# Demangle original KernelNames
|
||||
kernel_name_shortener(tmp_df, kernel_verbose)
|
||||
|
||||
@@ -228,117 +228,126 @@ def create_df_pmc(
|
||||
# multiindexing level. Here, we add it into pmc_perf
|
||||
# as it is the main sub-df which can be handled easily
|
||||
# later.
|
||||
if f == "pmc_perf.csv" and node_name != None:
|
||||
if file_name == "pmc_perf.csv" and node_name is not None:
|
||||
tmp_df.insert(0, "Node", node_name)
|
||||
|
||||
dfs.append(tmp_df)
|
||||
coll_levels.append(f[:-4])
|
||||
# Remove .csv extension for collection level
|
||||
coll_levels.append(file_name[:-4])
|
||||
|
||||
if not dfs:
|
||||
return pd.DataFrame()
|
||||
|
||||
# TODO: double check the case if all tmp_df.shape[0] are not on the same page
|
||||
final_df = pd.concat(dfs, keys=coll_levels, axis=1, join="inner", copy=False)
|
||||
if verbose >= 2:
|
||||
console_debug("pmc_raw_data final_single_df %s" % final_df.info)
|
||||
console_debug(f"pmc_raw_data final_single_df {final_df.info}")
|
||||
return final_df
|
||||
|
||||
root_path = Path(raw_data_root_dir)
|
||||
|
||||
# 1. spatial multiplexing case
|
||||
if spatial_multiplexing:
|
||||
df = pd.DataFrame()
|
||||
# todo: more err check
|
||||
for subdir in Path(raw_data_root_dir).iterdir():
|
||||
dfs: list[pd.DataFrame] = []
|
||||
|
||||
for subdir in root_path.iterdir():
|
||||
if subdir.is_dir():
|
||||
new_df = create_single_df_pmc(
|
||||
subdir, str(subdir.name), kernel_verbose, verbose
|
||||
str(subdir), str(subdir.name), kernel_verbose, verbose
|
||||
)
|
||||
df = pd.concat([df, new_df])
|
||||
return df
|
||||
if not new_df.empty:
|
||||
dfs.append(new_df)
|
||||
return pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
|
||||
|
||||
# specified node list
|
||||
else:
|
||||
# regular single node case
|
||||
if nodes is None:
|
||||
return create_single_df_pmc(
|
||||
raw_data_root_dir, None, kernel_verbose, verbose
|
||||
)
|
||||
# 2. regular single node case (nodes=None)
|
||||
if nodes is None:
|
||||
return create_single_df_pmc(raw_data_root_dir, None, kernel_verbose, verbose)
|
||||
|
||||
# "empty list" means all nodes
|
||||
elif not nodes:
|
||||
df = pd.DataFrame()
|
||||
# todo: more err check
|
||||
for subdir in Path(raw_data_root_dir).iterdir():
|
||||
if subdir.is_dir():
|
||||
new_df = create_single_df_pmc(
|
||||
subdir, str(subdir.name), kernel_verbose, verbose
|
||||
)
|
||||
df = pd.concat([df, new_df])
|
||||
return df
|
||||
# 3. all nodes case (nodes=[])
|
||||
if not nodes:
|
||||
dfs: list[pd.DataFrame] = []
|
||||
|
||||
# specified node list
|
||||
else:
|
||||
df = pd.DataFrame()
|
||||
# todo: more err check
|
||||
for subdir in nodes:
|
||||
p = Path(raw_data_root_dir)
|
||||
for subdir in root_path.iterdir():
|
||||
if subdir.is_dir():
|
||||
new_df = create_single_df_pmc(
|
||||
p.joinpath(subdir), subdir, kernel_verbose, verbose
|
||||
str(subdir), str(subdir.name), kernel_verbose, verbose
|
||||
)
|
||||
df = pd.concat([df, new_df])
|
||||
return df
|
||||
if not new_df.empty:
|
||||
dfs.append(new_df)
|
||||
return pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
|
||||
|
||||
# 4. specified node list case (nodes=[...])
|
||||
dfs: list[pd.DataFrame] = []
|
||||
|
||||
for node in nodes:
|
||||
node_path = root_path / node
|
||||
if node_path.exists():
|
||||
new_df = create_single_df_pmc(str(node_path), node, kernel_verbose, verbose)
|
||||
if not new_df.empty:
|
||||
dfs.append(new_df)
|
||||
return pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
|
||||
|
||||
|
||||
def collect_wave_occu_per_cu(in_dir, out_dir, numSE):
|
||||
def collect_wave_occu_per_cu(in_dir: str, out_dir: str, num_se: int) -> None:
|
||||
"""
|
||||
Collect wave occupancy info from in_dir csv files
|
||||
and consolidate into out_dir/wave_occu_per_cu.csv.
|
||||
It depends highly on wave_occu_se*.csv format.
|
||||
"""
|
||||
in_path = Path(in_dir)
|
||||
all_data = pd.DataFrame()
|
||||
|
||||
all = pd.DataFrame()
|
||||
for i in range(num_se):
|
||||
file_path = in_path / f"wave_occu_se{i}.csv"
|
||||
if not file_path.exists():
|
||||
continue
|
||||
|
||||
for i in range(numSE):
|
||||
p = Path(in_dir, "wave_occu_se" + str(i) + ".csv")
|
||||
if p.exists():
|
||||
tmp_df = pd.read_csv(p)
|
||||
SE_idx = "SE" + str(tmp_df.loc[0, "SE"])
|
||||
tmp_df.rename(
|
||||
columns={
|
||||
"Dispatch": "Dispatch",
|
||||
"SE": "SE",
|
||||
"CU": "CU",
|
||||
"Occupancy": SE_idx,
|
||||
},
|
||||
inplace=True,
|
||||
)
|
||||
tmp_df = pd.read_csv(file_path)
|
||||
if tmp_df.empty:
|
||||
continue
|
||||
|
||||
# TODO: join instead of concat!
|
||||
if i == 0:
|
||||
all = tmp_df[{"CU", SE_idx}]
|
||||
all.sort_index(axis=1, inplace=True)
|
||||
else:
|
||||
all = pd.concat([all, tmp_df[SE_idx]], axis=1, copy=False)
|
||||
se_idx = f"SE{tmp_df.loc[0, 'SE']}"
|
||||
tmp_df.rename(
|
||||
columns={
|
||||
"Dispatch": "Dispatch",
|
||||
"SE": "SE",
|
||||
"CU": "CU",
|
||||
"Occupancy": se_idx,
|
||||
}
|
||||
)
|
||||
|
||||
if not all.empty:
|
||||
# print(all.transpose())
|
||||
all.to_csv(Path(out_dir, "wave_occu_per_cu.csv"), index=False)
|
||||
# TODO: join instead of concat!
|
||||
if i == 0:
|
||||
all_data = tmp_df[{"CU", se_idx}]
|
||||
all_data.sort_index(axis=1, inplace=True)
|
||||
else:
|
||||
all_data = pd.concat([all_data, tmp_df[se_idx]], axis=1, copy=False)
|
||||
|
||||
if not all_data.empty:
|
||||
all_data.to_csv(Path(out_dir) / "wave_occu_per_cu.csv", index=False)
|
||||
|
||||
|
||||
def is_single_panel_config(root_dir, supported_archs):
|
||||
def is_single_panel_config(
|
||||
root_dir: str, supported_archs: dict[str, str]
|
||||
) -> Optional[bool]:
|
||||
"""
|
||||
Check the root configs dir structure to decide using one config set for all
|
||||
archs, or one for each arch.
|
||||
"""
|
||||
# If not single config, verify all supported archs have defined configs
|
||||
supported_archs = supported_archs.keys()
|
||||
counter = 0
|
||||
for arch in supported_archs:
|
||||
if root_dir.joinpath(arch).exists():
|
||||
counter += 1
|
||||
if counter == 0:
|
||||
arch_names = list(supported_archs.keys())
|
||||
root_path = Path(root_dir)
|
||||
arch_count = sum(1 for arch in arch_names if (root_path / arch).exists())
|
||||
|
||||
if arch_count == 0:
|
||||
return True
|
||||
elif counter == len(supported_archs):
|
||||
elif arch_count == len(arch_names):
|
||||
return False
|
||||
else:
|
||||
console_error("Found multiple panel config sets but incomplete for all archs.")
|
||||
|
||||
|
||||
def find_1st_sub_dir(directory):
|
||||
def find_1st_sub_dir(directory: str) -> Optional[str]:
|
||||
"""
|
||||
Find the first sub dir in a directory
|
||||
"""
|
||||
@@ -347,7 +356,7 @@ def find_1st_sub_dir(directory):
|
||||
# Iterate over entries in the directory
|
||||
for entry in dir_path.iterdir():
|
||||
if entry.is_dir(): # Check if it's a directory
|
||||
return entry
|
||||
return str(entry)
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print(f"The directory '{directory}' does not exist.")
|
||||
return None
|
||||
console_error(f'The directory "{directory}" does not exist.', exit=False)
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import colorlover
|
||||
from typing import Any
|
||||
|
||||
import colorlover # type: ignore
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
from dash import dash_table, html
|
||||
import plotly.express as px # type: ignore
|
||||
from dash import dash_table, html # type: ignore
|
||||
|
||||
from utils import schema
|
||||
from utils.logger import console_error
|
||||
@@ -41,73 +43,79 @@ IS_DARK = True # TODO: Remove hardcoded in favor of class property
|
||||
##################
|
||||
# HELPER FUNCTIONS
|
||||
##################
|
||||
def filter_df(column, df, filt):
|
||||
filt_df = df
|
||||
if filt != []:
|
||||
filt_df = df.loc[df[schema.pmc_perf_file_prefix][column].astype(str).isin(filt)]
|
||||
return filt_df
|
||||
def filter_df(column: str, df: pd.DataFrame, filt: list[str]) -> pd.DataFrame:
|
||||
if not filt:
|
||||
return df
|
||||
return df.loc[df[schema.PMC_PERF_FILE_PREFIX][column].astype(str).isin(filt)]
|
||||
|
||||
|
||||
def multi_bar_chart(table_id, display_df):
|
||||
def multi_bar_chart(
|
||||
table_id: int, display_df: pd.DataFrame
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
nested_bar: dict[str, dict[str, Any]] = {}
|
||||
if table_id == 1604:
|
||||
nested_bar = {}
|
||||
for index, row in display_df.iterrows():
|
||||
if not row["Coherency"] in nested_bar:
|
||||
nested_bar[row["Coherency"]] = {}
|
||||
nested_bar[row["Coherency"]][row["Xfer"]] = row["Avg"]
|
||||
if table_id == 1705: # L2 - Fabric Interface Stalls
|
||||
nested_bar = {}
|
||||
for index, row in display_df.iterrows():
|
||||
if not row["Transaction"] in nested_bar:
|
||||
nested_bar[row["Transaction"]] = {}
|
||||
nested_bar[row["Transaction"]][row["Type"]] = row["Avg"]
|
||||
for _, row in display_df.iterrows():
|
||||
coherency = row["Coherency"]
|
||||
if coherency not in nested_bar:
|
||||
nested_bar[coherency] = {}
|
||||
nested_bar[coherency][row["Xfer"]] = row["Avg"]
|
||||
elif table_id == 1705: # L2 - Fabric Interface Stalls
|
||||
for _, row in display_df.iterrows():
|
||||
transaction = row["Transaction"]
|
||||
if transaction not in nested_bar:
|
||||
nested_bar[transaction] = {}
|
||||
nested_bar[transaction][row["Type"]] = row["Avg"]
|
||||
|
||||
return nested_bar
|
||||
|
||||
|
||||
def discrete_background_color_bins(df, n_bins=5, columns="all"):
|
||||
def discrete_background_color_bins(
|
||||
df: pd.DataFrame, n_bins: int = 5, columns: str | list[str] = "all"
|
||||
) -> tuple[list[dict[str, Any]], html.Div]:
|
||||
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
|
||||
|
||||
if columns == "all":
|
||||
if "id" in df:
|
||||
df_numeric_columns = df.select_dtypes("number").drop(["id"], axis=1)
|
||||
else:
|
||||
df_numeric_columns = df.select_dtypes("number")
|
||||
df_numeric_columns = (
|
||||
df.select_dtypes("number").drop(["id"], axis=1)
|
||||
if "id" in df.columns
|
||||
else df.select_dtypes("number")
|
||||
)
|
||||
else:
|
||||
df_numeric_columns = df[columns]
|
||||
|
||||
df_max = df_numeric_columns.max().max()
|
||||
df_min = df_numeric_columns.min().min()
|
||||
ranges = [((df_max - df_min) * i) + df_min for i in bounds]
|
||||
styles = []
|
||||
legend = []
|
||||
|
||||
styles: list[dict[str, Any]] = []
|
||||
legend: list[html.Div] = []
|
||||
|
||||
for i in range(1, len(bounds)):
|
||||
min_bound = ranges[i - 1]
|
||||
max_bound = ranges[i]
|
||||
backgroundColor = colorlover.scales[str(n_bins)]["seq"]["Blues"][i - 1]
|
||||
background_color = colorlover.scales[str(n_bins)]["seq"]["Blues"][i - 1]
|
||||
color = "white" if i > len(bounds) / 2.0 else "inherit"
|
||||
|
||||
for column in df_numeric_columns:
|
||||
for column in df_numeric_columns.columns:
|
||||
filter_query = f"{{{column}}} >= {min_bound}" + (
|
||||
f" && {{{column}}} < {max_bound}" if i < len(bounds) - 1 else ""
|
||||
)
|
||||
styles.append({
|
||||
"if": {
|
||||
"filter_query": (
|
||||
"{{{column}}} >= {min_bound}"
|
||||
+ (
|
||||
" && {{{column}}} < {max_bound}"
|
||||
if (i < len(bounds) - 1)
|
||||
else ""
|
||||
)
|
||||
).format(column=column, min_bound=min_bound, max_bound=max_bound),
|
||||
"filter_query": filter_query,
|
||||
"column_id": column,
|
||||
},
|
||||
"backgroundColor": backgroundColor,
|
||||
"backgroundColor": background_color,
|
||||
"color": color,
|
||||
})
|
||||
|
||||
legend.append(
|
||||
html.Div(
|
||||
style={"display": "inline-block", "width": "60px"},
|
||||
children=[
|
||||
html.Div(
|
||||
style={
|
||||
"backgroundColor": backgroundColor,
|
||||
"backgroundColor": background_color,
|
||||
"borderLeft": "1px rgb(50, 50, 50) solid",
|
||||
"height": "10px",
|
||||
}
|
||||
@@ -117,112 +125,84 @@ def discrete_background_color_bins(df, n_bins=5, columns="all"):
|
||||
)
|
||||
)
|
||||
|
||||
return (styles, html.Div(legend, style={"padding": "5px 0 5px 0"}))
|
||||
return styles, html.Div(legend, style={"padding": "5px 0 5px 0"})
|
||||
|
||||
|
||||
####################
|
||||
# GRAPHICAL ELEMENTS
|
||||
####################
|
||||
def build_bar_chart(display_df, table_config, barchart_elements, norm_filt):
|
||||
"""
|
||||
Read data into a bar chart. ID will determine which subtype of barchart.
|
||||
"""
|
||||
d_figs = []
|
||||
def create_instruction_mix_bar_chart(display_df: pd.DataFrame, df_unit: str) -> px.bar:
|
||||
display_df = display_df.copy()
|
||||
display_df["Avg"] = display_df["Avg"].apply(lambda x: int(x) if x != "" else 0)
|
||||
|
||||
# Insr Mix bar chart
|
||||
if table_config["id"] in barchart_elements["instr_mix"]:
|
||||
display_df["Avg"] = [
|
||||
x.astype(int) if x != "" else int(0) for x in display_df["Avg"]
|
||||
]
|
||||
df_unit = display_df["Unit"].iloc[0]
|
||||
d_figs.append(
|
||||
return px.bar(
|
||||
display_df,
|
||||
x="Avg",
|
||||
y="Metric",
|
||||
color="Avg",
|
||||
labels={"Avg": f"# of {df_unit.lower()}"},
|
||||
height=400,
|
||||
orientation="h",
|
||||
)
|
||||
|
||||
|
||||
def create_multi_bar_charts(
|
||||
display_df: pd.DataFrame, table_id: int, df_unit: str
|
||||
) -> list[px.bar]:
|
||||
display_df = display_df.copy()
|
||||
display_df["Avg"] = display_df["Avg"].apply(lambda x: int(x) if x != "" else 0)
|
||||
|
||||
nested_bar = multi_bar_chart(table_id, display_df)
|
||||
charts = []
|
||||
|
||||
for group, metric in nested_bar.items():
|
||||
chart = px.bar(
|
||||
title=group,
|
||||
x=list(metric.values()),
|
||||
y=list(metric.keys()),
|
||||
labels={"x": df_unit, "y": ""},
|
||||
text=list(metric.values()),
|
||||
orientation="h",
|
||||
height=200,
|
||||
)
|
||||
chart.update_xaxes(showgrid=False, rangemode="nonnegative")
|
||||
chart.update_yaxes(showgrid=False)
|
||||
chart.update_layout(title_x=0.5)
|
||||
charts.append(chart)
|
||||
|
||||
return charts
|
||||
|
||||
|
||||
def create_sol_charts(display_df: pd.DataFrame, table_id: int) -> list[px.bar]:
|
||||
display_df = display_df.copy()
|
||||
display_df["Avg"] = display_df["Avg"].apply(lambda x: float(x) if x != "" else 0.0)
|
||||
|
||||
charts = []
|
||||
|
||||
if table_id == 1701:
|
||||
# Special layout for L2 Cache SOL
|
||||
pct_data = display_df[display_df["Unit"] == "Pct"]
|
||||
charts.append(
|
||||
px.bar(
|
||||
display_df,
|
||||
pct_data,
|
||||
x="Avg",
|
||||
y="Metric",
|
||||
color="Avg",
|
||||
labels={"Avg": "# of {}".format(df_unit.lower())},
|
||||
height=400,
|
||||
range_color=[0, 100],
|
||||
labels={"Avg": "%"},
|
||||
height=220,
|
||||
orientation="h",
|
||||
)
|
||||
).update_xaxes(range=[0, 110], ticks="inside", title="%")
|
||||
)
|
||||
|
||||
# Multi bar chart
|
||||
elif table_config["id"] in barchart_elements["multi_bar"]:
|
||||
display_df["Avg"] = [
|
||||
x.astype(int) if x != "" else int(0) for x in display_df["Avg"]
|
||||
]
|
||||
df_unit = display_df["Unit"].iloc[0]
|
||||
nested_bar = multi_bar_chart(table_config["id"], display_df)
|
||||
# generate chart for each coherency
|
||||
for group, metric in nested_bar.items():
|
||||
d_figs.append(
|
||||
# HBM Bandwidth chart
|
||||
hbm_row = display_df[display_df["Metric"] == "HBM Bandwidth"]
|
||||
if not hbm_row.empty:
|
||||
hbm_bw = float(hbm_row["Avg"].iloc[0])
|
||||
gb_data = display_df[display_df["Unit"] == "Gb/s"]
|
||||
charts.append(
|
||||
px.bar(
|
||||
title=group,
|
||||
x=metric.values(),
|
||||
y=metric.keys(),
|
||||
labels={"x": df_unit, "y": ""},
|
||||
text=metric.values(),
|
||||
orientation="h",
|
||||
height=200,
|
||||
)
|
||||
.update_xaxes(showgrid=False, rangemode="nonnegative")
|
||||
.update_yaxes(showgrid=False)
|
||||
.update_layout(title_x=0.5)
|
||||
)
|
||||
# L2 Cache per channel
|
||||
# elif table_config["id"] in barchart_elements["l2_cache_per_chan"]:
|
||||
# nested_bar = {}
|
||||
# channels = []
|
||||
# for colName, colData in display_df.items():
|
||||
# if colName == "Channel":
|
||||
# channels = list(colData.values)
|
||||
# else:
|
||||
# display_df[colName] = [
|
||||
# x.astype(float) if x != "" and x != None else float(0)
|
||||
# for x in display_df[colName]
|
||||
# ]
|
||||
# nested_bar[colName] = list(display_df[colName])
|
||||
# for group, metric in nested_bar.items():
|
||||
# d_figs.append(
|
||||
# px.bar(
|
||||
# title=group[0 : group.rfind("(")],
|
||||
# x=channels,
|
||||
# y=metric,
|
||||
# labels={
|
||||
# "x": "Channel",
|
||||
# "y": group[group.rfind("(") + 1 : len(group) - 1].replace(
|
||||
# "per", norm_filt
|
||||
# ),
|
||||
# },
|
||||
# ).update_yaxes(rangemode="nonnegative")
|
||||
# )
|
||||
|
||||
# Speed-of-light bar chart
|
||||
elif table_config["id"] in barchart_elements["sol"]:
|
||||
display_df["Avg"] = [
|
||||
float(x) if x != "" else float(0) for x in display_df["Avg"]
|
||||
]
|
||||
if table_config["id"] == 1701:
|
||||
# special layout for L2 Cache SOL
|
||||
d_figs.append(
|
||||
px.bar(
|
||||
display_df[display_df["Unit"] == "Pct"],
|
||||
x="Avg",
|
||||
y="Metric",
|
||||
color="Avg",
|
||||
range_color=[0, 100],
|
||||
labels={"Avg": "%"},
|
||||
height=220,
|
||||
orientation="h",
|
||||
).update_xaxes(range=[0, 110], ticks="inside", title="%")
|
||||
) # append first % chart
|
||||
hbm_bw = float(
|
||||
display_df[display_df["Metric"] == "HBM Bandwidth"]["Avg"].iloc[0]
|
||||
)
|
||||
d_figs.append(
|
||||
px.bar(
|
||||
display_df[display_df["Unit"] == "Gb/s"],
|
||||
gb_data,
|
||||
x="Avg",
|
||||
y="Metric",
|
||||
color="Avg",
|
||||
@@ -231,88 +211,145 @@ def build_bar_chart(display_df, table_config, barchart_elements, norm_filt):
|
||||
height=220,
|
||||
orientation="h",
|
||||
).update_xaxes(range=[0, hbm_bw])
|
||||
) # append second GB/s chart
|
||||
elif table_config["id"] == 1101:
|
||||
# Special formatting reference 'Pct of Peak' value
|
||||
display_df["Pct of Peak"] = [
|
||||
x.astype(float) if x != "" else float(0)
|
||||
for x in display_df["Pct of Peak"]
|
||||
]
|
||||
d_figs.append(
|
||||
px.bar(
|
||||
display_df,
|
||||
x="Pct of Peak",
|
||||
y="Metric",
|
||||
color="Pct of Peak",
|
||||
range_color=[0, 100],
|
||||
labels={"Avg": "%"},
|
||||
height=400,
|
||||
orientation="h",
|
||||
).update_xaxes(range=[0, 110])
|
||||
)
|
||||
else:
|
||||
d_figs.append(
|
||||
px.bar(
|
||||
display_df,
|
||||
x="Avg",
|
||||
y="Metric",
|
||||
color="Avg",
|
||||
range_color=[0, 100],
|
||||
labels={"Avg": "%"},
|
||||
height=400,
|
||||
orientation="h",
|
||||
).update_xaxes(range=[0, 110])
|
||||
)
|
||||
|
||||
elif table_id == 1101:
|
||||
# Special formatting reference 'Pct of Peak' value
|
||||
display_df["Pct of Peak"] = display_df["Pct of Peak"].apply(
|
||||
lambda x: float(x) if x != "" else 0.0
|
||||
)
|
||||
charts.append(
|
||||
px.bar(
|
||||
display_df,
|
||||
x="Pct of Peak",
|
||||
y="Metric",
|
||||
color="Pct of Peak",
|
||||
range_color=[0, 100],
|
||||
labels={"Avg": "%"},
|
||||
height=400,
|
||||
orientation="h",
|
||||
).update_xaxes(range=[0, 110])
|
||||
)
|
||||
else:
|
||||
console_error(
|
||||
"Table id %s. Cannot determine barchart type." % table_config["id"]
|
||||
charts.append(
|
||||
px.bar(
|
||||
display_df,
|
||||
x="Avg",
|
||||
y="Metric",
|
||||
color="Avg",
|
||||
range_color=[0, 100],
|
||||
labels={"Avg": "%"},
|
||||
height=400,
|
||||
orientation="h",
|
||||
).update_xaxes(range=[0, 110])
|
||||
)
|
||||
|
||||
# update layout for each of the charts
|
||||
for fig in d_figs:
|
||||
return charts
|
||||
|
||||
|
||||
def build_bar_chart(
|
||||
display_df: pd.DataFrame,
|
||||
table_config: dict[str, Any],
|
||||
barchart_elements: dict[str, Any],
|
||||
) -> list:
|
||||
"""
|
||||
Read data into a bar chart. ID will determine which subtype of barchart.
|
||||
"""
|
||||
table_id = table_config["id"]
|
||||
charts: list[px.bar] = []
|
||||
|
||||
# Get unit from first row if available
|
||||
df_unit = display_df["Unit"].iloc[0] if "Unit" in display_df.columns else ""
|
||||
|
||||
# Instruction Mix bar chart
|
||||
if table_id in barchart_elements["instr_mix"]:
|
||||
charts.append(create_instruction_mix_bar_chart(display_df, df_unit))
|
||||
|
||||
# Multi bar chart
|
||||
elif table_id in barchart_elements["multi_bar"]:
|
||||
charts.extend(create_multi_bar_charts(display_df, table_id, df_unit))
|
||||
|
||||
# Speed-of-light bar chart
|
||||
elif table_id in barchart_elements["sol"]:
|
||||
charts.extend(create_sol_charts(display_df, table_id))
|
||||
|
||||
else:
|
||||
console_error(
|
||||
f"Table id {table_id}. Cannot determine barchart type.", exit=False
|
||||
)
|
||||
return []
|
||||
|
||||
# Apply consistent styling to all charts
|
||||
for fig in charts:
|
||||
fig.update_layout(
|
||||
margin=dict(l=50, r=50, b=50, t=50, pad=4),
|
||||
paper_bgcolor="rgba(0,0,0,0)",
|
||||
plot_bgcolor="rgba(0,0,0,0)",
|
||||
font={"color": "#ffffff"},
|
||||
)
|
||||
return d_figs
|
||||
|
||||
return charts
|
||||
|
||||
|
||||
def get_dark_mode_styles() -> tuple[
|
||||
dict[str, Any], dict[str, Any], list[dict[str, Any]]
|
||||
]:
|
||||
if not IS_DARK:
|
||||
return {}, {}, []
|
||||
|
||||
style_header = {
|
||||
"backgroundColor": "rgb(30, 30, 30)",
|
||||
"color": "white",
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
|
||||
style_data = {
|
||||
"backgroundColor": "rgb(50, 50, 50)",
|
||||
"color": "white",
|
||||
"whiteSpace": "normal",
|
||||
"height": "auto",
|
||||
}
|
||||
|
||||
style_data_conditional = [
|
||||
{"if": {"row_index": "odd"}, "backgroundColor": "rgb(60, 60, 60)"}
|
||||
]
|
||||
|
||||
return style_header, style_data, style_data_conditional
|
||||
|
||||
|
||||
def build_table_chart(
|
||||
display_df, table_config, original_df, display_columns, comparable_columns, decimal
|
||||
):
|
||||
display_df: pd.DataFrame,
|
||||
table_config: dict[str, Any],
|
||||
original_df: pd.DataFrame,
|
||||
display_columns: list[str],
|
||||
comparable_columns: list[str],
|
||||
decimal: int,
|
||||
) -> list[dash_table.DataTable]:
|
||||
"""
|
||||
Read data into a DashTable
|
||||
"""
|
||||
d_figs = []
|
||||
|
||||
# build comlumns/header with formatting
|
||||
formatted_columns = []
|
||||
for col in display_df.columns:
|
||||
if (
|
||||
str(col).lower() == "pct"
|
||||
or str(col).lower() == "pop"
|
||||
or str(col).lower() == "percentage"
|
||||
):
|
||||
formatted_columns.append(
|
||||
dict(
|
||||
id=col,
|
||||
name=col,
|
||||
type="numeric",
|
||||
format={"specifier": ".{}f".format(decimal)},
|
||||
)
|
||||
)
|
||||
col_lower = str(col).lower()
|
||||
if col_lower in {"pct", "pop", "percentage"}:
|
||||
formatted_columns.append({
|
||||
"id": col,
|
||||
"name": col,
|
||||
"type": "numeric",
|
||||
"format": {"specifier": f".{decimal}f"},
|
||||
})
|
||||
elif col in comparable_columns:
|
||||
formatted_columns.append(
|
||||
dict(
|
||||
id=col,
|
||||
name=col,
|
||||
type="numeric",
|
||||
format={"specifier": ".{}f".format(decimal)},
|
||||
)
|
||||
)
|
||||
formatted_columns.append({
|
||||
"id": col,
|
||||
"name": col,
|
||||
"type": "numeric",
|
||||
"format": {"specifier": f".{decimal}f"},
|
||||
})
|
||||
else:
|
||||
formatted_columns.append(dict(id=col, name=col, type="text"))
|
||||
formatted_columns.append({"id": col, "name": col, "type": "text"})
|
||||
|
||||
# tooltip shows only on the 1st col for now if 'Metric Description' available
|
||||
table_tooltip = (
|
||||
@@ -326,7 +363,7 @@ def build_table_chart(
|
||||
),
|
||||
"type": "markdown",
|
||||
}
|
||||
for column, value in row.items()
|
||||
for column in row.keys()
|
||||
}
|
||||
for row in original_df.to_dict("records")
|
||||
]
|
||||
@@ -334,6 +371,9 @@ def build_table_chart(
|
||||
else None
|
||||
)
|
||||
|
||||
# Get styling based on dark mode
|
||||
style_header, style_data, style_data_conditional = get_dark_mode_styles()
|
||||
|
||||
# build data table with columns, tooltip, df and other properties
|
||||
d_t = dash_table.DataTable(
|
||||
id=str(table_config["id"]),
|
||||
@@ -348,36 +388,11 @@ def build_table_chart(
|
||||
# style cell
|
||||
style_cell={"maxWidth": "500px"},
|
||||
# display style
|
||||
style_header=(
|
||||
{
|
||||
"backgroundColor": "rgb(30, 30, 30)",
|
||||
"color": "white",
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
if IS_DARK
|
||||
else {}
|
||||
),
|
||||
style_data=(
|
||||
{
|
||||
"backgroundColor": "rgb(50, 50, 50)",
|
||||
"color": "white",
|
||||
"whiteSpace": "normal",
|
||||
"height": "auto",
|
||||
}
|
||||
if IS_DARK
|
||||
else {}
|
||||
),
|
||||
style_data_conditional=(
|
||||
[
|
||||
{"if": {"row_index": "odd"}, "backgroundColor": "rgb(60, 60, 60)"},
|
||||
]
|
||||
if IS_DARK
|
||||
else []
|
||||
),
|
||||
style_header=style_header,
|
||||
style_data=style_data,
|
||||
style_data_conditional=style_data_conditional,
|
||||
# the df to display
|
||||
data=display_df.to_dict("records"),
|
||||
)
|
||||
# print("DATA: \n", display_df.to_dict('records'))
|
||||
d_figs.append(d_t)
|
||||
return d_figs
|
||||
# print(d_t.columns)
|
||||
|
||||
@@ -23,16 +23,19 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
import dash_bootstrap_components as dbc
|
||||
import pandas as pd
|
||||
from dash import dcc, html
|
||||
|
||||
from utils import schema
|
||||
|
||||
avail_normalizations = ["per_wave", "per_cycle", "per_second", "per_kernel"]
|
||||
AVAIL_NORMALIZATIONS = ["per_wave", "per_cycle", "per_second", "per_kernel"]
|
||||
|
||||
|
||||
# List all the unique column values for desired column in df, 'target_col'
|
||||
def list_unique(orig_list, is_numeric):
|
||||
def list_unique(orig_list: list[str], is_numeric: bool) -> list[str]:
|
||||
list_set = set(orig_list)
|
||||
unique_list = list(list_set)
|
||||
if is_numeric:
|
||||
@@ -40,18 +43,23 @@ def list_unique(orig_list, is_numeric):
|
||||
return unique_list
|
||||
|
||||
|
||||
def create_span(input):
|
||||
return {"label": html.Span(str(input), title=str(input)), "value": str(input)}
|
||||
def create_span(input_value: str) -> dict[str, Union[html.Span, str]]:
|
||||
return {
|
||||
"label": html.Span(str(input_value), title=str(input_value)),
|
||||
"value": str(input_value),
|
||||
}
|
||||
|
||||
|
||||
def get_header(raw_pmc, input_filters, kernel_names):
|
||||
kernel_names = list(
|
||||
map(
|
||||
str,
|
||||
raw_pmc[schema.pmc_perf_file_prefix]["Kernel_Name"],
|
||||
)
|
||||
)
|
||||
kernel_names = [x.strip() for x in kernel_names]
|
||||
def get_header(
|
||||
raw_pmc: pd.DataFrame, input_filters: dict[str, Any], kernel_names: list[str]
|
||||
) -> html.Header:
|
||||
pmc_data = raw_pmc[schema.PMC_PERF_FILE_PREFIX]
|
||||
kernel_names = [str(name).strip() for name in pmc_data["Kernel_Name"]]
|
||||
|
||||
# Extract GPU and Dispatch IDs
|
||||
gpu_ids = [str(gpu_id) for gpu_id in pmc_data["GPU_ID"]]
|
||||
dispatch_ids = [str(dispatch_id) for dispatch_id in pmc_data["Dispatch_ID"]]
|
||||
|
||||
return html.Header(
|
||||
id="home",
|
||||
children=[
|
||||
@@ -175,7 +183,7 @@ def get_header(raw_pmc, input_filters, kernel_names):
|
||||
children=["Normalization:"],
|
||||
),
|
||||
dcc.Dropdown(
|
||||
avail_normalizations,
|
||||
AVAIL_NORMALIZATIONS,
|
||||
id="norm-filt",
|
||||
value=input_filters["normalization"],
|
||||
clearable=False,
|
||||
@@ -196,14 +204,7 @@ def get_header(raw_pmc, input_filters, kernel_names):
|
||||
),
|
||||
dcc.Dropdown(
|
||||
list_unique(
|
||||
list(
|
||||
map(
|
||||
str,
|
||||
raw_pmc[
|
||||
schema.pmc_perf_file_prefix
|
||||
]["GPU_ID"],
|
||||
)
|
||||
),
|
||||
gpu_ids,
|
||||
True,
|
||||
), # list avail gcd ids
|
||||
id="gcd-filt",
|
||||
@@ -229,14 +230,7 @@ def get_header(raw_pmc, input_filters, kernel_names):
|
||||
children=["Dispatch Filter:"],
|
||||
),
|
||||
dcc.Dropdown(
|
||||
list(
|
||||
map(
|
||||
str,
|
||||
raw_pmc[
|
||||
schema.pmc_perf_file_prefix
|
||||
]["Dispatch_ID"],
|
||||
)
|
||||
),
|
||||
dispatch_ids,
|
||||
id="disp-filt",
|
||||
multi=True,
|
||||
# default to any dispatch
|
||||
@@ -282,15 +276,12 @@ def get_header(raw_pmc, input_filters, kernel_names):
|
||||
children=["Kernels:"],
|
||||
),
|
||||
dcc.Dropdown(
|
||||
list(
|
||||
map(
|
||||
create_span,
|
||||
list_unique(
|
||||
orig_list=kernel_names,
|
||||
is_numeric=False,
|
||||
), # list avail kernel names
|
||||
[
|
||||
create_span(name)
|
||||
for name in list_unique(
|
||||
kernel_names, False
|
||||
)
|
||||
),
|
||||
],
|
||||
id="kernel-filt",
|
||||
multi=True,
|
||||
value=input_filters["kernel"],
|
||||
|
||||
@@ -22,29 +22,34 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
##############################################################################
|
||||
from typing import Any
|
||||
|
||||
from dash import html
|
||||
from dash_svg import G, Path, Rect, Svg, Text
|
||||
|
||||
from utils import schema
|
||||
from utils.logger import console_error
|
||||
from utils.utils import format_scientific_notation_if_needed
|
||||
|
||||
# Constants for display formatting
|
||||
DEFAULT_MAX_LENGTH = 6
|
||||
DEFAULT_PRECISION = 1
|
||||
DEFAULT_SCIENTIFIC_WIDTH = 8
|
||||
|
||||
def insert_chart_data(mem_data, base_data):
|
||||
|
||||
def insert_chart_data(mem_data: list[dict[str, Any]], base_data: schema.Workload) -> G:
|
||||
if len(mem_data) != 1:
|
||||
console_error("Memory Chart config doesn't follow expected formatting")
|
||||
|
||||
table_config = mem_data[0]["metric_table"]
|
||||
|
||||
original_df = base_data.dfs[table_config["id"]]
|
||||
|
||||
display_columns = original_df.columns.values.tolist().copy()
|
||||
display_df = original_df[display_columns]
|
||||
|
||||
alias = display_df["Metric"].values
|
||||
values = display_df["Value"].values
|
||||
|
||||
memchart_values = {}
|
||||
memchart_values: dict[str, Any] = {}
|
||||
for i in range(0, len(alias)):
|
||||
memchart_values[alias[i]] = values[i]
|
||||
|
||||
@@ -521,7 +526,9 @@ def insert_chart_data(mem_data, base_data):
|
||||
)
|
||||
|
||||
|
||||
def get_memchart(mem_data, base_data):
|
||||
def get_memchart(
|
||||
mem_data: list[dict[str, Any]], base_data: schema.Workload
|
||||
) -> html.Section:
|
||||
return html.Section(
|
||||
id="memchart",
|
||||
children=[
|
||||
@@ -2039,7 +2046,7 @@ def get_memchart(mem_data, base_data):
|
||||
)
|
||||
|
||||
|
||||
def format_value_for_display(value, max_length=6):
|
||||
def format_value_for_display(value: Any, max_length: int = DEFAULT_MAX_LENGTH) -> str: # noqa: ANN401
|
||||
"""
|
||||
Format a value (int, float, or str) into a concise string suitable for display.
|
||||
|
||||
@@ -2097,8 +2104,8 @@ def format_value_for_display(value, max_length=6):
|
||||
sci = format_scientific_notation_if_needed(
|
||||
abs_val,
|
||||
align=">",
|
||||
width_align=8,
|
||||
precision=1,
|
||||
width_align=DEFAULT_SCIENTIFIC_WIDTH,
|
||||
precision=DEFAULT_PRECISION,
|
||||
fmt_type_align="e",
|
||||
max_length=max_length,
|
||||
).strip()
|
||||
@@ -2110,7 +2117,7 @@ def format_value_for_display(value, max_length=6):
|
||||
value = normal
|
||||
|
||||
if is_negative:
|
||||
value = "-" + value
|
||||
value = f"-{value}"
|
||||
|
||||
else:
|
||||
value = str(value)
|
||||
@@ -2123,11 +2130,11 @@ def format_value_for_display(value, max_length=6):
|
||||
exponent = value[e_index:]
|
||||
max_mantissa_len = max_length - len(exponent)
|
||||
if max_mantissa_len < 1:
|
||||
value = exponent[: max_length - 1] + "…"
|
||||
value = f"{exponent[: max_length - 1]}…"
|
||||
else:
|
||||
truncated_mantissa = mantissa[:max_mantissa_len]
|
||||
value = truncated_mantissa + exponent
|
||||
else:
|
||||
value = value[: max_length - 1] + "…"
|
||||
value = f"{value[: max_length - 1]}…"
|
||||
|
||||
return value
|
||||
|
||||
@@ -26,112 +26,183 @@
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from utils.logger import console_debug, console_error, console_log
|
||||
|
||||
cache = dict()
|
||||
# Module-level cache for demangled kernel names
|
||||
_NAME_CACHE: dict[str, str] = {}
|
||||
|
||||
# Constants
|
||||
|
||||
# NOTE: c++filt is a Linux-only solution for demangling C++ symbols.
|
||||
# TODO: We need to think about Windows support in the future.
|
||||
# Windows equivalent might be undname.exe or using llvm-cxxfilt.
|
||||
# CONCERN: Using absolute path here is brittle - c++filt location may vary
|
||||
# across distributions. TODO: Consider using shutil.which() or PATH lookup instead.
|
||||
CPP_FILT_PATH = "/usr/bin/c++filt"
|
||||
MAX_SHORTENING_LEVEL = 5
|
||||
KERNEL_NAME_COLUMNS = ["Kernel_Name", "Name"]
|
||||
|
||||
|
||||
# Note: shortener is now dependent on a rocprof install with llvm
|
||||
def kernel_name_shortener(df, level):
|
||||
def shorten_file(df, level):
|
||||
global cache
|
||||
def validate_cpp_filt(cpp_filt_path: str = CPP_FILT_PATH) -> bool:
|
||||
"""Validate that c++filt binary exists and is executable."""
|
||||
if not Path(cpp_filt_path).is_file():
|
||||
console_error(
|
||||
f"Could not resolve c++filt in expected directory: {cpp_filt_path}"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
column_name = ""
|
||||
if "Kernel_Name" in df:
|
||||
column_name = "Kernel_Name"
|
||||
if "Name" in df:
|
||||
column_name = "Name"
|
||||
|
||||
if column_name == "Kernel_Name" or column_name == "Name":
|
||||
# loop through all indices
|
||||
for index in df.index:
|
||||
original_name = df.loc[index, column_name]
|
||||
if original_name in cache:
|
||||
continue
|
||||
def demangle_kernel_name(original_name: str, cpp_filt_path: str = CPP_FILT_PATH) -> str:
|
||||
cmd = [cpp_filt_path, original_name]
|
||||
|
||||
cmd = [cpp_filt, original_name]
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
demangled_name, error = proc.communicate()
|
||||
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
console_error(f"c++filt failed for {original_name}: {error}", exit=False)
|
||||
return original_name
|
||||
|
||||
demangled_name, e = proc.communicate()
|
||||
demangled_name = str(demangled_name, "UTF-8").strip()
|
||||
return demangled_name.strip()
|
||||
|
||||
# cache miss, add the shortened name to the dictionary
|
||||
new_name = ""
|
||||
matches = ""
|
||||
except (subprocess.SubprocessError, OSError) as e:
|
||||
console_error(f"Error running c++filt: {e}", exit=False)
|
||||
return original_name
|
||||
|
||||
names_and_args = re.compile(
|
||||
r"(?P<name>[( )A-Za-z0-9_]+)([ ,*<>()]+)(::)?"
|
||||
)
|
||||
|
||||
# works for name:
|
||||
# Kokkos::namespace::init_lock_array_kernel_threadid(int) [clone .kd]
|
||||
if names_and_args.search(demangled_name):
|
||||
matches = names_and_args.findall(demangled_name)
|
||||
else:
|
||||
# Works for first case '__amd_rocclr_fillBuffer.kd'
|
||||
cache[original_name] = new_name
|
||||
if new_name == None or new_name == "":
|
||||
cache[original_name] = demangled_name
|
||||
continue
|
||||
def parse_template_depth(text: str, level: int, current_level: int) -> tuple[str, int]:
|
||||
result = ""
|
||||
curr_index = 0
|
||||
|
||||
current_level = 0
|
||||
for name in matches:
|
||||
# can cause errors if a function name-
|
||||
# or argument is equal to 'clone'
|
||||
if name[0] == "clone":
|
||||
continue
|
||||
if len(name) == 3:
|
||||
if name[2] == "::":
|
||||
continue
|
||||
while curr_index < len(text) and ">" in text:
|
||||
if current_level < level:
|
||||
result += text[curr_index:]
|
||||
current_level -= text[curr_index:].count(">")
|
||||
break
|
||||
elif text[curr_index] == ">":
|
||||
current_level -= 1
|
||||
curr_index += 1
|
||||
|
||||
if current_level < level:
|
||||
new_name += name[0]
|
||||
# closing '>' is to be taken account by the while loop
|
||||
if name[1].count(">") == 0:
|
||||
if current_level < level:
|
||||
if not (
|
||||
current_level == level - 1 and name[1].count("<") > 0
|
||||
):
|
||||
new_name += name[1]
|
||||
current_level += name[1].count("<")
|
||||
return result, current_level
|
||||
|
||||
curr_index = 0
|
||||
# cases include '>' '> >, ' have to go in depth here to-
|
||||
# not lose account of commas and current level
|
||||
while name[1].count(">") > 0 and curr_index < len(name[1]):
|
||||
if current_level < level:
|
||||
new_name += name[1][curr_index:]
|
||||
current_level -= name[1][curr_index:].count(">")
|
||||
curr_index = len(name[1])
|
||||
elif name[1][curr_index] == (">"):
|
||||
current_level -= 1
|
||||
curr_index += 1
|
||||
|
||||
cache[original_name] = new_name
|
||||
if new_name == None or new_name == "":
|
||||
cache[original_name] = demangled_name
|
||||
def shorten_demangled_name(demangled_name: str, level: int) -> str:
|
||||
names_and_args_pattern = re.compile(r"(?P<name>[( )A-Za-z0-9_]+)([ ,*<>()]+)(::)?")
|
||||
|
||||
df[column_name] = df[column_name].map(cache)
|
||||
matches = names_and_args_pattern.findall(demangled_name)
|
||||
|
||||
if not matches:
|
||||
# Handle cases like '__amd_rocclr_fillBuffer.kd'
|
||||
return demangled_name
|
||||
|
||||
shortened_name = ""
|
||||
current_level = 0
|
||||
|
||||
for name_part, args_part, scope_op in matches:
|
||||
# Skip 'clone' parts as they can cause errors
|
||||
if name_part == "clone":
|
||||
continue
|
||||
|
||||
# Skip scope operators
|
||||
if scope_op == "::":
|
||||
continue
|
||||
|
||||
# Add name part if within level limit
|
||||
if current_level < level:
|
||||
shortened_name += name_part
|
||||
|
||||
# Handle template arguments
|
||||
if ">" not in args_part:
|
||||
if current_level < level:
|
||||
# Don't add opening brackets at the deepest level
|
||||
if not (current_level == level - 1 and "<" in args_part):
|
||||
shortened_name += args_part
|
||||
current_level += args_part.count("<")
|
||||
else:
|
||||
# Handle closing template brackets
|
||||
if current_level < level:
|
||||
shortened_name += args_part
|
||||
current_level -= args_part.count(">")
|
||||
else:
|
||||
_, current_level = parse_template_depth(args_part, level, current_level)
|
||||
|
||||
return shortened_name if shortened_name else demangled_name
|
||||
|
||||
|
||||
def process_single_kernel_name(
|
||||
original_name: str, level: int, cpp_filt_path: str = CPP_FILT_PATH
|
||||
) -> str:
|
||||
if original_name in _NAME_CACHE:
|
||||
return _NAME_CACHE[original_name]
|
||||
|
||||
demangled_name = demangle_kernel_name(original_name, cpp_filt_path)
|
||||
shortened_name = shorten_demangled_name(demangled_name, level)
|
||||
|
||||
final_name = shortened_name if shortened_name else demangled_name
|
||||
_NAME_CACHE[original_name] = final_name
|
||||
|
||||
return final_name
|
||||
|
||||
|
||||
def get_kernel_column_name(df: pd.DataFrame) -> Optional[str]:
|
||||
for column_name in KERNEL_NAME_COLUMNS:
|
||||
if column_name in df.columns:
|
||||
return column_name
|
||||
return None
|
||||
|
||||
|
||||
def shorten_file(
|
||||
df: pd.DataFrame, level: int, cpp_filt_path: str = CPP_FILT_PATH
|
||||
) -> pd.DataFrame:
|
||||
column_name = get_kernel_column_name(df)
|
||||
if not column_name:
|
||||
console_debug("No kernel name column found")
|
||||
return df
|
||||
|
||||
# Only shorten if valid shortening level
|
||||
if level < 5:
|
||||
cpp_filt = str(Path("/usr").joinpath("bin", "c++filt"))
|
||||
if not Path(cpp_filt).is_file():
|
||||
console_error(
|
||||
"Could not resolve c++filt in expected directory: %s" % cpp_filt
|
||||
)
|
||||
df_copy = df.copy()
|
||||
|
||||
try:
|
||||
modified_df = shorten_file(df, level)
|
||||
console_log("profiling", "Kernel_Name shortening complete.")
|
||||
return modified_df
|
||||
except pd.errors.EmptyDataError:
|
||||
console_debug("profiling", "Skipping shortening on empty csv")
|
||||
df_copy[column_name] = df_copy[column_name].apply(
|
||||
lambda name: process_single_kernel_name(name, level, cpp_filt_path)
|
||||
)
|
||||
|
||||
return df_copy
|
||||
|
||||
|
||||
def kernel_name_shortener(df: pd.DataFrame, level: int) -> Optional[pd.DataFrame]:
|
||||
"""Shorten kernel names in a DataFrame.
|
||||
|
||||
NOTE: shortener is now dependent on a rocprof install with llvm
|
||||
|
||||
Args:
|
||||
df: DataFrame containing kernel names
|
||||
level: Shortening level (0-4)
|
||||
|
||||
Returns:
|
||||
DataFrame with shortened kernel names, or None if processing fails
|
||||
"""
|
||||
|
||||
if level >= MAX_SHORTENING_LEVEL:
|
||||
console_debug("profiling", "Skipping kernel name shortening: level >= 5")
|
||||
return df
|
||||
|
||||
cpp_filt = CPP_FILT_PATH
|
||||
if not validate_cpp_filt(cpp_filt):
|
||||
return df
|
||||
|
||||
try:
|
||||
modified_df = shorten_file(df, level, cpp_filt)
|
||||
console_log("profiling", "Kernel_Name shortening complete.")
|
||||
return modified_df
|
||||
except pd.errors.EmptyDataError:
|
||||
console_debug("profiling", "Skipping shortening on empty csv")
|
||||
return df
|
||||
except Exception as e:
|
||||
console_error(f"Error during kernel name shortening: {e}")
|
||||
return df
|
||||
|
||||
@@ -27,6 +27,9 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Optional, TypeVar
|
||||
|
||||
R = TypeVar("R")
|
||||
|
||||
# Define the colors
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
||||
@@ -42,58 +45,80 @@ COLORS = {
|
||||
"TRACE": MAGENTA,
|
||||
}
|
||||
|
||||
# Constants
|
||||
TRACE_LEVEL = logging.DEBUG - 5
|
||||
|
||||
def demarcate(function):
|
||||
def wrap_function(*args, **kwargs):
|
||||
logging.trace("----- [entering function] -> %s()" % (function.__qualname__))
|
||||
LOG_LEVEL_MAPPING = {
|
||||
"DEBUG": logging.DEBUG,
|
||||
"debug": logging.DEBUG,
|
||||
"TRACE": TRACE_LEVEL,
|
||||
"trace": TRACE_LEVEL,
|
||||
"INFO": logging.INFO,
|
||||
"info": logging.INFO,
|
||||
"ERROR": logging.ERROR,
|
||||
"error": logging.ERROR,
|
||||
}
|
||||
|
||||
|
||||
def demarcate(function: Callable[..., R]) -> Callable[..., R]:
|
||||
def wrap_function(*args: Any, **kwargs: Any) -> R:
|
||||
trace_logger(f"----- [entering function] -> {function.__qualname__}()")
|
||||
result = function(*args, **kwargs)
|
||||
logging.trace("----- [exiting function] -> %s()" % function.__qualname__)
|
||||
trace_logger(f"----- [exiting function] -> {function.__qualname__}()")
|
||||
return result
|
||||
|
||||
return wrap_function
|
||||
|
||||
|
||||
def console_error(*argv, exit=True):
|
||||
def console_error(*argv: Any, exit: bool = True) -> None:
|
||||
if len(argv) > 1:
|
||||
logging.error(f"[{argv[0]}] {argv[1]}")
|
||||
else:
|
||||
elif len(argv) == 1:
|
||||
logging.error(f"{argv[0]}")
|
||||
else:
|
||||
logging.error("Empty error message")
|
||||
if exit:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def console_log(*argv, indent_level=0):
|
||||
def console_log(*argv: Any, indent_level: int = 0) -> None:
|
||||
indent = ""
|
||||
if indent_level >= 1:
|
||||
indent = " " * 3 * indent_level + "|-> " # spaces per indent level
|
||||
indent = " " * (3 * indent_level) + "|-> " # spaces per indent level
|
||||
|
||||
if len(argv) > 1:
|
||||
logging.info(indent + f"[{argv[0]}] {argv[1]}")
|
||||
else:
|
||||
elif len(argv) == 1:
|
||||
logging.info(indent + f"{argv[0]}")
|
||||
else:
|
||||
logging.info(indent + "Empty log message")
|
||||
|
||||
|
||||
def console_debug(*argv):
|
||||
def console_debug(*argv: Any) -> None:
|
||||
if len(argv) > 1:
|
||||
logging.debug(f"[{argv[0]}] {argv[1]}")
|
||||
else:
|
||||
elif len(argv) == 1:
|
||||
logging.debug(f"{argv[0]}")
|
||||
else:
|
||||
logging.debug("Empty debug message")
|
||||
|
||||
|
||||
def console_warning(*argv):
|
||||
def console_warning(*argv: Any) -> None:
|
||||
if len(argv) > 1:
|
||||
logging.warning(f"[{argv[0]}] {argv[1]}")
|
||||
else:
|
||||
elif len(argv) == 1:
|
||||
logging.warning(f"{argv[0]}")
|
||||
else:
|
||||
logging.warning("Empty warning message")
|
||||
|
||||
|
||||
def trace_logger(message, *args, **kwargs):
|
||||
logging.log(logging.TRACE, message, *args, **kwargs)
|
||||
def trace_logger(message: str, *args: Any, **kwargs: Any) -> None:
|
||||
logging.log(TRACE_LEVEL, message, *args, **kwargs)
|
||||
|
||||
|
||||
# Define the formatter
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
levelname = record.levelname
|
||||
if levelname in COLORS:
|
||||
levelname_color = (
|
||||
@@ -104,7 +129,7 @@ class ColoredFormatter(logging.Formatter):
|
||||
|
||||
|
||||
class ColoredFormatterAll(logging.Formatter):
|
||||
def format(self, record):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
levelname = record.levelname
|
||||
if levelname in COLORS:
|
||||
if levelname == "INFO":
|
||||
@@ -115,11 +140,12 @@ class ColoredFormatterAll(logging.Formatter):
|
||||
f"%(levelname)s: %(message)s{RESET_SEQ}"
|
||||
)
|
||||
formatter = logging.Formatter(log_fmt)
|
||||
return formatter.format(record)
|
||||
return formatter.format(record)
|
||||
return super().format(record)
|
||||
|
||||
|
||||
class PlainFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
if record.levelno == logging.ERROR:
|
||||
self._style._fmt = "%(levelname)s %(message)s"
|
||||
else:
|
||||
@@ -129,12 +155,11 @@ class PlainFormatter(logging.Formatter):
|
||||
|
||||
# Setup console handler - provided as separate function to be called
|
||||
# prior to argument parsing
|
||||
def setup_console_handler():
|
||||
def setup_console_handler() -> None:
|
||||
logging.getLogger().handlers.clear()
|
||||
# register a trace level logger
|
||||
logging.TRACE = logging.DEBUG - 5
|
||||
logging.addLevelName(logging.TRACE, "TRACE")
|
||||
setattr(logging, "TRACE", logging.TRACE)
|
||||
logging.addLevelName(TRACE_LEVEL, "TRACE")
|
||||
setattr(logging, "TRACE", TRACE_LEVEL)
|
||||
setattr(logging, "trace", trace_logger)
|
||||
|
||||
color_setting = 1
|
||||
@@ -164,8 +189,8 @@ def setup_console_handler():
|
||||
|
||||
|
||||
# Setup file handler - enabled in profile mode
|
||||
def setup_file_handler(loglevel, workload_dir):
|
||||
filename = str(Path(workload_dir).joinpath("log.txt"))
|
||||
def setup_file_handler(loglevel: int, workload_dir: str) -> None:
|
||||
filename = str(Path(workload_dir) / "log.txt")
|
||||
file_handler = logging.FileHandler(filename, "w")
|
||||
file_loglevel = min([loglevel, logging.INFO])
|
||||
file_handler.setLevel(file_loglevel)
|
||||
@@ -174,9 +199,11 @@ def setup_file_handler(loglevel, workload_dir):
|
||||
|
||||
|
||||
# Setup logger priority - called after argument parsing
|
||||
def setup_logging_priority(verbosity, quietmode, appmode, guimode):
|
||||
def setup_logging_priority(
|
||||
verbosity: int, quietmode: bool, appmode: str, guimode: Optional[bool] = None
|
||||
) -> int:
|
||||
# set loglevel based on selected verbosity and quietmode
|
||||
levels = [logging.INFO, logging.DEBUG, logging.TRACE]
|
||||
levels = [logging.INFO, logging.DEBUG, TRACE_LEVEL]
|
||||
|
||||
if quietmode:
|
||||
loglevel = logging.ERROR
|
||||
@@ -191,18 +218,11 @@ def setup_logging_priority(verbosity, quietmode, appmode, guimode):
|
||||
# optional: override of default loglevel via env variable which takes precedence
|
||||
if "ROCPROFCOMPUTE_LOGLEVEL" in os.environ.keys():
|
||||
loglevel = os.environ["ROCPROFCOMPUTE_LOGLEVEL"]
|
||||
if loglevel in {"DEBUG", "debug"}:
|
||||
loglevel = logging.DEBUG
|
||||
elif loglevel in {"TRACE", "trace"}:
|
||||
loglevel = logging.TRACE
|
||||
elif loglevel in {"INFO", "info"}:
|
||||
loglevel = logging.INFO
|
||||
elif loglevel in {"ERROR", "error"}:
|
||||
loglevel = logging.ERROR
|
||||
|
||||
if loglevel in LOG_LEVEL_MAPPING:
|
||||
loglevel = LOG_LEVEL_MAPPING[loglevel]
|
||||
else:
|
||||
print(
|
||||
"Ignoring unsupported ROCPROFCOMPUTE_LOGLEVEL setting (%s)" % loglevel
|
||||
)
|
||||
print(f"Ignoring unsupported ROCPROFCOMPUTE_LOGLEVEL setting ({loglevel})")
|
||||
sys.exit(1)
|
||||
|
||||
# update console loglevel based on command-line args/env settings
|
||||
|
||||
@@ -25,14 +25,12 @@
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from typing import Dict
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from plotille import Canvas
|
||||
|
||||
from .utils import format_scientific_notation_if_needed
|
||||
from plotille import Canvas # type: ignore
|
||||
|
||||
|
||||
def make_format_spec(num, align=">"):
|
||||
def make_format_spec(num: Union[int, float], align: str = ">") -> str:
|
||||
"""
|
||||
Generate alignment string for a given input
|
||||
"""
|
||||
@@ -45,6 +43,11 @@ def make_format_spec(num, align=">"):
|
||||
|
||||
int_part = str(d.to_integral_value())
|
||||
|
||||
# Handle special cases where exponent is not an integer (NaN, Infinity, etc.)
|
||||
if not isinstance(exponent, int):
|
||||
# For special values, just return basic format
|
||||
return f"{align}{str(num)}f"
|
||||
|
||||
if exponent >= 0:
|
||||
# Pure integer, or float like 6.0, 6.00 (no decimal places)
|
||||
if isinstance(num, int):
|
||||
@@ -60,7 +63,7 @@ def make_format_spec(num, align=">"):
|
||||
return f"{align}{num_str}f"
|
||||
|
||||
|
||||
def is_value_valid(value):
|
||||
def is_value_valid(value: Union[int, float, str, None]) -> bool:
|
||||
"""
|
||||
Check if a value is valid and display N/A if not
|
||||
(to be valid, it needs to be not None, and be int or float)
|
||||
@@ -75,15 +78,15 @@ def is_value_valid(value):
|
||||
|
||||
|
||||
def format_text(
|
||||
value,
|
||||
key=None,
|
||||
value: Union[int, float, str, None],
|
||||
key: Union[str, Union[int, float], None] = None,
|
||||
mark_between: str = ": ",
|
||||
post_description_with_space: str = "",
|
||||
value_step_prec_rightalign=0,
|
||||
key_step_prec_leftalign=0,
|
||||
key_align="<",
|
||||
value_align=">",
|
||||
):
|
||||
value_step_prec_rightalign: Union[int, float] = 0,
|
||||
key_step_prec_leftalign: Union[int, float] = 0,
|
||||
key_align: str = "<",
|
||||
value_align: str = ">",
|
||||
) -> str:
|
||||
"""
|
||||
Format a text string for canvas to display according to
|
||||
input key-value pair and make proper alignment.
|
||||
@@ -94,61 +97,25 @@ def format_text(
|
||||
# Step 1: Build format spec using make_format_spec
|
||||
value_format = make_format_spec(value_step_prec_rightalign, value_align)
|
||||
|
||||
# Step 2: Extract width and precision as integer
|
||||
match = re.match(r"([<>=^])(\d+)(?:\.(\d+))?([a-zA-Z])?", value_format)
|
||||
if match:
|
||||
align_char = match.group(1)
|
||||
width_align = int(match.group(2))
|
||||
precision_digits = match.group(3)
|
||||
fmt_type_align = match.group(4) or "f"
|
||||
precision = int(precision_digits) if precision_digits else 0
|
||||
else:
|
||||
# Fallback to default values
|
||||
align_char = value_align
|
||||
width_align = 6
|
||||
precision = 2
|
||||
fmt_type_align = "f"
|
||||
|
||||
# Step 3: Format the key using make_format_spec
|
||||
key_format = (
|
||||
make_format_spec(key_step_prec_leftalign, key_align)
|
||||
if key is not None
|
||||
else None
|
||||
)
|
||||
key_str = (
|
||||
"{key:{key_format}}".format(key=key, key_format=key_format)
|
||||
if key is not None and isinstance(key, (int, float))
|
||||
else str(key)
|
||||
if key is not None
|
||||
else None
|
||||
)
|
||||
|
||||
# Step 4: Format the value or fallback to N/A
|
||||
if is_value_valid(value):
|
||||
formatted_value = format_scientific_notation_if_needed(
|
||||
value,
|
||||
align=align_char,
|
||||
width_align=width_align,
|
||||
precision=precision,
|
||||
fmt_type_align=fmt_type_align,
|
||||
max_length=width_align,
|
||||
sci_lower_bound=1e-3,
|
||||
sci_upper_bound=1e3,
|
||||
)
|
||||
value_str = formatted_value
|
||||
value_str = f"{value:{value_format}}"
|
||||
else:
|
||||
value_str = f"{'N/A':{align_char}{width_align}}"
|
||||
match = re.search(r"[<>=^](\d+)", value_format)
|
||||
width = int(match.group(1)) if match else 6
|
||||
|
||||
# Step 5: Unit and Final Output
|
||||
unit_string = post_description_with_space if "N/A" not in value_str else ""
|
||||
# Use same alignment as in value_format (first char)
|
||||
align = value_format[0]
|
||||
value_str = f"{'N/A':{align}{width}}"
|
||||
|
||||
if key_str is not None:
|
||||
if key is not None:
|
||||
key_format = make_format_spec(key_step_prec_leftalign, key_align)
|
||||
key_str = f"{key:{key_format}}" if isinstance(key, (int, float)) else str(key)
|
||||
result_str_no_unit = f"{key_str}{mark_between}{value_str}"
|
||||
else:
|
||||
result_str_no_unit = value_str
|
||||
result_str_no_unit = f"{value_str}"
|
||||
|
||||
result_str = result_str_no_unit + unit_string
|
||||
return result_str
|
||||
unit_string = post_description_with_space if "N/A" not in value_str else ""
|
||||
return result_str_no_unit + unit_string
|
||||
|
||||
|
||||
# A basic rect frame for any block or group of wires where all its elements should
|
||||
@@ -166,11 +133,10 @@ class RectFrame:
|
||||
# Instr Buff Block
|
||||
@dataclass
|
||||
class InstrBuff(RectFrame):
|
||||
wave_occupancy: int = None
|
||||
wave_life: int = None
|
||||
wave_occupancy: Optional[int] = None
|
||||
wave_life: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
# print("---------", self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max - 2.0, self.y_max - 1.0)
|
||||
@@ -208,8 +174,8 @@ class InstrBuff(RectFrame):
|
||||
# Wires between Instr Buff and Instr Dispatch
|
||||
@dataclass
|
||||
class Wire_InstrBuff_InstrDispatch(RectFrame):
|
||||
def draw(self, canvas):
|
||||
# Todo: finer wires for connections
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
# TODO: finer wires for connections
|
||||
canvas.line(self.x_min + 2, self.y_min, self.x_min + 2, self.y_max)
|
||||
canvas.line(self.x_max, self.y_min + 1.5, self.x_max, self.y_max - 1.5)
|
||||
canvas.line(self.x_min + 2, self.y_min, self.x_max, self.y_min + 1.5)
|
||||
@@ -227,9 +193,9 @@ class InstrDispatch(RectFrame):
|
||||
text_y_offset: float = 0.5
|
||||
line_y_offset: float = 0.5
|
||||
rect_y_offset: float = 3.0
|
||||
instrs: Dict[str, int] = field(default_factory=dict)
|
||||
instrs: dict[str, int] = field(default_factory=dict)
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
|
||||
self.top_rect_x_min = self.x_min + 2.0
|
||||
@@ -237,9 +203,7 @@ class InstrDispatch(RectFrame):
|
||||
self.top_rect_y_min = self.y_max - 1.5
|
||||
self.top_rect_y_max = self.y_max
|
||||
|
||||
i = 0
|
||||
for k, v in self.instrs.items():
|
||||
# print(k,v)
|
||||
for i, (k, v) in enumerate(self.instrs.items()):
|
||||
text = format_text(
|
||||
key=k,
|
||||
value=v,
|
||||
@@ -258,7 +222,6 @@ class InstrDispatch(RectFrame):
|
||||
self.top_rect_y_min - self.rect_y_offset * i,
|
||||
"------------------>",
|
||||
)
|
||||
i = i + 1
|
||||
|
||||
|
||||
# Exec Block
|
||||
@@ -273,7 +236,7 @@ class Exec(RectFrame):
|
||||
wavefronts: int = 0
|
||||
workgroups: int = 0
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
@@ -378,13 +341,13 @@ class Exec(RectFrame):
|
||||
class Wire_E_GLVS(RectFrame):
|
||||
text_x_offset: float = 3.0
|
||||
|
||||
lds_req: int = None
|
||||
vl1_rd: int = None
|
||||
vl1_wr: int = None
|
||||
vl1_atomic: int = None
|
||||
sl1_rd: int = None
|
||||
lds_req: Optional[int] = None
|
||||
vl1_rd: Optional[int] = None
|
||||
vl1_wr: Optional[int] = None
|
||||
vl1_atomic: Optional[int] = None
|
||||
sl1_rd: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(
|
||||
self.x_min + self.text_x_offset,
|
||||
self.y_max - 2.0,
|
||||
@@ -459,7 +422,7 @@ class Wire_E_GLVS(RectFrame):
|
||||
class Wire_InstrBuff_IL1Cache(RectFrame):
|
||||
il1_fetch: int = 0
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
end_col = int(self.y_max - self.y_min)
|
||||
canvas.text(self.x_min, self.y_max - 1, "^")
|
||||
for i in range(2, end_col):
|
||||
@@ -482,10 +445,10 @@ class Wire_InstrBuff_IL1Cache(RectFrame):
|
||||
# GDS Block
|
||||
@dataclass
|
||||
class GDS(RectFrame):
|
||||
gws: int = None
|
||||
latency: int = None
|
||||
gws: Optional[int] = None
|
||||
latency: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
|
||||
@@ -523,10 +486,10 @@ class GDS(RectFrame):
|
||||
# LDS Block
|
||||
@dataclass
|
||||
class LDS(RectFrame):
|
||||
util: int = None
|
||||
latency: int = None
|
||||
util: Optional[int] = None
|
||||
latency: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
canvas.text(
|
||||
@@ -556,12 +519,12 @@ class LDS(RectFrame):
|
||||
# Vector L1 Cache Block
|
||||
@dataclass
|
||||
class VectorL1Cache(RectFrame):
|
||||
hit: int = None
|
||||
latency: int = None
|
||||
coales: int = None
|
||||
stall: int = None
|
||||
hit: Optional[int] = None
|
||||
latency: Optional[int] = None
|
||||
coales: Optional[int] = None
|
||||
stall: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
|
||||
@@ -614,10 +577,10 @@ class VectorL1Cache(RectFrame):
|
||||
# Scalar L1D Cache
|
||||
@dataclass
|
||||
class ScalarL1DCache(RectFrame):
|
||||
hit: int = None
|
||||
latency: int = None
|
||||
hit: Optional[int] = None
|
||||
latency: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
|
||||
@@ -648,10 +611,10 @@ class ScalarL1DCache(RectFrame):
|
||||
# Instr L1 Cache
|
||||
@dataclass
|
||||
class InstrL1Cache(RectFrame):
|
||||
hit: int = None
|
||||
latency: int = None
|
||||
hit: Optional[int] = None
|
||||
latency: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
|
||||
@@ -684,15 +647,15 @@ class InstrL1Cache(RectFrame):
|
||||
class Wires_L1_L2(RectFrame):
|
||||
text_v_x_offset: float = 0.0
|
||||
|
||||
vl1_l2_rd: int = None
|
||||
vl1_l2_wr: int = None
|
||||
vl1_l2_atomic: int = None
|
||||
sl1_l2_rd: int = None
|
||||
sl1_l2_wr: int = None
|
||||
sl1_l2_atomic: int = None
|
||||
il1_l2_req: int = None
|
||||
vl1_l2_rd: Optional[int] = None
|
||||
vl1_l2_wr: Optional[int] = None
|
||||
vl1_l2_atomic: Optional[int] = None
|
||||
sl1_l2_rd: Optional[int] = None
|
||||
sl1_l2_wr: Optional[int] = None
|
||||
sl1_l2_atomic: Optional[int] = None
|
||||
il1_l2_req: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(
|
||||
self.x_min + self.text_v_x_offset,
|
||||
self.y_max - 2.0,
|
||||
@@ -783,14 +746,14 @@ class Wires_L1_L2(RectFrame):
|
||||
# L2 Cache
|
||||
@dataclass
|
||||
class L2Cache(RectFrame):
|
||||
rd: int = None
|
||||
wr: int = None
|
||||
atomic: int = None
|
||||
hit: int = None
|
||||
rd_lat: int = None
|
||||
wr_lat: int = None
|
||||
rd: Optional[int] = None
|
||||
wr: Optional[int] = None
|
||||
atomic: Optional[int] = None
|
||||
hit: Optional[int] = None
|
||||
rd_lat: Optional[int] = None
|
||||
wr_lat: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min, self.y_max + 1.0, self.label)
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
|
||||
@@ -876,11 +839,11 @@ class L2Cache(RectFrame):
|
||||
class Wire_L2_Fabric(RectFrame):
|
||||
text_x_offset: float = 3.0
|
||||
|
||||
rd: int = None
|
||||
wr: int = None
|
||||
atomic: int = None
|
||||
rd: Optional[int] = None
|
||||
wr: Optional[int] = None
|
||||
atomic: Optional[int] = None
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(
|
||||
self.x_min + self.text_x_offset,
|
||||
self.y_max - 2.0,
|
||||
@@ -925,7 +888,7 @@ class Wire_L2_Fabric(RectFrame):
|
||||
# xGMI/PCIe block with wires to fabric
|
||||
@dataclass
|
||||
class xGMI_PCIe(RectFrame):
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
canvas.text(self.x_min + 1.0, self.y_max - 2.0, self.label)
|
||||
canvas.text(self.x_min + 3.0, self.y_max - 5.0, "^ |")
|
||||
@@ -937,9 +900,9 @@ class xGMI_PCIe(RectFrame):
|
||||
# Fabric Cache Block
|
||||
@dataclass
|
||||
class Fabric(RectFrame):
|
||||
lat: Dict[str, int] = field(default_factory=dict)
|
||||
lat: dict[str, int] = field(default_factory=dict)
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
canvas.text(self.x_min + 6.0, self.y_max - 2.0, " " + self.label)
|
||||
canvas.text(self.x_min + 2.0, self.y_max - 4.0, "Latency (cycles)")
|
||||
@@ -947,24 +910,20 @@ class Fabric(RectFrame):
|
||||
self.x_min + 2.0, self.y_max - 9, self.x_max - 2.0, self.y_max - 4.5
|
||||
)
|
||||
|
||||
i = 1
|
||||
for k, v in self.lat.items():
|
||||
# print(k,v)
|
||||
for i, (k, v) in enumerate(self.lat.items(), 1):
|
||||
text = format_text(
|
||||
key=k,
|
||||
value=v,
|
||||
key_step_prec_leftalign=6,
|
||||
value_step_prec_rightalign=6.0,
|
||||
)
|
||||
|
||||
canvas.text(self.x_min + 4.0, self.y_max - 4.5 - i, text)
|
||||
i = i + 1
|
||||
|
||||
|
||||
# GMI block with wires to fabric
|
||||
@dataclass
|
||||
class GMI(RectFrame):
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(self.x_min + 3.0, self.y_max + 4.0, "^ |")
|
||||
canvas.text(self.x_min + 3.0, self.y_max + 3.0, "| |")
|
||||
canvas.text(self.x_min + 3.0, self.y_max + 2.0, "| |")
|
||||
@@ -981,7 +940,7 @@ class Wire_Fabric_HBM(RectFrame):
|
||||
rd: int = 0
|
||||
wr: int = 0
|
||||
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.text(
|
||||
self.x_min + self.text_x_offset,
|
||||
self.y_max,
|
||||
@@ -1013,30 +972,31 @@ class Wire_Fabric_HBM(RectFrame):
|
||||
# HBM
|
||||
@dataclass
|
||||
class HBM(RectFrame):
|
||||
def draw(self, canvas):
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
canvas.text(self.x_min + 4.0, self.y_max - 2.0, self.label)
|
||||
|
||||
|
||||
# Memory chart pannel for 1 instance
|
||||
class MemChart:
|
||||
def __init__(self, x_min, y_min, x_max, y_max):
|
||||
def __init__(self, x_min: float, y_min: float, x_max: float, y_max: float) -> None:
|
||||
self.x_min = x_min
|
||||
self.x_max = x_max
|
||||
self.y_min = y_min
|
||||
self.y_max = y_max
|
||||
|
||||
def draw(self, canvas, normal_unit, metric_dict):
|
||||
def draw(
|
||||
self, canvas: Canvas, normal_unit: str, metric_dict: dict[str, Any]
|
||||
) -> None:
|
||||
# ----------------------------------------
|
||||
# Overall rect and title
|
||||
canvas.rect(self.x_min, self.y_min, self.x_max, self.y_max)
|
||||
canvas.text(
|
||||
self.x_min + 2.0, self.y_max - 2.0, "(Normalization: " + normal_unit + ")"
|
||||
self.x_min + 2.0, self.y_max - 2.0, f"(Normalization: {normal_unit})"
|
||||
)
|
||||
|
||||
# Fixme: this is temp solution to filter out non-numeric string
|
||||
# FIXME: this is temp solution to filter out non-numeric string
|
||||
for k, v in metric_dict.items():
|
||||
# print(k, type(v))
|
||||
metric_dict[k] = None if isinstance(v, str) else v
|
||||
|
||||
# Typically, the drawing order would be: left->right, top->down
|
||||
@@ -1317,20 +1277,18 @@ class MemChart:
|
||||
block_hbm.draw(canvas)
|
||||
|
||||
|
||||
def plot_mem_chart(arch, normal_unit, metric_dict):
|
||||
"""plot memory chart from an arch with given metrics dict"""
|
||||
|
||||
def plot_mem_chart(arch: str, normal_unit: str, metric_dict: dict[str, Any]) -> str:
|
||||
# TODO: verify metrics dict for given arch first
|
||||
|
||||
canvas = Canvas(width=234, height=42, xmax=234, ymax=42)
|
||||
mc = MemChart(0, 0, 233, 41)
|
||||
mc.draw(canvas, normal_unit, metric_dict)
|
||||
|
||||
# return the plot string stream
|
||||
return canvas.plot()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# TODO: unit test should be moved to tests/*
|
||||
# Unit test
|
||||
metric_dict = {}
|
||||
metric_dict["Wavefront Occupancy"] = 1
|
||||
|
||||
@@ -24,60 +24,42 @@
|
||||
##############################################################################
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
from utils.logger import console_debug, console_error, console_warning
|
||||
|
||||
# Constants for MI series
|
||||
# NOTE: Currently supports MI50, MI100, MI200, MI300
|
||||
MI50 = 0
|
||||
MI100 = 1
|
||||
MI200 = 2
|
||||
MI300 = 3
|
||||
MI350 = 4
|
||||
|
||||
MI_CONSTANS = {
|
||||
MI50: "mi50",
|
||||
MI100: "mi100",
|
||||
MI200: "mi200",
|
||||
MI300: "mi300",
|
||||
MI350: "mi350",
|
||||
}
|
||||
|
||||
# ----------------------------
|
||||
# Data Class handling to preserve the hierarchical gpu information
|
||||
# ----------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class MIGPUSpecs:
|
||||
_instance = None
|
||||
_instance: Optional["MIGPUSpecs"] = None
|
||||
|
||||
_gpu_series_dict = {} # key: gpu_arch
|
||||
_gpu_model_dict = {} # key: gpu_arch
|
||||
_num_xcds_dict = {} # key: gpu_model
|
||||
_chip_id_dict = {} # key: chip_id (int)
|
||||
_perfmon_config = {} # key: gpu_arch
|
||||
_gpu_series_dict: dict[str, str] = {} # key: gpu_arch
|
||||
_gpu_model_dict: dict[str, list[str]] = {} # key: gpu_arch
|
||||
_num_xcds_dict: dict[str, dict[str, int]] = {} # key: gpu_model
|
||||
_chip_id_dict: dict[int, str] = {} # key: chip_id (int)
|
||||
_perfmon_config: dict[str, Any] = {} # key: gpu_arch
|
||||
|
||||
_gpu_arch_to_compute_partition_dict = {} # key: gpu_arch, used for gpu archs
|
||||
# containing only one gpu model and
|
||||
# key: gpu_arch, used for gpu archs containing only one gpu model and
|
||||
# thus one compute partition
|
||||
_gpu_arch_to_compute_partition_dict: dict[str, dict[str, int]] = {}
|
||||
|
||||
_all_gpu_models = []
|
||||
_all_gpu_models: list[str] = []
|
||||
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
def __new__(cls) -> "MIGPUSpecs":
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._initialize()
|
||||
return cls._instance
|
||||
|
||||
@classmethod
|
||||
def _initialize(cls):
|
||||
def _initialize(cls) -> None:
|
||||
if not cls._initialized:
|
||||
cls._parse_mi_gpu_spec()
|
||||
cls._initialized = True
|
||||
@@ -87,7 +69,7 @@ class MIGPUSpecs:
|
||||
# ----------------------------
|
||||
|
||||
@classmethod
|
||||
def _load_yaml(cls, file_path: str) -> Dict[str, Any]:
|
||||
def _load_yaml(cls, file_path: str) -> dict[str, Any]:
|
||||
"""
|
||||
Loads MI GPU YAML data /util into a Python dictionary.
|
||||
|
||||
@@ -98,23 +80,14 @@ class MIGPUSpecs:
|
||||
Dict[str, Any]: Parsed YAML data as a nested dictionary.
|
||||
Exit with console error if an error occurs.
|
||||
"""
|
||||
console_debug("[load_yaml]")
|
||||
try:
|
||||
with open(file_path, "r") as file:
|
||||
data = yaml.safe_load(file)
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
console_error(f"Error: The file '{file_path}' was not found.")
|
||||
except yaml.YAMLError as exc:
|
||||
console_error(f"Error parsing YAML file '{file_path}': {exc}")
|
||||
except Exception as e:
|
||||
console_error(
|
||||
f"An unexpected error occurred while loading YAML "
|
||||
f"file '{file_path}': {e}"
|
||||
)
|
||||
|
||||
console_debug("mi_gpu_spec", "[load_yaml]")
|
||||
with open(file_path) as file:
|
||||
data = yaml.safe_load(file)
|
||||
return data or {}
|
||||
|
||||
@classmethod
|
||||
def _parse_mi_gpu_spec(cls):
|
||||
def _parse_mi_gpu_spec(cls) -> None:
|
||||
"""
|
||||
Parse out mi gpu data from yaml file and store in memory.
|
||||
MI GPUs
|
||||
@@ -136,16 +109,26 @@ class MIGPUSpecs:
|
||||
# Load the YAML data
|
||||
yaml_data = cls._load_yaml(yaml_file_path)
|
||||
|
||||
for series in yaml_data["mi_gpu_spec"]:
|
||||
curr_gpu_series = series["gpu_series"]
|
||||
console_debug("[parse_mi_gpu_spec] Processing series: %s" % curr_gpu_series)
|
||||
for archs in series["gpu_archs"]:
|
||||
curr_gpu_arch = archs["gpu_arch"]
|
||||
for series in yaml_data.get("mi_gpu_spec", []):
|
||||
curr_gpu_series = series.get("gpu_series")
|
||||
if not curr_gpu_series:
|
||||
continue
|
||||
|
||||
console_debug(
|
||||
"mi_gpu_spec",
|
||||
f"[parse_mi_gpu_spec] Processing series: {curr_gpu_series}",
|
||||
)
|
||||
|
||||
for archs in series.get("gpu_archs", []):
|
||||
curr_gpu_arch = archs.get("gpu_arch")
|
||||
|
||||
cls._gpu_series_dict[curr_gpu_arch] = curr_gpu_series
|
||||
cls._perfmon_config[curr_gpu_arch] = archs["perfmon_config"]
|
||||
cls._perfmon_config[curr_gpu_arch] = archs.get("perfmon_config", {})
|
||||
cls._gpu_model_dict[curr_gpu_arch] = []
|
||||
for models in archs["models"]:
|
||||
|
||||
for models in archs.get("models", []):
|
||||
curr_gpu_model = models["gpu_model"]
|
||||
|
||||
cls._all_gpu_models.append(curr_gpu_model)
|
||||
cls._gpu_model_dict[curr_gpu_arch].append(curr_gpu_model)
|
||||
cls._num_xcds_dict[curr_gpu_model] = (
|
||||
@@ -153,6 +136,7 @@ class MIGPUSpecs:
|
||||
.get("compute_partition_mode", {})
|
||||
.get("num_xcds", {})
|
||||
)
|
||||
|
||||
if "chip_ids" in models and "physical" in models["chip_ids"]:
|
||||
cls._chip_id_dict[models["chip_ids"]["physical"]] = (
|
||||
curr_gpu_model
|
||||
@@ -166,7 +150,7 @@ class MIGPUSpecs:
|
||||
cls._populate_gpu_arch_to_compute_partition_dict()
|
||||
|
||||
@classmethod
|
||||
def _populate_gpu_arch_to_compute_partition_dict(cls):
|
||||
def _populate_gpu_arch_to_compute_partition_dict(cls) -> None:
|
||||
"""
|
||||
This creates a mapping of gpu_arch -> compute_partition for architectures
|
||||
where there's only one model (and therefore one partition configuration).
|
||||
@@ -181,53 +165,55 @@ class MIGPUSpecs:
|
||||
compute_partition
|
||||
)
|
||||
console_debug(
|
||||
"[populate_single_arch_partition_dict] Single model "
|
||||
"arch found: %s -> %s (partition: %s)"
|
||||
% (gpu_arch, single_model, compute_partition)
|
||||
f"[populate_single_arch_partition_dict] Single model "
|
||||
f"arch found: {gpu_arch} -> {single_model} (partition:"
|
||||
f" {compute_partition})"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_gpu_series_dict(cls):
|
||||
def get_gpu_series_dict(cls) -> dict[str, str]:
|
||||
if not cls._gpu_series_dict:
|
||||
console_error(
|
||||
"gpu_series_dict not yet populated, did you run parse_mi_gpu_spec()?"
|
||||
"gpu_series_dict not yet populated, did you run parse_mi_gpu_spec()?",
|
||||
exit=False,
|
||||
)
|
||||
return None
|
||||
return cls._gpu_series_dict
|
||||
|
||||
@classmethod
|
||||
def get_gpu_series(cls, gpu_arch_):
|
||||
def get_gpu_series(cls, gpu_arch: str) -> Optional[str]:
|
||||
if not cls._gpu_series_dict:
|
||||
console_error(
|
||||
"gpu_series_dict not yet populated, did you run parse_mi_gpu_spec()?"
|
||||
"gpu_series_dict not yet populated, did you run parse_mi_gpu_spec()?",
|
||||
exit=False,
|
||||
)
|
||||
return None
|
||||
|
||||
# Normalize the key by checking both the raw and lowercase versions
|
||||
gpu_series = cls._gpu_series_dict.get(gpu_arch_) or cls._gpu_series_dict.get(
|
||||
gpu_arch_.lower()
|
||||
gpu_series = cls._gpu_series_dict.get(gpu_arch) or cls._gpu_series_dict.get(
|
||||
gpu_arch.lower()
|
||||
)
|
||||
if gpu_series:
|
||||
return gpu_series.upper()
|
||||
|
||||
console_warning(f"No matching gpu series found for gpu arch: {gpu_arch_}")
|
||||
console_warning(f"No matching gpu series found for gpu arch: {gpu_arch}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_perfmon_config(cls, gpu_arch_):
|
||||
def get_perfmon_config(cls, gpu_arch: str) -> dict[Any, Any]:
|
||||
# Check that gpu_model_dict is populated first
|
||||
if not cls._perfmon_config:
|
||||
console_error(
|
||||
"gpu_model_dict not yet populated. Did you run parse_mi_gpu_spec()?"
|
||||
)
|
||||
return None
|
||||
|
||||
gpu_arch_lower = gpu_arch_.lower()
|
||||
|
||||
return cls._perfmon_config.get(gpu_arch_lower, None)
|
||||
return cls._perfmon_config.get(gpu_arch.lower(), {})
|
||||
|
||||
@classmethod
|
||||
def get_gpu_model(cls, gpu_arch_, chip_id_):
|
||||
def get_gpu_model(
|
||||
cls, gpu_arch: Optional[str], chip_id: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
if not gpu_arch and not chip_id:
|
||||
return None
|
||||
|
||||
# Check that gpu_model_dict is populated first
|
||||
if not cls._gpu_model_dict:
|
||||
console_error(
|
||||
@@ -235,43 +221,55 @@ class MIGPUSpecs:
|
||||
)
|
||||
return None
|
||||
|
||||
gpu_arch_lower = gpu_arch_.lower()
|
||||
gpu_arch_lower = gpu_arch.lower()
|
||||
|
||||
# Handle gfx942 with chip_id mapping
|
||||
if gpu_arch_lower not in ("gfx908", "gfx90a"):
|
||||
if chip_id_ and int(chip_id_) in cls._chip_id_dict:
|
||||
gpu_model = cls._chip_id_dict.get(int(chip_id_))
|
||||
if chip_id and chip_id.isdigit():
|
||||
chip_id_int = int(chip_id)
|
||||
if chip_id_int in cls._chip_id_dict:
|
||||
gpu_model = cls._chip_id_dict[chip_id_int]
|
||||
else:
|
||||
console_warning(f"No gpu model found for chip id: {chip_id}")
|
||||
return None
|
||||
else:
|
||||
console_warning(f"No gpu model found for chip id: {chip_id_}")
|
||||
console_warning(f"No valid chip id provided: {chip_id}")
|
||||
return None
|
||||
|
||||
# Otherwise use gpu_model_dict mapping for other mi architectures
|
||||
elif gpu_arch_lower in cls._gpu_model_dict:
|
||||
# NOTE: take the first element works for now
|
||||
gpu_model = cls._gpu_model_dict[gpu_arch_lower][0]
|
||||
gpu_models = cls._gpu_model_dict[gpu_arch_lower]
|
||||
if gpu_models:
|
||||
gpu_model = gpu_models[0]
|
||||
else:
|
||||
console_warning(f"No gpu models found for gpu arch: {gpu_arch_lower}")
|
||||
return None
|
||||
else:
|
||||
console_warning(f"No gpu model found for gpu arch: {gpu_arch_lower}")
|
||||
return None
|
||||
|
||||
if not gpu_model:
|
||||
console_warning(f"No gpu model found for gpu arch: {gpu_arch_lower}")
|
||||
return None
|
||||
|
||||
return gpu_model.upper()
|
||||
return gpu_model.upper() if gpu_model else None
|
||||
|
||||
@classmethod
|
||||
def set_default_gpu_settings(self, gpu_arch, gpu_model, compute_partition):
|
||||
def set_default_gpu_settings(
|
||||
cls,
|
||||
gpu_arch: Optional[str],
|
||||
gpu_model: Optional[str],
|
||||
compute_partition: Optional[str],
|
||||
) -> int:
|
||||
"""
|
||||
Set default GPU settings when model is unknown or cannot be
|
||||
determined. NOTE: This is a fallback to gfx942 settings -
|
||||
consider making this architecture-specific.
|
||||
"""
|
||||
|
||||
DEFAULT_COMPUTE_PARTITION = "SPX"
|
||||
DEFAULT_NUM_XCD = 8
|
||||
console_warning(
|
||||
"Unable to determine xcd count from:\n\t"
|
||||
f"GPU arch: '{gpu_arch}', model: '{gpu_model}',\n\t"
|
||||
f"partition: '{compute_partition}'"
|
||||
f'GPU arch: "{gpu_arch}", model: "{gpu_model}",\n\t'
|
||||
f'partition: "{compute_partition}"'
|
||||
)
|
||||
console_warning(
|
||||
f"Applying default gfx942 settings:\n"
|
||||
@@ -283,8 +281,11 @@ class MIGPUSpecs:
|
||||
|
||||
@classmethod
|
||||
def get_num_xcds(
|
||||
cls, gpu_arch: str = None, gpu_model: str = None, compute_partition: str = None
|
||||
):
|
||||
cls,
|
||||
gpu_arch: Optional[str] = None,
|
||||
gpu_model: Optional[str] = None,
|
||||
compute_partition: Optional[str] = None,
|
||||
) -> int:
|
||||
"""
|
||||
Retrieve the number of XCDs based on GPU architecture, model,
|
||||
and compute partition.
|
||||
@@ -310,22 +311,22 @@ class MIGPUSpecs:
|
||||
return 1
|
||||
|
||||
# 2. Try architecture-based lookup first (preferred method)
|
||||
if gpu_arch_norm and hasattr(cls, "_gpu_arch_to_compute_partition_dict"):
|
||||
arch_dict = cls._gpu_arch_to_compute_partition_dict
|
||||
if gpu_arch_norm in arch_dict:
|
||||
num_xcds = arch_dict[gpu_arch_norm]
|
||||
if gpu_arch_norm and gpu_arch_norm in cls._gpu_arch_to_compute_partition_dict:
|
||||
arch_dict = cls._gpu_arch_to_compute_partition_dict[gpu_arch_norm]
|
||||
if partition_norm and partition_norm in arch_dict:
|
||||
num_xcds = arch_dict[partition_norm]
|
||||
if num_xcds is not None:
|
||||
return num_xcds
|
||||
else:
|
||||
console_warning(
|
||||
f"No compute partition data found for "
|
||||
f"architecture '{gpu_arch.upper()}'"
|
||||
)
|
||||
else:
|
||||
console_warning(
|
||||
f"No compute partition data found for "
|
||||
f"architecture: {gpu_arch.upper() if gpu_arch else None}"
|
||||
)
|
||||
|
||||
# 3. Fall back to model + partition-based lookup
|
||||
if gpu_model_norm:
|
||||
# Validate XCD dictionary is populated
|
||||
if not hasattr(cls, "_num_xcds_dict") or not cls._num_xcds_dict:
|
||||
if not cls._num_xcds_dict:
|
||||
console_error(
|
||||
"mi300_num_xcds_dict not populated. "
|
||||
"Did you run parse_mi_gpu_spec()?"
|
||||
@@ -343,9 +344,8 @@ class MIGPUSpecs:
|
||||
)
|
||||
elif partition_norm not in model_dict:
|
||||
console_warning(
|
||||
f"Unknown compute partition "
|
||||
f"'{compute_partition}' for model "
|
||||
f"'{gpu_model}'"
|
||||
f"Unknown compute partition: "
|
||||
f"{compute_partition} for model: {gpu_model}"
|
||||
)
|
||||
else:
|
||||
num_xcds = model_dict[partition_norm]
|
||||
@@ -364,25 +364,19 @@ class MIGPUSpecs:
|
||||
return cls.set_default_gpu_settings(gpu_arch, gpu_model, compute_partition)
|
||||
|
||||
@classmethod
|
||||
def get_chip_id_dict(cls):
|
||||
if cls._chip_id_dict:
|
||||
return cls._chip_id_dict
|
||||
else:
|
||||
console_error()
|
||||
def get_chip_id_dict(cls) -> dict[int, str]:
|
||||
return cls._chip_id_dict
|
||||
|
||||
@classmethod
|
||||
def get_num_xcds_dict(cls):
|
||||
if cls._num_xcds_dict:
|
||||
return cls._num_xcds_dict
|
||||
else:
|
||||
console_error()
|
||||
def get_num_xcds_dict(cls) -> dict[str, dict[str, int]]:
|
||||
return cls._num_xcds_dict
|
||||
|
||||
@classmethod
|
||||
def get_gpu_arch_to_compute_partition_dict(cls):
|
||||
def get_gpu_arch_to_compute_partition_dict(cls) -> dict[str, dict[str, int]]:
|
||||
return cls._gpu_arch_to_compute_partition_dict
|
||||
|
||||
@classmethod
|
||||
def get_all_gpu_models(cls):
|
||||
def get_all_gpu_models(cls) -> list:
|
||||
return cls._all_gpu_models
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,9 @@
|
||||
import csv
|
||||
import sqlite3
|
||||
from contextlib import closing
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from utils.logger import console_error
|
||||
|
||||
@@ -70,19 +73,22 @@ def convert_db_to_csv(
|
||||
])
|
||||
for row in cursor:
|
||||
writer.writerow(row)
|
||||
except (sqlite3.DatabaseError, IOError) as e:
|
||||
console_error(f"Error converting database to CSV: {e}")
|
||||
except OSError as e:
|
||||
console_error(f"Database error while converting to CSV: {e}")
|
||||
except Exception as e:
|
||||
console_error(f"Unexpected error converting database to CSV: {e}")
|
||||
|
||||
|
||||
def process_rocpd_csv(df):
|
||||
def process_rocpd_csv(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Merge counters across unique dispatches from the
|
||||
input dataframe and return processed dataframe.
|
||||
"""
|
||||
# Only import pandas if needed
|
||||
import pandas as pd
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
data: list[dict[str, Any]] = []
|
||||
|
||||
data = list()
|
||||
# Group by unique kernel and merge into a single row
|
||||
for _, group_df in df.groupby([
|
||||
"Dispatch_ID",
|
||||
|
||||
@@ -27,18 +27,18 @@
|
||||
import csv
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from utils import schema
|
||||
from utils.logger import console_debug, console_warning
|
||||
from utils.parser import apply_filters, eval_metric
|
||||
from utils.specs import MachineSpecs
|
||||
|
||||
################################################
|
||||
# Global vars
|
||||
################################################
|
||||
|
||||
IMGNAME = "empirRoof"
|
||||
|
||||
XMIN = 0.01
|
||||
XMAX = 1000
|
||||
|
||||
@@ -48,7 +48,7 @@ FONT_WEIGHT = "bold"
|
||||
|
||||
# SUPPORTED_DATATYPES table is based on datatype support in rocm-amdgpu-bench repository
|
||||
# Indicates which datatypes per gpu arch can be generated by the roofline binary
|
||||
SUPPORTED_DATATYPES = {
|
||||
SUPPORTED_DATATYPES: dict[str, list[str]] = {
|
||||
"gfx90a": [
|
||||
"FP16",
|
||||
"BF16",
|
||||
@@ -135,7 +135,49 @@ class AI_Data:
|
||||
avgDuration: float
|
||||
|
||||
|
||||
def get_font():
|
||||
@dataclass
|
||||
class PlotPoints:
|
||||
"""Data structure for storing roofline plot points."""
|
||||
|
||||
ai_l1: list[list[float]]
|
||||
ai_l2: list[list[float]]
|
||||
ai_hbm: list[list[float]]
|
||||
kernelNames: list[str]
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "PlotPoints":
|
||||
"""Create empty plot points structure."""
|
||||
return cls(ai_l1=[[], []], ai_l2=[[], []], ai_hbm=[[], []], kernelNames=[])
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphPoints:
|
||||
"""Data structure for storing roofline graph ceiling points."""
|
||||
|
||||
hbm: list[Union[list[float], float, None]]
|
||||
l2: list[Union[list[float], float, None]]
|
||||
l1: list[Union[list[float], float, None]]
|
||||
lds: list[Union[list[float], float, None]]
|
||||
valu: list[Union[list[float], float, None]]
|
||||
mfma: list[Union[list[float], float, None]]
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "GraphPoints":
|
||||
"""Create empty graph points structure."""
|
||||
return cls(
|
||||
hbm=[None, None, None],
|
||||
l2=[None, None, None],
|
||||
l1=[None, None, None],
|
||||
lds=[None, None, None],
|
||||
valu=[None, None, None],
|
||||
mfma=[None, None, None],
|
||||
)
|
||||
|
||||
|
||||
################################################
|
||||
# Helper functions
|
||||
################################################
|
||||
def get_font() -> dict[str, Union[int, str]]:
|
||||
return {
|
||||
"size": FONT_SIZE,
|
||||
"color": FONT_COLOR,
|
||||
@@ -144,160 +186,164 @@ def get_font():
|
||||
}
|
||||
|
||||
|
||||
def get_color(catagory):
|
||||
if catagory == "ai_l1":
|
||||
return "green"
|
||||
elif catagory == "ai_l2":
|
||||
return "blue"
|
||||
elif catagory == "ai_hbm":
|
||||
return "red"
|
||||
else:
|
||||
raise RuntimeError("Invalid catagory passed to get_color()")
|
||||
def get_color(category: str) -> str:
|
||||
color_map = {"ai_l1": "green", "ai_l2": "blue", "ai_hbm": "red"}
|
||||
|
||||
if category not in color_map:
|
||||
raise RuntimeError(f"Invalid category passed to get_color(): {category}")
|
||||
|
||||
return color_map[category]
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Plot BW at each cache level
|
||||
# -------------------------------------------------------------------------------------
|
||||
def calc_ceilings(roofline_parameters, dtype, benchmark_data):
|
||||
def calc_ceilings(
|
||||
roofline_parameters: dict[str, Any],
|
||||
dtype: str,
|
||||
benchmark_data: dict[str, list[str]],
|
||||
) -> dict[str, list[Union[list[float], float, None]]]:
|
||||
"""Given benchmarking data, calculate ceilings (or peak performance) for
|
||||
empirical roofline"""
|
||||
# TODO: This is where filtering by memory level will need to occur for standalone
|
||||
graphPoints = {"hbm": [], "l2": [], "l1": [], "lds": [], "valu": [], "mfma": []}
|
||||
graph_points: dict[str, list[Union[list[float], float, None]]] = {
|
||||
"hbm": [],
|
||||
"l2": [],
|
||||
"l1": [],
|
||||
"lds": [],
|
||||
"valu": [],
|
||||
"mfma": [],
|
||||
}
|
||||
|
||||
if roofline_parameters["mem_level"] == "ALL":
|
||||
cacheHierarchy = CACHE_HIERARCHY
|
||||
else:
|
||||
cacheHierarchy = roofline_parameters["mem_level"]
|
||||
cache_hierarchy = (
|
||||
CACHE_HIERARCHY
|
||||
if roofline_parameters["mem_level"] == "ALL"
|
||||
else roofline_parameters["mem_level"]
|
||||
)
|
||||
|
||||
x1 = y1 = x2 = y2 = -1
|
||||
x1_mfma = y1_mfma = x2_mfma = y2_mfma = -1
|
||||
|
||||
ops_flops = "Ops" if (dtype[:1] == "I") else "Flops"
|
||||
ops_flops = "Ops" if dtype.startswith("I") else "Flops"
|
||||
|
||||
peak_ops = 0.0
|
||||
if dtype in PEAK_OPS_DATATYPES:
|
||||
peakOps = float(
|
||||
benchmark_data[dtype + "{}".format(ops_flops)][
|
||||
roofline_parameters["device_id"]
|
||||
]
|
||||
peak_ops = float(
|
||||
benchmark_data[f"{dtype}{ops_flops}"][roofline_parameters["device_id"]]
|
||||
)
|
||||
for i in range(0, len(cacheHierarchy)):
|
||||
|
||||
for cache_level in cache_hierarchy:
|
||||
# Plot BW line
|
||||
console_debug("roofline", "Current cache level is %s" % cacheHierarchy[i])
|
||||
curr_bw = cacheHierarchy[i] + "Bw"
|
||||
peakBw = float(benchmark_data[curr_bw][roofline_parameters["device_id"]])
|
||||
console_debug("roofline", f"Current cache level is {cache_level}")
|
||||
curr_bw = f"{cache_level}Bw"
|
||||
peak_bw = float(benchmark_data[curr_bw][roofline_parameters["device_id"]])
|
||||
|
||||
x1 = float(XMIN)
|
||||
y1 = float(XMIN) * peakBw
|
||||
y1 = float(XMIN) * peak_bw
|
||||
|
||||
if dtype in PEAK_OPS_DATATYPES:
|
||||
x2 = peakOps / peakBw
|
||||
y2 = peakOps # noqa
|
||||
x2 = peak_ops / peak_bw
|
||||
y2 = peak_ops # noqa
|
||||
|
||||
# Plot MFMA lines (NOTE: Assuming MI200 soc)
|
||||
x1_mfma = peakOps / peakBw
|
||||
y1_mfma = peakOps
|
||||
x1_mfma = peak_ops / peak_bw
|
||||
y1_mfma = peak_ops
|
||||
|
||||
peak_mfma = 0.0
|
||||
if dtype in MFMA_DATATYPES:
|
||||
target_precision = (dtype) if (dtype[:1] == "I") else ("F" + dtype[2:])
|
||||
target_precision = dtype if dtype.startswith("I") else f"F{dtype[2:]}"
|
||||
|
||||
peakMFMA = float(
|
||||
benchmark_data["MFMA{}{}".format(target_precision, ops_flops)][
|
||||
peak_mfma = float(
|
||||
benchmark_data[f"MFMA{target_precision}{ops_flops}"][
|
||||
roofline_parameters["device_id"]
|
||||
]
|
||||
)
|
||||
x2_mfma = peakMFMA / peakBw
|
||||
y2_mfma = peakMFMA
|
||||
x2_mfma = peak_mfma / peak_bw
|
||||
y2_mfma = peak_mfma
|
||||
|
||||
# Check which peak is higher for formatting bandwidth lines
|
||||
if y2_mfma > y1_mfma: # peakMFMA
|
||||
peakX = x2_mfma
|
||||
peakY = y2_mfma
|
||||
if y2_mfma > y1_mfma: # peak_mfma
|
||||
peak_x = x2_mfma
|
||||
peak_y = y2_mfma
|
||||
else: # peakVALU
|
||||
peakX = x1_mfma
|
||||
peakY = y1_mfma
|
||||
peak_x = x1_mfma
|
||||
peak_y = y1_mfma
|
||||
|
||||
# These are the points to use:
|
||||
console_debug("roofline", "coordinate points:")
|
||||
console_debug("x = [{}, {}]".format(x1, peakX))
|
||||
console_debug("y = [{}, {}]".format(y1, peakY))
|
||||
console_debug(f"x = [{x1}, {peak_x}]")
|
||||
console_debug(f"y = [{y1}, {peak_y}]")
|
||||
|
||||
graphPoints[cacheHierarchy[i].lower()].append([x1, peakX])
|
||||
graphPoints[cacheHierarchy[i].lower()].append([y1, peakY])
|
||||
graphPoints[cacheHierarchy[i].lower()].append(peakBw)
|
||||
cache_key = cache_level.lower()
|
||||
graph_points[cache_key].extend([[x1, peak_x], [y1, peak_y], peak_bw])
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
# Plot computing roof
|
||||
# ----------------------------------------------------------------------------------
|
||||
if dtype in PEAK_OPS_DATATYPES:
|
||||
# Plot FMA roof
|
||||
x0 = XMAX
|
||||
if x2 < x0:
|
||||
x0 = x2
|
||||
x0 = min(x2, XMAX) if x2 < XMAX else XMAX
|
||||
|
||||
console_debug("FMA ROOF [{}, {}], [{},{}]".format(x0, XMAX, peakOps, peakOps))
|
||||
graphPoints["valu"].append([x0, XMAX])
|
||||
graphPoints["valu"].append([peakOps, peakOps])
|
||||
graphPoints["valu"].append(peakOps)
|
||||
console_debug(f"FMA ROOF [{x0}, {XMAX}], [{peak_ops},{peak_ops}]")
|
||||
graph_points["valu"].extend([[x0, XMAX], [peak_ops, peak_ops], peak_ops])
|
||||
|
||||
# Plot MFMA roof
|
||||
if dtype in MFMA_DATATYPES: # assert that mfma has been assigned
|
||||
x0_mfma = XMAX
|
||||
if x2_mfma < x0_mfma:
|
||||
x0_mfma = x2_mfma
|
||||
x0_mfma = min(x2_mfma, XMAX) if x2_mfma < XMAX else XMAX
|
||||
|
||||
console_debug(
|
||||
"MFMA ROOF [{}, {}], [{},{}]".format(x0_mfma, XMAX, peakMFMA, peakMFMA)
|
||||
)
|
||||
graphPoints["mfma"].append([x0_mfma, XMAX])
|
||||
graphPoints["mfma"].append([peakMFMA, peakMFMA])
|
||||
graphPoints["mfma"].append(peakMFMA)
|
||||
console_debug(f"MFMA ROOF [{x0_mfma}, {XMAX}], [{peak_mfma},{peak_mfma}]")
|
||||
graph_points["mfma"].extend([
|
||||
[x0_mfma, XMAX],
|
||||
[peak_mfma, peak_mfma],
|
||||
peak_mfma,
|
||||
])
|
||||
|
||||
return graphPoints
|
||||
return graph_points
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Overlay application performance
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Calculate relevant metrics for ai calculation
|
||||
def calc_ai_analyze(workload, mspec, sort_type, config, arch_config):
|
||||
def calc_ai_analyze(
|
||||
workload: schema.Workload,
|
||||
mspec: MachineSpecs,
|
||||
sort_type: str,
|
||||
config: dict[str, Any],
|
||||
arch_config: schema.ArchConfig,
|
||||
) -> dict[str, Union[list[list[float]], list[str]]]:
|
||||
"""
|
||||
Calculate per-kernel metrics and AI points with Roofline yamls using eval_metric.
|
||||
"""
|
||||
console_debug("calc_ai_analyze: Starting calc_ai analysis using Roofline yamls")
|
||||
plot_points = {
|
||||
"ai_l1": [[], []],
|
||||
"ai_l2": [[], []],
|
||||
"ai_hbm": [[], []],
|
||||
"kernelNames": [],
|
||||
}
|
||||
console_debug("calc_ai_analyze", "Starting calc_ai analysis using Roofline yamls")
|
||||
plot_points = PlotPoints.empty()
|
||||
|
||||
workload.roofline_metrics = {}
|
||||
filtered_pmc = apply_filters(workload, workload.path, is_gui=False, debug=False)
|
||||
|
||||
kernel_ids_to_process = []
|
||||
kernel_ids_to_process: list[int] = []
|
||||
kernel_top_table_id = 1
|
||||
|
||||
if workload.filter_kernel_ids:
|
||||
kernel_ids_to_process = workload.filter_kernel_ids
|
||||
else:
|
||||
if kernel_top_table_id in workload.dfs:
|
||||
kernel_top_df = workload.dfs[kernel_top_table_id]
|
||||
kernel_ids_to_process = kernel_top_df.index.tolist()
|
||||
console_debug(
|
||||
"roofline", f"Found {len(kernel_ids_to_process)} kernels to process"
|
||||
)
|
||||
elif kernel_top_table_id in workload.dfs:
|
||||
kernel_top_df = workload.dfs[kernel_top_table_id]
|
||||
kernel_ids_to_process = kernel_top_df.index.tolist()
|
||||
console_debug(
|
||||
"roofline", f"Found {len(kernel_ids_to_process)} kernels to process"
|
||||
)
|
||||
|
||||
if not kernel_ids_to_process:
|
||||
console_warning("No kernels found to process for roofline")
|
||||
return plot_points
|
||||
return plot_points.__dict__
|
||||
|
||||
for kernel_id in kernel_ids_to_process:
|
||||
kernel_name = ""
|
||||
if kernel_top_table_id in workload.dfs:
|
||||
kernel_top_df = workload.dfs[kernel_top_table_id]
|
||||
if kernel_id in kernel_top_df.index:
|
||||
kernel_name = kernel_top_df.loc[kernel_id, "Kernel_Name"]
|
||||
else:
|
||||
if kernel_id not in kernel_top_df.index:
|
||||
continue
|
||||
kernel_name = kernel_top_df.loc[kernel_id, "Kernel_Name"]
|
||||
else:
|
||||
continue
|
||||
|
||||
@@ -314,8 +360,8 @@ def calc_ai_analyze(workload, mspec, sort_type, config, arch_config):
|
||||
|
||||
kernel_only_data = {"pmc_perf": kernel_pmc_df["pmc_perf"]}
|
||||
|
||||
kernel_dfs = {}
|
||||
kernel_dfs_type = {}
|
||||
kernel_dfs: dict[int, pd.DataFrame] = {}
|
||||
kernel_dfs_type: dict[int, str] = {}
|
||||
|
||||
for table_id in [401, 402]:
|
||||
if table_id in arch_config.dfs:
|
||||
@@ -368,16 +414,16 @@ def calc_ai_analyze(workload, mspec, sort_type, config, arch_config):
|
||||
# add to plot points if we have valid data
|
||||
if performance > 0:
|
||||
if ai_hbm > 0:
|
||||
plot_points["ai_hbm"][0].append(ai_hbm)
|
||||
plot_points["ai_hbm"][1].append(performance)
|
||||
plot_points.ai_hbm[0].append(ai_hbm)
|
||||
plot_points.ai_hbm[1].append(performance)
|
||||
if ai_l2 > 0:
|
||||
plot_points["ai_l2"][0].append(ai_l2)
|
||||
plot_points["ai_l2"][1].append(performance)
|
||||
plot_points.ai_l2[0].append(ai_l2)
|
||||
plot_points.ai_l2[1].append(performance)
|
||||
if ai_l1 > 0:
|
||||
plot_points["ai_l1"][0].append(ai_l1)
|
||||
plot_points["ai_l1"][1].append(performance)
|
||||
plot_points.ai_l1[0].append(ai_l1)
|
||||
plot_points.ai_l1[1].append(performance)
|
||||
|
||||
plot_points["kernelNames"].append(f"K{kernel_id}")
|
||||
plot_points.kernelNames.append(f"K{kernel_id}")
|
||||
console_debug("roofline", f"Added kernel {kernel_id} to plot points")
|
||||
else:
|
||||
console_debug(
|
||||
@@ -391,14 +437,14 @@ def calc_ai_analyze(workload, mspec, sort_type, config, arch_config):
|
||||
"calc_table": kernel_dfs.get(402, pd.DataFrame()),
|
||||
}
|
||||
|
||||
console_debug(
|
||||
"roofline", f"Generated {len(plot_points['kernelNames'])} plot points"
|
||||
)
|
||||
console_debug("roofline", f"Generated {len(plot_points.kernelNames)} plot points")
|
||||
console_debug("roofline", f"Plot points: {plot_points}")
|
||||
return plot_points
|
||||
return plot_points.__dict__
|
||||
|
||||
|
||||
def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
def calc_ai_profile(
|
||||
mspec: MachineSpecs, sort_type: str, ret_df: dict[str, pd.DataFrame]
|
||||
) -> dict[str, Union[list[list[float]], list[str]]]:
|
||||
"""Given counter data, calculate arithmetic intensity for each kernel
|
||||
in the application. Leverage hard-coded equations to calculate AI values.
|
||||
|
||||
@@ -410,8 +456,7 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
)
|
||||
df = ret_df["pmc_perf"]
|
||||
# Sort by top kernels or top dispatches?
|
||||
df = df.sort_values(by=["Kernel_Name"])
|
||||
df = df.reset_index(drop=True)
|
||||
df = df.sort_values(by=["Kernel_Name"]).reset_index(drop=True)
|
||||
|
||||
total_flops = valu_flops = mfma_flops_f6f4 = mfma_flops_f8 = mfma_flops_bf16 = (
|
||||
mfma_flops_f16
|
||||
@@ -419,25 +464,24 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
L2cache_data
|
||||
) = hbm_data = calls = totalDuration = avgDuration = 0.0
|
||||
|
||||
kernelName = ""
|
||||
kernel_name = ""
|
||||
my_list: list[AI_Data] = []
|
||||
|
||||
myList = []
|
||||
at_end = False
|
||||
next_kernelName = ""
|
||||
|
||||
supported_dt = SUPPORTED_DATATYPES[mspec.gpu_arch]
|
||||
supported_dt = (
|
||||
SUPPORTED_DATATYPES[mspec.gpu_arch]
|
||||
if mspec.gpu_arch in SUPPORTED_DATATYPES
|
||||
else None
|
||||
)
|
||||
|
||||
for idx in df.index:
|
||||
# CASE: Top kernels
|
||||
# Calculate + append AI data if
|
||||
# a) current KernelName is different than previous OR
|
||||
# b) We've reached the end of list
|
||||
if idx + 1 == df.shape[0]:
|
||||
at_end = True
|
||||
else:
|
||||
next_kernelName = df["Kernel_Name"][idx + 1]
|
||||
at_end = idx + 1 == df.shape[0]
|
||||
next_kernel_name = df["Kernel_Name"][idx + 1] if not at_end else ""
|
||||
kernel_name = df["Kernel_Name"][idx]
|
||||
|
||||
kernelName = df["Kernel_Name"][idx]
|
||||
try:
|
||||
total_flops += (
|
||||
(
|
||||
@@ -476,10 +520,10 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
total_flops += df["SQ_INSTS_VALU_MFMA_MOPS_F8"][idx] * 512
|
||||
if ("FP4" in supported_dt) or ("FP6" in supported_dt):
|
||||
total_flops += df["SQ_INSTS_VALU_MFMA_MOPS_F6F4"][idx] * 512
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped total_flops at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped total_flops at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
try:
|
||||
@@ -506,10 +550,10 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
+ df["SQ_INSTS_VALU_TRANS_F64"][idx]
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped valu_flops at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped valu_flops at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -523,10 +567,10 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
mfma_flops_f32 += df["SQ_INSTS_VALU_MFMA_MOPS_F32"][idx] * 512
|
||||
mfma_flops_f64 += df["SQ_INSTS_VALU_MFMA_MOPS_F64"][idx] * 512
|
||||
mfma_iops_i8 += df["SQ_INSTS_VALU_MFMA_MOPS_I8"][idx] * 512
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped mfma ops at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped mfma ops at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -536,19 +580,19 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
* 4
|
||||
* (mspec.lds_banks_per_cu)
|
||||
)
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped lds_data at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped lds_data at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
|
||||
try:
|
||||
L1cache_data += df["TCP_TOTAL_CACHE_ACCESSES_sum"][idx] * 64
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped L1cache_data at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped L1cache_data at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -559,10 +603,10 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
+ df["TCP_TCC_ATOMIC_WITHOUT_RET_REQ_sum"][idx] * 64
|
||||
+ df["TCP_TCC_READ_REQ_sum"][idx] * 64
|
||||
)
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped L2cache_data at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped L2cache_data at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
try:
|
||||
@@ -602,22 +646,21 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
)
|
||||
+ (df["TCC_EA0_WRREQ_64B_sum"][idx] * 64)
|
||||
)
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
console_debug(
|
||||
"roofline",
|
||||
"{}: Skipped hbm_data at index {}".format(kernelName[:35], idx),
|
||||
f"{kernel_name[:35]}: Skipped hbm_data at index {idx} due to {e}",
|
||||
)
|
||||
pass
|
||||
|
||||
totalDuration += df["End_Timestamp"][idx] - df["Start_Timestamp"][idx]
|
||||
avgDuration += df["End_Timestamp"][idx] - df["Start_Timestamp"][idx]
|
||||
|
||||
calls += 1
|
||||
|
||||
if sort_type == "kernels" and (at_end or (kernelName != next_kernelName)):
|
||||
myList.append(
|
||||
if sort_type == "kernels" and (at_end or (kernel_name != next_kernel_name)):
|
||||
my_list.append(
|
||||
AI_Data(
|
||||
kernelName,
|
||||
kernel_name,
|
||||
calls,
|
||||
total_flops / calls,
|
||||
valu_flops / calls,
|
||||
@@ -636,11 +679,8 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
avgDuration / calls,
|
||||
)
|
||||
)
|
||||
console_debug(
|
||||
"Just added {} to AI_Data at index {}. # of calls: {}".format(
|
||||
kernelName, idx, calls
|
||||
)
|
||||
)
|
||||
console_debug(f"Just added {kernel_name} to AI_Data. # of calls: {calls}")
|
||||
|
||||
total_flops = valu_flops = mfma_flops_f6f4 = mfma_flops_f8 = (
|
||||
mfma_flops_bf16
|
||||
) = mfma_flops_f16 = mfma_iops_i8 = mfma_flops_f32 = mfma_flops_f64 = (
|
||||
@@ -650,9 +690,9 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
) = 0.0
|
||||
|
||||
if sort_type == "dispatches":
|
||||
myList.append(
|
||||
my_list.append(
|
||||
AI_Data(
|
||||
kernelName,
|
||||
kernel_name,
|
||||
calls,
|
||||
total_flops,
|
||||
valu_flops,
|
||||
@@ -679,73 +719,64 @@ def calc_ai_profile(mspec, sort_type, ret_df):
|
||||
avgDuration
|
||||
) = 0.0
|
||||
|
||||
myList.sort(key=lambda x: x.totalDuration, reverse=True)
|
||||
my_list.sort(key=lambda x: x.totalDuration, reverse=True)
|
||||
|
||||
intensities = {"ai_l1": [], "ai_l2": [], "ai_hbm": []}
|
||||
curr_perf = []
|
||||
kernelNames = []
|
||||
i = 0
|
||||
# Create list of top 5 intensities
|
||||
while i < TOP_N and i != len(myList):
|
||||
if myList[i].total_flops == 0:
|
||||
intensities: dict[str, list[float]] = {"ai_l1": [], "ai_l2": [], "ai_hbm": []}
|
||||
curr_perf: list[float] = []
|
||||
kernel_names: list[str] = []
|
||||
|
||||
# Create list of top N intensities
|
||||
for i in range(min(TOP_N, len(my_list))):
|
||||
kernel_data = my_list[i]
|
||||
|
||||
if my_list[i].total_flops == 0:
|
||||
console_debug(
|
||||
f"No flops counted for {myList[i].KernelName}, "
|
||||
f"No flops counted for {my_list[i].KernelName}, "
|
||||
"arithmetic intensities will not display on plots."
|
||||
)
|
||||
|
||||
kernelNames.append(myList[i].KernelName)
|
||||
(
|
||||
intensities["ai_l1"].append(myList[i].total_flops / myList[i].L1cache_data)
|
||||
if myList[i].L1cache_data
|
||||
else intensities["ai_l1"].append(0)
|
||||
kernel_names.append(my_list[i].KernelName)
|
||||
|
||||
# Calculate arithmetic intensities
|
||||
intensities["ai_l1"].append(
|
||||
kernel_data.total_flops / kernel_data.L1cache_data
|
||||
if kernel_data.L1cache_data
|
||||
else 0
|
||||
)
|
||||
# print("cur_ai_L1", myList[i].total_flops/myList[i].L1cache_data) if myList[i].L1cache_data else print("null") #noqa
|
||||
# print()
|
||||
(
|
||||
intensities["ai_l2"].append(myList[i].total_flops / myList[i].L2cache_data)
|
||||
if myList[i].L2cache_data
|
||||
else intensities["ai_l2"].append(0)
|
||||
intensities["ai_l2"].append(
|
||||
kernel_data.total_flops / kernel_data.L2cache_data
|
||||
if kernel_data.L2cache_data
|
||||
else 0
|
||||
)
|
||||
# print("cur_ai_L2", myList[i].total_flops/myList[i].L2cache_data) if myList[i].L2cache_data else print("null") #noqa
|
||||
# print()
|
||||
(
|
||||
intensities["ai_hbm"].append(myList[i].total_flops / myList[i].hbm_data)
|
||||
if myList[i].hbm_data
|
||||
else intensities["ai_hbm"].append(0)
|
||||
intensities["ai_hbm"].append(
|
||||
kernel_data.total_flops / kernel_data.hbm_data
|
||||
if kernel_data.hbm_data
|
||||
else 0
|
||||
)
|
||||
# print("cur_ai_hbm", myList[i].total_flops/myList[i].hbm_data) if myList[i].hbm_data else print("null") #noqa
|
||||
# print()
|
||||
(
|
||||
curr_perf.append(myList[i].total_flops / myList[i].avgDuration)
|
||||
if myList[i].avgDuration
|
||||
else curr_perf.append(0)
|
||||
curr_perf.append(
|
||||
kernel_data.total_flops / kernel_data.avgDuration
|
||||
if kernel_data.avgDuration
|
||||
else 0
|
||||
)
|
||||
# print("cur_perf", myList[i].total_flops/myList[i].avgDuration) if myList[i].avgDuration else print("null") #noqa
|
||||
|
||||
i += 1
|
||||
# Create intensity points for plotting
|
||||
intensity_points: dict[str, Union[list[list[float]], list[str]]] = {}
|
||||
|
||||
intensityPoints = {"ai_l1": [], "ai_l2": [], "ai_hbm": []}
|
||||
for ai_type in intensities:
|
||||
values = intensities[ai_type]
|
||||
|
||||
for i in intensities:
|
||||
values = intensities[i]
|
||||
x = values
|
||||
y = curr_perf[: len(values)]
|
||||
intensity_points[ai_type] = [x, y]
|
||||
|
||||
color = get_color(i) # noqa
|
||||
x = []
|
||||
y = []
|
||||
for entryIndx in range(0, len(values)):
|
||||
x.append(values[entryIndx])
|
||||
y.append(curr_perf[entryIndx])
|
||||
|
||||
intensityPoints[i].append(x)
|
||||
intensityPoints[i].append(y)
|
||||
|
||||
# Add an entry for kernel names
|
||||
intensityPoints["kernelNames"] = kernelNames
|
||||
|
||||
return intensityPoints
|
||||
# Add kernel names
|
||||
intensity_points["kernelNames"] = kernel_names
|
||||
return intensity_points
|
||||
|
||||
|
||||
def constuct_roof(roofline_parameters, dtype):
|
||||
def construct_roof(
|
||||
roofline_parameters: dict[str, Any], dtype: str
|
||||
) -> dict[str, list[Union[list[float], float, None]]]:
|
||||
workload_dir = roofline_parameters.get("workload_dir")
|
||||
if isinstance(workload_dir, list):
|
||||
base_dir = (
|
||||
@@ -756,44 +787,35 @@ def constuct_roof(roofline_parameters, dtype):
|
||||
else:
|
||||
base_dir = workload_dir
|
||||
|
||||
benchmark_results = str(Path(base_dir) / "roofline.csv")
|
||||
benchmark_results = Path(base_dir) / "roofline.csv"
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Initialize roofline data dictionary from roofline.csv
|
||||
# -----------------------------------------------------
|
||||
# TODO: consider changing this to an ordered dict for consistency over py versions
|
||||
benchmark_data = {}
|
||||
headers = []
|
||||
benchmark_data: dict[str, list[str]] = {}
|
||||
headers: list[str] = []
|
||||
|
||||
try:
|
||||
with open(benchmark_results, "r") as csvfile:
|
||||
csvReader = csv.reader(csvfile, delimiter=",")
|
||||
rowCount = 0
|
||||
for row in csvReader:
|
||||
with open(benchmark_results) as csvfile:
|
||||
csv_reader = csv.reader(csvfile, delimiter=",")
|
||||
row_count = 0
|
||||
|
||||
for row in csv_reader:
|
||||
row.pop(0) # remove devID
|
||||
if rowCount == 0:
|
||||
if row_count == 0:
|
||||
headers = row
|
||||
for i in headers:
|
||||
benchmark_data[i] = []
|
||||
for header in headers:
|
||||
benchmark_data[header] = []
|
||||
else:
|
||||
for i, key in enumerate(headers):
|
||||
benchmark_data[key].append(row[i])
|
||||
|
||||
rowCount += 1
|
||||
csvfile.close()
|
||||
except Exception:
|
||||
graphPoints = {
|
||||
"hbm": [None, None, None],
|
||||
"l2": [None, None, None],
|
||||
"l1": [None, None, None],
|
||||
"lds": [None, None, None],
|
||||
"valu": [None, None, None],
|
||||
"mfma": [None, None, None],
|
||||
}
|
||||
return graphPoints
|
||||
row_count += 1
|
||||
except Exception as e:
|
||||
console_debug("roofline", f"Failed to read benchmark results: {e}")
|
||||
return GraphPoints.empty().__dict__
|
||||
|
||||
# ------------------
|
||||
# Generate Roofline
|
||||
# ------------------
|
||||
results = calc_ceilings(roofline_parameters, dtype, benchmark_data)
|
||||
|
||||
return results
|
||||
return calc_ceilings(roofline_parameters, dtype, benchmark_data)
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
from collections import OrderedDict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
|
||||
@@ -38,10 +38,10 @@ import pandas as pd
|
||||
@dataclass
|
||||
class ArchConfig:
|
||||
# [id: panel_config] pairs
|
||||
panel_configs: OrderedDict = field(default=dict)
|
||||
panel_configs: OrderedDict[int, Any] = field(default_factory=OrderedDict)
|
||||
|
||||
# [id: df] pairs
|
||||
dfs: Dict[int, pd.DataFrame] = field(default_factory=dict)
|
||||
dfs: dict[int, pd.DataFrame] = field(default_factory=dict)
|
||||
|
||||
# NB:
|
||||
# dfs_type should be a meta info embeded into df.
|
||||
@@ -49,30 +49,34 @@ class ArchConfig:
|
||||
# So do it as below for now.
|
||||
|
||||
# [id: df_type] pairs
|
||||
dfs_type: Dict[int, str] = field(default_factory=dict)
|
||||
dfs_type: dict[int, str] = field(default_factory=dict)
|
||||
|
||||
# [Index: Metric name] pairs
|
||||
metric_list: Dict[str, str] = field(default_factory=dict)
|
||||
metric_list: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
# [Metric name: Counters] pairs
|
||||
metric_counters: Dict[str, list] = field(default_factory=dict)
|
||||
metric_counters: dict[str, list] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Workload:
|
||||
sys_info: pd.DataFrame = None
|
||||
raw_pmc: pd.DataFrame = None
|
||||
dfs: Dict[int, pd.DataFrame] = field(default_factory=dict)
|
||||
dfs_type: Dict[int, str] = field(default_factory=dict)
|
||||
filter_kernel_ids: List[int] = field(default_factory=list)
|
||||
filter_gpu_ids: List[int] = field(default_factory=list)
|
||||
filter_dispatch_ids: List[int] = field(default_factory=list)
|
||||
filter_nodes: List[str] = field(default_factory=list)
|
||||
avail_ips: List[int] = field(default_factory=list)
|
||||
sys_info: pd.DataFrame = field(default_factory=pd.DataFrame)
|
||||
raw_pmc: pd.DataFrame = field(default_factory=pd.DataFrame)
|
||||
dfs: dict[int, pd.DataFrame] = field(default_factory=dict)
|
||||
dfs_type: dict[int, str] = field(default_factory=dict)
|
||||
filter_kernel_ids: list[int] = field(default_factory=list)
|
||||
filter_gpu_ids: list[int] = field(default_factory=list)
|
||||
filter_dispatch_ids: list[int] = field(default_factory=list)
|
||||
filter_nodes: list[str] = field(default_factory=list)
|
||||
avail_ips: list[int] = field(default_factory=list)
|
||||
roofline_peaks: pd.DataFrame = field(default_factory=pd.DataFrame)
|
||||
roofline_metrics: dict[int, dict[str, Any]] = field(default_factory=dict)
|
||||
path: str = field(default_factory=str)
|
||||
filter_top_n: str = field(default_factory=str)
|
||||
|
||||
|
||||
# Metrics will be calculated ONLY when the header(key) is in below list
|
||||
supported_field = [
|
||||
SUPPORTED_FIELD = [
|
||||
"Value",
|
||||
"Minimum",
|
||||
"Maximum",
|
||||
@@ -121,4 +125,4 @@ supported_field = [
|
||||
]
|
||||
|
||||
# The prefix of raw pmc_perf.csv
|
||||
pmc_perf_file_prefix = "pmc_perf"
|
||||
PMC_PERF_FILE_PREFIX = "pmc_perf"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,41 +23,46 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TextIO
|
||||
|
||||
import pandas as pd
|
||||
from tabulate import tabulate
|
||||
|
||||
import config
|
||||
from utils import mem_chart, parser
|
||||
from utils import mem_chart, parser, schema
|
||||
from utils.kernel_name_shortener import kernel_name_shortener
|
||||
from utils.logger import console_error, console_log, console_warning
|
||||
from utils.utils import convert_metric_id_to_panel_info, get_uuid
|
||||
|
||||
|
||||
def string_multiple_lines(source, width, max_rows):
|
||||
def string_multiple_lines(source: str, width: int, max_rows: int) -> str:
|
||||
"""
|
||||
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
|
||||
lines: list[str] = []
|
||||
for i in range(0, len(source), width):
|
||||
if len(lines) >= max_rows:
|
||||
break
|
||||
lines.append(source[i : i + width])
|
||||
|
||||
if len(lines) == max_rows and len(source) > max_rows * width:
|
||||
lines[-1] = lines[-1][:-3] + "..."
|
||||
|
||||
if idx < len(source):
|
||||
last = lines[-1]
|
||||
lines[-1] = last[0:-3] + "..."
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def get_table_string(df, transpose=False, decimal=2):
|
||||
def get_table_string(
|
||||
df: pd.DataFrame, transpose: bool = False, decimal: int = 2
|
||||
) -> str:
|
||||
"""
|
||||
Convert DataFrame to a formatted table string, wrapping specified columns.
|
||||
"""
|
||||
df_to_show = df.transpose() if transpose else df
|
||||
|
||||
wrap_columns = ["Description"]
|
||||
wrap_width = 40
|
||||
for col in wrap_columns:
|
||||
@@ -67,42 +72,40 @@ def get_table_string(df, transpose=False, decimal=2):
|
||||
.astype(str)
|
||||
.apply(lambda x: textwrap.fill(x, width=wrap_width))
|
||||
)
|
||||
df_with_index = df_to_show.reset_index()
|
||||
return tabulate(
|
||||
df_to_show,
|
||||
headers="keys",
|
||||
df_with_index.values,
|
||||
headers=list(df_with_index.columns),
|
||||
tablefmt="fancy_grid",
|
||||
floatfmt="." + str(decimal) + "f",
|
||||
floatfmt=f".{decimal}f",
|
||||
)
|
||||
|
||||
|
||||
def convert_time_columns(df, time_unit):
|
||||
def convert_time_columns(df: pd.DataFrame, time_unit: str) -> pd.DataFrame:
|
||||
"""
|
||||
Convert time column values based on the specified time unit.
|
||||
Uses the Unit column to identify which columns contain time data.
|
||||
"""
|
||||
|
||||
if time_unit not in config.TIME_UNITS or "Unit" not in df.columns:
|
||||
return df
|
||||
|
||||
# Avoid modifying the original
|
||||
df_copy = df.copy()
|
||||
|
||||
time_rows = df_copy["Unit"].str.lower().str.contains("ns", na=False)
|
||||
|
||||
time_value_columns = ["Avg", "Min", "Max"]
|
||||
|
||||
for col in time_value_columns:
|
||||
if col in df_copy.columns:
|
||||
mask = time_rows
|
||||
if mask.any():
|
||||
try:
|
||||
numeric_values = pd.to_numeric(
|
||||
df_copy.loc[mask, col], errors="coerce"
|
||||
)
|
||||
df_copy.loc[mask, col] = (
|
||||
numeric_values / config.TIME_UNITS[time_unit]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if col in df_copy.columns and time_rows.any():
|
||||
try:
|
||||
numeric_values = pd.to_numeric(
|
||||
df_copy.loc[time_rows, col], errors="coerce"
|
||||
)
|
||||
df_copy.loc[time_rows, col] = (
|
||||
numeric_values / config.TIME_UNITS[time_unit]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Update the Unit column
|
||||
if time_rows.any():
|
||||
@@ -111,31 +114,341 @@ def convert_time_columns(df, time_unit):
|
||||
return df_copy
|
||||
|
||||
|
||||
def has_time_data(df):
|
||||
def has_time_data(df: pd.DataFrame) -> bool:
|
||||
"""
|
||||
Check if the dataframe contains time data by looking at the Unit column.
|
||||
"""
|
||||
|
||||
if "Unit" not in df.columns:
|
||||
return False
|
||||
# NOTE: "ns" / "NS" / "nS" / "Ns" are reserved for Nanosec time unit
|
||||
return df["Unit"].str.lower().str.contains("ns", na=False).any()
|
||||
return bool(df["Unit"].str.lower().str.contains("ns", na=False).any())
|
||||
|
||||
|
||||
def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
|
||||
def is_roofline_shown(
|
||||
args: argparse.Namespace,
|
||||
runs: dict[str, Any],
|
||||
output: Optional[TextIO],
|
||||
panel: dict[str, Any],
|
||||
roof_plot: Optional[str],
|
||||
hidden_cols: list[str],
|
||||
) -> bool:
|
||||
has_roofline_style = any(
|
||||
data_source.get(table_type, {}).get("cli_style") == "Roofline"
|
||||
for data_source in panel["data source"]
|
||||
for table_type in data_source
|
||||
)
|
||||
|
||||
if not has_roofline_style or (
|
||||
args.filter_metrics and "4" not in args.filter_metrics
|
||||
):
|
||||
return False
|
||||
|
||||
print(f"\n{'=' * 80}", file=output)
|
||||
print("4. Roofline", file=output)
|
||||
print("=" * 80, file=output)
|
||||
|
||||
# Display roofline metrics for each run
|
||||
for run_path, workload in runs.items():
|
||||
if hasattr(workload, "roofline_metrics") and workload.roofline_metrics:
|
||||
print(
|
||||
"\n(4.1) Per-Kernel Roofline Metrics and (4.2) AI Plot Points",
|
||||
file=output,
|
||||
)
|
||||
print("-" * 80, file=output)
|
||||
|
||||
kernel_top_df = workload.dfs.get(1, pd.DataFrame())
|
||||
if not kernel_top_df.empty:
|
||||
kernel_name_shortener(kernel_top_df, args.kernel_verbose)
|
||||
|
||||
# Display roofline metrics
|
||||
for kernel_id, metrics in workload.roofline_metrics.items():
|
||||
if not kernel_top_df.empty and kernel_id in kernel_top_df.index:
|
||||
kernel_name = kernel_top_df.loc[kernel_id, "Kernel_Name"]
|
||||
kernel_pct = (
|
||||
kernel_top_df.loc[kernel_id, "Pct"]
|
||||
if "Pct" in kernel_top_df.columns
|
||||
else 0
|
||||
)
|
||||
else:
|
||||
kernel_name = metrics.get("name", f"Kernel {kernel_id}")
|
||||
kernel_pct = 0
|
||||
|
||||
display_name = (
|
||||
kernel_name[:80] + "..." if len(kernel_name) > 80 else kernel_name
|
||||
)
|
||||
print(
|
||||
f"\nKernel {kernel_id}: {display_name} ({kernel_pct:.1f}%)",
|
||||
file=output,
|
||||
)
|
||||
|
||||
base_indent = " "
|
||||
table_indent_prefix = f"{base_indent}| "
|
||||
print(f"{base_indent}|", file=output)
|
||||
|
||||
tables = {
|
||||
401: (
|
||||
"4.1 Roofline Rate Metrics:",
|
||||
metrics.get("ai_table", pd.DataFrame()),
|
||||
),
|
||||
402: (
|
||||
"4.2 Roofline AI Plot Points:",
|
||||
metrics.get("calc_table", pd.DataFrame()),
|
||||
),
|
||||
}
|
||||
|
||||
for table_id, (table_name, df) in tables.items():
|
||||
if df.empty:
|
||||
continue
|
||||
|
||||
print(f"{base_indent}├─ {table_name}", file=output)
|
||||
|
||||
# Remove hidden columns
|
||||
display_df = df.copy()
|
||||
for col in hidden_cols:
|
||||
if col in display_df.columns:
|
||||
display_df = display_df.drop(columns=[col])
|
||||
|
||||
table_string = get_table_string(
|
||||
display_df, transpose=False, decimal=args.decimal
|
||||
)
|
||||
indented_table = textwrap.indent(table_string, table_indent_prefix)
|
||||
print(indented_table, file=output)
|
||||
|
||||
else:
|
||||
print("\nNo per-kernel metrics available", file=output)
|
||||
|
||||
# Show the roofline plot
|
||||
if roof_plot:
|
||||
show_roof_plot(roof_plot)
|
||||
return True
|
||||
|
||||
|
||||
def process_table_data(
|
||||
args: argparse.Namespace,
|
||||
runs: dict[str, Any],
|
||||
table_config: dict[str, Any],
|
||||
table_type: str,
|
||||
comparable_columns: list[str],
|
||||
hidden_cols: list[str],
|
||||
) -> pd.DataFrame:
|
||||
# take the 1st run as baseline
|
||||
base_run, base_data = next(iter(runs.items()))
|
||||
base_df = base_data.dfs[table_config["id"]]
|
||||
|
||||
if args.time_unit and has_time_data(base_df):
|
||||
base_df = convert_time_columns(base_df, args.time_unit)
|
||||
|
||||
result_df = pd.DataFrame(index=base_df.index)
|
||||
|
||||
for header in base_df.columns:
|
||||
# Skip filtered columns
|
||||
if (
|
||||
table_type != "raw_csv_table"
|
||||
and args.cols
|
||||
and base_df.columns.get_loc(header) not in args.cols
|
||||
):
|
||||
continue
|
||||
|
||||
if header in hidden_cols:
|
||||
continue
|
||||
|
||||
if header not in comparable_columns:
|
||||
# Process columns that are not comparable across runs.
|
||||
if (
|
||||
table_type == "raw_csv_table"
|
||||
and table_config["source"]
|
||||
in ["pmc_kernel_top.csv", "pmc_dispatch_info.csv"]
|
||||
and header == "Kernel_Name"
|
||||
):
|
||||
# NB: the width of kernel name might depend
|
||||
# on the header of the table.
|
||||
width = 40 if table_config["source"] == "pmc_kernel_top.csv" else 80
|
||||
max_rows = 3 if table_config["source"] == "pmc_kernel_top.csv" else 4
|
||||
|
||||
adjusted_names = base_df["Kernel_Name"].apply(
|
||||
lambda x: string_multiple_lines(x, width, max_rows)
|
||||
)
|
||||
result_df = pd.concat([result_df, adjusted_names], axis=1)
|
||||
|
||||
elif table_type == "raw_csv_table" and header == "Info":
|
||||
for run_data in runs.values():
|
||||
cur_df = run_data.dfs[table_config["id"]]
|
||||
result_df = pd.concat([result_df, cur_df[header]], axis=1)
|
||||
else:
|
||||
result_df = pd.concat([result_df, base_df[header]], axis=1)
|
||||
else:
|
||||
# Process columns that can be compared across runs.
|
||||
for run_name, run_data in runs.items():
|
||||
cur_df = run_data.dfs[table_config["id"]]
|
||||
|
||||
if args.time_unit and has_time_data(base_df):
|
||||
cur_df = convert_time_columns(cur_df, args.time_unit)
|
||||
|
||||
if (table_type == "raw_csv_table") or (
|
||||
table_type == "metric_table" and header not in hidden_cols
|
||||
):
|
||||
if run_name != base_run:
|
||||
# Calculate percentage difference between current and
|
||||
# base dataframe.
|
||||
base_series = pd.to_numeric(
|
||||
base_df[header], errors="coerce"
|
||||
).fillna(0.0)
|
||||
cur_series = pd.to_numeric(
|
||||
cur_df[header], errors="coerce"
|
||||
).fillna(0.0)
|
||||
|
||||
# Calculate absolute and percentage differences
|
||||
absolute_diff = (cur_series - base_series).round(args.decimal)
|
||||
percentage_diff = (
|
||||
absolute_diff / base_series.replace(0, 1) * 100
|
||||
).round(args.decimal)
|
||||
|
||||
if args.verbose >= 2:
|
||||
console_log("---------", header, percentage_diff)
|
||||
|
||||
# Format as "value (percentage%)"
|
||||
formatted_diff = (
|
||||
cur_series.round(args.decimal).astype(str)
|
||||
+ " ("
|
||||
+ percentage_diff.astype(str)
|
||||
+ "%)"
|
||||
)
|
||||
|
||||
result_df = pd.concat([result_df, formatted_diff], axis=1)
|
||||
|
||||
# DEBUG: When in a CI setting and flag is set,
|
||||
# then verify metrics meet threshold
|
||||
# requirement
|
||||
if (
|
||||
header in ["Value", "Count", "Avg"]
|
||||
and percentage_diff.abs().gt(args.report_diff).any()
|
||||
):
|
||||
result_df["Abs Diff"] = absolute_diff
|
||||
|
||||
if args.report_diff:
|
||||
violation_idx = percentage_diff.index[
|
||||
percentage_diff.abs() > args.report_diff
|
||||
]
|
||||
console_warning(
|
||||
f"Dataframe diff exceeds {args.report_diff}% "
|
||||
"threshold requirement\n"
|
||||
f"See metric {violation_idx.to_numpy()}"
|
||||
)
|
||||
console_warning(result_df)
|
||||
else:
|
||||
# Base run - just add the rounded values
|
||||
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]
|
||||
]
|
||||
result_df = pd.concat([result_df, cur_df_copy[header]], axis=1)
|
||||
|
||||
return result_df
|
||||
|
||||
|
||||
def format_table_output(
|
||||
args: argparse.Namespace,
|
||||
table_config: dict[str, Any],
|
||||
df: pd.DataFrame,
|
||||
table_type: str,
|
||||
runs: dict[str, Any],
|
||||
csv_dir: Optional[Path] = None,
|
||||
) -> str:
|
||||
"""Format table for output, handling special cases and saving to files if needed."""
|
||||
|
||||
table_id_str = f"{table_config['id'] // 100}.{table_config['id'] % 100}"
|
||||
content = ""
|
||||
|
||||
# Check if any column in df is empty
|
||||
is_empty_columns_exist = any(
|
||||
df.replace("", None).iloc[:, col_idx].isnull().all()
|
||||
for col_idx in range(len(df.columns))
|
||||
)
|
||||
|
||||
# Do not print the table if any column is empty
|
||||
if is_empty_columns_exist:
|
||||
title = table_config.get("title", "")
|
||||
console_log(f"Not showing table with empty column(s): {table_id_str} {title}")
|
||||
return content
|
||||
|
||||
if "title" in table_config and table_config["title"]:
|
||||
content += f"{table_id_str} {table_config['title']}\n"
|
||||
|
||||
if args.output_format == "csv" and csv_dir and csv_dir.is_dir():
|
||||
if "title" in table_config and table_config["title"]:
|
||||
table_id_str += f"_{table_config['title']}"
|
||||
|
||||
csv_filename = csv_dir / f"{table_id_str.replace(' ', '_')}.csv"
|
||||
df.to_csv(csv_filename, index=False)
|
||||
console_warning(f"Created file: {csv_filename}")
|
||||
|
||||
# Only show top N kernels (as specified in --max-kernel-num)
|
||||
# in "Top Stats" section
|
||||
if table_type == "raw_csv_table" and table_config["source"] in [
|
||||
"pmc_kernel_top.csv",
|
||||
"pmc_dispatch_info.csv",
|
||||
]:
|
||||
df = df.head(args.max_stat_num)
|
||||
# NB:
|
||||
# "columnwise: True" is a special attr of a table/df
|
||||
# For raw_csv_table, such as system_info, we transpose the
|
||||
# df when load it, because we need those items in column.
|
||||
# For metric_table, we only need to show the data in column
|
||||
# fash for now.
|
||||
transpose = table_type != "raw_csv_table" and table_config.get("columnwise", False)
|
||||
|
||||
# enable mem_chart only with single run
|
||||
if (
|
||||
table_config.get("cli_style") == "mem_chart"
|
||||
and len(runs) == 1
|
||||
and "Metric" in df.columns
|
||||
and "Value" in df.columns
|
||||
):
|
||||
mem_data = (
|
||||
pd.DataFrame([df["Metric"], df["Value"]])
|
||||
.transpose()
|
||||
.set_index("Metric")
|
||||
.to_dict()["Value"]
|
||||
)
|
||||
content += mem_chart.plot_mem_chart("", args.normal_unit, mem_data) + "\n"
|
||||
else:
|
||||
content += (
|
||||
get_table_string(df, transpose=transpose, decimal=args.decimal) + "\n"
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def show_all(
|
||||
args: argparse.Namespace,
|
||||
runs: dict[str, Any],
|
||||
arch_configs: schema.ArchConfig,
|
||||
output: Optional[TextIO],
|
||||
profiling_config: dict[str, Any],
|
||||
roof_plot: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Show all panels with their data in plain text mode.
|
||||
"""
|
||||
comparable_columns = parser.build_comparable_columns(args.time_unit)
|
||||
filter_panel_ids = profiling_config.get("filter_blocks", [])
|
||||
csv_dir = None
|
||||
|
||||
if isinstance(filter_panel_ids, dict):
|
||||
# For backward compatibility
|
||||
filter_panel_ids = [
|
||||
name for name, type in filter_panel_ids.items() if type == "metric_id"
|
||||
name
|
||||
for name, table_type in filter_panel_ids.items()
|
||||
if table_type == "metric_id"
|
||||
]
|
||||
filter_panel_ids = [
|
||||
int(convert_metric_id_to_panel_info(metric_id)[0])
|
||||
int(result[0])
|
||||
for metric_id in filter_panel_ids
|
||||
if (result := convert_metric_id_to_panel_info(metric_id)) is not None
|
||||
]
|
||||
|
||||
if args.include_cols:
|
||||
hidden_cols = list(set(config.HIDDEN_COLUMNS_CLI) - set(args.include_cols))
|
||||
else:
|
||||
@@ -149,119 +462,19 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
|
||||
if not csv_dir.exists():
|
||||
csv_dir.mkdir()
|
||||
|
||||
for panel_id, panel in archConfigs.panel_configs.items():
|
||||
for panel_id, panel in arch_configs.panel_configs.items():
|
||||
# Skip panels that don't support baseline comparison
|
||||
if len(args.path) > 1 and panel_id in config.HIDDEN_SECTIONS:
|
||||
continue
|
||||
ss = "" # store content of all data_source from one panel
|
||||
|
||||
panel_content = "" # store content of all data_source from one panel
|
||||
|
||||
if panel_id == 400:
|
||||
has_roofline_style = any(
|
||||
data_source.get(type, {}).get("cli_style") == "Roofline"
|
||||
for data_source in panel["data source"]
|
||||
for type in data_source
|
||||
)
|
||||
|
||||
if has_roofline_style and (
|
||||
not args.filter_metrics or "4" in args.filter_metrics
|
||||
):
|
||||
print("\n" + "=" * 80, file=output)
|
||||
print("4. Roofline", file=output)
|
||||
print("=" * 80, file=output)
|
||||
|
||||
for run_path, workload in runs.items():
|
||||
if (
|
||||
hasattr(workload, "roofline_metrics")
|
||||
and workload.roofline_metrics
|
||||
):
|
||||
print(
|
||||
"\n(4.1) Per-Kernel Roofline Metrics and "
|
||||
"(4.2) AI Plot Points",
|
||||
file=output,
|
||||
)
|
||||
print("-" * 80, file=output)
|
||||
|
||||
kernel_top_df = workload.dfs.get(1, pd.DataFrame())
|
||||
if not kernel_top_df.empty:
|
||||
kernel_name_shortener(kernel_top_df, args.kernel_verbose)
|
||||
|
||||
for i, (kernel_id, metrics) in enumerate(
|
||||
workload.roofline_metrics.items()
|
||||
):
|
||||
if (
|
||||
not kernel_top_df.empty
|
||||
and kernel_id in kernel_top_df.index
|
||||
):
|
||||
kernel_name = kernel_top_df.loc[
|
||||
kernel_id, "Kernel_Name"
|
||||
]
|
||||
kernel_pct = (
|
||||
kernel_top_df.loc[kernel_id, "Pct"]
|
||||
if "Pct" in kernel_top_df.columns
|
||||
else 0
|
||||
)
|
||||
else:
|
||||
kernel_name = metrics.get("name", f"Kernel {kernel_id}")
|
||||
kernel_pct = 0
|
||||
|
||||
display_name = (
|
||||
kernel_name[:80] + "..."
|
||||
if len(kernel_name) > 80
|
||||
else kernel_name
|
||||
)
|
||||
print(
|
||||
f"\nKernel {kernel_id}: "
|
||||
f"{display_name} "
|
||||
f"({kernel_pct:.1f}%)",
|
||||
file=output,
|
||||
)
|
||||
|
||||
base_indent = " "
|
||||
table_indent_prefix = f"{base_indent}| "
|
||||
|
||||
tables = {
|
||||
401: (
|
||||
"4.1 Roofline Rate Metrics:",
|
||||
metrics.get("ai_table", pd.DataFrame()),
|
||||
),
|
||||
402: (
|
||||
"4.2 Roofline AI Plot Points:",
|
||||
metrics.get("calc_table", pd.DataFrame()),
|
||||
),
|
||||
}
|
||||
|
||||
print(f"{base_indent}|")
|
||||
|
||||
for table_id, (table_name, df) in tables.items():
|
||||
if df.empty:
|
||||
continue
|
||||
|
||||
print(f"{base_indent}├─ {table_name}", file=output)
|
||||
|
||||
display_df = df.copy()
|
||||
|
||||
for col in hidden_cols:
|
||||
if col in display_df.columns:
|
||||
display_df = display_df.drop(columns=[col])
|
||||
|
||||
table_string = get_table_string(
|
||||
display_df, transpose=False, decimal=args.decimal
|
||||
)
|
||||
indented_table_string = textwrap.indent(
|
||||
table_string, table_indent_prefix
|
||||
)
|
||||
print(indented_table_string, file=output)
|
||||
|
||||
else:
|
||||
print("\nNo per-kernel metrics available", file=output)
|
||||
|
||||
# Show the roofline plot
|
||||
if roof_plot:
|
||||
show_roof_plot(roof_plot)
|
||||
if is_roofline_shown(args, runs, output, panel, roof_plot, hidden_cols):
|
||||
continue
|
||||
|
||||
for data_source in panel["data source"]:
|
||||
for type, table_config in data_source.items():
|
||||
for table_type, table_config in data_source.items():
|
||||
# If block filtering was used during analysis, then don't use profiling
|
||||
# config. If block filtering was used in profiling config, only show
|
||||
# those panels. If block filtering not used in profiling config, show
|
||||
@@ -275,14 +488,12 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
|
||||
and panel_id > 100
|
||||
):
|
||||
table_id_str = (
|
||||
str(table_config["id"] // 100)
|
||||
+ "."
|
||||
+ str(table_config["id"] % 100)
|
||||
f"{table_config['id'] // 100}.{table_config['id'] % 100}"
|
||||
)
|
||||
|
||||
console_log(
|
||||
f"Not showing table not selected during profiling: "
|
||||
f"{table_id_str} "
|
||||
f"{table_config['title']}"
|
||||
f"{table_id_str} {table_config['title']}"
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -290,273 +501,58 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
|
||||
# We cannot guarantee that all runs have the same metrics.
|
||||
# Only show common metrics.
|
||||
if (
|
||||
type == "metric_table"
|
||||
table_type == "metric_table"
|
||||
and "Metric" in table_config["header"].values()
|
||||
and len(runs) > 1
|
||||
):
|
||||
# Common metrics across all runs
|
||||
common_metrics = set()
|
||||
for _, data in runs.items():
|
||||
if not common_metrics:
|
||||
common_metrics = set(data.dfs[table_config["id"]]["Metric"])
|
||||
else:
|
||||
common_metrics &= set(
|
||||
data.dfs[table_config["id"]]["Metric"]
|
||||
)
|
||||
# Find common metrics across all runs
|
||||
common_metrics: set[str] = set()
|
||||
for run_data in runs.values():
|
||||
run_metrics = set(run_data.dfs[table_config["id"]]["Metric"])
|
||||
common_metrics = (
|
||||
run_metrics
|
||||
if not common_metrics
|
||||
else common_metrics & run_metrics
|
||||
)
|
||||
|
||||
# Apply common metrics across all runs
|
||||
# Reindex all runs based on first run
|
||||
initial_index = None
|
||||
for key in runs.keys():
|
||||
runs[key].dfs[table_config["id"]] = (
|
||||
runs[key]
|
||||
.dfs[table_config["id"]]
|
||||
.loc[lambda d: d["Metric"].isin(common_metrics)]
|
||||
)
|
||||
for run_data in runs.values():
|
||||
run_data.dfs[table_config["id"]] = run_data.dfs[
|
||||
table_config["id"]
|
||||
].loc[lambda df: df["Metric"].isin(common_metrics)]
|
||||
if initial_index is None:
|
||||
initial_index = runs[key].dfs[table_config["id"]].index
|
||||
initial_index = run_data.dfs[table_config["id"]].index
|
||||
else:
|
||||
runs[key].dfs[table_config["id"]].index = initial_index
|
||||
run_data.dfs[table_config["id"]].index = initial_index
|
||||
|
||||
# take the 1st run as baseline
|
||||
base_run, base_data = next(iter(runs.items()))
|
||||
base_df = base_data.dfs[table_config["id"]]
|
||||
processed_df = process_table_data(
|
||||
args,
|
||||
runs,
|
||||
table_config,
|
||||
table_type,
|
||||
comparable_columns,
|
||||
hidden_cols,
|
||||
)
|
||||
|
||||
if args.time_unit and has_time_data(base_df):
|
||||
base_df = convert_time_columns(base_df, args.time_unit)
|
||||
|
||||
df = pd.DataFrame(index=base_df.index)
|
||||
|
||||
for header in list(base_df.keys()):
|
||||
# For raw csv table, columns cannot be filtered
|
||||
# If columns are filtered, then skip the headers not in
|
||||
# filtered columns
|
||||
if (
|
||||
type == "raw_csv_table"
|
||||
or not args.cols
|
||||
or base_df.columns.get_loc(header) in args.cols
|
||||
):
|
||||
if header in hidden_cols:
|
||||
pass
|
||||
elif header not in comparable_columns:
|
||||
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"
|
||||
):
|
||||
# NB: the width of kernel name might depend
|
||||
# on the header of the table.
|
||||
if table_config["source"] == "pmc_kernel_top.csv":
|
||||
adjusted_name = base_df["Kernel_Name"].apply(
|
||||
lambda x: string_multiple_lines(x, 40, 3)
|
||||
)
|
||||
else:
|
||||
adjusted_name = base_df["Kernel_Name"].apply(
|
||||
lambda x: string_multiple_lines(x, 80, 4)
|
||||
)
|
||||
df = pd.concat([df, adjusted_name], axis=1)
|
||||
elif type == "raw_csv_table" and header == "Info":
|
||||
for run, data in runs.items():
|
||||
cur_df = data.dfs[table_config["id"]]
|
||||
df = pd.concat([df, cur_df[header]], axis=1)
|
||||
else:
|
||||
df = pd.concat([df, base_df[header]], axis=1)
|
||||
else:
|
||||
for run, data in runs.items():
|
||||
cur_df = data.dfs[table_config["id"]]
|
||||
|
||||
if args.time_unit and has_time_data(base_df):
|
||||
cur_df = convert_time_columns(
|
||||
cur_df, args.time_unit
|
||||
)
|
||||
|
||||
if (type == "raw_csv_table") or (
|
||||
type == "metric_table"
|
||||
and (not header in hidden_cols)
|
||||
):
|
||||
if run != base_run:
|
||||
# calc percentage over the baseline
|
||||
base_df[header] = [
|
||||
float(x) if x != "" else float(0)
|
||||
for x in base_df[header]
|
||||
]
|
||||
cur_df[header] = [
|
||||
float(x) if x != "" else float(0)
|
||||
for x in cur_df[header]
|
||||
]
|
||||
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
|
||||
)
|
||||
if args.verbose >= 2:
|
||||
console_log("---------", header, t_df)
|
||||
|
||||
t_df_pretty = (
|
||||
t_df.astype(float)
|
||||
.mul(100)
|
||||
.round(args.decimal)
|
||||
)
|
||||
# show value + percentage
|
||||
# TODO: better alignment
|
||||
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)
|
||||
# DEBUG: When in a CI setting and flag is set,
|
||||
# then verify metrics meet threshold
|
||||
# requirement
|
||||
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
|
||||
]
|
||||
console_warning(
|
||||
"Dataframe diff exceeds %s "
|
||||
"threshold requirement\n"
|
||||
"See metric %s"
|
||||
% (
|
||||
str(args.report_diff) + "%",
|
||||
violation_idx.to_numpy(),
|
||||
)
|
||||
)
|
||||
console_warning(df)
|
||||
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
|
||||
)
|
||||
|
||||
if not df.empty:
|
||||
# subtitle for each table in a panel if existing
|
||||
table_id_str = (
|
||||
str(table_config["id"] // 100)
|
||||
+ "."
|
||||
+ str(table_config["id"] % 100)
|
||||
if not processed_df.empty:
|
||||
panel_content += format_table_output(
|
||||
args, table_config, processed_df, table_type, runs, csv_dir
|
||||
)
|
||||
|
||||
# Check if any column in df is empty
|
||||
is_empty_columns_exist = any([
|
||||
df.columns[col_idx]
|
||||
for col_idx in range(len(df.columns))
|
||||
if df.replace("", None).iloc[:, col_idx].isnull().all()
|
||||
])
|
||||
# Do not print the table if any column is empty
|
||||
if is_empty_columns_exist:
|
||||
if "title" in table_config:
|
||||
console_log(
|
||||
f"Not showing table with empty column(s): "
|
||||
f"{table_id_str} "
|
||||
f"{table_config['title']}"
|
||||
)
|
||||
else:
|
||||
console_log(
|
||||
f"Not showing table with empty column(s): "
|
||||
f"{table_id_str}"
|
||||
)
|
||||
if (
|
||||
"title" in table_config
|
||||
and table_config["title"]
|
||||
and not is_empty_columns_exist
|
||||
):
|
||||
ss += table_id_str + " " + table_config["title"] + "\n"
|
||||
|
||||
if args.output_format == "csv" and csv_dir.is_dir():
|
||||
if "title" in table_config and table_config["title"]:
|
||||
table_id_str += "_" + table_config["title"]
|
||||
csv_filename = str(
|
||||
csv_dir.joinpath(table_id_str.replace(" ", "_") + ".csv"),
|
||||
)
|
||||
df.to_csv(csv_filename, index=False)
|
||||
console_warning(f"Created file: {csv_filename}")
|
||||
|
||||
# Only show top N kernels (as specified in --max-kernel-num)
|
||||
# in "Top Stats" section
|
||||
if type == "raw_csv_table" and (
|
||||
table_config["source"] == "pmc_kernel_top.csv"
|
||||
or table_config["source"] == "pmc_dispatch_info.csv"
|
||||
):
|
||||
df = df.head(args.max_stat_num)
|
||||
# NB:
|
||||
# "columnwise: True" is a special attr of a table/df
|
||||
# For raw_csv_table, such as system_info, we transpose the
|
||||
# df when load it, because we need those items in column.
|
||||
# For metric_table, we only need to show the data in column
|
||||
# fash for now.
|
||||
transpose = (
|
||||
type != "raw_csv_table"
|
||||
and "columnwise" in table_config
|
||||
and table_config["columnwise"]
|
||||
)
|
||||
if not is_empty_columns_exist:
|
||||
# enable mem_chart only with single run
|
||||
if (
|
||||
"cli_style" in table_config
|
||||
and table_config["cli_style"] == "mem_chart"
|
||||
and len(runs) == 1
|
||||
):
|
||||
# NB: to avoid broken test with
|
||||
# arbitrary number with "--cols" option
|
||||
if "Metric" in df.columns and "Value" in df.columns:
|
||||
ss += mem_chart.plot_mem_chart(
|
||||
"",
|
||||
args.normal_unit,
|
||||
pd.DataFrame([df["Metric"], df["Value"]])
|
||||
.transpose()
|
||||
.set_index("Metric")
|
||||
.to_dict()["Value"],
|
||||
)
|
||||
ss += "\n"
|
||||
else:
|
||||
ss += (
|
||||
get_table_string(
|
||||
df, transpose=transpose, decimal=args.decimal
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
if ss:
|
||||
print("\n" + "-" * 80, file=output)
|
||||
print(str(panel_id // 100) + ". " + panel["title"], file=output)
|
||||
print(ss, file=output)
|
||||
if panel_content:
|
||||
print(f"\n{'-' * 80}", file=output)
|
||||
print(f"{panel_id // 100}. {panel['title']}", file=output)
|
||||
print(panel_content, file=output)
|
||||
|
||||
|
||||
def show_roof_plot(roof_plot):
|
||||
def show_roof_plot(roof_plot: str) -> None:
|
||||
# TODO: short term solution to display roofline plot
|
||||
print("\n" + "-" * 80)
|
||||
print(f"\n{'-' * 80}")
|
||||
print("4. Roofline")
|
||||
print("4.3 Roofline Plot")
|
||||
|
||||
if roof_plot:
|
||||
print(roof_plot)
|
||||
else:
|
||||
@@ -567,35 +563,47 @@ def show_roof_plot(roof_plot):
|
||||
)
|
||||
|
||||
|
||||
def show_kernel_stats(args, runs, archConfigs, output):
|
||||
def show_kernel_stats(
|
||||
args: argparse.Namespace,
|
||||
runs: dict[str, Any],
|
||||
arch_configs: schema.ArchConfig,
|
||||
output: Optional[TextIO],
|
||||
) -> None:
|
||||
"""
|
||||
Show the kernels and dispatches from "Top Stats" section.
|
||||
"""
|
||||
|
||||
df = pd.DataFrame()
|
||||
for panel_id, panel in archConfigs.panel_configs.items():
|
||||
for panel_id, panel in arch_configs.panel_configs.items():
|
||||
for data_source in panel["data source"]:
|
||||
for type, table_config in data_source.items():
|
||||
for table_type, table_config in data_source.items():
|
||||
for run, data in runs.items():
|
||||
df = pd.DataFrame()
|
||||
single_df = data.dfs[table_config["id"]]
|
||||
# NB:
|
||||
# For pmc_kernel_top.csv, have to sort here if not
|
||||
# sorted when load_table_data.
|
||||
if table_config["id"] == 1:
|
||||
print("\n" + "-" * 80, file=output)
|
||||
print(f"\n{'-' * 80}", file=output)
|
||||
print(
|
||||
"Detected Kernels (sorted descending by duration)",
|
||||
file=output,
|
||||
)
|
||||
df = pd.concat([df, single_df["Kernel_Name"]], axis=1)
|
||||
display_df = pd.DataFrame()
|
||||
display_df = pd.concat(
|
||||
[display_df, single_df["Kernel_Name"]], axis=1
|
||||
)
|
||||
print(
|
||||
get_table_string(
|
||||
display_df, transpose=False, decimal=args.decimal
|
||||
),
|
||||
file=output,
|
||||
)
|
||||
|
||||
if table_config["id"] == 2:
|
||||
print("\n" + "-" * 80, file=output)
|
||||
print(f"\n{'-' * 80}", file=output)
|
||||
print("Dispatch list", file=output)
|
||||
df = single_df
|
||||
|
||||
print(
|
||||
get_table_string(df, transpose=False, decimal=args.decimal),
|
||||
file=output,
|
||||
)
|
||||
print(
|
||||
get_table_string(
|
||||
single_df, transpose=False, decimal=args.decimal
|
||||
),
|
||||
file=output,
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@ def test_valid_path(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -71,7 +71,7 @@ def test_list_kernels(binary_handler_analyze_rocprof_compute):
|
||||
"--list-stats",
|
||||
])
|
||||
assert code == 0
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.list_metrics
|
||||
@@ -94,7 +94,7 @@ def test_list_metrics_gfx90a(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.list_metrics
|
||||
@@ -117,7 +117,7 @@ def test_list_metrics_gfx908(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.list_metrics
|
||||
@@ -213,7 +213,7 @@ def test_filter_block_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.filter_block
|
||||
@@ -229,7 +229,7 @@ def test_filter_block_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.filter_block
|
||||
@@ -245,7 +245,7 @@ def test_filter_block_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.filter_block
|
||||
@@ -261,7 +261,7 @@ def test_filter_block_4(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.filter_block
|
||||
@@ -277,7 +277,7 @@ def test_filter_block_5(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.filter_block
|
||||
@@ -293,7 +293,7 @@ def test_filter_block_6(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -309,7 +309,7 @@ def test_filter_kernel_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -325,7 +325,7 @@ def test_filter_kernel_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 1
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -342,7 +342,7 @@ def test_filter_kernel_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 1
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -358,7 +358,7 @@ def test_dispatch_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -374,7 +374,7 @@ def test_dispatch_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -390,7 +390,7 @@ def test_dispatch_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -407,7 +407,7 @@ def test_dispatch_4(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 1
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
@@ -424,7 +424,7 @@ def test_dispatch_5(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 1
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -444,7 +444,7 @@ def test_gpu_ids(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.normal_unit
|
||||
@@ -460,7 +460,7 @@ def test_normal_unit_per_wave(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.normal_unit
|
||||
@@ -476,7 +476,7 @@ def test_normal_unit_per_cycle(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.normal_unit
|
||||
@@ -492,7 +492,7 @@ def test_normal_unit_per_second(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.normal_unit
|
||||
@@ -508,7 +508,7 @@ def test_normal_unit_per_kernel(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.max_stat
|
||||
@@ -524,7 +524,7 @@ def test_max_stat_num_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.max_stat
|
||||
@@ -540,7 +540,7 @@ def test_max_stat_num_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.max_stat
|
||||
@@ -556,7 +556,7 @@ def test_max_stat_num_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.max_stat
|
||||
@@ -572,7 +572,7 @@ def test_max_stat_num_4(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.time_unit
|
||||
@@ -588,7 +588,7 @@ def test_time_unit_s(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.time_unit
|
||||
@@ -604,7 +604,7 @@ def test_time_unit_ms(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.time_unit
|
||||
@@ -620,7 +620,7 @@ def test_time_unit_us(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.time_unit
|
||||
@@ -636,7 +636,7 @@ def test_time_unit_ns(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.decimal
|
||||
@@ -652,7 +652,7 @@ def test_decimal_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.decimal
|
||||
@@ -668,7 +668,7 @@ def test_decimal_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.decimal
|
||||
@@ -684,7 +684,7 @@ def test_decimal_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -709,7 +709,7 @@ def test_save_dfs(binary_handler_analyze_rocprof_compute):
|
||||
assert len(df.index) >= 1
|
||||
|
||||
shutil.rmtree(output_path)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], output_path)
|
||||
|
||||
|
||||
@@ -726,7 +726,7 @@ def test_col_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.col
|
||||
@@ -744,7 +744,7 @@ def test_col_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.col
|
||||
@@ -761,7 +761,7 @@ def test_col_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -776,7 +776,7 @@ def test_g(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -792,7 +792,7 @@ def test_kernel_verbose_0(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -808,7 +808,7 @@ def test_kernel_verbose_1(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -824,7 +824,7 @@ def test_kernel_verbose_2(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -840,7 +840,7 @@ def test_kernel_verbose_3(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -856,7 +856,7 @@ def test_kernel_verbose_4(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -872,7 +872,7 @@ def test_kernel_verbose_5(binary_handler_analyze_rocprof_compute):
|
||||
])
|
||||
assert code == 0
|
||||
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
test_utils.clean_output_dir(config["cleanup"], workload_dir)
|
||||
|
||||
|
||||
@pytest.mark.kernel_verbose
|
||||
@@ -1055,7 +1055,11 @@ def test_parser_error_handling():
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
from utils.parser import build_eval_string, calc_builtin_var, update_denom_string
|
||||
from utils.parser import (
|
||||
build_eval_string,
|
||||
calc_builtin_var,
|
||||
update_denominator_string,
|
||||
)
|
||||
|
||||
try:
|
||||
build_eval_string("AVG(SQ_WAVES)", None, config={})
|
||||
@@ -1064,7 +1068,7 @@ def test_parser_error_handling():
|
||||
assert "coll_level can not be None" in str(e)
|
||||
|
||||
assert build_eval_string("", "pmc_perf", config={}) == ""
|
||||
assert update_denom_string("", "per_wave") == ""
|
||||
assert update_denominator_string("", "per_wave") == ""
|
||||
|
||||
class MockSysInfo:
|
||||
total_l2_chan = 32
|
||||
@@ -1115,14 +1119,14 @@ def test_ast_transformer_edge_cases():
|
||||
f"Expected 'Unknown call' in error, got: {str(e)}"
|
||||
)
|
||||
|
||||
supported_call = ast.Call(
|
||||
SUPPORTED_CALL = ast.Call(
|
||||
func=ast.Name(id="MIN", ctx=ast.Load()),
|
||||
args=[ast.Constant(value=5) if hasattr(ast, "Constant") else ast.Num(n=5)],
|
||||
keywords=[],
|
||||
)
|
||||
|
||||
try:
|
||||
result = transformer.visit_Call(supported_call)
|
||||
result = transformer.visit_Call(SUPPORTED_CALL)
|
||||
assert result.func.id == "to_min", f"Expected 'to_min', got: {result.func.id}"
|
||||
except Exception as e:
|
||||
assert False, f"Supported function call should not raise exception: {e}"
|
||||
@@ -1280,7 +1284,7 @@ def test_missing_files_scenarios(binary_handler_analyze_rocprof_compute):
|
||||
if os.path.exists(csv_path):
|
||||
os.remove(csv_path)
|
||||
|
||||
code = binary_handler_analyze_rocprof_compute([ # noqa: F841
|
||||
binary_handler_analyze_rocprof_compute([
|
||||
"analyze",
|
||||
"--path",
|
||||
workload_dir,
|
||||
@@ -1350,27 +1354,27 @@ def test_build_dfs_edge_cases():
|
||||
|
||||
@pytest.mark.misc
|
||||
def test_update_functions_coverage():
|
||||
"""Test update_denom_string and update_normUnit_string branches"""
|
||||
"""Test update_denominator_string and update_norm_unit_string branches"""
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
from utils.parser import update_denom_string, update_normUnit_string
|
||||
from utils.parser import update_denominator_string, update_normal_unit_string
|
||||
|
||||
result = update_denom_string("AVG(SQ_WAVES / $denom)", "per_wave")
|
||||
result = update_denominator_string("AVG(SQ_WAVES / $denom)", "per_wave")
|
||||
assert "$denom" not in result
|
||||
assert "SQ_WAVES" in result
|
||||
|
||||
result = update_denom_string("AVG(DATA / $denom)", "per_cycle")
|
||||
result = update_denominator_string("AVG(DATA / $denom)", "per_cycle")
|
||||
assert "$GRBM_GUI_ACTIVE_PER_XCD" in result
|
||||
|
||||
result = update_denom_string("AVG(DATA / $denom)", "per_second")
|
||||
result = update_denominator_string("AVG(DATA / $denom)", "per_second")
|
||||
assert "End_Timestamp - Start_Timestamp" in result
|
||||
|
||||
result = update_denom_string("AVG(DATA / $denom)", "unsupported_unit")
|
||||
result = update_denominator_string("AVG(DATA / $denom)", "unsupported_unit")
|
||||
assert "$denom" in result
|
||||
|
||||
result = update_normUnit_string("(Prefix + $normUnit)", "per_wave")
|
||||
result = update_normal_unit_string("(Prefix + $normUnit)", "per_wave")
|
||||
assert "per wave" in result.lower()
|
||||
assert result[0].isupper()
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import yaml
|
||||
def test_modification_time():
|
||||
# Ensure hash map consistency
|
||||
hash_path = Path("utils/autogen_hash.yaml")
|
||||
with open(hash_path, "r") as f:
|
||||
with open(hash_path) as f:
|
||||
hash_map = yaml.safe_load(f)
|
||||
for file, hash in hash_map.items():
|
||||
file_hash = hashlib.sha256(Path(file).read_bytes()).hexdigest()
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
##############################################################################
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -152,7 +151,7 @@ def test_num_xcds_spec_class(monkeypatch):
|
||||
num_xcds = get_num_xcds()
|
||||
|
||||
# 2. load machine specs
|
||||
machine_spec = generate_machine_specs(None)
|
||||
machine_spec = generate_machine_specs(None, None)
|
||||
|
||||
# 3. check results are expected
|
||||
assert machine_spec.compute_partition is not None
|
||||
@@ -203,26 +202,24 @@ def test_load_yaml_file_not_found():
|
||||
"""Test _load_yaml with non-existent file - covers lines 104-105"""
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
non_existent_path = "/path/that/does/not/exist/file.yaml"
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
MIGPUSpecs._load_yaml(non_existent_path)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
MIGPUSpecs._load_yaml("non_existent_file.yaml")
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
def test_load_yaml_invalid_yaml():
|
||||
"""Test _load_yaml with corrupted YAML - covers lines 106-107"""
|
||||
import yaml
|
||||
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
# Create invalid YAML file
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||
f.write("invalid: yaml: content: [\nunclosed bracket")
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
with pytest.raises(SystemExit):
|
||||
MIGPUSpecs._load_yaml(temp_path)
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
with pytest.raises(yaml.YAMLError):
|
||||
MIGPUSpecs._load_yaml(str(temp_path))
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -231,7 +228,7 @@ def test_load_yaml_generic_exception():
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
with patch("builtins.open", side_effect=PermissionError("Access denied")):
|
||||
with pytest.raises(SystemExit):
|
||||
with pytest.raises(PermissionError, match="Access denied"):
|
||||
MIGPUSpecs._load_yaml("some_file.yaml")
|
||||
|
||||
|
||||
@@ -241,8 +238,7 @@ def test_get_gpu_series_dict_uninitialized():
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
with patch.object(MIGPUSpecs, "_gpu_series_dict", {}):
|
||||
with pytest.raises(SystemExit):
|
||||
MIGPUSpecs.get_gpu_series_dict()
|
||||
assert MIGPUSpecs.get_gpu_series_dict() == {}
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -251,8 +247,7 @@ def test_get_gpu_series_uninitialized():
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
with patch.object(MIGPUSpecs, "_gpu_series_dict", {}):
|
||||
with pytest.raises(SystemExit):
|
||||
result = MIGPUSpecs.get_gpu_series("gfx942") # noqa: F841
|
||||
assert MIGPUSpecs.get_gpu_series_dict() == {}
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -328,7 +323,9 @@ def test_get_num_xcds_unknown_gpu_model():
|
||||
"""Test get_num_xcds with unknown gpu model - covers lines 319-321"""
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
result = MIGPUSpecs.get_num_xcds(gpu_arch="gfx950", gpu_model="UNKNOWN_MODEL") # noqa: F841
|
||||
result = MIGPUSpecs.get_num_xcds( # noqa: F841
|
||||
gpu_arch="gfx950", gpu_model="UNKNOWN_MODEL"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -379,9 +376,7 @@ def test_get_chip_id_dict_empty():
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
with patch.object(MIGPUSpecs, "_chip_id_dict", {}):
|
||||
with patch("src.utils.mi_gpu_spec.console_error") as mock_error:
|
||||
result = MIGPUSpecs.get_chip_id_dict() # noqa: F841
|
||||
mock_error.assert_called_once()
|
||||
assert MIGPUSpecs.get_chip_id_dict() == {}
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -390,9 +385,7 @@ def test_get_num_xcds_dict_empty():
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
with patch.object(MIGPUSpecs, "_num_xcds_dict", {}):
|
||||
with patch("src.utils.mi_gpu_spec.console_error") as mock_error:
|
||||
result = MIGPUSpecs.get_num_xcds_dict() # noqa: F841
|
||||
mock_error.assert_called_once()
|
||||
assert MIGPUSpecs.get_num_xcds_dict() == {}
|
||||
|
||||
|
||||
@pytest.mark.misc
|
||||
@@ -401,6 +394,7 @@ def test_normal_functionality_still_works():
|
||||
from src.utils.mi_gpu_spec import MIGPUSpecs
|
||||
|
||||
result = MIGPUSpecs.get_gpu_model("gfx90a", None)
|
||||
|
||||
assert result is not None
|
||||
|
||||
result = MIGPUSpecs.get_gpu_series("gfx90a")
|
||||
|
||||
@@ -580,7 +580,7 @@ def test_path(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("This test is not supported for {}".format(soc))
|
||||
print(f"This test is not supported for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(inspect.stack()[0][3], workload_dir, file_dict)
|
||||
@@ -821,7 +821,7 @@ def test_roofline_workload_dir_not_set_error():
|
||||
self.roofline_data_type = ["FP32"]
|
||||
|
||||
args = MockArgs()
|
||||
mspec = generate_machine_specs(None)
|
||||
mspec = generate_machine_specs(None, None)
|
||||
|
||||
run_parameters = {
|
||||
"workload_dir": None,
|
||||
@@ -1090,7 +1090,7 @@ def test_roofline_missing_file_handling(binary_handler_profile_rocprof_compute):
|
||||
self.roofline_data_type = ["FP32"]
|
||||
|
||||
args = MockArgs()
|
||||
mspec = generate_machine_specs(None)
|
||||
mspec = generate_machine_specs(None, None)
|
||||
|
||||
workload_dir = test_utils.get_output_dir()
|
||||
|
||||
@@ -1144,7 +1144,7 @@ def test_roofline_invalid_datatype_cli(binary_handler_profile_rocprof_compute):
|
||||
self.roofline_data_type = ["FP32"]
|
||||
|
||||
args = MockArgs()
|
||||
mspec = generate_machine_specs(None)
|
||||
mspec = generate_machine_specs(None, None)
|
||||
|
||||
run_parameters = {
|
||||
"workload_dir": test_utils.get_output_dir(),
|
||||
@@ -1212,7 +1212,7 @@ def test_device_filter(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
# TODO - verify expected device id in results
|
||||
@@ -1250,7 +1250,7 @@ def test_kernel(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(
|
||||
@@ -1286,7 +1286,7 @@ def test_dispatch_0(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(
|
||||
@@ -1326,7 +1326,7 @@ def test_dispatch_0_1(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(
|
||||
@@ -1363,7 +1363,7 @@ def test_dispatch_2(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(
|
||||
@@ -1403,7 +1403,7 @@ def test_join_type_grid(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(
|
||||
@@ -1440,7 +1440,7 @@ def test_join_type_kernel(binary_handler_profile_rocprof_compute):
|
||||
elif "MI350" in soc:
|
||||
assert sorted(list(file_dict.keys())) == sorted(ALL_CSVS_MI350)
|
||||
else:
|
||||
print("Testing isn't supported yet for {}".format(soc))
|
||||
print(f"Testing isn't supported yet for {soc}")
|
||||
assert 0
|
||||
|
||||
validate(
|
||||
@@ -1887,7 +1887,7 @@ class TestSetsIntegration:
|
||||
|
||||
memory_metrics = ["16.1.2", "17.1.0"]
|
||||
for metric_id in memory_metrics:
|
||||
assert metric_id in open(Path(workload_dir) / "log.txt", "r").read(), (
|
||||
assert metric_id in open(Path(workload_dir) / "log.txt").read(), (
|
||||
f"Expected memory metric {metric_id} not found"
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@ def detect_repo_structure():
|
||||
return True, monorepo_root, project_root
|
||||
|
||||
if (cwd / "CMakeLists.txt").exists():
|
||||
with open(cwd / "CMakeLists.txt", "r") as f:
|
||||
with open(cwd / "CMakeLists.txt") as f:
|
||||
content = f.read()
|
||||
if (
|
||||
"project(rocprofiler-compute" in content
|
||||
|
||||
@@ -42,17 +42,15 @@ import yaml
|
||||
|
||||
# Get root directory of the project
|
||||
ROOT_DIR = Path(__file__).parent.parent
|
||||
SOURCE_DIR = ROOT_DIR.joinpath("utils")
|
||||
TARGET_DIR = ROOT_DIR.joinpath("src", "rocprof_compute_soc", "analysis_configs")
|
||||
SETS_TARGET_DIR = ROOT_DIR.joinpath(
|
||||
"src", "rocprof_compute_soc", "profile_configs", "sets"
|
||||
)
|
||||
DOC_TARGET_DIR = ROOT_DIR.joinpath("docs", "data")
|
||||
SOURCE_DIR = ROOT_DIR / "utils"
|
||||
TARGET_DIR = ROOT_DIR / "src" / "rocprof_compute_soc" / "analysis_configs"
|
||||
SETS_TARGET_DIR = ROOT_DIR / "src" / "rocprof_compute_soc" / "profile_configs" / "sets"
|
||||
DOC_TARGET_DIR = ROOT_DIR / "docs" / "data"
|
||||
AUTOGEN_TEXT = (
|
||||
"# AUTOGENERATED FILE. Only edit for testing purposes, not for development. "
|
||||
"Generated from utils/unified_config.yaml. Generated by utils/split_config.py\n"
|
||||
)
|
||||
HASH_FILE = ROOT_DIR.joinpath("utils", "autogen_hash.yaml")
|
||||
HASH_FILE = ROOT_DIR / "utils" / "autogen_hash.yaml"
|
||||
HASH_FILE_MAP = {}
|
||||
GFX_VERSIONS = ["gfx908", "gfx90a", "gfx940", "gfx941", "gfx942", "gfx950"]
|
||||
METRIC_ID_TO_NAME_MAP = {gfx_version: {} for gfx_version in GFX_VERSIONS}
|
||||
@@ -70,7 +68,7 @@ def update_analysis_config():
|
||||
global METRIC_ID_TO_NAME_MAP
|
||||
|
||||
# Read the unified config file
|
||||
with open(SOURCE_DIR.joinpath("unified_config.yaml")) as file:
|
||||
with open(SOURCE_DIR / "unified_config.yaml") as file:
|
||||
unified_config = yaml.safe_load(file)
|
||||
|
||||
# Create per panel config file
|
||||
@@ -94,7 +92,7 @@ def update_analysis_config():
|
||||
|
||||
for gfx_version in GFX_VERSIONS:
|
||||
# Create per gfx architecture folder
|
||||
gfx_dir = TARGET_DIR.joinpath(gfx_version)
|
||||
gfx_dir = TARGET_DIR / gfx_version
|
||||
# Create directory if it doesn't exist
|
||||
if not gfx_dir.exists():
|
||||
gfx_dir.mkdir()
|
||||
@@ -120,9 +118,7 @@ def update_analysis_config():
|
||||
data_source_config
|
||||
)
|
||||
# Write panel config to file
|
||||
filename = Path(
|
||||
TARGET_DIR.joinpath(gfx_version, f"{panel_id}_{panel_title}.yaml")
|
||||
)
|
||||
filename = TARGET_DIR / gfx_version / f"{panel_id}_{panel_title}.yaml"
|
||||
with open(filename, "w") as file:
|
||||
file.write(get_autogen_text())
|
||||
yaml.dump(new_panel_config, file, sort_keys=False)
|
||||
@@ -148,7 +144,7 @@ def update_sets_config():
|
||||
print(f"Created directory: {SETS_TARGET_DIR}")
|
||||
|
||||
# Read the unified config file
|
||||
with open(SOURCE_DIR.joinpath("unified_sets.yaml")) as file:
|
||||
with open(SOURCE_DIR / "unified_sets.yaml") as file:
|
||||
unified_sets = yaml.safe_load(file)
|
||||
|
||||
# Create per gfx version file
|
||||
@@ -172,7 +168,7 @@ def update_sets_config():
|
||||
new_sets["sets"].append(current_set)
|
||||
|
||||
# Write gfx version sets to file
|
||||
filename = Path(SETS_TARGET_DIR.joinpath(f"{gfx_version}_sets.yaml"))
|
||||
filename = SETS_TARGET_DIR / f"{gfx_version}_sets.yaml"
|
||||
with open(filename, "w") as file:
|
||||
file.write(get_autogen_text("utils/unified_sets.yaml"))
|
||||
yaml.dump(new_sets, file, sort_keys=False)
|
||||
@@ -223,7 +219,7 @@ def update_documentation():
|
||||
}
|
||||
|
||||
# Read the unified config file
|
||||
with open(SOURCE_DIR.joinpath("unified_config.yaml")) as file:
|
||||
with open(SOURCE_DIR / "unified_config.yaml") as file:
|
||||
unified_config = yaml.safe_load(file)
|
||||
|
||||
panel_metric_map = {}
|
||||
@@ -258,7 +254,7 @@ def update_documentation():
|
||||
section_metric_map[section] = panel_metric_map[panel_id]
|
||||
|
||||
# Write documentation metrics description file
|
||||
filename = Path(DOC_TARGET_DIR.joinpath("metrics_description.yaml"))
|
||||
filename = DOC_TARGET_DIR / "metrics_description.yaml"
|
||||
with open(filename, "w") as file:
|
||||
file.write(get_autogen_text())
|
||||
yaml.dump(section_metric_map, file, sort_keys=False)
|
||||
|
||||
@@ -45,11 +45,11 @@ maxHeaderLines = 200
|
||||
|
||||
def cacheLicenseFile(infile, comment="#"):
|
||||
if not Path(infile).is_file():
|
||||
logging.error("Unable to access license file - >%s" % infile)
|
||||
logging.error(f"Unable to access license file - >{infile}")
|
||||
sys.exit(1)
|
||||
|
||||
license = ""
|
||||
with open(infile, "r") as file_in:
|
||||
with open(infile) as file_in:
|
||||
for line in file_in:
|
||||
license += comment
|
||||
if line.strip() != "":
|
||||
@@ -80,17 +80,17 @@ if args.files:
|
||||
specificFiles = args.files.split(",")
|
||||
|
||||
print("")
|
||||
logging.info("Source directory = %s" % srcDir)
|
||||
logging.info(f"Source directory = {srcDir}")
|
||||
if fileExtension:
|
||||
logging.info("File extension = %s" % fileExtension)
|
||||
logging.info(f"File extension = {fileExtension}")
|
||||
if specificFiles:
|
||||
logging.info("Specific files = %s" % specificFiles)
|
||||
logging.info(f"Specific files = {specificFiles}")
|
||||
|
||||
# cache license file
|
||||
license = cacheLicenseFile(args.license)
|
||||
|
||||
# Scan files in provided source directory...
|
||||
for filename in glob.iglob(srcDir + "/**", recursive=True):
|
||||
for filename in glob.iglob(f"{srcDir}/**", recursive=True):
|
||||
# skip directories
|
||||
if Path(filename).is_dir():
|
||||
continue
|
||||
@@ -106,22 +106,22 @@ for filename in glob.iglob(srcDir + "/**", recursive=True):
|
||||
if specificFiles:
|
||||
found = False
|
||||
for file in specificFiles:
|
||||
fullPath = str(Path(srcDir).joinpath(file))
|
||||
fullPath = str(Path(srcDir) / file)
|
||||
if fullPath == filename:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
continue
|
||||
|
||||
logging.debug("Examining %s for license..." % filename)
|
||||
logging.debug(f"Examining {filename} for license...")
|
||||
|
||||
# Update license header contents if delimiters are found
|
||||
with open(filename, "r") as file_in:
|
||||
baseName = Path(filename).name
|
||||
dirName = str(Path(filename).parent)
|
||||
tmpFile = dirName + "/." + baseName + ".tmp"
|
||||
with open(filename) as file_in:
|
||||
base_name = Path(filename).name
|
||||
dir_name = Path(filename).parent
|
||||
tmp_file = dir_name / f".{base_name}.tmp"
|
||||
|
||||
file_out = open(tmpFile, "w")
|
||||
file_out = open(tmp_file, "w")
|
||||
for line in file_in:
|
||||
if re.search(begDelim, line):
|
||||
logging.debug("Found beginning delimiter")
|
||||
@@ -147,10 +147,10 @@ for filename in glob.iglob(srcDir + "/**", recursive=True):
|
||||
file_out.close()
|
||||
|
||||
# Check if file changed and update
|
||||
if not filecmp.cmp(filename, tmpFile, shallow=False):
|
||||
logging.info("%s changed" % filename)
|
||||
shutil.copystat(filename, tmpFile)
|
||||
if not filecmp.cmp(filename, tmp_file, shallow=False):
|
||||
logging.info(f"{filename} changed")
|
||||
shutil.copystat(filename, tmp_file)
|
||||
if not args.dryrun:
|
||||
os.rename(tmpFile, filename)
|
||||
os.rename(tmp_file, filename)
|
||||
else:
|
||||
os.unlink(tmpFile)
|
||||
os.unlink(tmp_file)
|
||||
|
||||
@@ -35,20 +35,20 @@ parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--tag", type=str, required=True, help="tagname to check")
|
||||
args = parser.parse_args()
|
||||
|
||||
execPath = str(Path(__file__).parent)
|
||||
with open(execPath + "/../VERSION") as f:
|
||||
repoVer = f.readline().strip()
|
||||
exec_path = Path(__file__).parent
|
||||
with open(exec_path / "../VERSION") as f:
|
||||
repo_ver = f.readline().strip()
|
||||
|
||||
repoCheck = "v" + repoVer
|
||||
repo_check = f"v{repo_ver}"
|
||||
tag = args.tag
|
||||
|
||||
print("Current repository version = %s" % repoVer)
|
||||
print("--> tagname = %s" % tag)
|
||||
print(f"Current repository version = {repo_ver}")
|
||||
print(f"--> tagname = {tag}")
|
||||
|
||||
if repoCheck == tag:
|
||||
if repo_check == tag:
|
||||
print("OK: exact match")
|
||||
exit(0)
|
||||
elif tag.startswith(repoCheck + "-"):
|
||||
elif tag.startswith(repo_check + "-"):
|
||||
print("OK: allowed match with extra delimiter")
|
||||
exit(0)
|
||||
elif tag.startswith("rocm-"):
|
||||
|
||||
Reference in New Issue
Block a user