1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

perf(computes): the host's info is read once per request

The overview read the host's info four times, now once (12 libvirt calls).
A connection object keeps it next to the capabilities, and the VM page and
VM stats take it from there; the VM page's cached_reads block holds only
the VM's own data. Free memory is still read each time.
This commit is contained in:
catborise 2026-10-09 15:52:11 +03:00
parent 9adc808d26
commit 57f592df70
6 changed files with 57 additions and 32 deletions

View file

@ -403,6 +403,11 @@ class wvmConnect(object):
"""The host's capabilities, read once per object (a request): most getters below parse them"""
return self.wvm.getCapabilities()
@cached_property
def host_info(self):
"""The host's info (architecture, memory in MiB, CPUs, ...), read once per object"""
return self.wvm.getInfo()
@cached_property
def dom_cap_xmls(self):
"""The domain capabilities read so far, by emulator, arch, machine and domain type"""

View file

@ -34,7 +34,7 @@ class wvmHostDetails(wvmConnect):
"""
Function return memory usage on node.
"""
all_mem = self.wvm.getInfo()[1] * 1048576
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
@ -78,9 +78,9 @@ class wvmHostDetails(wvmConnect):
Function return host server information: hostname, cpu, memory, ...
"""
info = [self.wvm.getHostname()] # hostname
info.append(self.wvm.getInfo()[0]) # architecture
info.append(self.wvm.getInfo()[1] * 1048576) # memory
info.append(self.wvm.getInfo()[2]) # cpu core count
info.append(self.host_info[0]) # architecture
info.append(self.host_info[1] * 1048576) # memory
info.append(self.host_info[2]) # cpu core count
info.append(
get_xml_path(self.wvm.getSysinfo(0), func=cpu_version)
) # cpu version

View file

@ -490,13 +490,13 @@ class wvmInstance(wvmConnect):
@contextlib.contextmanager
def cached_reads(self):
"""
Within the block the domain XML (per flags value), the VM's state,
the host's info and each disk's volume are read from libvirt once
(the host's capabilities are read once per object anyway), for pages
that only read: a VM page reads the XML in some 30 getters and each
disk in two lists. Code that changes the VM must not run inside: most
changes (devices, QoS, memory) do not go through _defineXML, the only
call that drops the cache.
Within the block the domain XML (per flags value), the VM's state and
each disk's volume are read from libvirt once (the host's info and
capabilities are read once per object anyway), for pages that only
read: a VM page reads the XML in some 30 getters and each disk in two
lists. Code that changes the VM must not run inside: most changes
(devices, QoS, memory) do not go through _defineXML, the only call
that drops the cache.
"""
if self._read_cache is not None:
raise RuntimeError("cached_reads blocks do not nest")
@ -522,9 +522,6 @@ class wvmInstance(wvmConnect):
self._read_cache.clear()
return self.wvm.defineXML(xml)
def _host_info(self):
return self._cached("host info", self.wvm.getInfo)
def get_status(self):
"""
VIR_DOMAIN_NOSTATE = 0
@ -617,11 +614,11 @@ class wvmInstance(wvmConnect):
return description or ""
def get_max_memory(self):
return self._host_info()[1] * 1048576
return self.host_info[1] * 1048576
def get_max_cpus(self):
"""Get number of physical CPUs."""
hostinfo = self._host_info()
hostinfo = self.host_info
pcpus = hostinfo[4] * hostinfo[5] * hostinfo[6] * hostinfo[7]
return range(1, int(pcpus + 1))
@ -1308,7 +1305,7 @@ class wvmInstance(wvmConnect):
"nics": [{"dev": i, "rx": 0, "tx": 0} for i in range(len(nics))],
}
key = [self.instance.ID(), self._host_info()[2], disks, nics]
key = [self.instance.ID(), self.host_info[2], disks, nics]
unread = (-1,) * 8
def read():

View file

