1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 04:36:48 +00:00
webvirtcloud/computes/capacity.py
catborise 1436ed13af feat(computes): the Computes page shows what each host has given its VMs
Each host is a row of what it has committed, read live from the host (in
parallel, by a 10 s deadline):

- Host: a status dot, the name, its address and details; when it cannot
  be reached, why.
- vCPUs: the overcommit ratio, large (warning above 2:1, red above
  4:1), over "N vCPUs on M CPUs" and the running VMs' share.
- Memory: memory given to all VMs against the host's memory, as one bar:
  running VMs dark, shut-off VMs light (they take their memory again
  when they start), and a line where the host's memory ends; red when
  the VMs were given more than the host has.
- Storage: the default pool (or the first) with its used share and free
  space; the others, a host may have dozens, open on a click.

A host that cannot be reached, or does not answer in time, says so in
one line instead. vCPUs and Memory sort by ratio and share. A host whose
read hangs is not read again until that read ends, so a stuck host keeps
at most one thread. The table's hidden status texts stay inside it, so
the page does not widen on a phone.
2026-10-25 11:57:09 +03:00

82 lines
3 KiB
Python

"""
What each compute has given its VMs: vCPUs and memory of all VMs (a
shut-off VM takes its share again when it starts) and of the running ones,
against the host's CPUs and memory, and the free space of its active
storage pools. Read live from libvirt for the Computes page.
"""
import threading
import time
from concurrent import futures
from libvirt import VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE, VIR_DOMAIN_SHUTOFF, libvirtError
MAX_WORKERS = 8
# A libvirt call may hang: its thread outlives the page. Until it ends, its
# compute is not read again, so the threads stay at one per compute.
_reading = set()
_reading_lock = threading.Lock()
def read(compute):
"""The compute's capacity, or None when it cannot be reached. A VM that
goes while it is read is skipped."""
if compute.status is not True:
return None
conn = compute.connection
_arch, memory_mib, cpus, *_ = conn.getInfo()
vcpus = {"all": 0, "running": 0}
assigned = {"all": 0, "running": 0}
for dom in conn.listAllDomains(0):
try:
state, _max_memory, memory_kib, vcpu_count, _cpu_time = dom.info()
except libvirtError:
continue
for share in ("all", "running") if state != VIR_DOMAIN_SHUTOFF else ("all",):
vcpus[share] += vcpu_count
assigned[share] += memory_kib * 1024
pools = []
for pool in conn.listAllStoragePools(VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE):
try:
_state, size, _allocation, available = pool.info()
except libvirtError:
continue
pools.append((pool.name(), size, available))
return {"cpus": cpus, "memory": memory_mib * 1024 * 1024, "vcpus": vcpus, "assigned": assigned,
"pools": sorted(pools)}
def _read_once(compute):
try:
return read(compute)
finally:
with _reading_lock:
_reading.discard(compute.id)
def read_all(computes, deadline=10.0):
"""{compute id: capacity} of the computes read by the deadline (seconds),
in parallel; a compute down, failing, late, or still being read for an
earlier page is left out."""
with _reading_lock:
computes = [compute for compute in computes if compute.id not in _reading]
_reading.update(compute.id for compute in computes)
pool = futures.ThreadPoolExecutor(max_workers=MAX_WORKERS)
end = time.monotonic() + deadline
try:
reads = {pool.submit(_read_once, compute): compute.id for compute in computes}
done, _late = futures.wait(reads, timeout=max(0, end - time.monotonic()))
found = {}
for future in done:
try:
result = future.result()
except libvirtError:
continue
if result is not None:
found[reads[future]] = result
return found
finally:
pool.shutdown(wait=False, cancel_futures=True)
with _reading_lock: # a read cancelled before it started ends nothing itself
_reading.difference_update(cid for future, cid in reads.items() if future.cancelled())