app: refresh getting VM statistics, rename to QubesHost.get_vm_stats
Get a VM statistics once. If previous measurements are provided, calculate difference too. This is backend part of upcoming admin.vm.Stats service. QubesOS/qubes-issues#853
This commit is contained in:
parent
0d9574d9fc
commit
e8b875f552
77
qubes/app.py
77
qubes/app.py
@ -172,7 +172,7 @@ class VMMConnection(object):
|
||||
|
||||
if 'xen.lowlevel.xs' in sys.modules:
|
||||
self._xs = xen.lowlevel.xs.xs()
|
||||
if 'xen.lowlevel.cs' in sys.modules:
|
||||
if 'xen.lowlevel.xc' in sys.modules:
|
||||
self._xc = xen.lowlevel.xc.xc()
|
||||
self._libvirt_conn = VirConnectWrapper(
|
||||
qubes.config.defaults['libvirt_uri'])
|
||||
@ -214,7 +214,7 @@ class VMMConnection(object):
|
||||
'xc object is available under Xen hypervisor only')
|
||||
|
||||
self.init_vmm_connection()
|
||||
return self._xs
|
||||
return self._xc
|
||||
|
||||
def register_event_handlers(self, app):
|
||||
'''Register libvirt event handlers, which will translate libvirt
|
||||
@ -314,55 +314,70 @@ class QubesHost(object):
|
||||
return int(self._physinfo['free_memory'])
|
||||
|
||||
|
||||
def measure_cpu_usage(self, previous_time=None, previous=None,
|
||||
wait_time=1):
|
||||
def get_vm_stats(self, previous_time=None, previous=None, only_vm=None):
|
||||
'''Measure cpu usage for all domains at once.
|
||||
|
||||
If previous measurements are given, CPU usage will be given in
|
||||
percents of time. Otherwise only absolute value (seconds).
|
||||
|
||||
Return a tuple of (measurements_time, measurements),
|
||||
where measurements is a dictionary with key: domid, value: dict:
|
||||
- cpu_time - absolute CPU usage (seconds since its startup)
|
||||
- cpu_usage - CPU usage in %
|
||||
- memory_kb - current memory assigned, in kb
|
||||
|
||||
This function requires Xen hypervisor.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
argument order to match return tuple
|
||||
..warning:
|
||||
|
||||
This function may return info about implementation-specific VMs,
|
||||
like stubdomains for HVM
|
||||
|
||||
:param previous: previous measurement
|
||||
:param previous_time: time of previous measurement
|
||||
:param only_vm: get measurements only for this VM
|
||||
|
||||
:raises NotImplementedError: when not under Xen
|
||||
'''
|
||||
|
||||
if previous is None:
|
||||
previous_time = time.time()
|
||||
previous = {}
|
||||
try:
|
||||
info = self.app.vmm.xc.domain_getinfo(0, qubes.config.max_qid)
|
||||
except AttributeError:
|
||||
raise NotImplementedError(
|
||||
'This function requires Xen hypervisor')
|
||||
if (previous_time is None) != (previous is None):
|
||||
raise ValueError(
|
||||
'previous and previous_time must be given together (or none)')
|
||||
|
||||
for vm in info:
|
||||
previous[vm['domid']] = {}
|
||||
previous[vm['domid']]['cpu_time'] = (
|
||||
vm['cpu_time'] / max(vm['online_vcpus'], 1))
|
||||
previous[vm['domid']]['cpu_usage'] = 0
|
||||
time.sleep(wait_time)
|
||||
if previous is None:
|
||||
previous = {}
|
||||
|
||||
current_time = time.time()
|
||||
current = {}
|
||||
try:
|
||||
info = self.app.vmm.xc.domain_getinfo(0, qubes.config.max_qid)
|
||||
if only_vm:
|
||||
xid = only_vm.xid
|
||||
if xid < 0:
|
||||
raise qubes.exc.QubesVMNotRunningError(only_vm)
|
||||
info = self.app.vmm.xc.domain_getinfo(xid, 1)
|
||||
if info[0]['domid'] != xid:
|
||||
raise qubes.exc.QubesVMNotRunningError(only_vm)
|
||||
else:
|
||||
info = self.app.vmm.xc.domain_getinfo(0, 1024)
|
||||
except AttributeError:
|
||||
raise NotImplementedError(
|
||||
'This function requires Xen hypervisor')
|
||||
# TODO: add stubdomain stats to actual VMs
|
||||
for vm in info:
|
||||
current[vm['domid']] = {}
|
||||
current[vm['domid']]['cpu_time'] = (
|
||||
domid = vm['domid']
|
||||
current[domid] = {}
|
||||
current[domid]['memory_kb'] = vm['mem_kb']
|
||||
current[domid]['cpu_time'] = int(
|
||||
vm['cpu_time'] / max(vm['online_vcpus'], 1))
|
||||
if vm['domid'] in previous.keys():
|
||||
current[vm['domid']]['cpu_usage'] = (
|
||||
float(current[vm['domid']]['cpu_time'] -
|
||||
previous[vm['domid']]['cpu_time']) /
|
||||
1000 ** 3 / (current_time - previous_time) * 100)
|
||||
if current[vm['domid']]['cpu_usage'] < 0:
|
||||
if domid in previous:
|
||||
current[domid]['cpu_usage'] = int(
|
||||
(current[domid]['cpu_time'] - previous[domid]['cpu_time'])
|
||||
/ 1000 ** 3 * 100 / (current_time - previous_time))
|
||||
if current[domid]['cpu_usage'] < 0:
|
||||
# VM has been rebooted
|
||||
current[vm['domid']]['cpu_usage'] = 0
|
||||
current[domid]['cpu_usage'] = 0
|
||||
else:
|
||||
current[vm['domid']]['cpu_usage'] = 0
|
||||
current[domid]['cpu_usage'] = 0
|
||||
|
||||
return (current_time, current)
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
#
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import unittest.mock as mock
|
||||
|
||||
import lxml.etree
|
||||
|
||||
@ -35,6 +35,120 @@ import qubes.tests.init
|
||||
class TestApp(qubes.tests.TestEmitter):
|
||||
pass
|
||||
|
||||
|
||||
class TC_20_QubesHost(qubes.tests.QubesTestCase):
|
||||
sample_xc_domain_getinfo = [
|
||||
{'paused': 0, 'cpu_time': 243951379111104, 'ssidref': 0,
|
||||
'hvm': 0, 'shutdown_reason': 255, 'dying': 0,
|
||||
'mem_kb': 3733212, 'domid': 0, 'max_vcpu_id': 7,
|
||||
'crashed': 0, 'running': 1, 'maxmem_kb': 3734236,
|
||||
'shutdown': 0, 'online_vcpus': 8,
|
||||
'handle': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
'cpupool': 0, 'blocked': 0},
|
||||
{'paused': 0, 'cpu_time': 2849496569205, 'ssidref': 0,
|
||||
'hvm': 0, 'shutdown_reason': 255, 'dying': 0,
|
||||
'mem_kb': 303916, 'domid': 1, 'max_vcpu_id': 0,
|
||||
'crashed': 0, 'running': 0, 'maxmem_kb': 308224,
|
||||
'shutdown': 0, 'online_vcpus': 1,
|
||||
'handle': [116, 174, 229, 207, 17, 1, 79, 39, 191, 37, 41,
|
||||
186, 205, 158, 219, 8],
|
||||
'cpupool': 0, 'blocked': 1},
|
||||
{'paused': 0, 'cpu_time': 249658663079978, 'ssidref': 0,
|
||||
'hvm': 0, 'shutdown_reason': 255, 'dying': 0,
|
||||
'mem_kb': 3782668, 'domid': 11, 'max_vcpu_id': 7,
|
||||
'crashed': 0, 'running': 0, 'maxmem_kb': 3783692,
|
||||
'shutdown': 0, 'online_vcpus': 8,
|
||||
'handle': [169, 95, 55, 127, 140, 94, 79, 220, 186, 210,
|
||||
117, 5, 148, 11, 185, 206],
|
||||
'cpupool': 0, 'blocked': 1}]
|
||||
|
||||
def setUp(self):
|
||||
super(TC_20_QubesHost, self).setUp()
|
||||
self.app = TestApp()
|
||||
self.app.vmm = mock.Mock()
|
||||
self.qubes_host = qubes.app.QubesHost(self.app)
|
||||
|
||||
def test_000_get_vm_stats_single(self):
|
||||
self.app.vmm.configure_mock(**{
|
||||
'xc.domain_getinfo.return_value': self.sample_xc_domain_getinfo
|
||||
})
|
||||
|
||||
info_time, info = self.qubes_host.get_vm_stats()
|
||||
self.assertEqual(self.app.vmm.mock_calls, [
|
||||
('xc.domain_getinfo', (0, 1024), {}),
|
||||
])
|
||||
self.assertIsNotNone(info_time)
|
||||
expected_info = {
|
||||
0: {
|
||||
'cpu_time': 243951379111104//8,
|
||||
'cpu_usage': 0,
|
||||
'memory_kb': 3733212,
|
||||
},
|
||||
1: {
|
||||
'cpu_time': 2849496569205,
|
||||
'cpu_usage': 0,
|
||||
'memory_kb': 303916,
|
||||
},
|
||||
11: {
|
||||
'cpu_time': 249658663079978//8,
|
||||
'cpu_usage': 0,
|
||||
'memory_kb': 3782668,
|
||||
},
|
||||
}
|
||||
self.assertEqual(info, expected_info)
|
||||
|
||||
def test_001_get_vm_stats_twice(self):
|
||||
self.app.vmm.configure_mock(**{
|
||||
'xc.domain_getinfo.return_value': self.sample_xc_domain_getinfo
|
||||
})
|
||||
|
||||
prev_time, prev_info = self.qubes_host.get_vm_stats()
|
||||
prev_time -= 1
|
||||
prev_info[0]['cpu_time'] -= 10**8
|
||||
prev_info[1]['cpu_time'] -= 10**9
|
||||
prev_info[11]['cpu_time'] -= 125 * 10**6
|
||||
info_time, info = self.qubes_host.get_vm_stats(prev_time, prev_info)
|
||||
self.assertIsNotNone(info_time)
|
||||
expected_info = {
|
||||
0: {
|
||||
'cpu_time': 243951379111104//8,
|
||||
'cpu_usage': 9,
|
||||
'memory_kb': 3733212,
|
||||
},
|
||||
1: {
|
||||
'cpu_time': 2849496569205,
|
||||
'cpu_usage': 99,
|
||||
'memory_kb': 303916,
|
||||
},
|
||||
11: {
|
||||
'cpu_time': 249658663079978//8,
|
||||
'cpu_usage': 12,
|
||||
'memory_kb': 3782668,
|
||||
},
|
||||
}
|
||||
self.assertEqual(info, expected_info)
|
||||
self.assertEqual(self.app.vmm.mock_calls, [
|
||||
('xc.domain_getinfo', (0, 1024), {}),
|
||||
('xc.domain_getinfo', (0, 1024), {}),
|
||||
])
|
||||
|
||||
def test_002_get_vm_stats_one_vm(self):
|
||||
self.app.vmm.configure_mock(**{
|
||||
'xc.domain_getinfo.return_value': [self.sample_xc_domain_getinfo[1]]
|
||||
})
|
||||
|
||||
vm = mock.Mock
|
||||
vm.xid = 1
|
||||
vm.name = 'somevm'
|
||||
|
||||
info_time, info = self.qubes_host.get_vm_stats(only_vm=vm)
|
||||
self.assertIsNotNone(info_time)
|
||||
self.assertEqual(self.app.vmm.mock_calls, [
|
||||
('xc.domain_getinfo', (1, 1), {}),
|
||||
])
|
||||
|
||||
|
||||
|
||||
class TC_30_VMCollection(qubes.tests.QubesTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
Loading…
Reference in New Issue
Block a user