You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2022/11/09 17:00:18 UTC

[cloudstack] branch main updated: Fix memory stats for KVM (#6358)

This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 1843632c24d Fix memory stats for KVM (#6358)
1843632c24d is described below

commit 1843632c24daf4767249c04249a6c37c224f6e87
Author: José Flauzino <jo...@gmail.com>
AuthorDate: Wed Nov 9 14:00:12 2022 -0300

    Fix memory stats for KVM (#6358)
    
    Co-authored-by: joseflauzino <jo...@scclouds.com.br>
---
 agent/conf/agent.properties                        |   4 +
 agent/src/main/java/com/cloud/agent/Agent.java     |   3 +-
 .../cloud/agent/properties/AgentProperties.java    |  15 ++
 .../kvm/resource/LibvirtComputingResource.java     |  87 +++++++++++-
 .../kvm/resource/LibvirtDomainXMLParser.java       |  27 ++++
 .../hypervisor/kvm/resource/LibvirtVMDef.java      |  60 +++++++-
 .../kvm/resource/LibvirtComputingResourceTest.java | 154 ++++++++++++++++++++-
 .../kvm/resource/LibvirtDomainXMLParserTest.java   |   6 +
 .../hypervisor/kvm/resource/LibvirtVMDefTest.java  |  23 +++
 9 files changed, 375 insertions(+), 4 deletions(-)

diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties
index b877e97afab..e6d8844bda2 100644
--- a/agent/conf/agent.properties
+++ b/agent/conf/agent.properties
@@ -202,6 +202,10 @@ hypervisor.type=kvm
 # Disable memory ballooning on vm guests for overcommit, by default overcommit
 # feature enables balloon and sets currentMemory to a minimum value.
 #
+# The time interval (in seconds) at which the balloon driver will get memory stats updates.
+# This is equivalent to Libvirt's --period parameter when using the dommemstat command.
+# vm.memballoon.stats.period=0
+#
 # vm.diskactivity.checkenabled=false
 # Set to true to check disk activity on VM's disks before starting a VM. This only applies
 # to QCOW2 files, and ensures that there is no other running instance accessing
diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java
index 21faaa2a1d4..b0028c148fb 100644
--- a/agent/src/main/java/com/cloud/agent/Agent.java
+++ b/agent/src/main/java/com/cloud/agent/Agent.java
@@ -100,7 +100,7 @@ import com.cloud.utils.script.Script;
  *
  **/
 public class Agent implements HandlerFactory, IAgentControl {
-    private static final Logger s_logger = Logger.getLogger(Agent.class.getName());
+    protected static Logger s_logger = Logger.getLogger(Agent.class);
 
     public enum ExitStatus {
         Normal(0), // Normal status = 0.
@@ -303,6 +303,7 @@ public class Agent implements HandlerFactory, IAgentControl {
         }
         _shell.updateConnectedHost();
         scavengeOldAgentObjects();
+
     }
 
     public void stop(final String reason, final String detail) {
diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
index 657876c13ac..9f84bb572db 100644
--- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
+++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
@@ -54,6 +54,21 @@ public class AgentProperties{
      */
     public static final Property<Boolean> ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM = new Property<Boolean>("enable.manually.setting.cpu.topology.on.kvm.vm", true);
 
+    /**
+     * Disables memory ballooning on VM guests for overcommit.<br>
+     * By default overcommit feature enables balloon and sets currentMemory to a minimum value.<br>
+     * Data type: Boolean.<br>
+     * Default value: <code>false</code>
+     */
+    public static final Property<Boolean> VM_MEMBALLOON_DISABLE = new Property<>("vm.memballoon.disable", false);
+
+    /**
+     * The time interval (in seconds) at which the balloon driver will get memory stats updates. This is equivalent to Libvirt's <code>--period</code> parameter when using the dommemstat command.
+     * Data type: Integer.<br>
+     * Default value: <code>0</code>
+     */
+    public static final Property<Integer> VM_MEMBALLOON_STATS_PERIOD = new Property<>("vm.memballoon.stats.period", 0);
+
     public static class Property <T>{
         private final String name;
         private final T defaultValue;
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index 59eaa6b64d8..bcfff1b6a32 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -147,6 +147,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
@@ -221,7 +222,7 @@ import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION;
  *         pool | the parent of the storage pool hierarchy * }
  **/
 public class LibvirtComputingResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer {
-    private static final Logger s_logger = Logger.getLogger(LibvirtComputingResource.class);
+    protected static Logger s_logger = Logger.getLogger(LibvirtComputingResource.class);
 
     private static final String CONFIG_VALUES_SEPARATOR = ",";
 
@@ -448,6 +449,15 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
 
     protected Boolean enableManuallySettingCpuTopologyOnKvmVm = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM);
 
+    protected LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
+
+    /**
+     * Virsh command to set the memory balloon stats period.<br><br>
+     * 1st parameter: the VM ID or name;<br>
+     * 2nd parameter: the period (in seconds).
+     */
+    private static final String COMMAND_SET_MEM_BALLOON_STATS_PERIOD = "virsh dommemstat %s --period %s --live";
+
     protected long getHypervisorLibvirtVersion() {
         return _hypervisorLibvirtVersion;
     }
@@ -1298,9 +1308,84 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
             s_logger.info("iscsi session clean up is disabled");
         }
 
+        setupMemoryBalloonStatsPeriod(conn);
+
         return true;
     }
 
+    /**
+     * Gets the ID list of the VMs to set memory balloon stats period.
+     * @param conn the Libvirt connection.
+     * @return the list of VM IDs.
+     */
+    protected List<Integer> getVmsToSetMemoryBalloonStatsPeriod(Connect conn) {
+        List<Integer> vmIdList = new ArrayList<>();
+        Integer[] vmIds = null;
+        try {
+            vmIds = ArrayUtils.toObject(conn.listDomains());
+        } catch (final LibvirtException e) {
+            s_logger.error("Unable to get the list of Libvirt domains on this host.", e);
+            return vmIdList;
+        }
+        vmIdList.addAll(Arrays.asList(vmIds));
+        s_logger.debug(String.format("We have found a total of [%s] VMs (Libvirt domains) on this host: [%s].", vmIdList.size(), vmIdList.toString()));
+
+        if (vmIdList.isEmpty()) {
+            s_logger.info("Skipping the memory balloon stats period setting, since there are no VMs (active Libvirt domains) on this host.");
+        }
+        return vmIdList;
+    }
+
+    /**
+     * Gets the current VM balloon stats period from the agent.properties file.
+     * @return the current VM balloon stats period.
+     */
+    protected Integer getCurrentVmBalloonStatsPeriod() {
+        if (Boolean.TRUE.equals(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_DISABLE))) {
+            s_logger.info(String.format("The [%s] property is set to 'true', so the memory balloon stats period will be set to 0 for all VMs.",
+                    AgentProperties.VM_MEMBALLOON_DISABLE.getName()));
+            return 0;
+        }
+        Integer vmBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD);
+        if (vmBalloonStatsPeriod == 0) {
+            s_logger.info(String.format("The [%s] property is set to '0', this prevents memory statistics from being displayed correctly. "
+                    + "Adjust (increase) the value of this parameter to correct this.", AgentProperties.VM_MEMBALLOON_STATS_PERIOD.getName()));
+        }
+        return vmBalloonStatsPeriod;
+    }
+
+    /**
+     * Sets the balloon driver of each VM to get the memory stats at the time interval defined in the agent.properties file.
+     * @param conn the Libvirt connection.
+     */
+    protected void setupMemoryBalloonStatsPeriod(Connect conn) {
+        List<Integer> vmIdList = getVmsToSetMemoryBalloonStatsPeriod(conn);
+        Integer currentVmBalloonStatsPeriod = getCurrentVmBalloonStatsPeriod();
+        for (Integer vmId : vmIdList) {
+            Domain dm = null;
+            try {
+                dm = conn.domainLookupByID(vmId);
+                parser.parseDomainXML(dm.getXMLDesc(0));
+                MemBalloonDef memBalloon = parser.getMemBalloon();
+                if (!MemBalloonDef.MemBalloonModel.VIRTIO.equals(memBalloon.getMemBalloonModel())) {
+                    s_logger.debug(String.format("Skipping the memory balloon stats period setting for the VM (Libvirt Domain) with ID [%s] and name [%s] because this VM has no memory"
+                            + " balloon.", vmId, dm.getName()));
+                }
+                String setMemBalloonStatsPeriodCommand = String.format(COMMAND_SET_MEM_BALLOON_STATS_PERIOD, vmId, currentVmBalloonStatsPeriod);
+                String setMemBalloonStatsPeriodResult = Script.runSimpleBashScript(setMemBalloonStatsPeriodCommand);
+                if (StringUtils.isNotBlank(setMemBalloonStatsPeriodResult)) {
+                    s_logger.error(String.format("Unable to set up memory balloon stats period for VM (Libvirt Domain) with ID [%s] due to an error when running the [%s] "
+                            + "command. Output: [%s].", vmId, setMemBalloonStatsPeriodCommand, setMemBalloonStatsPeriodResult));
+                    continue;
+                }
+                s_logger.debug(String.format("The memory balloon stats period [%s] has been set successfully for the VM (Libvirt Domain) with ID [%s] and name [%s].",
+                        currentVmBalloonStatsPeriod, vmId, dm.getName()));
+            } catch (final LibvirtException e) {
+                s_logger.warn("Failed to set up memory balloon stats period." + e.getMessage());
+            }
+        }
+    }
+
     private void enableSSLForKvmAgent(final Map<String, Object> params) {
         final File keyStoreFile = PropertiesUtil.findConfigFile(KeyStoreUtils.KS_FILENAME);
         if (keyStoreFile == null) {
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
index da9b2233c8d..d28ace6d576 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
@@ -41,6 +41,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.NicModel;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
@@ -50,6 +51,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel;
 public class LibvirtDomainXMLParser {
     private static final Logger s_logger = Logger.getLogger(LibvirtDomainXMLParser.class);
     private final List<InterfaceDef> interfaces = new ArrayList<InterfaceDef>();
+    private MemBalloonDef memBalloonDef = new MemBalloonDef();
     private final List<DiskDef> diskDefs = new ArrayList<DiskDef>();
     private final List<RngDef> rngDefs = new ArrayList<RngDef>();
     private final List<ChannelDef> channels = new ArrayList<ChannelDef>();
@@ -206,6 +208,8 @@ public class LibvirtDomainXMLParser {
                 diskDefs.add(def);
             }
 
+            memBalloonDef = parseMemBalloonTag(devices);
+
             NodeList nics = devices.getElementsByTagName("interface");
             for (int i = 0; i < nics.getLength(); i++) {
                 Element nic = (Element)nics.item(i);
@@ -342,6 +346,25 @@ public class LibvirtDomainXMLParser {
         return false;
     }
 
+    /**
+     * Parse the memballoon tag.
+     * @param devices the devices tag.
+     * @return the MemBalloonDef.
+     */
+    private MemBalloonDef parseMemBalloonTag(Element devices) {
+        MemBalloonDef def = new MemBalloonDef();
+        NodeList memBalloons = devices.getElementsByTagName("memballoon");
+        if (memBalloons != null && memBalloons.getLength() != 0) {
+            Element memBalloon = (Element)memBalloons.item(0);
+            String model = memBalloon.getAttribute("model");
+            if (model.equalsIgnoreCase("virtio")) {
+                String statsPeriod = getAttrValue("stats", "period", memBalloon);
+                def.defVirtioMemBalloon(statsPeriod);
+            }
+        }
+        return def;
+    }
+
     private static String getTagValue(String tag, Element eElement) {
         NodeList tagNodeList = eElement.getElementsByTagName(tag);
         if (tagNodeList == null || tagNodeList.getLength() == 0) {
@@ -372,6 +395,10 @@ public class LibvirtDomainXMLParser {
         return interfaces;
     }
 
+    public MemBalloonDef getMemBalloon() {
+        return memBalloonDef;
+    }
+
     public List<DiskDef> getDisks() {
         return diskDefs;
     }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
index 2c1e362fc63..e8af21cc681 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
@@ -27,6 +27,9 @@ import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
+
 public class LibvirtVMDef {
     private static final Logger s_logger = Logger.getLogger(LibvirtVMDef.class);
 
@@ -236,6 +239,7 @@ public class LibvirtVMDef {
         private int vcpu = -1;
         private int maxVcpu = -1;
         private boolean memoryBalloning = false;
+        private int memoryBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD);
 
         public void setMemorySize(long mem) {
             this.memory = mem;
@@ -276,7 +280,14 @@ public class LibvirtVMDef {
                 response.append(String.format("<cpu> <numa> <cell id='0' cpus='0-%s' memory='%s' unit='KiB'/> </numa> </cpu>\n", this.maxVcpu - 1, this.currentMemory));
             }
 
-            response.append(String.format("<devices>\n<memballoon model='%s'/>\n</devices>\n", this.memoryBalloning ? "virtio" : "none"));
+            MemBalloonDef memBalloonDef = new MemBalloonDef();
+            if (this.memoryBalloning) {
+                memBalloonDef.defVirtioMemBalloon(String.valueOf(memoryBalloonStatsPeriod));
+            } else {
+                memBalloonDef.defNoneMemBalloon();
+            }
+            response.append(String.format("<devices>%n%s%n</devices>%n", memBalloonDef.toString()));
+
             response.append(String.format("<vcpu current=\"%s\">%s</vcpu>\n", this.vcpu, this.maxVcpu));
             return response.toString();
         }
@@ -1172,6 +1183,53 @@ public class LibvirtVMDef {
         }
     }
 
+    public static class MemBalloonDef {
+        private MemBalloonModel memBalloonModel;
+        private String memBalloonStatsPeriod;
+
+        public enum MemBalloonModel {
+            NONE("none"), VIRTIO("virtio");
+            String model;
+
+            MemBalloonModel(String model) {
+                this.model = model;
+            }
+
+            @Override
+            public String toString() {
+                return model;
+            }
+        }
+
+        public void defNoneMemBalloon() {
+            memBalloonModel = MemBalloonModel.NONE;
+        }
+
+        public void defVirtioMemBalloon(String period) {
+            memBalloonModel = MemBalloonModel.VIRTIO;
+            memBalloonStatsPeriod = period;
+        }
+
+        public MemBalloonModel getMemBalloonModel() {
+            return memBalloonModel;
+        }
+
+        public String getMemBalloonStatsPeriod() {
+            return memBalloonStatsPeriod;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder memBalloonBuilder = new StringBuilder();
+            memBalloonBuilder.append("<memballoon model='" + memBalloonModel + "'>\n");
+            if (StringUtils.isNotBlank(memBalloonStatsPeriod)) {
+                memBalloonBuilder.append("<stats period='" + memBalloonStatsPeriod +"'/>\n");
+            }
+            memBalloonBuilder.append("</memballoon>");
+            return memBalloonBuilder.toString();
+        }
+    }
+
     public static class InterfaceDef {
         public enum GuestNetType {
             BRIDGE("bridge"), DIRECT("direct"), NETWORK("network"), USER("user"), ETHERNET("ethernet"), INTERNAL("internal"), VHOSTUSER("vhostuser");
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java
index f3fb1df74a0..c9c288f4951 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java
@@ -61,8 +61,10 @@ import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
 import org.apache.cloudstack.utils.linux.CPUStat;
 import org.apache.cloudstack.utils.linux.MemStat;
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.apache.commons.lang.ArrayUtils;
 import org.apache.commons.lang.SystemUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
 import org.joda.time.Duration;
 import org.junit.Assert;
 import org.junit.Before;
@@ -164,6 +166,8 @@ import com.cloud.agent.api.to.NicTO;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.agent.api.to.VolumeTO;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
 import com.cloud.exception.InternalErrorException;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
@@ -181,6 +185,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef.GuestType;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SerialDef;
@@ -213,7 +218,7 @@ import com.cloud.vm.VirtualMachine.PowerState;
 import com.cloud.vm.VirtualMachine.Type;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {MemStat.class, SshHelper.class})
+@PrepareForTest(value = {MemStat.class, SshHelper.class, AgentPropertiesFileHandler.class, AgentProperties.class, Script.class})
 @PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*"})
 public class LibvirtComputingResourceTest {
 
@@ -223,6 +228,13 @@ public class LibvirtComputingResourceTest {
     VirtualMachineTO vmTO;
     @Mock
     LibvirtVMDef vmDef;
+    @Mock
+    Logger loggerMock;
+    @Mock
+    Connect connMock;
+    @Mock
+    LibvirtDomainXMLParser parserMock;
+
     @Spy
     private LibvirtComputingResource libvirtComputingResourceSpy = Mockito.spy(LibvirtComputingResource.class);
 
@@ -245,8 +257,10 @@ public class LibvirtComputingResourceTest {
     @Before
     public void setup() throws Exception {
         libvirtComputingResourceSpy._qemuSocketsPath = new File("/var/run/qemu");
+        libvirtComputingResourceSpy.parser = parserMock;
         Scanner scanner = new Scanner(memInfo);
         PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
+        LibvirtComputingResource.s_logger = loggerMock;
     }
 
     /**
@@ -5928,4 +5942,142 @@ public class LibvirtComputingResourceTest {
         configLocalStorageTests(params);
     }
 
+    @Test
+    public void getVmsToSetMemoryBalloonStatsPeriodTestLibvirtError() throws LibvirtException {
+        Mockito.when(connMock.listDomains()).thenThrow(LibvirtException.class);
+
+        List<Integer> result = libvirtComputingResourceSpy.getVmsToSetMemoryBalloonStatsPeriod(connMock);
+
+        Mockito.verify(loggerMock).error(Mockito.anyString(), Mockito.any());
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void getVmsToSetMemoryBalloonStatsPeriodTestWithNoVMs() throws LibvirtException {
+        Mockito.when(connMock.listDomains()).thenReturn(new int[0]);
+
+        List<Integer> result = libvirtComputingResourceSpy.getVmsToSetMemoryBalloonStatsPeriod(connMock);
+
+        Mockito.verify(loggerMock).info("Skipping the memory balloon stats period setting, since there are no VMs (active Libvirt domains) on this host.");
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void getVmsToSetMemoryBalloonStatsPeriodTestWhenSuccessfullyGetVmIds() throws LibvirtException {
+        int[] fakeList = new int[]{1};
+        List<Integer> expected = Arrays.asList(ArrayUtils.toObject(fakeList));
+        Mockito.when(connMock.listDomains()).thenReturn(fakeList);
+
+        List<Integer> result = libvirtComputingResourceSpy.getVmsToSetMemoryBalloonStatsPeriod(connMock);
+
+        Assert.assertEquals(expected, result);
+    }
+
+    @Test
+    public void getCurrentVmBalloonStatsPeriodTestWhenMemBalloonIsDisabled() {
+        Integer expected = 0;
+        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
+        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(true);
+
+        Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
+
+        Assert.assertEquals(expected, result);
+    }
+
+    @Test
+    public void getCurrentVmBalloonStatsPeriodTestWhenStatsPeriodIsZero() {
+        Integer expected = 0;
+        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
+        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(false);
+        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD))).thenReturn(0);
+
+        Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
+
+        Mockito.verify(loggerMock).info(String.format("The [%s] property is set to '0', this prevents memory statistics from being displayed correctly. "
+                + "Adjust (increase) the value of this parameter to correct this.", AgentProperties.VM_MEMBALLOON_STATS_PERIOD.getName()));
+        Assert.assertEquals(expected, result);
+    }
+
+    @Test
+    public void getCurrentVmBalloonStatsPeriodTestSuccess() {
+        Integer expected = 60;
+        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
+        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(false);
+        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD))).thenReturn(60);
+
+        Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
+
+        Assert.assertEquals(expected, result);
+    }
+
+    private void prepareMocksToSetupMemoryBalloonStatsPeriod(Integer currentVmBalloonStatsPeriod) throws LibvirtException {
+        Integer[] fakeList = ArrayUtils.toObject(new int[]{1});
+        Mockito.doReturn(Arrays.asList(fakeList)).when(libvirtComputingResourceSpy).getVmsToSetMemoryBalloonStatsPeriod(connMock);
+        Mockito.doReturn(currentVmBalloonStatsPeriod).when(libvirtComputingResourceSpy).getCurrentVmBalloonStatsPeriod();
+        Mockito.when(domainMock.getXMLDesc(Mockito.anyInt())).thenReturn("");
+        Mockito.when(domainMock.getName()).thenReturn("fake-VM-name");
+        Mockito.when(connMock.domainLookupByID(1)).thenReturn(domainMock);
+    }
+
+    @Test
+    public void setupMemoryBalloonStatsPeriodTestMemBalloonPropertyDisabled() throws LibvirtException {
+        prepareMocksToSetupMemoryBalloonStatsPeriod(0);
+        MemBalloonDef memBalloonDef = new MemBalloonDef();
+        memBalloonDef.defVirtioMemBalloon("60");
+        Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
+        Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
+        PowerMockito.mockStatic(Script.class);
+        PowerMockito.when(Script.runSimpleBashScript(Mockito.any())).thenReturn(null);
+
+        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+
+        Mockito.verify(loggerMock).debug("The memory balloon stats period [0] has been set successfully for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name].");
+    }
+
+    @Test
+    public void setupMemoryBalloonStatsPeriodTestErrorWhenSetNewPeriod() throws LibvirtException {
+        prepareMocksToSetupMemoryBalloonStatsPeriod(60);
+        MemBalloonDef memBalloonDef = new MemBalloonDef();
+        memBalloonDef.defVirtioMemBalloon("0");
+        Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
+        Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
+        PowerMockito.mockStatic(Script.class);
+        PowerMockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live"))).thenReturn("some-fake-error");
+
+        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+
+        Mockito.verify(loggerMock).error("Unable to set up memory balloon stats period for VM (Libvirt Domain) with ID [1] due to an error when running the [virsh "
+                + "dommemstat 1 --period 60 --live] command. Output: [some-fake-error].");
+    }
+
+    @Test
+    public void setupMemoryBalloonStatsPeriodTestSetNewPeriodSuccessfully() throws LibvirtException {
+        prepareMocksToSetupMemoryBalloonStatsPeriod(60);
+        MemBalloonDef memBalloonDef = new MemBalloonDef();
+        memBalloonDef.defVirtioMemBalloon("0");
+        Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
+        Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
+        PowerMockito.mockStatic(Script.class);
+        PowerMockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live"))).thenReturn(null);
+
+        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+
+        PowerMockito.verifyStatic(Script.class);
+        Script.runSimpleBashScript("virsh dommemstat 1 --period 60 --live");
+        Mockito.verify(loggerMock, Mockito.never()).error(Mockito.anyString());
+    }
+
+    @Test
+    public void setupMemoryBalloonStatsPeriodTestSkipVm() throws LibvirtException {
+        prepareMocksToSetupMemoryBalloonStatsPeriod(60);
+        MemBalloonDef memBalloonDef = new MemBalloonDef();
+        memBalloonDef.defNoneMemBalloon();
+        Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
+        Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
+
+        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+
+        Mockito.verify(loggerMock).debug("Skipping the memory balloon stats period setting for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name] because this"
+                + " VM has no memory balloon.");
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java
index ccab4b01c33..5dc8e1fcca0 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java
@@ -25,6 +25,7 @@ import java.util.List;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
 
@@ -180,6 +181,7 @@ public class LibvirtDomainXMLParserTest extends TestCase {
                      "<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>" +
                      "</video>" +
                      "<memballoon model='virtio'>" +
+                     "<stats period='60'/>" +
                      "<alias name='balloon0'/>" +
                      "<address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>" +
                      "</memballoon>" +
@@ -238,6 +240,10 @@ public class LibvirtDomainXMLParserTest extends TestCase {
             assertEquals("vnet" + i, ifs.get(i).getDevName());
         }
 
+        MemBalloonDef memBalloon = parser.getMemBalloon();
+        assertEquals(MemBalloonDef.MemBalloonModel.VIRTIO, memBalloon.getMemBalloonModel());
+        assertEquals("60", memBalloon.getMemBalloonStatsPeriod());
+
         List<RngDef> rngs = parser.getRngs();
         assertEquals("/dev/random", rngs.get(0).getPath());
         assertEquals(RngDef.RngBackendModel.RANDOM, rngs.get(0).getRngBackendModel());
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
index 9da1f5ea75b..ec59265c832 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
@@ -29,6 +29,7 @@ import junit.framework.TestCase;
 
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
 import org.apache.cloudstack.utils.linux.MemStat;
 import org.apache.cloudstack.utils.qemu.QemuObject;
@@ -358,6 +359,28 @@ public class LibvirtVMDefTest extends TestCase {
                 assertEquals(xmlDef, expectedXml);
     }
 
+    @Test
+    public void memBalloonDefTestNone() {
+        String expectedXml = "<memballoon model='none'>\n</memballoon>";
+        MemBalloonDef memBalloonDef = new MemBalloonDef();
+        memBalloonDef.defNoneMemBalloon();
+
+        String xmlDef = memBalloonDef.toString();
+
+        assertEquals(xmlDef, expectedXml);
+    }
+
+    @Test
+    public void memBalloonDefTestVirtio() {
+        String expectedXml = "<memballoon model='virtio'>\n<stats period='60'/>\n</memballoon>";
+        MemBalloonDef memBalloonDef = new MemBalloonDef();
+        memBalloonDef.defVirtioMemBalloon("60");
+
+        String xmlDef = memBalloonDef.toString();
+
+        assertEquals(xmlDef, expectedXml);
+    }
+
     @Test
     public void testHypervEnlightDef() {
         LibvirtVMDef.FeaturesDef featuresDef = new LibvirtVMDef.FeaturesDef();