@ -1,6 +1,7 @@
"""A wvmConnect reads the host's capabilities once, and its domain
"""A wvmConnect reads the host's info and capabilities once, and its domain
capabilities once per emulator, arch, machine and domain type: the create
page's getters parse them some 60 times. An object lives for one request."""
page's getters parse the capabilities some 60 times, the overview reads the
info four times. An object lives for one request."""
import unittest
@ -10,6 +11,7 @@ if not settings.configured:
settings.configure(MAC_OUI="52:54:10")
from vrtManager.connection import wvmConnect
from vrtManager.hostdetails import wvmHostDetails
CAPABILITIES = """<capabilities><host><cpu><arch>x86_64</arch></cpu></host>
<guest><os_type>hvm</os_type><arch name='x86_64'><emulator>/usr/bin/qemu-kvm</emulator>
@ -25,13 +27,30 @@ class FakeConn:
self.reads.append("capabilities")
return CAPABILITIES
def getInfo(self):
self.reads.append("info")
return ["x86_64", 2048, 4, 0, 1, 2, 2, 1]
def getMemoryStats(self, cell, flags):
self.reads.append("memory")
return {"buffers": 0, "free": 1048576, "cached": 0}
def getHostname(self):
return "host"
def getSysinfo(self, flags):
return "<sysinfo/>"
def getURI(self):
return "qemu:///system"
def getDomainCapabilities(self, emulator, arch, machine, virttype):
self.reads.append((emulator, arch, machine, virttype))
return "<domainCapabilities/>"
def connection():
conn = wvmConnect.__new__(wvmConnect) # no libvirt connection: only these calls
def connection(cls=wvmConnect):
conn = cls.__new__(cls) # no libvirt connection: only these calls
conn.wvm = FakeConn()
return conn
@ -57,6 +76,13 @@ class CapabilitiesOnceTestCase(unittest.TestCase):
["capabilities", ("/usr/bin/qemu-kvm", "x86_64", "q35", "kvm"), ("/usr/bin/qemu-kvm", "x86_64", "pc", "kvm")],
)
def test_the_overview_reads_the_info_once_and_the_free_memory_each_time(self):
conn = connection(wvmHostDetails)
conn.get_node_info()
conn.get_memory_usage()
self.assertEqual(conn.get_memory_usage()["total"], 2048 * 1048576)
self.assertEqual(conn.wvm.reads, ["info", "memory", "memory"])
def test_two_objects_do_not_share_their_reads(self):
a, b = connection(), connection()
a.get_cap_xml()

View file

@ -1,7 +1,7 @@
"""wvmInstance.cached_reads(): inside the block the domain XML (per flags),
the VM's state, the host info and each disk's volume are read from libvirt
once; outside it every read goes to libvirt, as code that changes the VM
needs. The host capabilities are read once per object."""
the VM's state and each disk's volume are read from libvirt once; outside it
every read goes to libvirt, as code that changes the VM needs. The host's
info and capabilities are read once per object."""
import unittest
from unittest.mock import MagicMock
@ -60,20 +60,17 @@ class CachedReadsTestCase(unittest.TestCase):
p = proxy()
p._XMLDesc(0)
p._XMLDesc(0)
p._host_info()
p._host_info()
self.assertEqual((p.instance.reads, p.wvm.caps), ([0, 0], 2))
p.get_status()
p.get_status()
self.assertEqual(p.instance.reads, [0, 0, "info", "info"])
def test_inside_the_block_each_flags_value_is_read_once(self):
p = proxy()
with p.cached_reads():
first = [p._XMLDesc(0), p._XMLDesc(1), p._XMLDesc(3)]
again = [p._XMLDesc(0), p._XMLDesc(1), p._XMLDesc(3)]
p._host_info()
p._host_info()
self.assertEqual(first, again)
self.assertEqual(p.instance.reads, [0, 1, 3])
self.assertEqual(p.wvm.caps, 1)
def test_the_block_ends_with_its_cache(self):
p = proxy()

View file

@ -55,7 +55,7 @@ def instance(state, clock):
proxy._read_cache = None
proxy.instance = FakeDomain(state, clock)
proxy._XMLDesc = lambda flags: XML
proxy._host_info = lambda: ["x86_64", 0, 4] # 4 host CPUs
proxy.host_info = ["x86_64", 0, 4] # 4 host CPUs
proxy.get_status = lambda: state
return proxy
@ -106,7 +106,7 @@ class UsageTestCase(unittest.TestCase):
proxy.instance.domain_id = 8
def other_host_cpus(proxy):
proxy._host_info = lambda: ["x86_64", 0, 8]
proxy.host_info = ["x86_64", 0, 8]
def nic_renamed(proxy):
proxy._XMLDesc = lambda flags: XML.replace("vnet0", "vnet1")