diff --git a/backend/app/routers/monitor.py b/backend/app/routers/monitor.py
index be6621e..c4d3ffe 100644
--- a/backend/app/routers/monitor.py
+++ b/backend/app/routers/monitor.py
@@ -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
diff --git a/backend/app/routers/vm.py b/backend/app/routers/vm.py
index 1a8cba3..3b07a77 100644
--- a/backend/app/routers/vm.py
+++ b/backend/app/routers/vm.py
@@ -8,6 +8,7 @@ import os
from app.libvirt_conn import conn_pool
from app.utils import parse_vm_info, generate_vm_xml
+from app.routers.monitor import get_vm_runtime_stats
import libvirt
router = APIRouter()
@@ -45,6 +46,12 @@ async def list_vms(host_id: str = Query("local"), include_ip: bool = False):
for dom in domains:
try:
vm_info = parse_vm_info(dom, host_id, include_ip=include_ip)
+ # 添加运行时统计(CPU使用率、内存使用量)
+ cache_key = f"{host_id}_{vm_info['name']}"
+ runtime = get_vm_runtime_stats(dom, cache_key)
+ vm_info["cpu_percent"] = runtime["cpu_percent"]
+ vm_info["memory_rss_mb"] = runtime["memory"].get("rss_mb", 0)
+ vm_info["memory_usage_percent"] = runtime["memory"].get("usage_percent", 0)
vms.append(vm_info)
except Exception as e:
vms.append({
@@ -71,6 +78,12 @@ async def list_all_vms(include_ip: bool = False):
vm_info = parse_vm_info(dom, host.id, include_ip=include_ip)
vm_info["host_id"] = host.id
vm_info["host_name"] = host.name
+ # 添加运行时统计
+ cache_key = f"{host.id}_{vm_info['name']}"
+ runtime = get_vm_runtime_stats(dom, cache_key)
+ vm_info["cpu_percent"] = runtime["cpu_percent"]
+ vm_info["memory_rss_mb"] = runtime["memory"].get("rss_mb", 0)
+ vm_info["memory_usage_percent"] = runtime["memory"].get("usage_percent", 0)
all_vms.append(vm_info)
except Exception:
all_vms.append({
@@ -100,8 +113,9 @@ async def get_vm_detail(name: str, host_id: str = Query("local")):
# 运行中的虚拟机获取更多动态信息
if info["state"] == "running":
try:
- _, _, cpu_time, _ = dom.info()
- info["cpu_time_ns"] = cpu_time
+ # dom.info(): [state, maxMem, memory, nrVirtCpu, cpuTime]
+ dom_info = dom.info()
+ info["cpu_time_ns"] = dom_info[4] # cpuTime 在索引4
except Exception:
pass
diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue
index 2cab734..31578f8 100644
--- a/frontend/src/views/Dashboard.vue
+++ b/frontend/src/views/Dashboard.vue
@@ -63,9 +63,23 @@
-
-
- {{ row.memory_mb }} MB
+
+
+
+
+ {{ row.cpu_percent }}% / {{ row.vcpus }}核
+
+ {{ row.vcpus }}核
+
+
+
+
+
+
+ {{ formatMem(row.memory_rss_mb) }} / {{ formatMem(row.memory_mb) }}
+
+ {{ formatMem(row.memory_mb) }}
+
@@ -188,6 +202,12 @@ function poolColor(pool) {
return '#409eff'
}
+function formatMem(mb) {
+ if (!mb) return '-'
+ if (mb >= 1024) return (mb / 1024).toFixed(1) + ' GB'
+ return mb + ' MB'
+}
+
let refreshTimer = null
async function loadData() {
diff --git a/frontend/src/views/VMList.vue b/frontend/src/views/VMList.vue
index 8ae6989..06914fb 100644
--- a/frontend/src/views/VMList.vue
+++ b/frontend/src/views/VMList.vue
@@ -63,9 +63,23 @@
-
-
- {{ formatMem(row.memory_mb) }}
+
+
+
+
+ {{ row.cpu_percent }}% / {{ row.vcpus }}核
+
+ {{ row.vcpus }}核
+
+
+
+
+
+
+ {{ formatMem(row.memory_rss_mb) }} / {{ formatMem(row.memory_mb) }}
+
+ {{ formatMem(row.memory_mb) }}
+