|
|
@@ -112,32 +112,65 @@ async def monitor_vm(name: str, host_id: str = Query("local")):
|
|
|
|
|
|
|
|
|
def _get_vm_cpu_percent(dom, cache_key: str) -> float:
|
|
|
- """计算虚拟机 CPU 使用率"""
|
|
|
+ """计算虚拟机 CPU 使用率(归一化到 0-100%,基于 vCPU 数量)"""
|
|
|
try:
|
|
|
+ # dom.info() 返回: [state, maxMem, memory, nrVirtCpu, cpuTime]
|
|
|
info1 = dom.info()
|
|
|
- cpu_time1 = info1[2]
|
|
|
+ cpu_time1 = info1[4] # cpuTime 在索引4(之前错误地用了索引2=memory)
|
|
|
+ nr_vcpu = info1[3] or 1 # vCPU 数量
|
|
|
t1 = time.time()
|
|
|
|
|
|
with _cache_lock:
|
|
|
prev = _stats_cache.get(cache_key)
|
|
|
|
|
|
if prev:
|
|
|
- cpu_time0, t0 = prev
|
|
|
+ cpu_time0, t0, prev_vcpu = prev
|
|
|
elapsed = t1 - t0
|
|
|
- cpu_diff = cpu_time1 - cpu_time0
|
|
|
- cpu_percent = round((cpu_diff / 1e9) / elapsed * 100, 1)
|
|
|
- cpu_percent = min(cpu_percent, 100.0)
|
|
|
+ if elapsed > 0:
|
|
|
+ cpu_diff = cpu_time1 - cpu_time0
|
|
|
+ # 除以 vCPU 数量归一化,使满载时 = 100%
|
|
|
+ cpu_percent = round((cpu_diff / 1e9) / elapsed / nr_vcpu * 100, 1)
|
|
|
+ cpu_percent = max(0.0, min(cpu_percent, 100.0))
|
|
|
+ else:
|
|
|
+ cpu_percent = 0.0
|
|
|
else:
|
|
|
cpu_percent = 0.0
|
|
|
|
|
|
with _cache_lock:
|
|
|
- _stats_cache[cache_key] = (cpu_time1, t1)
|
|
|
+ _stats_cache[cache_key] = (cpu_time1, t1, nr_vcpu)
|
|
|
|
|
|
return cpu_percent
|
|
|
except Exception:
|
|
|
return 0.0
|
|
|
|
|
|
|
|
|
+def get_vm_runtime_stats(dom, cache_key: str) -> dict:
|
|
|
+ """获取虚拟机运行时统计(供其他模块调用,如 VM 列表页)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ dict: { cpu_percent, memory: { rss_mb, actual_mb, usage_percent } }
|
|
|
+ """
|
|
|
+ if not dom.isActive():
|
|
|
+ return {"cpu_percent": 0, "memory": {}}
|
|
|
+
|
|
|
+ cpu_percent = _get_vm_cpu_percent(dom, cache_key)
|
|
|
+
|
|
|
+ mem_stats = {}
|
|
|
+ try:
|
|
|
+ raw = dom.memoryStats()
|
|
|
+ mem_stats = {
|
|
|
+ "rss_mb": raw.get("rss", 0) // 1024 if "rss" in raw else 0,
|
|
|
+ "actual_mb": raw.get("actual", 0) // 1024 if "actual" in raw else 0,
|
|
|
+ "usage_percent": round(
|
|
|
+ raw.get("rss", 0) / raw.get("actual", 1) * 100, 1
|
|
|
+ ) if "rss" in raw and "actual" in raw else 0,
|
|
|
+ }
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+
|
|
|
+ return {"cpu_percent": cpu_percent, "memory": mem_stats}
|
|
|
+
|
|
|
+
|
|
|
def _get_vm_disk_stats(dom) -> list:
|
|
|
"""获取虚拟机磁盘IO"""
|
|
|
from lxml import etree
|