Cache power state when caching is enabled
Power state changes are signaled with events too, so it is possible to cache it and update/invalidate cache with events. Additionally, admin.vm.List returns a power state, so the cache can be populated early. This in particular greatly improves qvm-ls performance - eliminate admin.vm.CurrentState call at all. QubesOS/qubes-issues#3293
This commit is contained in:
parent
bfe1a3d541
commit
79c7392424
@ -70,6 +70,12 @@ class VMCollection(object):
|
|||||||
props = props.split(' ')
|
props = props.split(' ')
|
||||||
new_vm_list[vm_name] = dict(
|
new_vm_list[vm_name] = dict(
|
||||||
[vm_prop.split('=', 1) for vm_prop in props])
|
[vm_prop.split('=', 1) for vm_prop in props])
|
||||||
|
# if cache not enabled, drop power state
|
||||||
|
if not self.app.cache_enabled:
|
||||||
|
try:
|
||||||
|
del new_vm_list[vm_name]['state']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
self._vm_list = new_vm_list
|
self._vm_list = new_vm_list
|
||||||
for name, vm in list(self._vm_objects.items()):
|
for name, vm in list(self._vm_objects.items()):
|
||||||
@ -103,9 +109,12 @@ class VMCollection(object):
|
|||||||
# done by 'item not in self' check above, unless blind_mode is
|
# done by 'item not in self' check above, unless blind_mode is
|
||||||
# enabled
|
# enabled
|
||||||
klass = None
|
klass = None
|
||||||
|
power_state = None
|
||||||
if self._vm_list and item in self._vm_list:
|
if self._vm_list and item in self._vm_list:
|
||||||
klass = self._vm_list[item]['class']
|
klass = self._vm_list[item]['class']
|
||||||
self._vm_objects[item] = cls(self.app, item, klass=klass)
|
power_state = self._vm_list[item].get('state')
|
||||||
|
self._vm_objects[item] = cls(self.app, item, klass=klass,
|
||||||
|
power_state=power_state)
|
||||||
return self._vm_objects[item]
|
return self._vm_objects[item]
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
@ -608,6 +617,45 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _update_power_state_cache(self, subject, event, **kwargs):
|
||||||
|
""" Update cached VM power state.
|
||||||
|
|
||||||
|
This method is designed to be hooed as an event handler for:
|
||||||
|
- domain-pre-start
|
||||||
|
- domain-start
|
||||||
|
- domain-shutdown
|
||||||
|
- domain-paused
|
||||||
|
- domain-unpaused
|
||||||
|
|
||||||
|
This is done in :py:class:`qubesadmin.events.EventsDispatcher` class
|
||||||
|
directly, before calling other handlers.
|
||||||
|
|
||||||
|
:param subject: a VM object
|
||||||
|
:param event: name of the event
|
||||||
|
:param kwargs: other arguments
|
||||||
|
:return:
|
||||||
|
""" # pylint: disable=unused-argument,no-self-use
|
||||||
|
|
||||||
|
if not self.app.cache_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
if event == 'domain-pre-start':
|
||||||
|
power_state = 'Transient'
|
||||||
|
elif event == 'domain-start':
|
||||||
|
power_state = 'Running'
|
||||||
|
elif event == 'domain-shutdown':
|
||||||
|
power_state = 'Halted'
|
||||||
|
elif event == 'domain-paused':
|
||||||
|
power_state = 'Paused'
|
||||||
|
elif event == 'domain-unpaused':
|
||||||
|
power_state = 'Running'
|
||||||
|
else:
|
||||||
|
# unknown power state change, drop cached power state
|
||||||
|
power_state = None
|
||||||
|
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
subject._power_state_cache = power_state
|
||||||
|
|
||||||
|
|
||||||
class QubesLocal(QubesBase):
|
class QubesLocal(QubesBase):
|
||||||
"""Application object communicating through local socket.
|
"""Application object communicating through local socket.
|
||||||
|
@ -215,6 +215,9 @@ class EventsDispatcher(object):
|
|||||||
if event.startswith('property-set:') or \
|
if event.startswith('property-set:') or \
|
||||||
event.startswith('property-reset:'):
|
event.startswith('property-reset:'):
|
||||||
self.app._invalidate_cache(subject, event, **kwargs)
|
self.app._invalidate_cache(subject, event, **kwargs)
|
||||||
|
elif event in ('domain-pre-start', 'domain-start', 'domain-shutdown',
|
||||||
|
'domain-paused', 'domain-unpaused'):
|
||||||
|
self.app._update_power_state_cache(subject, event, **kwargs)
|
||||||
|
|
||||||
handlers = [h_func for h_name, h_func_set in self.handlers.items()
|
handlers = [h_func for h_name, h_func_set in self.handlers.items()
|
||||||
for h_func in h_func_set
|
for h_func in h_func_set
|
||||||
|
@ -131,6 +131,33 @@ class TC_00_VMCollection(qubesadmin.tests.QubesTestCase):
|
|||||||
self.fail('VM not found in collection')
|
self.fail('VM not found in collection')
|
||||||
self.assertAllCalled()
|
self.assertAllCalled()
|
||||||
|
|
||||||
|
def test_010_getitem_cache_power_state(self):
|
||||||
|
self.app.cache_enabled = True
|
||||||
|
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
|
||||||
|
b'0\x00test-vm class=AppVM state=Running\n'
|
||||||
|
try:
|
||||||
|
vm = self.app.domains['test-vm']
|
||||||
|
self.assertEqual(vm.name, 'test-vm')
|
||||||
|
self.assertEqual(vm.klass, 'AppVM')
|
||||||
|
self.assertEqual(vm.get_power_state(), 'Running')
|
||||||
|
except KeyError:
|
||||||
|
self.fail('VM not found in collection')
|
||||||
|
self.assertAllCalled()
|
||||||
|
|
||||||
|
def test_011_getitem_non_cache_power_state(self):
|
||||||
|
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
|
||||||
|
b'0\x00test-vm class=AppVM state=Running\n'
|
||||||
|
self.app.expected_calls[('test-vm', 'admin.vm.CurrentState', None, None)] = \
|
||||||
|
b'0\x00power_state=Running mem=1024'
|
||||||
|
try:
|
||||||
|
vm = self.app.domains['test-vm']
|
||||||
|
self.assertEqual(vm.name, 'test-vm')
|
||||||
|
self.assertEqual(vm.klass, 'AppVM')
|
||||||
|
self.assertEqual(vm.get_power_state(), 'Running')
|
||||||
|
except KeyError:
|
||||||
|
self.fail('VM not found in collection')
|
||||||
|
self.assertAllCalled()
|
||||||
|
|
||||||
|
|
||||||
class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
|
class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -52,10 +52,11 @@ class QubesVM(qubesadmin.base.PropertyHolder):
|
|||||||
|
|
||||||
firewall = None
|
firewall = None
|
||||||
|
|
||||||
def __init__(self, app, name, klass=None):
|
def __init__(self, app, name, klass=None, power_state=None):
|
||||||
super(QubesVM, self).__init__(app, 'admin.vm.property.', name)
|
super(QubesVM, self).__init__(app, 'admin.vm.property.', name)
|
||||||
self._volumes = None
|
self._volumes = None
|
||||||
self._klass = klass
|
self._klass = klass
|
||||||
|
self._power_state_cache = power_state
|
||||||
self.log = logging.getLogger(name)
|
self.log = logging.getLogger(name)
|
||||||
self.tags = qubesadmin.tags.Tags(self)
|
self.tags = qubesadmin.tags.Tags(self)
|
||||||
self.features = qubesadmin.features.Features(self)
|
self.features = qubesadmin.features.Features(self)
|
||||||
@ -181,8 +182,13 @@ class QubesVM(qubesadmin.base.PropertyHolder):
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
if self._power_state_cache is not None:
|
||||||
|
return self._power_state_cache
|
||||||
try:
|
try:
|
||||||
return self._get_current_state()['power_state']
|
power_state = self._get_current_state()['power_state']
|
||||||
|
if self.app.cache_enabled:
|
||||||
|
self._power_state_cache = power_state
|
||||||
|
return power_state
|
||||||
except qubesadmin.exc.QubesDaemonNoResponseError:
|
except qubesadmin.exc.QubesDaemonNoResponseError:
|
||||||
return 'NA'
|
return 'NA'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user