Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/host/Host.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public static String[] toStrings(Host.Type... types) {
String HOST_VIRTV2V_INPLACE_SUPPORT = "host.virtv2v.inplace.support";
String HOST_QEMU_RBD_SUPPORT = "host.qemu.rbd.support";
String HOST_SSH_PORT = "host.ssh.port";
String HOST_CPU_MAX_SPEED_MHZ = "host.cpu.max.speed.mhz";
String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count";
String GUEST_OS_CATEGORY_ID = "guest.os.category.id";
String GUEST_OS_RULE = "guest.os.rule";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.cloud.hypervisor.kvm.resource;

import static com.cloud.host.Host.HOST_CDROM_MAX_COUNT;
import static com.cloud.host.Host.HOST_CPU_MAX_SPEED_MHZ;
import static com.cloud.host.Host.HOST_INSTANCE_CONVERSION;
import static com.cloud.host.Host.HOST_KVM_DISK_ONLY_VM_SNAPSHOT_NVRAM;
import static com.cloud.host.Host.HOST_OVFTOOL_VERSION;
Expand Down Expand Up @@ -4449,6 +4450,9 @@ public StartupCommand[] initialize() {
new StartupRoutingCommand(info.getAllocatableCpus(), info.getCpuSpeed(), info.getTotalMemory(), info.getReservedMemory(), capabilities, hypervisorType,
RouterPrivateIpStrategy.HostLocal);
cmd.setCpuSockets(info.getCpuSockets());
if (info.getCpuMaxSpeed() > 0) {
cmd.getHostDetails().put(HOST_CPU_MAX_SPEED_MHZ, String.valueOf(info.getCpuMaxSpeed()));
}
fillNetworkInformation(cmd);
privateIp = cmd.getPrivateIpAddress();
cmd.getHostDetails().putAll(getVersionStrings());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
Expand All @@ -43,6 +45,7 @@

import com.cloud.hypervisor.kvm.resource.LibvirtCapXMLParser;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.script.Script;

public class KVMHostInfo {
Expand All @@ -53,13 +56,20 @@ public class KVMHostInfo {
private int allocatableCpus;
private int cpusockets;
private long cpuSpeed;
private long cpuMaxSpeed;
private String cpuArch;
private long totalMemory;
private long reservedMemory;
private long overCommitMemory;
private List<String> capabilities = new ArrayList<>();
private static String cpuArchRetrieveExecutable = "arch";
private static List<String> cpuInfoFreqFileNames = List.of("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency","/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");
private static final String LSCPU_CPU_INFO_COMMAND = "lscpu | grep -iE 'Model name|CPU max MHz|CPU dynamic MHz'";
private static final String LSCPU_MODEL_NAME_FIELD = "Model name";
private static final String LSCPU_CPU_MAX_MHZ_FIELD = "CPU max MHz";
private static final String LSCPU_CPU_DYNAMIC_MHZ_FIELD = "CPU dynamic MHz";
private static final Pattern LSCPU_MODEL_NAME_FREQUENCY_PATTERN = Pattern.compile("([0-9]\\.[0-9]+)GHz");
private static final int MHZ_PER_GHZ = 1000;
private static final String cpuArchRetrieveExecutable = "arch";
private static final List<String> cpuInfoFreqFileNames = List.of("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency","/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");

public KVMHostInfo(long reservedMemory, long overCommitMemory, long manualSpeed, int reservedCpus) {
this.cpuSpeed = manualSpeed;
Expand Down Expand Up @@ -90,6 +100,14 @@ public long getCpuSpeed() {
return this.cpuSpeed;
}

/**
* @return the maximum frequency of the CPU, in MHz, which may include the turbo frequency; or 0 if it could not be retrieved. This value is only
* informative; {@link #getCpuSpeed()} is the one used to calculate the capacity of the host.
*/
public long getCpuMaxSpeed() {
return this.cpuMaxSpeed;
}

public long getTotalMemory() {
return this.totalMemory;
}
Expand All @@ -114,59 +132,100 @@ public static boolean isHostS390x() {
return "s390x".equals(System.getProperty("os.arch"));
}

protected static long getCpuSpeed(final String cpabilities, final NodeInfo nodeInfo) {
long speed = 0L;
speed = getCpuSpeedFromCommandLscpu();
if(speed > 0L) {
protected static long getCpuSpeed(final String lscpuOutput, final String capabilities, final NodeInfo nodeInfo) {
long speed = getBaseCpuSpeedFromLscpuOutput(lscpuOutput);
if (speed > 0L) {
return speed;
}

speed = getCpuSpeedFromFile();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the amd-pstate driver isnt scaling_max_freq the turbo speed too? looks like newer epyc hosts would still end up with the turbo value here

if(speed > 0L) {
if (speed > 0L) {
return speed;
}

speed = getCpuSpeedFromHostCapabilities(cpabilities);
if(speed > 0L) {
speed = getCpuMaxSpeedFromLscpuOutput(lscpuOutput);
if (speed > 0L) {
return speed;
}

LOGGER.info(String.format("Using the value [%s] provided by Libvirt.", nodeInfo.mhz));
speed = nodeInfo.mhz;
return speed;
speed = getCpuSpeedFromHostCapabilities(capabilities);
if (speed > 0L) {
return speed;
}

LOGGER.info("Using the value [{}] provided by Libvirt.", nodeInfo.mhz);
return nodeInfo.mhz;
}

private static long getCpuSpeedFromCommandLscpu() {
long speed = 0L;
LOGGER.info("Fetching CPU speed from command \"lscpu\".");
try {
String command = "lscpu | grep -i 'CPU max MHz' | head -n 1 | sed 's/^.*: //' | xargs";
if(isHostS390x()) {
command = "lscpu | grep 'CPU dynamic MHz' | cut -d ':' -f 2 | tr -d ' ' | awk '{printf \"%.1f\\n\", $1 / 1000}'";
}
String result = Script.runSimpleBashScript(command);
speed = (long) (Float.parseFloat(result));
LOGGER.info(String.format("Command [%s] resulted in the value [%s] for CPU speed.", command, speed));
/**
* Retrieves the base CPU speed from the output of the {@code lscpu} command. The frequency advertised in the model name is preferred because the CPU speed
* is used to calculate the capacity of the host and the CPU quota of the VMs with CPU cap enabled; thus, it must be a frequency that the CPU is able to
* sustain. On s390x, the "CPU dynamic MHz" value is used instead.
*
* @return the base CPU speed, in MHz, or 0 if it could not be retrieved.
*/
protected static long getBaseCpuSpeedFromLscpuOutput(final String lscpuOutput) {
return getBaseCpuSpeedFromLscpuOutput(lscpuOutput, isHostS390x());
}

/**
* @param lscpuOutput the output of the {@code lscpu} command.
* @param isS390x whether the host CPU architecture is s390x; on s390x the base speed is read from the "CPU dynamic MHz" field.
* @return the base CPU speed, in MHz, or 0 if it could not be retrieved.
*/
protected static long getBaseCpuSpeedFromLscpuOutput(final String lscpuOutput, final boolean isS390x) {
if (isS390x) {
final long speed = (long) NumbersUtil.parseFloat(getLscpuFieldValue(lscpuOutput, LSCPU_CPU_DYNAMIC_MHZ_FIELD), 0f);
logCpuSpeedFromLscpuField(LSCPU_CPU_DYNAMIC_MHZ_FIELD, speed);
return speed;
} catch (NullPointerException | NumberFormatException e) {
LOGGER.error(String.format("Unable to retrieve the CPU speed from lscpu."), e);
}
try {
String command = "lscpu | grep -i 'Model name' | head -n 1 | egrep -o '[[:digit:]].[[:digit:]]+GHz' | sed 's/GHz//g'";
if(isHostS390x()) {
command = "lscpu | grep 'CPU dynamic MHz' | cut -d ':' -f 2 | tr -d ' ' | awk '{printf \"%.1f\\n\", $1 / 1000}'";
}
String result = Script.runSimpleBashScript(command);
speed = (long) (Float.parseFloat(result) * 1000);
LOGGER.info(String.format("Command [%s] resulted in the value [%s] for CPU speed.", command, speed));
final String modelName = getLscpuFieldValue(lscpuOutput, LSCPU_MODEL_NAME_FIELD);
final Matcher matcher = LSCPU_MODEL_NAME_FREQUENCY_PATTERN.matcher(StringUtils.defaultString(modelName));
if (matcher.find()) {
final long speed = (long) (NumbersUtil.parseFloat(matcher.group(1), 0f) * MHZ_PER_GHZ);
logCpuSpeedFromLscpuField(LSCPU_MODEL_NAME_FIELD, speed);
return speed;
} catch (NullPointerException | NumberFormatException e) {
LOGGER.error(String.format("Unable to retrieve the CPU speed from lscpu."), e);
}
logCpuSpeedFromLscpuField(LSCPU_MODEL_NAME_FIELD, 0L);
return 0L;
}

/**
* @return the maximum frequency of the CPU, in MHz, from the output of the {@code lscpu} command; or 0 if it could not be retrieved. This value may
* include the turbo frequency; it is only informative.
*/
protected static long getCpuMaxSpeedFromLscpuOutput(final String lscpuOutput) {
final long speed = (long) NumbersUtil.parseFloat(getLscpuFieldValue(lscpuOutput, LSCPU_CPU_MAX_MHZ_FIELD), 0f);
logCpuSpeedFromLscpuField(LSCPU_CPU_MAX_MHZ_FIELD, speed);
return speed;
}

private static long getCpuSpeedFromFile() {
private static void logCpuSpeedFromLscpuField(final String field, final long speed) {
if (speed > 0L) {
LOGGER.info("Retrieved the CPU speed [{}] MHz from the \"{}\" field of the lscpu command output.", speed, field);
} else {
LOGGER.debug("Unable to retrieve the CPU speed from the \"{}\" field of the lscpu command output.", field);
}
}

private static String getLscpuFieldValue(final String lscpuOutput, final String field) {
if (StringUtils.isBlank(lscpuOutput)) {
return null;
}
for (final String line : lscpuOutput.split("\n")) {
if (StringUtils.startsWithIgnoreCase(line.trim(), field)) {
return StringUtils.substringAfter(line, ":").trim();
}
}
return null;
}

private static String getLscpuOutput() {
LOGGER.info("Fetching CPU speed from command \"lscpu\".");
return Script.executeCommand(LSCPU_CPU_INFO_COMMAND).first();
}

protected static long getCpuSpeedFromFile() {
for (final String cpuInfoFreqFileName: cpuInfoFreqFileNames) {
LOGGER.info(String.format("Fetching CPU speed from file [%s].", cpuInfoFreqFileName));
try (Reader reader = new FileReader(cpuInfoFreqFileName)) {
Expand Down Expand Up @@ -213,11 +272,13 @@ private void getHostInfoFromLibvirt() {
final Connect conn = LibvirtConnection.getConnection();
final NodeInfo hosts = conn.nodeInfo();
final String capabilities = conn.getCapabilities();
final String lscpuOutput = getLscpuOutput();
if (this.cpuSpeed == 0) {
this.cpuSpeed = getCpuSpeed(capabilities, hosts);
this.cpuSpeed = getCpuSpeed(lscpuOutput, capabilities, hosts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens to existing vms on amd hosts after the upgrade? if their offering speed is above the new lower host speed they wont fit on that host anymore when they restart or migrate

} else {
LOGGER.debug(String.format("Using existing configured CPU frequency %s", this.cpuSpeed));
}
this.cpuMaxSpeed = getCpuMaxSpeedFromLscpuOutput(lscpuOutput);

/*
* Some CPUs report a single socket and multiple NUMA cells.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@

@RunWith(MockitoJUnitRunner.class)
public class KVMHostInfoTest {
private static final String CAPABILITIES_WITH_TSC_FREQUENCY = "<host>\n" +
"<uuid>8a330742-345f-b0df-7954-c9960b88116c</uuid>\n" +
" <cpu>\n" +
" <arch>x86_64</arch>\n" +
" <model>Opteron_G2</model>\n" +
" <vendor>AMD</vendor>\n" +
" <counter name='tsc' frequency='2350000000' scaling='no'/>\n" +
" </cpu>\n" +
"</host>\n";
private static final String CAPABILITIES_WITHOUT_TSC_FREQUENCY = "<host>\n" +
" <cpu>\n" +
" <vendor>AMD</vendor>\n" +
" </cpu>\n" +
"</host>\n";
private static final String LSCPU_OUTPUT_WITH_MODEL_NAME_FREQUENCY = "Model name: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz\nCPU max MHz: 3400.0000";
private static final String LSCPU_OUTPUT_WITHOUT_MODEL_NAME_FREQUENCY = "Model name: AMD EPYC 7742 64-Core Processor\nCPU max MHz: 3400.0000";
private static final String LSCPU_OUTPUT_WITHOUT_FREQUENCY = "Model name: AMD EPYC 7742 64-Core Processor";
private static final String LSCPU_OUTPUT_WITH_CPU_DYNAMIC_MHZ = "Model name: IBM/S390\nCPU dynamic MHz: 5000.0000";

@Test
public void getCpuSpeed() {
if (!System.getProperty("os.name").equals("Linux")) {
Expand All @@ -39,21 +58,12 @@ public void getCpuSpeed() {
Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
nodeInfo.mhz = 1000;
Assert.assertTrue(KVMHostInfo.getCpuSpeed(null, nodeInfo) > 0L);
Assert.assertTrue(KVMHostInfo.getCpuSpeed(null, null, nodeInfo) > 0L);
}

@Test
public void getCpuSpeedFromHostCapabilities() {
String capabilities = "<host>\n" +
"<uuid>8a330742-345f-b0df-7954-c9960b88116c</uuid>\n" +
" <cpu>\n" +
" <arch>x86_64</arch>\n" +
" <model>Opteron_G2</model>\n" +
" <vendor>AMD</vendor>\n" +
" <counter name='tsc' frequency='2350000000' scaling='no'/>\n" +
" </cpu>\n" +
"</host>\n";
Assert.assertEquals(2350L, KVMHostInfo.getCpuSpeedFromHostCapabilities(capabilities));
Assert.assertEquals(2350L, KVMHostInfo.getCpuSpeedFromHostCapabilities(CAPABILITIES_WITH_TSC_FREQUENCY));
}

@Test
Expand Down Expand Up @@ -102,4 +112,79 @@ public void reservedCpuCoresTest() throws Exception {
Assert.assertEquals("Misconfigured/too large CPU reserve", 0, kvmHostInfo.getAllocatableCpus());
}
}

@Test
public void getBaseCpuSpeedFromLscpuOutputPrefersModelNameFrequency() {
Assert.assertEquals(2400L, KVMHostInfo.getBaseCpuSpeedFromLscpuOutput(LSCPU_OUTPUT_WITH_MODEL_NAME_FREQUENCY, false));
}

@Test
public void getBaseCpuSpeedFromLscpuOutputReturnsZeroWhenModelNameHasNoFrequency() {
Assert.assertEquals(0L, KVMHostInfo.getBaseCpuSpeedFromLscpuOutput(LSCPU_OUTPUT_WITHOUT_MODEL_NAME_FREQUENCY, false));
}

@Test
public void getBaseCpuSpeedFromLscpuOutputUsesCpuDynamicMhzForS390x() {
Assert.assertEquals(5000L, KVMHostInfo.getBaseCpuSpeedFromLscpuOutput(LSCPU_OUTPUT_WITH_CPU_DYNAMIC_MHZ, true));
}

@Test
public void getCpuMaxSpeedFromLscpuOutputReturnsCpuMaxMhz() {
Assert.assertEquals(3400L, KVMHostInfo.getCpuMaxSpeedFromLscpuOutput(LSCPU_OUTPUT_WITH_MODEL_NAME_FREQUENCY));
}

@Test
public void getCpuMaxSpeedFromLscpuOutputReturnsZeroWhenLscpuHasNoCpuMaxMhz() {
Assert.assertEquals(0L, KVMHostInfo.getCpuMaxSpeedFromLscpuOutput(LSCPU_OUTPUT_WITHOUT_FREQUENCY));
}

@Test
public void getCpuSpeedPrefersBaseCpuSpeedFromLscpu() {
try (MockedStatic<KVMHostInfo> mocked = Mockito.mockStatic(KVMHostInfo.class, Mockito.CALLS_REAL_METHODS)) {
mocked.when(KVMHostInfo::isHostS390x).thenReturn(false);
Assert.assertEquals(2400L, KVMHostInfo.getCpuSpeed(LSCPU_OUTPUT_WITH_MODEL_NAME_FREQUENCY, CAPABILITIES_WITH_TSC_FREQUENCY, nodeInfoWithMhz(1000)));
}
}

@Test
public void getCpuSpeedPrefersCpuFrequencyFileOverLscpuMaxMhz() {
try (MockedStatic<KVMHostInfo> mocked = Mockito.mockStatic(KVMHostInfo.class, Mockito.CALLS_REAL_METHODS)) {
mocked.when(KVMHostInfo::isHostS390x).thenReturn(false);
mocked.when(KVMHostInfo::getCpuSpeedFromFile).thenReturn(2500L);
Assert.assertEquals(2500L, KVMHostInfo.getCpuSpeed(LSCPU_OUTPUT_WITHOUT_MODEL_NAME_FREQUENCY, CAPABILITIES_WITH_TSC_FREQUENCY, nodeInfoWithMhz(1000)));
}
}

@Test
public void getCpuSpeedFallsBackToLscpuMaxMhzWhenCpuFrequencyFileIsNotAvailable() {
try (MockedStatic<KVMHostInfo> mocked = Mockito.mockStatic(KVMHostInfo.class, Mockito.CALLS_REAL_METHODS)) {
mocked.when(KVMHostInfo::isHostS390x).thenReturn(false);
mocked.when(KVMHostInfo::getCpuSpeedFromFile).thenReturn(0L);
Assert.assertEquals(3400L, KVMHostInfo.getCpuSpeed(LSCPU_OUTPUT_WITHOUT_MODEL_NAME_FREQUENCY, CAPABILITIES_WITH_TSC_FREQUENCY, nodeInfoWithMhz(1000)));
}
}

@Test
public void getCpuSpeedFallsBackToHostCapabilities() {
try (MockedStatic<KVMHostInfo> mocked = Mockito.mockStatic(KVMHostInfo.class, Mockito.CALLS_REAL_METHODS)) {
mocked.when(KVMHostInfo::isHostS390x).thenReturn(false);
mocked.when(KVMHostInfo::getCpuSpeedFromFile).thenReturn(0L);
Assert.assertEquals(2350L, KVMHostInfo.getCpuSpeed(LSCPU_OUTPUT_WITHOUT_FREQUENCY, CAPABILITIES_WITH_TSC_FREQUENCY, nodeInfoWithMhz(1000)));
}
}

@Test
public void getCpuSpeedFallsBackToLibvirt() {
try (MockedStatic<KVMHostInfo> mocked = Mockito.mockStatic(KVMHostInfo.class, Mockito.CALLS_REAL_METHODS)) {
mocked.when(KVMHostInfo::isHostS390x).thenReturn(false);
mocked.when(KVMHostInfo::getCpuSpeedFromFile).thenReturn(0L);
Assert.assertEquals(1000L, KVMHostInfo.getCpuSpeed(LSCPU_OUTPUT_WITHOUT_FREQUENCY, CAPABILITIES_WITHOUT_TSC_FREQUENCY, nodeInfoWithMhz(1000)));
}
}

private static NodeInfo nodeInfoWithMhz(final int mhz) {
final NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
nodeInfo.mhz = mhz;
return nodeInfo;
}
}
1 change: 1 addition & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,7 @@
"label.home": "Home",
"label.host": "IP address",
"label.host.alerts": "Hosts in alert state",
"label.host.cpu.max.speed": "CPU maximum frequency (turbo)",
"label.host.name": "Host name",
"label.host.ovftool.version": "OVFTool Version",
"label.host.qemu.img.version": "QEMU Image Version",
Expand Down
3 changes: 3 additions & 0 deletions ui/src/components/view/InfoCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@
{{ resource.arch }}
</a-tag>
</div>
<div v-if="resource.details && resource.details['host.cpu.max.speed.mhz']">
{{ $t('label.host.cpu.max.speed') }}: {{ (resource.details['host.cpu.max.speed.mhz'] / 1000.0).toFixed(2) }} GHz
</div>
<div>
<span v-if="resource.cpuused">
<a-progress
Expand Down
Loading