Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

511 lines
19 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (C) Advanced Micro Devices. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import argparse
import datetime
import os
import pathlib
import sys
import textwrap
version_number = "1.0.0"
build_date = f"{datetime.datetime.now():%b %d %Y}"
verbose_choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "ALWAYS"]
not_supported_error_codes = [
("2", "AMDSMI_STATUS_NOT_SUPPORTED"),
("3", "AMDSMI_STATUS_NOT_YET_IMPLEMENTED"),
("49", "AMDSMI_STATUS_NO_HSMP_MSG_SUP"),
]
open_parenthesis = "("
close_parenthesis = ")"
colon = ":"
header = "any C(any, func, unit) py(any, func, unit)"
def Print(cond, msg, ending=None, sysout=sys.stdout):
if isinstance(cond, str): # Check verbose level
if cond in verbose_choices:
cond_num = verbose_choices.index(cond)
if cond_num < args.verbose_num:
return
elif not cond: # Check cond
return
if ending == None:
print(msg, file=sysout)
else:
print(msg, end=ending, file=sysout)
return
def Find_Root_Path():
last_path = pathlib.Path("/")
root_path = pathlib.Path.cwd()
while True:
git_path = root_path / ".git"
if git_path.exists():
return root_path / "projects/amdsmi"
if root_path == last_path:
break
root_path = root_path.parent
return None
def Check_Inputs(amdsmi, file_names):
root_path = Find_Root_Path()
# Check for header file
amdsmi_path = pathlib.Path(amdsmi)
if not amdsmi_path.exists():
if root_path:
amdsmi_path = root_path / amdsmi_path
if not amdsmi_path.exists():
amdsmi_path = None
else:
amdsmi_path = None
# Check for any log files
file_paths = {}
missing_file_paths = {}
for key, file_name in file_names.items():
file_path = args.log_dir / pathlib.Path(file_name)
if file_path.exists():
file_paths[key] = file_path
else:
if root_path:
file_path = root_path / args.log_dir / file_name
if file_path.exists():
file_paths[key] = file_path
else:
missing_file_paths[key] = file_path
else:
missing_file_paths[key] = file_path
return (amdsmi_path, file_paths, missing_file_paths)
def ReadAmdsmiHeader(file_content):
api_map = {}
end_block_pos = 0
while True:
start_block_pos = file_content.find("@ingroup", end_block_pos)
if start_block_pos == -1:
break
end_block_pos = file_content.find(f"{close_parenthesis};", start_block_pos)
if end_block_pos == -1:
Print(
"ERROR", f"Could not find func definition end, {start_block_pos}", sysout=sys.stderr
)
break
end_pos = file_content.rfind(open_parenthesis, start_block_pos, end_block_pos)
start_pos = file_content.rfind("amdsmi_", start_block_pos, end_pos)
func_name = file_content[start_pos:end_pos].strip()
api_map[func_name] = 0
sorted_map = {}
sorted_keys = sorted(api_map.keys())
for key in sorted_keys:
sorted_map[key] = api_map[key]
return sorted_map
def Find_Name(line, start, name_start, name_end, cof):
if start in line:
pos_start = line.find(start)
pos_start = line.find(name_start, pos_start)
if pos_start == -1:
Print("DEBUG", f'start: Bad "{start}" definition, {line}')
return None
pos_end = line.find(name_end, pos_start)
if pos_end == -1:
if not cof:
Print("DEBUG", f'end: Bad "{start}" definition, {line}')
return None
pos_end = len(line)
data = line[pos_start:pos_end].strip()
return data
return None
def ReadTestingInput(file_contents):
# api_map[file_name][func_name] = number_times_called
api_map = {}
api_support_map = {}
fc = {}
lines = []
# Create content that only contains API name and status
# This eliminates any statements between API name and status
for file_name in file_contents:
for line in file_contents[file_name]:
if "### amdsmi_" in line or "AMDSMI API" in line:
lines.append(line)
fc[file_name] = lines
for file_name in fc:
len_file_contents = len(file_contents[file_name])
api_map[file_name] = {}
for index, line in enumerate(file_contents[file_name]):
# Finding API name
func_name = Find_Name(line, "### amdsmi_", "amdsmi_", open_parenthesis, False)
if func_name == None:
continue
if func_name in api_map[file_name]:
api_map[file_name][func_name] += 1
else:
api_map[file_name][func_name] = 1
# Finding APIs that are Not Supported
# An API must have at least one SUCCESS to be supported
if index + 1 < len_file_contents:
line = file_contents[file_name][index + 1]
else:
line = ""
status = Find_Name(line, "AMDSMI API Returned", "AMDSMI_", colon, True)
if status:
if func_name in api_support_map:
if api_support_map[func_name] > 0:
api_support_map[func_name] += 1 # API is already supported
elif not any(status in value for value in not_supported_error_codes):
if status == "AMDSMI_STATUS_SUCCESS":
api_support_map[func_name] = 1 # API is now supported
else:
supported = not any(status in value for value in not_supported_error_codes)
if supported and status == "AMDSMI_STATUS_SUCCESS":
api_support_map[func_name] = 1 # API is supported
else:
api_support_map[func_name] = 0 # API is not supported
else:
api_support_map[func_name] = 1 # API is supported if there is no status by default
return (api_map, api_support_map)
def Main(amdsmi, file_names):
amdsmi_path, file_paths, missing_file_paths = Check_Inputs(amdsmi, file_names)
if not amdsmi_path:
Print("WARNING", f"Missing header, {amdsmi}", sysout=sys.stderr)
for key, file_path in missing_file_paths.items():
Print("WARNING", f"Missing file, {key}={file_path}", sysout=sys.stderr)
if amdsmi_path:
Print("INFO", f"Using header, {amdsmi_path}")
for key, file_path in file_paths.items():
Print("INFO", f"Using file, {key}={file_path}")
if not amdsmi_path and not len(file_paths):
Print("ERROR", "No header or log files found, exiting script", sysout=sys.stderr)
return 1
# Header file is stored as a string
amdsmi_content = amdsmi_path.read_text()
# Each log file is stored as a [string]
file_contents = {}
for key, file_name in file_paths.items():
content = file_name.read_text()
file_contents[key] = content.split("\n")
# Read in header input
amdsmi_map = {}
if amdsmi_content:
amdsmi_map = ReadAmdsmiHeader(amdsmi_content)
num_api = len(amdsmi_map)
if not num_api:
num_api = 1 # set so code does not divide by zero
Print("DEBUG", "No header APIs found")
Print("DEBUG", f"amdsmi APIs = {num_api}")
for func_name in amdsmi_map:
Print("DEBUG", f"\tfunc: {func_name}")
# Read in testing inputs
api_map, api_support_map = ReadTestingInput(file_contents)
found = False
for file_name in api_map:
if len(api_map[file_name]):
found = True
if not found:
Print("ERROR", "No testing APIs found", sysout=sys.stderr)
return 1
for file_name in api_map:
Print("DEBUG", f"Tested {file_name} funcs = {len(api_map[file_name])}")
for func_name in api_map[file_name]:
Print("DEBUG", f"\t{func_name}()")
if not args.output_dir.exists():
args.output_dir.mkdir(parents=True)
# Initialize
for func_name in amdsmi_map:
amdsmi_map[func_name] = {
"tested": 0,
"c_unit_test": 0,
"c_integration": 0,
"py_unit_test": 0,
"py_integration": 0,
}
# api_map[file_name][func_name] = number_times_called
for file_name in api_map:
for func_name in api_map[file_name]:
if func_name not in amdsmi_map:
Print(
"ERROR",
f"func {func_name}() in {file_name} tested but not in amdsmi.h",
sysout=sys.stderr,
)
continue
amdsmi_map[func_name]["tested"] += 1
amdsmi_map[func_name][file_name] = 1
api_summary = []
msg = f"API, Tested, c_unit_test, c_integration, py_unit_test, py_integration"
api_summary.append(msg)
for func_name, tests_map in amdsmi_map.items():
msg = f"{func_name}, {tests_map['tested']}, {tests_map['c_unit_test']}, {tests_map['c_integration']}, {tests_map['py_unit_test']}, {tests_map['py_integration']}"
api_summary.append(msg)
api_summary.append("")
api_summary = "\n".join(api_summary)
api_summary_csv = pathlib.Path(args.output_dir / "_api_summary.csv")
api_summary_csv.write_text(api_summary)
# Get information about each test component
c_any_total = 0
c_unit_test_total = 0
c_integration_total = 0
py_any_total = 0
py_unit_test_total = 0
py_integration_total = 0
any_total = 0
unit_test_total = 0
integration_total = 0
api_missing = []
api_tested = []
for func_name, values in amdsmi_map.items():
c_any = 0
py_any = 0
if values["tested"]:
if values["c_unit_test"] or values["c_integration"]:
c_any = 1
c_any_total += 1
if values["c_unit_test"]:
c_unit_test_total += 1
if values["c_integration"]:
c_integration_total += 1
if values["py_unit_test"] or values["py_integration"]:
py_any = 1
py_any_total += 1
if values["py_unit_test"]:
py_unit_test_total += 1
if values["py_integration"]:
py_integration_total += 1
if values["c_integration"] or values["py_integration"]:
integration_total += 1
if values["c_unit_test"] or values["py_unit_test"]:
unit_test_total += 1
if (
values["c_unit_test"]
or values["c_integration"]
or values["py_unit_test"]
or values["py_integration"]
):
any_total += 1
if values["tested"] == 0:
api_missing.append(func_name)
Print("DEBUG", f"Missing:", ending="")
else:
api_tested.append(func_name)
Print("DEBUG", f" Tested:", ending="")
Print(
"DEBUG",
f" {func_name}: C(any={c_any} unit={values['c_unit_test']} int={values['c_integration']}) py(any={py_any} unit={values['py_unit_test']} int={values['py_integration']})",
)
# Create summary report
api_summary_support = []
if len(api_support_map):
sorted_map = {}
sorted_keys = sorted(api_support_map.keys())
for key in sorted_keys:
sorted_map[key] = api_support_map[key]
num_supported = 0
num_not_supported = 0
for func_name, supported in sorted_map.items():
if supported:
num_supported += 1
else:
num_not_supported += 1
api_summary_support.append(f"APIs Supported: {num_supported}")
for func_name, supported in sorted_map.items():
if supported:
api_summary_support.append(f"\t{func_name}()")
api_summary_support.append(f"\nAPIs Not Supported: {num_not_supported}")
for func_name, supported in sorted_map.items():
if not supported:
api_summary_support.append(f"\t{func_name}()")
api_summary_support.append(f"\nAPIs Missing: {len(api_missing)}")
for func_name in api_missing:
api_summary_support.append(f"\t{func_name}()")
api_summary_support.append(f"\nAPI Summary")
api_summary_support.append(f"Not Supported: {num_not_supported:>3d}")
api_summary_support.append(f" Supported: {num_supported:>3d}")
api_summary_support.append(f" Missing: {len(api_missing):>3d}")
api_summary_support.append(f" Total: {len(sorted_map) + len(api_missing):>3d}")
api_summary_support = "\n".join(api_summary_support)
api_summary_support_txt = pathlib.Path(args.output_dir / "_api_summary_support.txt")
api_summary_support_txt.write_text(api_summary_support)
# Create summary table
api_summary_table = []
c_any_total_percent = (c_any_total / num_api) * 100
c_unit_test_total_percent = (c_unit_test_total / num_api) * 100
c_integration_total_percent = (c_integration_total / num_api) * 100
py_any_total_percent = (py_any_total / num_api) * 100
py_unit_test_total_percent = (py_unit_test_total / num_api) * 100
py_integration_total_percent = (py_integration_total / num_api) * 100
any_total_percent = (any_total / num_api) * 100
unit_test_total_percent = (unit_test_total / num_api) * 100
integration_total_percent = (integration_total / num_api) * 100
msg = f"{'API':^5s} {'Test':>5s}({'%':^5s}) {'Unit':>5s}({'%':^5s}) {'Func':>5s}({'%':^5s})"
api_summary_table.append(msg)
msg = f"{'C':^5s} {c_any_total:>5d}({c_any_total_percent:>5.1f}) {c_unit_test_total:>5d}({c_unit_test_total_percent:>5.1f}) {c_integration_total:>5d}({c_integration_total_percent:>5.1f})"
api_summary_table.append(msg)
msg = f"{'Py':^5s} {py_any_total:>5d}({py_any_total_percent:>5.1f}) {py_unit_test_total:>5d}({py_unit_test_total_percent:>5.1f}) {py_integration_total:>5d}({py_integration_total_percent:>5.1f})"
api_summary_table.append(msg)
msg = f"{'Total':^5s} {any_total:>5d}({any_total_percent:>5.1f}) {unit_test_total:>5d}({unit_test_total_percent:>5.1f}) {integration_total:>5d}({integration_total_percent:>5.1f})"
api_summary_table.append(msg)
api_summary_table.append(f"Num APIs: {num_api}")
api_summary_table.append("")
api_summary_table = "\n".join(api_summary_table)
api_summary_table_txt = pathlib.Path(args.output_dir / "_api_summary_table.txt")
api_summary_table_txt.write_text(api_summary_table)
return 0
def Parse_Command_Line(cmds=None):
class CalledAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
setattr(namespace, f"{self.dest}_called", True)
msg_description = "Create API coverage report for unit_test.py and integration_test.py tests"
msg_epilog = "Example:\n\t%(prog)s --c_unit_test <c_unit_test.log> --py_integration_test <py_integration_test.log>"
parser = argparse.ArgumentParser(
description=msg_description,
formatter_class=argparse.RawTextHelpFormatter,
epilog=textwrap.dedent(msg_epilog),
)
parser_header = parser.add_argument_group("Information")
parser.add_argument(
"--version", action="version", version=version_number, help="Show version and exit"
)
parser.add_argument("--build", action="version", version=build_date, help="Show build and exit")
parser.add_argument(
"--verbose",
choices=verbose_choices,
type=str,
default="WARNING",
help="Level of information to output, default=%(default)s",
)
parser_header = parser.add_argument_group("Header File")
parser_header.add_argument(
"--amdsmi",
default="include/amd_smi/amdsmi.h",
help="Path to header file, default=%(default)s",
)
parser_logs = parser.add_argument_group("Log Files")
parser_logs.add_argument(
"--log_dir", default="build", help="Path to where logs exist, default=%(default)s"
)
parser_logs.add_argument(
"--c_unit_test",
default="_c_unit_test.log",
help="Filename for C unit_test output, default=%(default)s",
)
parser_logs.add_argument(
"--c_integration",
default="_amdsmitst.log",
help="Filename for C integration_test output, default=%(default)s",
)
parser_logs.add_argument(
"--py_unit_test",
default="_unit_test.log",
help="Filename for Python unit_test output, default=%(default)s",
)
parser_logs.add_argument(
"--py_integration",
default="_integration_test.log",
help="Filename for Python integration_test output, default=%(default)s",
)
parser_output = parser.add_argument_group("Output File")
parser_output.add_argument(
"--output_dir",
default="build",
action=CalledAction,
help="Path to output dir, will create if does not exist, default=%(default)s",
)
if cmds:
args = parser.parse_args(cmds.split())
else:
args = parser.parse_args()
args.verbose_num = verbose_choices.index(args.verbose)
if getattr(args, "output_dir_called", False):
args.output_dir = pathlib.Path(args.output_dir)
else:
root_path = Find_Root_Path()
args.output_dir = root_path / args.output_dir
args.file_names = {}
args.file_names["c_unit_test"] = args.c_unit_test
args.file_names["c_integration"] = args.c_integration
args.file_names["py_unit_test"] = args.py_unit_test
args.file_names["py_integration"] = args.py_integration
return args
if __name__ == "__main__":
rc = 1
args = Parse_Command_Line()
rc = Main(args.amdsmi, args.file_names)
sys.exit(rc)