208 lines
5.9 KiB
Python
208 lines
5.9 KiB
Python
"""资源监控路由"""
|
||
from fastapi import APIRouter, HTTPException
|
||
from app.libvirt_conn import libvirt_conn
|
||
import libvirt
|
||
import time
|
||
import threading
|
||
|
||
router = APIRouter()
|
||
|
||
# 简易内存缓存
|
||
_stats_cache = {}
|
||
_cache_lock = threading.Lock()
|
||
|
||
|
||
@router.get("/overview")
|
||
async def monitor_overview():
|
||
"""宿主机总览监控"""
|
||
conn = libvirt_conn.conn
|
||
|
||
# 宿主机信息
|
||
host_info = conn.getInfo()
|
||
hostname = conn.getHostname()
|
||
|
||
# CPU 使用率(通过 node info)
|
||
cpu_stats = conn.getCPUStats(-1, 0) # 全局 CPU 统计
|
||
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):
|
||
"""获取虚拟机实时监控数据"""
|
||
conn = libvirt_conn.conn
|
||
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": {}}
|
||
|
||
# CPU 百分比
|
||
cpu_percent = _get_vm_cpu_percent(dom)
|
||
|
||
# 内存
|
||
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
|
||
|
||
# 磁盘IO
|
||
disk_stats = _get_vm_disk_stats(dom)
|
||
|
||
# 网络IO
|
||
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) -> float:
|
||
"""计算虚拟机 CPU 使用率"""
|
||
cache_key = f"cpu_{dom.name()}"
|
||
|
||
try:
|
||
# 第一次采样
|
||
info1 = dom.info()
|
||
cpu_time1 = info1[2]
|
||
t1 = time.time()
|
||
|
||
# 从缓存获取上一次数据
|
||
with _cache_lock:
|
||
prev = _stats_cache.get(cache_key)
|
||
|
||
if prev:
|
||
cpu_time0, t0 = prev
|
||
elapsed = t1 - t0
|
||
cpu_diff = cpu_time1 - cpu_time0
|
||
# CPU时间单位是纳秒
|
||
cpu_percent = round((cpu_diff / 1e9) / elapsed * 100, 1)
|
||
cpu_percent = min(cpu_percent, 100.0)
|
||
else:
|
||
cpu_percent = 0.0
|
||
|
||
# 更新缓存
|
||
with _cache_lock:
|
||
_stats_cache[cache_key] = (cpu_time1, t1)
|
||
|
||
return cpu_percent
|
||
except Exception:
|
||
return 0.0
|
||
|
||
|
||
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
|