From 185c4fdbc0c904bdaa9e16266c31d4ccfdd31bdf Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Wed, 23 Sep 2026 11:13:42 +0530 Subject: [PATCH 1/9] feature: Add more power APIs to L0 Sysman python binding Added following power APIs to python binding: 1. zesPowerGetUsage 2. zesPowerGetLimitsExt2 3. zesPowerSetLimitsExt2 4. zesPowerGetEnergyThreshold 5. zesPowerSetEnergyThreshold Along with the zes_energy_threshold_t structure, unit tests, power black box test coverage and README updates. Related-To: SYSM-430 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 5 + .../sysman/python/source/pyzes/__init__.py | 111 ++++++++++++ .../pyzes/examples/pyzes_black_box_test.py | 171 +++++++++++++++++- .../python/test/unit_tests/test_power.py | 118 ++++++++++++ 4 files changed, 403 insertions(+), 2 deletions(-) diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index df6192cb..9ec8eafb 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -178,6 +178,11 @@ SUPPORTED APIs | `zesPowerGetProperties` | Power | 0.1.2 | None | | `zesPowerGetLimitsExt` | Power | 0.1.2 | Extended power limits API | | `zesPowerSetLimitsExt` | Power | 0.1.2 | Linux: Requires superuser or write permissions for telem nodes | +| `zesPowerGetUsage` | Power | 0.1.3 | None | +| `zesPowerGetLimitsExt2` | Power | 0.1.3 | None | +| `zesPowerSetLimitsExt2` | Power | 0.1.3 | Linux: Requires superuser | +| `zesPowerGetEnergyThreshold` | Power | 0.1.3 | None | +| `zesPowerSetEnergyThreshold` | Power | 0.1.3 | Linux: Requires superuser | | **Frequency Management** |-|-|-| | `zesDeviceEnumFrequencyDomains` | Frequency | 0.1.0 | None | | `zesFrequencyGetState` | Frequency | 0.1.0 | None | diff --git a/bindings/sysman/python/source/pyzes/__init__.py b/bindings/sysman/python/source/pyzes/__init__.py index e83b288b..224aa417 100644 --- a/bindings/sysman/python/source/pyzes/__init__.py +++ b/bindings/sysman/python/source/pyzes/__init__.py @@ -697,6 +697,15 @@ class zes_power_energy_counter_t(_PrintableStructure): _fmt_ = {"energy": "%d", "timestamp": "%d microseconds"} +class zes_energy_threshold_t(_PrintableStructure): + _fields_ = [ + ("enable", ze_bool_t), # is the energy threshold enabled + ("threshold", c_double), # energy threshold in Joules, 0.0 if not set + ("processId", c_uint32), # host process ID that set the threshold + ] + _fmt_ = {"threshold": "%.3f J"} + + ## Frequency structures ## class zes_freq_properties_t(_PrintableStructure): _fields_ = [ @@ -1319,6 +1328,108 @@ def zesPowerSetLimitsExt(hPower, pCount, pSustained): return retVal +def zesPowerGetUsage(hPower, pInstantPower, pAveragePower): + """Wraps API: + ze_result_t zesPowerGetUsage( + zes_pwr_handle_t hPower, + uint32_t* pInstantPower, + uint32_t* pAveragePower) + + Parameters: + hPower: power handle + pInstantPower: POINTER(c_uint32) or None - instant power usage in milliwatts + pAveragePower: POINTER(c_uint32) or None - average power usage in milliwatts + Returns: + ze_result_t - return code only, power usage is filled into pInstantPower and pAveragePower + """ + funcPtr = getFunctionPointerList("zesPowerGetUsage") + funcPtr.argtypes = [zes_pwr_handle_t, POINTER(c_uint32), POINTER(c_uint32)] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hPower, pInstantPower, pAveragePower) + return retVal + + +def zesPowerGetLimitsExt2(hPower, pLimit): + """Wraps API: + ze_result_t zesPowerGetLimitsExt2( + zes_pwr_handle_t hPower, + uint32_t* pLimit) + + Parameters: + hPower: power handle + pLimit: POINTER(c_uint32) - power limit in milliwatts to fill + Returns: + ze_result_t - return code only, power limit is filled into pLimit + """ + funcPtr = getFunctionPointerList("zesPowerGetLimitsExt2") + funcPtr.argtypes = [zes_pwr_handle_t, POINTER(c_uint32)] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hPower, pLimit) + return retVal + + +def zesPowerSetLimitsExt2(hPower, limit): + """Wraps API: + ze_result_t zesPowerSetLimitsExt2( + zes_pwr_handle_t hPower, + const uint32_t limit) + + Parameters: + hPower: power handle + limit: power limit in milliwatts to set + Returns: + ze_result_t - return code only + """ + funcPtr = getFunctionPointerList("zesPowerSetLimitsExt2") + funcPtr.argtypes = [zes_pwr_handle_t, c_uint32] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hPower, limit) + return retVal + + +def zesPowerGetEnergyThreshold(hPower, pThreshold): + """Wraps API: + ze_result_t zesPowerGetEnergyThreshold( + zes_pwr_handle_t hPower, + zes_energy_threshold_t* pThreshold) + + Parameters: + hPower: power handle + pThreshold: POINTER(zes_energy_threshold_t) - energy threshold structure to fill + Returns: + ze_result_t - return code only, energy threshold is filled into pThreshold + """ + funcPtr = getFunctionPointerList("zesPowerGetEnergyThreshold") + funcPtr.argtypes = [zes_pwr_handle_t, POINTER(zes_energy_threshold_t)] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hPower, pThreshold) + return retVal + + +def zesPowerSetEnergyThreshold(hPower, threshold): + """Wraps API: + ze_result_t zesPowerSetEnergyThreshold( + zes_pwr_handle_t hPower, + double threshold) + + Parameters: + hPower: power handle + threshold: energy threshold in Joules to set + Returns: + ze_result_t - return code only + """ + funcPtr = getFunctionPointerList("zesPowerSetEnergyThreshold") + funcPtr.argtypes = [zes_pwr_handle_t, c_double] + funcPtr.restype = ze_result_t + + retVal = funcPtr(hPower, threshold) + return retVal + + ## Frequency module functions ## def zesDeviceEnumFrequencyDomains(hDevice, pCount, phFrequency): """Wraps API: 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..bec6c78d 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 @@ -1115,7 +1115,9 @@ def test_memory_modules(device_handle, device_index): return True -def test_power_module(device_handle, device_index): +def test_power_module( + device_handle, device_index, set_global_limit=None, set_energy_threshold=None +): """Test power domain enumeration, properties, energy-derived power, and power limit extension operations""" print(f"\n---- Device {device_index} Power Domains Test ----") @@ -1242,6 +1244,140 @@ def test_power_module(device_handle, device_index): else: print_verbose(" Current Power: unavailable due to zero delta time") + instant_power = c_uint32(0) + average_power = c_uint32(0) + rc = pz.zesPowerGetUsage( + power_handles[i], byref(instant_power), byref(average_power) + ) + if rc == pz.ZE_RESULT_SUCCESS: + print_verbose(" Power Usage:") + print_verbose(f" Instant Power: {instant_power.value} mW") + print_verbose(f" Average Power: {average_power.value} mW") + else: + print_verbose(f" Power Usage: Not available ({get_result_string(rc)})") + + limit_ext2 = c_uint32(0) + rc = pz.zesPowerGetLimitsExt2(power_handles[i], byref(limit_ext2)) + limit_ext2_available = rc == pz.ZE_RESULT_SUCCESS + if limit_ext2_available: + print_verbose(f" Power Limit (Ext2): {limit_ext2.value} mW") + else: + print_verbose( + f" Power Limit (Ext2): Not available ({get_result_string(rc)})" + ) + + energy_threshold = None + if properties.isEnergyThresholdSupported: + energy_threshold = pz.zes_energy_threshold_t() + rc = pz.zesPowerGetEnergyThreshold( + power_handles[i], byref(energy_threshold) + ) + if rc == pz.ZE_RESULT_SUCCESS: + print_verbose(" Energy Threshold:") + print_verbose(f" Enabled: {bool(energy_threshold.enable)}") + print_verbose(f" Threshold: {energy_threshold.threshold} J") + print_verbose(f" Process ID: 0x{energy_threshold.processId:X}") + else: + print_verbose( + f" Energy Threshold: Not available ({get_result_string(rc)})" + ) + energy_threshold = None + + # Requested set operations only apply to the root power domain of the selected device + requested_limit = None + if set_global_limit is not None and set_global_limit[0] == device_index: + requested_limit = set_global_limit[1] + requested_threshold = None + if ( + set_energy_threshold is not None + and int(set_energy_threshold[0]) == device_index + ): + requested_threshold = set_energy_threshold[1] + if properties.onSubdevice: + requested_limit = None + requested_threshold = None + + if not is_root_user(): + if requested_limit is not None or requested_threshold is not None: + print_verbose( + " Skipping power set operations due to insufficient permissions" + ) + elif properties.canControl: + if limit_ext2_available and requested_limit is not None: + rc = pz.zesPowerSetLimitsExt2(power_handles[i], requested_limit) + if check_rc(f"zesPowerSetLimitsExt2(power {i}, {requested_limit})", rc): + print_verbose(f" Power limit (Ext2) set to {requested_limit} mW") + read_back = c_uint32(0) + rc = pz.zesPowerGetLimitsExt2(power_handles[i], byref(read_back)) + if check_rc(f"zesPowerGetLimitsExt2(power {i}, verify)", rc): + if read_back.value == requested_limit: + print_verbose( + f" Read back power limit (Ext2): {read_back.value} mW (OK)" + ) + else: + print_verbose( + f" Warning: requested power limit {requested_limit} mW " + f"does not match applied limit {read_back.value} mW" + ) + + # Restore the limit read before the test + rc = pz.zesPowerSetLimitsExt2(power_handles[i], limit_ext2.value) + if check_rc(f"zesPowerSetLimitsExt2(power {i}, restore)", rc): + print_verbose( + f" Restored power limit (Ext2) to {limit_ext2.value} mW" + ) + elif limit_ext2_available: + # Write back the limit just read so the device configuration is unchanged + rc = pz.zesPowerSetLimitsExt2(power_handles[i], limit_ext2.value) + if check_rc(f"zesPowerSetLimitsExt2(power {i})", rc): + print_verbose(" Set power limit (Ext2) successfully") + + if energy_threshold is not None and requested_threshold is not None: + rc = pz.zesPowerSetEnergyThreshold( + power_handles[i], requested_threshold + ) + if check_rc( + f"zesPowerSetEnergyThreshold(power {i}, {requested_threshold})", rc + ): + print_verbose( + f" Energy threshold set to {requested_threshold} J" + ) + read_back_threshold = pz.zes_energy_threshold_t() + rc = pz.zesPowerGetEnergyThreshold( + power_handles[i], byref(read_back_threshold) + ) + if check_rc(f"zesPowerGetEnergyThreshold(power {i}, verify)", rc): + print_verbose( + f" Read back energy threshold: {read_back_threshold.threshold} J " + f"(enabled: {bool(read_back_threshold.enable)}, " + f"process ID: 0x{read_back_threshold.processId:X})" + ) + + # An energy threshold cannot be disabled, so only an enabled one can be restored + if energy_threshold.enable: + rc = pz.zesPowerSetEnergyThreshold( + power_handles[i], energy_threshold.threshold + ) + if check_rc(f"zesPowerSetEnergyThreshold(power {i}, restore)", rc): + print_verbose( + f" Restored energy threshold to {energy_threshold.threshold} J" + ) + else: + print_verbose( + " Energy threshold was not enabled before the test and cannot be disabled" + ) + elif energy_threshold is not None and energy_threshold.enable: + # Only re-apply an already enabled threshold since it cannot be unset afterwards + rc = pz.zesPowerSetEnergyThreshold( + power_handles[i], energy_threshold.threshold + ) + if check_rc(f"zesPowerSetEnergyThreshold(power {i})", rc): + print_verbose(" Set energy threshold successfully") + elif requested_limit is not None or requested_threshold is not None: + print_verbose( + " Skipping power set operations since the domain cannot be controlled" + ) + if properties.onSubdevice or limit_descs is None: continue @@ -1598,6 +1734,8 @@ def main(): %(prog)s -p # PCI tests only %(prog)s -C # ECC tests only %(prog)s -o # Power tests only + %(prog)s -o --set-global-limit 0 150000 # Power tests, set then restore device 0 limit (root) + %(prog)s -o --set-energy-threshold 0 5000.0 # Power tests, set device 0 energy threshold (root) %(prog)s -f # Frequency tests only %(prog)s -t # Temperature tests only %(prog)s -e # Engine tests only @@ -1621,6 +1759,20 @@ def main(): parser.add_argument( "-o", "--power", action="store_true", help="Run only power-related tests" ) + parser.add_argument( + "--set-global-limit", + nargs=2, + type=int, + metavar=("DEVICE", "LIMIT_MW"), + help="With -o, set the power limit (Ext2) of a device in mW, verify it and restore the original (requires root)", + ) + parser.add_argument( + "--set-energy-threshold", + nargs=2, + type=float, + metavar=("DEVICE", "JOULES"), + help="With -o, set the energy threshold of a device in Joules and read it back (requires root)", + ) parser.add_argument("-p", "--pci", action="store_true", help="Run only PCI tests") parser.add_argument("-C", "--ecc", action="store_true", help="Run only ECC tests") parser.add_argument( @@ -1659,6 +1811,16 @@ def main(): 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.set_global_limit is not None and min(args.set_global_limit) < 0: + parser.error("--set-global-limit values must be non-negative integers") + if args.set_energy_threshold is not None and ( + args.set_energy_threshold[0] < 0 + or not args.set_energy_threshold[0].is_integer() + or args.set_energy_threshold[1] < 0 + ): + parser.error( + "--set-energy-threshold needs a device index and a non-negative threshold" + ) # Check if any specific test is requested specific_test = ( @@ -1715,7 +1877,12 @@ def main(): test_engine_modules(devices[device_idx], device_idx) if args.power: - test_power_module(devices[device_idx], device_idx) + test_power_module( + devices[device_idx], + device_idx, + set_global_limit=args.set_global_limit, + set_energy_threshold=args.set_energy_threshold, + ) if args.frequency: test_frequency_domains(devices[device_idx], device_idx) diff --git a/bindings/sysman/python/test/unit_tests/test_power.py b/bindings/sysman/python/test/unit_tests/test_power.py index a9094205..cd2967bc 100644 --- a/bindings/sysman/python/test/unit_tests/test_power.py +++ b/bindings/sysman/python/test/unit_tests/test_power.py @@ -202,6 +202,124 @@ def mock_set_limits_ext(power_handle, count_ptr, limits_ptr): mock_get_func.assert_called_with("zesPowerSetLimitsExt") mock_func.assert_called_once() + def test_GivenValidPowerHandleWhenCallingZesPowerGetUsageThenCallSucceedsWithUsageData( + self, mock_get_func + ): + def mock_get_usage(power_handle, instant_ptr, average_ptr): + instant_ptr._obj.value = 45000 + average_ptr._obj.value = 40000 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_get_usage) + mock_get_func.return_value = mock_func + + power_handle = self.pyzes.zes_pwr_handle_t() + instant_power = c_uint32(0) + average_power = c_uint32(0) + + result = self.pyzes.zesPowerGetUsage( + power_handle, byref(instant_power), byref(average_power) + ) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(instant_power.value, 45000) + self.assertEqual(average_power.value, 40000) + mock_get_func.assert_called_with("zesPowerGetUsage") + mock_func.assert_called_once() + + def test_GivenValidPowerHandleWhenCallingZesPowerGetUsageWithOnlyInstantPowerThenCallSucceeds( + self, mock_get_func + ): + def mock_get_usage(power_handle, instant_ptr, average_ptr): + self.assertIsNone(average_ptr) + instant_ptr._obj.value = 45000 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_get_usage) + mock_get_func.return_value = mock_func + + power_handle = self.pyzes.zes_pwr_handle_t() + instant_power = c_uint32(0) + + result = self.pyzes.zesPowerGetUsage(power_handle, byref(instant_power), None) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(instant_power.value, 45000) + mock_get_func.assert_called_with("zesPowerGetUsage") + mock_func.assert_called_once() + + def test_GivenValidPowerHandleWhenCallingZesPowerGetLimitsExt2ThenCallSucceedsWithLimit( + self, mock_get_func + ): + def mock_get_limits_ext2(power_handle, limit_ptr): + limit_ptr._obj.value = 250000 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_get_limits_ext2) + mock_get_func.return_value = mock_func + + power_handle = self.pyzes.zes_pwr_handle_t() + limit = c_uint32(0) + + result = self.pyzes.zesPowerGetLimitsExt2(power_handle, byref(limit)) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(limit.value, 250000) + mock_get_func.assert_called_with("zesPowerGetLimitsExt2") + mock_func.assert_called_once() + + def test_GivenValidPowerHandleWhenCallingZesPowerSetLimitsExt2ThenCallSucceeds( + self, mock_get_func + ): + mock_func = MagicMock(return_value=self.pyzes.ZE_RESULT_SUCCESS) + mock_get_func.return_value = mock_func + + power_handle = self.pyzes.zes_pwr_handle_t() + + result = self.pyzes.zesPowerSetLimitsExt2(power_handle, 275000) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + mock_get_func.assert_called_with("zesPowerSetLimitsExt2") + mock_func.assert_called_once_with(power_handle, 275000) + + def test_GivenValidPowerHandleWhenCallingZesPowerGetEnergyThresholdThenCallSucceedsWithThresholdData( + self, mock_get_func + ): + def mock_get_energy_threshold(power_handle, threshold_ptr): + threshold_ptr._obj.enable = 1 + threshold_ptr._obj.threshold = 1500.5 + threshold_ptr._obj.processId = 4321 + return self.pyzes.ZE_RESULT_SUCCESS + + mock_func = MagicMock(side_effect=mock_get_energy_threshold) + mock_get_func.return_value = mock_func + + power_handle = self.pyzes.zes_pwr_handle_t() + threshold = self.pyzes.zes_energy_threshold_t() + + result = self.pyzes.zesPowerGetEnergyThreshold(power_handle, byref(threshold)) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + self.assertEqual(threshold.enable, 1) + self.assertEqual(threshold.threshold, 1500.5) + self.assertEqual(threshold.processId, 4321) + mock_get_func.assert_called_with("zesPowerGetEnergyThreshold") + mock_func.assert_called_once() + + def test_GivenValidPowerHandleWhenCallingZesPowerSetEnergyThresholdThenCallSucceeds( + self, mock_get_func + ): + mock_func = MagicMock(return_value=self.pyzes.ZE_RESULT_SUCCESS) + mock_get_func.return_value = mock_func + + power_handle = self.pyzes.zes_pwr_handle_t() + + result = self.pyzes.zesPowerSetEnergyThreshold(power_handle, 1500.5) + + self.assertEqual(result, self.pyzes.ZE_RESULT_SUCCESS) + mock_get_func.assert_called_with("zesPowerSetEnergyThreshold") + mock_func.assert_called_once_with(power_handle, 1500.5) + if __name__ == "__main__": unittest.main() From 399d9fb59d8f3b216d3cedfb78537f899c48460e Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Mon, 28 Sep 2026 14:06:49 +0530 Subject: [PATCH 2/9] fix: Set power limit (Ext2) and energy threshold only when requested in pyzes black box test --- .../source/pyzes/examples/pyzes_black_box_test.py | 12 ------------ 1 file changed, 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 bec6c78d..cf4d78ee 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 @@ -1326,11 +1326,6 @@ def test_power_module( print_verbose( f" Restored power limit (Ext2) to {limit_ext2.value} mW" ) - elif limit_ext2_available: - # Write back the limit just read so the device configuration is unchanged - rc = pz.zesPowerSetLimitsExt2(power_handles[i], limit_ext2.value) - if check_rc(f"zesPowerSetLimitsExt2(power {i})", rc): - print_verbose(" Set power limit (Ext2) successfully") if energy_threshold is not None and requested_threshold is not None: rc = pz.zesPowerSetEnergyThreshold( @@ -1366,13 +1361,6 @@ def test_power_module( print_verbose( " Energy threshold was not enabled before the test and cannot be disabled" ) - elif energy_threshold is not None and energy_threshold.enable: - # Only re-apply an already enabled threshold since it cannot be unset afterwards - rc = pz.zesPowerSetEnergyThreshold( - power_handles[i], energy_threshold.threshold - ) - if check_rc(f"zesPowerSetEnergyThreshold(power {i})", rc): - print_verbose(" Set energy threshold successfully") elif requested_limit is not None or requested_threshold is not None: print_verbose( " Skipping power set operations since the domain cannot be controlled" From 49c4f23a069ad5d91b2d4148a63f2c08a0187c2b Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Thu, 8 Oct 2026 12:02:51 +0530 Subject: [PATCH 3/9] fix: Gate energy threshold set on isEnergyThresholdSupported in pyzes black box test canControl only reports whether the power limits can be changed, and energy threshold support is reported independently. Check each setter against its own capability, so --set-energy-threshold is not skipped on a domain that supports thresholds but not power limit control. Related-To: SYSM-430 Signed-off-by: Aviral Nigam --- .../pyzes/examples/pyzes_black_box_test.py | 130 +++++++++++------- 1 file changed, 78 insertions(+), 52 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 cf4d78ee..4b90fd9d 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 @@ -1302,69 +1302,95 @@ def test_power_module( print_verbose( " Skipping power set operations due to insufficient permissions" ) - elif properties.canControl: - if limit_ext2_available and requested_limit is not None: - rc = pz.zesPowerSetLimitsExt2(power_handles[i], requested_limit) - if check_rc(f"zesPowerSetLimitsExt2(power {i}, {requested_limit})", rc): - print_verbose(f" Power limit (Ext2) set to {requested_limit} mW") - read_back = c_uint32(0) - rc = pz.zesPowerGetLimitsExt2(power_handles[i], byref(read_back)) - if check_rc(f"zesPowerGetLimitsExt2(power {i}, verify)", rc): - if read_back.value == requested_limit: - print_verbose( - f" Read back power limit (Ext2): {read_back.value} mW (OK)" - ) - else: - print_verbose( - f" Warning: requested power limit {requested_limit} mW " - f"does not match applied limit {read_back.value} mW" - ) - - # Restore the limit read before the test - rc = pz.zesPowerSetLimitsExt2(power_handles[i], limit_ext2.value) - if check_rc(f"zesPowerSetLimitsExt2(power {i}, restore)", rc): + else: + # Power limit control and energy threshold support are reported independently + if requested_limit is not None: + if not properties.canControl: print_verbose( - f" Restored power limit (Ext2) to {limit_ext2.value} mW" + " Skipping zesPowerSetLimitsExt2 since the domain cannot be controlled" ) - - if energy_threshold is not None and requested_threshold is not None: - rc = pz.zesPowerSetEnergyThreshold( - power_handles[i], requested_threshold - ) - if check_rc( - f"zesPowerSetEnergyThreshold(power {i}, {requested_threshold})", rc - ): + elif not limit_ext2_available: print_verbose( - f" Energy threshold set to {requested_threshold} J" - ) - read_back_threshold = pz.zes_energy_threshold_t() - rc = pz.zesPowerGetEnergyThreshold( - power_handles[i], byref(read_back_threshold) + " Skipping zesPowerSetLimitsExt2 since zesPowerGetLimitsExt2 is not available" ) - if check_rc(f"zesPowerGetEnergyThreshold(power {i}, verify)", rc): + else: + rc = pz.zesPowerSetLimitsExt2(power_handles[i], requested_limit) + if check_rc( + f"zesPowerSetLimitsExt2(power {i}, {requested_limit})", rc + ): + print_verbose( + f" Power limit (Ext2) set to {requested_limit} mW" + ) + read_back = c_uint32(0) + rc = pz.zesPowerGetLimitsExt2( + power_handles[i], byref(read_back) + ) + if check_rc(f"zesPowerGetLimitsExt2(power {i}, verify)", rc): + if read_back.value == requested_limit: + print_verbose( + f" Read back power limit (Ext2): {read_back.value} mW (OK)" + ) + else: + print_verbose( + f" Warning: requested power limit {requested_limit} mW " + f"does not match applied limit {read_back.value} mW" + ) + + # Restore the limit read before the test + rc = pz.zesPowerSetLimitsExt2(power_handles[i], limit_ext2.value) + if check_rc(f"zesPowerSetLimitsExt2(power {i}, restore)", rc): print_verbose( - f" Read back energy threshold: {read_back_threshold.threshold} J " - f"(enabled: {bool(read_back_threshold.enable)}, " - f"process ID: 0x{read_back_threshold.processId:X})" + f" Restored power limit (Ext2) to {limit_ext2.value} mW" ) - # An energy threshold cannot be disabled, so only an enabled one can be restored - if energy_threshold.enable: + if requested_threshold is not None: + if not properties.isEnergyThresholdSupported: + print_verbose( + " Skipping zesPowerSetEnergyThreshold since the domain does not support energy thresholds" + ) + elif energy_threshold is None: + print_verbose( + " Skipping zesPowerSetEnergyThreshold since zesPowerGetEnergyThreshold failed" + ) + else: rc = pz.zesPowerSetEnergyThreshold( - power_handles[i], energy_threshold.threshold + power_handles[i], requested_threshold ) - if check_rc(f"zesPowerSetEnergyThreshold(power {i}, restore)", rc): + if check_rc( + f"zesPowerSetEnergyThreshold(power {i}, {requested_threshold})", + rc, + ): print_verbose( - f" Restored energy threshold to {energy_threshold.threshold} J" + f" Energy threshold set to {requested_threshold} J" + ) + read_back_threshold = pz.zes_energy_threshold_t() + rc = pz.zesPowerGetEnergyThreshold( + power_handles[i], byref(read_back_threshold) + ) + if check_rc( + f"zesPowerGetEnergyThreshold(power {i}, verify)", rc + ): + print_verbose( + f" Read back energy threshold: {read_back_threshold.threshold} J " + f"(enabled: {bool(read_back_threshold.enable)}, " + f"process ID: 0x{read_back_threshold.processId:X})" + ) + + # An energy threshold cannot be disabled, so only an enabled one can be restored + if energy_threshold.enable: + rc = pz.zesPowerSetEnergyThreshold( + power_handles[i], energy_threshold.threshold + ) + if check_rc( + f"zesPowerSetEnergyThreshold(power {i}, restore)", rc + ): + print_verbose( + f" Restored energy threshold to {energy_threshold.threshold} J" + ) + else: + print_verbose( + " Energy threshold was not enabled before the test and cannot be disabled" ) - else: - print_verbose( - " Energy threshold was not enabled before the test and cannot be disabled" - ) - elif requested_limit is not None or requested_threshold is not None: - print_verbose( - " Skipping power set operations since the domain cannot be controlled" - ) if properties.onSubdevice or limit_descs is None: continue From 0d0442ec2e6754c6c258faa2111f27c8cc80fc7b Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Thu, 8 Oct 2026 18:54:23 +0530 Subject: [PATCH 4/9] fix: Use zello_sysman power limit options in pyzes black box test zello_sysman sets a power limit with -o --setlimit --global-limit . Replace --set-global-limit DEVICE LIMIT_MW with the same two options so both tools take the same command line; they must be given together. Related-To: SYSM-430 Signed-off-by: Aviral Nigam --- .../pyzes/examples/pyzes_black_box_test.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 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 4b90fd9d..197d7733 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 @@ -1748,7 +1748,7 @@ def main(): %(prog)s -p # PCI tests only %(prog)s -C # ECC tests only %(prog)s -o # Power tests only - %(prog)s -o --set-global-limit 0 150000 # Power tests, set then restore device 0 limit (root) + %(prog)s -o --setlimit --global-limit 0 150000 # Power tests, set then restore device 0 limit (root) %(prog)s -o --set-energy-threshold 0 5000.0 # Power tests, set device 0 energy threshold (root) %(prog)s -f # Frequency tests only %(prog)s -t # Temperature tests only @@ -1774,11 +1774,16 @@ def main(): "-o", "--power", action="store_true", help="Run only power-related tests" ) parser.add_argument( - "--set-global-limit", + "--setlimit", + action="store_true", + help="With -o, set a power limit, verify it and restore the original; the level is given by --global-limit (requires root)", + ) + parser.add_argument( + "--global-limit", nargs=2, type=int, metavar=("DEVICE", "LIMIT_MW"), - help="With -o, set the power limit (Ext2) of a device in mW, verify it and restore the original (requires root)", + help="With -o --setlimit, the device index and the global power limit (Ext2) in mW to set", ) parser.add_argument( "--set-energy-threshold", @@ -1825,8 +1830,10 @@ def main(): 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.set_global_limit is not None and min(args.set_global_limit) < 0: - parser.error("--set-global-limit values must be non-negative integers") + if args.setlimit != (args.global_limit is not None): + parser.error("--setlimit and --global-limit DEVICE LIMIT_MW are used together") + if args.global_limit is not None and min(args.global_limit) < 0: + parser.error("--global-limit values must be non-negative integers") if args.set_energy_threshold is not None and ( args.set_energy_threshold[0] < 0 or not args.set_energy_threshold[0].is_integer() @@ -1894,7 +1901,7 @@ def main(): test_power_module( devices[device_idx], device_idx, - set_global_limit=args.set_global_limit, + set_global_limit=args.global_limit, set_energy_threshold=args.set_energy_threshold, ) From 8dcc7553e6052e07e99544fa28c9cdf0bce3a8c0 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Fri, 9 Oct 2026 10:05:02 +0530 Subject: [PATCH 5/9] feature: Add more power APIs to L0 Sysman python binding Added following power APIs to python binding: 1. zesPowerGetUsage 2. zesPowerGetLimitsExt2 3. zesPowerSetLimitsExt2 4. zesPowerGetEnergyThreshold 5. zesPowerSetEnergyThreshold Along with the zes_energy_threshold_t structure, unit tests, power black box test coverage and README updates. Related-To: SYSM-430 Signed-off-by: Aviral Nigam From 4a74e2d9d69cf9d3b4088d6bb5b831b9fdc942f1 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Fri, 9 Oct 2026 18:00:45 +0530 Subject: [PATCH 6/9] feature: Add more power APIs to L0 Sysman python binding Added following power APIs to python binding: 1. zesPowerGetUsage 2. zesPowerGetLimitsExt2 3. zesPowerSetLimitsExt2 4. zesPowerGetEnergyThreshold 5. zesPowerSetEnergyThreshold Along with the zes_energy_threshold_t structure, unit tests, power black box test coverage and README updates. Related-To: SYSM-430 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index 9ec8eafb..e46a2c57 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -178,7 +178,7 @@ SUPPORTED APIs | `zesPowerGetProperties` | Power | 0.1.2 | None | | `zesPowerGetLimitsExt` | Power | 0.1.2 | Extended power limits API | | `zesPowerSetLimitsExt` | Power | 0.1.2 | Linux: Requires superuser or write permissions for telem nodes | -| `zesPowerGetUsage` | Power | 0.1.3 | None | +| `zesPowerGetUsage` | Power | 0.1.3 | Linux: Requires superuser or read permissions for telem nodes | | `zesPowerGetLimitsExt2` | Power | 0.1.3 | None | | `zesPowerSetLimitsExt2` | Power | 0.1.3 | Linux: Requires superuser | | `zesPowerGetEnergyThreshold` | Power | 0.1.3 | None | @@ -253,6 +253,7 @@ Version 0.1.0 (Initial Release) Notes: Linux: zesPowerGetEnergyCounter + zesPowerGetUsage zesTemperatureGetState zesMemoryGetBandwidth From 17fd12d1a23ea0a312a869164dc565198a486dec Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Mon, 19 Oct 2026 17:14:11 +0530 Subject: [PATCH 7/9] feature: Add more power APIs to L0 Sysman python binding Added following power APIs to python binding: 1. zesPowerGetUsage 2. zesPowerGetLimitsExt2 3. zesPowerSetLimitsExt2 4. zesPowerGetEnergyThreshold 5. zesPowerSetEnergyThreshold Along with the zes_energy_threshold_t structure, unit tests, power black box test coverage and README updates. Related-To: SYSM-430 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 e46a2c57..a0738099 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -178,11 +178,11 @@ SUPPORTED APIs | `zesPowerGetProperties` | Power | 0.1.2 | None | | `zesPowerGetLimitsExt` | Power | 0.1.2 | Extended power limits API | | `zesPowerSetLimitsExt` | Power | 0.1.2 | Linux: Requires superuser or write permissions for telem nodes | -| `zesPowerGetUsage` | Power | 0.1.3 | Linux: Requires superuser or read permissions for telem nodes | -| `zesPowerGetLimitsExt2` | Power | 0.1.3 | None | -| `zesPowerSetLimitsExt2` | Power | 0.1.3 | Linux: Requires superuser | -| `zesPowerGetEnergyThreshold` | Power | 0.1.3 | None | -| `zesPowerSetEnergyThreshold` | Power | 0.1.3 | Linux: Requires superuser | +| `zesPowerGetUsage` | Power | 0.1.4 | Linux: Requires superuser or read permissions for telem nodes | +| `zesPowerGetLimitsExt2` | Power | 0.1.4 | None | +| `zesPowerSetLimitsExt2` | Power | 0.1.4 | Linux: Requires superuser | +| `zesPowerGetEnergyThreshold` | Power | 0.1.4 | None | +| `zesPowerSetEnergyThreshold` | Power | 0.1.4 | Linux: Requires superuser | | **Frequency Management** |-|-|-| | `zesDeviceEnumFrequencyDomains` | Frequency | 0.1.0 | None | | `zesFrequencyGetState` | Frequency | 0.1.0 | None | From 71303f2e7297a974e3b7ae9b6cc07314ef027fec Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Wed, 21 Oct 2026 11:17:32 +0530 Subject: [PATCH 8/9] feature: Add more power APIs to L0 Sysman python binding Added following power APIs to python binding: 1. zesPowerGetUsage 2. zesPowerGetLimitsExt2 3. zesPowerSetLimitsExt2 4. zesPowerGetEnergyThreshold 5. zesPowerSetEnergyThreshold Along with the zes_energy_threshold_t structure, unit tests, power black box test coverage and README updates. Related-To: SYSM-430 Signed-off-by: Aviral Nigam --- bindings/sysman/python/source/pyzes/__init__.py | 6 +++--- .../python/source/pyzes/examples/pyzes_black_box_test.py | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/bindings/sysman/python/source/pyzes/__init__.py b/bindings/sysman/python/source/pyzes/__init__.py index 224aa417..2a7f81ec 100644 --- a/bindings/sysman/python/source/pyzes/__init__.py +++ b/bindings/sysman/python/source/pyzes/__init__.py @@ -699,9 +699,9 @@ class zes_power_energy_counter_t(_PrintableStructure): class zes_energy_threshold_t(_PrintableStructure): _fields_ = [ - ("enable", ze_bool_t), # is the energy threshold enabled - ("threshold", c_double), # energy threshold in Joules, 0.0 if not set - ("processId", c_uint32), # host process ID that set the threshold + ("enable", ze_bool_t), + ("threshold", c_double), + ("processId", c_uint32), ] _fmt_ = {"threshold": "%.3f J"} 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 197d7733..d78d27e4 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 @@ -1283,7 +1283,6 @@ def test_power_module( ) energy_threshold = None - # Requested set operations only apply to the root power domain of the selected device requested_limit = None if set_global_limit is not None and set_global_limit[0] == device_index: requested_limit = set_global_limit[1] @@ -1303,7 +1302,6 @@ def test_power_module( " Skipping power set operations due to insufficient permissions" ) else: - # Power limit control and energy threshold support are reported independently if requested_limit is not None: if not properties.canControl: print_verbose( @@ -1336,7 +1334,6 @@ def test_power_module( f"does not match applied limit {read_back.value} mW" ) - # Restore the limit read before the test rc = pz.zesPowerSetLimitsExt2(power_handles[i], limit_ext2.value) if check_rc(f"zesPowerSetLimitsExt2(power {i}, restore)", rc): print_verbose( @@ -1376,7 +1373,6 @@ def test_power_module( f"process ID: 0x{read_back_threshold.processId:X})" ) - # An energy threshold cannot be disabled, so only an enabled one can be restored if energy_threshold.enable: rc = pz.zesPowerSetEnergyThreshold( power_handles[i], energy_threshold.threshold From 8725297a9f89df0449a0c4f4114cb7e2b762ba10 Mon Sep 17 00:00:00 2001 From: Aviral Nigam Date: Wed, 21 Oct 2026 18:52:34 +0530 Subject: [PATCH 9/9] feature: Add more power APIs to L0 Sysman python binding Added following power APIs to python binding: 1. zesPowerGetUsage 2. zesPowerGetLimitsExt2 3. zesPowerSetLimitsExt2 4. zesPowerGetEnergyThreshold 5. zesPowerSetEnergyThreshold Along with the zes_energy_threshold_t structure, unit tests, power black box test coverage and README updates. Related-To: SYSM-430 Signed-off-by: Aviral Nigam --- bindings/sysman/python/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/bindings/sysman/python/README.md b/bindings/sysman/python/README.md index a0738099..0938fdeb 100644 --- a/bindings/sysman/python/README.md +++ b/bindings/sysman/python/README.md @@ -253,7 +253,6 @@ Version 0.1.0 (Initial Release) Notes: Linux: zesPowerGetEnergyCounter - zesPowerGetUsage zesTemperatureGetState zesMemoryGetBandwidth