From b831f23f5fe9850a8984634b687e865119bfccda Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 6 Sep 2022 16:45:05 +0530 Subject: [PATCH] kvm: add libvirt host capabilities method for cpu speed retrieval (#6696) Fixes #6680 While finding CPU speed for KVM host following methods will be used in the same order: 1. lscpu 2. value in /sys/devices/system/cpu/cpu0/cpufreq/base_frequency 3. virsh capabilities 4. libvirt nodeinfo This will allow correct value for AMD based hosts when first two methods doesn't give a value Signed-off-by: Abhishek Kumar --- .../cloudstack/utils/linux/KVMHostInfo.java | 68 ++++++++++++++++--- .../utils/linux/KVMHostInfoTest.java | 27 ++++++-- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java index e34f39fde34..807b2541fd3 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java @@ -16,21 +16,33 @@ // under the License. package org.apache.cloudstack.utils.linux; -import com.cloud.hypervisor.kvm.resource.LibvirtCapXMLParser; -import com.cloud.hypervisor.kvm.resource.LibvirtConnection; -import com.cloud.utils.script.Script; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.cloudstack.utils.security.ParserUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.libvirt.Connect; import org.libvirt.LibvirtException; import org.libvirt.NodeInfo; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; -import java.io.FileReader; -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; +import com.cloud.hypervisor.kvm.resource.LibvirtCapXMLParser; +import com.cloud.hypervisor.kvm.resource.LibvirtConnection; +import com.cloud.utils.script.Script; public class KVMHostInfo { @@ -82,7 +94,7 @@ public class KVMHostInfo { return this.capabilities; } - protected static long getCpuSpeed(final NodeInfo nodeInfo) { + protected static long getCpuSpeed(final String cpabilities, final NodeInfo nodeInfo) { long speed = 0L; speed = getCpuSpeedFromCommandLscpu(); if(speed > 0L) { @@ -94,6 +106,11 @@ public class KVMHostInfo { return speed; } + speed = getCpuSpeedFromHostCapabilities(cpabilities); + if(speed > 0L) { + return speed; + } + LOGGER.info(String.format("Using the value [%s] provided by Libvirt.", nodeInfo.mhz)); speed = nodeInfo.mhz; return speed; @@ -125,12 +142,41 @@ public class KVMHostInfo { } } + protected static long getCpuSpeedFromHostCapabilities(final String capabilities) { + LOGGER.info("Fetching CPU speed from \"host capabilities\""); + long speed = 0L; + try { + DocumentBuilderFactory docFactory = ParserUtils.getSaferDocumentBuilderFactory(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(new InputSource(new StringReader(capabilities))); + Element rootElement = doc.getDocumentElement(); + NodeList nodes = rootElement.getElementsByTagName("cpu"); + Node node = nodes.item(0); + nodes = ((Element)node).getElementsByTagName("counter"); + for (int i = 0; i < nodes.getLength(); i++) { + node = nodes.item(i); + NamedNodeMap attributes = node.getAttributes(); + Node nameNode = attributes.getNamedItem("name"); + Node freqNode = attributes.getNamedItem("frequency"); + if (nameNode != null && "tsc".equals(nameNode.getNodeValue()) && freqNode != null && StringUtils.isNotEmpty(freqNode.getNodeValue())) { + speed = Long.parseLong(freqNode.getNodeValue()) / 1000000; + LOGGER.info(String.format("Retrieved value [%s] from \"host capabilities\". This corresponds to a CPU speed of [%s] MHz.", freqNode.getNodeValue(), speed)); + } + } + } catch (Exception ex) { + LOGGER.error("Unable to fetch CPU speed from \"host capabilities\"", ex); + speed = 0L; + } + return speed; + } + private void getHostInfoFromLibvirt() { try { final Connect conn = LibvirtConnection.getConnection(); final NodeInfo hosts = conn.nodeInfo(); + final String capabilities = conn.getCapabilities(); if (this.cpuSpeed == 0) { - this.cpuSpeed = getCpuSpeed(hosts); + this.cpuSpeed = getCpuSpeed(capabilities, hosts); } else { LOGGER.debug(String.format("Using existing configured CPU frequency %s", this.cpuSpeed)); } @@ -146,7 +192,7 @@ public class KVMHostInfo { this.cpus = hosts.cpus; final LibvirtCapXMLParser parser = new LibvirtCapXMLParser(); - parser.parseCapabilitiesXML(conn.getCapabilities()); + parser.parseCapabilitiesXML(capabilities); final ArrayList oss = parser.getGuestOsType(); for (final String s : oss) { /* diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java index 360f72579a6..67d3e011f81 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java @@ -16,23 +16,22 @@ // under the License. package org.apache.cloudstack.utils.linux; -import com.cloud.hypervisor.kvm.resource.LibvirtConnection; import org.apache.commons.lang.SystemUtils; - import org.hamcrest.Matchers; -import org.junit.Test; -import org.junit.Assume; import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; import org.junit.runner.RunWith; import org.libvirt.Connect; -import org.mockito.Mockito; - import org.libvirt.NodeInfo; +import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.cloud.hypervisor.kvm.resource.LibvirtConnection; + @RunWith(PowerMockRunner.class) @PrepareForTest(value = {LibvirtConnection.class}) @PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"}) @@ -45,7 +44,21 @@ public class KVMHostInfoTest { Assume.assumeTrue(SystemUtils.IS_OS_LINUX); NodeInfo nodeInfo = Mockito.mock(NodeInfo.class); nodeInfo.mhz = 1000; - Assert.assertThat(KVMHostInfo.getCpuSpeed(nodeInfo), Matchers.greaterThan(0l)); + Assert.assertThat(KVMHostInfo.getCpuSpeed(null, nodeInfo), Matchers.greaterThan(0l)); + } + + @Test + public void getCpuSpeedFromHostCapabilities() { + String capabilities = "\n" + + "8a330742-345f-b0df-7954-c9960b88116c\n" + + " \n" + + " x86_64\n" + + " Opteron_G2\n" + + " AMD\n" + + " \n" + + " \n" + + "\n";; + Assert.assertEquals(2350L, KVMHostInfo.getCpuSpeedFromHostCapabilities(capabilities)); } @Test