mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
A host's capacity (Computes page, new VM dialog) came from get_host_instances, which listed the VMs by ID and by name and then looked each up, read its XML and its info: four calls a VM, for every host on the page. It now uses the VM lists' get_domain_rows: the states in one call and each VM's XML once. get_host_instances had no other caller and is gone. Checked on QEMU against the per-VM info of every domain (running, paused, shut off): the same vCPUs and memory. A VM gone while the host is read is left out instead of making the host's capacity unreadable.
140 lines
5.5 KiB
Python
140 lines
5.5 KiB
Python
import time
|
|
|
|
from libvirt import (
|
|
libvirtError,
|
|
VIR_DOMAIN_BLOCKED,
|
|
VIR_DOMAIN_PAUSED,
|
|
VIR_DOMAIN_RUNNING,
|
|
VIR_DOMAIN_SHUTOFF,
|
|
VIR_NODE_CPU_STATS_ALL_CPUS,
|
|
)
|
|
|
|
from vrtManager.connection import wvmConnect
|
|
from vrtManager.util import get_xml_path
|
|
|
|
|
|
# a VM's state as the host's VM counts group it; others count as "other"
|
|
VM_STATES = {
|
|
VIR_DOMAIN_RUNNING: "running",
|
|
VIR_DOMAIN_BLOCKED: "running", # running, waiting on a resource
|
|
VIR_DOMAIN_PAUSED: "paused",
|
|
VIR_DOMAIN_SHUTOFF: "shutoff",
|
|
}
|
|
|
|
# the times of virNodeGetCPUStats that add up to the elapsed CPU time; guest is
|
|
# already part of user and utilization is a percentage
|
|
CPU_TIMES = ("kernel", "user", "idle", "iowait")
|
|
|
|
|
|
def cpu_percent(before, after):
|
|
"""Busy share in percent between two (idle, total) samples, or None when
|
|
the second does not follow the first."""
|
|
idle = after[0] - before[0]
|
|
total = after[1] - before[1]
|
|
if total <= 0 or not 0 <= idle <= total:
|
|
return None
|
|
return round(100 * (total - idle) / total, 1)
|
|
|
|
|
|
def cpu_version(doc):
|
|
for info in doc.xpath("/sysinfo/processor/entry"):
|
|
elem = info.xpath("@name")[0]
|
|
if elem == "version":
|
|
return info.text
|
|
return "Unknown"
|
|
|
|
|
|
class wvmHostDetails(wvmConnect):
|
|
def get_memory_usage(self):
|
|
"""
|
|
Function return memory usage on node.
|
|
"""
|
|
all_mem = self.host_info[1] * 1048576
|
|
freemem = self.wvm.getMemoryStats(-1, 0)
|
|
if isinstance(freemem, dict):
|
|
free = (freemem["buffers"] + freemem["free"] + freemem["cached"]) * 1024
|
|
percent = abs(100 - free * 100 // all_mem)
|
|
usage = all_mem - free
|
|
return {"total": all_mem, "usage": usage, "percent": percent}
|
|
else:
|
|
return {"total": None, "usage": None, "percent": None}
|
|
|
|
def _cpu_times(self):
|
|
"""(idle, total) nanoseconds of all host CPUs, from one reading."""
|
|
stats = self.wvm.getCPUStats(VIR_NODE_CPU_STATS_ALL_CPUS, 0)
|
|
return stats["idle"], sum(stats.get(name, 0) for name in CPU_TIMES)
|
|
|
|
def get_cpu_usage(self, previous=None):
|
|
"""
|
|
Busy share of all host CPUs in percent since previous, an (idle, total,
|
|
time) sample this method returned, or over one second when there is
|
|
none or it does not lead to this one (the host restarted). Also returns
|
|
the closing sample and the seconds the usage covers.
|
|
"""
|
|
times = self._cpu_times()
|
|
now = time.time()
|
|
usage = None
|
|
if previous is not None:
|
|
usage = cpu_percent(previous[:2], times)
|
|
start = previous[2]
|
|
if usage is None:
|
|
before, start = times, now
|
|
time.sleep(1)
|
|
times = self._cpu_times()
|
|
now = time.time()
|
|
usage = cpu_percent(before, times)
|
|
return {"usage": usage, "sample": (*times, now), "window": now - start}
|
|
|
|
def get_node_info(self):
|
|
"""
|
|
Function return host server information: hostname, cpu, memory, ...
|
|
"""
|
|
info = [self.wvm.getHostname()] # hostname
|
|
info.append(self.host_info[0]) # architecture
|
|
info.append(self.host_info[1] * 1048576) # memory
|
|
info.append(self.host_info[2]) # cpu core count
|
|
try:
|
|
info.append(get_xml_path(self.wvm.getSysinfo(0), func=cpu_version)) # cpu version
|
|
except libvirtError: # not every driver or host gives its sysinfo
|
|
info.append(None)
|
|
info.append(self.wvm.getURI()) # uri
|
|
return info
|
|
|
|
def get_capacity(self):
|
|
"""
|
|
What the host has given its VMs: vCPUs and memory (bytes) of all VMs
|
|
and of the running ones (paused ones too: they keep theirs), against
|
|
its CPUs and memory. A shut-off VM takes its share again when it
|
|
starts. Also its VMs by state: running, paused, shut off and other
|
|
(crashed, suspended, shutting down).
|
|
"""
|
|
vcpus = {"all": 0, "running": 0}
|
|
assigned = {"all": 0, "running": 0}
|
|
vms = {"running": 0, "paused": 0, "shutoff": 0, "other": 0}
|
|
# the VM lists' read: the states in one call, each VM's XML once
|
|
for (state, _max_memory, memory, vcpu), _title in self.get_domain_rows().values():
|
|
for share in ("all",) if state == VIR_DOMAIN_SHUTOFF else ("all", "running"):
|
|
vcpus[share] += vcpu
|
|
assigned[share] += memory * 1024
|
|
vms[VM_STATES.get(state, "other")] += 1
|
|
return {"cpus": self.host_info[2], "memory": self.host_info[1] * 1048576,
|
|
"vcpus": vcpus, "assigned": assigned, "vms": vms}
|
|
|
|
def get_cpu_topology(self):
|
|
"""
|
|
The host's logical CPUs and MHz, and from the CPUs of its NUMA cells
|
|
in its capabilities, the NUMA nodes, sockets and physical cores (None
|
|
when it does not say). Not virNodeGetInfo's topology: it makes one up
|
|
for hosts it cannot describe, such as asymmetric NUMA.
|
|
"""
|
|
|
|
def topology(doc):
|
|
cpus = doc.xpath("/capabilities/host/topology/cells/cell/cpus/cpu[@socket_id]")
|
|
return {
|
|
"nodes": len(doc.xpath("/capabilities/host/topology/cells/cell")) or None,
|
|
"sockets": len({cpu.get("socket_id") for cpu in cpus}) or None,
|
|
# a core is one within its socket and die
|
|
"cores": len({(cpu.get("socket_id"), cpu.get("die_id"), cpu.get("core_id")) for cpu in cpus}) or None,
|
|
}
|
|
|
|
return {**get_xml_path(self.get_cap_xml(), func=topology), "cpus": self.host_info[2], "mhz": self.host_info[3]}
|