-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxpu_server.py
More file actions
155 lines (135 loc) · 5.95 KB
/
Copy pathxpu_server.py
File metadata and controls
155 lines (135 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
xpu_server.py — WebSocket / HTTP endpoint for GPU metrics.
Registers a /xpusys/stats route on ComfyUI's PromptServer that returns
the latest GPUSnapshot as JSON. A background task also broadcasts data
to all connected WebSocket clients at the configured interval.
This module is provider-agnostic: it works with any BaseGPUProvider
implementation (Intel, NVIDIA, AMD, ...).
"""
import asyncio
import json
import logging
import os
from aiohttp import web
try:
import folder_paths as _fp
except ImportError:
_fp = None
logger = logging.getLogger("XPUSYSMonitor")
# Will be set by __init__.py after creating the provider
_provider = None
def set_provider(provider):
global _provider
_provider = provider
def _snapshot_to_dict(snap) -> dict:
return {
# VRAM breakdown
"vram_total_gb": round(snap.vram_total_gb, 2),
"vram_free_gb": round(snap.vram_free_gb, 2),
"vram_driver_used_gb": round(snap.vram_driver_used_gb, 2),
"vram_allocated_gb": round(snap.vram_allocated_gb, 2),
"vram_reserved_gb": round(snap.vram_reserved_gb, 2),
# GPU
"gpu_load_pct": round(snap.gpu_load_pct, 1),
"gpu_freq_mhz": round(snap.gpu_freq_mhz, 0),
"gpu_temp_c": round(snap.gpu_temp_c, 1),
# Power
"power_w": round(snap.power_w, 1),
"power_available": snap.power_available,
"tgp_w": round(snap.tgp_w, 1),
"device_name": snap.device_name,
# CPU
"cpu_pct": round(snap.cpu_pct, 1),
"cpu_freq_ghz": round(snap.cpu_freq_ghz, 2),
"cpu_model": snap.cpu_model,
"cpu_threads": snap.cpu_threads,
# RAM
"ram_pct": round(snap.ram_pct, 1),
"ram_total_gb": round(snap.ram_total_gb, 2),
"ram_used_gb": round(snap.ram_used_gb, 2),
"ram_free_gb": round(snap.ram_free_gb, 2),
"commit_used_gb": round(snap.commit_used_gb, 2),
"commit_limit_gb": round(snap.commit_limit_gb, 2),
# Meta
"is_admin": snap.is_admin,
"gpu_vendor": snap.gpu_vendor,
"error": snap.error,
}
def register_routes(server):
"""Call this with the PromptServer instance to attach our HTTP route."""
@server.routes.get("/xpusys/stats")
async def get_stats(request):
if _provider is None:
return web.json_response({"error": "provider not ready"}, status=503)
snap = _provider.get_snapshot()
return web.json_response(_snapshot_to_dict(snap))
@server.routes.post("/xpusys/model_sizes")
async def get_model_sizes(request):
"""
Accept a list of {name, type} model descriptors from the frontend,
resolve each to a physical file, and return {name, size} in GB.
Route name is intentionally distinct from /vram_predict/* used by
the standalone ComfyUI-Vram-Predictor plugin.
"""
if _fp is None:
return web.json_response({"models": []})
try:
data = await request.json()
items = data.get("models", [])
_ALLOWED = {".safetensors", ".gguf", ".ckpt", ".pt", ".pth", ".bin", ".onnx", ".pkl"}
_SEARCH = ["checkpoints", "vae", "loras", "controlnet", "clip",
"upscale_models", "unet", "diffusion_models",
"ultralytics", "annotator", "bbox", "onnx",
"mmaudio", "audio", "rife", "vfi"]
results = []
for m in items:
name = m.get("name", "")
model_path = m.get("path", "")
if not name:
continue
if os.path.splitext(name)[1].lower() not in _ALLOWED:
continue
# 在所有搜索目录中按优先级查找
path = None
for folder in _SEARCH:
# 优先使用完整路径查找(支持子文件夹)
if model_path:
p = _fp.get_full_path(folder, model_path)
if p and os.path.isfile(p):
path = p
break
# 兜底:用文件名在所有目录下递归搜索
if not path:
for folder in _SEARCH:
base_folder = _fp.get_full_path(folder, "")
if base_folder and os.path.isdir(base_folder):
for root, _, files in os.walk(base_folder):
if name in files:
path = os.path.join(root, name)
break
if path:
break
if path and os.path.isfile(path):
size_gb = os.path.getsize(path) / (1024 ** 3)
if size_gb > 0.001:
results.append({"name": name, "size": round(size_gb, 2)})
return web.json_response({"models": results})
except Exception as exc:
logger.debug(f"XPUSYSMonitor: model_sizes error — {exc}")
return web.json_response({"models": []})
logger.info("XPUSYSMonitor: HTTP routes /xpusys/stats and /xpusys/model_sizes registered.")
async def broadcast_loop(server, interval_s: float = 1.0):
"""
Continuously broadcast XPU stats to all WebSocket clients via
ComfyUI's built-in send_json helper.
"""
while True:
await asyncio.sleep(interval_s)
if _provider is None:
continue
try:
snap = _provider.get_snapshot()
data = _snapshot_to_dict(snap)
await server.send_json("xpusys_stats", data)
except Exception as exc:
logger.debug(f"XPUSYSMonitor: broadcast error — {exc}")