diff --git a/.pylintrc b/.pylintrc index eaa3a43a1..9b5ca6970 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,6 @@ [MASTER] persistent=no -ignore-paths=qubes/tests +ignore-paths=^qubes/tests/(?!integ/(dispvm|network).*$)(?!(app|api|ext|vm/(adminvm|appvm|dispvm|mix/(dvmtemplate|net))).py$).* init-hook="import astroid.bases; astroid.bases.POSSIBLE_PROPERTIES.add('stateless_property')" [MESSAGES CONTROL] @@ -61,10 +61,10 @@ class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$ function-rgx=(test_[0-9]{3}_[a-z0-9_]{2,50}|[a-z_][a-z0-9_]{2,50})$ # Regular expression which should only match correct method names -method-rgx=(test_[0-9]{3}_[a-z0-9_]{2,50}|[a-z_][a-z0-9_]{2,50})$ +method-rgx=(setUp|setUpClass|tearDown|tearDownClass|test_[0-9]{3}_[a-z0-9_]{2,50}|[a-z_][a-z0-9_]{2,50})$ # Regular expression which should only match correct instance attribute names -attr-rgx=[a-z_][a-z0-9_]{2,40}$ +attr-rgx=(maxDiff|[a-z_][a-z0-9_]{2,40})$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{2,40}$ @@ -198,6 +198,6 @@ max-positional-arguments=7 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception,EnvironmentError +overgeneral-exceptions=builtins.Exception,builtins.EnvironmentError # vim: ft=conf diff --git a/qubes/ext/utils.py b/qubes/ext/utils.py index c0c89d993..832d2ba7d 100644 --- a/qubes/ext/utils.py +++ b/qubes/ext/utils.py @@ -169,7 +169,7 @@ def compare_device_cache(vm, devices_cache, current_devices): async def confirm_device_attachment(device, frontends) -> str: try: return await _do_confirm_device_attachment(device, frontends) - except Exception as exc: + except Exception as exc: # pylint: disable=broad-exception-caught print(str(exc.__class__.__name__) + ":", str(exc), file=sys.stderr) return "" diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index 8a8abe3a1..baf4585ab 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -183,7 +183,7 @@ def mem_set(self, domid, val) -> None: int(domid), int(val / 1024) + 1024 ) # LIBXL_MAXMEM_CONSTANT=1024 self.xc.domain_set_target_mem(int(domid), int(val / 1024)) - except Exception: + except Exception: # pylint: disable=broad-exception-caught pass # VM sees about 16MB memory less, so adjust for it here - qmemman # handle Xen view of memory diff --git a/qubes/rngdoc.py b/qubes/rngdoc.py index 2a808b060..ae4f6622b 100755 --- a/qubes/rngdoc.py +++ b/qubes/rngdoc.py @@ -121,7 +121,7 @@ def get_child_elements(self): number = "\\+" else: print(parent.tag) - raise Exception( + raise ValueError( f"Cannot choose number format for tag {parent.tag}" ) diff --git a/qubes/tests/api.py b/qubes/tests/api.py index 80c6be18c..228fde64f 100644 --- a/qubes/tests/api.py +++ b/qubes/tests/api.py @@ -27,7 +27,7 @@ import qubes.tests -class TestMgmt(object): +class TestMgmt: def __init__(self, app, src, method, dest, arg, send_event=None): self.app = app self.src = src @@ -35,6 +35,7 @@ def __init__(self, app, src, method, dest, arg, send_event=None): self.dest = dest self.arg = arg self.send_event = send_event + self.task = None try: self.function = { "mgmt.success": self.success, @@ -67,12 +68,14 @@ async def qubesexception(self, untrusted_payload): raise qubes.exc.QubesException("qubes-exception") async def exception(self, untrusted_payload): + # pylint: disable=broad-exception-raised raise Exception("exception") async def event(self, untrusted_payload): future = asyncio.get_event_loop().create_future() class Subject: + # pylint: disable=too-few-public-methods name = "subject" def __str__(self): @@ -93,7 +96,7 @@ def __str__(self): class TC_00_QubesDaemonProtocol(qubes.tests.QubesTestCase): def setUp(self): - super(TC_00_QubesDaemonProtocol, self).setUp() + super().setUp() self.app = unittest.mock.Mock() self.app.log = self.log self.sock_client, self.sock_server = socket.socketpair() @@ -116,7 +119,7 @@ def tearDown(self): except AttributeError: # old python in travis pass self.transport.close() - super(TC_00_QubesDaemonProtocol, self).tearDown() + super().tearDown() def test_000_message_ok(self): self.writer.write(b"mgmt.success+arg src name dest\0payload") diff --git a/qubes/tests/api_internal.py b/qubes/tests/api_internal.py index 86a512f48..48cbfd844 100644 --- a/qubes/tests/api_internal.py +++ b/qubes/tests/api_internal.py @@ -26,9 +26,9 @@ import uuid -def mock_coro(f): +def mock_coro(coro): async def coro_f(*args, **kwargs): - return f(*args, **kwargs) + return coro(*args, **kwargs) return coro_f diff --git a/qubes/tests/api_misc.py b/qubes/tests/api_misc.py index cf885abcd..fafeb4ecf 100644 --- a/qubes/tests/api_misc.py +++ b/qubes/tests/api_misc.py @@ -30,7 +30,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase): maxDiff = None def setUp(self): - super(TC_00_API_Misc, self).setUp() + super().setUp() self.tpl = mock.NonCallableMagicMock(name="template") del self.tpl.template self.async_src = mock.AsyncMock() @@ -393,9 +393,9 @@ def test_027_notify_updates_template_based_template_running(self): self.assertEqual(self.app.mock_calls, []) def test_028_notify_updates_template_based_dispvm(self): - self.dvm = self.src - self.dvm.updateable = False - self.srv = mock.NonCallableMagicMock(template=self.dvm) + dvm = self.src + dvm.updateable = False + _srv = mock.NonCallableMagicMock(template=dvm) self.src.updateable = False self.src.features.get.return_value = False self.src.template.is_running.return_value = False @@ -424,7 +424,7 @@ def test_028_notify_updates_template_based_dispvm(self): + "\nExpected:\n" + str(expected).replace(",", ",\n"), ) - self.assertEqual(self.dvm.mock_calls, []) + self.assertEqual(dvm.mock_calls, []) self.assertIsInstance(self.src.updates_available, mock.Mock) self.assertEqual(self.app.mock_calls, [mock.call.save()]) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index bbea5920b..a3ab4a4b7 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -21,8 +21,7 @@ # import os -import unittest -import unittest.mock as mock +from unittest import mock import lxml.etree @@ -139,7 +138,7 @@ class TC_20_QubesHost(qubes.tests.QubesTestCase): ] def setUp(self): - super(TC_20_QubesHost, self).setUp() + super().setUp() self.app = TestApp() self.app.vmm = mock.Mock() self.qubes_host = qubes.app.QubesHost(self.app) @@ -234,7 +233,7 @@ def test_002_get_vm_stats_one_vm(self): vm.xid = 1 vm.name = "somevm" - info_time, info = self.qubes_host.get_vm_stats(only_vm=vm) + info_time, _info = self.qubes_host.get_vm_stats(only_vm=vm) self.assertIsNotNone(info_time) self.assertEqual( self.app.vmm.mock_calls, @@ -491,7 +490,7 @@ class TC_89_QubesEmpty(qubes.tests.QubesTestCase): def tearDown(self): try: os.unlink("/tmp/qubestest.xml") - except: + except: # pylint: disable=bare-except pass try: self.app.close() @@ -558,93 +557,85 @@ def test_100_property_migrate_default_fw_netvm(self): """ with self.subTest("default_setup"): - with open("/tmp/qubestest.xml", "w") as xml_file: + with open("/tmp/qubestest.xml", "w", encoding="ascii") as xml_file: xml_file.write( xml_template.format( default_netvm="sys-firewall", default_fw_netvm="sys-net" ) ) - self.app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) - self.assertEqual(self.app.domains["sys-net"].netvm, None) + app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) + self.assertEqual(app.domains["sys-net"].netvm, None) self.assertEqual( - self.app.domains["sys-firewall"].netvm, - self.app.domains["sys-net"], + app.domains["sys-firewall"].netvm, + app.domains["sys-net"], ) # property is no longer "default" self.assertFalse( - self.app.domains["sys-firewall"].property_is_default("netvm") + app.domains["sys-firewall"].property_is_default("netvm") ) # verify that appvm.netvm is unaffected - self.assertTrue( - self.app.domains["appvm"].property_is_default("netvm") - ) + self.assertTrue(app.domains["appvm"].property_is_default("netvm")) self.assertEqual( - self.app.domains["appvm"].netvm, - self.app.domains["sys-firewall"], + app.domains["appvm"].netvm, + app.domains["sys-firewall"], ) with self.assertRaises(AttributeError): - self.app.default_fw_netvm + app.default_fw_netvm # pylint: disable=no-member - self.app.close() - del self.app + app.close() + del app with self.subTest("same"): - with open("/tmp/qubestest.xml", "w") as xml_file: + with open("/tmp/qubestest.xml", "w", encoding="ascii") as xml_file: xml_file.write( xml_template.format( default_netvm="sys-net", default_fw_netvm="sys-net" ) ) - self.app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) - self.assertEqual(self.app.domains["sys-net"].netvm, None) + app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) + self.assertEqual(app.domains["sys-net"].netvm, None) self.assertEqual( - self.app.domains["sys-firewall"].netvm, - self.app.domains["sys-net"], + app.domains["sys-firewall"].netvm, + app.domains["sys-net"], ) self.assertTrue( - self.app.domains["sys-firewall"].property_is_default("netvm") + app.domains["sys-firewall"].property_is_default("netvm") ) # verify that appvm.netvm is unaffected - self.assertTrue( - self.app.domains["appvm"].property_is_default("netvm") - ) - self.assertEqual( - self.app.domains["appvm"].netvm, self.app.domains["sys-net"] - ) + self.assertTrue(app.domains["appvm"].property_is_default("netvm")) + self.assertEqual(app.domains["appvm"].netvm, app.domains["sys-net"]) with self.assertRaises(AttributeError): - self.app.default_fw_netvm + app.default_fw_netvm # pylint: disable=no-member - self.app.close() - del self.app + app.close() + del app with self.subTest("loop"): - with open("/tmp/qubestest.xml", "w") as xml_file: + with open("/tmp/qubestest.xml", "w", encoding="ascii") as xml_file: xml_file.write( xml_template.format( default_netvm="sys-firewall", default_fw_netvm="sys-firewall", ) ) - self.app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) - self.assertEqual(self.app.domains["sys-net"].netvm, None) + app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) + self.assertEqual(app.domains["sys-net"].netvm, None) # this was netvm loop, better set to none, to not crash qubesd - self.assertEqual(self.app.domains["sys-firewall"].netvm, None) + self.assertEqual(app.domains["sys-firewall"].netvm, None) self.assertFalse( - self.app.domains["sys-firewall"].property_is_default("netvm") + app.domains["sys-firewall"].property_is_default("netvm") ) # verify that appvm.netvm is unaffected - self.assertTrue( - self.app.domains["appvm"].property_is_default("netvm") - ) + self.assertTrue(app.domains["appvm"].property_is_default("netvm")) self.assertEqual( - self.app.domains["appvm"].netvm, - self.app.domains["sys-firewall"], + app.domains["appvm"].netvm, + app.domains["sys-firewall"], ) with self.assertRaises(AttributeError): - self.app.default_fw_netvm + app.default_fw_netvm # pylint: disable=no-member - self.app.close() - del self.app + app.close() + del app def test_101_property_migrate_label(self): xml_template = """ @@ -672,25 +663,25 @@ def test_101_property_migrate_label(self): """ with self.subTest("replace_label"): - with open("/tmp/qubestest.xml", "w") as xml_file: + with open("/tmp/qubestest.xml", "w", encoding="ascii") as xml_file: xml_file.write(xml_template.format(old_gray="0x555753")) - self.app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) - self.assertEqual(self.app.get_label("gray").color, "0x555555") - self.app.close() - del self.app + app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) + self.assertEqual(app.get_label("gray").color, "0x555555") + app.close() + del app with self.subTest("dont_replace_label"): - with open("/tmp/qubestest.xml", "w") as xml_file: + with open("/tmp/qubestest.xml", "w", encoding="ascii") as xml_file: xml_file.write(xml_template.format(old_gray="0x123456")) - self.app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) - self.assertEqual(self.app.get_label("gray").color, "0x123456") - self.app.close() - del self.app + app = qubes.Qubes("/tmp/qubestest.xml", offline_mode=True) + self.assertEqual(app.get_label("gray").color, "0x123456") + app.close() + del app class TC_90_Qubes(qubes.tests.QubesTestCase): def setUp(self): - super(TC_90_Qubes, self).setUp() + super().setUp() self.app = qubes.Qubes( "/tmp/qubestest.xml", load=False, offline_mode=True ) @@ -725,7 +716,7 @@ def setUp(self): def tearDown(self): try: os.unlink("/tmp/qubestest.xml") - except: + except: # pylint: disable=bare-except pass del self.emitter super().tearDown() @@ -813,7 +804,7 @@ class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): def test_113_guivm(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): - guivm = qubes.property("guivm", default=(lambda self: "dom0")) + guivm = qubes.property("guivm", default=lambda self: "dom0") holder = MyTestHolder(None) guivm = self.app.add_new_vm( @@ -893,7 +884,7 @@ class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): def test_115_audiovm(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): - audiovm = qubes.property("audiovm", default=(lambda self: "dom0")) + audiovm = qubes.property("audiovm", default=lambda self: "dom0") holder = MyTestHolder(None) audiovm = self.app.add_new_vm( @@ -968,7 +959,7 @@ def test_117_remotevm_status(self): remotevm1.get_mem(), ] == ["Running", 0, 0] - @unittest.mock.patch("qubes.vm.qubesvm.QubesVM.untrusted_qdb") + @mock.patch("qubes.vm.qubesvm.QubesVM.untrusted_qdb") def test_118_remotevm_set_relayvm(self, mock_qubesdb): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): relayvm = qubes.property("relayvm") @@ -1020,7 +1011,7 @@ class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): ) def test_200_remove_template(self): - appvm = self.app.add_new_vm( + self.app.add_new_vm( "AppVM", name="test-vm", template=self.template, label="red" ) with mock.patch.object(self.app, "vmm"): @@ -1070,7 +1061,7 @@ def test_204_remove_appvm_dispvm(self): dispvm = self.app.add_new_vm( "AppVM", name="test-appvm", template=self.template, label="red" ) - appvm = self.app.add_new_vm( + self.app.add_new_vm( "AppVM", name="test-appvm2", template=self.template, @@ -1089,7 +1080,7 @@ def test_205_remove_appvm_dispvm(self): template_for_dispvms=True, label="red", ) - dispvm = self.app.add_new_vm( + self.app.add_new_vm( "DispVM", name="test-dispvm", template=appvm, label="red" ) with mock.patch.object(self.app, "vmm"): @@ -1267,9 +1258,7 @@ def test_303_preload_default_dispvm_change_partial_noop(self): @qubes.tests.skipUnlessGit def test_900_example_xml_in_doc(self): - self.assertXMLIsValid( - lxml.etree.parse( - open(os.path.join(qubes.tests.in_git, "doc/example.xml"), "rb") - ), - "qubes.rng", - ) + path = os.path.join(qubes.tests.in_git, "doc/example.xml") + with open(path, "rb") as file: + tree = lxml.etree.parse(file) + self.assertXMLIsValid(tree, "qubes.rng") diff --git a/qubes/tests/ext.py b/qubes/tests/ext.py index 7187dc99e..e98010c91 100644 --- a/qubes/tests/ext.py +++ b/qubes/tests/ext.py @@ -19,7 +19,8 @@ # License along with this library; if not, see . import os -import unittest.mock + +from unittest import mock import qubes.ext.admin import qubes.ext.audio @@ -32,8 +33,6 @@ import qubes.tests import qubes.vm.qubesvm -from unittest import mock - class TC_00_CoreFeatures(qubes.tests.QubesTestCase): maxDiff = None @@ -233,7 +232,7 @@ def test_017_notify_tools_template_based(self): ("template.__bool__", (), {}), ( "log.warning", - ("Ignoring qubes.NotifyTools for template-based " "VM",), + ("Ignoring qubes.NotifyTools for template-based VM",), {}, ), ], @@ -430,8 +429,8 @@ def test_032_distro_meta_invalid(self): ("os-distribution-like", "debian"), {}, ), - ("log.warning", unittest.mock.ANY, {}), - ("log.warning", unittest.mock.ANY, {}), + ("log.warning", mock.ANY, {}), + ("log.warning", mock.ANY, {}), ("features.items", (), {}), ("features.get", ("qrexec", False), {}), ], @@ -462,8 +461,8 @@ def test_033_distro_meta_invalid2(self): ("os-distribution-like", "debian"), {}, ), - ("log.warning", unittest.mock.ANY, {}), - ("log.warning", unittest.mock.ANY, {}), + ("log.warning", mock.ANY, {}), + ("log.warning", mock.ANY, {}), ("features.items", (), {}), ("features.get", ("qrexec", False), {}), ], @@ -1698,19 +1697,18 @@ def setUp(self): self.vm.configure_mock( **{ "features.get.side_effect": self.features.get, - "features.check_with_template.side_effect": self.mock_check_with_template, + "features.check_with_template.side_effect": self.mock_check_tpl, "features.__contains__.side_effect": self.features.__contains__, "features.__setitem__.side_effect": self.features.__setitem__, } ) - def mock_check_with_template(self, name, default): + def mock_check_tpl(self, name, default): if hasattr(self.vm, "template"): return self.features.get( name, self.template_features.get(name, default) ) - else: - return self.features.get(name, default) + return self.features.get(name, default) def test_000_notify_tools_full(self): del self.vm.template @@ -1963,14 +1961,14 @@ def test_012_supported_services_remove(self): ) def test_013_feature_set_dom0(self): - self.test_base_dir = "/tmp/qubes-test-dir" - self.base_dir_patch = mock.patch.dict( - qubes.config.system_path, {"dom0_services_dir": self.test_base_dir} + test_base_dir = "/tmp/qubes-test-dir" + base_dir_patch = mock.patch.dict( + qubes.config.system_path, {"dom0_services_dir": test_base_dir} ) - self.base_dir_patch.start() - self.addCleanup(self.base_dir_patch.stop) + base_dir_patch.start() + self.addCleanup(base_dir_patch.stop) service = "guivm-gui-agent" - service_path = self.test_base_dir + "/" + service + service_path = test_base_dir + "/" + service self.ext.on_domain_feature_set( self.dom0, @@ -1981,14 +1979,14 @@ def test_013_feature_set_dom0(self): self.assertEqual(os.path.exists(service_path), True) def test_014_feature_delete_dom0(self): - self.test_base_dir = "/tmp/qubes-test-dir" - self.base_dir_patch = mock.patch.dict( - qubes.config.system_path, {"dom0_services_dir": self.test_base_dir} + test_base_dir = "/tmp/qubes-test-dir" + base_dir_patch = mock.patch.dict( + qubes.config.system_path, {"dom0_services_dir": test_base_dir} ) - self.base_dir_patch.start() - self.addCleanup(self.base_dir_patch.stop) + base_dir_patch.start() + self.addCleanup(base_dir_patch.stop) service = "guivm-gui-agent" - service_path = self.test_base_dir + "/" + service + service_path = test_base_dir + "/" + service self.ext.on_domain_feature_set( self.dom0, @@ -2006,14 +2004,14 @@ def test_014_feature_delete_dom0(self): self.assertEqual(os.path.exists(service_path), False) def test_014_feature_set_empty_value_dom0(self): - self.test_base_dir = "/tmp/qubes-test-dir" - self.base_dir_patch = mock.patch.dict( - qubes.config.system_path, {"dom0_services_dir": self.test_base_dir} + test_base_dir = "/tmp/qubes-test-dir" + base_dir_patch = mock.patch.dict( + qubes.config.system_path, {"dom0_services_dir": test_base_dir} ) - self.base_dir_patch.start() - self.addCleanup(self.base_dir_patch.stop) + base_dir_patch.start() + self.addCleanup(base_dir_patch.stop) service = "guivm-gui-agent" - service_path = self.test_base_dir + "/" + service + service_path = test_base_dir + "/" + service self.ext.on_domain_feature_set( self.dom0, @@ -2631,11 +2629,9 @@ def test_000_shutdown(self): ) def test_000_shutdown_used(self): - with unittest.mock.patch.object( + with mock.patch.object( self.ext, "attached_vms", return_value=[self.client] - ), unittest.mock.patch.object( - self.client, "is_running", return_value=True - ): + ), mock.patch.object(self.client, "is_running", return_value=True): self.client.is_preload = False with self.assertRaises(qubes.exc.QubesVMInUseError): self.ext.on_domain_pre_shutdown( @@ -2650,13 +2646,13 @@ def test_000_shutdown_used(self): ) def test_000_shutdown_used_by_some(self): - with unittest.mock.patch.object( + with mock.patch.object( self.ext, "attached_vms", return_value=[self.client, self.client_alt], - ), unittest.mock.patch.object( + ), mock.patch.object( self.client, "is_running", return_value=True - ), unittest.mock.patch.object( + ), mock.patch.object( self.client_alt, "is_running", return_value=True ): self.client.is_preload = False diff --git a/qubes/tests/integ/dispvm.py b/qubes/tests/integ/dispvm.py index 62892e898..2ac559b03 100644 --- a/qubes/tests/integ/dispvm.py +++ b/qubes/tests/integ/dispvm.py @@ -26,7 +26,7 @@ import time import unittest from contextlib import suppress -from distutils import spawn +from shutil import which from unittest.mock import patch, mock_open import asyncio import sys @@ -34,7 +34,6 @@ import qubes.config import qubes.tests -import qubesadmin.exc # nose will duplicate this logger. logger = logging.getLogger(__name__) @@ -49,7 +48,7 @@ class TC_04_DispVM(qubes.tests.SystemTestCase): def setUp(self): - super(TC_04_DispVM, self).setUp() + super().setUp() self.init_default_template() self.disp_base = self.app.add_new_vm( qubes.vm.appvm.AppVM, @@ -73,7 +72,7 @@ def setUp(self): def tearDown(self): self.app.default_dispvm = None - super(TC_04_DispVM, self).tearDown() + super().tearDown() def wait_for_dispvm_destroy(self, dispvm_name: list): timeout = 20 @@ -138,6 +137,7 @@ def test_003_cleanup_destroyed(self): self.assertNotIn(dispvm_name, self.app.domains) def _count_dispvms(self, *args, **kwargs): + # pylint: disable=unused-argument self.startup_counter += 1 def test_010_failed_start(self): @@ -195,10 +195,10 @@ def test_011_failed_start_timeout(self): self.assertEqual(self.startup_counter, 1) -class TC_20_DispVMMixin(object): +class TC_20_DispVMMixin: def setUp(self): # pylint: disable=invalid-name logger.info("start") - super(TC_20_DispVMMixin, self).setUp() + super().setUp() if "whonix-g" in self.template: self.skipTest( "whonix gateway is not supported as DisposableVM Template" @@ -227,11 +227,11 @@ def setUp(self): # pylint: disable=invalid-name self.start_vm(self.disp_base), self.start_vm(self.disp_base_alt), ] + self.loop.run_until_complete(asyncio.gather(*start_tasks)) shutdown_tasks = [ self.disp_base.shutdown(wait=True), self.disp_base_alt.shutdown(wait=True), ] - self.loop.run_until_complete(asyncio.gather(*start_tasks)) self.loop.run_until_complete(asyncio.gather(*shutdown_tasks)) # Setting "default_dispvm" fires the preload event before patches of # each test function is applied. @@ -259,7 +259,7 @@ def tearDown(self): # pylint: disable=invalid-name if "_preload_" not in self._testMethodName: self.app.default_dispvm = None self.app.save() - super(TC_20_DispVMMixin, self).tearDown() + super().tearDown() logger.info("end") def _test_event_handler( @@ -787,9 +787,7 @@ async def _test_019_preload_refresh(self): self.log_preload() logger.info("end") - @unittest.skipUnless( - spawn.find_executable("xdotool"), "xdotool not installed" - ) + @unittest.skipUnless(which("xdotool"), "xdotool not installed") def test_020_gui_app(self): dispvm = self.loop.run_until_complete( qubes.vm.dispvm.DispVM.from_appvm(self.disp_base) @@ -807,8 +805,8 @@ def test_020_gui_app(self): # wait for DispVM startup: p.stdin.write(b"echo test\n") self.loop.run_until_complete(p.stdin.drain()) - l = self.loop.run_until_complete(p.stdout.readline()) - self.assertEqual(l, b"test\n") + line = self.loop.run_until_complete(p.stdout.readline()) + self.assertEqual(line, b"test\n") self.assertTrue(dispvm.is_running()) try: @@ -849,9 +847,10 @@ def test_020_gui_app(self): ) def _handle_editor(self, winid, copy=False): - (window_title, _) = subprocess.Popen( + with subprocess.Popen( ["xdotool", "getwindowname", winid], stdout=subprocess.PIPE - ).communicate() + ) as proc: + (window_title, _) = proc.communicate() window_title = ( window_title.decode() .strip() @@ -861,7 +860,7 @@ def _handle_editor(self, winid, copy=False): time.sleep(1) if "LibreOffice" in window_title: # wait for actual editor (we've got splash screen) - search = subprocess.Popen( + with subprocess.Popen( [ "xdotool", "search", @@ -873,11 +872,11 @@ def _handle_editor(self, winid, copy=False): "disp*|Writer", ], stdout=subprocess.PIPE, - stderr=open(os.path.devnull, "w"), - ) - retcode = search.wait() - if retcode == 0: - winid = search.stdout.read().strip() + stderr=subprocess.DEVNULL, + ) as search: + retcode = search.wait() + if retcode == 0: + winid = search.stdout.read().strip() time.sleep(0.5) subprocess.check_call( ["xdotool", "windowactivate", "--sync", winid] @@ -929,24 +928,25 @@ def _handle_editor(self, winid, copy=False): ) if copy: raise NotImplementedError("copy not implemented for vim") - else: - subprocess.check_call( - ["xdotool", "key", "i", "type", "Test test 2"] - ) - subprocess.check_call( - ["xdotool", "key", "--window", winid, "key", "Return"] - ) - subprocess.check_call( - ["xdotool", "key", "Escape", "colon", "w", "q", "Return"] - ) - elif ( - "gedit" in window_title - or "KWrite" in window_title - or "Mousepad" in window_title - or "Geany" in window_title - or "Text Editor" in window_title - # FeatherPad (default in Whonix 18), no app name in the title... - or "test.txt" in window_title + subprocess.check_call( + ["xdotool", "key", "i", "type", "Test test 2"] + ) + subprocess.check_call( + ["xdotool", "key", "--window", winid, "key", "Return"] + ) + subprocess.check_call( + ["xdotool", "key", "Escape", "colon", "w", "q", "Return"] + ) + elif any( + e in window_title + for e in ( + "gedit", + "KWrite", + "Mousepad", + "Geany", + "Text Editor", + "test.txt", + ) ): subprocess.check_call( ["xdotool", "windowactivate", "--sync", winid] @@ -984,7 +984,7 @@ def _whonix_ws_dispvm_confirm(self, action_str): include_tray=False, timeout=5, ) - except Exception: + except Exception: # pylint: disable=broad-exception-caught return ( False, "Failed to find qrexec confirmation window for " @@ -1011,9 +1011,7 @@ def _whonix_ws_dispvm_confirm(self, action_str): ) return (True, "") - @unittest.skipUnless( - spawn.find_executable("xdotool"), "xdotool not installed" - ) + @unittest.skipUnless(which("xdotool"), "xdotool not installed") def test_030_edit_file(self): self.testvm1 = self.app.add_new_vm( qubes.vm.appvm.AppVM, @@ -1052,18 +1050,17 @@ def test_030_edit_file(self): include_tray=False, timeout=60, ) - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught try: self.loop.run_until_complete(asyncio.wait_for(p.wait(), 1)) except asyncio.TimeoutError: raise e - else: - stdout = self.loop.run_until_complete(p.stdout.read()) - self.fail( - "qvm-open-in-dvm exited prematurely with {}: {}".format( - p.returncode, stdout - ) + stdout = self.loop.run_until_complete(p.stdout.read()) + self.fail( + "qvm-open-in-dvm exited prematurely with {}: {}".format( + p.returncode, stdout ) + ) # let the application initialize self.loop.run_until_complete(asyncio.sleep(1)) try: @@ -1106,8 +1103,8 @@ def _get_open_script(self, application): "/usr/share/qubes/tests-data/" "dispvm-open-thunderbird-attachment", "rb", - ) as f: - return f.read() + ) as file: + return file.read() assert False def _get_apps_list(self, template): @@ -1130,9 +1127,7 @@ def _get_apps_list(self, template): if l.endswith(".desktop") ] - @unittest.skipUnless( - spawn.find_executable("xdotool"), "xdotool not installed" - ) + @unittest.skipUnless(which("xdotool"), "xdotool not installed") def test_100_open_in_dispvm(self): if "whonix-w" in self.template: self.skipTest( @@ -1197,7 +1192,7 @@ def test_100_open_in_dispvm(self): self.loop.run_until_complete(asyncio.sleep(3)) try: - click_to_open = self.loop.run_until_complete( + self.loop.run_until_complete( self.testvm1.run_for_stdio( "./open-file test.txt", stdout=subprocess.PIPE, @@ -1211,7 +1206,7 @@ def test_100_open_in_dispvm(self): self.skipTest("{} not installed".format(app_id)) self.fail( "'./open-file test.txt' failed with {}: {}{}".format( - err.cmd, err.returncode, err.stdout, err.stderr + err.returncode, err.stdout, err.stderr ) ) @@ -1241,8 +1236,8 @@ def test_100_open_in_dispvm(self): self.wait_for_window_hide_coro("editor", winid) ) - with open("/var/run/qubes/qubes-clipboard.bin", "rb") as f: - test_txt_content = f.read() + with open("/var/run/qubes/qubes-clipboard.bin", "rb") as file: + test_txt_content = file.read() self.assertEqual(test_txt_content.strip(), b"test1") # this doesn't really close the application, only the qrexec-client @@ -1260,7 +1255,7 @@ def create_testcases_for_templates(): ) -def load_tests(loader, tests, pattern): +def load_tests(loader, tests, _pattern): tests.addTests(loader.loadTestsFromNames(create_testcases_for_templates())) return tests diff --git a/qubes/tests/integ/dispvm_perf.py b/qubes/tests/integ/dispvm_perf.py index 960bc8b25..dce1d2715 100644 --- a/qubes/tests/integ/dispvm_perf.py +++ b/qubes/tests/integ/dispvm_perf.py @@ -21,10 +21,27 @@ import shutil import sys import tempfile - +import unittest import qubes.tests +def env_with_reason(env_var, default): + env_var = "QUBES_TEST_PERF_" + env_var + value = os.environ.get(env_var, default) + reason = "environment variable is empty: " + env_var + return value, reason + + +CONCURRENCY = env_with_reason("CONCURRENCY", False) +GUI = env_with_reason("GUI", True) +EXTENDED = env_with_reason("EXTENDED", False) +DOM0_DISPVM = env_with_reason("DOM0_DISPVM", False) +DOM0_DISPVM_API = env_with_reason("DOM0_DISPVM_API", True) +DOM0_VM_API = env_with_reason("DOM0_VM_API", True) +VM_DISPVM = env_with_reason("VM_DISPVM", False) +VM_VM = env_with_reason("VM_VM", False) + + class TC_00_DispVMPerfMixin: def setUp(self: qubes.tests.SystemTestCase): super().setUp() @@ -41,6 +58,8 @@ def setUp(self: qubes.tests.SystemTestCase): self.test_dir = tempfile.mkdtemp(prefix=prefix) self.vm2 = None return + self.app.domains["dom0"].features["preload-dispvm-max"] = "" + self.app.domains["dom0"].features["preload-dispvm-threshold"] = "" self.dvm = self.app.add_new_vm( "AppVM", name=self.make_vm_name("dvm"), @@ -127,147 +146,256 @@ def run_reader(self, args: list): if p.returncode: self.fail(f"'{' '.join(cmd)}' failed: {p.returncode}") + @unittest.skipUnless(*VM_DISPVM) def test_000_vm_dispvm(self): """Latency of vm-dispvm calls""" self.run_test("vm-dispvm") + @unittest.skipUnless(*VM_DISPVM) + @unittest.skipUnless(*GUI) def test_001_vm_dispvm_gui(self): """Latency of vm-dispvm GUI calls""" self.run_test("vm-dispvm-gui") + @unittest.skipUnless(*VM_DISPVM) + @unittest.skipUnless(*CONCURRENCY) def test_002_vm_dispvm_concurrent(self): """Latency of vm-dispvm concurrent calls""" self.run_test("vm-dispvm-concurrent") + @unittest.skipUnless(*VM_DISPVM) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) def test_003_vm_dispvm_gui_concurrent(self): """Latency of vm-dispvm concurrent GUI calls""" self.run_test("vm-dispvm-gui-concurrent") - def test_006_dom0_dispvm(self): + @unittest.skipUnless(*DOM0_DISPVM) + def test_100_dom0_dispvm(self): """Latency of dom0-dispvm calls""" self.run_test("dom0-dispvm") - def test_007_dom0_dispvm_gui(self): + @unittest.skipUnless(*DOM0_DISPVM) + @unittest.skipUnless(*GUI) + def test_101_dom0_dispvm_gui(self): """Latency of dom0-dispvm GUI calls""" self.run_test("dom0-dispvm-gui") - def test_008_dom0_dispvm_concurrent(self): + @unittest.skipUnless(*DOM0_DISPVM) + @unittest.skipUnless(*CONCURRENCY) + def test_102_dom0_dispvm_concurrent(self): """Latency of dom0-dispvm concurrent calls""" self.run_test("dom0-dispvm-concurrent") - def test_009_dom0_dispvm_gui_concurrent(self): + @unittest.skipUnless(*DOM0_DISPVM) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) + def test_103_dom0_dispvm_gui_concurrent(self): """Latency of dom0-dispvm concurrent GUI calls""" self.run_test("dom0-dispvm-gui-concurrent") - def test_020_vm_dispvm_preload(self): + @unittest.skipUnless(*VM_DISPVM) + def test_200_vm_dispvm_preload(self): """Latency of vm-dispvm (preload) calls""" self.run_test("vm-dispvm-preload") - def test_021_vm_dispvm_preload_gui(self): + @unittest.skipUnless(*VM_DISPVM) + @unittest.skipUnless(*GUI) + def test_201_vm_dispvm_preload_gui(self): """Latency of vm-dispvm (preload) GUI calls""" self.run_test("vm-dispvm-preload-gui") - def test_022_vm_dispvm_preload_concurrent(self): + @unittest.skipUnless(*VM_DISPVM) + @unittest.skipUnless(*CONCURRENCY) + def test_202_vm_dispvm_preload_concurrent(self): """Latency of vm-dispvm (preload) concurrent calls""" self.run_test("vm-dispvm-preload-concurrent") - def test_023_vm_dispvm_preload_gui_concurrent(self): + @unittest.skipUnless(*VM_DISPVM) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) + def test_203_vm_dispvm_preload_gui_concurrent(self): """Latency of vm-dispvm (preload) concurrent GUI calls""" self.run_test("vm-dispvm-preload-gui-concurrent") - def test_026_dom0_dispvm_preload(self): + @unittest.skipUnless(*DOM0_DISPVM) + def test_300_dom0_dispvm_preload(self): """Latency of dom0-dispvm (preload) calls""" self.run_test("dom0-dispvm-preload") - def test_027_dom0_dispvm_preload_gui(self): + @unittest.skipUnless(*DOM0_DISPVM) + @unittest.skipUnless(*GUI) + def test_301_dom0_dispvm_preload_gui(self): """Latency of dom0-dispvm (preload) GUI calls""" self.run_test("dom0-dispvm-preload-gui") - def test_028_dom0_dispvm_preload_concurrent(self): + @unittest.skipUnless(*DOM0_DISPVM) + @unittest.skipUnless(*CONCURRENCY) + def test_302_dom0_dispvm_preload_concurrent(self): """Latency of dom0-dispvm (preload) concurrent calls""" self.run_test("dom0-dispvm-preload-concurrent") - def test_029_dom0_dispvm_preload_gui_concurrent(self): + @unittest.skipUnless(*DOM0_DISPVM) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) + def test_303_dom0_dispvm_preload_gui_concurrent(self): """Latency of dom0-dispvm (preload) concurrent GUI calls""" self.run_test("dom0-dispvm-preload-gui-concurrent") + @unittest.skipUnless(*DOM0_DISPVM_API) def test_400_dom0_dispvm_api(self): """Latency of dom0-dispvm API calls""" self.run_test("dom0-dispvm-api") + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) def test_401_dom0_dispvm_gui_api(self): """Latency of dom0-dispvm GUI API calls""" self.run_test("dom0-dispvm-gui-api") + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*CONCURRENCY) def test_402_dom0_dispvm_concurrent_api(self): """Latency of dom0-dispvm concurrent API calls""" self.run_test("dom0-dispvm-concurrent-api") + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) def test_403_dom0_dispvm_gui_concurrent_api(self): """Latency of dom0-dispvm concurrent GUI API calls""" self.run_test("dom0-dispvm-gui-concurrent-api") - def test_404_dom0_dispvm_preload_less_less_api(self): - """Latency of dom0-dispvm (preload less less) API calls""" - self.run_test("dom0-dispvm-preload-less-less-api") - - def test_405_dom0_dispvm_preload_less_api(self): - """Latency of dom0-dispvm (preload less) API calls""" - self.run_test("dom0-dispvm-preload-less-api") - - def test_406_dom0_dispvm_preload_api(self): - """Latency of dom0-dispvm (preload) API calls""" - self.run_test("dom0-dispvm-preload-api") - - def test_407_dom0_dispvm_preload_more_api(self): - """Latency of dom0-dispvm (preload more) API calls""" - self.run_test("dom0-dispvm-preload-more-api") - - def test_408_dom0_dispvm_preload_more_more_api(self): - """Latency of dom0-dispvm (preload more more) API calls""" - self.run_test("dom0-dispvm-preload-more-more-api") - - def test_409_dom0_dispvm_preload_gui_api(self): - """Latency of dom0-dispvm (preload) GUI API calls""" - self.run_test("dom0-dispvm-preload-gui-api") - - def test_410_dom0_dispvm_preload_concurrent_api(self): - """Latency of dom0-dispvm (preload) concurrent GUI API calls""" + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*CONCURRENCY) + def test_404_dom0_dispvm_preload_concurrent_api(self): + """Latency of dom0-dispvm (preload) concurrent API calls""" self.run_test("dom0-dispvm-preload-concurrent-api") - def test_411_dom0_dispvm_preload_gui_concurrent_api(self): + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) + def test_405_dom0_dispvm_preload_gui_concurrent_api(self): """Latency of dom0-dispvm (preload) concurrent GUI API calls""" self.run_test("dom0-dispvm-preload-gui-concurrent-api") - def test_800_vm_vm(self): + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*EXTENDED) + def test_410_dom0_dispvm_preload_1_api(self): + """Latency of dom0-dispvm (1 preload) API calls""" + self.run_test("dom0-dispvm-preload-1-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + def test_411_dom0_dispvm_preload_2_api(self): + """Latency of dom0-dispvm (2 preload) API calls""" + self.run_test("dom0-dispvm-preload-2-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*EXTENDED) + def test_412_dom0_dispvm_preload_3_api(self): + """Latency of dom0-dispvm (3 preload) API calls""" + self.run_test("dom0-dispvm-preload-3-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + def test_413_dom0_dispvm_preload_4_api(self): + """Latency of dom0-dispvm (4 preload) API calls""" + self.run_test("dom0-dispvm-preload-4-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*EXTENDED) + def test_414_dom0_dispvm_preload_5_api(self): + """Latency of dom0-dispvm (5 preload) API calls""" + self.run_test("dom0-dispvm-preload-5-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + def test_415_dom0_dispvm_preload_6_api(self): + """Latency of dom0-dispvm (6 preload) API calls""" + self.run_test("dom0-dispvm-preload-6-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*EXTENDED) + def test_420_dom0_dispvm_preload_1_gui_api(self): + """Latency of dom0-dispvm (1 preload) GUI API calls""" + self.run_test("dom0-dispvm-preload-1-gui-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + def test_421_dom0_dispvm_preload_2_gui_api(self): + """Latency of dom0-dispvm (2 preload) GUI API calls""" + self.run_test("dom0-dispvm-preload-2-gui-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*EXTENDED) + def test_422_dom0_dispvm_preload_3_gui_api(self): + """Latency of dom0-dispvm (3 preload) GUI API calls""" + self.run_test("dom0-dispvm-preload-3-gui-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + def test_423_dom0_dispvm_preload_4_gui_api(self): + """Latency of dom0-dispvm (4 preload) GUI API calls""" + self.run_test("dom0-dispvm-preload-4-gui-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*EXTENDED) + def test_424_dom0_dispvm_preload_5_gui_api(self): + """Latency of dom0-dispvm (5 preload) GUI API calls""" + self.run_test("dom0-dispvm-preload-5-gui-api") + + @unittest.skipUnless(*DOM0_DISPVM_API) + @unittest.skipUnless(*GUI) + def test_425_dom0_dispvm_preload_6_gui_api(self): + """Latency of dom0-dispvm (6 preload) GUI API calls""" + self.run_test("dom0-dispvm-preload-6-gui-api") + + @unittest.skipUnless(*VM_VM) + def test_700_vm_vm(self): """Latency of vm-vm calls""" self.run_test("vm-vm") - def test_801_vm_vm_gui(self): + @unittest.skipUnless(*VM_VM) + @unittest.skipUnless(*GUI) + def test_701_vm_vm_gui(self): """Latency of vm-vm GUI calls""" self.run_test("vm-vm-gui") - def test_802_vm_vm_concurrent(self): + @unittest.skipUnless(*VM_VM) + @unittest.skipUnless(*CONCURRENCY) + def test_702_vm_vm_concurrent(self): """Latency of vm-vm concurrent calls""" self.run_test("vm-vm-concurrent") - def test_803_vm_vm_gui_concurrent(self): + @unittest.skipUnless(*VM_VM) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) + def test_703_vm_vm_gui_concurrent(self): """Latency of vm-vm concurrent GUI calls""" self.run_test("vm-vm-gui-concurrent") - def test_804_dom0_vm_api(self): + @unittest.skipUnless(*DOM0_VM_API) + def test_800_dom0_vm_api(self): """Latency of dom0-vm API calls""" self.run_test("dom0-vm-api") - def test_805_dom0_vm_gui_api(self): + @unittest.skipUnless(*DOM0_VM_API) + @unittest.skipUnless(*GUI) + def test_801_dom0_vm_gui_api(self): """Latency of dom0-vm GUI API calls""" self.run_test("dom0-vm-gui-api") - def test_806_dom0_vm_concurrent_api(self): + @unittest.skipUnless(*DOM0_VM_API) + @unittest.skipUnless(*CONCURRENCY) + def test_802_dom0_vm_concurrent_api(self): """Latency of dom0-vm concurrent API calls""" self.run_test("dom0-vm-concurrent-api") - def test_807_dom0_vm_gui_concurrent_api(self): + @unittest.skipUnless(*DOM0_VM_API) + @unittest.skipUnless(*GUI) + @unittest.skipUnless(*CONCURRENCY) + def test_803_dom0_vm_gui_concurrent_api(self): """Latency of dom0-vm concurrent GUI API calls""" self.run_test("dom0-vm-gui-concurrent-api") diff --git a/qubes/tests/integ/network.py b/qubes/tests/integ/network.py index d114e9e11..2c97f0798 100644 --- a/qubes/tests/integ/network.py +++ b/qubes/tests/integ/network.py @@ -19,7 +19,7 @@ # License along with this library; if not, see . # import contextlib -from distutils import spawn +from shutil import which import asyncio import subprocess @@ -35,7 +35,7 @@ # noinspection PyAttributeOutsideInit,PyPep8Naming @qubes.tests.skipIfTemplate("whonix") -class VmNetworkingMixin(object): +class VmNetworkingMixin: test_ip = "192.168.123.45" test_name = "test.example.com" @@ -80,7 +80,7 @@ def setUp(self): """ :type self: qubes.tests.SystemTestCase | VmNetworkingMixin """ - super(VmNetworkingMixin, self).setUp() + super().setUp() if self.template.startswith("whonix-"): self.skipTest( "Test not supported here - Whonix uses its own " @@ -163,7 +163,7 @@ def tearDown(self): ) self._run_cmd_and_log_output(self.app.domains[0], "xl list") del self.netvms - super(VmNetworkingMixin, self).tearDown() + super().tearDown() def configure_netvm(self): """ @@ -211,11 +211,10 @@ def run_netvm_cmd(qube, cmd): ip=self.test_ip, name=self.test_name ), ) + cmd = "rm -f /etc/resolv.conf && echo nameserver" run_netvm_cmd( qube, - "rm -f /etc/resolv.conf && echo nameserver {} > /etc/resolv.conf".format( - self.test_ip - ), + "{} {} > /etc/resolv.conf".format(cmd, self.test_ip), ) run_netvm_cmd(qube, "systemctl try-restart systemd-resolved || :") run_netvm_cmd(qube, "/usr/lib/qubes/qubes-setup-dnat-to-ns") @@ -474,9 +473,7 @@ def test_010_simple_proxyvm(self): ) @qubes.tests.expectedFailureIfTemplate("debian-7") - @unittest.skipUnless( - spawn.find_executable("xdotool"), "xdotool not installed" - ) + @unittest.skipUnless(which("xdotool"), "xdotool not installed") def test_020_simple_proxyvm_nm(self): """ :type self: qubes.tests.SystemTestCase | VmNetworkingMixin @@ -1157,9 +1154,8 @@ def test_203_fake_ip_inter_vm_allow(self): self.loop.run_until_complete(self.start_vm(self.testvm1)) self.loop.run_until_complete(self.start_vm(self.testvm2)) - cmd = "nft add ip qubes custom-forward ip saddr {} ip daddr {} accept".format( - self.testvm2.ip, self.testvm1.ip - ) + cmd = "nft add ip qubes custom-forward ip saddr " + cmd += "{} ip daddr {} accept".format(self.testvm2.ip, self.testvm1.ip) try: self.loop.run_until_complete( self.proxy.run_for_stdio(cmd, user="root") @@ -1170,9 +1166,8 @@ def test_203_fake_ip_inter_vm_allow(self): ) from None try: - cmd = "nft add ip qubes custom-input ip saddr {} counter accept".format( - self.testvm2.ip - ) + cmd = "nft add ip qubes custom-input ip saddr " + cmd += "{} counter accept".format(self.testvm2.ip) self.loop.run_until_complete( self.testvm1.run_for_stdio(cmd, user="root") ) @@ -1369,7 +1364,7 @@ def create_testcases_for_templates(): ) -def load_tests(loader, tests, pattern): +def load_tests(loader, tests, _pattern): tests.addTests(loader.loadTestsFromNames(create_testcases_for_templates())) return tests diff --git a/qubes/tests/integ/network_ipv6.py b/qubes/tests/integ/network_ipv6.py index 646638acb..6665fc17e 100644 --- a/qubes/tests/integ/network_ipv6.py +++ b/qubes/tests/integ/network_ipv6.py @@ -24,7 +24,7 @@ import sys import time import unittest -from distutils import spawn +from shutil import which import qubes.firewall import qubes.tests @@ -46,7 +46,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): ) def setUp(self): - super(VmIPv6NetworkingMixin, self).setUp() + super().setUp() self.ping6_ip = self.ping6_cmd.format(target=self.test_ip6) self.ping6_name = self.ping6_cmd.format(target=self.test_name) self.ping6_deadline_ip = self.ping6_deadline_cmd.format( @@ -92,7 +92,7 @@ def run_netvm_cmd(qube, cmd): for qube in self.netvms: qube.features["ipv6"] = True - super(VmIPv6NetworkingMixin, self).configure_netvm() + super().configure_netvm() for qube in self.netvms: run_netvm_cmd( @@ -106,12 +106,11 @@ def run_netvm_cmd(qube, cmd): ) # ignore failure self.run_cmd(qube, "while pkill dnsmasq; do sleep 1; done") - run_netvm_cmd( - qube, - "dnsmasq -a {ip} -A /{name}/{ip} -A /{name}/{ip6} -i test0 -z".format( - ip=self.test_ip, ip6=self.test_ip6, name=self.test_name - ), + dnsmasq_args = "-a {ip} -A /{name}/{ip} -A /{name}/{ip6}".format( + ip=self.test_ip, ip6=self.test_ip6, name=self.test_name ) + dnsmasq_cmd = "dnsmasq {} -i test0 -z".format(dnsmasq_args) + run_netvm_cmd(qube, dnsmasq_cmd) def test_500_ipv6_simple_networking(self): """ @@ -204,9 +203,7 @@ def test_510_ipv6_simple_proxyvm(self): ) @qubes.tests.expectedFailureIfTemplate("debian-7") - @unittest.skipUnless( - spawn.find_executable("xdotool"), "xdotool not installed" - ) + @unittest.skipUnless(which("xdotool"), "xdotool not installed") def test_520_ipv6_simple_proxyvm_nm(self): """ :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin @@ -533,13 +530,10 @@ def test_550_ipv6_spoof_ip(self): ) retcode = self.run_cmd(self.testnetvm, cmd) if retcode == 127: + ip6_args = "! -s {} -p icmpv6 -j LOG".format(self.testvm1.ip6) + ip6_cmd = "ip6tables -I INPUT -i vif+ {}".format(ip6_args) self.assertEqual( - self.run_cmd( - self.testnetvm, - "ip6tables -I INPUT -i vif+ ! -s {} -p icmpv6 -j LOG".format( - self.testvm1.ip6 - ), - ), + self.run_cmd(self.testnetvm, ip6_cmd), 0, ) iptables = True @@ -690,7 +684,7 @@ def create_testcases_for_templates(): ) -def load_tests(loader, tests, pattern): +def load_tests(loader, tests, _pattern): tests.addTests(loader.loadTestsFromNames(create_testcases_for_templates())) return tests diff --git a/qubes/tests/vm/adminvm.py b/qubes/tests/vm/adminvm.py index 55734ef8d..5d16105c5 100644 --- a/qubes/tests/vm/adminvm.py +++ b/qubes/tests/vm/adminvm.py @@ -23,10 +23,6 @@ import unittest import unittest.mock -import functools - -import asyncio - import qubes import qubes.exc import qubes.vm @@ -69,7 +65,7 @@ def tearDown(self) -> None: del self.emitter try: os.unlink("/tmp/qubestest.xml") - except: + except: # pylint: disable=bare-except pass super().tearDown() @@ -121,8 +117,8 @@ def test_311_suspend(self): def test_700_run_service(self, mock_subprocess): # if there is a user in 'qubes' group, it should be used by default try: - gr = grp.getgrnam("qubes") - default_user = gr.gr_mem[0] + group = grp.getgrnam("qubes") + default_user = group.gr_mem[0] command_prefix = ["runuser", "-u", default_user, "--"] except (KeyError, IndexError): command_prefix = [] diff --git a/qubes/tests/vm/appvm.py b/qubes/tests/vm/appvm.py index adac199c1..05e1639f5 100644 --- a/qubes/tests/vm/appvm.py +++ b/qubes/tests/vm/appvm.py @@ -29,19 +29,20 @@ import qubes.vm.templatevm -class TestApp(object): +class TestApp: + # pylint: disable=too-few-public-methods labels = {1: qubes.Label(1, "0xcc0000", "red")} def __init__(self): self.domains = {} -class TestProp(object): +class TestProp: # pylint: disable=too-few-public-methods __name__ = "testprop" -class TestVM(object): +class TestVM: # pylint: disable=too-few-public-methods app = TestApp() @@ -56,13 +57,13 @@ def is_running(self): class TestVolume(qubes.storage.Volume): - def create(self): + def create(self): # pylint: disable=invalid-overridden-method pass class TestPool(qubes.storage.Pool): def __init__(self, *args, **kwargs): - super(TestPool, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._volumes = {} def init_volume(self, vm, volume_config): @@ -98,7 +99,7 @@ def cleanup_appvm(self): self.app.domains.clear() self.app.pools.clear() - def get_vm(self, **kwargs): + def get_vm(self, *_args, **kwargs): vm = qubes.vm.appvm.AppVM( self.app, None, @@ -189,13 +190,13 @@ def test_500_property_migrate_template_for_dispvms(self): vm = qubes.vm.appvm.AppVM(self.app, xml) self.assertEqual(vm.template_for_dispvms, True) with self.assertRaises(AttributeError): - vm.dispvm_allowed + vm.dispvm_allowed # pylint: disable=no-member,pointless-statement xml = lxml.etree.XML(xml_template.format(value="False")) vm = qubes.vm.appvm.AppVM(self.app, xml) self.assertEqual(vm.template_for_dispvms, False) with self.assertRaises(AttributeError): - vm.dispvm_allowed + vm.dispvm_allowed # pylint: disable=no-member,pointless-statement def test_600_load_volume_config(self): xml_template = """ diff --git a/qubes/tests/vm/dispvm.py b/qubes/tests/vm/dispvm.py index 7644f5fb7..281595145 100644 --- a/qubes/tests/vm/dispvm.py +++ b/qubes/tests/vm/dispvm.py @@ -19,7 +19,7 @@ # License along with this library; if not, see . import unittest -import unittest.mock as mock +from unittest import mock import asyncio @@ -34,7 +34,7 @@ class TestApp(qubes.tests.vm.TestApp): def __init__(self): - super(TestApp, self).__init__() + super().__init__() self.qid_counter = 0 def add_new_vm(self, cls, **kwargs): @@ -52,7 +52,7 @@ def add_new_vm(self, cls, **kwargs): class TC_00_DispVM(qubes.tests.QubesTestCase): def setUp(self): - super(TC_00_DispVM, self).setUp() + super().setUp() self.app = TestApp() self.app.save = mock.Mock() self.app.pools["default"] = qubes.tests.vm.appvm.TestPool( @@ -85,7 +85,7 @@ def setUp(self): def tearDown(self): del self.emitter - super(TC_00_DispVM, self).tearDown() + super().tearDown() def cleanup_dispvm(self): if hasattr(self, "dispvm"): @@ -335,7 +335,7 @@ def test_000_get_preload_templates(self): def test_001_from_appvm_reject_not_allowed(self): with self.assertRaises(qubes.exc.QubesException): - dispvm = self.loop.run_until_complete( + self.loop.run_until_complete( qubes.vm.dispvm.DispVM.from_appvm(self.appvm) ) @@ -352,20 +352,20 @@ def test_002_template_change(self): "__getitem__.side_effect": orig_getitem, } ) - self.dispvm = self.app.add_new_vm( + dispvm = self.app.add_new_vm( qubes.vm.dispvm.DispVM, name="test-dispvm", template=self.appvm ) - self.dispvm.template = self.appvm - self.loop.run_until_complete(self.dispvm.start()) + dispvm.template = self.appvm + self.loop.run_until_complete(dispvm.start()) if not self.app.vmm.offline_mode: assert not dispvm.is_halted() with self.assertRaises(qubes.exc.QubesVMNotHaltedError): - self.dispvm.template = self.appvm + dispvm.template = self.appvm with self.assertRaises(qubes.exc.QubesValueError): - self.dispvm.template = qubes.property.DEFAULT - self.loop.run_until_complete(self.dispvm.kill()) - self.dispvm.template = self.appvm + dispvm.template = qubes.property.DEFAULT + self.loop.run_until_complete(dispvm.kill()) + dispvm.template = self.appvm def test_003_dvmtemplate_template_change(self): self.appvm.template_for_dispvms = True @@ -381,7 +381,7 @@ def test_003_dvmtemplate_template_change(self): "__setitem__.side_effect": orig_domains.__setitem__, } ) - self.dispvm = self.app.add_new_vm( + self.app.add_new_vm( qubes.vm.dispvm.DispVM, name="test-dispvm", template=self.appvm ) @@ -403,7 +403,7 @@ def test_004_dvmtemplate_allowed_change(self): "__setitem__.side_effect": orig_domains.__setitem__, } ) - self.dispvm = self.app.add_new_vm( + self.app.add_new_vm( qubes.vm.dispvm.DispVM, name="test-dispvm", template=self.appvm ) @@ -422,11 +422,10 @@ def test_010_create_direct(self): "__getitem__.side_effect": orig_getitem, } ) - self.dispvm = self.app.add_new_vm( + dispvm = self.app.add_new_vm( qubes.vm.dispvm.DispVM, name="test-dispvm", template=self.appvm ) mock_domains.get_new_unused_dispid.assert_called_once_with() - dispvm = self.dispvm self.assertEqual(dispvm.name, "test-dispvm") self.assertEqual(dispvm.template, self.appvm) self.assertEqual(dispvm.label, self.appvm.label) @@ -475,7 +474,7 @@ def test_011_create_direct_reject(self): @mock.patch("os.symlink") @mock.patch("os.makedirs") - def test_020_copy_storage_pool(self, mock_makedirs, mock_symlink): + def test_020_copy_storage_pool(self, _mock_makedirs, _mock_symlink): self.app.pools["alternative"] = qubes.tests.vm.appvm.TestPool( name="alternative" ) @@ -525,11 +524,11 @@ def test_021_storage_template_change(self): "__setitem__.side_effect": orig_domains.__setitem__, } ) - vm = self.dispvm = self.app.add_new_vm( + dispvm = self.app.add_new_vm( qubes.vm.dispvm.DispVM, name="test-dispvm", template=self.appvm ) self.assertIs( - vm.volume_config["root"]["source"], + dispvm.volume_config["root"]["source"], self.template.volumes["root"], ) # create new mock, so new template will get different volumes @@ -543,20 +542,21 @@ def test_021_storage_template_change(self): self.app.domains[template2] = template2 self.appvm.template = template2 - self.assertFalse(vm.volume_config["root"]["save_on_stop"]) - self.assertTrue(vm.volume_config["root"]["snap_on_start"]) + self.assertFalse(dispvm.volume_config["root"]["save_on_stop"]) + self.assertTrue(dispvm.volume_config["root"]["snap_on_start"]) self.assertNotEqual( - vm.volume_config["root"]["source"], self.template.volumes["root"] + dispvm.volume_config["root"]["source"], + self.template.volumes["root"], ) self.assertIs( - vm.volume_config["root"]["source"], template2.volumes["root"] + dispvm.volume_config["root"]["source"], template2.volumes["root"] ) self.assertIs( - vm.volume_config["root"]["source"], + dispvm.volume_config["root"]["source"], self.appvm.volume_config["root"]["source"], ) self.assertIs( - vm.volume_config["private"]["source"], + dispvm.volume_config["private"]["source"], self.appvm.volumes["private"], ) @@ -575,7 +575,7 @@ def test_022_storage_app_change(self): "__setitem__.side_effect": orig_domains.__setitem__, } ) - vm = self.dispvm = self.app.add_new_vm( + vm = dispvm = self.app.add_new_vm( qubes.vm.dispvm.DispVM, name="test-dispvm", template=self.appvm ) self.assertTrue(vm.events_enabled) @@ -600,9 +600,9 @@ def test_022_storage_app_change(self): app2.template_for_dispvms = True self.app.domains[app2.name] = app2 self.app.domains[app2] = app2 - self.dispvm.template = app2 + dispvm.template = app2 - self.assertIs(vm, self.dispvm) + self.assertIs(vm, dispvm) self.assertFalse(vm.volume_config["root"]["save_on_stop"]) self.assertTrue(vm.volume_config["root"]["snap_on_start"]) self.assertFalse(vm.volume_config["private"]["save_on_stop"]) @@ -630,7 +630,7 @@ def test_022_storage_app_change(self): @mock.patch("os.symlink") @mock.patch("os.makedirs") - def test_023_inherit_ephemeral(self, mock_makedirs, mock_symlink): + def test_023_inherit_ephemeral(self, _mock_makedirs, _mock_symlink): self.app.pools["alternative"] = qubes.tests.vm.appvm.TestPool( name="alternative" ) diff --git a/qubes/tests/vm/mix/dvmtemplate.py b/qubes/tests/vm/mix/dvmtemplate.py index 154473fdc..238e877cf 100755 --- a/qubes/tests/vm/mix/dvmtemplate.py +++ b/qubes/tests/vm/mix/dvmtemplate.py @@ -33,7 +33,7 @@ class TestApp(qubes.tests.vm.TestApp): def __init__(self): - super(TestApp, self).__init__() + super().__init__() self.qid_counter = 0 def add_new_vm(self, cls, **kwargs): @@ -54,7 +54,7 @@ class TC_00_DVMTemplateMixin( qubes.tests.QubesTestCase, ): def setUp(self): - super(TC_00_DVMTemplateMixin, self).setUp() + super().setUp() self.app = TestApp() self.app.save = mock.Mock() self.app.pools["default"] = qubes.tests.vm.appvm.TestPool( @@ -93,7 +93,7 @@ def setUp(self): def tearDown(self): self.app.default_dispvm = None del self.emitter - super(TC_00_DVMTemplateMixin, self).tearDown() + super().tearDown() def cleanup_adminvm(self): self.adminvm.close() diff --git a/qubes/tests/vm/mix/net.py b/qubes/tests/vm/mix/net.py index 1d286262f..f28ce98ae 100644 --- a/qubes/tests/vm/mix/net.py +++ b/qubes/tests/vm/mix/net.py @@ -20,7 +20,6 @@ # License along with this library; if not, see . # import ipaddress -import unittest from unittest.mock import patch import qubes @@ -35,7 +34,7 @@ class TC_00_NetVMMixin( qubes.tests.vm.qubesvm.QubesVMTestsMixin, qubes.tests.QubesTestCase ): def setUp(self): - super(TC_00_NetVMMixin, self).setUp() + super().setUp() self.app = qubes.tests.vm.TestApp() self.app.vmm.offline_mode = True diff --git a/qubes/tests/vm/qubesvm.py b/qubes/tests/vm/qubesvm.py index 50bfa1821..2af1763b3 100644 --- a/qubes/tests/vm/qubesvm.py +++ b/qubes/tests/vm/qubesvm.py @@ -29,9 +29,6 @@ from uuid import UUID import datetime -import asyncio - -import functools import lxml.etree import unittest.mock diff --git a/qubes/vm/appvm.py b/qubes/vm/appvm.py index f046d05d3..0089a1f6d 100644 --- a/qubes/vm/appvm.py +++ b/qubes/vm/appvm.py @@ -114,6 +114,7 @@ def __init__(self, app, xml, **kwargs): @qubes.stateless_property def icon(self): if self.template_for_dispvms: + # pylint: disable=no-member return "templatevm-" + self.label.name # multi-inheritance and properties confuses pylint here # pylint: disable=no-member diff --git a/qvm-tools/qubes-hcl-report b/qvm-tools/qubes-hcl-report index d71b76a38..e8540697a 100755 --- a/qvm-tools/qubes-hcl-report +++ b/qvm-tools/qubes-hcl-report @@ -103,6 +103,11 @@ cat /etc/qubes-release > "$TEMP_DIR/qubes-release" || exit cat /proc/cpuinfo > "$TEMP_DIR/cpuinfo" || exit sudo lspci -nnvk > "$TEMP_DIR/lspci" cat /proc/scsi/scsi > "$TEMP_DIR/scsi" +for drive in /sys/class/nvme-subsystem/nvme-subsys*/; do + test -d "$drive" || continue + cat "$drive"/{serial,model,firmware_rev} | + tr "\n" " " | sed "s/\s$//" > "$TEMP_DIR/nvme" +done sudo dmidecode > "$TEMP_DIR/dmidecode" xl info > "$TEMP_DIR/xl-info" || exit xl dmesg > "$TEMP_DIR/xl-dmesg" || exit @@ -204,6 +209,11 @@ CHIPSET=$(grep "00:00.0.*Host bridge" "$TEMP_DIR/lspci" |cut -d ':' -f3- |sed -e VGA=$(grep -E 'VGA|Display' "$TEMP_DIR/lspci" |cut -d ':' -f3- |sed -e "s/^[[:space:]]*/\ /") NET=$(grep -E 'Network|Ethernet' "$TEMP_DIR/lspci" |cut -d ':' -f3- |sed -e "s/^[[:space:]]*/\ /") SCSI=$(grep Model "$TEMP_DIR/scsi"|cut -d ':' -f3-|sed -e "s/^[[:space:]]*/\ /") +if test -s "$TEMP_DIR/nvme"; then + NVME=$(sed -e "s/^[[:space:]]*/ /" "$TEMP_DIR/nvme") +else + NVME="" +fi RAM=$(grep total_memory "$TEMP_DIR/xl-info"|cut -d ':' -f2 |tr -d ' ') USB=$(grep -c USB "$TEMP_DIR/lspci") XEN_MAJOR=$(grep xen_major "$TEMP_DIR/xl-info"|cut -d: -f2 |tr -d ' ') @@ -378,6 +388,8 @@ memory: | $RAM scsi: | $SCSI +nvme: | +$NVME usb: | $USB certified: diff --git a/tests-data/dispvm_perf/2025121204-4.3.json b/tests-data/dispvm_perf/2025121204-4.3.json new file mode 100644 index 000000000..0fb6568a9 --- /dev/null +++ b/tests-data/dispvm_perf/2025121204-4.3.json @@ -0,0 +1,1638 @@ +{ + "debian-13-xfce_dom0-dispvm-api": { + "name": "dom0-dispvm-api", + "gui": false, + "concurrent": false, + "from_dom0": true, + "preload_max": 0, + "target_dispvm": true, + "admin_api": true, + "extra_id": "", + "pretty_name": "Dom0 runs simple app in a disposable (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.016, + "disp": 0.68, + "start": 5.745, + "exec": 0.318, + "clean": 1.204, + "total": 7.963 + }, + "2": { + "dom": 0.02, + "disp": 0.645, + "start": 6.093, + "exec": 0.274, + "clean": 0.958, + "total": 7.989 + }, + "3": { + "dom": 0.013, + "disp": 0.863, + "start": 5.836, + "exec": 0.349, + "clean": 0.961, + "total": 8.022 + }, + "4": { + "dom": 0.02, + "disp": 0.58, + "start": 4.967, + "exec": 0.378, + "clean": 0.934, + "total": 6.879 + }, + "5": { + "dom": 0.019, + "disp": 0.764, + "start": 4.981, + "exec": 0.475, + "clean": 1.207, + "total": 7.446 + }, + "6": { + "dom": 0.016, + "disp": 0.463, + "start": 5.329, + "exec": 0.312, + "clean": 0.956, + "total": 7.077 + }, + "7": { + "dom": 0.015, + "disp": 0.523, + "start": 6.468, + "exec": 0.266, + "clean": 1.067, + "total": 8.338 + }, + "8": { + "dom": 0.019, + "disp": 0.485, + "start": 4.68, + "exec": 0.455, + "clean": 0.856, + "total": 6.496 + }, + "9": { + "dom": 0.016, + "disp": 0.438, + "start": 5.182, + "exec": 0.319, + "clean": 1.096, + "total": 7.051 + }, + "10": { + "dom": 0.014, + "disp": 0.644, + "start": 5.536, + "exec": 0.309, + "clean": 0.952, + "total": 7.454 + }, + "11": { + "dom": 0.018, + "disp": 0.489, + "start": 5.324, + "exec": 0.487, + "clean": 0.99, + "total": 7.309 + }, + "12": { + "dom": 0.025, + "disp": 0.622, + "start": 5.592, + "exec": 0.301, + "clean": 0.916, + "total": 7.457 + } + }, + "stage": { + "dom": { + "values": [ + 0.016, + 0.02, + 0.013, + 0.02, + 0.019, + 0.016, + 0.015, + 0.019, + 0.016, + 0.014, + 0.018, + 0.025 + ], + "mean": 0.018, + "median": 0.017 + }, + "disp": { + "values": [ + 0.68, + 0.645, + 0.863, + 0.58, + 0.764, + 0.463, + 0.523, + 0.485, + 0.438, + 0.644, + 0.489, + 0.622 + ], + "mean": 0.6, + "median": 0.601 + }, + "start": { + "values": [ + 5.745, + 6.093, + 5.836, + 4.967, + 4.981, + 5.329, + 6.468, + 4.68, + 5.182, + 5.536, + 5.324, + 5.592 + ], + "mean": 5.478, + "median": 5.432 + }, + "exec": { + "values": [ + 0.318, + 0.274, + 0.349, + 0.378, + 0.475, + 0.312, + 0.266, + 0.455, + 0.319, + 0.309, + 0.487, + 0.301 + ], + "mean": 0.354, + "median": 0.319 + }, + "clean": { + "values": [ + 1.204, + 0.958, + 0.961, + 0.934, + 1.207, + 0.956, + 1.067, + 0.856, + 1.096, + 0.952, + 0.99, + 0.916 + ], + "mean": 1.008, + "median": 0.96 + }, + "total": { + "values": [ + 7.963, + 7.989, + 8.022, + 6.879, + 7.446, + 7.077, + 8.338, + 6.496, + 7.051, + 7.454, + 7.309, + 7.457 + ], + "mean": 7.457, + "median": 7.45 + } + } + }, + "iterations": 12, + "mean": 7.457, + "total": 89.481, + "date": "2025-12-12T12:00:33", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-dispvm-gui-api": { + "name": "dom0-dispvm-gui-api", + "gui": true, + "concurrent": false, + "from_dom0": true, + "preload_max": 0, + "target_dispvm": true, + "admin_api": true, + "extra_id": "", + "pretty_name": "Dom0 runs GUI app in a disposable (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.016, + "disp": 0.581, + "start": 4.878, + "exec": 2.447, + "clean": 1.021, + "total": 8.942 + }, + "2": { + "dom": 0.014, + "disp": 0.675, + "start": 5.334, + "exec": 2.023, + "clean": 1.08, + "total": 9.126 + }, + "3": { + "dom": 0.017, + "disp": 0.578, + "start": 5.065, + "exec": 1.629, + "clean": 1.077, + "total": 8.365 + }, + "4": { + "dom": 0.015, + "disp": 0.467, + "start": 4.796, + "exec": 1.649, + "clean": 1.182, + "total": 8.109 + }, + "5": { + "dom": 0.02, + "disp": 0.627, + "start": 5.964, + "exec": 1.734, + "clean": 1.0, + "total": 9.346 + }, + "6": { + "dom": 0.017, + "disp": 0.516, + "start": 5.865, + "exec": 1.562, + "clean": 1.132, + "total": 9.092 + }, + "7": { + "dom": 0.015, + "disp": 0.462, + "start": 5.304, + "exec": 1.927, + "clean": 0.999, + "total": 8.706 + }, + "8": { + "dom": 0.018, + "disp": 0.608, + "start": 5.74, + "exec": 1.616, + "clean": 0.947, + "total": 8.93 + }, + "9": { + "dom": 0.014, + "disp": 0.518, + "start": 6.002, + "exec": 1.743, + "clean": 1.028, + "total": 9.306 + }, + "10": { + "dom": 0.021, + "disp": 0.839, + "start": 5.442, + "exec": 1.851, + "clean": 1.009, + "total": 9.162 + }, + "11": { + "dom": 0.013, + "disp": 0.576, + "start": 5.204, + "exec": 1.762, + "clean": 1.018, + "total": 8.575 + }, + "12": { + "dom": 0.016, + "disp": 0.511, + "start": 5.189, + "exec": 1.597, + "clean": 0.886, + "total": 8.2 + } + }, + "stage": { + "dom": { + "values": [ + 0.016, + 0.014, + 0.017, + 0.015, + 0.02, + 0.017, + 0.015, + 0.018, + 0.014, + 0.021, + 0.013, + 0.016 + ], + "mean": 0.016, + "median": 0.016 + }, + "disp": { + "values": [ + 0.581, + 0.675, + 0.578, + 0.467, + 0.627, + 0.516, + 0.462, + 0.608, + 0.518, + 0.839, + 0.576, + 0.511 + ], + "mean": 0.58, + "median": 0.577 + }, + "start": { + "values": [ + 4.878, + 5.334, + 5.065, + 4.796, + 5.964, + 5.865, + 5.304, + 5.74, + 6.002, + 5.442, + 5.204, + 5.189 + ], + "mean": 5.399, + "median": 5.319 + }, + "exec": { + "values": [ + 2.447, + 2.023, + 1.629, + 1.649, + 1.734, + 1.562, + 1.927, + 1.616, + 1.743, + 1.851, + 1.762, + 1.597 + ], + "mean": 1.795, + "median": 1.739 + }, + "clean": { + "values": [ + 1.021, + 1.08, + 1.077, + 1.182, + 1.0, + 1.132, + 0.999, + 0.947, + 1.028, + 1.009, + 1.018, + 0.886 + ], + "mean": 1.032, + "median": 1.019 + }, + "total": { + "values": [ + 8.942, + 9.126, + 8.365, + 8.109, + 9.346, + 9.092, + 8.706, + 8.93, + 9.306, + 9.162, + 8.575, + 8.2 + ], + "mean": 8.822, + "median": 8.936 + } + } + }, + "iterations": 12, + "mean": 8.822, + "total": 105.86, + "date": "2025-12-12T12:03:00", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-dispvm-preload-2-api": { + "name": "dom0-dispvm-preload-2-api", + "gui": false, + "concurrent": false, + "from_dom0": true, + "preload_max": 2, + "target_dispvm": true, + "admin_api": true, + "extra_id": "dom0-dispvm-preload-api", + "pretty_name": "Dom0 runs simple app in disposables (2 preloaded) (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.018, + "disp": 0.184, + "exec": 0.19, + "clean": 2.075, + "total": 2.466 + }, + "2": { + "dom": 0.018, + "disp": 0.184, + "exec": 0.102, + "clean": 2.209, + "total": 2.513 + }, + "3": { + "dom": 0.022, + "disp": 1.585, + "exec": 0.14, + "clean": 2.227, + "total": 3.973 + }, + "4": { + "dom": 0.045, + "disp": 0.279, + "exec": 0.126, + "clean": 2.252, + "total": 2.702 + }, + "5": { + "dom": 0.062, + "disp": 1.432, + "exec": 0.125, + "clean": 2.158, + "total": 3.776 + }, + "6": { + "dom": 0.021, + "disp": 0.609, + "exec": 0.139, + "clean": 2.483, + "total": 3.252 + }, + "7": { + "dom": 0.066, + "disp": 1.015, + "exec": 0.165, + "clean": 2.324, + "total": 3.57 + }, + "8": { + "dom": 0.066, + "disp": 1.084, + "exec": 0.149, + "clean": 2.301, + "total": 3.599 + }, + "9": { + "dom": 0.063, + "disp": 1.127, + "exec": 0.133, + "clean": 2.3, + "total": 3.624 + }, + "10": { + "dom": 0.069, + "disp": 0.583, + "exec": 0.117, + "clean": 2.379, + "total": 3.149 + }, + "11": { + "dom": 0.069, + "disp": 0.956, + "exec": 0.127, + "clean": 2.26, + "total": 3.411 + }, + "12": { + "dom": 0.063, + "disp": 1.068, + "exec": 0.182, + "clean": 2.083, + "total": 3.396 + } + }, + "stage": { + "dom": { + "values": [ + 0.018, + 0.018, + 0.022, + 0.045, + 0.062, + 0.021, + 0.066, + 0.066, + 0.063, + 0.069, + 0.069, + 0.063 + ], + "mean": 0.049, + "median": 0.062 + }, + "disp": { + "values": [ + 0.184, + 0.184, + 1.585, + 0.279, + 1.432, + 0.609, + 1.015, + 1.084, + 1.127, + 0.583, + 0.956, + 1.068 + ], + "mean": 0.842, + "median": 0.985 + }, + "exec": { + "values": [ + 0.19, + 0.102, + 0.14, + 0.126, + 0.125, + 0.139, + 0.165, + 0.149, + 0.133, + 0.117, + 0.127, + 0.182 + ], + "mean": 0.141, + "median": 0.136 + }, + "clean": { + "values": [ + 2.075, + 2.209, + 2.227, + 2.252, + 2.158, + 2.483, + 2.324, + 2.301, + 2.3, + 2.379, + 2.26, + 2.083 + ], + "mean": 2.254, + "median": 2.256 + }, + "total": { + "values": [ + 2.466, + 2.513, + 3.973, + 2.702, + 3.776, + 3.252, + 3.57, + 3.599, + 3.624, + 3.149, + 3.411, + 3.396 + ], + "mean": 3.286, + "median": 3.404 + } + } + }, + "iterations": 12, + "mean": 3.286, + "total": 39.433, + "date": "2025-12-12T12:04:34", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-dispvm-preload-4-api": { + "name": "dom0-dispvm-preload-4-api", + "gui": false, + "concurrent": false, + "from_dom0": true, + "preload_max": 4, + "target_dispvm": true, + "admin_api": true, + "extra_id": "", + "pretty_name": "Dom0 runs simple app in disposables (4 preloaded) (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.023, + "disp": 0.175, + "exec": 0.088, + "clean": 2.186, + "total": 2.472 + }, + "2": { + "dom": 0.053, + "disp": 0.144, + "exec": 0.108, + "clean": 2.261, + "total": 2.566 + }, + "3": { + "dom": 0.02, + "disp": 0.255, + "exec": 0.16, + "clean": 2.469, + "total": 2.904 + }, + "4": { + "dom": 0.069, + "disp": 0.162, + "exec": 0.345, + "clean": 2.401, + "total": 2.978 + }, + "5": { + "dom": 0.03, + "disp": 0.177, + "exec": 0.096, + "clean": 1.392, + "total": 1.695 + }, + "6": { + "dom": 0.019, + "disp": 1.558, + "exec": 0.229, + "clean": 2.87, + "total": 4.676 + }, + "7": { + "dom": 0.018, + "disp": 0.155, + "exec": 0.122, + "clean": 3.437, + "total": 3.731 + }, + "8": { + "dom": 0.046, + "disp": 0.162, + "exec": 0.145, + "clean": 2.389, + "total": 2.742 + }, + "9": { + "dom": 0.019, + "disp": 0.244, + "exec": 0.189, + "clean": 1.458, + "total": 1.909 + }, + "10": { + "dom": 1.213, + "disp": 0.146, + "exec": 0.267, + "clean": 1.261, + "total": 2.887 + }, + "11": { + "dom": 0.02, + "disp": 1.537, + "exec": 0.169, + "clean": 2.492, + "total": 4.218 + }, + "12": { + "dom": 0.022, + "disp": 0.15, + "exec": 0.088, + "clean": 3.632, + "total": 3.893 + } + }, + "stage": { + "dom": { + "values": [ + 0.023, + 0.053, + 0.02, + 0.069, + 0.03, + 0.019, + 0.018, + 0.046, + 0.019, + 1.213, + 0.02, + 0.022 + ], + "mean": 0.129, + "median": 0.022 + }, + "disp": { + "values": [ + 0.175, + 0.144, + 0.255, + 0.162, + 0.177, + 1.558, + 0.155, + 0.162, + 0.244, + 0.146, + 1.537, + 0.15 + ], + "mean": 0.405, + "median": 0.168 + }, + "exec": { + "values": [ + 0.088, + 0.108, + 0.16, + 0.345, + 0.096, + 0.229, + 0.122, + 0.145, + 0.189, + 0.267, + 0.169, + 0.088 + ], + "mean": 0.167, + "median": 0.152 + }, + "clean": { + "values": [ + 2.186, + 2.261, + 2.469, + 2.401, + 1.392, + 2.87, + 3.437, + 2.389, + 1.458, + 1.261, + 2.492, + 3.632 + ], + "mean": 2.354, + "median": 2.395 + }, + "total": { + "values": [ + 2.472, + 2.566, + 2.904, + 2.978, + 1.695, + 4.676, + 3.731, + 2.742, + 1.909, + 2.887, + 4.218, + 3.893 + ], + "mean": 3.056, + "median": 2.896 + } + } + }, + "iterations": 12, + "mean": 3.056, + "total": 36.671, + "date": "2025-12-12T12:06:10", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-dispvm-preload-2-gui-api": { + "name": "dom0-dispvm-preload-2-gui-api", + "gui": true, + "concurrent": false, + "from_dom0": true, + "preload_max": 2, + "target_dispvm": true, + "admin_api": true, + "extra_id": "dom0-dispvm-preload-gui-api", + "pretty_name": "Dom0 runs GUI app in disposables (2 preloaded) (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.015, + "disp": 0.152, + "exec": 1.765, + "clean": 1.494, + "total": 3.426 + }, + "2": { + "dom": 0.015, + "disp": 0.141, + "exec": 1.714, + "clean": 1.554, + "total": 3.424 + }, + "3": { + "dom": 0.029, + "disp": 0.241, + "exec": 1.716, + "clean": 1.832, + "total": 3.819 + }, + "4": { + "dom": 0.025, + "disp": 0.17, + "exec": 1.618, + "clean": 1.866, + "total": 3.679 + }, + "5": { + "dom": 0.032, + "disp": 0.198, + "exec": 1.845, + "clean": 1.63, + "total": 3.705 + }, + "6": { + "dom": 0.016, + "disp": 0.14, + "exec": 2.233, + "clean": 1.288, + "total": 3.677 + }, + "7": { + "dom": 0.016, + "disp": 0.144, + "exec": 1.777, + "clean": 1.891, + "total": 3.828 + }, + "8": { + "dom": 0.019, + "disp": 0.143, + "exec": 1.927, + "clean": 1.654, + "total": 3.743 + }, + "9": { + "dom": 0.043, + "disp": 0.22, + "exec": 1.894, + "clean": 1.681, + "total": 3.838 + }, + "10": { + "dom": 0.016, + "disp": 0.183, + "exec": 1.865, + "clean": 1.485, + "total": 3.549 + }, + "11": { + "dom": 0.02, + "disp": 0.137, + "exec": 1.698, + "clean": 2.102, + "total": 3.957 + }, + "12": { + "dom": 0.027, + "disp": 0.178, + "exec": 1.978, + "clean": 1.7, + "total": 3.883 + } + }, + "stage": { + "dom": { + "values": [ + 0.015, + 0.015, + 0.029, + 0.025, + 0.032, + 0.016, + 0.016, + 0.019, + 0.043, + 0.016, + 0.02, + 0.027 + ], + "mean": 0.023, + "median": 0.019 + }, + "disp": { + "values": [ + 0.152, + 0.141, + 0.241, + 0.17, + 0.198, + 0.14, + 0.144, + 0.143, + 0.22, + 0.183, + 0.137, + 0.178 + ], + "mean": 0.171, + "median": 0.161 + }, + "exec": { + "values": [ + 1.765, + 1.714, + 1.716, + 1.618, + 1.845, + 2.233, + 1.777, + 1.927, + 1.894, + 1.865, + 1.698, + 1.978 + ], + "mean": 1.836, + "median": 1.811 + }, + "clean": { + "values": [ + 1.494, + 1.554, + 1.832, + 1.866, + 1.63, + 1.288, + 1.891, + 1.654, + 1.681, + 1.485, + 2.102, + 1.7 + ], + "mean": 1.681, + "median": 1.667 + }, + "total": { + "values": [ + 3.426, + 3.424, + 3.819, + 3.679, + 3.705, + 3.677, + 3.828, + 3.743, + 3.838, + 3.549, + 3.957, + 3.883 + ], + "mean": 3.711, + "median": 3.724 + } + } + }, + "iterations": 12, + "mean": 3.711, + "total": 44.529, + "date": "2025-12-12T12:07:52", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-dispvm-preload-4-gui-api": { + "name": "dom0-dispvm-preload-4-gui-api", + "gui": true, + "concurrent": false, + "from_dom0": true, + "preload_max": 4, + "target_dispvm": true, + "admin_api": true, + "extra_id": "", + "pretty_name": "Dom0 runs GUI app in disposables (4 preloaded) (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.017, + "disp": 0.183, + "exec": 1.741, + "clean": 1.462, + "total": 3.403 + }, + "2": { + "dom": 0.018, + "disp": 0.192, + "exec": 1.94, + "clean": 1.57, + "total": 3.72 + }, + "3": { + "dom": 0.024, + "disp": 0.209, + "exec": 2.207, + "clean": 1.472, + "total": 3.912 + }, + "4": { + "dom": 0.037, + "disp": 0.171, + "exec": 2.418, + "clean": 1.129, + "total": 3.755 + }, + "5": { + "dom": 0.019, + "disp": 0.196, + "exec": 1.932, + "clean": 2.053, + "total": 4.2 + }, + "6": { + "dom": 0.022, + "disp": 0.764, + "exec": 1.849, + "clean": 1.806, + "total": 4.442 + }, + "7": { + "dom": 0.028, + "disp": 0.153, + "exec": 1.73, + "clean": 1.83, + "total": 3.741 + }, + "8": { + "dom": 0.02, + "disp": 0.136, + "exec": 1.71, + "clean": 1.831, + "total": 3.697 + }, + "9": { + "dom": 0.022, + "disp": 0.163, + "exec": 2.422, + "clean": 1.134, + "total": 3.741 + }, + "10": { + "dom": 0.02, + "disp": 0.152, + "exec": 1.992, + "clean": 1.62, + "total": 3.784 + }, + "11": { + "dom": 0.024, + "disp": 0.173, + "exec": 1.817, + "clean": 1.715, + "total": 3.729 + }, + "12": { + "dom": 0.022, + "disp": 0.138, + "exec": 2.053, + "clean": 1.523, + "total": 3.736 + } + }, + "stage": { + "dom": { + "values": [ + 0.017, + 0.018, + 0.024, + 0.037, + 0.019, + 0.022, + 0.028, + 0.02, + 0.022, + 0.02, + 0.024, + 0.022 + ], + "mean": 0.023, + "median": 0.022 + }, + "disp": { + "values": [ + 0.183, + 0.192, + 0.209, + 0.171, + 0.196, + 0.764, + 0.153, + 0.136, + 0.163, + 0.152, + 0.173, + 0.138 + ], + "mean": 0.219, + "median": 0.172 + }, + "exec": { + "values": [ + 1.741, + 1.94, + 2.207, + 2.418, + 1.932, + 1.849, + 1.73, + 1.71, + 2.422, + 1.992, + 1.817, + 2.053 + ], + "mean": 1.984, + "median": 1.936 + }, + "clean": { + "values": [ + 1.462, + 1.57, + 1.472, + 1.129, + 2.053, + 1.806, + 1.83, + 1.831, + 1.134, + 1.62, + 1.715, + 1.523 + ], + "mean": 1.595, + "median": 1.595 + }, + "total": { + "values": [ + 3.403, + 3.72, + 3.912, + 3.755, + 4.2, + 4.442, + 3.741, + 3.697, + 3.741, + 3.784, + 3.729, + 3.736 + ], + "mean": 3.822, + "median": 3.741 + } + } + }, + "iterations": 12, + "mean": 3.822, + "total": 45.86, + "date": "2025-12-12T12:09:40", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-vm-api": { + "name": "dom0-vm-api", + "gui": false, + "concurrent": false, + "from_dom0": true, + "preload_max": 0, + "target_dispvm": false, + "admin_api": true, + "extra_id": "", + "pretty_name": "Dom0 runs simple app in another running qube (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.024, + "exec": 0.019, + "total": 0.042 + }, + "2": { + "dom": 0.017, + "exec": 0.017, + "total": 0.035 + }, + "3": { + "dom": 0.016, + "exec": 0.015, + "total": 0.031 + }, + "4": { + "dom": 0.017, + "exec": 0.019, + "total": 0.036 + }, + "5": { + "dom": 0.015, + "exec": 0.022, + "total": 0.037 + }, + "6": { + "dom": 0.014, + "exec": 0.022, + "total": 0.037 + }, + "7": { + "dom": 0.015, + "exec": 0.024, + "total": 0.039 + }, + "8": { + "dom": 0.021, + "exec": 0.031, + "total": 0.051 + }, + "9": { + "dom": 0.018, + "exec": 0.019, + "total": 0.038 + }, + "10": { + "dom": 0.015, + "exec": 0.022, + "total": 0.037 + }, + "11": { + "dom": 0.018, + "exec": 0.028, + "total": 0.046 + }, + "12": { + "dom": 0.023, + "exec": 0.028, + "total": 0.051 + } + }, + "stage": { + "dom": { + "values": [ + 0.024, + 0.017, + 0.016, + 0.017, + 0.015, + 0.014, + 0.015, + 0.021, + 0.018, + 0.015, + 0.018, + 0.023 + ], + "mean": 0.018, + "median": 0.017 + }, + "exec": { + "values": [ + 0.019, + 0.017, + 0.015, + 0.019, + 0.022, + 0.022, + 0.024, + 0.031, + 0.019, + 0.022, + 0.028, + 0.028 + ], + "mean": 0.022, + "median": 0.022 + }, + "total": { + "values": [ + 0.042, + 0.035, + 0.031, + 0.036, + 0.037, + 0.037, + 0.039, + 0.051, + 0.038, + 0.037, + 0.046, + 0.051 + ], + "mean": 0.04, + "median": 0.037 + } + } + }, + "iterations": 12, + "mean": 0.04, + "total": 0.481, + "date": "2025-12-12T12:10:32", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + }, + "debian-13-xfce_dom0-vm-gui-api": { + "name": "dom0-vm-gui-api", + "gui": true, + "concurrent": false, + "from_dom0": true, + "preload_max": 0, + "target_dispvm": false, + "admin_api": true, + "extra_id": "", + "pretty_name": "Dom0 runs GUI app in another running qube (API)", + "template": "debian-13-xfce", + "api_results": { + "iteration": { + "1": { + "dom": 0.018, + "exec": 0.032, + "total": 0.049 + }, + "2": { + "dom": 0.016, + "exec": 0.022, + "total": 0.038 + }, + "3": { + "dom": 0.016, + "exec": 0.022, + "total": 0.038 + }, + "4": { + "dom": 0.025, + "exec": 0.026, + "total": 0.05 + }, + "5": { + "dom": 0.022, + "exec": 0.024, + "total": 0.046 + }, + "6": { + "dom": 0.018, + "exec": 0.021, + "total": 0.039 + }, + "7": { + "dom": 0.022, + "exec": 0.023, + "total": 0.045 + }, + "8": { + "dom": 0.018, + "exec": 0.024, + "total": 0.042 + }, + "9": { + "dom": 0.017, + "exec": 0.02, + "total": 0.037 + }, + "10": { + "dom": 0.017, + "exec": 0.023, + "total": 0.04 + }, + "11": { + "dom": 0.027, + "exec": 0.05, + "total": 0.077 + }, + "12": { + "dom": 0.016, + "exec": 0.021, + "total": 0.036 + } + }, + "stage": { + "dom": { + "values": [ + 0.018, + 0.016, + 0.016, + 0.025, + 0.022, + 0.018, + 0.022, + 0.018, + 0.017, + 0.017, + 0.027, + 0.016 + ], + "mean": 0.019, + "median": 0.018 + }, + "exec": { + "values": [ + 0.032, + 0.022, + 0.022, + 0.026, + 0.024, + 0.021, + 0.023, + 0.024, + 0.02, + 0.023, + 0.05, + 0.021 + ], + "mean": 0.026, + "median": 0.023 + }, + "total": { + "values": [ + 0.049, + 0.038, + 0.038, + 0.05, + 0.046, + 0.039, + 0.045, + 0.042, + 0.037, + 0.04, + 0.077, + 0.036 + ], + "mean": 0.045, + "median": 0.041 + } + } + }, + "iterations": 12, + "mean": 0.045, + "total": 0.538, + "date": "2025-12-12T12:11:09", + "memory": 400, + "maxmem": 4000, + "vcpus": 2, + "qrexec_timeout": 60, + "shutdown_timeout": 60, + "kernel": "6.17.9-1.fc41", + "kernelopts": "swiotlb=2048", + "template-buildtime": "2025-09-19 11:46:06", + "last-update": "2025-09-22 11:32:55", + "os": "Linux", + "os-distribution": "debian", + "os-version": 13, + "hcl-qubes": "R4.3-rc3", + "hcl-xen": "4.19.3", + "hcl-kernel": "6.17.9-1", + "hcl-memory": 32404 + } +} \ No newline at end of file diff --git a/tests/dispvm_perf.py b/tests/dispvm_perf.py index 5b5d09c44..f65ec20c6 100755 --- a/tests/dispvm_perf.py +++ b/tests/dispvm_perf.py @@ -91,7 +91,7 @@ class TestConfig: :param bool concurrent: request concurrently :param bool from_dom0: initiate call from dom0 :param int preload_max: number of disposables to preload - :param bool non_dispvm: target a non disposable qube + :param bool target_dispvm: target or not a disposable qube :param bool admin_api: use the Admin API directly :param str extra_id: base test that extra ID varies from :param str pretty_name: human-readable name @@ -143,7 +143,7 @@ class TestConfig: concurrent: bool = False from_dom0: bool = False preload_max: int = 0 - non_dispvm: bool = False + target_dispvm: bool = True admin_api: bool = False extra_id: str = "" pretty_name: str = dataclasses.field(init=False) @@ -171,9 +171,7 @@ def __post_init__(self): else: pretty_what = "simple" - if self.non_dispvm: - pretty_to = "in another running qube" - else: + if self.target_dispvm: disp_suffix = "" disp_prefix = "a " if self.preload_max: @@ -189,6 +187,8 @@ def __post_init__(self): disp_prefix = "" disp_suffix = "s" pretty_to = "in {}disposable{}".format(disp_prefix, disp_suffix) + else: + pretty_to = "in another running qube" pretty_name = "{} runs".format(pretty_from.capitalize()) if pretty_strategy: @@ -207,7 +207,7 @@ def __post_init__(self): POLICY_FILE = "/run/qubes/policy.d/10-test-dispvm-perf.policy" # MAX_PRELOAD is the number doesn't overpreload or underpreload (best # performance) on sequential calls between the tests -# "dispvm-preload(-(more|less))-api" (tested on fedora-42-xfce). Machines with +# "dispvm-preload(-NUMBER)-api" (tested on fedora-42-xfce). Machines with # different hardware or domains that boot faster or slower can theoretically # have a different best value. MAX_PRELOAD = 4 @@ -223,24 +223,26 @@ def __post_init__(self): ROUND_PRECISION = 3 ALL_TESTS = [ - TestConfig("vm-vm", non_dispvm=True), - TestConfig("vm-vm-gui", gui=True, non_dispvm=True), - TestConfig("vm-vm-concurrent", concurrent=True, non_dispvm=True), + TestConfig("vm-vm", target_dispvm=False), + TestConfig("vm-vm-gui", gui=True, target_dispvm=False), + TestConfig("vm-vm-concurrent", concurrent=True, target_dispvm=False), + TestConfig( + "vm-vm-gui-concurrent", gui=True, concurrent=True, target_dispvm=False + ), TestConfig( - "vm-vm-gui-concurrent", gui=True, concurrent=True, non_dispvm=True + "dom0-vm-api", target_dispvm=False, admin_api=True, from_dom0=True ), - TestConfig("dom0-vm-api", non_dispvm=True, admin_api=True, from_dom0=True), TestConfig( "dom0-vm-gui-api", gui=True, - non_dispvm=True, + target_dispvm=False, admin_api=True, from_dom0=True, ), TestConfig( "dom0-vm-concurrent-api", concurrent=True, - non_dispvm=True, + target_dispvm=False, admin_api=True, from_dom0=True, ), @@ -248,7 +250,7 @@ def __post_init__(self): "dom0-vm-gui-concurrent-api", gui=True, concurrent=True, - non_dispvm=True, + target_dispvm=False, admin_api=True, from_dom0=True, ), @@ -256,12 +258,6 @@ def __post_init__(self): TestConfig("vm-dispvm-gui", gui=True), TestConfig("vm-dispvm-concurrent", concurrent=True), TestConfig("vm-dispvm-gui-concurrent", gui=True, concurrent=True), - TestConfig("dom0-dispvm", from_dom0=True), - TestConfig("dom0-dispvm-gui", gui=True, from_dom0=True), - TestConfig("dom0-dispvm-concurrent", concurrent=True, from_dom0=True), - TestConfig( - "dom0-dispvm-gui-concurrent", gui=True, concurrent=True, from_dom0=True - ), TestConfig("vm-dispvm-preload", preload_max=MAX_PRELOAD), TestConfig("vm-dispvm-preload-gui", gui=True, preload_max=MAX_PRELOAD), TestConfig( @@ -275,6 +271,12 @@ def __post_init__(self): concurrent=True, preload_max=MAX_CONCURRENCY, ), + TestConfig("dom0-dispvm", from_dom0=True), + TestConfig("dom0-dispvm-gui", gui=True, from_dom0=True), + TestConfig("dom0-dispvm-concurrent", concurrent=True, from_dom0=True), + TestConfig( + "dom0-dispvm-gui-concurrent", gui=True, concurrent=True, from_dom0=True + ), TestConfig("dom0-dispvm-preload", from_dom0=True, preload_max=MAX_PRELOAD), TestConfig( "dom0-dispvm-preload-gui", @@ -311,50 +313,97 @@ def __post_init__(self): from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-less-less-api", - preload_max=MAX_PRELOAD - 2, + "dom0-dispvm-preload-1-api", + preload_max=1, + admin_api=True, + extra_id="dom0-dispvm-preload-api", + from_dom0=True, + ), + TestConfig( + "dom0-dispvm-preload-1-gui-api", + preload_max=1, + gui=True, admin_api=True, - extra_id="dispvm-preload-api", + extra_id="dom0-dispvm-preload-gui-api", from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-less-api", - preload_max=MAX_PRELOAD - 1, + "dom0-dispvm-preload-2-api", + preload_max=2, admin_api=True, - extra_id="dispvm-preload-api", + extra_id="dom0-dispvm-preload-api", from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-api", - preload_max=MAX_PRELOAD, + "dom0-dispvm-preload-2-gui-api", + preload_max=2, + gui=True, admin_api=True, + extra_id="dom0-dispvm-preload-gui-api", from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-concurrent-api", - concurrent=True, - preload_max=MAX_CONCURRENCY, + "dom0-dispvm-preload-3-api", + preload_max=3, admin_api=True, + extra_id="dom0-dispvm-preload-api", from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-more-api", - preload_max=MAX_PRELOAD + 1, + "dom0-dispvm-preload-3-gui-api", + preload_max=3, + gui=True, admin_api=True, - extra_id="dispvm-preload-api", + extra_id="dom0-dispvm-preload-gui-api", from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-more-more-api", - preload_max=MAX_PRELOAD + 2, + "dom0-dispvm-preload-4-api", + preload_max=4, admin_api=True, - extra_id="dispvm-preload-api", from_dom0=True, ), TestConfig( - "dom0-dispvm-preload-gui-api", + "dom0-dispvm-preload-4-gui-api", + preload_max=4, + gui=True, + admin_api=True, + from_dom0=True, + ), + TestConfig( + "dom0-dispvm-preload-5-api", + preload_max=5, + admin_api=True, + extra_id="dom0-dispvm-preload-api", + from_dom0=True, + ), + TestConfig( + "dom0-dispvm-preload-5-gui-api", + preload_max=5, + gui=True, + admin_api=True, + extra_id="dom0-dispvm-preload-gui-api", + from_dom0=True, + ), + TestConfig( + "dom0-dispvm-preload-6-api", + preload_max=6, + admin_api=True, + extra_id="dom0-dispvm-preload-api", + from_dom0=True, + ), + TestConfig( + "dom0-dispvm-preload-6-gui-api", + preload_max=6, gui=True, - preload_max=MAX_PRELOAD, + admin_api=True, + extra_id="dom0-dispvm-preload-gui-api", + from_dom0=True, + ), + TestConfig( + "dom0-dispvm-preload-concurrent-api", + concurrent=True, + preload_max=MAX_CONCURRENCY, admin_api=True, from_dom0=True, ), @@ -398,6 +447,8 @@ def hcl() -> dict: "hcl-model": report["model"].rstrip(), "hcl-bios": report["bios"].rstrip(), "hcl-cpu": report["cpu"].rstrip(), + "hcl-scsi": report["scsi"].rstrip(), + "hcl-nvme": report["nvme"].rstrip(), } ) return data @@ -493,10 +544,10 @@ def run_latency_calls(self, test): caller += f"--dispvm={self.dvm.name} " cmd = f"{caller} -- {service}" else: - if test.non_dispvm: - target = self.vm2.name - else: + if test.target_dispvm: target = "@dispvm" + else: + target = self.vm2.name cmd = f"qrexec-client-vm -- {target} {service}" code = ( @@ -547,35 +598,44 @@ def call_api(self, test, service, qube): start_time = get_time() app = qubesadmin.Qubes() domains = app.domains - if test.non_dispvm: + target_time = None + startup_time = None + if test.target_dispvm: + appvm = domains[qube] + domain_time = get_time() + target_wrapper = qubesadmin.vm.DispVM.from_appvm(app, appvm) + target_qube = target_wrapper.create_disposable() + target_time = get_time() + if not test.preload_max: + target_qube.start() + startup_time = get_time() + pre_exec_time = startup_time + else: + pre_exec_time = target_time + else: # Even though we already have the qube object passed from the # class, assume we don't so we can calculate gathering. target_qube = domains[self.vm1.name] domain_time = get_time() - else: - appvm = domains[qube] - domain_time = get_time() - target_qube = qubesadmin.vm.DispVM.from_appvm(app, appvm) - name = target_qube.name - # A very small number, if it appears, it will show a bottleneck at - # DispVM.from_appvm. - target_time = get_time() + pre_exec_time = domain_time try: target_qube.run_service_for_stdio(service, timeout=60) except subprocess.CalledProcessError as e: + name = target_qube.name raise Exception( f"'{name}': service '{service}' failed ({e.returncode}):" f" {e.stdout}," f" {e.stderr}" ) except subprocess.TimeoutExpired as e: + name = target_qube.name raise Exception( f"'{name}': service '{service}' failed: timeout expired:" f" {e.stdout}," f" {e.stderr}" ) run_service_time = get_time() - if not test.non_dispvm: + if test.target_dispvm: target_qube.cleanup() cleanup_time = get_time() end_time = cleanup_time @@ -583,10 +643,16 @@ def call_api(self, test, service, qube): end_time = get_time() runtime = {} runtime["dom"] = round(domain_time - start_time, ROUND_PRECISION) - if not test.non_dispvm: + if test.target_dispvm: runtime["disp"] = round(target_time - domain_time, ROUND_PRECISION) - runtime["exec"] = round(run_service_time - target_time, ROUND_PRECISION) - if not test.non_dispvm: + if not test.preload_max: + runtime["start"] = round( + startup_time - target_time, ROUND_PRECISION + ) + runtime["exec"] = round( + run_service_time - pre_exec_time, ROUND_PRECISION + ) + if test.target_dispvm: runtime["clean"] = round( cleanup_time - run_service_time, ROUND_PRECISION ) @@ -611,10 +677,10 @@ async def run_latency_api_calls(self, test): service = self.gui_service else: service = self.nogui_service - if test.non_dispvm: - qube = self.vm2 - else: + if test.target_dispvm: qube = self.dvm + else: + qube = self.vm2 results = {} results["api_results"] = {} @@ -771,10 +837,10 @@ async def run_test(self, test: TestConfig): with open(POLICY_FILE, "w", encoding="ascii") as policy: gui_prefix = f"{self.gui_service} * {self.vm1.name}" nogui_prefix = f"{self.nogui_service} * {self.vm1.name}" - if test.non_dispvm: - target = f"{self.vm2.name}" - else: + if test.target_dispvm: target = "@dispvm" + else: + target = f"{self.vm2.name}" policy.write( f"{gui_prefix} {target} allow\n" f"{nogui_prefix} {target} allow\n" @@ -848,7 +914,7 @@ async def run_test(self, test: TestConfig): if not os.getenv("QUBES_TEST_SKIP_TEARDOWN_SLEEP"): logger.info("Load before sleep: '%s'", get_load()) delay = 5 - if not test.non_dispvm: + if test.target_dispvm: delay += 10 if test.gui: delay += 2 diff --git a/tests/dispvm_perf_reader.py b/tests/dispvm_perf_reader.py index cab868139..c3db859af 100755 --- a/tests/dispvm_perf_reader.py +++ b/tests/dispvm_perf_reader.py @@ -235,12 +235,23 @@ def __init__( ) if not self.data: logging.critical( - "Default template specified not found: '%s'", + "Template specified not found: '%s'", self.default_template, ) sys.exit(1) else: logging.info("Default template not specified, using newest fedora") + fedora_templates = [ + v + for v in self.orig_data.values() + if v["os-distribution"] == "fedora" + ] + if not fedora_templates: + logging.critical( + "Tried to find any fedora template but ultimately failed", + self.default_template, + ) + sys.exit(1) fedora_template = max( ( v @@ -279,7 +290,7 @@ def __init__( self.dispvm_api_tests = filter_data( self.api_tests, { - "non_dispvm": False, + "target_dispvm": True, "admin_api": True, "concurrent": False, "extra_id": "", @@ -290,7 +301,7 @@ def __init__( self.dispvm_tests = filter_data( self.data, { - "non_dispvm": False, + "target_dispvm": True, "admin_api": False, "concurrent": False, "extra_id": "", @@ -311,15 +322,30 @@ def __init__( for name, test in self.dispvm_tests.items() ] + self.vm_stage_dict = { + "dom": { + "color": COLORS["Icon Dark Gray"], + "legend": "Initialization", + }, + "exec": {"color": COLORS["Warning Orange"], "legend": "Execution"}, + } + self.stage_dict = { - "dom": {"color": COLORS["Icon Dark Gray"], "legend": "Others"}, - "disp": {"color": COLORS["Middle Gray"], "legend": "Others"}, + "dom": { + "color": COLORS["Icon Dark Gray"], + "legend": "Initialization", + }, + "disp": {"color": COLORS["Purple"], "legend": "Creation"}, + "start": {"color": COLORS["Danger Red"], "legend": "Startup"}, "exec": {"color": COLORS["Warning Orange"], "legend": "Execution"}, "clean": {"color": COLORS["Sub Gray"], "legend": "Cleanup"}, } self.preload_stage_dict = { - "dom": {"color": COLORS["Icon Dark Gray"], "legend": "Others"}, - "disp": {"color": COLORS["Middle Gray"], "legend": "Others"}, + "dom": { + "color": COLORS["Icon Dark Gray"], + "legend": "Initialization", + }, + "disp": {"color": COLORS["Purple"], "legend": "Creation"}, "exec": {"color": COLORS["Primary Blue"], "legend": "Execution"}, "clean": {"color": COLORS["Sub Gray"], "legend": "Cleanup"}, } @@ -328,7 +354,7 @@ def __init__( for test, result in self.api_tests.items(): if result.get("api_results", {}).get("stage", {}): self.stages.extend(result["api_results"]["stage"].keys()) - self.stage_order = ["dom", "disp", "exec", "clean", "total"] + self.stage_order = [*self.stage_dict.keys(), "total"] self.stages = sorted( list(set(self.stages)), key=lambda s: ( @@ -343,7 +369,6 @@ def run(self) -> None: """Loop through all enabled graphs.""" avail_graphs = get_graphs() failed_graphs = [] - # TODO: almost every graph should target all templates for graph in avail_graphs: method = getattr(self, "graph_" + graph.replace("-", "_")) if not (not self.graphs or graph in self.graphs): @@ -372,7 +397,7 @@ def end(self, plot, name: str = "", skip_fname: bool = False) -> None: else: if not skip_fname: name = get_fname() - name = self.default_template + "_" + name + name = "dispvm_perf-" + self.default_template + "_" + name if self.output_dir: logging.info("Saving figure %s", name) @@ -404,7 +429,7 @@ def bar_plot( supxlabel: str = "", file_prefix: str = "", ) -> None: - # pylint: disable=too-many-locals + # pylint: disable=too-many-locals,too-many-positional-arguments """ Assemble figure of bar plot where normal and preload tests are grouped. Each test passed is subplotted. @@ -555,42 +580,53 @@ def graph_00_specs(self) -> None: """System specifications graph.""" first_test = list(self.data.keys())[0] data = self.data[first_test] - specs = { - "date": data["date"], - "template-buildtime": data["template-buildtime"], - "kernel": data["kernel"], - "hcl-memory": data["hcl-memory"], - "hcl-certified": data["hcl-certified"], - "hcl-qubes": data["hcl-qubes"], - "hcl-xen": data["hcl-xen"], - "hcl-model": data["hcl-model"], - "hcl-bios": data["hcl-bios"], - "hcl-cpu": data["hcl-cpu"], - } specs_text = """ System specifications: - - Date: {} - - Template: {} - - Template build time: {} - - Certified: {} - - Qubes: {} - - Kernel: {} - - Xen: {} - - RAM: {} MiB - - CPU: {} - - BIOS: {} + - Global: + - Date: {date} + - Qubes: {hcl_qubes} + - Xen: {hcl_xen} + - Global Kernel: {hcl_kernel} + - Template: + - Name: {template} + - Build time: {template_buildtime} + - Last update: {template_last_update} + - Virtual CPUs: {vcpus} + - Bootstrap memory: {memory} + - Maximum memory: {maxmem} + - Kernel: {kernel} + - Kernel options: {kernelopts} + - Hardware: + - Certified: {hcl_certified} + - Brand: {hcl_brand} + - Model: {hcl_model} + - CPU: {hcl_cpu} + - RAM: {hcl_memory} MiB + - BIOS: {hcl_bios} + - SCSI: {hcl_scsi} + - NVMe: {hcl_nvme} """.format( - specs["date"], - self.default_template, - specs["template-buildtime"], - specs["hcl-certified"], - specs["hcl-qubes"], - specs["kernel"], - specs["hcl-xen"], - specs["hcl-memory"], - specs["hcl-cpu"], - specs["hcl-bios"], + date=data["date"], + template=self.default_template, + template_buildtime=data["template-buildtime"], + template_last_update=data["last-update"] or None, + memory=data["memory"], + maxmem=data["maxmem"], + vcpus=data["vcpus"], + kernel=data["kernel"], + kernelopts=data["kernelopts"], + hcl_memory=data["hcl-memory"], + hcl_certified=data.get("hcl-certified"), + hcl_qubes=data["hcl-qubes"], + hcl_xen=data["hcl-xen"], + hcl_kernel=data["hcl-kernel"], + hcl_brand=data.get("hcl-brand"), + hcl_model=data.get("hcl-model"), + hcl_bios=data.get("hcl-bios"), + hcl_cpu=data.get("hcl-cpu"), + hcl_scsi=data.get("hcl-scsi"), + hcl_nvme=data.get("hcl-nvme"), ) fig = plt.figure(figsize=(2 * WIDTH, 2 * HEIGHT)) fig.clf() @@ -625,6 +661,7 @@ def graph_01_bar(self) -> None: def graph_02_stage_stack(self) -> None: """Stacked bar by stage graph.""" + # pylint: disable=too-many-locals tests = filter_data(self.dispvm_api_tests, {"preload_max": 0}) assert_items_match(tests, ["iterations"]) iterations = get_value(tests, "iterations")[0] @@ -635,15 +672,23 @@ def graph_02_stage_stack(self) -> None: {"preload_max": operator.ge}, ) - important_stages = ["exec", "clean", "total"] label_array = np.arange(len(tests_names_pretty)) label_count = len(tests_names_pretty) width = 0.8 / label_count + # Don't count "dom" stage, when there are other large values, it + # overlaps the label. up_to_exec = self.stages[:-2] - for num, stage_intro in enumerate([up_to_exec, self.stages]): + up_to_exec.remove("dom") + up_to_last = self.stages + up_to_last.remove("dom") + important_stages = up_to_last + # pylint: disable=too-many-nested-blocks + for num, stage_intro in enumerate([up_to_exec, up_to_last]): fig, axs = plt.subplots(figsize=(WIDTH * 3, HEIGHT * 3)) bottom = np.zeros(len(tests_names_pretty)) bottom_preload = np.zeros(len(tests_names_pretty)) + stored_means_stage = [] + stored_preload_means_stage = [] for stage in stage_intro: means_stage = np.array( [ @@ -651,18 +696,27 @@ def graph_02_stage_stack(self) -> None: for test in tests.values() ] ) - preload_means_stage = np.array( - [ - test["api_results"]["stage"][stage]["mean"] - for test in preload_tests.values() - ] - ) + stored_means_stage.append(means_stage) + if stage == "start": + preload_means_stage = np.zeros(len(means_stage)) + else: + preload_means_stage = np.array( + [ + test["api_results"]["stage"][stage]["mean"] + for test in preload_tests.values() + ] + ) + stored_preload_means_stage.append(preload_means_stage) assert_items_length_match(tests, preload_tests) if stage in ["exec", "total"]: + sum_means_stage = [sum(x) for x in zip(*stored_means_stage)] + sum_preload_means_stage = [ + sum(x) for x in zip(*stored_preload_means_stage) + ] ratio = np.divide( - means_stage, - preload_means_stage, + sum_means_stage, + sum_preload_means_stage, out=np.zeros_like(means_stage, dtype=float), where=preload_means_stage != 0, ) @@ -673,12 +727,21 @@ def graph_02_stage_stack(self) -> None: if stage == "exec" and "total" in stage_intro: break xpos = label_array[i] - min_mean = min(means_stage[i], preload_means_stage[i]) - max_mean = max(means_stage[i], preload_means_stage[i]) - height = max( - min_mean + 1, min_mean + ((max_mean - min_mean) / 2) - ) - height = min_mean + 1 + if stage == "total": + min_mean = min( + means_stage[i], preload_means_stage[i] + ) + max_mean = max( + means_stage[i], preload_means_stage[i] + ) + height = max( + min_mean + 1, + min_mean + ((max_mean - min_mean) / 2), + ) + else: + height = ( + sum_preload_means_stage[i] + sum_means_stage[i] + ) / 2 if ratio[i] > 1: color = COLORS["Success Green"] else: @@ -704,19 +767,21 @@ def graph_02_stage_stack(self) -> None: bottom += means_stage positions = label_array + width / 2 - bars_preload = axs.bar( - positions, - preload_means_stage, - width=width, - bottom=bottom_preload, - color=self.preload_stage_dict[stage]["color"], - ) + if stage != "start": + bars_preload = axs.bar( + positions, + preload_means_stage, + width=width, + bottom=bottom_preload, + color=self.preload_stage_dict[stage]["color"], + ) bottom_preload += preload_means_stage if ( stage in important_stages - or stage == list(self.stage_dict.keys())[-1] + or stage == list(self.stages)[-1] ): + # pylint: disable=possibly-used-before-assignment for bbar in [bars_normal, bars_preload]: axs.bar_label( bbar, @@ -724,7 +789,26 @@ def graph_02_stage_stack(self) -> None: label_type="center", fontsize="xx-large", ) - + if stage not in ["exec", "clean"]: + continue + if stage == "exec" and "total" in stage_intro: + continue + axs.bar_label(bbar, fmt="%.2f", fontsize="xx-large") + + means_stage_total = [ + test["api_results"]["stage"]["total"]["mean"] + for test in tests.values() + ] + preload_means_stage_total = [ + test["api_results"]["stage"]["total"]["mean"] + for test in preload_tests.values() + ] + top_value = math.ceil( + max(*means_stage_total, *preload_means_stage_total) + ) + top_tick = int(10 * top_value / 10) + yticks = [round(x, 1) for x in np.linspace(0, top_tick, 4)] + plt.yticks(yticks) plt.xticks(label_array, tests_names_pretty) plt.ylabel("Time (s)") pretty_iterations = " over {} iterations".format(iterations) @@ -737,7 +821,7 @@ def graph_02_stage_stack(self) -> None: if stage in stage_intro ] legend_handles.insert( - -2, + -3, *[ matplotlib.patches.Patch( color=value["color"], label="Preload " + value["legend"] @@ -760,20 +844,41 @@ def graph_02_stage_stack(self) -> None: wrap=True, color=CAPTION_COLOR, ) - self.end(plt, str(num)) + self.end(plt, str(num) + "_until_" + stage_intro[-1]) def graph_03_stage_dist(self) -> None: """Scattered and jittered stage distribution graph.""" - tests = self.dispvm_api_tests + tests = filter_data( + self.api_tests, + { + "concurrent": False, + "extra_id": "", + }, + ) assert_items_match(tests, ["iterations"]) tests_names_pretty = get_value(tests, "pretty_name") - for num, stage in enumerate(["exec", "clean", "total"]): + for num, stage in enumerate(self.stages + ["response"]): stage_values = [] for _, test in tests.items(): - stage_values.append( - test["api_results"]["stage"][stage]["values"] - ) - fig, axs = plt.subplots(figsize=(WIDTH * 3, HEIGHT * 3)) + api_results = test["api_results"]["stage"] + if stage == "response": + if test["target_dispvm"]: + stage_values.append( + [ + x - y + for x, y in zip( + api_results["total"]["values"], + api_results["clean"]["values"], + ) + ] + ) + else: + stage_values.append(api_results["total"]["values"]) + else: + stage_values.append( + api_results.get(stage, {}).get("values", []) + ) + fig, axs = plt.subplots(figsize=(WIDTH * 4, HEIGHT * 4)) x_pos = np.arange(1, len(stage_values) + 1) jitter_strength = 0.1 for i, values in enumerate(stage_values, start=1): @@ -788,7 +893,7 @@ def graph_03_stage_dist(self) -> None: axs.scatter(x_jittered, values, s=100, alpha=0.8, color=color) axs.set_xticks(x_pos, wrap_text(tests_names_pretty, 25)) axs.set_ylabel("Time (s)") - if stage == "total": + if stage in ["total", "response"]: stage_pretty = stage else: stage_pretty = self.stage_dict[stage]["legend"].lower() @@ -807,72 +912,166 @@ def graph_03_stage_dist(self) -> None: def graph_04_line(self) -> None: """Line graph of performance of each iteration.""" - def filter_preload(value, cond): # pylint: disable=unused-argument - return value == 0 or (3 <= value <= 5) + def filter_preload_small(value, _cond): + return value <= 3 - tests = filter_data( + def filter_preload(value, _cond): + return value == 0 or (4 <= value <= 6) + + base_tests = filter_data( self.api_tests, { - "non_dispvm": False, "admin_api": True, "concurrent": False, - "gui": False, - "preload_max": None, - }, - { - "preload_max": filter_preload, }, ) + base_tests_gui = filter_data(base_tests, {"gui": True}) + base_tests_nogui = filter_data(base_tests, {"gui": False}) + + tests = filter_data( + base_tests_nogui, + {"preload_max": None}, + {"preload_max": filter_preload}, + ) + tests_small = filter_data( + base_tests_nogui, + {"preload_max": None}, + {"preload_max": filter_preload_small}, + ) + + tests_gui = filter_data( + base_tests_gui, + {"preload_max": None}, + {"preload_max": filter_preload}, + ) + tests_small_gui = filter_data( + base_tests_gui, + {"preload_max": None}, + {"preload_max": filter_preload_small}, + ) + tests = dict(sorted(tests.items(), key=lambda x: x[1]["preload_max"])) - for num, stage in enumerate(["exec", "clean", "total"]): - fig, axs = plt.subplots(figsize=(WIDTH * 3, HEIGHT * 3)) - points_seen = set() - for test in tests.values(): - name = test["pretty_name"] - iteration_data = test.get("api_results", {}).get( - "iteration", {} - ) - iterations = range(1, test["iterations"] + 1) - stage_data = test.get("api_results", {}).get("stage", {}) - mean = round(stage_data[stage]["mean"], 1) - times = [iteration_data[str(it)][stage] for it in iterations] - axs.plot( - iterations, - times, - label=f"{name} \u03bc {mean}", - linestyle="--", - linewidth=3, - ) - rounder = 1 - for x_val, y_val in zip(iterations, times): - if (x_val, round(y_val, rounder)) in points_seen: + tests_small = dict( + sorted(tests_small.items(), key=lambda x: x[1]["preload_max"]) + ) + tests_gui = dict( + sorted(tests_gui.items(), key=lambda x: x[1]["preload_max"]) + ) + tests_small_gui = dict( + sorted(tests_small_gui.items(), key=lambda x: x[1]["preload_max"]) + ) + all_tests = { + "nogui-small": { + "for simple calls with few preloaded disposables": tests_small + }, + "gui-small": { + "for GUI calls with few preloaded disposables": tests_small_gui + }, + "nogui-large": { + "for simple calls with some preloaded disposables": tests + }, + "gui-large": { + "for GUI calls with some preloaded disposables": tests_gui + }, + } + + num_list = [] + rounder = 2 + # pylint: disable=too-many-nested-blocks + for idx, (test_type, test_value) in enumerate(all_tests.items()): + desc = list(test_value.keys())[0] + tests = list(test_value.values())[0] + for num, stage in enumerate(self.stages + ["response"]): + num_list.append(num) + fig, axs = plt.subplots(figsize=(WIDTH * 3, HEIGHT * 3)) + points_seen = set() + for test in tests.values(): + if ( + not test["target_dispvm"] + and stage != "response" + and stage not in self.vm_stage_dict + ): continue - points_seen.add((x_val, round(y_val, rounder))) - axs.text( - x_val, - y_val, - str(round(y_val, rounder)), - ha="center", - fontsize="xx-large", + name = test["pretty_name"] + iteration_data = test.get("api_results", {}).get( + "iteration", {} ) - if stage == "total": - stage_pretty = stage - else: - stage_pretty = self.stage_dict[stage]["legend"].lower() - axs.set_ylabel("Time (seconds)") - axs.set_title(f"{stage_pretty.capitalize()} time per iteration") - axs.set_xticks(iterations) - axs.legend() - caption = ( - f"Compares the {stage_pretty} per iteration of normal " - + "disposables with preloaded disposables." - ) - fig.supxlabel( - caption, - wrap=True, - color=CAPTION_COLOR, - ) - self.end(plt, str(num) + "_" + stage) + iterations = range(1, test["iterations"] + 1) + stage_data = test.get("api_results", {}).get("stage", {}) + if not (stage == "start" and test["preload_max"] > 0): + if stage == "response": + if test["target_dispvm"]: + mean_response = ( + stage_data["total"]["mean"] + - stage_data["clean"]["mean"] + ) + mean = round(mean_response, rounder) + times = [ + iteration_data[str(it)]["total"] + - iteration_data[str(it)]["clean"] + for it in iterations + ] + else: + mean_response = stage_data["total"]["mean"] + mean = round(mean_response, rounder) + times = [ + iteration_data[str(it)]["total"] + for it in iterations + ] + else: + mean = round(stage_data[stage]["mean"], rounder) + times = [ + iteration_data[str(it)][stage] + for it in iterations + ] + else: + continue + axs.plot( + iterations, + times, + label=f"{name} \u03bc {mean}", + linestyle="--", + linewidth=3, + ) + for x_val, y_val in zip(iterations, times): + if (x_val, round(y_val, rounder)) in points_seen: + continue + points_seen.add((x_val, round(y_val, rounder))) + axs.text( + x_val, + y_val, + str(round(y_val, rounder)), + ha="center", + fontsize="xx-large", + ) + if stage in ["total", "response"]: + stage_pretty = stage + else: + stage_pretty = self.stage_dict[stage]["legend"].lower() + axs.set_ylabel("Time (seconds)") + axs.set_title( + f"{stage_pretty.capitalize()} time per iteration {desc}" + ) + axs.set_xticks(iterations) + axs.legend() + caption = ( + f"Compares the {stage_pretty} per iteration of normal " + + "disposables with preloaded disposables." + ) + fig.supxlabel( + caption, + wrap=True, + color=CAPTION_COLOR, + ) + idx_str = str(idx) + if idx < 10: + idx_str = "0" + idx_str + self.end( + plt, + "{}_{}_{}_{}".format( + str(idx_str), str(num), stage, test_type + ), + ) def graph_08_template(self) -> None: """Graph including all available templates.""" @@ -880,7 +1079,7 @@ def graph_08_template(self) -> None: self.orig_data, { "admin_api": True, - "non_dispvm": False, + "target_dispvm": True, "extra_id": "", "concurrent": False, }, @@ -910,11 +1109,19 @@ def graph_08_template(self) -> None: def graph_09_method(self) -> None: """Graph including different callers comparing GUI and concurrency.""" orig_tests = filter_data( - self.data, {"non_dispvm": False, "extra_id": ""} + self.data, {"target_dispvm": True, "extra_id": ""} ) + + concurrent_tests = filter_data(orig_tests, {"concurrent": True}) + gui_tests = filter_data(orig_tests, {"gui": True}) for query in ["concurrent-gui", "gui", "concurrent", "gui-concurrent"]: + if ("concurrent" in query and not concurrent_tests) or ( + "gui" in query and not gui_tests + ): + logging.warning("Skipped query without tests: %s", query) + continue if query == "concurrent-gui": - tests = filter_data(orig_tests, {"concurrent": True}) + tests = concurrent_tests query_name = "with concurrency (with and without GUI)," query_file = "wconc_nogui_gui" elif query == "gui": @@ -926,7 +1133,7 @@ def graph_09_method(self) -> None: query_name = "with and without concurrency," query_file = "noconc_conc" elif query == "gui-concurrent": - tests = filter_data(orig_tests, {"gui": True}) + tests = gui_tests query_name = "with GUI (with and without concurrency)," query_file = "wgui_noconc_conc" else: @@ -941,7 +1148,7 @@ def graph_09_method(self) -> None: vm_qrexec = filter_data( tests, {"from_dom0": False, "admin_api": False} ) - + method_tests = [t for t in [dom0_api, dom0_qvm, vm_qrexec] if t] for key in ["mean", "total"]: preload_tests = filter_data( tests, {"preload_max": 1}, {"preload_max": operator.ge} @@ -949,11 +1156,11 @@ def graph_09_method(self) -> None: assert_items_match(preload_tests, ["preload_max"]) preload_max = get_value(preload_tests, "preload_max")[0] caption = ( - f"Compares workflows of normal disposables with " - + "{preload_max} preloaded disposables." + "Compares workflows of normal disposables with " + + f"{preload_max} preloaded disposables." ) self.bar_plot( - [dom0_api, dom0_qvm, vm_qrexec], + method_tests, titles=["dom0 API", "dom0 qvm", "qube qrexec-client-vm"], title_prefix=False, keys=[key], @@ -1057,6 +1264,7 @@ def main() -> None: logging.info("Loaded data") if args.output_dir: os.makedirs(args.output_dir, exist_ok=True) + logging.info("Output directory is %s", repr(args.output_dir)) graph = Graph( data, default_template=args.template,