8269851: OperatingSystemMXBean getProcessCpuLoad reports incorrect process cpu usage in containers
Reviewed-by: clanger
Backport-of: 25f00d787cf56f6cdca6949115d04e7d8e675554
diff --git a/src/jdk.management/unix/classes/com/sun/management/internal/OperatingSystemImpl.java b/src/jdk.management/unix/classes/com/sun/management/internal/OperatingSystemImpl.java
index 663cb87..6c5530d 100644
--- a/src/jdk.management/unix/classes/com/sun/management/internal/OperatingSystemImpl.java
+++ b/src/jdk.management/unix/classes/com/sun/management/internal/OperatingSystemImpl.java
@@ -25,11 +25,14 @@
package com.sun.management.internal;
+import java.util.concurrent.TimeUnit;
+import java.util.function.DoubleSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.ToDoubleFunction;
+
import jdk.internal.platform.Metrics;
import sun.management.BaseOperatingSystemImpl;
import sun.management.VMManagement;
-
-import java.util.concurrent.TimeUnit;
/**
* Implementation class for the operating system.
* Standard and committed hotspot-specific metrics if any.
@@ -42,8 +45,137 @@
private static final int MAX_ATTEMPTS_NUMBER = 10;
private final Metrics containerMetrics;
- private long usageTicks = 0; // used for cpu load calculation
- private long totalTicks = 0; // used for cpu load calculation
+ private ContainerCpuTicks systemLoadTicks = new SystemCpuTicks();
+ private ContainerCpuTicks processLoadTicks = new ProcessCpuTicks();
+
+ private abstract class ContainerCpuTicks {
+ private long usageTicks = 0;
+ private long totalTicks = 0;
+
+ private double getUsageDividesTotal(long usageTicks, long totalTicks) {
+ // If cpu quota or cpu shares are in effect. Calculate the cpu load
+ // based on the following formula (similar to how
+ // getCpuLoad0() is being calculated):
+ //
+ // | usageTicks - usageTicks' |
+ // ------------------------------
+ // | totalTicks - totalTicks' |
+ //
+ // where usageTicks' and totalTicks' are historical values
+ // retrieved via an earlier call of this method.
+ if (usageTicks < 0 || totalTicks <= 0) {
+ return -1;
+ }
+ long distance = usageTicks - this.usageTicks;
+ this.usageTicks = usageTicks;
+ long totalDistance = totalTicks - this.totalTicks;
+ this.totalTicks = totalTicks;
+ double systemLoad = 0.0;
+ if (distance > 0 && totalDistance > 0) {
+ systemLoad = ((double)distance) / totalDistance;
+ }
+ // Ensure the return value is in the range 0.0 -> 1.0
+ systemLoad = Math.max(0.0, systemLoad);
+ systemLoad = Math.min(1.0, systemLoad);
+ return systemLoad;
+ }
+
+ public double getContainerCpuLoad() {
+ assert(containerMetrics != null);
+ long quota = containerMetrics.getCpuQuota();
+ long share = containerMetrics.getCpuShares();
+ if (quota > 0) {
+ long numPeriods = containerMetrics.getCpuNumPeriods();
+ long quotaNanos = TimeUnit.MICROSECONDS.toNanos(quota * numPeriods);
+ return getUsageDividesTotal(cpuUsageSupplier().getAsLong(), quotaNanos);
+ } else if (share > 0) {
+ long hostTicks = getHostTotalCpuTicks0();
+ int totalCPUs = getHostOnlineCpuCount0();
+ int containerCPUs = getAvailableProcessors();
+ // scale the total host load to the actual container cpus
+ hostTicks = hostTicks * containerCPUs / totalCPUs;
+ return getUsageDividesTotal(cpuUsageSupplier().getAsLong(), hostTicks);
+ } else {
+ // If CPU quotas and shares are not active then find the average load for
+ // all online CPUs that are allowed to run this container.
+
+ // If the cpuset is the same as the host's one there is no need to iterate over each CPU
+ if (isCpuSetSameAsHostCpuSet()) {
+ return defaultCpuLoadSupplier().getAsDouble();
+ } else {
+ int[] cpuSet = containerMetrics.getEffectiveCpuSetCpus();
+ // in case the effectiveCPUSetCpus are not available, attempt to use just cpusets.cpus
+ if (cpuSet == null || cpuSet.length <= 0) {
+ cpuSet = containerMetrics.getCpuSetCpus();
+ }
+ if (cpuSet == null) {
+ // cgroups is mounted, but CPU resource is not limited.
+ // We can assume the VM is run on the host CPUs.
+ return defaultCpuLoadSupplier().getAsDouble();
+ } else if (cpuSet.length > 0) {
+ return cpuSetCalc().applyAsDouble(cpuSet);
+ }
+ return -1;
+ }
+ }
+ }
+
+ protected abstract DoubleSupplier defaultCpuLoadSupplier();
+ protected abstract ToDoubleFunction<int[]> cpuSetCalc();
+ protected abstract LongSupplier cpuUsageSupplier();
+ }
+
+ private class ProcessCpuTicks extends ContainerCpuTicks {
+
+ @Override
+ protected DoubleSupplier defaultCpuLoadSupplier() {
+ return () -> getProcessCpuLoad0();
+ }
+
+ @Override
+ protected ToDoubleFunction<int[]> cpuSetCalc() {
+ return (int[] cpuSet) -> {
+ int totalCPUs = getHostOnlineCpuCount0();
+ int containerCPUs = getAvailableProcessors();
+ return Math.min(1.0, getProcessCpuLoad0() * totalCPUs / containerCPUs);
+ };
+ }
+
+ @Override
+ protected LongSupplier cpuUsageSupplier() {
+ return () -> getProcessCpuTime();
+ }
+
+ }
+
+ private class SystemCpuTicks extends ContainerCpuTicks {
+
+ @Override
+ protected DoubleSupplier defaultCpuLoadSupplier() {
+ return () -> getSystemCpuLoad0();
+ }
+
+ @Override
+ protected ToDoubleFunction<int[]> cpuSetCalc() {
+ return (int[] cpuSet) -> {
+ double systemLoad = 0.0;
+ for (int cpu : cpuSet) {
+ double cpuLoad = getSingleCpuLoad0(cpu);
+ if (cpuLoad < 0) {
+ return -1;
+ }
+ systemLoad += cpuLoad;
+ }
+ return systemLoad / cpuSet.length;
+ };
+ }
+
+ @Override
+ protected LongSupplier cpuUsageSupplier() {
+ return () -> containerMetrics.getCpuUsage();
+ }
+
+ }
OperatingSystemImpl(VMManagement vm) {
super(vm);
@@ -135,86 +267,17 @@
return getMaxFileDescriptorCount0();
}
- private double getUsageDividesTotal(long usageTicks, long totalTicks) {
- // If cpu quota or cpu shares are in effect calculate the cpu load
- // based on the following formula (similar to how
- // getCpuLoad0() is being calculated):
- //
- // | usageTicks - usageTicks' |
- // ------------------------------
- // | totalTicks - totalTicks' |
- //
- // where usageTicks' and totalTicks' are historical values
- // retrieved via an earlier call of this method.
- //
- // Total ticks should be scaled to the container effective number
- // of cpus, if cpu shares are in effect.
- if (usageTicks < 0 || totalTicks <= 0) {
- return -1;
- }
- long distance = usageTicks - this.usageTicks;
- this.usageTicks = usageTicks;
- long totalDistance = totalTicks - this.totalTicks;
- this.totalTicks = totalTicks;
-
- double systemLoad = 0.0;
- if (distance > 0 && totalDistance > 0) {
- systemLoad = ((double)distance) / totalDistance;
- }
- // Ensure the return value is in the range 0.0 -> 1.0
- systemLoad = Math.max(0.0, systemLoad);
- systemLoad = Math.min(1.0, systemLoad);
- return systemLoad;
- }
-
public double getSystemCpuLoad() {
if (containerMetrics != null) {
- long quota = containerMetrics.getCpuQuota();
- long share = containerMetrics.getCpuShares();
- long usageNanos = containerMetrics.getCpuUsage();
- if (quota > 0) {
- long numPeriods = containerMetrics.getCpuNumPeriods();
- long quotaNanos = TimeUnit.MICROSECONDS.toNanos(quota * numPeriods);
- return getUsageDividesTotal(usageNanos, quotaNanos);
- } else if (share > 0) {
- long hostTicks = getHostTotalCpuTicks0();
- int totalCPUs = getHostOnlineCpuCount0();
- int containerCPUs = getAvailableProcessors();
- // scale the total host load to the actual container cpus
- hostTicks = hostTicks * containerCPUs / totalCPUs;
- return getUsageDividesTotal(usageNanos, hostTicks);
- } else {
- // If CPU quotas and shares are not active then find the average system load for
- // all online CPUs that are allowed to run this container.
-
- // If the cpuset is the same as the host's one there is no need to iterate over each CPU
- if (isCpuSetSameAsHostCpuSet()) {
- return getSystemCpuLoad0();
- } else {
- int[] cpuSet = containerMetrics.getEffectiveCpuSetCpus();
- // in case the effectiveCPUSetCpus are not available, attempt to use just cpusets.cpus
- if (cpuSet == null || cpuSet.length <= 0) {
- cpuSet = containerMetrics.getCpuSetCpus();
- }
- if (cpuSet != null && cpuSet.length > 0) {
- double systemLoad = 0.0;
- for (int cpu : cpuSet) {
- double cpuLoad = getSingleCpuLoad0(cpu);
- if (cpuLoad < 0) {
- return -1;
- }
- systemLoad += cpuLoad;
- }
- return systemLoad / cpuSet.length;
- }
- return -1;
- }
- }
+ return systemLoadTicks.getContainerCpuLoad();
}
return getSystemCpuLoad0();
}
public double getProcessCpuLoad() {
+ if (containerMetrics != null) {
+ return processLoadTicks.getContainerCpuLoad();
+ }
return getProcessCpuLoad0();
}