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:
Marek Marczykowski-Górecki 2017-07-27 22:16:03 +02:00
parent 0d9574d9fc
commit e8b875f552
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 161 additions and 32 deletions

View File

@ -172,7 +172,7 @@ class VMMConnection(object):
if 'xen.lowlevel.xs' in sys.modules: if 'xen.lowlevel.xs' in sys.modules:
self._xs = xen.lowlevel.xs.xs() 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._xc = xen.lowlevel.xc.xc()
self._libvirt_conn = VirConnectWrapper( self._libvirt_conn = VirConnectWrapper(
qubes.config.defaults['libvirt_uri']) qubes.config.defaults['libvirt_uri'])
@ -214,7 +214,7 @@ class VMMConnection(object):
'xc object is available under Xen hypervisor only') 'xc object is available under Xen hypervisor only')
self.init_vmm_connection() self.init_vmm_connection()
return self._xs return self._xc
def register_event_handlers(self, app): def register_event_handlers(self, app):
'''Register libvirt event handlers, which will translate libvirt '''Register libvirt event handlers, which will translate libvirt
@ -314,55 +314,70 @@ class QubesHost(object):
return int(self._physinfo['free_memory']) return int(self._physinfo['free_memory'])
def measure_cpu_usage(self, previous_time=None, previous=None, def get_vm_stats(self, previous_time=None, previous=None, only_vm=None):
wait_time=1):
'''Measure cpu usage for all domains at once. '''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. This function requires Xen hypervisor.
.. versionchanged:: 3.0 ..warning:
argument order to match return tuple
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 :raises NotImplementedError: when not under Xen
''' '''
if previous is None: if (previous_time is None) != (previous is None):
previous_time = time.time() raise ValueError(
previous = {} 'previous and previous_time must be given together (or none)')
try:
info = self.app.vmm.xc.domain_getinfo(0, qubes.config.max_qid)
except AttributeError:
raise NotImplementedError(
'This function requires Xen hypervisor')
for vm in info: if previous is None:
previous[vm['domid']] = {} previous = {}
previous[vm['domid']]['cpu_time'] = (
vm['cpu_time'] / max(vm['online_vcpus'], 1))
previous[vm['domid']]['cpu_usage'] = 0
time.sleep(wait_time)
current_time = time.time() current_time = time.time()
current = {} current = {}
try: 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: except AttributeError:
raise NotImplementedError( raise NotImplementedError(
'This function requires Xen hypervisor') 'This function requires Xen hypervisor')
# TODO: add stubdomain stats to actual VMs
for vm in info: for vm in info:
current[vm['domid']] = {} domid = vm['domid']
current[vm['domid']]['cpu_time'] = ( current[domid] = {}
current[domid]['memory_kb'] = vm['mem_kb']
current[domid]['cpu_time'] = int(
vm['cpu_time'] / max(vm['online_vcpus'], 1)) vm['cpu_time'] / max(vm['online_vcpus'], 1))
if vm['domid'] in previous.keys(): if domid in previous:
current[vm['domid']]['cpu_usage'] = ( current[domid]['cpu_usage'] = int(
float(current[vm['domid']]['cpu_time'] - (current[domid]['cpu_time'] - previous[domid]['cpu_time'])
previous[vm['domid']]['cpu_time']) / / 1000 ** 3 * 100 / (current_time - previous_time))
1000 ** 3 / (current_time - previous_time) * 100) if current[domid]['cpu_usage'] < 0:
if current[vm['domid']]['cpu_usage'] < 0:
# VM has been rebooted # VM has been rebooted
current[vm['domid']]['cpu_usage'] = 0 current[domid]['cpu_usage'] = 0
else: else:
current[vm['domid']]['cpu_usage'] = 0 current[domid]['cpu_usage'] = 0
return (current_time, current) return (current_time, current)

View File

@ -22,7 +22,7 @@
# #
import os import os
import uuid import unittest.mock as mock
import lxml.etree import lxml.etree
@ -35,6 +35,120 @@ import qubes.tests.init
class TestApp(qubes.tests.TestEmitter): class TestApp(qubes.tests.TestEmitter):
pass 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): class TC_30_VMCollection(qubes.tests.QubesTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()