diff --git a/src/pynvraw/gpu.py b/src/pynvraw/gpu.py index ef128ad..3873b69 100644 --- a/src/pynvraw/gpu.py +++ b/src/pynvraw/gpu.py @@ -1,3 +1,4 @@ +import collections import ctypes import typing @@ -24,6 +25,11 @@ class ClockDelta(typing.NamedTuple): processor: Delta video: Delta +class PowerDetails(typing.NamedTuple): + power: float + current: float + voltage: float + domains = {NVAPI_GPU_PUBLIC_CLOCK_GRAPHICS: 'core', NVAPI_GPU_PUBLIC_CLOCK_MEMORY: 'memory', NVAPI_GPU_PUBLIC_CLOCK_PROCESSOR: 'processor', NVAPI_GPU_PUBLIC_CLOCK_VIDEO: 'video'} @@ -34,6 +40,7 @@ def __init__(self, handle: NvPhysicalGpu, api: NvAPI): self.api = api self.__name = None self.__sensor_hint = None + self.__power_topo = None def _get_temp(self, *indices): try: @@ -169,3 +176,15 @@ def power(self) -> float: if entry.domain == 0: # GPU consumption return entry.power return None + + def get_rail_powers(self) -> typing.Dict[str, typing.List[PowerDetails]]: + '''Reads power/current/voltage usings of different rails in the GPU.''' + if self.__power_topo is None: + self.__power_topo = self.api.get_detailed_power_topology(self.handle) + raw = self.api.get_detailed_power_status(self.handle, self.__power_topo) + result = collections.defaultdict(list) + for topo, status in zip(self.__power_topo.entries, raw.entries): + if topo.type == 0: + continue + result[topo.rail].append(PowerDetails(power=status.power, current=status.current, voltage=status.voltage)) + return result diff --git a/src/pynvraw/nvapi_api.py b/src/pynvraw/nvapi_api.py index 439edc8..01e9d20 100644 --- a/src/pynvraw/nvapi_api.py +++ b/src/pynvraw/nvapi_api.py @@ -220,6 +220,99 @@ class NV_GPU_TOPOLOGY_STATUS(NvVersioned): ('count', ctypes.c_uint32), ('entries', NV_GPU_TOPOLOGY_ENTRY * NVAPI_MAX_GPU_TOPOLOGY_ENTRIES)] +class NV_DETAILED_POWER_TOPOLOGY(NvVersioned): + class NV_DETAILED_POWER_TOPOLOGY_ENTRY(ctypes.Structure): + _fields_ = [('type', ctypes.c_uint32), + ('_rail', ctypes.c_uint32), + ('unknown', ctypes.c_uint32 * 13)] + RAILS = { + 1: 'OUT_NVVDD', + 2: 'OUT_FBVDD', + 3: 'OUT_FBVDDQ', + 4: 'OUT_FBVDD_Q', + 5: 'OUT_PEXVDD', + 6: 'OUT_A3V3', + 7: 'OUT_3V3NV', + 8: 'OUT_TOTAL_GPU', + 9: 'OUT_FBVDDQ_GPU', + 10: 'OUT_FBVDDQ_MEM', + 11: 'OUT_SRAM', + 222: 'IN_PEX12V1', + 223: 'IN_TOTAL_BOARD2', + 224: 'IN_HIGH_VOLT0', + 225: 'IN_HIGH_VOLT1', + 226: 'IN_NVVDD1', + 227: 'IN_NVVDD2', + 228: 'IN_EXT12V_8PIN2', + 229: 'IN_EXT12V_8PIN3', + 230: 'IN_EXT12V_8PIN4', + 231: 'IN_EXT12V_8PIN5', + 232: 'IN_MISC0', + 233: 'IN_MISC1', + 234: 'IN_MISC2', + 235: 'IN_MISC3', + 236: 'IN_USBC0', + 237: 'IN_USBC1', + 238: 'IN_FAN0', + 239: 'IN_FAN1', + 240: 'IN_SRAM', + 241: 'IN_PWR_SRC_PP', + 242: 'IN_3V3_PP', + 243: 'IN_3V3_MAIN', + 244: 'IN_3V3_AON', + 245: 'IN_TOTAL_BOARD', + 246: 'IN_NVVDD', + 247: 'IN_FBVDD', + 248: 'IN_FBVDDQ', + 249: 'IN_FBVDD_Q', + 250: 'IN_EXT12V_8PIN0', + 251: 'IN_EXT12V_8PIN1', + 252: 'IN_EXT12V_6PIN0', + 253: 'IN_EXT12V_6PIN1', + 254: 'IN_PEX3V3', + 255: 'IN_PEX12V' + } + @property + def rail(self): + return self.RAILS.get(self._rail, str(self._rail)) + + _nv_version_ = 1 + # check: version == 0x10AA8 + _fields_ = [('version', ctypes.c_uint32), + ('something', ctypes.c_uint32), # must be !=0, maybe count?.. + ('unknown1', ctypes.c_uint32 * 2), + ('selector', ctypes.c_uint32), + ('unknown2', ctypes.c_uint32 * 8), + ('entries', NV_DETAILED_POWER_TOPOLOGY_ENTRY * 32), + ('unknown2', ctypes.c_uint32 * 189)] + +class NV_DETAILED_POWER_STATUS(NvVersioned): + class NV_DETAILED_POWER_STATUS_ENTRY(ctypes.Structure): + _fields_ = [('_powerAvg', ctypes.c_int32), + ('_powerMin', ctypes.c_int32), + ('_powerMax', ctypes.c_int32), + ('_current', ctypes.c_int32), + ('_voltage', ctypes.c_int32), + ('_energy1', ctypes.c_int32), + ('_energy2', ctypes.c_int32), + ('unknown', ctypes.c_uint8 * 16)] + @property + def power(self): + return self._powerAvg / 1000.0 + @property + def current(self): + return self._current / 1000.0 + @property + def voltage(self): + return self._voltage / 1000000.0 + + _nv_version_ = 1 + # check: version == 0x1059C + _fields_ = [('version', ctypes.c_uint32), + ('selector', ctypes.c_uint32), + ('unknown1', ctypes.c_uint32 * 5), + ('entries', NV_DETAILED_POWER_STATUS_ENTRY * 32)] + class Method: def __init__(self, offset, restype, *argtypes): self.proto = ctypes.CFUNCTYPE(restype, *argtypes, use_errno=True, use_last_error=True) @@ -267,6 +360,8 @@ class NvAPI: NvAPI_GPU_ClientPowerPoliciesGetStatus = NvMethod(0x70916171, 'NvAPI_GPU_ClientPowerPoliciesGetStatus', NvPhysicalGpu, ctypes.POINTER(NV_GPU_POWER_STATUS)) NvAPI_GPU_ClientPowerPoliciesSetStatus = NvMethod(0xAD95F5ED, 'NvAPI_GPU_ClientPowerPoliciesSetStatus', NvPhysicalGpu, ctypes.POINTER(NV_GPU_POWER_STATUS)) NvAPI_GPU_ClientPowerTopologyGetStatus = NvMethod(0xEDCF624E, 'NvAPI_GPU_ClientPowerTopologyGetStatus', NvPhysicalGpu, ctypes.POINTER(NV_GPU_TOPOLOGY_STATUS)) + NvAPI_GPU_GetDetailedPowerTopology = NvMethod(0xC12EB19E, 'NvAPI_GPU_GetDetailedPowerTopology', NvPhysicalGpu, ctypes.POINTER(NV_DETAILED_POWER_TOPOLOGY)) + NvAPI_GPU_GetDetailedPowerInfo = NvMethod(0xF40238EF, 'NvAPI_GPU_GetDetailedPowerInfo', NvPhysicalGpu, ctypes.POINTER(NV_DETAILED_POWER_STATUS)) def __init__(self): self.NvAPI_Initialize() @@ -353,3 +448,14 @@ def get_topology_status(self, dev: NvPhysicalGpu) -> NV_GPU_TOPOLOGY_STATUS: value = NV_GPU_TOPOLOGY_STATUS() self.NvAPI_GPU_ClientPowerTopologyGetStatus(dev, ctypes.pointer(value)) return value + + def get_detailed_power_topology(self, dev: NvPhysicalGpu) -> NV_DETAILED_POWER_TOPOLOGY: + value = NV_DETAILED_POWER_TOPOLOGY() + self.NvAPI_GPU_GetDetailedPowerTopology(dev, ctypes.pointer(value)) + return value + + def get_detailed_power_status(self, dev: NvPhysicalGpu, topology: NV_DETAILED_POWER_TOPOLOGY) -> NV_DETAILED_POWER_STATUS: + value = NV_DETAILED_POWER_STATUS() + value.selector = topology.selector + self.NvAPI_GPU_GetDetailedPowerInfo(dev, ctypes.pointer(value)) + return value diff --git a/test.py b/test.py index 61640f1..f41a946 100644 --- a/test.py +++ b/test.py @@ -29,8 +29,11 @@ def main(): print(f'power limit: {gpu.power_limit}%') print(f'current power: {gpu.power}%') - #gpu.set_power_limit(80) + + for rail, powers in gpu.get_rail_powers().items(): + for power in powers: + print(f'{rail}: P={power.power:.2f}W I={power.current:.2f}A U={power.voltage:.2f}V') cuda_dev += 1