mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
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.
77 lines
2.7 KiB
Python
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}])
|