From 1f3f14cdea8f2073100b6a74fbfd20b862ac0a5e Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Sun, 11 Oct 2026 11:27:52 -0300 Subject: [PATCH] kvm: use the base CPU frequency as the host speed and expose the maximum frequency as a host detail --- api/src/main/java/com/cloud/host/Host.java | 1 + .../resource/LibvirtComputingResource.java | 4 ++ .../cloudstack/utils/linux/KVMHostInfo.java | 42 +++++++++++++---- .../utils/linux/KVMHostInfoTest.java | 46 +++++++++++++++++++ ui/public/locales/en.json | 1 + ui/src/components/view/InfoCard.vue | 3 ++ 6 files changed, 89 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/com/cloud/host/Host.java b/api/src/main/java/com/cloud/host/Host.java index a7b89b8a2b85..c2c43aee1378 100644 --- a/api/src/main/java/com/cloud/host/Host.java +++ b/api/src/main/java/com/cloud/host/Host.java @@ -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; 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 a9ed0c62cab8..6fde84f1afb9 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 @@ -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; @@ -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()); 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 d8e5bed2ef09..2ad63d01526f 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 @@ -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 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 cpuInfoFreqFileNames = List.of("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency","/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"); @@ -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; } @@ -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) { @@ -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)); @@ -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. 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 b41e2bf214a3..782d5e8270b3 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 @@ -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) @@ -102,4 +103,49 @@ public void reservedCpuCoresTest() throws Exception { Assert.assertEquals("Misconfigured/too large CPU reserve", 0, kvmHostInfo.getAllocatableCpus()); } } + + private void mockLscpuOutput(MockedStatic