Skip to content

Commit c496c84

Browse files
widoGabrielBrascher
authored andcommitted
kvm: Properly report available memory to Management Server (apache#2795)
The KVM Agent had two mechanisms for reporting its capabilities and memory to the Management Server. On startup it would ask libvirt the amount of Memory the Host has and subtract and add the reserved and overcommit memory. When the HostStats were however reported to the Management Server these two configured values on the Agent were no longer reported in the statistics thus showing all the available memory in the Agent/Host to the Management Server. This commit unifies this by using the same logic on Agent Startup and during statistics reporting. memory=3069636608, reservedMemory=1073741824 This was reported by a 4GB Hypervisor with this setting: host.reserved.mem.mb=1024 The GUI (thus API) would then show: Memory Total 2.86 GB This way the Agent properly 'lies' to the Management Server about its capabilities in terms of Memory. This is very helpful if you want to overprovision or undercommit machines for various reasons. Overcommitting can be done when KSM or ZSwap or a fast SWAP device is installed in the machine. Underprovisioning is done when the Host might run other tasks then a KVM hypervisor, for example when it runs in a hyperconverged setup with Ceph. In addition internally many values have been changed from a Double to a Long and also store the amount of bytes instead of Kilobytes. Signed-off-by: Wido den Hollander <[email protected]>
1 parent 323f791 commit c496c84

File tree

7 files changed

+233
-107
lines changed

7 files changed

+233
-107
lines changed

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

+8-74
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import java.io.BufferedReader;
2020
import java.io.File;
2121
import java.io.FileNotFoundException;
22-
import java.io.FileReader;
2322
import java.io.IOException;
24-
import java.io.Reader;
2523
import java.io.StringReader;
2624
import java.net.InetAddress;
2725
import java.net.URI;
@@ -55,12 +53,12 @@
5553
import org.apache.cloudstack.storage.to.VolumeObjectTO;
5654
import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
5755
import org.apache.cloudstack.utils.linux.CPUStat;
56+
import org.apache.cloudstack.utils.linux.KVMHostInfo;
5857
import org.apache.cloudstack.utils.linux.MemStat;
5958
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
6059
import org.apache.cloudstack.utils.security.KeyStoreUtils;
6160
import org.apache.commons.collections.MapUtils;
6261
import org.apache.commons.io.FileUtils;
63-
import org.apache.commons.io.IOUtils;
6462
import org.apache.commons.lang.ArrayUtils;
6563
import org.apache.commons.lang.math.NumberUtils;
6664
import org.apache.log4j.Logger;
@@ -311,8 +309,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
311309
protected int _cmdsTimeout;
312310
protected int _stopTimeout;
313311
protected CPUStat _cpuStat = new CPUStat();
314-
protected MemStat _memStat = new MemStat();
315-
312+
protected MemStat _memStat = new MemStat(_dom0MinMem, _dom0OvercommitMem);
316313
private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new LibvirtUtilitiesHelper();
317314

318315
@Override
@@ -871,7 +868,7 @@ public boolean configure(final String name, final Map<String, Object> params) th
871868

872869
value = (String)params.get("host.reserved.mem.mb");
873870
// Reserve 1GB unless admin overrides
874-
_dom0MinMem = NumbersUtil.parseInt(value, 1024) * 1024 * 1024L;
871+
_dom0MinMem = NumbersUtil.parseInt(value, 1024) * 1024* 1024L;
875872

876873
value = (String)params.get("host.overcommit.mem.mb");
877874
// Support overcommit memory for host if host uses ZSWAP, KSM and other memory
@@ -2661,12 +2658,14 @@ private Map<String, String> getVersionStrings() {
26612658
@Override
26622659
public StartupCommand[] initialize() {
26632660

2664-
final List<Object> info = getHostInfo();
2661+
final KVMHostInfo info = new KVMHostInfo(_dom0MinMem, _dom0OvercommitMem);
2662+
2663+
final String capabilities = String.join(",", info.getCapabilities());
26652664

26662665
final StartupRoutingCommand cmd =
2667-
new StartupRoutingCommand((Integer)info.get(0), (Long)info.get(1), (Long)info.get(2), (Long)info.get(4), (String)info.get(3), _hypervisorType,
2666+
new StartupRoutingCommand(info.getCpus(), info.getCpuSpeed(), info.getTotalMemory(), info.getReservedMemory(), capabilities, _hypervisorType,
26682667
RouterPrivateIpStrategy.HostLocal);
2669-
cmd.setCpuSockets((Integer)info.get(5));
2668+
cmd.setCpuSockets(info.getCpuSockets());
26702669
fillNetworkInformation(cmd);
26712670
_privateIp = cmd.getPrivateIpAddress();
26722671
cmd.getHostDetails().putAll(getVersionStrings());
@@ -2886,71 +2885,6 @@ private HashMap<String, HostVmStateReportEntry> getHostVmStateReport(final Conne
28862885
return vmStates;
28872886
}
28882887

2889-
protected List<Object> getHostInfo() {
2890-
final ArrayList<Object> info = new ArrayList<Object>();
2891-
long speed = 0;
2892-
long cpus = 0;
2893-
long ram = 0;
2894-
int cpuSockets = 0;
2895-
String cap = null;
2896-
try {
2897-
final Connect conn = LibvirtConnection.getConnection();
2898-
final NodeInfo hosts = conn.nodeInfo();
2899-
speed = getCpuSpeed(hosts);
2900-
2901-
/*
2902-
* Some CPUs report a single socket and multiple NUMA cells.
2903-
* We need to multiply them to get the correct socket count.
2904-
*/
2905-
cpuSockets = hosts.sockets;
2906-
if (hosts.nodes > 0) {
2907-
cpuSockets = hosts.sockets * hosts.nodes;
2908-
}
2909-
cpus = hosts.cpus;
2910-
ram = hosts.memory * 1024L;
2911-
final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
2912-
parser.parseCapabilitiesXML(conn.getCapabilities());
2913-
final ArrayList<String> oss = parser.getGuestOsType();
2914-
for (final String s : oss) {
2915-
/*
2916-
* Even host supports guest os type more than hvm, we only
2917-
* report hvm to management server
2918-
*/
2919-
if (s.equalsIgnoreCase("hvm")) {
2920-
cap = "hvm";
2921-
}
2922-
}
2923-
} catch (final LibvirtException e) {
2924-
s_logger.trace("Ignoring libvirt error.", e);
2925-
}
2926-
2927-
if (isSnapshotSupported()) {
2928-
cap = cap + ",snapshot";
2929-
}
2930-
2931-
info.add((int)cpus);
2932-
info.add(speed);
2933-
// Report system's RAM as actual RAM minus host OS reserved RAM
2934-
ram = ram - _dom0MinMem + _dom0OvercommitMem;
2935-
info.add(ram);
2936-
info.add(cap);
2937-
info.add(_dom0MinMem);
2938-
info.add(cpuSockets);
2939-
s_logger.debug("cpus=" + cpus + ", speed=" + speed + ", ram=" + ram + ", _dom0MinMem=" + _dom0MinMem + ", _dom0OvercommitMem=" + _dom0OvercommitMem + ", cpu sockets=" + cpuSockets);
2940-
2941-
return info;
2942-
}
2943-
2944-
protected static long getCpuSpeed(final NodeInfo nodeInfo) {
2945-
try (final Reader reader = new FileReader(
2946-
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")) {
2947-
return Long.parseLong(IOUtils.toString(reader).trim()) / 1000;
2948-
} catch (IOException | NumberFormatException e) {
2949-
s_logger.warn("Could not read cpuinfo_max_freq");
2950-
return nodeInfo.mhz;
2951-
}
2952-
}
2953-
29542888
public String rebootVM(final Connect conn, final String vmName) {
29552889
Domain dm = null;
29562890
String msg = null;

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetHostStatsCommandWrapper.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,10 @@ public Answer execute(final GetHostStatsCommand command, final LibvirtComputingR
4242
MemStat memStat = libvirtComputingResource.getMemStat();
4343

4444
final double cpuUtil = cpuStat.getCpuUsedPercent();
45-
memStat.refresh();
46-
double totMem = memStat.getTotal();
47-
double freeMem = memStat.getAvailable();
4845

4946
final Pair<Double, Double> nicStats = libvirtComputingResource.getNicStats(libvirtComputingResource.getPublicBridgeName());
5047

51-
final HostStatsEntry hostStats = new HostStatsEntry(command.getHostId(), cpuUtil, nicStats.first() / 1024, nicStats.second() / 1024, "host", totMem, freeMem, 0, 0);
48+
final HostStatsEntry hostStats = new HostStatsEntry(command.getHostId(), cpuUtil, nicStats.first() / 1024, nicStats.second() / 1024, "host", memStat.getTotal() / 1024, memStat.getAvailable() / 1024, 0, 0);
5249
return new GetHostStatsAnswer(command, hostStats);
5350
}
5451
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.utils.linux;
18+
19+
import com.cloud.hypervisor.kvm.resource.LibvirtCapXMLParser;
20+
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
21+
import org.apache.commons.io.IOUtils;
22+
import org.apache.log4j.Logger;
23+
import org.libvirt.Connect;
24+
import org.libvirt.LibvirtException;
25+
import org.libvirt.NodeInfo;
26+
27+
import java.io.FileReader;
28+
import java.io.IOException;
29+
import java.io.Reader;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
public class KVMHostInfo {
34+
35+
private static final Logger LOGGER = Logger.getLogger(KVMHostInfo.class);
36+
37+
private int cpus;
38+
private int cpusockets;
39+
private long cpuSpeed;
40+
private long totalMemory;
41+
private long reservedMemory;
42+
private long overCommitMemory;
43+
private List<String> capabilities = new ArrayList<>();
44+
45+
public KVMHostInfo(long reservedMemory, long overCommitMemory) {
46+
this.reservedMemory = reservedMemory;
47+
this.overCommitMemory = overCommitMemory;
48+
this.getHostInfoFromLibvirt();
49+
this.totalMemory = new MemStat(this.getReservedMemory(), this.getOverCommitMemory()).getTotal();
50+
}
51+
52+
public int getCpus() {
53+
return this.cpus;
54+
}
55+
56+
public int getCpuSockets() {
57+
return this.cpusockets;
58+
}
59+
60+
public long getCpuSpeed() {
61+
return this.cpuSpeed;
62+
}
63+
64+
public long getTotalMemory() {
65+
return this.totalMemory;
66+
}
67+
68+
public long getReservedMemory() {
69+
return this.reservedMemory;
70+
}
71+
72+
public long getOverCommitMemory() {
73+
return this.overCommitMemory;
74+
}
75+
76+
public List<String> getCapabilities() {
77+
return this.capabilities;
78+
}
79+
80+
protected static long getCpuSpeed(final NodeInfo nodeInfo) {
81+
try (final Reader reader = new FileReader(
82+
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")) {
83+
return Long.parseLong(IOUtils.toString(reader).trim()) / 1000;
84+
} catch (IOException | NumberFormatException e) {
85+
LOGGER.info("Could not read cpuinfo_max_freq, falling back on libvirt");
86+
return nodeInfo.mhz;
87+
}
88+
}
89+
90+
private void getHostInfoFromLibvirt() {
91+
try {
92+
final Connect conn = LibvirtConnection.getConnection();
93+
final NodeInfo hosts = conn.nodeInfo();
94+
this.cpuSpeed = getCpuSpeed(hosts);
95+
96+
/*
97+
* Some CPUs report a single socket and multiple NUMA cells.
98+
* We need to multiply them to get the correct socket count.
99+
*/
100+
this.cpusockets = hosts.sockets;
101+
if (hosts.nodes > 0) {
102+
this.cpusockets = hosts.sockets * hosts.nodes;
103+
}
104+
this.cpus = hosts.cpus;
105+
106+
final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
107+
parser.parseCapabilitiesXML(conn.getCapabilities());
108+
final ArrayList<String> oss = parser.getGuestOsType();
109+
for (final String s : oss) {
110+
/*
111+
* Even host supports guest os type more than hvm, we only
112+
* report hvm to management server
113+
*/
114+
String hvmCapability = "hvm";
115+
if (s.equalsIgnoreCase(hvmCapability)) {
116+
if (!this.capabilities.contains(hvmCapability)) {
117+
this.capabilities.add(hvmCapability);
118+
}
119+
}
120+
}
121+
122+
/*
123+
Any modern Qemu/KVM supports snapshots
124+
We used to check if this was supported, but that is no longer required
125+
*/
126+
this.capabilities.add("snapshot");
127+
conn.close();
128+
} catch (final LibvirtException e) {
129+
LOGGER.error("Caught libvirt exception while fetching host information", e);
130+
}
131+
}
132+
}

plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/MemStat.java

+25-8
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,47 @@
2222
import java.util.Map;
2323
import java.util.Scanner;
2424

25+
2526
public class MemStat {
27+
/*
28+
Gather Memory Statistics of the current node by opening /proc/meminfo
29+
which contains the memory information in KiloBytes.
30+
31+
Convert this all to bytes and return Long as a type with the information
32+
in bytes
33+
*/
2634
protected final static String MEMINFO_FILE = "/proc/meminfo";
2735
protected final static String FREE_KEY = "MemFree";
2836
protected final static String CACHE_KEY = "Cached";
2937
protected final static String TOTAL_KEY = "MemTotal";
38+
long reservedMemory;
39+
long overCommitMemory;
3040

31-
private final Map<String, Double> _memStats = new HashMap<String, Double>();
41+
private final Map<String, Long> _memStats = new HashMap<>();
3242

3343
public MemStat() {
44+
this(0,0);
45+
}
46+
47+
public MemStat(long reservedMemory, long overCommitMemory) {
48+
this.reservedMemory = reservedMemory;
49+
this.overCommitMemory = overCommitMemory;
50+
this.refresh();
3451
}
3552

36-
public Double getTotal() {
37-
return _memStats.get(TOTAL_KEY);
53+
public long getTotal() {
54+
return _memStats.get(TOTAL_KEY) - reservedMemory + overCommitMemory;
3855
}
3956

40-
public Double getAvailable() {
57+
public long getAvailable() {
4158
return getFree() + getCache();
4259
}
4360

44-
public Double getFree() {
45-
return _memStats.get(FREE_KEY);
61+
public long getFree() {
62+
return _memStats.get(FREE_KEY) - reservedMemory + overCommitMemory;
4663
}
4764

48-
public Double getCache() {
65+
public long getCache() {
4966
return _memStats.get(CACHE_KEY);
5067
}
5168

@@ -63,7 +80,7 @@ protected void parseFromScanner(Scanner scanner) {
6380
while(scanner.hasNext()) {
6481
String[] stats = scanner.next().split("\\:\\s+");
6582
if (stats.length == 2) {
66-
_memStats.put(stats[0], Double.valueOf(stats[1].replaceAll("\\s+\\w+","")));
83+
_memStats.put(stats[0], Long.valueOf(stats[1].replaceAll("\\s+\\w+","")) * 1024L);
6784
}
6885
}
6986
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -534,13 +534,6 @@ public List<DiskDef> getDisks(final Connect conn, final String vmName) {
534534
Assert.assertTrue(vmStat.getTargetMemoryKBs() >= vmStat.getMemoryKBs());
535535
}
536536

537-
@Test
538-
public void getCpuSpeed() {
539-
Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
540-
final NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
541-
LibvirtComputingResource.getCpuSpeed(nodeInfo);
542-
}
543-
544537
/*
545538
* New Tests
546539
*/
@@ -988,8 +981,8 @@ public void testGetHostStatsCommand() {
988981
when(libvirtComputingResource.getMemStat()).thenReturn(memStat);
989982
when(libvirtComputingResource.getNicStats(Mockito.anyString())).thenReturn(new Pair<Double, Double>(1.0d, 1.0d));
990983
when(cpuStat.getCpuUsedPercent()).thenReturn(0.5d);
991-
when(memStat.getAvailable()).thenReturn(1500.5d);
992-
when(memStat.getTotal()).thenReturn(15000d);
984+
when(memStat.getAvailable()).thenReturn(1500L);
985+
when(memStat.getTotal()).thenReturn(15000L);
993986

994987
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
995988
assertNotNull(wrapper);

0 commit comments

Comments
 (0)