"""资源监控路由""" from fastapi import APIRouter, HTTPException, Query from app.libvirt_conn import conn_pool import libvirt import time import threading router = APIRouter() # 简易内存缓存 _stats_cache = {} _cache_lock = threading.Lock() @router.get("/overview") async def monitor_overview(host_id: str = Query("local")): """宿主机总览监控""" conn = conn_pool.get_conn(host_id) host_info = conn.getInfo() hostname = conn.getHostname() cpu_stats = conn.getCPUStats(-1, 0) cpu_total = cpu_stats.get("user", 0) + cpu_stats.get("system", 0) + cpu_stats.get("idle", 0) cpu_used = cpu_stats.get("user", 0) + cpu_stats.get("system", 0) cpu_percent = round(cpu_used / cpu_total * 100, 1) if cpu_total > 0 else 0 memory_total_kb = host_info[1] try: with open("/proc/meminfo", "r") as f: meminfo = {} for line in f: parts = line.split() if len(parts) >= 2: meminfo[parts[0].rstrip(":")] = int(parts[1]) mem_total_mb = meminfo.get("MemTotal", 0) // 1024 mem_available_mb = meminfo.get("MemAvailable", 0) // 1024 mem_used_mb = mem_total_mb - mem_available_mb mem_percent = round(mem_used_mb / mem_total_mb * 100, 1) if mem_total_mb > 0 else 0 except Exception: mem_total_mb = memory_total_kb // 1024 mem_used_mb = 0 mem_available_mb = mem_total_mb mem_percent = 0 domains = conn.listAllDomains(0) running = sum(1 for d in domains if d.isActive()) stopped = len(domains) - running return { "hostname": hostname, "cpu": { "cores": host_info[2], "speed_mhz": host_info[3], "usage_percent": cpu_percent, }, "memory": { "total_mb": mem_total_mb, "used_mb": mem_used_mb, "available_mb": mem_available_mb, "usage_percent": mem_percent, }, "vms": { "total": len(domains), "running": running, "stopped": stopped, }, } @router.get("/vm/{name}") async def monitor_vm(name: str, host_id: str = Query("local")): """获取虚拟机实时监控数据""" conn = conn_pool.get_conn(host_id) try: dom = conn.lookupByName(name) except libvirt.libvirtError: raise HTTPException(status_code=404, detail=f"虚拟机 '{name}' 不存在") if not dom.isActive(): return {"name": name, "state": "stopped", "cpu_percent": 0, "memory": {}} cache_key = f"{host_id}_{name}" 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, "available_mb": raw.get("available", 0) // 1024 if "available" 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 disk_stats = _get_vm_disk_stats(dom) net_stats = _get_vm_net_stats(dom) return { "name": name, "state": "running", "cpu_percent": cpu_percent, "memory": mem_stats, "disk": disk_stats, "network": net_stats, } def _get_vm_cpu_percent(dom, cache_key: str) -> float: """计算虚拟机 CPU 使用率(归一化到 0-100%,基于 vCPU 数量)""" try: # dom.info() 返回: [state, maxMem, memory, nrVirtCpu, cpuTime] info1 = dom.info() 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_vcpu = prev elapsed = t1 - t0 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, 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 stats = [] try: xml = etree.fromstring(dom.XMLDesc(0).encode()) for disk in xml.findall(".//disk[@device='disk']"): target = disk.find("target") if target is not None: dev = target.get("dev", "") try: s = dom.blockStats(dev) stats.append({ "dev": dev, "read_bytes": s[1], "write_bytes": s[3], "read_requests": s[0], "write_requests": s[2], }) except Exception: pass except Exception: pass return stats def _get_vm_net_stats(dom) -> list: """获取虚拟机网络IO""" from lxml import etree stats = [] try: xml = etree.fromstring(dom.XMLDesc(0).encode()) for iface in xml.findall(".//interface"): target = iface.find("target") if target is not None: dev = target.get("dev", "") try: s = dom.interfaceStats(dev) stats.append({ "dev": dev, "rx_bytes": s[0], "tx_bytes": s[4], "rx_packets": s[1], "tx_packets": s[5], "rx_errors": s[2], "tx_errors": s[6], }) except Exception: pass except Exception: pass return stats