mirror of
https://github.com/forkless/XPUSYS-Monitor-NG.git
synced 2026-08-16 00:46:37 +02:00
135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
"""
|
|
providers/base.py — Abstract base class and shared data contract.
|
|
|
|
All GPU provider implementations must subclass BaseGPUProvider and
|
|
implement get_snapshot(). The GPUSnapshot dataclass is the single
|
|
contract between the hardware layer and every consumer (server,
|
|
predictor, frontend).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Shared data snapshot — the contract between providers and consumers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass
|
|
class GPUSnapshot:
|
|
# --- VRAM (all in GB) ---
|
|
vram_total_gb: float = 0.0 # driver total
|
|
vram_free_gb: float = 0.0 # driver free
|
|
vram_driver_used_gb: float = 0.0 # total - free (all consumers)
|
|
vram_allocated_gb: float = 0.0 # torch allocated (ComfyUI workload)
|
|
vram_reserved_gb: float = 0.0 # torch cached / reserved pool
|
|
|
|
# --- GPU ---
|
|
gpu_load_pct: float = 0.0
|
|
gpu_freq_mhz: float = 0.0 # current GPU clock in MHz (0 = unavailable)
|
|
gpu_temp_c: float = -1.0 # GPU core temp in °C (-1 = unavailable)
|
|
|
|
# --- Power ---
|
|
power_w: float = -1.0 # -1 = unavailable
|
|
power_available: bool = False
|
|
tgp_w: float = 0.0 # sustained TGP limit in W (0 = unknown)
|
|
device_name: str = ""
|
|
|
|
# --- CPU ---
|
|
cpu_pct: float = 0.0
|
|
cpu_freq_ghz: float = 0.0
|
|
cpu_model: str = ""
|
|
cpu_threads: int = 0
|
|
|
|
# --- RAM ---
|
|
ram_pct: float = 0.0
|
|
ram_total_gb: float = 0.0
|
|
ram_used_gb: float = 0.0
|
|
ram_free_gb: float = 0.0
|
|
# Windows Commit Charge (≈ Task Manager "已提交")
|
|
commit_used_gb: float = 0.0 # CommitTotal = ullTotalPageFile - ullAvailPageFile
|
|
commit_limit_gb: float = 0.0 # CommitLimit = ullTotalPageFile
|
|
|
|
# --- Meta ---
|
|
is_admin: bool = False
|
|
gpu_vendor: str = "" # "intel" | "nvidia" | "amd" | "unknown"
|
|
error: Optional[str] = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Abstract base provider
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class BaseGPUProvider:
|
|
"""
|
|
Abstract base class for all GPU hardware providers.
|
|
|
|
Subclasses implement _poll() to fill a GPUSnapshot and call
|
|
_update_snapshot(snap) when done. The base class handles
|
|
thread-safe snapshot storage and the polling loop lifecycle.
|
|
|
|
Consumers always call get_snapshot() — they never see the
|
|
concrete provider type.
|
|
"""
|
|
|
|
# Subclasses set this to identify their vendor in GPUSnapshot.gpu_vendor
|
|
GPU_VENDOR: str = "unknown"
|
|
|
|
def __init__(self, interval_ms: int = 1000):
|
|
self._interval = max(100, interval_ms) / 1000.0
|
|
self._snapshot = GPUSnapshot(gpu_vendor=self.GPU_VENDOR)
|
|
self._lock = threading.Lock()
|
|
self._stop = threading.Event()
|
|
|
|
self._thread = threading.Thread(
|
|
target=self._loop, daemon=True, name=f"XPUSYSMonitor-{self.GPU_VENDOR}"
|
|
)
|
|
self._thread.start()
|
|
|
|
# ------------------------------------------------------------------
|
|
# Public API
|
|
# ------------------------------------------------------------------
|
|
|
|
def get_snapshot(self) -> GPUSnapshot:
|
|
"""Return the latest hardware snapshot. Thread-safe."""
|
|
with self._lock:
|
|
return self._snapshot
|
|
|
|
def set_interval(self, ms: int) -> None:
|
|
"""Adjust polling interval at runtime."""
|
|
self._interval = max(100, ms) / 1000.0
|
|
|
|
def stop(self) -> None:
|
|
"""Stop the background polling thread."""
|
|
self._stop.set()
|
|
self._thread.join(timeout=5)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Internal helpers
|
|
# ------------------------------------------------------------------
|
|
|
|
def _update_snapshot(self, snap: GPUSnapshot) -> None:
|
|
"""Atomically replace the stored snapshot. Called from _poll()."""
|
|
with self._lock:
|
|
self._snapshot = snap
|
|
|
|
def _loop(self) -> None:
|
|
"""Background polling loop — calls _poll() every interval."""
|
|
while not self._stop.is_set():
|
|
try:
|
|
self._poll()
|
|
except Exception as exc:
|
|
snap = GPUSnapshot(gpu_vendor=self.GPU_VENDOR, error=str(exc))
|
|
self._update_snapshot(snap)
|
|
self._stop.wait(self._interval)
|
|
|
|
def _poll(self) -> None:
|
|
"""
|
|
Override in subclass: collect hardware data and call
|
|
_update_snapshot(snap) with a freshly built GPUSnapshot.
|
|
"""
|
|
raise NotImplementedError
|