Explorar o código

v0.0.3: 修复CPU/内存显示 - 列表和仪表盘显示使用量/总量格式,修复详情页CPU数据获取bug

admin hai 1 semana
pai
achega
f4f5475ffb

+ 40 - 7
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

+ 16 - 2
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
 

+ 23 - 3
frontend/src/views/Dashboard.vue

@@ -63,9 +63,23 @@
                 </el-tag>
               </template>
             </el-table-column>
-            <el-table-column prop="vcpus" label="CPU" width="80" align="center" />
-            <el-table-column label="内存" width="110" align="center">
-              <template #default="{ row }">{{ row.memory_mb }} MB</template>
+            <el-table-column label="CPU" width="120" align="center">
+              <template #default="{ row }">
+                <span v-if="row.state === 'running' && row.cpu_percent !== undefined">
+                  <span :style="{ color: row.cpu_percent > 80 ? '#f56c6c' : row.cpu_percent > 50 ? '#e6a23c' : '#67c23a' }">
+                    {{ row.cpu_percent }}%</span> / {{ row.vcpus }}核
+                </span>
+                <span v-else>{{ row.vcpus }}核</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="内存" width="150" align="center">
+              <template #default="{ row }">
+                <span v-if="row.state === 'running' && row.memory_rss_mb">
+                  <span :style="{ color: row.memory_usage_percent > 80 ? '#f56c6c' : '#67c23a' }">
+                    {{ formatMem(row.memory_rss_mb) }}</span> / {{ formatMem(row.memory_mb) }}
+                </span>
+                <span v-else>{{ formatMem(row.memory_mb) }}</span>
+              </template>
             </el-table-column>
             <el-table-column label="IP 地址" min-width="130">
               <template #default="{ row }">
@@ -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() {

+ 17 - 3
frontend/src/views/VMList.vue

@@ -63,9 +63,23 @@
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column prop="vcpus" label="CPU" width="80" align="center" />
-      <el-table-column label="内存" width="110" align="center">
-        <template #default="{ row }">{{ formatMem(row.memory_mb) }}</template>
+      <el-table-column label="CPU" width="120" align="center">
+        <template #default="{ row }">
+          <span v-if="row.state === 'running' && row.cpu_percent !== undefined">
+            <span :style="{ color: row.cpu_percent > 80 ? '#f56c6c' : row.cpu_percent > 50 ? '#e6a23c' : '#67c23a' }">
+              {{ row.cpu_percent }}%</span> / {{ row.vcpus }}核
+          </span>
+          <span v-else>{{ row.vcpus }}核</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="内存" width="150" align="center">
+        <template #default="{ row }">
+          <span v-if="row.state === 'running' && row.memory_rss_mb">
+            <span :style="{ color: row.memory_usage_percent > 80 ? '#f56c6c' : '#67c23a' }">
+              {{ formatMem(row.memory_rss_mb) }}</span> / {{ formatMem(row.memory_mb) }}
+          </span>
+          <span v-else>{{ formatMem(row.memory_mb) }}</span>
+        </template>
       </el-table-column>
       <el-table-column label="IP 地址" min-width="140">
         <template #default="{ row }">