-
-
Notifications
You must be signed in to change notification settings - Fork 108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes to speed up qvm-ls #165
Open
qubesuser
wants to merge
13
commits into
QubesOS:master
Choose a base branch
from
qubesuser:fast_qvm_ls
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
9cc86b3
don't access netvm if it's None in visible_gateway/netmask
qubesuser b297ecb
cache isinstance(default, collections.Callable)
qubesuser f2b8ad7
remove unused netid code
qubesuser d183ab1
cache PropertyHolder.property_list and use O(1) property name lookups
qubesuser 92c452a
change default pool code to be fast
qubesuser edae7a1
create "lvm" pool using rootfs thin pool instead of hardcoding qubes_…
qubesuser 015ee9c
don't replace pdb debugger's SIGINT handler
qubesuser cf27e10
rework libvirt connection code
qubesuser 690a88b
store libvirt state and update on events instead of asking libvirtd e…
qubesuser 9ee73a2
don't ask for domain state before asking for info
qubesuser 041bfbb
take startup lock while starting services
qubesuser 3ea172c
Add admin APIs to get all properties at once, and optimize the code
qubesuser 0175ad6
add GetAllData API to get data of all VMs
qubesuser File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,18 @@ def on_domain_delete(self, subject, event, vm): | |
vm.remove_handler('*', self.vm_handler) | ||
|
||
|
||
def escape(unescaped_string): | ||
'''Escape a string for the Admin API''' | ||
result = unescaped_string | ||
# TODO: can we do this faster? | ||
result = result.replace("\\", "\\\\") # this must be the first one | ||
|
||
result = result.replace("\r", "\\r") | ||
result = result.replace("\n", "\\n") | ||
result = result.replace("\t", "\\t") | ||
result = result.replace("\0", "\\0") | ||
return result | ||
|
||
class QubesAdminAPI(qubes.api.AbstractQubesAPI): | ||
'''Implementation of Qubes Management API calls | ||
|
||
|
@@ -107,6 +119,15 @@ def vmclass_list(self): | |
return ''.join('{}\n'.format(ep.name) | ||
for ep in entrypoints) | ||
|
||
# pylint: disable=no-self-use | ||
def _vm_line_strs(self, strs, vm): | ||
strs.append(vm.name) | ||
strs.append(" class=") | ||
strs.append(vm.__class__.__name__) | ||
strs.append(" state=") | ||
strs.append(vm.get_power_state()) | ||
strs.append("\n") | ||
|
||
@qubes.api.method('admin.vm.List', no_payload=True, | ||
scope='global', read=True) | ||
@asyncio.coroutine | ||
|
@@ -119,11 +140,37 @@ def vm_list(self): | |
else: | ||
domains = self.fire_event_for_filter([self.dest]) | ||
|
||
return ''.join('{} class={} state={}\n'.format( | ||
vm.name, | ||
vm.__class__.__name__, | ||
vm.get_power_state()) | ||
for vm in sorted(domains)) | ||
strs = [] | ||
for vm in sorted(domains): | ||
self._vm_line_strs(strs, vm) | ||
return ''.join(strs) | ||
|
||
@qubes.api.method('admin.vm.GetAllData', no_payload=True, | ||
scope='global', read=True) | ||
@asyncio.coroutine | ||
def vm_get_all_data(self): | ||
'''List all VMs and return the value of all their properties''' | ||
assert not self.arg | ||
|
||
if self.dest.name == 'dom0': | ||
domains = self.fire_event_for_filter(self.app.domains) | ||
else: | ||
domains = self.fire_event_for_filter([self.dest]) | ||
|
||
strs = [] | ||
orig_dest = self.dest | ||
orig_method = self.method | ||
try: | ||
for vm in sorted(domains): | ||
self.dest = vm | ||
self.method = "admin.vm.property.GetAll" | ||
self._vm_line_strs(strs, vm) | ||
self._property_get_all_strs(strs, vm, "\tP\t") | ||
finally: | ||
self.dest = orig_dest | ||
self.method = orig_method | ||
|
||
return ''.join(strs) | ||
|
||
@qubes.api.method('admin.vm.property.List', no_payload=True, | ||
scope='local', read=True) | ||
|
@@ -154,6 +201,13 @@ def vm_property_get(self): | |
'''Get a value of one property''' | ||
return self._property_get(self.dest) | ||
|
||
@qubes.api.method('admin.vm.property.GetAll', no_payload=True, | ||
scope='local', read=True) | ||
@asyncio.coroutine | ||
def vm_property_get_all(self): | ||
'''Get the value of all properties''' | ||
return self._property_get_all(self.dest) | ||
|
||
@qubes.api.method('admin.property.Get', no_payload=True, | ||
scope='global', read=True) | ||
@asyncio.coroutine | ||
|
@@ -162,24 +216,39 @@ def property_get(self): | |
assert self.dest.name == 'dom0' | ||
return self._property_get(self.app) | ||
|
||
def _property_get(self, dest): | ||
if self.arg not in dest.property_list(): | ||
raise qubes.exc.QubesNoSuchPropertyError(dest, self.arg) | ||
|
||
self.fire_event_for_permission() | ||
@qubes.api.method('admin.property.GetAll', no_payload=True, | ||
scope='global', read=True) | ||
@asyncio.coroutine | ||
def property_get_all(self): | ||
'''Get a value of all global properties''' | ||
assert self.dest.name == 'dom0' | ||
return self._property_get_all(self.app) | ||
|
||
property_def = dest.property_get_def(self.arg) | ||
# pylint: disable=no-self-use | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
def _property_type(self, property_def): | ||
# explicit list to be sure that it matches protocol spec | ||
if isinstance(property_def, qubes.vm.VMProperty): | ||
property_type = 'vm' | ||
elif property_def.type is int: | ||
property_def_type = property_def.type | ||
if property_def_type is str: | ||
property_type = 'str' | ||
elif property_def_type is int: | ||
property_type = 'int' | ||
elif property_def.type is bool: | ||
elif property_def_type is bool: | ||
property_type = 'bool' | ||
elif self.arg == 'label': | ||
elif property_def.__name__ == 'label': | ||
property_type = 'label' | ||
elif isinstance(property_def, qubes.vm.VMProperty): | ||
property_type = 'vm' | ||
else: | ||
property_type = 'str' | ||
return property_type | ||
|
||
def _property_get(self, dest): | ||
if self.arg not in dest.property_list(): | ||
raise qubes.exc.QubesNoSuchPropertyError(dest, self.arg) | ||
|
||
self.fire_event_for_permission() | ||
property_def = dest.property_get_def(self.arg) | ||
property_type = self._property_type(property_def) | ||
|
||
try: | ||
value = getattr(dest, self.arg) | ||
|
@@ -191,6 +260,49 @@ def _property_get(self, dest): | |
property_type, | ||
str(value) if value is not None else '') | ||
|
||
|
||
# this is a performance critical function for qvm-ls | ||
def _property_get_all_line_strs(self, strs, dest, property_def): | ||
property_type = self._property_type(property_def) | ||
property_attr = property_def._attr_name # pylint: disable=protected-access | ||
dest_dict = dest.__dict__ | ||
property_is_default = property_attr not in dest_dict | ||
|
||
if not property_is_default: | ||
value = dest_dict[property_attr] | ||
elif property_def.has_default(): | ||
value = property_def.get_default(dest) | ||
else: | ||
value = None | ||
|
||
if value is None: | ||
escaped_value = "" | ||
elif property_type == "str": | ||
escaped_value = escape(str(value)) | ||
else: | ||
escaped_value = str(value) | ||
|
||
strs.append(property_def.__name__) | ||
strs.append("\tD\t" if property_is_default else "\t-\t") | ||
strs.append(property_type) | ||
strs.append("\t") | ||
strs.append(escaped_value) | ||
strs.append("\n") | ||
|
||
def _property_get_all_strs(self, strs, dest, prefix=None): | ||
assert not self.arg | ||
|
||
properties = self.fire_event_for_filter(dest.property_list()) | ||
for prop in properties: | ||
if prefix: | ||
strs.append(prefix) | ||
self._property_get_all_line_strs(strs, dest, prop) | ||
|
||
def _property_get_all(self, dest): | ||
strs = [] | ||
self._property_get_all_strs(strs, dest) | ||
return "".join(strs) | ||
|
||
@qubes.api.method('admin.vm.property.GetDefault', no_payload=True, | ||
scope='local', read=True) | ||
@asyncio.coroutine | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
str.append
really faster thanstr.format
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to avoid doing any kind of string processing until the final join call, which should be the fastest since it should just allocate a single string in join and copy them all in, avoiding any intermediate allocations, partial concatenations, format string parsing, etc.
It's slightly uglier, but not that much, it looks like writing to an output stream (which would be even faster, but not really worth the effort).