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 @@ -64,6 +64,7 @@ public static String[] toStrings(Host.Type... types) {
String HOST_OVFTOOL_VERSION = "host.ovftool.version";
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
String HOST_SSH_PORT = "host.ssh.port";
String HOST_CPU_MAX_SPEED_MHZ = "host.cpu.max.speed.mhz";

int DEFAULT_SSH_PORT = 22;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package com.cloud.hypervisor.kvm.resource;

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 @@ -4262,6 +4263,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 @@ -53,11 +53,13 @@ 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 final String LSCPU_CPU_MAX_MHZ_COMMAND = "lscpu | grep -i 'CPU max MHz' | head -n 1 | sed 's/^.*: //' | xargs";
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");

Expand Down Expand Up @@ -90,6 +92,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 Down Expand Up @@ -136,25 +146,30 @@ protected static long getCpuSpeed(final String cpabilities, final NodeInfo nodeI
return speed;
}

private static long getCpuSpeedFromCommandLscpu() {
/**
* Retrieves the CPU speed, in MHz, from the "lscpu" command. The base 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.
* "CPU max MHz" may include the turbo frequency; therefore, it is only used when the model name does not advertise a frequency.
*/
protected 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";
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));
speed = (long) (Float.parseFloat(result) * 1000);
LOGGER.info(String.format("Command [%s] resulted in the value [%s] for CPU speed.", command, 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 command = LSCPU_CPU_MAX_MHZ_COMMAND;
String result = Script.runSimpleBashScript(command);
speed = (long) (Float.parseFloat(result) * 1000);
speed = (long) (Float.parseFloat(result));
LOGGER.info(String.format("Command [%s] resulted in the value [%s] for CPU speed.", command, speed));
return speed;
} catch (NullPointerException | NumberFormatException e) {
Expand All @@ -163,6 +178,16 @@ private static long getCpuSpeedFromCommandLscpu() {
return speed;
}

protected static long getCpuMaxSpeedFromCommandLscpu() {
String result = Script.runSimpleBashScript(LSCPU_CPU_MAX_MHZ_COMMAND);
try {
return (long) Float.parseFloat(result);
} catch (NullPointerException | NumberFormatException e) {
LOGGER.debug(String.format("Unable to retrieve the CPU max speed from lscpu; command [%s] resulted in the value [%s].", LSCPU_CPU_MAX_MHZ_COMMAND, result));
return 0L;
}
}

private static long getCpuSpeedFromFile() {
for (final String cpuInfoFreqFileName: cpuInfoFreqFileNames) {
LOGGER.info(String.format("Fetching CPU speed from file [%s].", cpuInfoFreqFileName));
Expand Down Expand Up @@ -215,6 +240,7 @@ private void getHostInfoFromLibvirt() {
} else {
LOGGER.debug(String.format("Using existing configured CPU frequency %s", this.cpuSpeed));
}
this.cpuMaxSpeed = getCpuMaxSpeedFromCommandLscpu();

/*
* Some CPUs report a single socket and multiple NUMA cells.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.mockito.Mockito;

import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.utils.script.Script;
import org.mockito.junit.MockitoJUnitRunner;

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

private void mockLscpuOutput(MockedStatic<Script> script, String modelNameGhz, String cpuMaxMhz) {
script.when(() -> Script.runSimpleBashScript(Mockito.contains("Model name"))).thenReturn(modelNameGhz);
script.when(() -> Script.runSimpleBashScript(Mockito.contains("CPU max MHz"))).thenReturn(cpuMaxMhz);
}

@Test
public void getCpuSpeedFromCommandLscpuTestPrefersModelNameFrequencyOverCpuMaxMhz() {
try (MockedStatic<Script> script = Mockito.mockStatic(Script.class)) {
mockLscpuOutput(script, "2.40", "3400.0000");
Assert.assertEquals(2400L, KVMHostInfo.getCpuSpeedFromCommandLscpu());
}
}

@Test
public void getCpuSpeedFromCommandLscpuTestFallsBackToCpuMaxMhzWhenModelNameHasNoFrequency() {
try (MockedStatic<Script> script = Mockito.mockStatic(Script.class)) {
mockLscpuOutput(script, null, "3400.0000");
Assert.assertEquals(3400L, KVMHostInfo.getCpuSpeedFromCommandLscpu());
}
}

@Test
public void getCpuSpeedFromCommandLscpuTestReturnsZeroWhenLscpuHasNoFrequency() {
try (MockedStatic<Script> script = Mockito.mockStatic(Script.class)) {
mockLscpuOutput(script, null, null);
Assert.assertEquals(0L, KVMHostInfo.getCpuSpeedFromCommandLscpu());
}
}

@Test
public void getCpuMaxSpeedFromCommandLscpuTestReturnsCpuMaxMhz() {
try (MockedStatic<Script> script = Mockito.mockStatic(Script.class)) {
mockLscpuOutput(script, "2.40", "3400.0000");
Assert.assertEquals(3400L, KVMHostInfo.getCpuMaxSpeedFromCommandLscpu());
}
}

@Test
public void getCpuMaxSpeedFromCommandLscpuTestReturnsZeroWhenLscpuHasNoCpuMaxMhz() {
try (MockedStatic<Script> script = Mockito.mockStatic(Script.class)) {
mockLscpuOutput(script, "2.40", null);
Assert.assertEquals(0L, KVMHostInfo.getCpuMaxSpeedFromCommandLscpu());
}
}
}
1 change: 1 addition & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,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.vddk.support": "VDDK Support",
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 @@ -217,6 +217,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