From f969495667d4e917eea65558da929dd9cb98b2f4 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Wed, 23 Sep 2026 11:21:44 +0530 Subject: [PATCH 01/10] feature: Add pyzes support for info log APIs Add python bindings, unit tests, black box test coverage and README entries for: 1. zesDriverEnumInfoLogsExt 2. zesInfoLogGetPropertiesExt 3. zesInfoLogCreateInstanceExt 4. zesInfoLogInstanceReadWithMetadataExt 5. zesInfoLogInstancePeekWithMetadataExt 6. zesInfoLogInstanceDeleteExt Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 7 + .../sysman/python/source/pyzes/__init__.py | 269 +++++++++++++ .../pyzes/examples/pyzes_black_box_test.py | 305 +++++++++++++++ .../python/test/unit_tests/test_info_log.py | 359 ++++++++++++++++++ 4 files changed, 940 insertions(+) create mode 100644 bindings/sysman/python/test/unit_tests/test_info_log.py diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index df6192cb..f5217a73 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -212,6 +212,13 @@ SUPPORTED APIs | `zesRasGetConfigExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | | `zesRasSetConfigExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | | `zesRasClearStateExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | +| **Info Log Management** |-|-|-| +| `zesDriverEnumInfoLogsExt` | Info Log | 0.1.3 | None | +| `zesInfoLogGetPropertiesExt` | Info Log | 0.1.3 | None | +| `zesInfoLogCreateInstanceExt` | Info Log | 0.1.3 | None | +| `zesInfoLogInstanceReadWithMetadataExt` | Info Log | 0.1.3 | Records returned are consumed | +| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.3 | Requires `isPeekDataSupported` | +| `zesInfoLogInstanceDeleteExt` | Info Log | 0.1.3 | None | RELEASE NOTES ------------- diff --git a/bindings/sysman/python/source/pyzes/__init__.py b/bindings/sysman/python/source/pyzes/__init__.py index e83b288b..4c9d42f9 100644 --- a/bindings/sysman/python/source/pyzes/__init__.py +++ b/bindings/sysman/python/source/pyzes/__init__.py @@ -152,6 +152,14 @@ class zes_ras_handle_t(c_void_p): pass +class zes_info_log_handle_t(c_void_p): + pass + + +class zes_info_log_instance_handle_t(c_void_p): + pass + + ## ze_bool_t = c_uint8 @@ -319,6 +327,23 @@ class zes_ras_handle_t(c_void_p): ZES_RAS_ERROR_CATEGORY_EXP_SOC_INTERNAL_ERRORS = 12 ZES_RAS_ERROR_CATEGORY_EXP_FORCE_UINT32 = 0x7FFFFFFF +## Info log enums ## +zes_info_log_type_ext_t = c_int32 +ZES_INFO_LOG_TYPE_EXT_DEVICE = 0 +ZES_INFO_LOG_TYPE_EXT_FORCE_UINT32 = 0x7FFFFFFF + +zes_info_log_format_ext_t = c_int32 +ZES_INFO_LOG_FORMAT_EXT_CPER = 0 +ZES_INFO_LOG_FORMAT_EXT_FORCE_UINT32 = 0x7FFFFFFF + +zes_info_log_record_type_ext_t = c_int32 +ZES_INFO_LOG_RECORD_TYPE_EXT_UNKNOWN = 0 +ZES_INFO_LOG_RECORD_TYPE_EXT_INFORMATIONAL = 1 +ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED = 2 +ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_RECOVERABLE = 3 +ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL = 4 +ZES_INFO_LOG_RECORD_TYPE_EXT_FORCE_UINT32 = 0x7FFFFFFF + ze_result_t = c_int32 ZE_RESULT_SUCCESS = 0 ZE_RESULT_NOT_READY = 1 @@ -404,6 +429,12 @@ class zes_ras_handle_t(c_void_p): ZES_STRUCTURE_TYPE_RAS_PROPERTIES = 0xF ZES_STRUCTURE_TYPE_RAS_STATE_EXP2 = 0x00020015 ZES_STRUCTURE_TYPE_RAS_CONFIG_EXP = 0x00020016 +ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES = 0x00020020 # Info log properties +ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC = ( + 0x00020021 # Info log instance descriptor +) +ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT = 0x00020022 # Info log record metadata +ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT = 0x00020023 # Info log read status ## Core ze_device UUID struct ## @@ -836,6 +867,51 @@ class zes_ras_config_exp_t(_PrintableStructure): _fmt_ = {"threshold": "%d"} +## Info log structures ## +class zes_info_log_ext_properties_t(_PrintableStructure): + _fields_ = [ + ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES + ("pNext", c_void_p), + ("infoLogType", zes_info_log_type_ext_t), # type of info carried + ("infoLogFormat", zes_info_log_format_ext_t), # format of records + ("isNamedInstanceSupported", ze_bool_t), # named instances supported + ("isPeekDataSupported", ze_bool_t), # peek without consuming supported + ] + + +class zes_info_log_instance_ext_desc_t(_PrintableStructure): + _fields_ = [ + ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC + ("pNext", c_void_p), + ("pBufferSizeInKb", POINTER(c_uint32)), # optional buffer size in KB + ] + + +class zes_info_log_metadata_ext_t(_PrintableStructure): + _fields_ = [ + ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT + ("pNext", c_void_p), + ("address", zes_pci_address_t), # PCI address of originating device + ("uuid", zes_uuid_t), # UUID of originating device + ("timestamp", c_uint64), # record time in nanoseconds + ("lengthOfData", c_uint32), # record length in bytes + ("offset", c_uint32), # record byte offset in data buffer + ("recordType", zes_info_log_record_type_ext_t), # record type + ] + _fmt_ = {"timestamp": "%d ns", "lengthOfData": "%d bytes"} + + +class zes_info_log_read_status_ext_t(_PrintableStructure): + _fields_ = [ + ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + ("pNext", c_void_p), + ("droppedRecordCount", c_int64), # dropped records, -1 if unknown + ("consumedDataSize", c_uint64), # bytes read out of the instance + ("hasDataToRead", ze_bool_t), # records remain to be read + ] + _fmt_ = {"consumedDataSize": "%d bytes"} + + ## Function access ## _getFunctionPointerList: Dict[str, Any] = dict() @@ -1775,3 +1851,196 @@ def zesRasClearStateExp(hRas, category): retVal = funcPtr(hRas, category) return retVal + + +## Info log functions ## + + +def zesDriverEnumInfoLogsExt(hDriver, pCount, phInfoLogs): + """Wraps API: + ze_result_t zesDriverEnumInfoLogsExt( + zes_driver_handle_t hDriver, + uint32_t* pCount, + zes_info_log_handle_t* phInfoLogs) + + Parameters: + hDriver: driver handle + pCount: POINTER(c_uint32) + phInfoLogs: POINTER(zes_info_log_handle_t) or None + """ + funcPtr = getFunctionPointerList("zesDriverEnumInfoLogsExt") + funcPtr.argtypes = [ + zes_driver_handle_t, + POINTER(c_uint32), + POINTER(zes_info_log_handle_t), + ] + funcPtr.restype = ze_result_t + retVal = funcPtr(hDriver, pCount, phInfoLogs) + return retVal + + +def zesInfoLogGetPropertiesExt(hInfoLog, pProperties): + """Wraps API: + ze_result_t zesInfoLogGetPropertiesExt( + zes_info_log_handle_t hInfoLog, + zes_info_log_ext_properties_t* pProperties) + + Parameters: + hInfoLog: info log handle + pProperties: POINTER(zes_info_log_ext_properties_t) - properties structure to fill + Returns: + ze_result_t - return code only, properties are filled into pProperties + """ + funcPtr = getFunctionPointerList("zesInfoLogGetPropertiesExt") + funcPtr.argtypes = [zes_info_log_handle_t, POINTER(zes_info_log_ext_properties_t)] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hInfoLog, pProperties) + return retVal + + +def zesInfoLogCreateInstanceExt(hInfoLog, pInstanceName, pDesc, phInfoLogInstance): + """Wraps API: + ze_result_t zesInfoLogCreateInstanceExt( + zes_info_log_handle_t hInfoLog, + const char* pInstanceName, + zes_info_log_instance_ext_desc_t* pDesc, + zes_info_log_instance_handle_t* phInfoLogInstance) + + Parameters: + hInfoLog: info log handle + pInstanceName: bytes or None - name of the instance; None uses the default buffer + pDesc: POINTER(zes_info_log_instance_ext_desc_t) - instance descriptor + phInfoLogInstance: POINTER(zes_info_log_instance_handle_t) - created instance handle + Returns: + ze_result_t - return code only, instance handle is filled into phInfoLogInstance + """ + funcPtr = getFunctionPointerList("zesInfoLogCreateInstanceExt") + funcPtr.argtypes = [ + zes_info_log_handle_t, + c_char_p, + POINTER(zes_info_log_instance_ext_desc_t), + POINTER(zes_info_log_instance_handle_t), + ] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hInfoLog, pInstanceName, pDesc, phInfoLogInstance) + return retVal + + +def zesInfoLogInstanceReadWithMetadataExt( + hInfoLogInstance, timeout, pSize, pBuffer, pRecordCount, pDescriptors, pReadStatus +): + """Wraps API: + ze_result_t zesInfoLogInstanceReadWithMetadataExt( + zes_info_log_instance_handle_t hInfoLogInstance, + uint64_t timeout, + uint32_t* pSize, + uint8_t* pBuffer, + uint32_t* pRecordCount, + zes_info_log_metadata_ext_t* pDescriptors, + zes_info_log_read_status_ext_t* pReadStatus) + + Records returned are consumed and are not returned again. + + Parameters: + hInfoLogInstance: info log collection instance handle + timeout: c_uint64 - maximum search time in milliseconds + pSize: POINTER(c_uint32) - size of pBuffer in bytes; 0 makes this a query call + pBuffer: POINTER(c_uint8) or None - buffer for record data + pRecordCount: POINTER(c_uint32) - number of elements in pDescriptors; 0 makes this a query call + pDescriptors: POINTER(zes_info_log_metadata_ext_t) or None - metadata for each record + pReadStatus: POINTER(zes_info_log_read_status_ext_t) or None - status of this call + Returns: + ze_result_t - return code only, records are filled into pBuffer and pDescriptors + """ + funcPtr = getFunctionPointerList("zesInfoLogInstanceReadWithMetadataExt") + funcPtr.argtypes = [ + zes_info_log_instance_handle_t, + c_uint64, + POINTER(c_uint32), + POINTER(c_uint8), + POINTER(c_uint32), + POINTER(zes_info_log_metadata_ext_t), + POINTER(zes_info_log_read_status_ext_t), + ] + funcPtr.restype = ze_result_t + + retVal = funcPtr( + hInfoLogInstance, + timeout, + pSize, + pBuffer, + pRecordCount, + pDescriptors, + pReadStatus, + ) + return retVal + + +def zesInfoLogInstancePeekWithMetadataExt( + hInfoLogInstance, timeout, pSize, pBuffer, pRecordCount, pDescriptors, pReadStatus +): + """Wraps API: + ze_result_t zesInfoLogInstancePeekWithMetadataExt( + zes_info_log_instance_handle_t hInfoLogInstance, + uint64_t timeout, + uint32_t* pSize, + uint8_t* pBuffer, + uint32_t* pRecordCount, + zes_info_log_metadata_ext_t* pDescriptors, + zes_info_log_read_status_ext_t* pReadStatus) + + Records returned are not consumed. + + Parameters: + hInfoLogInstance: info log collection instance handle + timeout: c_uint64 - maximum search time in milliseconds + pSize: POINTER(c_uint32) - size of pBuffer in bytes; 0 makes this a query call + pBuffer: POINTER(c_uint8) or None - buffer for record data + pRecordCount: POINTER(c_uint32) - number of elements in pDescriptors; 0 makes this a query call + pDescriptors: POINTER(zes_info_log_metadata_ext_t) or None - metadata for each record + pReadStatus: POINTER(zes_info_log_read_status_ext_t) or None - status of this call + Returns: + ze_result_t - return code only, records are filled into pBuffer and pDescriptors + """ + funcPtr = getFunctionPointerList("zesInfoLogInstancePeekWithMetadataExt") + funcPtr.argtypes = [ + zes_info_log_instance_handle_t, + c_uint64, + POINTER(c_uint32), + POINTER(c_uint8), + POINTER(c_uint32), + POINTER(zes_info_log_metadata_ext_t), + POINTER(zes_info_log_read_status_ext_t), + ] + funcPtr.restype = ze_result_t + + retVal = funcPtr( + hInfoLogInstance, + timeout, + pSize, + pBuffer, + pRecordCount, + pDescriptors, + pReadStatus, + ) + return retVal + + +def zesInfoLogInstanceDeleteExt(hInfoLogInstance): + """Wraps API: + ze_result_t zesInfoLogInstanceDeleteExt( + zes_info_log_instance_handle_t hInfoLogInstance) + + Parameters: + hInfoLogInstance: info log collection instance handle to delete + Returns: + ze_result_t - return code only + """ + funcPtr = getFunctionPointerList("zesInfoLogInstanceDeleteExt") + funcPtr.argtypes = [zes_info_log_instance_handle_t] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hInfoLogInstance) + return retVal diff --git a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py index edbe5ab0..5b67bafc 100755 --- a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py +++ b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py @@ -350,6 +350,36 @@ def get_ras_error_category_string(category): return category_map.get(category, f"UNKNOWN_RAS_ERROR_CATEGORY_{category}") +def get_info_log_type_string(info_log_type): + """Convert info log type enum to string""" + type_map = { + pz.ZES_INFO_LOG_TYPE_EXT_DEVICE: "ZES_INFO_LOG_TYPE_EXT_DEVICE", + } + return type_map.get(info_log_type, f"UNKNOWN_INFO_LOG_TYPE_{info_log_type}") + + +def get_info_log_format_string(info_log_format): + """Convert info log format enum to string""" + format_map = { + pz.ZES_INFO_LOG_FORMAT_EXT_CPER: "ZES_INFO_LOG_FORMAT_EXT_CPER", + } + return format_map.get(info_log_format, f"UNKNOWN_INFO_LOG_FORMAT_{info_log_format}") + + +def get_info_log_record_type_string(record_type): + """Convert info log record type enum to string""" + record_type_map = { + pz.ZES_INFO_LOG_RECORD_TYPE_EXT_UNKNOWN: "ZES_INFO_LOG_RECORD_TYPE_EXT_UNKNOWN", + pz.ZES_INFO_LOG_RECORD_TYPE_EXT_INFORMATIONAL: "ZES_INFO_LOG_RECORD_TYPE_EXT_INFORMATIONAL", + pz.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED: "ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED", + pz.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_RECOVERABLE: "ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_RECOVERABLE", + pz.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL: "ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL", + } + return record_type_map.get( + record_type, f"UNKNOWN_INFO_LOG_RECORD_TYPE_{record_type}" + ) + + def is_root_user(): """Return whether the current user has root privileges on platforms that support it""" geteuid = getattr(os, "geteuid", None) @@ -1026,6 +1056,232 @@ def test_ras_module(device_handle, device_index, set_threshold=None, clear_state return True +INFO_LOG_READ_TIMEOUT_MS = 1000 + + +def collect_info_log_records( + collect_fn, api_name, instance_handle, timeout, info_log_index +): + """Query and then collect info log records with the given read or peek function. + + Returns (record_count, size, buffer, descriptors, status) or None on failure. + """ + size = c_uint32(0) + record_count = c_uint32(0) + rc = collect_fn( + instance_handle, timeout, byref(size), None, byref(record_count), None, None + ) + if not check_rc(f"{api_name}(info log {info_log_index}, query)", rc): + return None + + print_verbose( + f" {api_name} query: {record_count.value} record(s), {size.value} bytes" + ) + if size.value == 0 or record_count.value == 0: + return 0, 0, None, None, None + + buffer = (c_uint8 * size.value)() + descriptors = (pz.zes_info_log_metadata_ext_t * record_count.value)() + for j in range(record_count.value): + descriptors[j].stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT + descriptors[j].pNext = None + + status = pz.zes_info_log_read_status_ext_t() + status.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + status.pNext = None + + rc = collect_fn( + instance_handle, + timeout, + byref(size), + buffer, + byref(record_count), + descriptors, + byref(status), + ) + if rc == pz.ZE_RESULT_WARNING_DROPPED_DATA: + print_verbose(f" Dropped Records: {status.droppedRecordCount}") + elif not check_rc(f"{api_name}(info log {info_log_index}, data)", rc): + return None + + return record_count.value, size.value, buffer, descriptors, status + + +def print_info_log_records(action, record_count, size, descriptors, status): + """Print the metadata of collected info log records and the read status""" + print_verbose(f" {action} {record_count} record(s), {size} bytes:") + for j in range(record_count): + record = descriptors[j] + print_verbose(f" Record {j}:") + print_verbose( + f" Type: {get_info_log_record_type_string(record.recordType)}" + ) + print_verbose( + f" BDF: {record.address.domain:04X}:{record.address.bus:02X}:" + f"{record.address.device:02X}.{record.address.function:X}" + ) + print_verbose(f" Timestamp: {record.timestamp} ns") + print_verbose(f" Offset: {record.offset}") + print_verbose(f" Length: {record.lengthOfData} bytes") + print_verbose(f" Consumed Data Size: {status.consumedDataSize} bytes") + print_verbose(f" Has Data To Read: {bool(status.hasDataToRead)}") + + +def test_info_log_module( + driver_handle, + driver_index, + instance_name=None, + buffer_size_kb=None, + timeout=INFO_LOG_READ_TIMEOUT_MS, + read_records=False, +): + """Test info log enumeration, properties, and collection instance operations""" + print(f"\n---- Driver {driver_index} Info Log Test ----") + + info_log_count = c_uint32(0) + rc = pz.zesDriverEnumInfoLogsExt(driver_handle, byref(info_log_count), None) + if not check_rc(f"zesDriverEnumInfoLogsExt(driver {driver_index}, count)", rc): + return False + + if info_log_count.value == 0: + print_verbose("No info logs found on this driver") + return True + + print_verbose(f"Found {info_log_count.value} info log(s)") + + InfoLogArray = pz.zes_info_log_handle_t * info_log_count.value + info_log_handles = InfoLogArray() + + rc = pz.zesDriverEnumInfoLogsExt( + driver_handle, byref(info_log_count), info_log_handles + ) + if not check_rc(f"zesDriverEnumInfoLogsExt(driver {driver_index}, handles)", rc): + return False + + for i in range(info_log_count.value): + print_verbose(f"\n Info Log {i}:") + + props = pz.zes_info_log_ext_properties_t() + props.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES + props.pNext = None + + rc = pz.zesInfoLogGetPropertiesExt(info_log_handles[i], byref(props)) + if not check_rc(f"zesInfoLogGetPropertiesExt(info log {i})", rc): + continue + + print_verbose(f" Type: {get_info_log_type_string(props.infoLogType)}") + print_verbose(f" Format: {get_info_log_format_string(props.infoLogFormat)}") + print_verbose( + f" Named Instance Supported: {bool(props.isNamedInstanceSupported)}" + ) + print_verbose(f" Peek Data Supported: {bool(props.isPeekDataSupported)}") + + if instance_name is not None and not props.isNamedInstanceSupported: + print_verbose( + f" Skipping named instance '{instance_name}' since named instances are not supported" + ) + continue + + # Unset options use the default buffer and default buffer size + desc = pz.zes_info_log_instance_ext_desc_t() + desc.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC + desc.pNext = None + buffer_size = None + if buffer_size_kb is not None: + buffer_size = c_uint32(buffer_size_kb) + desc.pBufferSizeInKb = pointer(buffer_size) + else: + desc.pBufferSizeInKb = None + + name = instance_name.encode() if instance_name is not None else None + instance_handle = pz.zes_info_log_instance_handle_t() + rc = pz.zesInfoLogCreateInstanceExt( + info_log_handles[i], name, byref(desc), byref(instance_handle) + ) + if rc != pz.ZE_RESULT_SUCCESS: + print_verbose( + f" Collection Instance: Not available ({get_result_string(rc)})" + ) + continue + + label = ( + f"'{instance_name}'" + if instance_name is not None + else "on the default buffer" + ) + print_verbose(f" Created collection instance {label} successfully") + if buffer_size is not None: + print_verbose( + f" Buffer Size: requested {buffer_size_kb} KB, applied {buffer_size.value} KB" + ) + + peeked_count = None + if props.isPeekDataSupported: + peeked = collect_info_log_records( + pz.zesInfoLogInstancePeekWithMetadataExt, + "zesInfoLogInstancePeekWithMetadataExt", + instance_handle, + timeout, + i, + ) + if peeked is not None: + peeked_count = peeked[0] + if peeked_count > 0: + print_info_log_records( + "Peeked", peeked[0], peeked[1], peeked[3], peeked[4] + ) + + if read_records: + # Records are consumed, so keep reading while the driver reports more data + read_count = 0 + while True: + read = collect_info_log_records( + pz.zesInfoLogInstanceReadWithMetadataExt, + "zesInfoLogInstanceReadWithMetadataExt", + instance_handle, + timeout, + i, + ) + if read is None or read[0] == 0: + break + read_count += read[0] + print_info_log_records("Read", read[0], read[1], read[3], read[4]) + if not read[4].hasDataToRead: + break + + print_verbose(f" Total Records Read: {read_count}") + if peeked_count is not None and peeked_count != read_count: + print_verbose( + f" Note: peek returned {peeked_count} record(s) and read returned {read_count}, " + "records may have arrived between the calls" + ) + else: + # Only a query call is made, so records in the shared default buffer are not consumed + size = c_uint32(0) + record_count = c_uint32(0) + rc = pz.zesInfoLogInstanceReadWithMetadataExt( + instance_handle, + timeout, + byref(size), + None, + byref(record_count), + None, + None, + ) + if check_rc( + f"zesInfoLogInstanceReadWithMetadataExt(info log {i}, query)", rc + ): + print_verbose( + f" Read Query: {record_count.value} record(s), {size.value} bytes" + ) + + rc = pz.zesInfoLogInstanceDeleteExt(instance_handle) + if check_rc(f"zesInfoLogInstanceDeleteExt(info log {i})", rc): + print_verbose(" Deleted collection instance successfully") + + return True + + def test_memory_modules(device_handle, device_index): """Test memory module enumeration and operations""" print(f"\n---- Device {device_index} Memory Modules Test ----") @@ -1583,6 +1839,9 @@ def run_all_tests(): test_ras_module(devices[device_idx], device_idx) + # Test info logs (driver scoped) + test_info_log_module(drivers[driver_idx], driver_idx) + print("\n=== Test Completed ===") return True @@ -1604,6 +1863,9 @@ def main(): %(prog)s -re # RAS tests only %(prog)s -re --set-threshold 5 # RAS tests, set then restore threshold 5 (root) %(prog)s -re --clear-ras-state # RAS tests, clear all error counters (root) + %(prog)s -l # Info log tests only + %(prog)s -l --instance-read --timeout 5000 # Info log tests, peek then consume all records + %(prog)s -l --instance mylog --buffer-size 1024 # Info log tests on a named 1 MB collection instance %(prog)s -h # Show help message""", formatter_class=argparse.RawDescriptionHelpFormatter, ) @@ -1655,10 +1917,40 @@ def main(): action="store_true", help="With -re, clear all RAS error counters and print them before and after (requires root)", ) + parser.add_argument( + "-l", "--infolog", action="store_true", help="Run only info log tests" + ) + parser.add_argument( + "--instance", + metavar="NAME", + help="With -l, collect into a named instance instead of the default buffer", + ) + parser.add_argument( + "--buffer-size", + type=int, + metavar="KB", + help="With -l, request a collection buffer size in kilobytes (0 reports the default size)", + ) + parser.add_argument( + "--timeout", + type=int, + default=INFO_LOG_READ_TIMEOUT_MS, + metavar="MS", + help=f"With -l, maximum time in milliseconds to search for records, default is {INFO_LOG_READ_TIMEOUT_MS}", + ) + parser.add_argument( + "--instance-read", + action="store_true", + help="With -l, read and consume all collected records after peeking them", + ) args = parser.parse_args() if args.set_threshold is not None and not 0 <= args.set_threshold <= 2**64 - 1: parser.error("--set-threshold must be an integer between 0 and 2^64 - 1") + if args.buffer_size is not None and args.buffer_size < 0: + parser.error("--buffer-size must be a non-negative integer") + if args.timeout < 0: + parser.error("--timeout must be a non-negative integer") # Check if any specific test is requested specific_test = ( @@ -1671,6 +1963,7 @@ def main(): or args.temperature or args.engine or args.rasexp + or args.infolog or args.all ) @@ -1697,6 +1990,18 @@ def main(): print("No devices available for testing") return 1 + # Info logs are driver scoped, so run them once per driver + if args.infolog: + for driver_idx in range(driver_count): + test_info_log_module( + drivers[driver_idx], + driver_idx, + instance_name=args.instance, + buffer_size_kb=args.buffer_size, + timeout=args.timeout, + read_records=args.instance_read, + ) + # Run selected tests on all devices for device_idx in range(device_count): if getattr(args, "global", False): diff --git a/bindings/sysman/python/test/unit_tests/test_info_log.py b/bindings/sysman/python/test/unit_tests/test_info_log.py new file mode 100644 index 00000000..7947cd24 --- /dev/null +++ b/bindings/sysman/python/test/unit_tests/test_info_log.py @@ -0,0 +1,359 @@ +## +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: MIT +# +## + +import os +import sys +import unittest +from ctypes import * +from unittest.mock import MagicMock, patch + +# Add the source directory to Python path so we can import pyzes +script_dir = os.path.dirname(os.path.abspath(__file__)) +source_dir = os.path.join(script_dir, "..", "..", "source") +source_dir = os.path.abspath(source_dir) +if source_dir not in sys.path: + sys.path.insert(0, source_dir) + + +@patch("pyzes.getFunctionPointerList") +class TestInfoLogFunctions(unittest.TestCase): + def setUp(self): + import pyzes + + self.pyzes = pyzes + + def test_GivenValidDriverHandleWhenCallingZesDriverEnumInfoLogsExtThenCallSucceedsWithValidCount( + self, mock_get_func + ): + mock_count = 1 + + def mock_enum_info_logs(driver_handle, count_ptr, handles_ptr): + count_ptr._obj.value = mock_count + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_enum_info_logs) + mock_get_func.return_value = mock_func + + driver_handle = self.pyzes.zes_driver_handle_t() + count = c_uint32(0) + + result = self.pyzes.zesDriverEnumInfoLogsExt(driver_handle, byref(count), None) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(count.value, mock_count) + mock_get_func.assert_called_with("zesDriverEnumInfoLogsExt") + mock_func.assert_called_once() + + def test_GivenValidInfoLogHandleWhenCallingZesInfoLogGetPropertiesExtThenCallSucceedsWithValidProperties( + self, mock_get_func + ): + def mock_get_properties(info_log_handle, properties_ptr): + properties = properties_ptr._obj + properties.infoLogType = self.pyzes.ZES_INFO_LOG_TYPE_EXT_DEVICE + properties.infoLogFormat = self.pyzes.ZES_INFO_LOG_FORMAT_EXT_CPER + properties.isNamedInstanceSupported = 1 + properties.isPeekDataSupported = 1 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_get_properties) + mock_get_func.return_value = mock_func + + info_log_handle = self.pyzes.zes_info_log_handle_t() + properties = self.pyzes.zes_info_log_ext_properties_t() + properties.stype = self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES + + result = self.pyzes.zesInfoLogGetPropertiesExt( + info_log_handle, byref(properties) + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual( + properties.infoLogType, self.pyzes.ZES_INFO_LOG_TYPE_EXT_DEVICE + ) + self.assertEqual( + properties.infoLogFormat, self.pyzes.ZES_INFO_LOG_FORMAT_EXT_CPER + ) + self.assertEqual(properties.isNamedInstanceSupported, 1) + self.assertEqual(properties.isPeekDataSupported, 1) + mock_get_func.assert_called_with("zesInfoLogGetPropertiesExt") + mock_func.assert_called_once() + + def test_GivenValidInfoLogHandleWhenCallingZesInfoLogCreateInstanceExtThenCallSucceedsWithInstanceHandle( + self, mock_get_func + ): + def mock_create_instance(info_log_handle, name, desc_ptr, instance_ptr): + self.assertEqual(name, b"pyzes_instance") + desc = desc_ptr._obj + self.assertEqual( + desc.stype, self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC + ) + # Driver reports the buffer size it actually applied + desc.pBufferSizeInKb[0] = 64 + instance_ptr._obj.value = 0x1234 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_create_instance) + mock_get_func.return_value = mock_func + + info_log_handle = self.pyzes.zes_info_log_handle_t() + buffer_size_kb = c_uint32(0) + desc = self.pyzes.zes_info_log_instance_ext_desc_t() + desc.stype = self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC + desc.pBufferSizeInKb = pointer(buffer_size_kb) + instance_handle = self.pyzes.zes_info_log_instance_handle_t() + + result = self.pyzes.zesInfoLogCreateInstanceExt( + info_log_handle, b"pyzes_instance", byref(desc), byref(instance_handle) + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(buffer_size_kb.value, 64) + self.assertEqual(instance_handle.value, 0x1234) + mock_get_func.assert_called_with("zesInfoLogCreateInstanceExt") + mock_func.assert_called_once() + + def test_GivenValidInstanceHandleWhenCallingZesInfoLogInstanceReadWithMetadataExtWithZeroSizeThenQueryReturnsSizeAndRecordCount( + self, mock_get_func + ): + def mock_query( + instance_handle, + timeout, + size_ptr, + buffer, + count_ptr, + descriptors, + status_ptr, + ): + self.assertEqual(timeout, 1000) + self.assertIsNone(buffer) + self.assertIsNone(descriptors) + size_ptr._obj.value = 512 + count_ptr._obj.value = 2 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_query) + mock_get_func.return_value = mock_func + + instance_handle = self.pyzes.zes_info_log_instance_handle_t() + size = c_uint32(0) + record_count = c_uint32(0) + + result = self.pyzes.zesInfoLogInstanceReadWithMetadataExt( + instance_handle, 1000, byref(size), None, byref(record_count), None, None + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(size.value, 512) + self.assertEqual(record_count.value, 2) + mock_get_func.assert_called_with("zesInfoLogInstanceReadWithMetadataExt") + mock_func.assert_called_once() + + def test_GivenValidInstanceHandleWhenCallingZesInfoLogInstanceReadWithMetadataExtWithBuffersThenRecordsAndStatusAreReturned( + self, mock_get_func + ): + def mock_read( + instance_handle, + timeout, + size_ptr, + buffer, + count_ptr, + descriptors, + status_ptr, + ): + buffer[0] = 0x43 + buffer[300] = 0x50 + descriptors[0].lengthOfData = 300 + descriptors[0].offset = 0 + descriptors[0].timestamp = 123456789 + descriptors[0].recordType = ( + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED + ) + descriptors[0].address.bus = 0x3A + descriptors[1].lengthOfData = 212 + descriptors[1].offset = 300 + descriptors[1].recordType = ( + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL + ) + status = status_ptr._obj + status.droppedRecordCount = 0 + status.consumedDataSize = 4096 + status.hasDataToRead = 0 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_read) + mock_get_func.return_value = mock_func + + instance_handle = self.pyzes.zes_info_log_instance_handle_t() + size = c_uint32(512) + record_count = c_uint32(2) + buffer = (c_uint8 * size.value)() + descriptors = (self.pyzes.zes_info_log_metadata_ext_t * record_count.value)() + for i in range(record_count.value): + descriptors[i].stype = self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT + status = self.pyzes.zes_info_log_read_status_ext_t() + status.stype = self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + + result = self.pyzes.zesInfoLogInstanceReadWithMetadataExt( + instance_handle, + 1000, + byref(size), + buffer, + byref(record_count), + descriptors, + byref(status), + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(buffer[0], 0x43) + self.assertEqual(buffer[300], 0x50) + self.assertEqual(descriptors[0].lengthOfData, 300) + self.assertEqual(descriptors[0].timestamp, 123456789) + self.assertEqual( + descriptors[0].recordType, + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED, + ) + self.assertEqual(descriptors[0].address.bus, 0x3A) + self.assertEqual(descriptors[1].offset, 300) + self.assertEqual( + descriptors[1].recordType, + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL, + ) + self.assertEqual(status.droppedRecordCount, 0) + self.assertEqual(status.consumedDataSize, 4096) + self.assertEqual(status.hasDataToRead, 0) + mock_get_func.assert_called_with("zesInfoLogInstanceReadWithMetadataExt") + mock_func.assert_called_once() + + def test_GivenValidInstanceHandleWhenCallingZesInfoLogInstancePeekWithMetadataExtWithZeroSizeThenQueryReturnsSizeAndRecordCount( + self, mock_get_func + ): + def mock_query( + instance_handle, + timeout, + size_ptr, + buffer, + count_ptr, + descriptors, + status_ptr, + ): + self.assertEqual(timeout, 1000) + self.assertIsNone(buffer) + self.assertIsNone(descriptors) + size_ptr._obj.value = 512 + count_ptr._obj.value = 2 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_query) + mock_get_func.return_value = mock_func + + instance_handle = self.pyzes.zes_info_log_instance_handle_t() + size = c_uint32(0) + record_count = c_uint32(0) + + result = self.pyzes.zesInfoLogInstancePeekWithMetadataExt( + instance_handle, 1000, byref(size), None, byref(record_count), None, None + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(size.value, 512) + self.assertEqual(record_count.value, 2) + mock_get_func.assert_called_with("zesInfoLogInstancePeekWithMetadataExt") + mock_func.assert_called_once() + + def test_GivenValidInstanceHandleWhenCallingZesInfoLogInstancePeekWithMetadataExtWithBuffersThenRecordsAndStatusAreReturned( + self, mock_get_func + ): + def mock_read( + instance_handle, + timeout, + size_ptr, + buffer, + count_ptr, + descriptors, + status_ptr, + ): + buffer[0] = 0x43 + buffer[300] = 0x50 + descriptors[0].lengthOfData = 300 + descriptors[0].offset = 0 + descriptors[0].timestamp = 123456789 + descriptors[0].recordType = ( + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED + ) + descriptors[0].address.bus = 0x3A + descriptors[1].lengthOfData = 212 + descriptors[1].offset = 300 + descriptors[1].recordType = ( + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL + ) + status = status_ptr._obj + status.droppedRecordCount = 0 + status.consumedDataSize = 4096 + status.hasDataToRead = 0 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_read) + mock_get_func.return_value = mock_func + + instance_handle = self.pyzes.zes_info_log_instance_handle_t() + size = c_uint32(512) + record_count = c_uint32(2) + buffer = (c_uint8 * size.value)() + descriptors = (self.pyzes.zes_info_log_metadata_ext_t * record_count.value)() + for i in range(record_count.value): + descriptors[i].stype = self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT + status = self.pyzes.zes_info_log_read_status_ext_t() + status.stype = self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + + result = self.pyzes.zesInfoLogInstancePeekWithMetadataExt( + instance_handle, + 1000, + byref(size), + buffer, + byref(record_count), + descriptors, + byref(status), + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(buffer[0], 0x43) + self.assertEqual(buffer[300], 0x50) + self.assertEqual(descriptors[0].lengthOfData, 300) + self.assertEqual(descriptors[0].timestamp, 123456789) + self.assertEqual( + descriptors[0].recordType, + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_CORRECTED, + ) + self.assertEqual(descriptors[0].address.bus, 0x3A) + self.assertEqual(descriptors[1].offset, 300) + self.assertEqual( + descriptors[1].recordType, + self.pyzes.ZES_INFO_LOG_RECORD_TYPE_EXT_ERROR_FATAL, + ) + self.assertEqual(status.droppedRecordCount, 0) + self.assertEqual(status.consumedDataSize, 4096) + self.assertEqual(status.hasDataToRead, 0) + mock_get_func.assert_called_with("zesInfoLogInstancePeekWithMetadataExt") + mock_func.assert_called_once() + + def test_GivenValidInstanceHandleWhenCallingZesInfoLogInstanceDeleteExtThenCallSucceeds( + self, mock_get_func + ): + mock_func = MagicMock(return_value=self.pyzes.ZE_RESULT_SUCCESS) + mock_get_func.return_value = mock_func + + instance_handle = self.pyzes.zes_info_log_instance_handle_t(0x1234) + + result = self.pyzes.zesInfoLogInstanceDeleteExt(instance_handle) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + mock_get_func.assert_called_with("zesInfoLogInstanceDeleteExt") + mock_func.assert_called_once_with(instance_handle) + + +if __name__ == "__main__": + unittest.main() From 355bf0d925d4b3ee6200c3487f42aa946b9b91eb Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Mon, 28 Sep 2026 14:18:06 +0530 Subject: [PATCH 02/10] fix: Align pyzes black box info log test with zello_sysman Report info log properties by default and collect records only with --instance-api, --instance-peek or --instance-read as root. --- .../pyzes/examples/pyzes_black_box_test.py | 651 +++++++++++++----- 1 file changed, 483 insertions(+), 168 deletions(-) diff --git a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py index 5b67bafc..f2fbd77d 100755 --- a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py +++ b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py @@ -1057,86 +1057,444 @@ def test_ras_module(device_handle, device_index, set_threshold=None, clear_state INFO_LOG_READ_TIMEOUT_MS = 1000 +INFO_LOG_RECORD_WAIT_MS = 5000 +INFO_LOG_POLL_INTERVAL_MS = 200 +INFO_LOG_MAX_DRAIN_CALLS = 16 +INFO_LOG_INSTANCE_NAME = "pyzes_black_box_infolog" -def collect_info_log_records( - collect_fn, api_name, instance_handle, timeout, info_log_index -): - """Query and then collect info log records with the given read or peek function. +class InfoLogChecks: + """Count and print the pass and fail results of the info log checks""" + + def __init__(self): + self.passed = 0 + self.failed = 0 + + def __call__(self, condition, description): + if condition: + print_verbose(f" [PASS] {description}") + self.passed += 1 + else: + print_verbose(f" [FAIL] {description}") + self.failed += 1 + + def print_summary(self): + print_verbose(f"{self.passed} check(s) passed, {self.failed} check(s) failed") + + +class InfoLogRecords: + """Records returned by one read or peek call""" + + def __init__(self): + self.size = 0 + self.record_count = 0 + self.buffer = None + self.descriptors = None + self.status = None + + +def query_info_log_records(collect_fn, instance_handle, timeout): + """Query the records held by the instance without consuming them. - Returns (record_count, size, buffer, descriptors, status) or None on failure. + A call with *pSize and *pRecordCount zero on input is a query call for both read and peek. + Returns (rc, size, record_count). """ size = c_uint32(0) record_count = c_uint32(0) rc = collect_fn( instance_handle, timeout, byref(size), None, byref(record_count), None, None ) - if not check_rc(f"{api_name}(info log {info_log_index}, query)", rc): + return rc, size.value, record_count.value + + +def collect_info_log_records(collect_fn, api_name, instance_handle, timeout): + """Query the records held by the instance, then collect them in a second call. + + Returns an InfoLogRecords object, or None on failure. + """ + records = InfoLogRecords() + rc, records.size, records.record_count = query_info_log_records( + collect_fn, instance_handle, timeout + ) + if not check_rc(f"{api_name}(query)", rc): return None print_verbose( - f" {api_name} query: {record_count.value} record(s), {size.value} bytes" + f"{api_name}() query: {records.record_count} record(s), {records.size} byte(s) pending" ) - if size.value == 0 or record_count.value == 0: - return 0, 0, None, None, None - - buffer = (c_uint8 * size.value)() - descriptors = (pz.zes_info_log_metadata_ext_t * record_count.value)() + if records.size == 0 or records.record_count == 0: + records.size = 0 + records.record_count = 0 + return records + + size = c_uint32(records.size) + record_count = c_uint32(records.record_count) + records.buffer = (c_uint8 * size.value)() + records.descriptors = (pz.zes_info_log_metadata_ext_t * record_count.value)() for j in range(record_count.value): - descriptors[j].stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT - descriptors[j].pNext = None + records.descriptors[j].stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT + records.descriptors[j].pNext = None - status = pz.zes_info_log_read_status_ext_t() - status.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT - status.pNext = None + records.status = pz.zes_info_log_read_status_ext_t() + records.status.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + records.status.pNext = None rc = collect_fn( instance_handle, timeout, byref(size), - buffer, + records.buffer, byref(record_count), - descriptors, - byref(status), + records.descriptors, + byref(records.status), + ) + if rc != pz.ZE_RESULT_WARNING_DROPPED_DATA and not check_rc(f"{api_name}()", rc): + return None + + records.size = size.value + records.record_count = record_count.value + print_verbose( + f"{api_name}(): {records.record_count} record(s), {records.size} byte(s) returned" + ) + dropped = ( + records.status.droppedRecordCount + if rc == pz.ZE_RESULT_WARNING_DROPPED_DATA + else 0 + ) + if dropped == -1: + dropped = "unknown" + print_verbose( + f" readStatus.hasDataToRead = {bool(records.status.hasDataToRead)}, " + f"readStatus.droppedRecordCount = {dropped}, " + f"readStatus.consumedDataSize = {records.status.consumedDataSize} bytes" + ) + if records.status.hasDataToRead: + print_verbose( + f" Records remain on the instance, {api_name}() has to be called again" + ) + return records + + +def print_info_log_records(records, first_record_number): + """Print the metadata and the start of the data of collected info log records""" + print_verbose("-" * 80) + for j in range(records.record_count): + record = records.descriptors[j] + print_verbose(f"\nEvent #{first_record_number + j}:") + print_verbose(f" Timestamp: {record.timestamp} nano seconds") + print_verbose( + f" BDF: {record.address.domain:04x}:{record.address.bus:02x}:" + f"{record.address.device:02x}.{record.address.function:x}" + ) + print_verbose( + f" Platform ID: {'-'.join(f'{record.uuid.id[k]:02x}' for k in range(16))}" + ) + print_verbose( + f" Record Type: {get_info_log_record_type_string(record.recordType)}" + ) + print_verbose(f" Data Size: {record.lengthOfData} bytes") + print_verbose(f" Data Offset: {record.offset}") + start = record.offset + end = min(start + record.lengthOfData, records.size) + stop = min(end, start + 64) + data = bytes(records.buffer[start:stop]) + suffix = " ..." if end - start > 64 else "" + print_verbose(f" CPER Data: {data.hex(' ')}{suffix}") + print_verbose("-" * 80) + + +def drain_info_log_records( + collect_fn, api_name, instance_handle, timeout, first_record_number +): + """Keep collecting while the driver reports more data. Returns (records collected, success).""" + collected = 0 + for _ in range(INFO_LOG_MAX_DRAIN_CALLS): + records = collect_info_log_records( + collect_fn, api_name, instance_handle, timeout + ) + if records is None: + return collected, False + if records.record_count == 0: + break + print_info_log_records(records, first_record_number + collected) + collected += records.record_count + if not records.status.hasDataToRead: + break + return collected, True + + +def records_match(first, second): + """Check that the first record of two collections has the same data""" + if first.record_count == 0 or second.record_count == 0: + return False + a = first.descriptors[0] + b = second.descriptors[0] + if a.lengthOfData != b.lengthOfData: + return False + a_start, a_end = a.offset, a.offset + a.lengthOfData + b_start, b_end = b.offset, b.offset + b.lengthOfData + return bytes(first.buffer[a_start:a_end]) == bytes(second.buffer[b_start:b_end]) + + +def get_info_log_properties(info_log_handle): + """Return the properties of an info log, or None on failure""" + props = pz.zes_info_log_ext_properties_t() + props.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES + props.pNext = None + rc = pz.zesInfoLogGetPropertiesExt(info_log_handle, byref(props)) + if not check_rc("zesInfoLogGetPropertiesExt", rc): + return None + return props + + +def create_info_log_instance(info_log_handle, instance_name, buffer_size_kb): + """Create a collection instance, unset options use the default buffer and buffer size""" + desc = pz.zes_info_log_instance_ext_desc_t() + desc.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC + desc.pNext = None + buffer_size = None + if buffer_size_kb is not None: + buffer_size = c_uint32(buffer_size_kb) + desc.pBufferSizeInKb = pointer(buffer_size) + print_verbose(f" requested buffer size: {buffer_size_kb} KB") + else: + desc.pBufferSizeInKb = None + + name = instance_name.encode() if instance_name is not None else None + instance_handle = pz.zes_info_log_instance_handle_t() + rc = pz.zesInfoLogCreateInstanceExt( + info_log_handle, name, byref(desc), byref(instance_handle) ) - if rc == pz.ZE_RESULT_WARNING_DROPPED_DATA: - print_verbose(f" Dropped Records: {status.droppedRecordCount}") - elif not check_rc(f"{api_name}(info log {info_log_index}, data)", rc): + if not check_rc("zesInfoLogCreateInstanceExt", rc): return None - return record_count.value, size.value, buffer, descriptors, status + # The descriptor members are updated in place with the values which were actually applied + if buffer_size is not None: + print_verbose(f" applied buffer size: {buffer_size.value} KB") + return instance_handle -def print_info_log_records(action, record_count, size, descriptors, status): - """Print the metadata of collected info log records and the read status""" - print_verbose(f" {action} {record_count} record(s), {size} bytes:") - for j in range(record_count): - record = descriptors[j] - print_verbose(f" Record {j}:") +def test_info_log_instance_read_peek(info_log_handle, props, timeout): + """Peek twice, read and drain the records of a named instance and check the results""" + print("\n---- Info Log instance read and peek tests ----") + + if not props.isNamedInstanceSupported: print_verbose( - f" Type: {get_info_log_record_type_string(record.recordType)}" + "Named collection instances are not supported. Skipping the test." + ) + return + if not props.isPeekDataSupported: + print_verbose("Peek is not supported. Skipping the test.") + return + + instance_handle = create_info_log_instance( + info_log_handle, INFO_LOG_INSTANCE_NAME, None + ) + if instance_handle is None: + return + print_verbose(f"Created collection instance '{INFO_LOG_INSTANCE_NAME}'") + + # Only records generated after the instance was created are collected into it, so the + # instance is polled with the non-consuming query until it reports data + print_verbose( + f"\nWaiting up to {INFO_LOG_RECORD_WAIT_MS} ms for CPER records. Records are generated " + "when the uncorrectable RAS counters are read, for example with " + "'zello_sysman -L --instanceapi' " + "from another shell." + ) + peek_name = "zesInfoLogInstancePeekWithMetadataExt" + read_name = "zesInfoLogInstanceReadWithMetadataExt" + pending_size = 0 + pending_count = 0 + for _ in range(0, INFO_LOG_RECORD_WAIT_MS, INFO_LOG_POLL_INTERVAL_MS): + rc, pending_size, pending_count = query_info_log_records( + pz.zesInfoLogInstancePeekWithMetadataExt, instance_handle, timeout ) + if not check_rc(f"{peek_name}(query)", rc) or pending_count != 0: + break + time.sleep(INFO_LOG_POLL_INTERVAL_MS / 1000) + + if pending_count == 0: print_verbose( - f" BDF: {record.address.domain:04X}:{record.address.bus:02X}:" - f"{record.address.device:02X}.{record.address.function:X}" + f"No CPER record was collected within {INFO_LOG_RECORD_WAIT_MS} ms. " + "Skipping the read and peek verification." + ) + check_rc( + "zesInfoLogInstanceDeleteExt", + pz.zesInfoLogInstanceDeleteExt(instance_handle), + ) + return + print_verbose( + f"{pending_count} record(s), {pending_size} byte(s) are pending on the instance" + ) + + check = InfoLogChecks() + + print_verbose("\nFirst peek:") + first_peek = collect_info_log_records( + pz.zesInfoLogInstancePeekWithMetadataExt, peek_name, instance_handle, timeout + ) + check( + first_peek is not None and first_peek.record_count != 0, + "peek returned the pending records", + ) + if first_peek is None: + first_peek = InfoLogRecords() + if first_peek.record_count != 0: + print_info_log_records(first_peek, 1) + + print_verbose("\nSecond peek, on the same records:") + second_peek = collect_info_log_records( + pz.zesInfoLogInstancePeekWithMetadataExt, peek_name, instance_handle, timeout + ) + check( + second_peek is not None + and second_peek.record_count == first_peek.record_count + and second_peek.size == first_peek.size, + "a second peek reports the same records, peek does not consume", + ) + + print_verbose("\nRead, on the records the peek left in place:") + read = collect_info_log_records( + pz.zesInfoLogInstanceReadWithMetadataExt, read_name, instance_handle, timeout + ) + check( + read is not None and read.record_count >= first_peek.record_count, + "read returned at least the records the peek reported", + ) + if read is None: + read = InfoLogRecords() + if read.record_count != 0: + print_info_log_records(read, 1) + + check( + records_match(first_peek, read), + "the first record the read returned is byte identical to the peeked one", + ) + + if read.status is not None and read.status.hasDataToRead: + drained, success = drain_info_log_records( + pz.zesInfoLogInstanceReadWithMetadataExt, + read_name, + instance_handle, + timeout, + read.record_count + 1, ) - print_verbose(f" Timestamp: {record.timestamp} ns") - print_verbose(f" Offset: {record.offset}") - print_verbose(f" Length: {record.lengthOfData} bytes") - print_verbose(f" Consumed Data Size: {status.consumedDataSize} bytes") - print_verbose(f" Has Data To Read: {bool(status.hasDataToRead)}") + print_verbose(f"{drained} further record(s) were read to drain the instance") + check(success, "the reads which drained the remaining records succeeded") + + rc, pending_size, pending_count = query_info_log_records( + pz.zesInfoLogInstanceReadWithMetadataExt, instance_handle, timeout + ) + check( + rc == pz.ZE_RESULT_SUCCESS and pending_count == 0, + "the instance is drained after the read, nothing is pending", + ) + + print_verbose("") + check.print_summary() + + rc = pz.zesInfoLogInstanceDeleteExt(instance_handle) + if check_rc("zesInfoLogInstanceDeleteExt", rc): + print_verbose(f"Deleted collection instance '{INFO_LOG_INSTANCE_NAME}'") + + +def test_info_log_instance_collect( + info_log_handle, props, instance_name, buffer_size_kb, timeout, use_peek +): + """Peek or read the records collected into an instance and check the results""" + operation = "peek" if use_peek else "read" + print(f"\n---- Info Log instance {operation} tests ----") + + if use_peek and not props.isPeekDataSupported: + print_verbose("Peek is not supported. Skipping the test.") + return + if instance_name is not None and not props.isNamedInstanceSupported: + print_verbose( + "Named collection instances are not supported. Skipping the test." + ) + return + + print_verbose( + f"Collection instance: {instance_name if instance_name is not None else 'the default buffer'}" + ) + instance_handle = create_info_log_instance( + info_log_handle, instance_name, buffer_size_kb + ) + if instance_handle is None: + return + + if use_peek: + collect_fn = pz.zesInfoLogInstancePeekWithMetadataExt + api_name = "zesInfoLogInstancePeekWithMetadataExt" + else: + collect_fn = pz.zesInfoLogInstanceReadWithMetadataExt + api_name = "zesInfoLogInstanceReadWithMetadataExt" + + check = InfoLogChecks() + records = collect_info_log_records(collect_fn, api_name, instance_handle, timeout) + if records is not None and records.record_count == 0: + print_verbose( + "No record is pending on the instance. Records are generated when the " + "uncorrectable RAS counters are read, for example with 'zello_sysman -L --instanceapi'." + ) + elif records is not None: + print_info_log_records(records, 1) + if use_peek: + # The peek left the records in place, so the same totals must still be reported + rc, pending_size, pending_count = query_info_log_records( + collect_fn, instance_handle, timeout + ) + check( + rc == pz.ZE_RESULT_SUCCESS + and pending_count == records.record_count + and pending_size == records.size, + "the peeked records are still pending on the instance", + ) + else: + if records.status.hasDataToRead: + drained, success = drain_info_log_records( + collect_fn, + api_name, + instance_handle, + timeout, + records.record_count + 1, + ) + print_verbose( + f"{drained} further record(s) were read to drain the instance" + ) + check( + success, "the reads which drained the remaining records succeeded" + ) + rc, pending_size, pending_count = query_info_log_records( + collect_fn, instance_handle, timeout + ) + check( + rc == pz.ZE_RESULT_SUCCESS and pending_count == 0, + "the read consumed the records, nothing is pending", + ) + print_verbose("") + check.print_summary() + + rc = pz.zesInfoLogInstanceDeleteExt(instance_handle) + if check_rc("zesInfoLogInstanceDeleteExt", rc): + print_verbose("Deleted collection instance") def test_info_log_module( driver_handle, driver_index, + mode=None, instance_name=None, buffer_size_kb=None, timeout=INFO_LOG_READ_TIMEOUT_MS, - read_records=False, ): - """Test info log enumeration, properties, and collection instance operations""" - print(f"\n---- Driver {driver_index} Info Log Test ----") + """Test info log properties, and with a mode the collection instance operations. + + mode is None for properties only, "api" to verify peek and read on a named instance, + "peek" to peek or "read" to read the records of an instance. + """ + print(f"\n---- Driver {driver_index} Info Log properties tests ----") info_log_count = c_uint32(0) rc = pz.zesDriverEnumInfoLogsExt(driver_handle, byref(info_log_count), None) @@ -1144,10 +1502,10 @@ def test_info_log_module( return False if info_log_count.value == 0: - print_verbose("No info logs found on this driver") + print_verbose("Could not retrieve Info Log handles") return True - print_verbose(f"Found {info_log_count.value} info log(s)") + print_verbose(f"Found {info_log_count.value} info log handles..") InfoLogArray = pz.zes_info_log_handle_t * info_log_count.value info_log_handles = InfoLogArray() @@ -1158,127 +1516,58 @@ def test_info_log_module( if not check_rc(f"zesDriverEnumInfoLogsExt(driver {driver_index}, handles)", rc): return False + cper_handle = None + cper_props = None for i in range(info_log_count.value): - print_verbose(f"\n Info Log {i}:") - - props = pz.zes_info_log_ext_properties_t() - props.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES - props.pNext = None - - rc = pz.zesInfoLogGetPropertiesExt(info_log_handles[i], byref(props)) - if not check_rc(f"zesInfoLogGetPropertiesExt(info log {i})", rc): + props = get_info_log_properties(info_log_handles[i]) + if props is None: continue - - print_verbose(f" Type: {get_info_log_type_string(props.infoLogType)}") - print_verbose(f" Format: {get_info_log_format_string(props.infoLogFormat)}") print_verbose( - f" Named Instance Supported: {bool(props.isNamedInstanceSupported)}" + f"properties.infoLogType = {get_info_log_type_string(props.infoLogType)}" ) - print_verbose(f" Peek Data Supported: {bool(props.isPeekDataSupported)}") - - if instance_name is not None and not props.isNamedInstanceSupported: - print_verbose( - f" Skipping named instance '{instance_name}' since named instances are not supported" - ) - continue - - # Unset options use the default buffer and default buffer size - desc = pz.zes_info_log_instance_ext_desc_t() - desc.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC - desc.pNext = None - buffer_size = None - if buffer_size_kb is not None: - buffer_size = c_uint32(buffer_size_kb) - desc.pBufferSizeInKb = pointer(buffer_size) - else: - desc.pBufferSizeInKb = None - - name = instance_name.encode() if instance_name is not None else None - instance_handle = pz.zes_info_log_instance_handle_t() - rc = pz.zesInfoLogCreateInstanceExt( - info_log_handles[i], name, byref(desc), byref(instance_handle) + print_verbose( + f"properties.infoLogFormat = {get_info_log_format_string(props.infoLogFormat)}" ) - if rc != pz.ZE_RESULT_SUCCESS: - print_verbose( - f" Collection Instance: Not available ({get_result_string(rc)})" - ) - continue - - label = ( - f"'{instance_name}'" - if instance_name is not None - else "on the default buffer" + print_verbose( + f"properties.isNamedInstanceSupported = {bool(props.isNamedInstanceSupported)}" ) - print_verbose(f" Created collection instance {label} successfully") - if buffer_size is not None: - print_verbose( - f" Buffer Size: requested {buffer_size_kb} KB, applied {buffer_size.value} KB" - ) + print_verbose( + f"properties.isPeekDataSupported = {bool(props.isPeekDataSupported)}" + ) + if ( + cper_handle is None + and props.infoLogFormat == pz.ZES_INFO_LOG_FORMAT_EXT_CPER + ): + cper_handle = info_log_handles[i] + cper_props = props - peeked_count = None - if props.isPeekDataSupported: - peeked = collect_info_log_records( - pz.zesInfoLogInstancePeekWithMetadataExt, - "zesInfoLogInstancePeekWithMetadataExt", - instance_handle, - timeout, - i, - ) - if peeked is not None: - peeked_count = peeked[0] - if peeked_count > 0: - print_info_log_records( - "Peeked", peeked[0], peeked[1], peeked[3], peeked[4] - ) + if mode is None: + print_verbose( + "\nRecords are collected with --instance-api, --instance-peek or --instance-read" + ) + return True - if read_records: - # Records are consumed, so keep reading while the driver reports more data - read_count = 0 - while True: - read = collect_info_log_records( - pz.zesInfoLogInstanceReadWithMetadataExt, - "zesInfoLogInstanceReadWithMetadataExt", - instance_handle, - timeout, - i, - ) - if read is None or read[0] == 0: - break - read_count += read[0] - print_info_log_records("Read", read[0], read[1], read[3], read[4]) - if not read[4].hasDataToRead: - break - - print_verbose(f" Total Records Read: {read_count}") - if peeked_count is not None and peeked_count != read_count: - print_verbose( - f" Note: peek returned {peeked_count} record(s) and read returned {read_count}, " - "records may have arrived between the calls" - ) - else: - # Only a query call is made, so records in the shared default buffer are not consumed - size = c_uint32(0) - record_count = c_uint32(0) - rc = pz.zesInfoLogInstanceReadWithMetadataExt( - instance_handle, - timeout, - byref(size), - None, - byref(record_count), - None, - None, - ) - if check_rc( - f"zesInfoLogInstanceReadWithMetadataExt(info log {i}, query)", rc - ): - print_verbose( - f" Read Query: {record_count.value} record(s), {size.value} bytes" - ) + if not is_root_user(): + print_verbose( + "Not running as Root. Skipping the info log collection instance test." + ) + return True - rc = pz.zesInfoLogInstanceDeleteExt(instance_handle) - if check_rc(f"zesInfoLogInstanceDeleteExt(info log {i})", rc): - print_verbose(" Deleted collection instance successfully") + if cper_handle is None: + print_verbose("No info log handle reports the CPER format") + return True + if mode == "api": + test_info_log_instance_read_peek(cper_handle, cper_props, timeout) + else: + test_info_log_instance_collect( + cper_handle, + cper_props, + instance_name, + buffer_size_kb, + timeout, + use_peek=(mode == "peek"), + ) return True @@ -1863,9 +2152,9 @@ def main(): %(prog)s -re # RAS tests only %(prog)s -re --set-threshold 5 # RAS tests, set then restore threshold 5 (root) %(prog)s -re --clear-ras-state # RAS tests, clear all error counters (root) - %(prog)s -l # Info log tests only - %(prog)s -l --instance-read --timeout 5000 # Info log tests, peek then consume all records - %(prog)s -l --instance mylog --buffer-size 1024 # Info log tests on a named 1 MB collection instance + %(prog)s -l # Info log properties tests only + %(prog)s -l --instance-api # Info log tests, verify peek and read on a named collection instance + %(prog)s -l --instance-read --instance mylog --buffer-size 1024 # Info log tests, read the records of a named 1 MB collection instance %(prog)s -h # Show help message""", formatter_class=argparse.RawDescriptionHelpFormatter, ) @@ -1918,18 +2207,43 @@ def main(): help="With -re, clear all RAS error counters and print them before and after (requires root)", ) parser.add_argument( - "-l", "--infolog", action="store_true", help="Run only info log tests" + "-l", + "--infolog", + action="store_true", + help="Run only info log tests, reporting what each info log supports", + ) + instance_mode = parser.add_mutually_exclusive_group() + instance_mode.add_argument( + "--instance-api", + action="store_const", + const="api", + dest="instance_mode", + help="With -l, create a named collection instance, wait for CPER records, then verify peek and read (requires root)", + ) + instance_mode.add_argument( + "--instance-peek", + action="store_const", + const="peek", + dest="instance_mode", + help="With -l, create a collection instance and peek the records (requires root)", + ) + instance_mode.add_argument( + "--instance-read", + action="store_const", + const="read", + dest="instance_mode", + help="With -l, create a collection instance and read the records (requires root)", ) parser.add_argument( "--instance", metavar="NAME", - help="With -l, collect into a named instance instead of the default buffer", + help="With --instance-peek/--instance-read, collect into a named instance instead of the default buffer", ) parser.add_argument( "--buffer-size", type=int, metavar="KB", - help="With -l, request a collection buffer size in kilobytes (0 reports the default size)", + help="With --instance-peek/--instance-read, request a total collection buffer size in kilobytes", ) parser.add_argument( "--timeout", @@ -1938,17 +2252,18 @@ def main(): metavar="MS", help=f"With -l, maximum time in milliseconds to search for records, default is {INFO_LOG_READ_TIMEOUT_MS}", ) - parser.add_argument( - "--instance-read", - action="store_true", - help="With -l, read and consume all collected records after peeking them", - ) args = parser.parse_args() if args.set_threshold is not None and not 0 <= args.set_threshold <= 2**64 - 1: parser.error("--set-threshold must be an integer between 0 and 2^64 - 1") - if args.buffer_size is not None and args.buffer_size < 0: - parser.error("--buffer-size must be a non-negative integer") + if args.buffer_size is not None and args.buffer_size <= 0: + parser.error("--buffer-size must be a positive integer") + if ( + args.instance is not None or args.buffer_size is not None + ) and args.instance_mode not in ("peek", "read"): + parser.error( + "--instance and --buffer-size are used with --instance-peek or --instance-read" + ) if args.timeout < 0: parser.error("--timeout must be a non-negative integer") @@ -1996,10 +2311,10 @@ def main(): test_info_log_module( drivers[driver_idx], driver_idx, + mode=args.instance_mode, instance_name=args.instance, buffer_size_kb=args.buffer_size, timeout=args.timeout, - read_records=args.instance_read, ) # Run selected tests on all devices From 89993997d2752bec1bebbb84f80f651d30eb5c2a Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Thu, 8 Oct 2026 12:07:03 +0530 Subject: [PATCH 03/10] fix: Propagate info log test failures in pyzes black box test A failed zesDriverEnumInfoLogsExt call printed an error but both -a and -l still exited with status 0. Aggregate the info log test result into the exit status. Info logs are driver scoped, so also run them when device enumeration fails or returns no devices. Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- .../pyzes/examples/pyzes_black_box_test.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py index f2fbd77d..5196e290 100755 --- a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py +++ b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py @@ -2085,16 +2085,17 @@ def run_all_tests(): return False drivers, driver_count = driver_info + success = True # Test each driver for driver_idx in range(driver_count): print(f"\n=== Driver {driver_idx} Tests ===") - # Get devices for this driver + # Get devices for this driver, driver scoped tests still run without them device_info = get_devices(drivers[driver_idx]) if device_info is None: print(f"No devices found for driver {driver_idx}") - continue + device_info = (None, 0) devices, device_count = device_info @@ -2129,10 +2130,11 @@ def run_all_tests(): test_ras_module(devices[device_idx], device_idx) # Test info logs (driver scoped) - test_info_log_module(drivers[driver_idx], driver_idx) + if not test_info_log_module(drivers[driver_idx], driver_idx): + success = False print("\n=== Test Completed ===") - return True + return success def main(): @@ -2301,21 +2303,24 @@ def main(): print("No drivers available for testing") return 1 - if not devices or device_count == 0: - print("No devices available for testing") - return 1 + success = True - # Info logs are driver scoped, so run them once per driver + # Info logs are driver scoped, so run them once per driver before the device check if args.infolog: for driver_idx in range(driver_count): - test_info_log_module( + if not test_info_log_module( drivers[driver_idx], driver_idx, mode=args.instance_mode, instance_name=args.instance, buffer_size_kb=args.buffer_size, timeout=args.timeout, - ) + ): + success = False + + if not devices or device_count == 0: + print("No devices available for testing") + return 1 # Run selected tests on all devices for device_idx in range(device_count): @@ -2351,8 +2356,6 @@ def main(): clear_state=args.clear_ras_state, ) - success = True - return 0 if success else 1 except Exception as e: From 0d41249805e64496d0174d74ae83ff252a1b54a5 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Thu, 8 Oct 2026 12:25:56 +0530 Subject: [PATCH 04/10] fix: Accept dropped data warning on info log query calls in pyzes black box test ZE_RESULT_WARNING_DROPPED_DATA can be returned by a query call and still comes with a valid size and record count. The query helper passed no read status and its callers failed on the warning. Pass a read status, report the dropped record count and treat the warning as success, as queryInfoLogRecords in zello_sysman does. Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- .../pyzes/examples/pyzes_black_box_test.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py index 5196e290..22efa743 100755 --- a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py +++ b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py @@ -1097,13 +1097,31 @@ def query_info_log_records(collect_fn, instance_handle, timeout): """Query the records held by the instance without consuming them. A call with *pSize and *pRecordCount zero on input is a query call for both read and peek. + ZE_RESULT_WARNING_DROPPED_DATA still returns a valid query result, so it is reported as success. Returns (rc, size, record_count). """ size = c_uint32(0) record_count = c_uint32(0) + status = pz.zes_info_log_read_status_ext_t() + status.stype = pz.ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + status.pNext = None rc = collect_fn( - instance_handle, timeout, byref(size), None, byref(record_count), None, None + instance_handle, + timeout, + byref(size), + None, + byref(record_count), + None, + byref(status), ) + if rc == pz.ZE_RESULT_WARNING_DROPPED_DATA: + dropped = status.droppedRecordCount + if dropped == -1: + dropped = "unknown" + print_verbose( + f" query: records were dropped, readStatus.droppedRecordCount = {dropped}" + ) + rc = pz.ZE_RESULT_SUCCESS return rc, size.value, record_count.value From a748e6527a1d60ceaaa30ea1ae7955b8fb163328 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Thu, 8 Oct 2026 18:54:25 +0530 Subject: [PATCH 05/10] fix: Use zello_sysman option names for info log tests in pyzes black box test zello_sysman runs the info log test with -L and takes --instanceapi, --instancepeek, --instanceread and --buffersize. Use the same spellings instead of -l, --instance-api, --instance-peek, --instance-read and --buffer-size. Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- .../pyzes/examples/pyzes_black_box_test.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py index 22efa743..c4cc7f6b 100755 --- a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py +++ b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py @@ -1561,7 +1561,7 @@ def test_info_log_module( if mode is None: print_verbose( - "\nRecords are collected with --instance-api, --instance-peek or --instance-read" + "\nRecords are collected with --instanceapi, --instancepeek or --instanceread" ) return True @@ -2172,9 +2172,9 @@ def main(): %(prog)s -re # RAS tests only %(prog)s -re --set-threshold 5 # RAS tests, set then restore threshold 5 (root) %(prog)s -re --clear-ras-state # RAS tests, clear all error counters (root) - %(prog)s -l # Info log properties tests only - %(prog)s -l --instance-api # Info log tests, verify peek and read on a named collection instance - %(prog)s -l --instance-read --instance mylog --buffer-size 1024 # Info log tests, read the records of a named 1 MB collection instance + %(prog)s -L # Info log properties tests only + %(prog)s -L --instanceapi # Info log tests, verify peek and read on a named collection instance + %(prog)s -L --instanceread --instance mylog --buffersize 1024 # Info log tests, read the records of a named 1 MB collection instance %(prog)s -h # Show help message""", formatter_class=argparse.RawDescriptionHelpFormatter, ) @@ -2227,62 +2227,62 @@ def main(): help="With -re, clear all RAS error counters and print them before and after (requires root)", ) parser.add_argument( - "-l", + "-L", "--infolog", action="store_true", help="Run only info log tests, reporting what each info log supports", ) instance_mode = parser.add_mutually_exclusive_group() instance_mode.add_argument( - "--instance-api", + "--instanceapi", action="store_const", const="api", dest="instance_mode", - help="With -l, create a named collection instance, wait for CPER records, then verify peek and read (requires root)", + help="With -L, create a named collection instance, wait for CPER records, then verify peek and read (requires root)", ) instance_mode.add_argument( - "--instance-peek", + "--instancepeek", action="store_const", const="peek", dest="instance_mode", - help="With -l, create a collection instance and peek the records (requires root)", + help="With -L, create a collection instance and peek the records (requires root)", ) instance_mode.add_argument( - "--instance-read", + "--instanceread", action="store_const", const="read", dest="instance_mode", - help="With -l, create a collection instance and read the records (requires root)", + help="With -L, create a collection instance and read the records (requires root)", ) parser.add_argument( "--instance", metavar="NAME", - help="With --instance-peek/--instance-read, collect into a named instance instead of the default buffer", + help="With --instancepeek/--instanceread, collect into a named instance instead of the default buffer", ) parser.add_argument( - "--buffer-size", + "--buffersize", type=int, metavar="KB", - help="With --instance-peek/--instance-read, request a total collection buffer size in kilobytes", + help="With --instancepeek/--instanceread, request a total collection buffer size in kilobytes", ) parser.add_argument( "--timeout", type=int, default=INFO_LOG_READ_TIMEOUT_MS, metavar="MS", - help=f"With -l, maximum time in milliseconds to search for records, default is {INFO_LOG_READ_TIMEOUT_MS}", + help=f"With -L, maximum time in milliseconds to search for records, default is {INFO_LOG_READ_TIMEOUT_MS}", ) args = parser.parse_args() if args.set_threshold is not None and not 0 <= args.set_threshold <= 2**64 - 1: parser.error("--set-threshold must be an integer between 0 and 2^64 - 1") - if args.buffer_size is not None and args.buffer_size <= 0: - parser.error("--buffer-size must be a positive integer") + if args.buffersize is not None and args.buffersize <= 0: + parser.error("--buffersize must be a positive integer") if ( - args.instance is not None or args.buffer_size is not None + args.instance is not None or args.buffersize is not None ) and args.instance_mode not in ("peek", "read"): parser.error( - "--instance and --buffer-size are used with --instance-peek or --instance-read" + "--instance and --buffersize are used with --instancepeek or --instanceread" ) if args.timeout < 0: parser.error("--timeout must be a non-negative integer") @@ -2331,7 +2331,7 @@ def main(): driver_idx, mode=args.instance_mode, instance_name=args.instance, - buffer_size_kb=args.buffer_size, + buffer_size_kb=args.buffersize, timeout=args.timeout, ): success = False From 471eb777f3ddb1e4cae8a883721223e5e53da19d Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Fri, 9 Oct 2026 10:05:06 +0530 Subject: [PATCH 06/10] feature: Add pyzes support for info log APIs Add python bindings, unit tests, black box test coverage and README entries for: 1. zesDriverEnumInfoLogsExt 2. zesInfoLogGetPropertiesExt 3. zesInfoLogCreateInstanceExt 4. zesInfoLogInstanceReadWithMetadataExt 5. zesInfoLogInstancePeekWithMetadataExt 6. zesInfoLogInstanceDeleteExt Related-To: SYSM-432 Signed-off-by: Aviral Nigam From 10f8f2b5c83c305d518f148e23ee2df04338b6fa Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Tue, 13 Oct 2026 16:30:40 +0530 Subject: [PATCH 07/10] feature: Add pyzes support for info log APIs Add python bindings, unit tests, black box test coverage and README entries for: 1. zesDriverEnumInfoLogsExt 2. zesInfoLogGetPropertiesExt 3. zesInfoLogCreateInstanceExt 4. zesInfoLogInstanceReadWithMetadataExt 5. zesInfoLogInstancePeekWithMetadataExt 6. zesInfoLogInstanceDeleteExt Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index f5217a73..f7dcb461 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -213,12 +213,12 @@ SUPPORTED APIs | `zesRasSetConfigExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | | `zesRasClearStateExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | | **Info Log Management** |-|-|-| -| `zesDriverEnumInfoLogsExt` | Info Log | 0.1.3 | None | +| `zesDriverEnumInfoLogsExt` | Info Log | 0.1.3 | Linux: Requires superuser | | `zesInfoLogGetPropertiesExt` | Info Log | 0.1.3 | None | -| `zesInfoLogCreateInstanceExt` | Info Log | 0.1.3 | None | -| `zesInfoLogInstanceReadWithMetadataExt` | Info Log | 0.1.3 | Records returned are consumed | -| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.3 | Requires `isPeekDataSupported` | -| `zesInfoLogInstanceDeleteExt` | Info Log | 0.1.3 | None | +| `zesInfoLogCreateInstanceExt` | Info Log | 0.1.3 | Linux: Requires superuser | +| `zesInfoLogInstanceReadWithMetadataExt` | Info Log | 0.1.3 | Records returned are consumed. Linux: Requires superuser | +| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.3 | Requires `isPeekDataSupported`. Linux: Requires superuser | +| `zesInfoLogInstanceDeleteExt` | Info Log | 0.1.3 | Linux: Requires superuser | RELEASE NOTES ------------- From 29e1bba4dd83c09b4fe7dbf1bfa5d910af053a97 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Mon, 19 Oct 2026 17:14:12 +0530 Subject: [PATCH 08/10] feature: Add pyzes support for info log APIs Add python bindings, unit tests, black box test coverage and README entries for: 1. zesDriverEnumInfoLogsExt 2. zesInfoLogGetPropertiesExt 3. zesInfoLogCreateInstanceExt 4. zesInfoLogInstanceReadWithMetadataExt 5. zesInfoLogInstancePeekWithMetadataExt 6. zesInfoLogInstanceDeleteExt Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index f7dcb461..a2ca8482 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -213,12 +213,12 @@ SUPPORTED APIs | `zesRasSetConfigExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | | `zesRasClearStateExp` | RAS | 0.1.4 | Experimental API. Linux: Requires superuser | | **Info Log Management** |-|-|-| -| `zesDriverEnumInfoLogsExt` | Info Log | 0.1.3 | Linux: Requires superuser | -| `zesInfoLogGetPropertiesExt` | Info Log | 0.1.3 | None | -| `zesInfoLogCreateInstanceExt` | Info Log | 0.1.3 | Linux: Requires superuser | -| `zesInfoLogInstanceReadWithMetadataExt` | Info Log | 0.1.3 | Records returned are consumed. Linux: Requires superuser | -| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.3 | Requires `isPeekDataSupported`. Linux: Requires superuser | -| `zesInfoLogInstanceDeleteExt` | Info Log | 0.1.3 | Linux: Requires superuser | +| `zesDriverEnumInfoLogsExt` | Info Log | 0.1.4 | Linux: Requires superuser | +| `zesInfoLogGetPropertiesExt` | Info Log | 0.1.4 | None | +| `zesInfoLogCreateInstanceExt` | Info Log | 0.1.4 | Linux: Requires superuser | +| `zesInfoLogInstanceReadWithMetadataExt` | Info Log | 0.1.4 | Records returned are consumed. Linux: Requires superuser | +| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.4 | Requires `isPeekDataSupported`. Linux: Requires superuser | +| `zesInfoLogInstanceDeleteExt` | Info Log | 0.1.4 | Linux: Requires superuser | RELEASE NOTES ------------- From e84f0776ca1c185bb81f07b2105bab978c692f47 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Wed, 21 Oct 2026 11:17:35 +0530 Subject: [PATCH 09/10] feature: Add pyzes support for info log APIs Add python bindings, unit tests, black box test coverage and README entries for: 1. zesDriverEnumInfoLogsExt 2. zesInfoLogGetPropertiesExt 3. zesInfoLogCreateInstanceExt 4. zesInfoLogInstanceReadWithMetadataExt 5. zesInfoLogInstancePeekWithMetadataExt 6. zesInfoLogInstanceDeleteExt Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- .../sysman/python/source/pyzes/__init__.py | 51 ++++++++----------- .../pyzes/examples/pyzes_black_box_test.py | 8 +-- .../python/test/unit_tests/test_info_log.py | 2 - 3 files changed, 23 insertions(+), 38 deletions(-) diff --git a/bindings/sysman/python/source/pyzes/__init__.py b/bindings/sysman/python/source/pyzes/__init__.py index 4c9d42f9..88905c8a 100644 --- a/bindings/sysman/python/source/pyzes/__init__.py +++ b/bindings/sysman/python/source/pyzes/__init__.py @@ -327,7 +327,6 @@ class zes_info_log_instance_handle_t(c_void_p): ZES_RAS_ERROR_CATEGORY_EXP_SOC_INTERNAL_ERRORS = 12 ZES_RAS_ERROR_CATEGORY_EXP_FORCE_UINT32 = 0x7FFFFFFF -## Info log enums ## zes_info_log_type_ext_t = c_int32 ZES_INFO_LOG_TYPE_EXT_DEVICE = 0 ZES_INFO_LOG_TYPE_EXT_FORCE_UINT32 = 0x7FFFFFFF @@ -429,12 +428,10 @@ class zes_info_log_instance_handle_t(c_void_p): ZES_STRUCTURE_TYPE_RAS_PROPERTIES = 0xF ZES_STRUCTURE_TYPE_RAS_STATE_EXP2 = 0x00020015 ZES_STRUCTURE_TYPE_RAS_CONFIG_EXP = 0x00020016 -ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES = 0x00020020 # Info log properties -ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC = ( - 0x00020021 # Info log instance descriptor -) -ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT = 0x00020022 # Info log record metadata -ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT = 0x00020023 # Info log read status +ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES = 0x00020020 +ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC = 0x00020021 +ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT = 0x00020022 +ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT = 0x00020023 ## Core ze_device UUID struct ## @@ -867,47 +864,46 @@ class zes_ras_config_exp_t(_PrintableStructure): _fmt_ = {"threshold": "%d"} -## Info log structures ## class zes_info_log_ext_properties_t(_PrintableStructure): _fields_ = [ - ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_EXT_PROPERTIES + ("stype", c_int32), ("pNext", c_void_p), - ("infoLogType", zes_info_log_type_ext_t), # type of info carried - ("infoLogFormat", zes_info_log_format_ext_t), # format of records - ("isNamedInstanceSupported", ze_bool_t), # named instances supported - ("isPeekDataSupported", ze_bool_t), # peek without consuming supported + ("infoLogType", zes_info_log_type_ext_t), + ("infoLogFormat", zes_info_log_format_ext_t), + ("isNamedInstanceSupported", ze_bool_t), + ("isPeekDataSupported", ze_bool_t), ] class zes_info_log_instance_ext_desc_t(_PrintableStructure): _fields_ = [ - ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC + ("stype", c_int32), ("pNext", c_void_p), - ("pBufferSizeInKb", POINTER(c_uint32)), # optional buffer size in KB + ("pBufferSizeInKb", POINTER(c_uint32)), ] class zes_info_log_metadata_ext_t(_PrintableStructure): _fields_ = [ - ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_METADATA_EXT + ("stype", c_int32), ("pNext", c_void_p), - ("address", zes_pci_address_t), # PCI address of originating device - ("uuid", zes_uuid_t), # UUID of originating device - ("timestamp", c_uint64), # record time in nanoseconds - ("lengthOfData", c_uint32), # record length in bytes - ("offset", c_uint32), # record byte offset in data buffer - ("recordType", zes_info_log_record_type_ext_t), # record type + ("address", zes_pci_address_t), + ("uuid", zes_uuid_t), + ("timestamp", c_uint64), + ("lengthOfData", c_uint32), + ("offset", c_uint32), + ("recordType", zes_info_log_record_type_ext_t), ] _fmt_ = {"timestamp": "%d ns", "lengthOfData": "%d bytes"} class zes_info_log_read_status_ext_t(_PrintableStructure): _fields_ = [ - ("stype", c_int32), # ZES_STRUCTURE_TYPE_INFO_LOG_READ_STATUS_EXT + ("stype", c_int32), ("pNext", c_void_p), - ("droppedRecordCount", c_int64), # dropped records, -1 if unknown - ("consumedDataSize", c_uint64), # bytes read out of the instance - ("hasDataToRead", ze_bool_t), # records remain to be read + ("droppedRecordCount", c_int64), + ("consumedDataSize", c_uint64), + ("hasDataToRead", ze_bool_t), ] _fmt_ = {"consumedDataSize": "%d bytes"} @@ -1853,9 +1849,6 @@ def zesRasClearStateExp(hRas, category): return retVal -## Info log functions ## - - def zesDriverEnumInfoLogsExt(hDriver, pCount, phInfoLogs): """Wraps API: ze_result_t zesDriverEnumInfoLogsExt( diff --git a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py index c4cc7f6b..dee4fde2 100755 --- a/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py +++ b/bindings/sysman/python/source/pyzes/examples/pyzes_black_box_test.py @@ -1286,7 +1286,6 @@ def create_info_log_instance(info_log_handle, instance_name, buffer_size_kb): if not check_rc("zesInfoLogCreateInstanceExt", rc): return None - # The descriptor members are updated in place with the values which were actually applied if buffer_size is not None: print_verbose(f" applied buffer size: {buffer_size.value} KB") return instance_handle @@ -1312,8 +1311,6 @@ def test_info_log_instance_read_peek(info_log_handle, props, timeout): return print_verbose(f"Created collection instance '{INFO_LOG_INSTANCE_NAME}'") - # Only records generated after the instance was created are collected into it, so the - # instance is polled with the non-consuming query until it reports data print_verbose( f"\nWaiting up to {INFO_LOG_RECORD_WAIT_MS} ms for CPER records. Records are generated " "when the uncorrectable RAS counters are read, for example with " @@ -1459,7 +1456,6 @@ def test_info_log_instance_collect( elif records is not None: print_info_log_records(records, 1) if use_peek: - # The peek left the records in place, so the same totals must still be reported rc, pending_size, pending_count = query_info_log_records( collect_fn, instance_handle, timeout ) @@ -2109,7 +2105,7 @@ def run_all_tests(): for driver_idx in range(driver_count): print(f"\n=== Driver {driver_idx} Tests ===") - # Get devices for this driver, driver scoped tests still run without them + # Get devices for this driver device_info = get_devices(drivers[driver_idx]) if device_info is None: print(f"No devices found for driver {driver_idx}") @@ -2147,7 +2143,6 @@ def run_all_tests(): test_ras_module(devices[device_idx], device_idx) - # Test info logs (driver scoped) if not test_info_log_module(drivers[driver_idx], driver_idx): success = False @@ -2323,7 +2318,6 @@ def main(): success = True - # Info logs are driver scoped, so run them once per driver before the device check if args.infolog: for driver_idx in range(driver_count): if not test_info_log_module( diff --git a/bindings/sysman/python/test/unit_tests/test_info_log.py b/bindings/sysman/python/test/unit_tests/test_info_log.py index 7947cd24..11897ac9 100644 --- a/bindings/sysman/python/test/unit_tests/test_info_log.py +++ b/bindings/sysman/python/test/unit_tests/test_info_log.py @@ -11,7 +11,6 @@ from ctypes import * from unittest.mock import MagicMock, patch -# Add the source directory to Python path so we can import pyzes script_dir = os.path.dirname(os.path.abspath(__file__)) source_dir = os.path.join(script_dir, "..", "..", "source") source_dir = os.path.abspath(source_dir) @@ -91,7 +90,6 @@ def mock_create_instance(info_log_handle, name, desc_ptr, instance_ptr): self.assertEqual( desc.stype, self.pyzes.ZES_STRUCTURE_TYPE_INFO_LOG_INSTANCE_EXT_DESC ) - # Driver reports the buffer size it actually applied desc.pBufferSizeInKb[0] = 64 instance_ptr._obj.value = 0x1234 return self.pyzes.ZE_RESULT_SUCCESS From ab90a758d95aef8efa95d735f0536aa2d1a97a07 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Wed, 21 Oct 2026 18:39:39 +0530 Subject: [PATCH 10/10] feature: Add pyzes support for info log APIs Add python bindings, unit tests, black box test coverage and README entries for: 1. zesDriverEnumInfoLogsExt 2. zesInfoLogGetPropertiesExt 3. zesInfoLogCreateInstanceExt 4. zesInfoLogInstanceReadWithMetadataExt 5. zesInfoLogInstancePeekWithMetadataExt 6. zesInfoLogInstanceDeleteExt Related-To: SYSM-432 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index a2ca8482..10bf3de4 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -217,7 +217,7 @@ SUPPORTED APIs | `zesInfoLogGetPropertiesExt` | Info Log | 0.1.4 | None | | `zesInfoLogCreateInstanceExt` | Info Log | 0.1.4 | Linux: Requires superuser | | `zesInfoLogInstanceReadWithMetadataExt` | Info Log | 0.1.4 | Records returned are consumed. Linux: Requires superuser | -| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.4 | Requires `isPeekDataSupported`. Linux: Requires superuser | +| `zesInfoLogInstancePeekWithMetadataExt` | Info Log | 0.1.4 | Linux: Requires superuser | | `zesInfoLogInstanceDeleteExt` | Info Log | 0.1.4 | Linux: Requires superuser | RELEASE NOTES