Files
Anonymzx-BangtrixToolkit/utils/amd_gpu_counter.py
T
2026-05-16 06:34:19 +07:00

312 lines
13 KiB
Python

"""
GPU Performance Counters via PowerShell
========================================
Windows-only: reads GPU utilization, VRAM usage, and temperature
via built-in performance counters and WMI.
No dependencies required beyond built-in Python + PowerShell.
"""
import subprocess
import logging
import platform
import json
import re
logger = logging.getLogger(__name__)
class GPUCounterMonitor:
"""Monitor GPU metrics via PowerShell performance counters and WMI"""
def __init__(self):
self.available = False
self.gpu_count = 0
self.gpu_names = []
self.method = "powershell-counters"
self._detect()
def _detect(self):
"""Detect GPU availability via WMI"""
if platform.system() != "Windows":
return
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject Win32_VideoController | "
"Select-Object Name, AdapterRAM | ConvertTo-Json"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=5,
creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
data = json.loads(result.stdout.strip())
if isinstance(data, dict):
data = [data]
for gpu in data:
name = gpu.get('Name', 'Unknown GPU').strip()
if name:
self.gpu_names.append(name)
self.gpu_count = len(self.gpu_names)
if self.gpu_count > 0:
self.available = True
logger.info(f"GPU Counters: {self.gpu_count} GPU(s): {self.gpu_names}")
return
except Exception as e:
logger.debug(f"GPU detection error: {e}")
def get_gpu_utilization(self, gpu_id: int = 0) -> float:
"""Get GPU utilization % via performance counters"""
# Method 1: Query GPU engine utilization (sum of all 3D engines)
try:
ps_cmd = (
"Get-Counter '\\GPU Engine(*engtype_3D)\\*' -ErrorAction SilentlyContinue "
"| Select-Object -ExpandProperty CounterSamples "
"| Measure-Object -Property CookedValue -Sum "
"| Select-Object -ExpandProperty Sum"
)
result = subprocess.run(
["powershell", "-NoProfile", "-Command", ps_cmd],
capture_output=True, text=True, timeout=3,
creationflags=subprocess.CREATE_NO_WINDOW
)
if result.returncode == 0 and result.stdout.strip():
val = float(result.stdout.strip())
return min(100.0, max(0.0, val / 100000.0))
except Exception as e:
logger.debug(f"GPU utilization error: {e}")
# Method 2: Try GPU Adapter utilization percentage directly
try:
ps_cmd = (
"Get-Counter '\\GPU Adapter(*)\\*' -ErrorAction SilentlyContinue "
"| Select-Object -ExpandProperty CounterSamples "
"| Where-Object { $_.Path -match 'Utilization Percentage' } "
"| Select-Object -First 1 -ExpandProperty CookedValue"
)
result = subprocess.run(
["powershell", "-NoProfile", "-Command", ps_cmd],
capture_output=True, text=True, timeout=3,
creationflags=subprocess.CREATE_NO_WINDOW
)
if result.returncode == 0 and result.stdout.strip():
val = float(result.stdout.strip())
return min(100.0, max(0.0, val))
except:
pass
return 0.0
def _detect_hardware_monitors(self):
"""Detect which hardware monitoring tools are installed"""
monitors = []
# LibreHardwareMonitor / OpenHardwareMonitor WMI namespace
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject -Namespace 'root/LibreHardwareMonitor' -Class Sensor 2>$null | "
"Select-Object -First 1 | ConvertTo-Json"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3,
creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip() and result.stdout.strip() != 'null':
monitors.append("LibreHardwareMonitor")
except:
pass
if 'LibreHardwareMonitor' not in monitors:
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject -Namespace 'root/OpenHardwareMonitor' -Class Sensor 2>$null | "
"Select-Object -First 1 | ConvertTo-Json"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3,
creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip() and result.stdout.strip() != 'null':
monitors.append("OpenHardwareMonitor")
except:
pass
# MSI Afterburner shared memory
try:
import ctypes
try:
import ctypes.wintypes
handle = ctypes.windll.kernel32.OpenFileMappingW(
0x0001, False, "MSE_Afterburner_SharedMemory"
)
if handle:
monitors.append("MSIAfterburner")
ctypes.windll.kernel32.CloseHandle(handle)
except:
pass
except:
pass
self._hw_monitors = monitors
if monitors:
logger.info(f"GPU Counters: Detected HW monitors: {monitors}")
return monitors
def get_gpu_temperature(self, gpu_id: int = 0) -> float:
"""Get GPU temperature via LibreHardwareMonitor, OpenHardwareMonitor, or other tools"""
# Try LibreHardwareMonitor first (if installed)
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject -Namespace 'root/LibreHardwareMonitor' -Class Sensor "
"2>$null | Where-Object { $_.SensorType -eq 'Temperature' -and "
"$_.Name -match 'GPU' } | Select-Object -First 1 -ExpandProperty Value"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3, creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
val = float(result.stdout.strip())
if val > 0:
return val
except:
pass
# Try OpenHardwareMonitor
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject -Namespace 'root/OpenHardwareMonitor' -Class Sensor "
"2>$null | Where-Object { $_.SensorType -eq 'Temperature' -and "
"$_.Name -match 'GPU' } | Select-Object -First 1 -ExpandProperty Value"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3, creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
val = float(result.stdout.strip())
if val > 0:
return val
except:
pass
return 0.0
def get_fan_speed(self, gpu_id: int = 0) -> int:
"""Get fan speed from hardware monitors"""
# LibreHardwareMonitor
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject -Namespace 'root/LibreHardwareMonitor' -Class Sensor "
"2>$null | Where-Object { $_.SensorType -eq 'Fan' -and "
"$_.Name -match 'GPU' } | Select-Object -First 1 -ExpandProperty Value"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3, creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
val = float(result.stdout.strip())
if val > 0:
return int(val)
except:
pass
# OpenHardwareMonitor
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"Get-WmiObject -Namespace 'root/OpenHardwareMonitor' -Class Sensor "
"2>$null | Where-Object { $_.SensorType -eq 'Fan' -and "
"$_.Name -match 'GPU' } | Select-Object -First 1 -ExpandProperty Value"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3, creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
val = float(result.stdout.strip())
if val > 0:
return int(val)
except:
pass
return 0
def get_gpu_vram_mb(self, gpu_id: int = 0) -> dict:
"""
Get VRAM info from WMI.
Works for dedicated GPUs and AMD APU shared memory.
"""
# Method 1: Registry query (dedicated + APU)
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"$paths = Get-ChildItem 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\"
"{4d36e968-e325-11ce-bfc1-08002be10318}\\*' -ErrorAction SilentlyContinue; "
"foreach ($p in $paths) { "
" $uma = $p.GetValue('UMA_FB_SIZE'); "
" if ($uma) { $uma * 1MB; break } "
" $gpuMem = $p.GetValue('HardwareInformation.GpuMemorySize'); "
" if ($gpuMem) { $gpuMem; break } "
" $mem = $p.GetValue('HardwareInformation.qwMemorySize'); "
" if ($mem) { $mem; break } "
"}"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3,
creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
total_bytes = int(result.stdout.strip())
total_mb = total_bytes / (1024 * 1024)
if total_mb > 0:
return {'total_mb': total_mb, 'used_mb': 0, 'free_mb': total_mb}
except:
pass
# Method 2: WMI AdapterRAM
try:
cmd = [
"powershell", "-NoProfile", "-Command",
"$gpu = Get-WmiObject Win32_VideoController | Select-Object -First 1; "
"if ($gpu) { "
" $total = $gpu.AdapterRAM; "
" if (-not $total -or $total -eq 0) { $total = 512MB }; "
" $total "
"}"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3,
creationflags=subprocess.CREATE_NO_WINDOW)
if result.returncode == 0 and result.stdout.strip():
total_bytes = int(result.stdout.strip())
total_mb = total_bytes / (1024 * 1024)
if total_mb > 0:
return {'total_mb': total_mb, 'used_mb': 0, 'free_mb': total_mb}
except:
pass
return {'total_mb': 0, 'used_mb': 0, 'free_mb': 0}
def get_all_metrics(self, gpu_id: int = 0) -> dict:
"""Get all metrics for a GPU in one call"""
return {
'utilization': self.get_gpu_utilization(gpu_id),
'temperature': self.get_gpu_temperature(gpu_id),
'vram': self.get_gpu_vram_mb(gpu_id),
'fan_speed': self.get_fan_speed(gpu_id),
'gpu_name': self.gpu_names[gpu_id] if gpu_id < len(self.gpu_names) else f"GPU {gpu_id}",
}
# Singleton
_gpu_counter = None
def get_gpu_counter() -> GPUCounterMonitor:
global _gpu_counter
if _gpu_counter is None:
_gpu_counter = GPUCounterMonitor()
return _gpu_counter
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
mon = get_gpu_counter()
if mon.available:
print(f"GPU Counters: {mon.gpu_count} GPU(s)")
for i, name in enumerate(mon.gpu_names):
print(f"\n GPU {i}: {name}")
metrics = mon.get_all_metrics(i)
print(f" Utilization: {metrics['utilization']:.1f}%")
print(f" Temperature: {metrics['temperature']:.1f}°C")
print(f" VRAM: {metrics['vram']['total_mb']:.0f} MB total")
print(f" Fan: {metrics['fan_speed']}%")
else:
print("GPU Counters: Not available")