mirror of
https://github.com/forkless/XPUSYS-Monitor-NG.git
synced 2026-08-16 08:46:38 +02:00
feat: add PowerShell GPU util fallback when PDH unavailable
This commit is contained in:
@@ -274,10 +274,84 @@ class _PdhQuery:
|
||||
self._ok = False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PowerShell-based GPU utilisation fallback
|
||||
#
|
||||
# Used when PDH counters are unavailable. Calls PowerShell's Get-Counter
|
||||
# cmdlet via subprocess to read the same WDDM GPU engine counters.
|
||||
#
|
||||
# Slower than PDH (~100-300ms per call vs <1ms) but works on any Windows
|
||||
# version with WDDM drivers — no extra dependencies.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
import subprocess as _subprocess
|
||||
|
||||
_POWERSHELL_GPU_CMD = (
|
||||
'try{(Get-Counter \\"\\GPU Engine(*)\\Utilization Percentage\\" '
|
||||
'-MaxSamples 1 -ErrorAction Stop).CounterSamples|'
|
||||
'?{$_.Status -eq 0}|'
|
||||
'Measure-Object CookedValue -Average|'
|
||||
'%{$_.Average}}catch{0}'
|
||||
)
|
||||
|
||||
|
||||
class _PowerShellGpuQuery:
|
||||
"""Fallback GPU utilisation reader via PowerShell Get-Counter."""
|
||||
|
||||
def __init__(self):
|
||||
self._ok = False
|
||||
|
||||
def init(self) -> bool:
|
||||
# Quick self-test: can we launch PowerShell and get a number back?
|
||||
try:
|
||||
val = self._run_query()
|
||||
self._ok = val is not None
|
||||
if self._ok:
|
||||
logger.info(
|
||||
f"XPUSYSMonitor: PowerShell GPU counters OK "
|
||||
f"(test={val:.1f}%)."
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"XPUSYSMonitor: PowerShell GPU counters unavailable."
|
||||
)
|
||||
return self._ok
|
||||
except Exception as exc:
|
||||
logger.debug(f"XPUSYSMonitor: PowerShell GPU init error — {exc}")
|
||||
return False
|
||||
|
||||
def read_gpu_utilization(self) -> float:
|
||||
"""Query total GPU utilisation % via PowerShell."""
|
||||
if not self._ok:
|
||||
return 0.0
|
||||
try:
|
||||
val = self._run_query()
|
||||
return min(val, 100.0) if val is not None else 0.0
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def _run_query() -> float | None:
|
||||
"""Run the PowerShell query and return the average, or None."""
|
||||
try:
|
||||
r = _subprocess.run(
|
||||
["powershell", "-NoProfile", "-Command", _POWERSHELL_GPU_CMD],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
creationflags=0x08000000, # CREATE_NO_WINDOW
|
||||
)
|
||||
if r.returncode != 0:
|
||||
return None
|
||||
val = r.stdout.strip()
|
||||
return float(val) if val else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
__all__ = [
|
||||
"_is_admin",
|
||||
"_get_cpu_info",
|
||||
"_read_cpu_ram_stats",
|
||||
"_read_commit_charge",
|
||||
"_PdhQuery",
|
||||
"_PowerShellGpuQuery",
|
||||
]
|
||||
|
||||
+10
-5
@@ -18,7 +18,7 @@ import sys
|
||||
from typing import Tuple
|
||||
|
||||
from .base import BaseGPUProvider, GPUSnapshot
|
||||
from ._utils import _get_cpu_info, _read_cpu_ram_stats, _PdhQuery, _is_admin
|
||||
from ._utils import _get_cpu_info, _read_cpu_ram_stats, _PdhQuery, _PowerShellGpuQuery, _is_admin
|
||||
|
||||
logger = logging.getLogger("XPUSYSMonitor")
|
||||
|
||||
@@ -52,9 +52,11 @@ class AMDProvider(BaseGPUProvider):
|
||||
self._check_torch()
|
||||
self._check_psutil()
|
||||
|
||||
# Windows PDH — GPU engine utilisation (graceful if unavailable)
|
||||
# Windows GPU utilisation — chain: PDH (fast) -> PowerShell (fallback)
|
||||
self._pdh = _PdhQuery()
|
||||
self._pdh_ok = self._pdh.init()
|
||||
self._ps_gpu = _PowerShellGpuQuery()
|
||||
self._ps_gpu_ok = self._ps_gpu.init() if not self._pdh_ok else False
|
||||
|
||||
# BaseGPUProvider.__init__ starts the polling thread — call last
|
||||
super().__init__(interval_ms=interval_ms)
|
||||
@@ -181,13 +183,16 @@ class AMDProvider(BaseGPUProvider):
|
||||
|
||||
def _read_gpu_load(self) -> float:
|
||||
"""
|
||||
Return GPU utilisation % via Windows PDH API.
|
||||
Return GPU utilisation %.
|
||||
|
||||
Falls back to 0 if PDH is unavailable (non-Windows, or
|
||||
counters not installed by the AMD driver).
|
||||
Tries PDH API first (sub-millisecond, ctypes).
|
||||
Falls back to PowerShell Get-Counter if PDH is unavailable
|
||||
(slower ~100-300ms but works on any Windows WDDM driver).
|
||||
"""
|
||||
if self._pdh_ok:
|
||||
return self._pdh.read_gpu_utilization()
|
||||
if self._ps_gpu_ok:
|
||||
return self._ps_gpu.read_gpu_utilization()
|
||||
return 0.0
|
||||
|
||||
def _read_gpu_freq_mhz(self) -> float:
|
||||
|
||||
Reference in New Issue
Block a user