Repository navigation
kvm: prefer the base speed for AMD CPUs + a few refactorings #14384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
winterhazel
wants to merge
1
commit into
apache:main
Choose a base branch
from
winterhazel:fix-amd-cpu-speed
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+203
−48
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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 { | ||
|
|
@@ -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; | ||
|
|
@@ -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; | ||
| } | ||
|
|
@@ -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(); | ||
| 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)) { | ||
|
|
@@ -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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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