From 71f24dfeeeb6713f530b260a0e2b4bb23c00effa Mon Sep 17 00:00:00 2001 From: Eli Qiao Date: Wed, 25 May 2016 14:48:08 +0800 Subject: [PATCH] libvirt: add perf event support when create instance Libvirt 1.3.3 has intergrated `perf`, which can be used as performance statistics. This patch enable perf event when create instances, the perf event data can be collected by Ceilometer or other external monitor system. Implement blueprint: support-perf-event Co-Authored-By: Qiaowei Ren Change-Id: I2ffdabe40a7706cd5061a45f3e46c8245adb3b07 --- nova/conf/libvirt.py | 27 +++++- nova/tests/unit/virt/libvirt/test_config.py | 39 ++++++++ nova/tests/unit/virt/libvirt/test_driver.py | 95 ++++++++++++++++++- nova/virt/libvirt/config.py | 19 ++++ nova/virt/libvirt/driver.py | 44 +++++++++ .../add-perf-event-e1385b6b6346fbda.yaml | 6 ++ 6 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-perf-event-e1385b6b6346fbda.yaml diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py index d4b77a170aed..883ad8238e35 100644 --- a/nova/conf/libvirt.py +++ b/nova/conf/libvirt.py @@ -216,7 +216,32 @@ libvirt_general_opts = [ default=1, help='In a realtime host context vCPUs for guest will run in ' 'that scheduling priority. Priority depends on the host ' - 'kernel (usually 1-99)') + 'kernel (usually 1-99)'), + cfg.ListOpt('enabled_perf_events', + default=[], + help= """ +This is a performance event list which could be used as monitor. These events +will be passed to libvirt domain xml while creating a new instances. +Then event statistics data can be collected from libvirt. The minimum +libvirt version is 1.3.3. + +* Possible values: + A string list. + For example: + ``enabled_perf_events = cmt, mbml, mbmt`` + + The supported events list can be found in + https://libvirt.org/html/libvirt-libvirt-domain.html , which + you may need to search key words ``VIR_PERF_PARAM_*`` + +* Services that use this: + + ``nova-compute`` + +* Related options: + None + +"""), ] libvirt_imagebackend_opts = [ diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py index 09c4512a2b7b..0af125b29150 100644 --- a/nova/tests/unit/virt/libvirt/test_config.py +++ b/nova/tests/unit/virt/libvirt/test_config.py @@ -1990,6 +1990,32 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest): """) + def test_config_perf(self): + obj = config.LibvirtConfigGuest() + obj.virt_type = "kvm" + obj.memory = 100 * units.Mi + obj.vcpus = 2 + obj.name = "perf" + obj.uuid = "f01cf68d-515c-4daf-b85f-ef1424d93bfc" + obj.os_type = "fake" + obj.perf_events = ['cmt', 'mbml'] + xml = obj.to_xml() + + self.assertXmlEqual(xml, """ + + f01cf68d-515c-4daf-b85f-ef1424d93bfc + perf + 104857600 + 2 + + fake + + + + + + """) + def test_config_machine_type(self): obj = config.LibvirtConfigGuest() obj.virt_type = "kvm" @@ -2054,6 +2080,19 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest): self.assertEqual(obj.cpu.match, 'exact') self.assertEqual(obj.cpu.model, 'kvm64') + def test_ConfigGuest_parse_perf(self): + xmldoc = """ + + + + + + """ + obj = config.LibvirtConfigGuest() + obj.parse_str(xmldoc) + + self.assertEqual(['cmt'], obj.perf_events) + class LibvirtConfigGuestSnapshotTest(LibvirtConfigBaseTest): diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 14ba080812fa..2304f10f2d49 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -5276,6 +5276,95 @@ class LibvirtConnTestCase(test.NoDBTestCase): break self.assertTrue(no_exist) + @mock.patch('nova.virt.libvirt.driver.LOG.warning') + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + @mock.patch.object(host.Host, "get_capabilities") + def test_get_supported_perf_events_foo(self, mock_get_caps, + mock_min_version, + mock_warn): + self.flags(enabled_perf_events=['foo'], group='libvirt') + + caps = vconfig.LibvirtConfigCaps() + caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = "x86_64" + caps.host.topology = self._fake_caps_numa_topology() + + mock_get_caps.return_value = caps + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + events = drvr._get_supported_perf_events() + + self.assertTrue(mock_warn.called) + self.assertEqual([], events) + + @mock.patch.object(host.Host, "get_capabilities") + def _test_get_guest_with_perf(self, caps, events, mock_get_caps): + self.flags(enabled_perf_events=['cmt'], group='libvirt') + mock_get_caps.return_value = caps + + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + drvr.init_host('test_perf') + instance_ref = objects.Instance(**self.test_instance) + image_meta = objects.ImageMeta.from_dict(self.test_image_meta) + + disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, + instance_ref, + image_meta) + cfg = drvr._get_guest_config(instance_ref, [], + image_meta, disk_info) + + self.assertEqual(events, cfg.perf_events) + + @mock.patch.object(fakelibvirt, 'VIR_PERF_PARAM_CMT', True, + create=True) + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + def test_get_guest_with_perf_supported(self, + mock_min_version): + self.flags(enabled_perf_events=['cmt'], group='libvirt') + caps = vconfig.LibvirtConfigCaps() + caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = "x86_64" + caps.host.topology = self._fake_caps_numa_topology() + feature = vconfig.LibvirtConfigGuestCPUFeature() + feature.name = 'cqm' + feature.policy = cpumodel.POLICY_REQUIRE + caps.host.cpu.features = set([feature]) + + self._test_get_guest_with_perf(caps, ['cmt']) + + @mock.patch.object(host.Host, 'has_min_version') + def test_get_guest_with_perf_libvirt_unsupported(self, mock_min_version): + + def fake_has_min_version(lv_ver=None, hv_ver=None, hv_type=None): + if lv_ver == libvirt_driver.MIN_LIBVIRT_PERF_VERSION: + return False + return True + + mock_min_version.side_effect = fake_has_min_version + self.flags(enabled_perf_events=['cmt'], group='libvirt') + + caps = vconfig.LibvirtConfigCaps() + caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = "x86_64" + + self._test_get_guest_with_perf(caps, []) + + @mock.patch.object(fakelibvirt, 'VIR_PERF_PARAM_CMT', True, + create=True) + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + def test_get_guest_with_perf_host_unsupported(self, + mock_min_version): + self.flags(enabled_perf_events=['cmt'], group='libvirt') + caps = vconfig.LibvirtConfigCaps() + caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = "x86_64" + caps.host.topology = self._fake_caps_numa_topology() + + self._test_get_guest_with_perf(caps, []) + def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) self._check_xml_and_uri(instance_data, @@ -10133,7 +10222,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): mock.patch.object(drvr, "_do_quality_warnings", return_value=None), mock.patch.object(objects.Service, "get_by_compute_host", - return_value=service_mock)): + return_value=service_mock), + mock.patch.object(host.Host, "get_capabilities")): drvr.init_host("wibble") self.assertRaises(exception.HypervisorUnavailable, @@ -10155,7 +10245,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): mock.patch.object(drvr, "_do_quality_warnings", return_value=None), mock.patch.object(objects.Service, "get_by_compute_host", - return_value=service_mock)): + return_value=service_mock), + mock.patch.object(host.Host, "get_capabilities")): drvr.init_host("wibble") drvr.get_num_instances() diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py index e3d5209b3686..051bb3ec7fde 100644 --- a/nova/virt/libvirt/config.py +++ b/nova/virt/libvirt/config.py @@ -2019,6 +2019,7 @@ class LibvirtConfigGuest(LibvirtConfigObject): self.devices = [] self.metadata = [] self.idmaps = [] + self.perf_events = [] def _format_basic_props(self, root): root.append(self._text_node("uuid", self.uuid)) @@ -2102,6 +2103,15 @@ class LibvirtConfigGuest(LibvirtConfigObject): idmaps.append(idmap.format_dom()) root.append(idmaps) + def _format_perf_events(self, root): + if len(self.perf_events) == 0: + return + perfs = etree.Element("perf") + for pe in self.perf_events: + event = etree.Element("event", name=pe, enabled="yes") + perfs.append(event) + root.append(perfs) + def format_dom(self): root = super(LibvirtConfigGuest, self).format_dom() @@ -2128,6 +2138,8 @@ class LibvirtConfigGuest(LibvirtConfigObject): self._format_idmaps(root) + self._format_perf_events(root) + return root def parse_dom(self, xmldoc): @@ -2167,10 +2179,17 @@ class LibvirtConfigGuest(LibvirtConfigObject): obj = LibvirtConfigGuestCPU() obj.parse_dom(c) self.cpu = obj + elif c.tag == 'perf': + for p in c.getchildren(): + if p.get('enabled') and p.get('enabled') == 'yes': + self.add_perf_event(p.get('name')) def add_device(self, dev): self.devices.append(dev) + def add_perf_event(self, event): + self.perf_events.append(event) + def set_clock(self, clk): self.clock = clk diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 27c20fc8532a..f89ac1c81694 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -297,6 +297,15 @@ MIN_QEMU_OTHER_ARCH = {arch.S390: MIN_QEMU_S390_VERSION, arch.PPC64LE: MIN_QEMU_PPC64_VERSION, } +# perf events support +MIN_LIBVIRT_PERF_VERSION = (1, 3, 3) +LIBVIRT_PERF_EVENT_PREFIX = 'VIR_PERF_PARAM_' + +PERF_EVENTS_CPU_FLAG_MAPPING = {'cmt': 'cqm', + 'mbml': 'cqm_mbm_local', + 'mbmt': 'cqm_mbm_total', + } + class LibvirtDriver(driver.ComputeDriver): capabilities = { @@ -320,6 +329,7 @@ class LibvirtDriver(driver.ComputeDriver): self._fc_wwnns = None self._fc_wwpns = None self._caps = None + self._supported_perf_events = [] self.firewall_driver = firewall.load_driver( DEFAULT_FIREWALL_DRIVER, host=self._host) @@ -442,6 +452,8 @@ class LibvirtDriver(driver.ComputeDriver): self._parse_migration_flags() + self._supported_perf_events = self._get_supported_perf_events() + if (CONF.libvirt.virt_type == 'lxc' and not (CONF.libvirt.uid_maps and CONF.libvirt.gid_maps)): LOG.warning(_LW("Running libvirt-lxc without user namespaces is " @@ -4148,6 +4160,35 @@ class LibvirtDriver(driver.ComputeDriver): self._host.has_min_version(MIN_LIBVIRT_UEFI_VERSION) and os.path.exists(DEFAULT_UEFI_LOADER_PATH[caps.host.cpu.arch])) + def _get_supported_perf_events(self): + + if (len(CONF.libvirt.enabled_perf_events) == 0 or + not self._host.has_min_version(MIN_LIBVIRT_PERF_VERSION)): + return [] + + supported_events = [] + host_cpu_info = self._get_cpu_info() + for event in CONF.libvirt.enabled_perf_events: + if self._supported_perf_event(event, host_cpu_info['features']): + supported_events.append(event) + return supported_events + + def _supported_perf_event(self, event, cpu_features): + + libvirt_perf_event_name = LIBVIRT_PERF_EVENT_PREFIX + event.upper() + + if not hasattr(libvirt, libvirt_perf_event_name): + LOG.warning(_LW("Libvirt doesn't support event type %s."), + event) + return False + + if (event in PERF_EVENTS_CPU_FLAG_MAPPING + and PERF_EVENTS_CPU_FLAG_MAPPING[event] not in cpu_features): + LOG.warning(_LW("Host does not support event type %s."), event) + return False + + return True + def _configure_guest_by_virt_type(self, guest, virt_type, caps, instance, image_meta, flavor, root_device_name): if virt_type == "xen": @@ -4320,6 +4361,9 @@ class LibvirtDriver(driver.ComputeDriver): instance)) guest.idmaps = self._get_guest_idmaps() + for event in self._supported_perf_events: + guest.add_perf_event(event) + self._update_guest_cputune(guest, flavor, virt_type) guest.cpu = self._get_guest_cpu_config( diff --git a/releasenotes/notes/add-perf-event-e1385b6b6346fbda.yaml b/releasenotes/notes/add-perf-event-e1385b6b6346fbda.yaml new file mode 100644 index 000000000000..d1bbd0766969 --- /dev/null +++ b/releasenotes/notes/add-perf-event-e1385b6b6346fbda.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add perf event support for libvirt driver. + This can be done by adding new configure option + 'enabled_perf_events' in libvirt section of + nova.conf. This feature requires libvirt>=1.3.3.