[SWDEV-522623] Add afid functionality to API and CLI (#330)
Change-Id: I015bde926491d54e09da8f39b05650515711e09f
[SWDEV-522623] Add afid functionality to API and CLI
Change-Id: I015bde926491d54e09da8f39b05650515711e09f
Signed-off-by: Oosman Saeed <oossaeed@amd.com>
Co-authored-by: Oosman Saeed <oossaeed@amd.com>
[ROCm/amdsmi commit: 1bb1f8acc2]
This commit is contained in:
@@ -33,6 +33,7 @@ from amdsmi_cli_exceptions import AmdSmiInvalidParameterException, AmdSmiRequire
|
||||
from amdsmi_helpers import AMDSMIHelpers
|
||||
from amdsmi_logger import AMDSMILogger
|
||||
from amdsmi import amdsmi_exception, amdsmi_interface
|
||||
from pathlib import Path
|
||||
|
||||
class AMDSMICommands():
|
||||
"""This class contains all the commands corresponding to AMDSMIParser
|
||||
@@ -6325,9 +6326,35 @@ class AMDSMICommands():
|
||||
with self.logger.destination.open('a', encoding="utf-8") as output_file:
|
||||
output_file.write(legend_output + '\n')
|
||||
|
||||
def __pvtDumpAfids(self, cper_file):
|
||||
# 1) Fetch the CPER “file” and ensure we have raw bytes
|
||||
raw_data = cper_file
|
||||
if hasattr(raw_data, "read"):
|
||||
# fetch_cper_file returned a file‐object
|
||||
raw = raw_data.read()
|
||||
elif isinstance(raw_data, Path):
|
||||
# Path: read the bytes directly
|
||||
raw = raw_data.read_bytes()
|
||||
elif isinstance(raw_data, str):
|
||||
# fetch_cper_file returned a filename
|
||||
with open(raw_data, "rb") as f:
|
||||
raw = f.read()
|
||||
else:
|
||||
# assume it's already bytes
|
||||
raw = raw_data
|
||||
size = len(raw)
|
||||
self.helpers.hexdump_to_string(raw)
|
||||
afids, num_afids = amdsmi_interface.amdsmi_get_afids_from_cper(raw)
|
||||
print(f"AFIDS: ", end="")
|
||||
for afid in afids:
|
||||
print(afid, end=" ")
|
||||
print("")
|
||||
|
||||
def ras(self, args, multiple_devices=False, gpu=None, cper=None,
|
||||
severity=None, folder=None, file_limit=None, follow=None):
|
||||
|
||||
|
||||
|
||||
def ras(self, args, multiple_devices=False, gpu=None, cper=None, afid=None,
|
||||
severity=None, folder=None, file_limit=None, cper_file=None, follow=None):
|
||||
"""
|
||||
Retrieve and process CPER (RAS) entries for a target GPU.
|
||||
|
||||
@@ -6338,23 +6365,32 @@ class AMDSMICommands():
|
||||
The output file name is auto-generated using the timestamp from the CPER header data (converted from
|
||||
the header’s "YYYY/MM/DD HH:MM:SS" format), along with the GPU/platform ID and error severity.
|
||||
"""
|
||||
|
||||
# GPU handle logic.
|
||||
if gpu:
|
||||
args.gpu = gpu
|
||||
if cper:
|
||||
args.cper = cper
|
||||
if afid:
|
||||
args.afid = afid
|
||||
if severity:
|
||||
args.severity = severity
|
||||
if folder:
|
||||
args.folder = folder
|
||||
if file_limit:
|
||||
args.file_limit = file_limit
|
||||
if cper_file:
|
||||
args.cper_file = cper_file
|
||||
if follow:
|
||||
args.follow = follow
|
||||
|
||||
if args.gpu == None:
|
||||
args.gpu = self.device_handles
|
||||
|
||||
#Fetching AFID
|
||||
if args.afid and args.cper_file:
|
||||
self.__pvtDumpAfids(args.cper_file)
|
||||
return
|
||||
|
||||
if not self.group_check_printed:
|
||||
self.helpers.check_required_groups()
|
||||
self.group_check_printed = True
|
||||
@@ -6362,7 +6398,6 @@ class AMDSMICommands():
|
||||
handled_multiple_gpus, device_handle = self.helpers.handle_gpus(args, self.logger, self.ras)
|
||||
if handled_multiple_gpus:
|
||||
return
|
||||
|
||||
args.gpu = device_handle
|
||||
|
||||
# Parse severity mask dynamically from the --severity option.
|
||||
@@ -6381,17 +6416,15 @@ class AMDSMICommands():
|
||||
severity_mask |= (1 << 0)
|
||||
elif sev in ("nonfatal-corrected", "corrected"):
|
||||
# Set bit corresponding to AMDSMI_CPER_SEV_NON_FATAL_CORRECTED (which is 2)
|
||||
severity_mask |= (1 << 2)
|
||||
|
||||
severity_mask |= (1 << 2)
|
||||
|
||||
cursor = 0
|
||||
buffer_size = 1048576
|
||||
if args.cper:
|
||||
# Start from cursor 0 (no timestamp argument provided).
|
||||
cursor = 0
|
||||
buffer_size = 1048576
|
||||
file_limit = int(args.file_limit) if args.file_limit else 1000
|
||||
|
||||
# Main loop: continuously retrieve CPER entries if --follow is set.
|
||||
gpu_id = self.helpers.get_gpu_id_from_device_handle(args.gpu)
|
||||
|
||||
# Print header only when dumping to a folder
|
||||
if args.follow and not getattr(self, "_cper_follow_prompted", False):
|
||||
print("Press CTRL + C to stop.")
|
||||
@@ -6409,12 +6442,11 @@ class AMDSMICommands():
|
||||
if partition_id != 0:
|
||||
logging.debug(f"Skipping gpu {gpu_id} on non zero partition {partition_id}")
|
||||
return
|
||||
|
||||
if args.folder and args.gpu:
|
||||
print(f"Dumping CPER file header entries for GPU {gpu_id} in folder {args.folder}")
|
||||
elif args.folder:
|
||||
|
||||
if args.folder and not getattr(self, "_cper_folder_prompted", False):
|
||||
print(f"Dumping CPER file header entries in folder {args.folder}")
|
||||
|
||||
self._cper_folder_prompted = True
|
||||
|
||||
self.logger.set_cper_exit_message(False)
|
||||
self.stop = False
|
||||
|
||||
|
||||
@@ -1078,7 +1078,7 @@ class AMDSMIHelpers():
|
||||
msg = (
|
||||
"WARNING: User is missing the following required groups: %s. "
|
||||
"Please add user to these groups."
|
||||
) % ", ".join(sorted(missing_groups))
|
||||
) % ", ".join(sodurted(missing_groups))
|
||||
print(msg)
|
||||
logging.warning(msg)
|
||||
|
||||
@@ -1116,7 +1116,7 @@ class AMDSMIHelpers():
|
||||
self._cper_warning_printed = True
|
||||
|
||||
# Header
|
||||
print(f"{'timestamp':<20} {'gpu_id':<6} {'severity':<10} {'file_name'}")
|
||||
print(f"{'timestamp':<20} {'gpu_id':<7} {'severity':<12} {'file_name':<17}")
|
||||
self._cper_display_initialized = True
|
||||
|
||||
for entry_index, entry in enumerate(entries.values()):
|
||||
@@ -1138,7 +1138,7 @@ class AMDSMIHelpers():
|
||||
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
gpu_id = self.get_gpu_id_from_device_handle(device_handle)
|
||||
print(f"{timestamp:<20} {gpu_id:<6} {prefix:<10} {cper_data_file}")
|
||||
print(f"{timestamp:<20} {gpu_id:<7} {prefix:<12} {cper_data_file:<17}")
|
||||
self.increment_cper_count()
|
||||
time.sleep(1)
|
||||
|
||||
@@ -1156,7 +1156,7 @@ class AMDSMIHelpers():
|
||||
self._cper_warning_printed = True
|
||||
|
||||
# Header
|
||||
print(f"{'timestamp':<20} {'gpu_id':<6} {'severity':<10} {'file_name'}")
|
||||
print(f"{'timestamp':<20} {'gpu_id':<7} {'severity':<12} {'file_name':<17}")
|
||||
self._cper_display_initialized = True
|
||||
|
||||
# Loop through all entries in the dictionary.
|
||||
@@ -1180,14 +1180,16 @@ class AMDSMIHelpers():
|
||||
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
gpu_id = self.get_gpu_id_from_device_handle(device_handle)
|
||||
print(f"{timestamp:<20} {gpu_id:<6} {prefix:<10} {cper_data_file}")
|
||||
print(f"{timestamp:<20} {gpu_id:<7} {prefix:<12} {cper_data_file:<17}")
|
||||
self.increment_cper_count()
|
||||
|
||||
|
||||
def dump_gpu_entries(self, folder, entries, cper_data, device_handle):
|
||||
# Header
|
||||
print(f"{'timestamp':<20} {'gpu_id':<6} {'severity':<10} {'file_name'}")
|
||||
self._cper_display_initialized = True
|
||||
# One‐time initialization: print warning & header only once
|
||||
if not getattr(self, "_cper_display_initialized", False):
|
||||
# Warning if no folder was specified elsewhere
|
||||
print(f"{'timestamp':<20} {'gpu_id':<7} {'severity':<12} {'file_name':<17}")
|
||||
self._cper_display_initialized = True
|
||||
|
||||
|
||||
if folder:
|
||||
@@ -1220,7 +1222,7 @@ class AMDSMIHelpers():
|
||||
#print header
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
gpu_id = self.get_gpu_id_from_device_handle(device_handle)
|
||||
print(f"{timestamp:<20} {gpu_id:<6} {prefix:<10} {cper_data_file}")
|
||||
print(f"{timestamp:<20} {gpu_id:<7} {prefix:<12} {cper_data_file:<17}")
|
||||
self.increment_cper_count()
|
||||
|
||||
|
||||
@@ -1241,9 +1243,11 @@ class AMDSMIHelpers():
|
||||
|
||||
|
||||
def dump_all_entries(self, folder, entries, cper_data, device_handle):
|
||||
# Header
|
||||
print(f"{'timestamp':<20} {'gpu_id':<6} {'severity':<10} {'file_name'}")
|
||||
self._cper_display_initialized = True
|
||||
# One‐time initialization: print warning & header only once
|
||||
if not getattr(self, "_cper_display_initialized", False):
|
||||
# Warning if no folder was specified elsewhere
|
||||
print(f"{'timestamp':<20} {'gpu_id':<7} {'severity':<12} {'file_name':<17}")
|
||||
self._cper_display_initialized = True
|
||||
|
||||
|
||||
if folder:
|
||||
@@ -1276,7 +1280,7 @@ class AMDSMIHelpers():
|
||||
#print header
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
gpu_id = self.get_gpu_id_from_device_handle(device_handle)
|
||||
print(f"{timestamp:<20} {gpu_id:<6} {prefix:<10} {cper_data_file}")
|
||||
print(f"{timestamp:<20} {gpu_id:<7} {prefix:<12} {cper_data_file:<17}")
|
||||
self.increment_cper_count()
|
||||
|
||||
try:
|
||||
@@ -1293,9 +1297,11 @@ class AMDSMIHelpers():
|
||||
|
||||
|
||||
def dump_all_entries_follow(self, folder, entries, cper_data, device_handle):
|
||||
# Header
|
||||
print(f"{'timestamp':<20} {'gpu_id':<6} {'severity':<10} {'file_name'}")
|
||||
self._cper_display_initialized = True
|
||||
# One‐time initialization: print warning & header only once
|
||||
if not getattr(self, "_cper_display_initialized", False):
|
||||
# Warning if no folder was specified elsewhere
|
||||
print(f"{'timestamp':<20} {'gpu_id':<7} {'severity':<12} {'file_name':<17}")
|
||||
self._cper_display_initialized = True
|
||||
|
||||
|
||||
if folder:
|
||||
@@ -1328,7 +1334,7 @@ class AMDSMIHelpers():
|
||||
#print header
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
gpu_id = self.get_gpu_id_from_device_handle(device_handle)
|
||||
print(f"{timestamp:<20} {gpu_id:<6} {prefix:<10} {cper_data_file}")
|
||||
print(f"{timestamp:<20} {gpu_id:<7} {prefix:<12} {cper_data_file:<17}")
|
||||
self.increment_cper_count()
|
||||
time.sleep(1)
|
||||
|
||||
@@ -1346,9 +1352,11 @@ class AMDSMIHelpers():
|
||||
|
||||
|
||||
def dump_gpu_entries_follow(self, folder, entries, cper_data, device_handle):
|
||||
# Header
|
||||
print(f"{'timestamp':<20} {'gpu_id':<6} {'severity':<10} {'file_name'}")
|
||||
self._cper_display_initialized = True
|
||||
# One‐time initialization: print warning & header only once
|
||||
if not getattr(self, "_cper_display_initialized", False):
|
||||
# Warning if no folder was specified elsewhere
|
||||
print(f"{'timestamp':<20} {'gpu_id':<7} {'severity':<12} {'file_name':<17}")
|
||||
self._cper_display_initialized = True
|
||||
|
||||
|
||||
if folder:
|
||||
@@ -1381,7 +1389,7 @@ class AMDSMIHelpers():
|
||||
#print header
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
gpu_id = self.get_gpu_id_from_device_handle(device_handle)
|
||||
print(f"{timestamp:<20} {gpu_id:<6} {prefix:<10} {cper_data_file}")
|
||||
print(f"{timestamp:<20} {gpu_id:<7} {prefix:<12} {cper_data_file:<17}")
|
||||
self.increment_cper_count()
|
||||
time.sleep(1)
|
||||
|
||||
@@ -1396,3 +1404,35 @@ class AMDSMIHelpers():
|
||||
else:
|
||||
print(json.dumps(entries, indent=2,
|
||||
default=lambda o: o.decode('utf-8') if isinstance(o, bytes) else o))
|
||||
|
||||
|
||||
def hexdump_to_string(self, data: Union[bytes, List[int]]) -> str:
|
||||
"""
|
||||
Convert binary data to a hexdump string.
|
||||
|
||||
Args:
|
||||
data: bytes object or list of integer byte values (0–255).
|
||||
|
||||
Returns:
|
||||
A multiline string, each line showing:
|
||||
offset (in hex), hex bytes (16 per line), and printable ASCII.
|
||||
"""
|
||||
# Normalize to list of ints
|
||||
if isinstance(data, bytes):
|
||||
data_ints = list(data)
|
||||
else:
|
||||
# allow list of ints or single-character strings
|
||||
data_ints = [b if isinstance(b, int) else ord(b) for b in data]
|
||||
|
||||
lines: List[str] = []
|
||||
size = len(data_ints)
|
||||
|
||||
for offset in range(0, size, 16):
|
||||
chunk = data_ints[offset : offset + 16]
|
||||
hex_values = " ".join(f"{b:02x}" for b in chunk)
|
||||
# pad hex_values to 16*3-1 = 47 chars (two hex digits + space)
|
||||
hex_values = hex_values.ljust(16 * 3 - 1)
|
||||
ascii_values = "".join(chr(b) if 32 <= b <= 126 else "." for b in chunk)
|
||||
lines.append(f"{offset:08x} {hex_values} |{ascii_values}|")
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -300,7 +300,7 @@ class AMDSMIParser(argparse.ArgumentParser):
|
||||
return CheckOutputFilePath
|
||||
|
||||
|
||||
def _check_input_file_path(self):
|
||||
def _check_cper_file_path(self):
|
||||
""" Argument action validator:
|
||||
Returns a path to a file from the input file path provided.
|
||||
If the file doesn't exist or is empty raise error
|
||||
@@ -310,8 +310,7 @@ class AMDSMIParser(argparse.ArgumentParser):
|
||||
def __call__(self, parser, args, values, option_string=None):
|
||||
path = Path(values)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(
|
||||
errno.ENOENT, os.strerror(errno.ENOENT), values)
|
||||
raise FileNotFoundError(f"CPER file could not be read. Make sure the path '{path}' is correct. ")
|
||||
|
||||
if path.is_dir():
|
||||
raise argparse.ArgumentTypeError(
|
||||
@@ -1413,12 +1412,13 @@ class AMDSMIParser(argparse.ArgumentParser):
|
||||
|
||||
# Help text for RAS arguments
|
||||
cper_help = "Trigger CPER data retrieval"
|
||||
|
||||
afid_help = "Generate an AFID (AMD Field ID) using CPER record, which is similar to XID."
|
||||
severity_choices = ["nonfatal-uncorrected", "fatal", "nonfatal-corrected", "all"]
|
||||
severity_choices_str = ", ".join(severity_choices)
|
||||
severity_help = f"Set the SEVERITY filters from the following:\n {severity_choices_str}"
|
||||
folder_help = "Folder to dump CPER report files"
|
||||
file_limit_help = "Maximum number of entries per output file"
|
||||
cper_file_help = "Full path of the cper record file to generate the AFID"
|
||||
follow_help = "Continuously monitor for new entries"
|
||||
|
||||
ras_parser = subparsers.add_parser("ras", help=ras_help, description=ras_description)
|
||||
@@ -1427,10 +1427,12 @@ class AMDSMIParser(argparse.ArgumentParser):
|
||||
ras_parser.set_defaults(func=func)
|
||||
|
||||
# Required flags and arguments:
|
||||
ras_parser.add_argument("--cper", action="store_true", required=True, help=cper_help)
|
||||
ras_parser.add_argument("--cper", action="store_true", required=False, help=cper_help)
|
||||
ras_parser.add_argument("--afid", action="store_true", required=False, help=afid_help)
|
||||
ras_parser.add_argument("--severity", type=str.lower, nargs='+', default=['all'], help=severity_help, choices=severity_choices, metavar='SEVERITY')
|
||||
ras_parser.add_argument("--folder", type=str, action=self._check_folder_path(), default=False, help=folder_help)
|
||||
ras_parser.add_argument("--file_limit", type=self._positive_int, action='store', default=1000, help=file_limit_help)
|
||||
ras_parser.add_argument("--cper_file", action=self._check_cper_file_path(), metavar="CPER_FILE", help=cper_file_help)
|
||||
ras_parser.add_argument("--follow", action="store_true", default=False, help=follow_help)
|
||||
|
||||
# Add common modifiers and device selection arguments.
|
||||
|
||||
Reference in New Issue
Block a user