1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/vrtManager/test_usage.py
catborise 5bb3a545a0 perf(instances): VM statistics sample all devices in one second
Each open VM page asks for its statistics every 10 seconds. For a
running VM the stats view measured the CPU, then each disk, then each
NIC, sleeping one second for each. With two disks and a NIC a request
held a sync gunicorn worker for over 4 s, so a few dozen open VM pages
could keep every worker busy.

wvmInstance.usage() replaces cpu_usage, disk_usage and net_usage, the
stats view's only callers: it reads all counters, sleeps one second
once and reads them again. Values and units are unchanged (CPU percent
of the host, disk bytes, network bits per second); compared against the
previous methods on the same counters, the results are identical.

Measured on the test host for a running VM with two disks and a NIC:
the stats request took 4.1-4.3 s before, 1.1-1.3 s now. The new test
fails on the previous code.
2026-10-05 15:27:55 +03:00

77 lines
2.7 KiB
Python

"""VM statistics sample CPU, disks and NICs over one shared second: the stats
view runs in a sync worker, polled every 10 s by each open VM page."""
import unittest
from unittest.mock import patch
from django.conf import settings
if not settings.configured:
settings.configure(MAC_OUI="52:54:10")
from vrtManager.instance import wvmInstance
XML = """<domain><devices>
<disk type='file' device='disk'><source file='/var/lib/libvirt/images/a.qcow2'/><target dev='vda'/></disk>
<disk type='file' device='disk'><source file='/var/lib/libvirt/images/b.qcow2'/><target dev='vdb'/></disk>
<disk type='file' device='cdrom'><target dev='sda'/></disk>
<interface type='network'><target dev='vnet0'/></interface>
</devices></domain>"""
class FakeDomain:
"""Counters grow by a fixed amount per elapsed second (per sleep)."""
def __init__(self, state):
self.state = state
self.second = 0
def info(self):
return [self.state, 0, 0, 2, self.second * 500_000_000] # 0.5 s of CPU time per second
def blockStats(self, dev):
n = {"vda": 1, "vdb": 2}[dev] # disks are read by target name
return (0, self.second * n * 1000, 0, self.second * n * 3000, 0)
def interfaceStats(self, dev):
return (self.second * 100, 0, 0, 0, self.second * 50, 0, 0, 0)
class FakeConn:
def getInfo(self):
return ["x86_64", 0, 4] # 4 host CPUs
def instance(state):
proxy = wvmInstance.__new__(wvmInstance) # no connection: only these calls
proxy.instance = FakeDomain(state)
proxy.wvm = FakeConn()
proxy._XMLDesc = lambda flags: XML
proxy.get_net_devices = lambda: [{"mac": "52:54:00:00:00:01"}]
return proxy
class UsageTestCase(unittest.TestCase):
def sample(self, state):
proxy = instance(state)
def sleep(seconds):
proxy.instance.second += seconds
with patch("vrtManager.instance.time.sleep", side_effect=sleep) as slept:
result = proxy.usage()
return result, slept
def test_a_running_vm_is_sampled_over_one_second(self):
(cpu, disks, nets), slept = self.sample(1)
slept.assert_called_once_with(1)
self.assertEqual(cpu, {"cpu": 12.5}) # 0.5 s of 4 host CPUs
self.assertEqual(disks, [{"dev": "vda", "rd": 1000, "wr": 3000}, {"dev": "vdb", "rd": 2000, "wr": 6000}])
self.assertEqual(nets, [{"dev": 0, "rx": 800, "tx": 400}]) # bits
def test_a_vm_that_is_not_running_is_not_sampled(self):
(cpu, disks, nets), slept = self.sample(5)
slept.assert_not_called()
self.assertEqual(cpu, {"cpu": 0})
self.assertEqual(disks, [{"dev": "vda", "rd": 0, "wr": 0}, {"dev": "vdb", "rd": 0, "wr": 0}])
self.assertEqual(nets, [{"dev": 0, "rx": 0, "tx": 0}])