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

812 lines
37 KiB
Python

#!/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.
"""Shared base class for the CLI unit tests.
``TestCliBase`` provides the instance-level scaffolding every CLI test
needs -- ``__init__`` (command constants), ``FindArgs``, ``CreateCmds``
and ``RunCmds``. Extracted from develop's monolithic ``TestAmdSmiCli``
so the per-feature CLI suites can share it: the per-command ``cli/test_*.py``
files each subclass ``TestCliBase`` and add their own test_* methods.
"""
import json
import os
import stat
import unittest
import common.common as common
import common.runcmd as runcmd
class TestCliBase(unittest.TestCase):
"""Base class for CLI functional tests.
The base now provides a cached ``setUpClass`` that fetches the ``--json``
baseline (metric/static/list/partition data, the derived ``gpus`` list and
the ``sub_args`` dict) exactly once and shares it across all CLI test
classes, so subclasses only need to add their own test_* methods.
"""
TMP_FILENAME = "_tmp.log"
TMP_FOLDER = "_tmp"
# Scaffolding shared across every CLI test class. setUpClass populates
# these once, directly on ``TestCliBase``; subclasses then resolve them
# natively through normal attribute inheritance -- no dynamic ``setattr``
# for a type checker (or go-to-definition) to lose track of. Declared here
# so editors / type checkers can resolve ``self.util``, ``self.static_data``.
common: "common.Common"
util: "runcmd.Util"
list_data: dict
static_data: dict
metric_data: dict
partition_data: dict
gpus: list
sub_args: dict
# Built lazily (not at import like common.py's pure, enum-derived parameter
# lists) because the --json baseline needs a real GPU and root. Still built
# only once for the whole CLI suite, then inherited by every command class;
# this flag guards the first-and-only initialization.
_initialized = False
@classmethod
def setUpClass(cls):
if TestCliBase._initialized:
return
TestCliBase.common = common.Common(common.verbose)
TestCliBase.util = runcmd.Util("WARNING")
# Print the per-device header (virtualization mode, asic and board
# info) once, before any CLI test class runs, rather than per test.
for i, _ in enumerate(TestCliBase.common.processors):
TestCliBase.common.print_device_header(i)
baseline = cls._build_baseline()
TestCliBase.metric_data = baseline["metric_data"]
TestCliBase.static_data = baseline["static_data"]
TestCliBase.list_data = baseline["list_data"]
TestCliBase.partition_data = baseline["partition_data"]
TestCliBase.gpus = baseline["gpus"]
TestCliBase.sub_args = baseline["sub_args"]
TestCliBase._initialized = True
# TODO: Remove this condition when CLI supports User automated input
# Commands that need User permission to run
cls.cmds_need_permission = {"set": ["--fan", "--memory-partition", "--compute-partition"]}
@classmethod
def _build_baseline(cls):
baseline = {}
# Record starting values; running here (once per class) rather than in
# __init__ (once per test method) reduces setup overhead from O(N) to
# O(1) — N being the number of test methods in this class.
cmds = [
("metric", "amd-smi metric --json"),
("static", "amd-smi static --json"),
("list", "amd-smi list --json"),
("partition", "amd-smi partition --current --json"),
]
for name, cmd in cmds:
(rc, data, std_err) = cls.util.RunCmdSync(cmd)
if rc:
raise RuntimeError(f'Error executing "{cmd}": {std_err}')
if not data:
raise RuntimeError(f'Empty JSON output from "{cmd}". stderr: {std_err}')
try:
baseline[f"{name}_data"] = json.loads(data)
except (json.JSONDecodeError, TypeError) as e:
# TODO(amdsmi_team): Known issue — several AI NIC and CPU commands can produce
# malformed JSON/CSV/error output, causing parsing & other failures.
# We need to log tickets on these issues.
# Log warning but continue — malformed JSON output is a CLI bug,
# not a test infrastructure failure; tests that depend on this
# data will fail individually with a KeyError pointing to the
# missing key, making the root cause clear.
cls.common.print(f'\n\tERROR: Could not parse JSON from "{cmd}": {e}')
baseline[f"{name}_data"] = {}
gpus = ["all"]
for entry in baseline["list_data"]:
gpus.append(entry["gpu"])
if entry["gpu"] == 0:
# Only test bdf and uuid when gpu=0
gpus.append(entry["bdf"])
gpus.append(entry["uuid"])
baseline["gpus"] = gpus
# When parsing, expand each arg with array element
baseline["sub_args"] = {
"CLOCK": ["SYS", "DF", "DCEF", "SOC", "MEM", "VCLK0", "VCLK1", "DCLK0", "DCLK1", "ALL"],
"PID": [123],
"NAME": ["AMD"],
"GPU": gpus,
"FILE": [
cls.TMP_FILENAME,
f"{cls.TMP_FILENAME} --overwrite",
f"{cls.TMP_FILENAME} --append",
],
"SEVERITY": ["nonfatal-uncorrected", "fatal", "nonfatal-corrected", "all"],
"FOLDER": [cls.TMP_FOLDER],
"FILE_LIMIT": [10],
#'LEVEL': ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
}
return baseline
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.Debug = False
self.ReduceCmds = True
self.PrintCmdsOnly = False
self.AddCmdMods = True
self.AddDeviceArgs = True
self.AddWatchArgs = True
self.AddLogLevel = "--loglevel DEBUG"
self.PASS = 0
self.FAIL = 1
self.tab = " "
self.tmp_filename = self.TMP_FILENAME
self.tmp_folder = self.TMP_FOLDER
self.openBracket = "["
self.closeBracket = "]"
self.openCurlyBrace = "{"
self.closeCurlyBrace = "}"
self.perf_levels = [
"AUTO",
"LOW",
"HIGH",
"MANUAL",
"STABLE_STD",
"STABLE_PEAK",
"STABLE_MIN_MCLK",
"STABLE_MIN_SCLK",
"DETERMINISM",
]
self.profile_levels = [
"CUSTOM",
"VIDEO",
"POWER_SAVING",
"COMPUTE",
"VR",
"3D_FULL_SCREEN",
"BOOTUP_DEFAULT",
]
self.compute_partition_modes = ["SPX", "DPX", "TPX", "QPX", "CPX"]
self.memory_partition_modes = ["NPS1", "NPS2", "NPS4", "NPS8"]
self.power_types = ["ppt0", "ppt1"]
self.ptl_formats = ["I8", "F16", "BF16", "F32", "F64", "F8", "VECTOR"]
self.clk_limits = ["SCLK", "MCLK"]
self.limit_types = ["MIN", "MAX"]
self.clk_levels = ["SCLK", "MCLK", "FCLK", "SOCCLK", "PCIE"]
# When parsing, ignore these entries as they are abnormal
self.cmd_arg_exceptions = ["--voltage"]
# When parsing, change these args into something else or add to arg
self.cmd_arg_changes = [
"--loglevel",
"--json",
"--csv",
"--append",
"--overwrite",
"--ucode-list",
"--watch",
"--watch_time",
"--iterations",
]
return
def setUp(self):
# Called before each test by unittest framework
return
def tearDown(self):
# Called after each test by unittest framework
return
def FindArgs(self, cmd, match_str):
if (
(not match_str)
or (not self.AddDeviceArgs and "Device" in match_str)
or (not self.AddWatchArgs and "Watch" in match_str)
or (not self.AddCmdMods and "Command" in match_str)
):
return ["pass"]
(rc, std_out, std_err) = self.util.RunCmdSync(cmd)
lines = std_out.split("\n") if std_out else []
found = False
options = []
for index, line in enumerate(lines):
if found:
if not line:
break
items = line.split()
for item_index, item in enumerate(items):
items[item_index] = item.strip()
item_index = -1
if "-h" == items[0][0:2]:
# Turn help into command without an option
if "Set" in match_str or "Reset" in match_str or "RAS" in match_str:
pass # These require an option
else:
options.append("")
elif "--" in items[0][0:2]:
item_index = 0
elif len(items) > 1 and "--" == items[1][0:2]:
item_index = 1
elif "-" == items[0][0:1]:
item_index = 0
sub_found = False
if item_index >= 0:
if items[item_index][-1:] == ",":
items[item_index] = items[item_index][:-1]
if items[item_index] in self.cmd_arg_exceptions:
pass
elif items[item_index] in self.cmd_arg_changes:
sub_found = True
if "--ucode-list" == items[item_index]:
options.append(f"{items[item_index]}")
options.append("--fw-list")
elif "--json" == items[item_index]:
options.append("{json}")
options.append("{json_file}")
options.append("{json_file_append}")
options.append("{json_file_overwrite}")
elif "--csv" == items[item_index]:
options.append("{csv}")
options.append("{csv_file}")
options.append("{csv_file_append}")
options.append("{csv_file_overwrite}")
elif "--append" == items[item_index] or "--overwrite" == items[item_index]:
pass
elif "--watch" == items[item_index]:
options.append("{watch_time}")
options.append("{watch_iterations}")
elif (
"--watch_time" == items[item_index]
or "--iterations" == items[item_index]
):
pass
elif "--loglevel" == items[item_index]:
pass
else:
print(f"ERROR: bad sub arg {items[item_index]}")
elif len(items) > item_index:
if items[item_index + 1][0:1] == self.openBracket:
items[item_index + 1] = items[item_index + 1][1:]
sub_arg = items[item_index + 1]
# Expand out sub_args
if sub_arg.isupper() and sub_arg in self.sub_args:
sub_found = True
for item in self.sub_args[sub_arg]:
options.append(f"{items[item_index]} {item}")
elif "Set" in match_str:
if sub_arg == "%": # arg --fan
options.append(f"{items[item_index]} 50%")
options.append(f"{items[item_index]} 50")
elif sub_arg == "LEVEL": # arg --perf-level
for perf_level in self.perf_levels:
options.append(f"{items[item_index]} {perf_level}")
elif sub_arg == "PROFILE_LEVEL": # arg --profile
for profile_level in self.profile_levels:
options.append(f"{items[item_index]} {profile_level}")
elif sub_arg == "SCLKMAX": # arg --perf-determinism
options.append("{perf_determinism}")
elif sub_arg == "TYPE/INDEX": # arg
for compute_partition_mode in self.compute_partition_modes:
options.append(f"{items[item_index]} {compute_partition_mode}")
elif sub_arg == "PARTITION": # arg --memory-partition
for memory_partition_mode in self.memory_partition_modes:
options.append(f"{items[item_index]} {memory_partition_mode}")
elif sub_arg == "MODE": # arg --compute-partition-mem-alloc-mode
for mem_alloc_mode in ["CAPPING", "ALL"]:
options.append(f"{items[item_index]} {mem_alloc_mode}")
elif sub_arg == "WATTS": # arg --power-cap
for power_type in self.power_types:
options.append(f"--power-cap {{min_power}} {power_type}")
options.append(f"--power-cap {{avg_power}} {power_type}")
options.append(f"--power-cap {{max_power}} {power_type}")
elif (
sub_arg == "POLICY_ID" and "soc" in items[item_index]
): # arg --soc-pstate
options.append(f"{items[item_index]} {{soc_pstate}}")
elif (
sub_arg == "POLICY_ID" and "xgmi" in items[item_index]
): # arg --xgmi-plpd
options.append(f"{items[item_index]} {{xgmi_plpd}}")
elif (
sub_arg == "CLK_TYPE" and "level" in items[item_index]
): # arg --clk-level
options.append(f"{items[item_index]} {{clk_level_sclk}}")
options.append(f"{items[item_index]} {{clk_level_mclk}}")
options.append(f"{items[item_index]} {{clk_level_fclk}}")
options.append(f"{items[item_index]} {{clk_level_socclk}}")
options.append(f"{items[item_index]} {{clk_level_pcie}}")
elif (
sub_arg == "STATUS" and "ptl" in items[item_index]
): # arg --ptl-status
options.append(f"{items[item_index]} 0")
options.append(f"{items[item_index]} 1")
pass
elif sub_arg == "FRMT1,FRMT2": # arg --ptl-format
for fmt1 in self.ptl_formats:
for fmt2 in self.ptl_formats:
if fmt1 == fmt2:
continue
options.append(f"{items[item_index]} {fmt1},{fmt2}")
elif (
sub_arg == "CLK_TYPE" and "limit" in items[item_index]
): # arg --clk-limit
options.append(f"{items[item_index]} {{clk_limit_sclk_min}}")
options.append(f"{items[item_index]} {{clk_limit_sclk_max}}")
options.append(f"{items[item_index]} {{clk_limit_mclk_min}}")
options.append(f"{items[item_index]} {{clk_limit_mclk_max}}")
elif (
sub_arg == "STATUS" and "process" in items[item_index]
): # arg --process-isolation
options.append(f"{items[item_index]} 0")
options.append(f"{items[item_index]} 1")
else:
print(
f"TODO: set {items[item_index]} sub_arg={sub_arg} match_str={match_str}"
)
if not sub_found:
# Put in sub_arg if it was not found
if "Set" in match_str:
pass
else:
options.append(items[item_index])
if match_str in line:
found = True
if not options:
return ["pass"]
return options
def CreateCmds(self, cmd_name, list1_name, list2_name, list3_name, list4_name):
cmd = f"amd-smi {cmd_name} --help"
list1_args = self.FindArgs(cmd, list1_name)
list2_args = self.FindArgs(cmd, list2_name)
list3_args = self.FindArgs(cmd, list3_name)
list4_args = self.FindArgs(cmd, list4_name)
if self.Debug:
print(f"{list1_name}: {'*' * 80}")
print(json.dumps(list1_args, sort_keys=False, indent=4), flush=True)
print(f"{list2_name}: {'*' * 80}")
print(json.dumps(list2_args, sort_keys=False, indent=4), flush=True)
print(f"{list3_name}: {'*' * 80}")
print(json.dumps(list3_args, sort_keys=False, indent=4), flush=True)
print(f"{list4_name}: {'*' * 80}")
print(json.dumps(list4_args, sort_keys=False, indent=4), flush=True)
cmds = []
cmd = f"amd-smi {cmd_name}"
for list1_arg in list1_args:
if list1_arg != "pass":
cmds.append((f"{cmd} {list1_arg} {self.AddLogLevel}", self.PASS))
if not list1_arg:
cmds.append((f"{cmd} --file {self.tmp_filename} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{json}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{json_file}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{json_file_append}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{json_file_overwrite}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{csv}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{csv_file}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{csv_file_append}} {self.AddLogLevel}", self.PASS))
cmds.append((f"{cmd} {{csv_file_overwrite}} {self.AddLogLevel}", self.PASS))
else:
list1_arg = ""
for list2_arg in list2_args:
if list2_arg != "pass":
cmds.append((f"{cmd} {list1_arg} {list2_arg} {self.AddLogLevel}", self.PASS))
else:
list2_arg = ""
for list3_arg in list3_args:
if list3_arg != "pass":
cmds.append(
(
f"{cmd} {list1_arg} {list2_arg} {list3_arg} {self.AddLogLevel}",
self.PASS,
)
)
else:
list3_arg = ""
for list4_arg in list4_args:
if list4_arg != "pass":
cmds.append(
(
f"{cmd} {list1_arg} {list2_arg} {list3_arg} {list4_arg} {self.AddLogLevel}",
self.PASS,
)
)
# Calculate and substitute in dependent values
# Removes cmds that are invalid
for index, cmd_cond in enumerate(cmds):
cmd, cond = cmd_cond
while self.openCurlyBrace in cmd:
items = cmd.split()
# Find gpu index
try:
i = items.index("--gpu")
gpu = items[i + 1]
if gpu.isdigit():
gpu_index = int(gpu)
else:
gpu_index = 0
except ValueError:
gpu_index = 0
# Find conditional arguments
posOpen = cmd.find(self.openCurlyBrace)
if posOpen < 0:
break
posClose = cmd.find(self.closeCurlyBrace, posOpen)
if posClose < 0:
break
nameStr = cmd[posOpen : posClose + 1]
if (
nameStr == "{json}"
or "json_file" in nameStr
or nameStr == "{csv}"
or "csv_file" in nameStr
):
# For adding file options
if nameStr == "{json}":
cmd = cmd.replace(nameStr, "--json", 1)
elif nameStr == "{json_file}":
cmd = cmd.replace(nameStr, f"--json --file {self.tmp_filename}", 1)
elif nameStr == "{json_file_append}":
cmd = cmd.replace(nameStr, f"--json --file {self.tmp_filename} --append", 1)
elif nameStr == "{json_file_overwrite}":
cmd = cmd.replace(
nameStr, f"--json --file {self.tmp_filename} --overwrite", 1
)
elif nameStr == "{csv}":
cmd = cmd.replace(nameStr, "--csv", 1)
elif nameStr == "{csv_file}":
cmd = cmd.replace(nameStr, f"--csv --file {self.tmp_filename}", 1)
elif nameStr == "{csv_file_append}":
cmd = cmd.replace(nameStr, f"--csv --file {self.tmp_filename} --append", 1)
elif nameStr == "{csv_file_overwrite}":
cmd = cmd.replace(
nameStr, f"--csv --file {self.tmp_filename} --overwrite", 1
)
else:
print(f"Error: could not replace json/csv options, {nameStr} cmd={cmd}")
cmd = ""
elif nameStr == "{watch_time}" or nameStr == "{watch_iterations}":
# For adding watch options
if nameStr == "{watch_time}":
cmd = cmd.replace(nameStr, "--watch 1 --watch_time 2", 1)
else:
cmd = cmd.replace(nameStr, "--watch 1 --iterations 2", 1)
elif (
nameStr == "{min_power}" or nameStr == "{avg_power}" or nameStr == "{max_power}"
):
# For setting --power-cap
# Find power_type
for power_type in self.power_types:
if power_type in cmd:
power_type = self.static_data["gpu_data"][gpu_index]["limit"][
power_type
]
else:
power_type = "N/A"
if (
power_type == "N/A"
or not isinstance(power_type, dict)
or power_type["min_power_limit"] == "N/A"
or power_type["max_power_limit"] == "N/A"
):
cmd = ""
else:
min_power = power_type["min_power_limit"]["value"]
max_power = power_type["max_power_limit"]["value"]
avg_power = int((min_power + max_power) / 2)
if nameStr == "{min_power}":
cmd = cmd.replace("{min_power}", str(min_power), 1)
elif nameStr == "{avg_power}":
cmd = cmd.replace("{avg_power}", str(avg_power), 1)
elif nameStr == "{max_power}":
cmd = cmd.replace("{max_power}", str(max_power), 1)
elif nameStr == "{perf_determinism}":
clock_sys = self.static_data["gpu_data"][gpu_index]["clock"]["sys"]
if clock_sys != "N/A" and len(clock_sys["frequency_levels"]):
num = len(clock_sys["frequency_levels"])
level = f"Level {num - 1}"
clock_freq = int(clock_sys["frequency_levels"][level]["value"])
cmd = cmd.replace(
"{perf_determinism}", f"--perf-determinism {clock_freq + 50}", 1
)
else:
cmd = ""
elif "clk_limit" in nameStr:
clock = self.metric_data["gpu_data"][gpu_index]["clock"]
clk_type = clk_type_name = limit_type = clk_limit_name = ""
if nameStr == "{clk_limit_sclk_min}":
clk_type = "SCLK"
clk_type_name = "socclk_0"
limit_type = "MIN"
clk_limit_name = "min_clk"
elif nameStr == "{clk_limit_sclk_max}":
clk_type = "SCLK"
clk_type_name = "socclk_0"
limit_type = "MAX"
clk_limit_name = "max_clk"
elif nameStr == "{clk_limit_mclk_min}":
clk_type = "MCLK"
clk_type_name = "mem_0"
limit_type = "MAX"
clk_limit_name = "min_clk"
elif nameStr == "{clk_limit_mclk_max}":
clk_type = "MCLK"
clk_type_name = "mem_0"
limit_type = "MIN"
clk_limit_name = "max_clk"
clk_type_limit_name = clock[clk_type_name][clk_limit_name]
if type(clk_type_limit_name) is dict:
value = clk_type_limit_name["value"]
cmd = cmd.replace(nameStr, f"{clk_type} {limit_type} {value}", 1)
else:
cmd = ""
elif "clk_level" in nameStr:
clock = self.static_data["gpu_data"][gpu_index]["clock"]
value = -1
clk_type = clk_type_name = ""
if nameStr == "{clk_level_sclk}":
clk_type = "SCLK"
clk_type_name = "sys"
elif nameStr == "{clk_level_mclk}":
clk_type = "MCLK"
clk_type_name = "mem"
elif nameStr == "{clk_level_fclk}":
clk_type = "FCLK"
clk_type_name = "df"
elif nameStr == "{clk_level_socclk}":
clk_type = "SOCCLK"
clk_type_name = "soc"
elif nameStr == "{clk_level_pcie}":
bus = self.static_data["gpu_data"][gpu_index]["bus"]
clk_type = "PCIE"
pcie_levels = bus["pcie_levels"]
if type(pcie_levels) is dict:
value = len(pcie_levels)
if value > 0:
value = 0
if clk_type != "PCIE" and value < 0:
clk_type_name = clock[clk_type_name]
if type(clk_type_name) is dict:
current_level = clk_type_name["current_level"]
freq_levels = clk_type_name["frequency_levels"]
if current_level == 0:
value = len(freq_levels) - 1
else:
value = 0
if value >= 0:
cmd = cmd.replace(nameStr, f"{clk_type} {value}", 1)
else:
cmd = ""
elif nameStr == "{soc_pstate}":
soc_pstate = self.static_data["gpu_data"][gpu_index]["soc_pstate"]
if type(soc_pstate) is dict:
num_supported = int(soc_pstate["num_supported"])
if num_supported > 0:
current = int(soc_pstate["current_id"])
if current == 0:
num = num_supported - 1
else:
num = 0
cmd = cmd.replace(nameStr, f"{num}", 1)
else:
cmd = ""
else:
cmd = ""
elif nameStr == "{xgmi_plpd}":
xgmi_plpd = self.static_data["gpu_data"][gpu_index]["xgmi_plpd"]
if type(xgmi_plpd) is dict:
num_supported = int(xgmi_plpd["num_supported"])
if num_supported > 0:
current = int(xgmi_plpd["current_id"])
if current == 0:
num = num_supported - 1
else:
num = 0
cmd = cmd.replace(nameStr, f"{num}", 1)
else:
cmd = ""
else:
cmd = ""
cmds[index] = (cmd, cond)
# Pare down commands
if self.ReduceCmds:
file_mods = ["--file", "--json", "--csv"]
watch_mods = ["--watch", "--watch_time", "--iterations"]
found_sub_arg = False
for index, cmd_cond in enumerate(cmds):
cmd, cond = cmd_cond
items = cmd.split()
# Find the first sub_arg
if not found_sub_arg and len(items) >= 3:
sub_arg = items[2]
for mod in file_mods + ["--gpu", "--loglevel"]:
if mod == sub_arg:
sub_arg = ""
break
found_sub_arg = sub_arg
# No explicit gpu infers a gpu=0
gpu_index = "0"
if "--gpu" in cmd:
try:
i = items.index("--gpu")
gpu_index = items[i + 1]
except ValueError:
# condition where --gpu is not in the cmd
# will get default gpu_index=0
pass
# Remove all --gpu for all sub_args except for the first sub_arg
if cmd and found_sub_arg:
sub_arg = items[2]
if sub_arg != found_sub_arg:
if "--gpu" in cmd:
cmd = ""
# Remove all file and watch modifiers except for gpu 0
if cmd and gpu_index != "0":
for mod in file_mods + watch_mods:
if mod in cmd:
cmd = ""
break
# Remove all --file and --watch combinations
if cmd and "--file" in cmd and "--watch" in cmd:
cmd = ""
# Remove all --watch mod for all sub_args except for the first sub_arg
if cmd and found_sub_arg and len(items) >= 3:
sub_arg = items[2]
if sub_arg != found_sub_arg:
if "--watch" in cmd:
cmd = ""
# Remove all file mod for all sub_args except for the first sub_arg
if cmd and found_sub_arg and len(items) >= 3:
sub_arg = items[2]
if sub_arg != found_sub_arg:
for mod in file_mods:
if mod in cmd:
cmd = ""
break
cmds[index] = (cmd, cond)
# Remove commands requiring input
for index, cmd_cond in enumerate(cmds):
cmd, cond = cmd_cond
if not cmd:
continue
items = cmd.split()
if len(items) < 3:
continue
if items[1] == "set":
if items[2] in self.cmds_need_permission["set"]:
cmd = ""
# Update cmds when cmd has changed
if not cmd:
cmds[index] = (cmd, cond)
# Remove empty (cmd,cond) arguments
cmds = [cmd_cond for cmd_cond in cmds if cmd_cond[0] != ""]
# Remove extra spaces between arguments
for index, cmd_cond in enumerate(cmds):
cmd, cond = cmd_cond
cmd = cmd.split()
cmd = " ".join(cmd).strip()
cmds[index] = (cmd, cond)
if self.Debug:
print(f"cmds: {'*' * 80}")
print(json.dumps(cmds, sort_keys=False, indent=4), flush=True)
return cmds
def RunCmds(self, cmds):
errors = []
msg_len = 0
for cmd, cond in cmds:
num = len(cmd)
if num > msg_len:
msg_len = num
msg_len += 2
for cmd, cond in cmds:
if self.Debug or self.PrintCmdsOnly:
print(f"cmd={cmd}")
if self.PrintCmdsOnly:
continue
(rc, std_out, std_err) = self.util.RunCmdSync(cmd)
error_code = rc
if rc and std_err:
items = std_err.split()
if "amdsmi_exception" in std_err:
# error code from amdsmi library exception
for index, item in enumerate(items):
if item == "Error":
error_code_str = items[index + 4]
error_code = error_code_str
# break
else:
# error code from amd-smi CLI
error_code = items[-1]
# Check for parse error 'choice'
if "CRITICAL" in error_code:
error_code = "Bad loglevel"
msg = f"{cmd:{msg_len}s}:"
if "--file" in cmd:
if not os.path.exists(self.tmp_filename):
_msg = f"{msg} Failure: File {self.tmp_filename} does not exist"
errors.append(_msg)
else:
with open(self.tmp_filename, "r") as fin:
std_out = fin.read()
if not len(std_out):
_msg = f"{msg} Failure: File {self.tmp_filename} was empty"
errors.append(_msg)
os.chmod(self.tmp_filename, stat.S_IWRITE)
os.remove(self.tmp_filename)
if rc and cond == self.PASS:
msg += f" Failure: Received FAIL ({error_code}), expected PASS (0)"
errors.append(msg)
elif not rc and cond != self.PASS:
msg += " Failure: Received PASS (0), expected FAIL (!0)"
errors.append(msg)
else:
if not rc:
expected = "PASS"
else:
expected = "FAIL"
msg += f" Success: Received and Expected {expected} ({error_code})"
self.common.print(f"{self.tab}{msg}")
if self.Debug:
print(f"{self.tab}rc={rc}")
print(f"{self.tab}error_code={error_code}")
print(f"{self.tab}std_out={std_out}")
print(f"{self.tab}std_err={std_err}")
if len(errors):
msg = f"\n{self.tab}".join(errors)
self.fail(f"Fail:\n{self.tab}{msg}")
return