diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java index af551a07b1e71..63dc4b0c4a985 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java @@ -120,6 +120,7 @@ public int[] getEffectiveCpuSetMems() { return subsystem.getEffectiveCpuSetMems(); } + @Override public long getMemoryFailCount() { return subsystem.getMemoryFailCount(); } @@ -200,7 +201,8 @@ public static Metrics getInstance() { private static native boolean isUseContainerSupport(); private static native boolean isContainerized0(); - private static native long getTotalMemorySize0(); - private static native long getTotalSwapSize0(); + public static native long getTotalMemorySize0(); + public static native long getTotalSwapSize0(); + public static native int getTotalCpuCount0(); } diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java index 38be8628fb48e..ea932ea6beb70 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java @@ -44,8 +44,30 @@ public interface CgroupSubsystemController { public static final String EMPTY_STR = ""; + /** + * The controller path + * + * @return The path to the interface files for this controller. + */ public String path(); + public void setPath(String cgroupPath); + + public String getCgroupPath(); + + /** + * Some systems don't have the interface files at the leaf of the controller + * path. That is, the hierarchy up to the root needs to get traversed looking + * for a lower limit at any of the paths. This is not needed for OCI containers + * which have the limits set at the leaf. + * + * @return {@code true} for any controller that needs adjusting, {@code false} + * otherwise. + */ + public default boolean needsAdjustment() { + return false; + } + /** * getStringValue * @@ -59,7 +81,9 @@ public interface CgroupSubsystemController { * an error occurs. */ public static String getStringValue(CgroupSubsystemController controller, String param) { - if (controller == null) return null; + if (controller == null) { + return null; + } try { return CgroupUtil.readStringValue(controller, param); @@ -122,7 +146,9 @@ public static long getLongValue(CgroupSubsystemController controller, Function conversion, long defaultRetval) { String strval = getStringValue(controller, param); - if (strval == null) return defaultRetval; + if (strval == null) { + return defaultRetval; + } return conversion.apply(strval); } @@ -137,7 +163,9 @@ public static long getLongValue(CgroupSubsystemController controller, public static double getDoubleValue(CgroupSubsystemController controller, String param, double defaultRetval) { String strval = getStringValue(controller, param); - if (strval == null) return defaultRetval; + if (strval == null) { + return defaultRetval; + } double retval = Double.parseDouble(strval); @@ -159,7 +187,9 @@ public static double getDoubleValue(CgroupSubsystemController controller, String * was found. */ public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname, long defaultRetval) { - if (controller == null) return defaultRetval; + if (controller == null) { + return defaultRetval; + } try (Stream lines = CgroupUtil.readFilePrivileged(Paths.get(controller.path(), param))) { @@ -187,7 +217,9 @@ public static long getLongEntry(CgroupSubsystemController controller, String par * was an empty string. */ public static int[] stringRangeToIntArray(String range) { - if (range == null || EMPTY_STR.equals(range)) return null; + if (range == null || EMPTY_STR.equals(range)) { + return null; + } ArrayList results = new ArrayList<>(); String strs[] = range.split(","); @@ -235,7 +267,9 @@ public static int[] stringRangeToIntArray(String range) { */ public static long convertStringToLong(String strval, long overflowRetval, long defaultRetval) { long retval = defaultRetval; - if (strval == null) return retval; + if (strval == null) { + return retval; + } try { retval = Long.parseLong(strval); diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemCpuController.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemCpuController.java new file mode 100644 index 0000000000000..34b9d8d2a81a1 --- /dev/null +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemCpuController.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Red Hat Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.platform; + +public interface CgroupSubsystemCpuController extends CgroupSubsystemController { + + public long getCpuPeriod(); + + public long getCpuQuota(); + + public long getCpuShares(); + + public long getCpuNumPeriods(); + + public long getCpuNumThrottled(); + + public long getCpuThrottledTime(); + +} diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java index 0a6d9958d11e0..40eef81af82f8 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java @@ -36,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Objects; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -111,9 +110,7 @@ public static CgroupMetrics create(Optional optResult) { Map infos = result.getInfos(); if (result.isCgroupV2()) { - // For unified it doesn't matter which controller we pick. - CgroupInfo anyController = infos.values().iterator().next(); - CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(Objects.requireNonNull(anyController)); + CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(infos); return new CgroupMetrics(subsystem); } else { CgroupV1Subsystem subsystem = CgroupV1Subsystem.getInstance(infos); diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemMemoryController.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemMemoryController.java new file mode 100644 index 0000000000000..ff473865a12b6 --- /dev/null +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemMemoryController.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, Red Hat Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.platform; + +public interface CgroupSubsystemMemoryController extends CgroupSubsystemController { + + public long getMemoryLimit(long physicalMemory); + + public long getMemoryUsage(); + + public long getTcpMemoryUsage(); + + public long getMemoryAndSwapLimit(long hostMemory, long hostSwap); + + public long getMemoryAndSwapUsage(); + + public long getMemorySoftLimit(long hostMemory); + + public long getMemoryFailCount(); + +} diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java index dbe8a85b2b2f5..723cfebbed0ee 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java @@ -54,12 +54,15 @@ public static Stream readFilePrivileged(Path path) throws IOException { static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IOException { Throwable x = pae.getCause(); - if (x instanceof IOException) + if (x instanceof IOException) { throw (IOException) x; - if (x instanceof RuntimeException) + } + if (x instanceof RuntimeException) { throw (RuntimeException) x; - if (x instanceof Error) + } + if (x instanceof Error) { throw (Error) x; + } } static String readStringValue(CgroupSubsystemController controller, String param) throws IOException { @@ -89,4 +92,68 @@ public static List readAllLinesPrivileged(Path path) throws IOException throw e.getCause(); } } + + /** + * Calculate the processor count based on the host CPUs and set cpu quota. + * + * @param cpu The cpu controller to read the quota values from. + * @param hostCpus The physical host CPUs + * @return The minimum of host CPUs and the configured cpu quota, never + * negative. + */ + static int processorCount(CgroupSubsystemCpuController cpu, int hostCpus) { + int limit = hostCpus; + long quota = cpu.getCpuQuota(); + long period = cpu.getCpuPeriod(); + int quotaCount = 0; + + if (quota > CgroupSubsystem.LONG_RETVAL_UNLIMITED && period > 0) { + quotaCount = (int) Math.ceilDiv(quota, period); + } + if (quotaCount != 0) { + limit = quotaCount; + } + return Math.min(hostCpus, limit); + } + + public static void adjustController(CgroupSubsystemCpuController cpu) { + if (!cpu.needsAdjustment()) { + return; + } + String origCgroupPath = cpu.getCgroupPath(); + Path workingPath = Path.of(origCgroupPath); + boolean adjustmentDone = false; + int hostCpus = CgroupMetrics.getTotalCpuCount0(); + + int limit = CgroupUtil.processorCount(cpu, hostCpus); + while (limit == hostCpus && ((workingPath = workingPath.getParent()) != null)) { + cpu.setPath(workingPath.toString()); // adjust path + limit = CgroupUtil.processorCount(cpu, hostCpus); + adjustmentDone = true; + } + if (adjustmentDone && limit == hostCpus) { + // No lower limit found adjust to original path + cpu.setPath(origCgroupPath); + } + } + + public static void adjustController(CgroupSubsystemMemoryController memory) { + if (!memory.needsAdjustment()) { + return; + } + long physicalMemory = CgroupMetrics.getTotalMemorySize0(); + String origCgroupPath = memory.getCgroupPath(); + Path workingPath = Path.of(origCgroupPath); + boolean adjustmentDone = false; + long limit = memory.getMemoryLimit(physicalMemory); + while (limit < 0 && ((workingPath = workingPath.getParent()) != null)) { + memory.setPath(workingPath.toString()); // adjust path + limit = memory.getMemoryLimit(physicalMemory); + adjustmentDone = true; + } + if (adjustmentDone && limit < 0) { + // No lower limit found adjust to original path + memory.setPath(origCgroupPath); + } + } } diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1CpuSubSystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1CpuSubSystemController.java new file mode 100644 index 0000000000000..cd9a6f13b7fb2 --- /dev/null +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1CpuSubSystemController.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, Red Hat Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.platform.cgroupv1; + +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.CgroupSubsystemCpuController; + +public class CgroupV1CpuSubSystemController extends CgroupV1SubsystemController + implements CgroupSubsystemCpuController { + + public CgroupV1CpuSubSystemController(String root, String mountPoint) { + super(root, mountPoint); + } + + @Override + public long getCpuPeriod() { + return CgroupV1Subsystem.getLongValue(this, "cpu.cfs_period_us"); + } + + @Override + public long getCpuQuota() { + return CgroupV1Subsystem.getLongValue(this, "cpu.cfs_quota_us"); + } + + @Override + public long getCpuShares() { + long retval = CgroupV1Subsystem.getLongValue(this, "cpu.shares"); + if (retval == 0 || retval == 1024) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } else { + return retval; + } + } + + @Override + public long getCpuNumPeriods() { + return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "nr_periods"); + } + + @Override + public long getCpuNumThrottled() { + return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "nr_throttled"); + } + + @Override + public long getCpuThrottledTime() { + return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "throttled_time"); + } + + @Override + public boolean needsAdjustment() { + // Container frameworks have them equal; we skip adjustment for them + return !getRoot().equals(getCgroupPath()); + } + +} diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java index 415ae930a935d..ba47a84786080 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java @@ -25,28 +25,138 @@ package jdk.internal.platform.cgroupv1; -public class CgroupV1MemorySubSystemController extends CgroupV1SubsystemController { +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.CgroupSubsystemMemoryController; + +public class CgroupV1MemorySubSystemController extends CgroupV1SubsystemController + implements CgroupSubsystemMemoryController { - private boolean hierarchical; private boolean swapenabled; public CgroupV1MemorySubSystemController(String root, String mountPoint) { super(root, mountPoint); } - boolean isHierarchical() { - return hierarchical; + private boolean isSwapEnabled() { + return swapenabled; } - void setHierarchical(boolean hierarchical) { - this.hierarchical = hierarchical; + private void setSwapEnabled() { + long memswBytes = CgroupV1Subsystem.getLongValue(this, "memory.memsw.limit_in_bytes"); + long swappiness = CgroupV1Subsystem.getLongValue(this, "memory.swappiness"); + this.swapenabled = (memswBytes > 0 && swappiness > 0); } - boolean isSwapEnabled() { - return swapenabled; + @Override + public void setPath(String cgroupPath) { + super.setPath(cgroupPath); + setSwapEnabled(); + } + + @Override + public long getMemoryLimit(long physicalMemory) { + long limit = CgroupV1Subsystem.getLongValue(this, "memory.limit_in_bytes"); + // Limits on cg v1 might return large numbers. Bound above by the + // physical limit of the host. If so, treat it as unlimited. + if (limit >= physicalMemory) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return limit; + } + + @Override + public long getMemoryUsage() { + return CgroupV1Subsystem.getLongValue(this, "memory.usage_in_bytes"); + } + + @Override + public long getTcpMemoryUsage() { + return CgroupV1Subsystem.getLongValue(this, "memory.kmem.tcp.usage_in_bytes"); + } + + @Override + public long getMemoryAndSwapLimit(long hostMemory, long hostSwap) { + if (!isSwapEnabled()) { + return getMemoryLimit(hostMemory); + } + long retval = CgroupV1Subsystem.getLongValue(this, "memory.memsw.limit_in_bytes"); + long upperBound = hostMemory + hostSwap; + // The limit value for cg v1 might be a large number when there is no + // limit. Ensure the limit doesn't exceed the physical bounds. + if (retval >= upperBound) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return retval; + } + + @Override + public long getMemoryAndSwapUsage() { + if (!isSwapEnabled()) { + return getMemoryUsage(); + } + return CgroupV1Subsystem.getLongValue(this, "memory.memsw.usage_in_bytes"); + } + + @Override + public long getMemorySoftLimit(long hostMemory) { + long softLimit = CgroupV1Subsystem.getLongValue(this, "memory.soft_limit_in_bytes"); + if (softLimit >= hostMemory) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return softLimit; + } + + @Override + public long getMemoryFailCount() { + return CgroupV1Subsystem.getLongValue(this, "memory.failcnt"); + } + + public long getMemoryMaxUsage() { + return CgroupV1Subsystem.getLongValue(this, "memory.max_usage_in_bytes"); + } + + public long getMemoryAndSwapMaxUsage() { + if (!isSwapEnabled()) { + return getMemoryMaxUsage(); + } + return CgroupV1Subsystem.getLongValue(this, "memory.memsw.max_usage_in_bytes"); + } + + public long getMemoryAndSwapFailCount() { + if (!isSwapEnabled()) { + return getMemoryFailCount(); + } + return CgroupV1Subsystem.getLongValue(this, "memory.memsw.failcnt"); + } + + public long getKernelMemoryFailCount() { + return CgroupV1Subsystem.getLongValue(this, "memory.kmem.failcnt"); + } + + public long getKernelMemoryMaxUsage() { + return CgroupV1Subsystem.getLongValue(this, "memory.kmem.max_usage_in_bytes"); + } + + public long getKernelMemoryUsage() { + return CgroupV1Subsystem.getLongValue(this, "memory.kmem.usage_in_bytes"); + } + + public long getTcpMemoryFailCount() { + return CgroupV1Subsystem.getLongValue(this, "memory.kmem.tcp.failcnt"); + } + + public long getTcpMemoryMaxUsage() { + return CgroupV1Subsystem.getLongValue(this, "memory.kmem.tcp.max_usage_in_bytes"); + } + + public Boolean isMemoryOOMKillEnabled() { + long val = CgroupV1SubsystemController.getLongEntry(this, "memory.oom_control", "oom_kill_disable"); + return (val == 0); } - void setSwapEnabled(boolean swapenabled) { - this.swapenabled = swapenabled; + @Override + public boolean needsAdjustment() { + // Container frameworks have them equal; we skip adjustment for them + return !getRoot().equals(getCgroupPath()); } } diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java index 464081d9bf2d6..2deb6925f5fd4 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java @@ -26,15 +26,18 @@ package jdk.internal.platform.cgroupv1; import java.util.Map; +import java.util.function.Supplier; import jdk.internal.platform.CgroupInfo; +import jdk.internal.platform.CgroupMetrics; import jdk.internal.platform.CgroupSubsystem; import jdk.internal.platform.CgroupSubsystemController; +import jdk.internal.platform.CgroupUtil; import jdk.internal.platform.CgroupV1Metrics; public class CgroupV1Subsystem implements CgroupSubsystem, CgroupV1Metrics { private CgroupV1MemorySubSystemController memory; - private CgroupV1SubsystemController cpu; + private CgroupV1CpuSubSystemController cpu; private CgroupV1SubsystemController cpuacct; private CgroupV1SubsystemController cpuset; private CgroupV1SubsystemController blkio; @@ -82,10 +85,7 @@ private static CgroupV1Subsystem initSubSystem(Map infos) { if (info.getMountRoot() != null && info.getMountPoint() != null) { CgroupV1MemorySubSystemController controller = new CgroupV1MemorySubSystemController(info.getMountRoot(), info.getMountPoint()); controller.setPath(info.getCgroupPath()); - boolean isHierarchial = getHierarchical(controller); - controller.setHierarchical(isHierarchial); - boolean isSwapEnabled = getSwapEnabled(controller); - controller.setSwapEnabled(isSwapEnabled); + CgroupUtil.adjustController(controller); subsystem.setMemorySubSystem(controller); anyActiveControllers = true; } @@ -111,8 +111,9 @@ private static CgroupV1Subsystem initSubSystem(Map infos) { } case "cpu": { if (info.getMountRoot() != null && info.getMountPoint() != null) { - CgroupV1SubsystemController controller = new CgroupV1SubsystemController(info.getMountRoot(), info.getMountPoint()); + CgroupV1CpuSubSystemController controller = new CgroupV1CpuSubSystemController(info.getMountRoot(), info.getMountPoint()); controller.setPath(info.getCgroupPath()); + CgroupUtil.adjustController(controller); subsystem.setCpuController(controller); anyActiveControllers = true; } @@ -149,23 +150,11 @@ private static CgroupV1Subsystem initSubSystem(Map infos) { return null; } - private static boolean getSwapEnabled(CgroupV1MemorySubSystemController controller) { - long memswBytes = getLongValue(controller, "memory.memsw.limit_in_bytes"); - long swappiness = getLongValue(controller, "memory.swappiness"); - return (memswBytes > 0 && swappiness > 0); - } - - - private static boolean getHierarchical(CgroupV1MemorySubSystemController controller) { - long hierarchical = getLongValue(controller, "memory.use_hierarchy"); - return hierarchical > 0; - } - private void setMemorySubSystem(CgroupV1MemorySubSystemController memory) { this.memory = memory; } - private void setCpuController(CgroupV1SubsystemController cpu) { + private void setCpuController(CgroupV1CpuSubSystemController cpu) { this.cpu = cpu; } @@ -185,7 +174,7 @@ private void setPidsController(CgroupV1SubsystemController pids) { this.pids = pids; } - private static long getLongValue(CgroupSubsystemController controller, + static long getLongValue(CgroupSubsystemController controller, String param) { return CgroupSubsystemController.getLongValue(controller, param, @@ -193,6 +182,24 @@ private static long getLongValue(CgroupSubsystemController controller, CgroupSubsystem.LONG_RETVAL_UNLIMITED); } + /** + * Accounts for optional controllers. If the controller is null the provided + * supplier will never be called. + * + * @param controller The controller to check for null + * @param supplier The supplier using the controller + * @return -1 (unlimited) when the controller is null, otherwise the supplier + * value. + */ + private static long valueOrUnlimited(CgroupSubsystemController controller, + Supplier supplier) { + if (controller == null) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return supplier.get(); + } + + @Override public String getProvider() { return PROVIDER_NAME; } @@ -202,10 +209,12 @@ public String getProvider() { ****************************************************************/ + @Override public long getCpuUsage() { return getLongValue(cpuacct, "cpuacct.usage"); } + @Override public long[] getPerCpuUsage() { String usagelist = CgroupSubsystemController.getStringValue(cpuacct, "cpuacct.usage_percpu"); if (usagelist == null) { @@ -220,10 +229,12 @@ public long[] getPerCpuUsage() { return percpu; } + @Override public long getCpuUserUsage() { return CgroupV1SubsystemController.getLongEntry(cpuacct, "cpuacct.stat", "user"); } + @Override public long getCpuSystemUsage() { return CgroupV1SubsystemController.getLongEntry(cpuacct, "cpuacct.stat", "system"); } @@ -234,34 +245,37 @@ public long getCpuSystemUsage() { ****************************************************************/ + @Override public long getCpuPeriod() { - return getLongValue(cpu, "cpu.cfs_period_us"); + return valueOrUnlimited(cpu, () -> cpu.getCpuPeriod()); } + @Override public long getCpuQuota() { - return getLongValue(cpu, "cpu.cfs_quota_us"); + return valueOrUnlimited(cpu, () -> cpu.getCpuQuota()); } + @Override public long getCpuShares() { - long retval = getLongValue(cpu, "cpu.shares"); - if (retval == 0 || retval == 1024) - return CgroupSubsystem.LONG_RETVAL_UNLIMITED; - else - return retval; + return valueOrUnlimited(cpu, () -> cpu.getCpuShares()); } + @Override public long getCpuNumPeriods() { - return CgroupV1SubsystemController.getLongEntry(cpu, "cpu.stat", "nr_periods"); + return valueOrUnlimited(cpu, () -> cpu.getCpuNumPeriods()); } + @Override public long getCpuNumThrottled() { - return CgroupV1SubsystemController.getLongEntry(cpu, "cpu.stat", "nr_throttled"); + return valueOrUnlimited(cpu, () -> cpu.getCpuNumThrottled()); } + @Override public long getCpuThrottledTime() { - return CgroupV1SubsystemController.getLongEntry(cpu, "cpu.stat", "throttled_time"); + return valueOrUnlimited(cpu, () -> cpu.getCpuThrottledTime()); } + @Override public long getEffectiveCpuCount() { return Runtime.getRuntime().availableProcessors(); } @@ -271,26 +285,32 @@ public long getEffectiveCpuCount() { * CPUSet Subsystem ****************************************************************/ + @Override public int[] getCpuSetCpus() { return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.cpus")); } + @Override public int[] getEffectiveCpuSetCpus() { return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.effective_cpus")); } + @Override public int[] getCpuSetMems() { return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.mems")); } + @Override public int[] getEffectiveCpuSetMems() { return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.effective_mems")); } + @Override public double getCpuSetMemoryPressure() { return CgroupV1SubsystemController.getDoubleValue(cpuset, "cpuset.memory_pressure"); } + @Override public Boolean isCpuSetMemoryPressureEnabled() { long val = getLongValue(cpuset, "cpuset.memory_pressure_enabled"); return (val == 1); @@ -301,114 +321,102 @@ public Boolean isCpuSetMemoryPressureEnabled() { * Memory Subsystem ****************************************************************/ - + @Override public long getMemoryFailCount() { - return getLongValue(memory, "memory.failcnt"); + return valueOrUnlimited(memory, () -> memory.getMemoryFailCount()); } + @Override public long getMemoryLimit() { - long retval = getLongValue(memory, "memory.limit_in_bytes"); - if (retval > CgroupV1SubsystemController.UNLIMITED_MIN) { - if (memory.isHierarchical()) { - // memory.limit_in_bytes returned unlimited, attempt - // hierarchical memory limit - String match = "hierarchical_memory_limit"; - retval = CgroupV1SubsystemController.getLongValueMatchingLine(memory, - "memory.stat", - match); - } - } - return CgroupV1SubsystemController.longValOrUnlimited(retval); + Supplier limitSupplier = () -> memory.getMemoryLimit(CgroupMetrics.getTotalMemorySize0()); + return valueOrUnlimited(memory, limitSupplier); } + @Override public long getMemoryMaxUsage() { - return getLongValue(memory, "memory.max_usage_in_bytes"); + return valueOrUnlimited(memory, () -> memory.getMemoryMaxUsage()); } + @Override public long getMemoryUsage() { - return getLongValue(memory, "memory.usage_in_bytes"); + return valueOrUnlimited(memory, () -> memory.getMemoryUsage()); } - public long getKernelMemoryFailCount() { - return getLongValue(memory, "memory.kmem.failcnt"); + @Override + public long getTcpMemoryUsage() { + return valueOrUnlimited(memory, () -> memory.getTcpMemoryUsage()); } - public long getKernelMemoryMaxUsage() { - return getLongValue(memory, "memory.kmem.max_usage_in_bytes"); + @Override + public long getMemoryAndSwapFailCount() { + return valueOrUnlimited(memory, () -> memory.getMemoryAndSwapFailCount()); } - public long getKernelMemoryUsage() { - return getLongValue(memory, "memory.kmem.usage_in_bytes"); + @Override + public long getMemoryAndSwapLimit() { + Supplier limitSupplier = () -> { + long hostMem = CgroupMetrics.getTotalMemorySize0(); + long hostSwap = CgroupMetrics.getTotalSwapSize0(); + return memory.getMemoryAndSwapLimit(hostMem, hostSwap); + }; + return valueOrUnlimited(memory, limitSupplier); } - public long getTcpMemoryFailCount() { - return getLongValue(memory, "memory.kmem.tcp.failcnt"); + @Override + public long getMemoryAndSwapMaxUsage() { + return valueOrUnlimited(memory, () -> memory.getMemoryAndSwapMaxUsage()); } - public long getTcpMemoryMaxUsage() { - return getLongValue(memory, "memory.kmem.tcp.max_usage_in_bytes"); + @Override + public long getMemoryAndSwapUsage() { + return valueOrUnlimited(memory, () -> memory.getMemoryAndSwapUsage()); } - public long getTcpMemoryUsage() { - return getLongValue(memory, "memory.kmem.tcp.usage_in_bytes"); + @Override + public long getKernelMemoryFailCount() { + return valueOrUnlimited(memory, () -> memory.getKernelMemoryFailCount()); } - public long getMemoryAndSwapFailCount() { - if (memory != null && !memory.isSwapEnabled()) { - return getMemoryFailCount(); - } - return getLongValue(memory, "memory.memsw.failcnt"); + @Override + public long getKernelMemoryMaxUsage() { + return valueOrUnlimited(memory, () -> memory.getKernelMemoryMaxUsage()); } - public long getMemoryAndSwapLimit() { - if (memory != null && !memory.isSwapEnabled()) { - return getMemoryLimit(); - } - long retval = getLongValue(memory, "memory.memsw.limit_in_bytes"); - if (retval > CgroupV1SubsystemController.UNLIMITED_MIN) { - if (memory.isHierarchical()) { - // memory.memsw.limit_in_bytes returned unlimited, attempt - // hierarchical memory limit - String match = "hierarchical_memsw_limit"; - retval = CgroupV1SubsystemController.getLongValueMatchingLine(memory, - "memory.stat", - match); - } - } - return CgroupV1SubsystemController.longValOrUnlimited(retval); + @Override + public long getKernelMemoryUsage() { + return valueOrUnlimited(memory, () -> memory.getKernelMemoryUsage()); } - public long getMemoryAndSwapMaxUsage() { - if (memory != null && !memory.isSwapEnabled()) { - return getMemoryMaxUsage(); - } - return getLongValue(memory, "memory.memsw.max_usage_in_bytes"); + @Override + public long getTcpMemoryFailCount() { + return valueOrUnlimited(memory, () -> memory.getTcpMemoryFailCount()); } - public long getMemoryAndSwapUsage() { - if (memory != null && !memory.isSwapEnabled()) { - return getMemoryUsage(); - } - return getLongValue(memory, "memory.memsw.usage_in_bytes"); + @Override + public long getTcpMemoryMaxUsage() { + return valueOrUnlimited(memory, () -> memory.getTcpMemoryMaxUsage()); } + @Override public Boolean isMemoryOOMKillEnabled() { - long val = CgroupV1SubsystemController.getLongEntry(memory, "memory.oom_control", "oom_kill_disable"); - return (val == 0); + return memory == null ? false : memory.isMemoryOOMKillEnabled(); } + @Override public long getMemorySoftLimit() { - return CgroupV1SubsystemController.longValOrUnlimited(getLongValue(memory, "memory.soft_limit_in_bytes")); + return valueOrUnlimited(memory, () -> memory.getMemorySoftLimit(CgroupMetrics.getTotalMemorySize0())); } /***************************************************************** * pids subsystem ****************************************************************/ + @Override public long getPidsMax() { String pidsMaxStr = CgroupSubsystemController.getStringValue(pids, "pids.max"); return CgroupSubsystem.limitFromString(pidsMaxStr); } + @Override public long getPidsCurrent() { return getLongValue(pids, "pids.current"); } @@ -418,10 +426,12 @@ public long getPidsCurrent() { ****************************************************************/ + @Override public long getBlkIOServiceCount() { return CgroupV1SubsystemController.getLongEntry(blkio, "blkio.throttle.io_service_bytes", "Total"); } + @Override public long getBlkIOServiced() { return CgroupV1SubsystemController.getLongEntry(blkio, "blkio.throttle.io_serviced", "Total"); } diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java index 051b4da5f78d8..74c447a424f45 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java @@ -33,16 +33,19 @@ public class CgroupV1SubsystemController implements CgroupSubsystemController { private static final double DOUBLE_RETVAL_UNLIMITED = CgroupSubsystem.LONG_RETVAL_UNLIMITED; // Values returned larger than this number are unlimited. static long UNLIMITED_MIN = 0x7FFFFFFFFF000000L; - String root; - String mountPoint; - String path; + private final String root; + private final String mountPoint; + private String path; + private String cgroupPath; public CgroupV1SubsystemController(String root, String mountPoint) { this.root = root; this.mountPoint = mountPoint; } + @Override public void setPath(String cgroupPath) { + this.cgroupPath = cgroupPath; if (root != null && cgroupPath != null) { if (root.equals("/")) { if (!cgroupPath.equals("/")) { @@ -68,6 +71,15 @@ public void setPath(String cgroupPath) { } } + @Override + public String getCgroupPath() { + return cgroupPath; + } + + protected String getRoot() { + return root; + } + @Override public String path() { return path; diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2CpuSubSystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2CpuSubSystemController.java new file mode 100644 index 0000000000000..8e7f44032085a --- /dev/null +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2CpuSubSystemController.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2024, Red Hat Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.platform.cgroupv2; + +import java.util.concurrent.TimeUnit; + +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.CgroupSubsystemController; +import jdk.internal.platform.CgroupSubsystemCpuController; + +public class CgroupV2CpuSubSystemController extends CgroupV2SubsystemController + implements CgroupSubsystemCpuController { + + private static final String ROOT_PATH = "/"; + private static final int PER_CPU_SHARES = 1024; + + public CgroupV2CpuSubSystemController(String mountPath, String cgroupPath) { + super(mountPath, cgroupPath); + } + + @Override + public long getCpuPeriod() { + return getFromCpuMax(1 /* $PERIOD index */); + } + + @Override + public long getCpuQuota() { + return getFromCpuMax(0 /* $MAX index */); + } + + @Override + public long getCpuShares() { + long sharesRaw = CgroupV2Subsystem.getLongVal(this, "cpu.weight"); + if (sharesRaw == 100 || sharesRaw <= 0) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + int shares = (int)sharesRaw; + // CPU shares (OCI) value needs to get translated into + // a proper Cgroups v2 value. See: + // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller + // + // Use the inverse of (x == OCI value, y == cgroupsv2 value): + // ((262142 * y - 1)/9999) + 2 = x + // + int x = 262142 * shares - 1; + double frac = x/9999.0; + x = ((int)frac) + 2; + if ( x <= PER_CPU_SHARES ) { + return PER_CPU_SHARES; // mimic cgroups v1 + } + int f = x/PER_CPU_SHARES; + int lower_multiple = f * PER_CPU_SHARES; + int upper_multiple = (f + 1) * PER_CPU_SHARES; + int distance_lower = Math.max(lower_multiple, x) - Math.min(lower_multiple, x); + int distance_upper = Math.max(upper_multiple, x) - Math.min(upper_multiple, x); + x = distance_lower <= distance_upper ? lower_multiple : upper_multiple; + return x; + } + + @Override + public long getCpuNumPeriods() { + return CgroupV2SubsystemController.getLongEntry(this, "cpu.stat", "nr_periods"); + } + + @Override + public long getCpuNumThrottled() { + return CgroupV2SubsystemController.getLongEntry(this, "cpu.stat", "nr_throttled"); + } + + @Override + public long getCpuThrottledTime() { + long micros = CgroupV2SubsystemController.getLongEntry(this, "cpu.stat", "throttled_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + private long getFromCpuMax(int tokenIdx) { + String cpuMaxRaw = CgroupSubsystemController.getStringValue(this, "cpu.max"); + if (cpuMaxRaw == null) { + // likely file not found + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + // $MAX $PERIOD + String[] tokens = cpuMaxRaw.split("\\s+"); + if (tokens.length != 2) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + String quota = tokens[tokenIdx]; + return CgroupSubsystem.limitFromString(quota); + } + + /** + * Implementations which use the cpu controller, but are cpuacct on cg v1 + */ + + public long getCpuUsage() { + long micros = CgroupV2SubsystemController.getLongEntry(this, "cpu.stat", "usage_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + public long getCpuUserUsage() { + long micros = CgroupV2SubsystemController.getLongEntry(this, "cpu.stat", "user_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + public long getCpuSystemUsage() { + long micros = CgroupV2SubsystemController.getLongEntry(this, "cpu.stat", "system_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + @Override + public boolean needsAdjustment() { + return !ROOT_PATH.equals(getCgroupPath()); + } + +} diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2MemorySubSystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2MemorySubSystemController.java new file mode 100644 index 0000000000000..dd4145280f615 --- /dev/null +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2MemorySubSystemController.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024, Red Hat Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.platform.cgroupv2; + +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.CgroupSubsystemController; +import jdk.internal.platform.CgroupSubsystemMemoryController; + +public class CgroupV2MemorySubSystemController extends CgroupV2SubsystemController implements CgroupSubsystemMemoryController { + + private static final String ROOT_PATH = "/"; + private static final long NO_SWAP = 0; + + CgroupV2MemorySubSystemController(String mountPath, String cgroupPath) { + super(mountPath, cgroupPath); + } + + @Override + public long getMemoryLimit(long physicalMemory) { + String strVal = CgroupSubsystemController.getStringValue(this, "memory.max"); + long limit = CgroupSubsystem.limitFromString(strVal); + // Return unlimited if we already determined it as unlimited or if + // the limit exceeds the host physical memory limit + if (limit == CgroupSubsystem.LONG_RETVAL_UNLIMITED || + limit >= physicalMemory) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return limit; + } + + @Override + public long getMemoryUsage() { + return CgroupV2Subsystem.getLongVal(this, "memory.current"); + } + + /** + * Note that for cgroups v2 the actual limits set for swap and + * memory live in two different files, memory.swap.max and memory.max + * respectively. In order to properly report a cgroup v1 like + * compound value we need to sum the two values. Setting a swap limit + * without also setting a memory limit is not allowed. + */ + @Override + public long getMemoryAndSwapLimit(long hostMemory, long hostSwap) { + String strVal = CgroupSubsystemController.getStringValue(this, "memory.swap.max"); + // We only get a null string when file memory.swap.max doesn't exist. + // In that case we return the memory limit without any swap. + if (strVal == null) { + return getMemoryLimit(hostMemory); + } + long swapLimit = CgroupSubsystem.limitFromString(strVal); + if (swapLimit >= 0) { + long memoryLimit = getMemoryLimit(hostMemory); + if (memoryLimit < 0) { + // We have the case where the memory limit is larger than the + // host memory size: swapLimit >= 0 && memoryLimit < 0 + // In that case reset the memory to the host memory for this + // calculation + memoryLimit = hostMemory; + } + long memSwapLimit = memoryLimit + swapLimit; + long hostBound = hostMemory + hostSwap; + // The limit must not exceed host values + if (memSwapLimit >= hostBound) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return memSwapLimit; + } + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; // unlimited swap case + } + + /** + * Note that for cgroups v2 the actual values set for swap usage and + * memory usage live in two different files, memory.current and memory.swap.current + * respectively. In order to properly report a cgroup v1 like + * compound value we need to sum the two values. Setting a swap limit + * without also setting a memory limit is not allowed. + */ + @Override + public long getMemoryAndSwapUsage() { + long memoryUsage = getMemoryUsage(); + if (memoryUsage >= 0) { + // If file memory.swap.current doesn't exist, only return the regular + // memory usage (without swap). Thus, use default value of NO_SWAP. + long swapUsage = CgroupV2Subsystem.getLongVal(this, "memory.swap.current", NO_SWAP); + return memoryUsage + swapUsage; + } + return memoryUsage; // case of no memory limits + } + + @Override + public long getMemorySoftLimit(long hostMemory) { + String softLimitStr = CgroupSubsystemController.getStringValue(this, "memory.low"); + long softLimit = CgroupSubsystem.limitFromString(softLimitStr); + // Avoid for the soft limit exceeding physical memory + if (softLimit == CgroupSubsystem.LONG_RETVAL_UNLIMITED || + softLimit >= hostMemory) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return softLimit; + } + + @Override + public long getMemoryFailCount() { + return CgroupV2SubsystemController.getLongEntry(this, "memory.events", "max"); + } + + @Override + public long getTcpMemoryUsage() { + return CgroupV2SubsystemController.getLongEntry(this, "memory.stat", "sock"); + } + + @Override + public boolean needsAdjustment() { + return !ROOT_PATH.equals(getCgroupPath()); + } + +} diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java index ddb4d8e271832..2ebc4964f2b37 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java @@ -28,11 +28,13 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Paths; -import java.util.concurrent.TimeUnit; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import jdk.internal.platform.CgroupInfo; +import jdk.internal.platform.CgroupMetrics; import jdk.internal.platform.CgroupSubsystem; import jdk.internal.platform.CgroupSubsystemController; import jdk.internal.platform.CgroupUtil; @@ -42,25 +44,29 @@ public class CgroupV2Subsystem implements CgroupSubsystem { private static volatile CgroupV2Subsystem INSTANCE; private static final long[] LONG_ARRAY_NOT_SUPPORTED = null; private static final int[] INT_ARRAY_UNAVAILABLE = null; - private final CgroupSubsystemController unified; private static final String PROVIDER_NAME = "cgroupv2"; - private static final int PER_CPU_SHARES = 1024; private static final Object EMPTY_STR = ""; - private static final long NO_SWAP = 0; + private final CgroupSubsystemController unified; + private final CgroupV2MemorySubSystemController memory; + private final CgroupV2CpuSubSystemController cpu; - private CgroupV2Subsystem(CgroupSubsystemController unified) { + private CgroupV2Subsystem(CgroupSubsystemController unified, + CgroupV2MemorySubSystemController memory, + CgroupV2CpuSubSystemController cpu) { this.unified = unified; + this.memory = memory; + this.cpu = cpu; } - private long getLongVal(String file, long defaultValue) { - return CgroupSubsystemController.getLongValue(unified, + static long getLongVal(CgroupSubsystemController controller, String file, long defaultValue) { + return CgroupSubsystemController.getLongValue(controller, file, CgroupV2SubsystemController::convertStringToLong, defaultValue); } - private long getLongVal(String file) { - return getLongVal(file, CgroupSubsystem.LONG_RETVAL_UNLIMITED); + static long getLongVal(CgroupSubsystemController controller, String file) { + return getLongVal(controller, file, CgroupSubsystem.LONG_RETVAL_UNLIMITED); } /** @@ -74,12 +80,9 @@ private long getLongVal(String file) { * * @return A singleton CgroupSubsystem instance, never null. */ - public static CgroupSubsystem getInstance(CgroupInfo anyController) { + public static CgroupSubsystem getInstance(Map infos) { if (INSTANCE == null) { - CgroupSubsystemController unified = new CgroupV2SubsystemController( - anyController.getMountPoint(), - anyController.getCgroupPath()); - CgroupV2Subsystem tmpCgroupSystem = new CgroupV2Subsystem(unified); + CgroupV2Subsystem tmpCgroupSystem = initSubSystem(infos); synchronized (CgroupV2Subsystem.class) { if (INSTANCE == null) { INSTANCE = tmpCgroupSystem; @@ -89,6 +92,28 @@ public static CgroupSubsystem getInstance(CgroupInfo anyController) { return INSTANCE; } + private static CgroupV2Subsystem initSubSystem(Map infos) { + // For unified it doesn't matter which controller we pick. + CgroupInfo anyController = infos.values().iterator().next(); + Objects.requireNonNull(anyController); + // Memory and cpu limits might not be at the leaf, so we ought to + // iterate the path up the hierarchy and chose the one with the lowest + // limit. Therefore, we have specific instances for memory and cpu. + // Other controllers ought to be separate too, but it hasn't yet come up. + CgroupSubsystemController unified = new CgroupV2SubsystemController( + anyController.getMountPoint(), + anyController.getCgroupPath()); + CgroupV2MemorySubSystemController memory = new CgroupV2MemorySubSystemController( + anyController.getMountPoint(), + anyController.getCgroupPath()); + CgroupUtil.adjustController(memory); + CgroupV2CpuSubSystemController cpu = new CgroupV2CpuSubSystemController( + anyController.getMountPoint(), + anyController.getCgroupPath()); + CgroupUtil.adjustController(cpu); + return new CgroupV2Subsystem(unified, memory, cpu); + } + @Override public String getProvider() { return PROVIDER_NAME; @@ -96,11 +121,7 @@ public String getProvider() { @Override public long getCpuUsage() { - long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "usage_usec"); - if (micros < 0) { - return micros; - } - return TimeUnit.MICROSECONDS.toNanos(micros); + return cpu.getCpuUsage(); } @Override @@ -110,93 +131,42 @@ public long[] getPerCpuUsage() { @Override public long getCpuUserUsage() { - long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "user_usec"); - if (micros < 0) { - return micros; - } - return TimeUnit.MICROSECONDS.toNanos(micros); + return cpu.getCpuUserUsage(); } @Override public long getCpuSystemUsage() { - long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "system_usec"); - if (micros < 0) { - return micros; - } - return TimeUnit.MICROSECONDS.toNanos(micros); + return cpu.getCpuSystemUsage(); } @Override public long getCpuPeriod() { - return getFromCpuMax(1 /* $PERIOD index */); + return cpu.getCpuPeriod(); } @Override public long getCpuQuota() { - return getFromCpuMax(0 /* $MAX index */); - } - - private long getFromCpuMax(int tokenIdx) { - String cpuMaxRaw = CgroupSubsystemController.getStringValue(unified, "cpu.max"); - if (cpuMaxRaw == null) { - // likely file not found - return CgroupSubsystem.LONG_RETVAL_UNLIMITED; - } - // $MAX $PERIOD - String[] tokens = cpuMaxRaw.split("\\s+"); - if (tokens.length != 2) { - return CgroupSubsystem.LONG_RETVAL_UNLIMITED; - } - String quota = tokens[tokenIdx]; - return CgroupSubsystem.limitFromString(quota); + return cpu.getCpuQuota(); } @Override public long getCpuShares() { - long sharesRaw = getLongVal("cpu.weight"); - if (sharesRaw == 100 || sharesRaw <= 0) { - return CgroupSubsystem.LONG_RETVAL_UNLIMITED; - } - int shares = (int)sharesRaw; - // CPU shares (OCI) value needs to get translated into - // a proper Cgroups v2 value. See: - // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller - // - // Use the inverse of (x == OCI value, y == cgroupsv2 value): - // ((262142 * y - 1)/9999) + 2 = x - // - int x = 262142 * shares - 1; - double frac = x/9999.0; - x = ((int)frac) + 2; - if ( x <= PER_CPU_SHARES ) { - return PER_CPU_SHARES; // mimic cgroups v1 - } - int f = x/PER_CPU_SHARES; - int lower_multiple = f * PER_CPU_SHARES; - int upper_multiple = (f + 1) * PER_CPU_SHARES; - int distance_lower = Math.max(lower_multiple, x) - Math.min(lower_multiple, x); - int distance_upper = Math.max(upper_multiple, x) - Math.min(upper_multiple, x); - x = distance_lower <= distance_upper ? lower_multiple : upper_multiple; - return x; + return cpu.getCpuShares(); } @Override public long getCpuNumPeriods() { - return CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "nr_periods"); + return cpu.getCpuNumPeriods(); } @Override public long getCpuNumThrottled() { - return CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "nr_throttled"); + return cpu.getCpuNumThrottled(); } @Override public long getCpuThrottledTime() { - long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "throttled_usec"); - if (micros < 0) { - return micros; - } - return TimeUnit.MICROSECONDS.toNanos(micros); + return cpu.getCpuThrottledTime(); } @Override @@ -237,72 +207,40 @@ private int[] getCpuSet(String cpuSetVal) { @Override public long getMemoryFailCount() { - return CgroupV2SubsystemController.getLongEntry(unified, "memory.events", "max"); + return memory.getMemoryFailCount(); } @Override public long getMemoryLimit() { - String strVal = CgroupSubsystemController.getStringValue(unified, "memory.max"); - return CgroupSubsystem.limitFromString(strVal); + return memory.getMemoryLimit(CgroupMetrics.getTotalMemorySize0()); } @Override public long getMemoryUsage() { - return getLongVal("memory.current"); + return memory.getMemoryUsage(); } @Override public long getTcpMemoryUsage() { - return CgroupV2SubsystemController.getLongEntry(unified, "memory.stat", "sock"); + return memory.getTcpMemoryUsage(); } - /** - * Note that for cgroups v2 the actual limits set for swap and - * memory live in two different files, memory.swap.max and memory.max - * respectively. In order to properly report a cgroup v1 like - * compound value we need to sum the two values. Setting a swap limit - * without also setting a memory limit is not allowed. - */ + @Override public long getMemoryAndSwapLimit() { - String strVal = CgroupSubsystemController.getStringValue(unified, "memory.swap.max"); - // We only get a null string when file memory.swap.max doesn't exist. - // In that case we return the memory limit without any swap. - if (strVal == null) { - return getMemoryLimit(); - } - long swapLimit = CgroupSubsystem.limitFromString(strVal); - if (swapLimit >= 0) { - long memoryLimit = getMemoryLimit(); - assert memoryLimit >= 0; - return memoryLimit + swapLimit; - } - return swapLimit; + return memory.getMemoryAndSwapLimit(CgroupMetrics.getTotalMemorySize0(), + CgroupMetrics.getTotalSwapSize0()); } - /** - * Note that for cgroups v2 the actual values set for swap usage and - * memory usage live in two different files, memory.current and memory.swap.current - * respectively. In order to properly report a cgroup v1 like - * compound value we need to sum the two values. Setting a swap limit - * without also setting a memory limit is not allowed. - */ + @Override public long getMemoryAndSwapUsage() { - long memoryUsage = getMemoryUsage(); - if (memoryUsage >= 0) { - // If file memory.swap.current doesn't exist, only return the regular - // memory usage (without swap). Thus, use default value of NO_SWAP. - long swapUsage = getLongVal("memory.swap.current", NO_SWAP); - return memoryUsage + swapUsage; - } - return memoryUsage; // case of no memory limits + return memory.getMemoryAndSwapUsage(); } @Override public long getMemorySoftLimit() { - String softLimitStr = CgroupSubsystemController.getStringValue(unified, "memory.low"); - return CgroupSubsystem.limitFromString(softLimitStr); + return memory.getMemorySoftLimit(CgroupMetrics.getTotalMemorySize0()); } @Override @@ -313,7 +251,7 @@ public long getPidsMax() { @Override public long getPidsCurrent() { - return getLongVal("pids.current"); + return getLongVal(unified, "pids.current"); } @Override diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java index c9a3ff4f96f5a..4d0fb3f29c8f3 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java @@ -32,9 +32,13 @@ public class CgroupV2SubsystemController implements CgroupSubsystemController { - private final String path; + private final String mountPath; + private String cgroupPath; + private String path; public CgroupV2SubsystemController(String mountPath, String cgroupPath) { + this.mountPath = mountPath; + this.cgroupPath = cgroupPath; this.path = Paths.get(mountPath, cgroupPath).toString(); } @@ -43,6 +47,17 @@ public String path() { return path; } + @Override + public void setPath(String cgroupPath) { + this.cgroupPath = cgroupPath; + this.path = Paths.get(this.mountPath, cgroupPath).toString(); + } + + @Override + public String getCgroupPath() { + return this.cgroupPath; + } + public static long convertStringToLong(String strval) { return CgroupSubsystemController.convertStringToLong(strval, CgroupSubsystem.LONG_RETVAL_UNLIMITED /* overflow retval */, diff --git a/src/java.base/linux/native/libjava/CgroupMetrics.c b/src/java.base/linux/native/libjava/CgroupMetrics.c index e2f98633459be..8eff6cdb8e263 100644 --- a/src/java.base/linux/native/libjava/CgroupMetrics.c +++ b/src/java.base/linux/native/libjava/CgroupMetrics.c @@ -62,3 +62,12 @@ Java_jdk_internal_platform_CgroupMetrics_getTotalSwapSize0 } return (jlong)(si.totalswap * si.mem_unit); } + +JNIEXPORT jint JNICALL +Java_jdk_internal_platform_CgroupMetrics_getTotalCpuCount0 + (JNIEnv *env, jclass ignored) +{ + // FIXME: This ought to use some JVM api to query host CPUs + int retval = sysconf(_SC_NPROCESSORS_ONLN); + return (jint)(retval); +} diff --git a/test/jdk/jdk/internal/platform/cgroup/CgroupSubsystemCpuControllerTest.java b/test/jdk/jdk/internal/platform/cgroup/CgroupSubsystemCpuControllerTest.java new file mode 100644 index 0000000000000..1879f643dd6fa --- /dev/null +++ b/test/jdk/jdk/internal/platform/cgroup/CgroupSubsystemCpuControllerTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import jdk.internal.platform.CgroupMetrics; +import jdk.internal.platform.CgroupSubsystemCpuController; +import jdk.internal.platform.CgroupUtil; + +/* + * @test + * @summary Unit test for controller adjustment for cpu controllers via CgroupUtil. + * @key cgroups + * @requires os.family == "linux" + * @modules java.base/jdk.internal.platform + * @run junit/othervm CgroupSubsystemCpuControllerTest + */ +public class CgroupSubsystemCpuControllerTest { + + private static final CpuLimit UNLIMITED = new CpuLimit(10_000, -1); + @Test + public void noAdjustementNeededIsNoop() { + MockCgroupSubsystemCpuController cpu = new MockCgroupSubsystemCpuController(false, null); + CgroupUtil.adjustController(cpu); + + assertNull(cpu.getCgroupPath()); + } + + @Test + public void adjustmentWithNoLowerLimit() { + String cgroupPath = "/a/b/c/d"; + Map limits = Map.of(cgroupPath, UNLIMITED, + "/a/b/c", UNLIMITED, + "/a/b", UNLIMITED, + "/a", UNLIMITED, + "/", UNLIMITED); + MockCgroupSubsystemCpuController cpu = new MockCgroupSubsystemCpuController(true, cgroupPath, limits); + CgroupUtil.adjustController(cpu); + + assertNotNull(cpu.getCgroupPath()); + assertEquals(cgroupPath, cpu.getCgroupPath()); + assertEquals(5, cpu.setCgroupPaths.size()); + List expectedList = List.of( "/a/b/c", "/a/b", "/a", "/", cgroupPath); + assertEquals(expectedList, cpu.setCgroupPaths); + } + + @Test + public void adjustedLimitLowerInHierarchy() { + String cgroupPath = "/a/b/c/d"; + String expectedPath = "/a/b"; + long hostCpus = CgroupMetrics.getTotalCpuCount0(); + assumeTrue(hostCpus > 2); // Skip on systems < 3 host cpus. + Map limits = Map.of(cgroupPath, UNLIMITED, + "/a/b/c", UNLIMITED, + expectedPath, new CpuLimit(10_000, 20_000) /* two cores */, + "/a", UNLIMITED, + "/", UNLIMITED); + MockCgroupSubsystemCpuController cpu = new MockCgroupSubsystemCpuController(true, cgroupPath, limits); + CgroupUtil.adjustController(cpu); + + assertNotNull(cpu.getCgroupPath()); + assertEquals(expectedPath, cpu.getCgroupPath()); + assertEquals(2, cpu.setCgroupPaths.size()); + List expectedList = List.of("/a/b/c", "/a/b"); + assertEquals(expectedList, cpu.setCgroupPaths); + } + + private static class MockCgroupSubsystemCpuController implements CgroupSubsystemCpuController { + + private String cgroupPath; + private final boolean needsAdjustment; + private final List setCgroupPaths = new ArrayList<>(); + private final Map limits; + + private MockCgroupSubsystemCpuController(boolean needsAdjustment, String cgroupPath) { + this(needsAdjustment, cgroupPath, null); + } + + private MockCgroupSubsystemCpuController(boolean needsAdjustment, String cgroupPath, Map limits) { + this.needsAdjustment = needsAdjustment; + this.cgroupPath = cgroupPath; + this.limits = limits; + } + + + @Override + public String path() { + return null; // doesn't matter + } + + @Override + public void setPath(String cgroupPath) { + this.setCgroupPaths.add(cgroupPath); + this.cgroupPath = cgroupPath; + } + + @Override + public String getCgroupPath() { + return cgroupPath; + } + + @Override + public long getCpuPeriod() { + CpuLimit l = limits.get(cgroupPath); + return l.getPeriod(); + } + + @Override + public long getCpuQuota() { + CpuLimit l = limits.get(cgroupPath); + return l.getQuota(); + } + + @Override + public long getCpuShares() { + // Doesn't matter + return 0; + } + + @Override + public long getCpuNumPeriods() { + // Doesn't matter + return 0; + } + + @Override + public long getCpuNumThrottled() { + // Doesn't matter + return 0; + } + + @Override + public long getCpuThrottledTime() { + // Doesn't matter + return 0; + } + + @Override + public boolean needsAdjustment() { + return needsAdjustment; + } + + } + + private static record CpuLimit(long period, long quota) { + long getPeriod() { + return period; + } + + long getQuota() { + return quota; + } + } +} diff --git a/test/jdk/jdk/internal/platform/cgroup/CgroupSubsystemMemoryControllerTest.java b/test/jdk/jdk/internal/platform/cgroup/CgroupSubsystemMemoryControllerTest.java new file mode 100644 index 0000000000000..cdf9078b83289 --- /dev/null +++ b/test/jdk/jdk/internal/platform/cgroup/CgroupSubsystemMemoryControllerTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Unit test for controller adjustment for memory controllers via CgroupUtil. + * @key cgroups + * @requires os.family == "linux" + * @modules java.base/jdk.internal.platform + * @run junit/othervm CgroupSubsystemMemoryControllerTest + */ + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import jdk.internal.platform.CgroupSubsystemMemoryController; +import jdk.internal.platform.CgroupUtil; + +public class CgroupSubsystemMemoryControllerTest { + + private static final Long UNLIMITED = -1L; + private static final Long FIVE_HUNDRED_MB = (long)500 * 1024 * 1024 * 1024; + + @Test + public void noAdjustementNeededIsNoop() { + MockCgroupSubsystemMemoryController memory = new MockCgroupSubsystemMemoryController(false, null); + CgroupUtil.adjustController(memory); + + assertNull(memory.getCgroupPath()); + } + + @Test + public void adjustmentWithNoLowerLimit() { + String cgroupPath = "/a/b/c/d"; + Map limits = Map.of(cgroupPath, UNLIMITED, + "/a/b/c", UNLIMITED, + "/a/b", UNLIMITED, + "/a", UNLIMITED, + "/", UNLIMITED); + MockCgroupSubsystemMemoryController memory = new MockCgroupSubsystemMemoryController(true, cgroupPath, limits); + CgroupUtil.adjustController(memory); + + assertNotNull(memory.getCgroupPath()); + assertEquals(cgroupPath, memory.getCgroupPath()); + assertEquals(5, memory.setCgroupPaths.size()); + List expectedList = List.of( "/a/b/c", "/a/b", "/a", "/", cgroupPath); + assertEquals(expectedList, memory.setCgroupPaths); + } + + @Test + public void adjustedLimitAtRoot() { + String cgroupPath = "/a/b/c/d"; + String expectedPath = "/"; + Map limits = Map.of(cgroupPath, UNLIMITED, + "/a/b/c", UNLIMITED, + "/a/b", UNLIMITED, + "/a", UNLIMITED, + expectedPath, FIVE_HUNDRED_MB); + MockCgroupSubsystemMemoryController cpu = new MockCgroupSubsystemMemoryController(true, cgroupPath, limits); + CgroupUtil.adjustController(cpu); + + assertNotNull(cpu.getCgroupPath()); + assertEquals(expectedPath, cpu.getCgroupPath()); + assertEquals(4, cpu.setCgroupPaths.size()); + List expectedList = List.of("/a/b/c", "/a/b", "/a", "/"); + assertEquals(expectedList, cpu.setCgroupPaths); + } + + private static class MockCgroupSubsystemMemoryController implements CgroupSubsystemMemoryController { + + private String cgroupPath; + private final boolean needsAdjustment; + private final List setCgroupPaths = new ArrayList<>(); + private final Map limits; + + private MockCgroupSubsystemMemoryController(boolean needsAdjustment, String cgroupPath) { + this(needsAdjustment, cgroupPath, null); + } + + private MockCgroupSubsystemMemoryController(boolean needsAdjustment, String cgroupPath, Map limits) { + this.needsAdjustment = needsAdjustment; + this.limits = limits; + this.cgroupPath = cgroupPath; + } + + @Override + public String path() { + // doesn't matter + return null; + } + + @Override + public void setPath(String cgroupPath) { + setCgroupPaths.add(cgroupPath); + this.cgroupPath = cgroupPath; + } + + @Override + public String getCgroupPath() { + return cgroupPath; + } + + @Override + public long getMemoryLimit(long physicalMemory) { + return limits.get(cgroupPath); + } + + @Override + public long getMemoryUsage() { + // doesn't matter + return 0; + } + + @Override + public long getTcpMemoryUsage() { + // doesn't matter + return 0; + } + + @Override + public long getMemoryAndSwapLimit(long hostMemory, long hostSwap) { + // doesn't matter + return 0; + } + + @Override + public long getMemoryAndSwapUsage() { + // doesn't matter + return 0; + } + + @Override + public long getMemorySoftLimit(long hostMemory) { + // doesn't matter + return 0; + } + + @Override + public long getMemoryFailCount() { + // doesn't matter + return 0; + } + + @Override + public boolean needsAdjustment() { + return needsAdjustment; + } + + + } + +} diff --git a/test/jdk/jdk/internal/platform/cgroup/TestCgroupMetrics.java b/test/jdk/jdk/internal/platform/cgroup/TestCgroupMetrics.java index a25f31da3976f..fae74c43a4571 100644 --- a/test/jdk/jdk/internal/platform/cgroup/TestCgroupMetrics.java +++ b/test/jdk/jdk/internal/platform/cgroup/TestCgroupMetrics.java @@ -31,7 +31,6 @@ */ import jdk.test.lib.containers.cgroup.MetricsTester; -import jdk.internal.platform.Metrics; public class TestCgroupMetrics { diff --git a/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java b/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java index 2db1d6cb922a5..aaf9d8b1a19d8 100644 --- a/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java +++ b/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java @@ -214,6 +214,16 @@ public String path() { return path; } + @Override + public void setPath(String cgroupPath) { + // nothing; + } + + @Override + public String getCgroupPath() { + return null; // doesn't matter + } + } } diff --git a/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemFactory.java b/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemFactory.java index ede74b5011e4c..c4dc2c92c19dc 100644 --- a/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemFactory.java +++ b/test/jdk/jdk/internal/platform/cgroup/TestCgroupSubsystemFactory.java @@ -42,9 +42,7 @@ import jdk.internal.platform.CgroupInfo; import jdk.internal.platform.CgroupSubsystemFactory; import jdk.internal.platform.CgroupSubsystemFactory.CgroupTypeResult; -import jdk.internal.platform.CgroupV1MetricsImpl; import jdk.internal.platform.cgroupv1.CgroupV1Subsystem; -import jdk.internal.platform.Metrics; import jdk.test.lib.Utils; import jdk.test.lib.util.FileUtils; diff --git a/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java b/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java index 92b8cf282b784..6a3b32bfa5c55 100644 --- a/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java +++ b/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java @@ -21,9 +21,10 @@ * questions. */ -import com.sun.management.OperatingSystemMXBean; import java.lang.management.ManagementFactory; +import com.sun.management.OperatingSystemMXBean; + // Usage: // GetFreeSwapSpaceSize public class GetFreeSwapSpaceSize { @@ -32,6 +33,7 @@ public static void main(String[] args) { throw new RuntimeException("Unexpected arguments. Expected 4, got " + args.length); } String memoryAlloc = args[0]; + @SuppressWarnings("unused") long expectedMemory = Long.parseLong(args[1]); String memorySwapAlloc = args[2]; long expectedSwap = Long.parseLong(args[3]); diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java b/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java index 5518943a6e666..082da712c49fe 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java @@ -31,7 +31,6 @@ * @run main/timeout=360 TestDockerBasic */ -import jdk.test.lib.Utils; import jdk.test.lib.containers.docker.Common; import jdk.test.lib.containers.docker.DockerRunOptions; import jdk.test.lib.containers.docker.DockerTestUtils; diff --git a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java index 22e03293c486e..aa4b18907c621 100644 --- a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java +++ b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java @@ -38,9 +38,7 @@ import java.io.File; import java.io.FileOutputStream; import java.util.List; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import jdk.test.lib.Asserts; + import jdk.test.lib.Utils; import jdk.test.lib.containers.docker.Common; import jdk.test.lib.containers.docker.DockerRunOptions; @@ -49,7 +47,7 @@ public class TestLimitsUpdating { private static final long M = 1024 * 1024; - private static final String TARGET_CONTAINER = "limitsUpdatingJDK_" + Runtime.getRuntime().version().major(); + private static final String TARGET_CONTAINER = "limitsUpdatingJDK_" + Runtime.version().feature(); private static final String imageName = Common.imageName("limitsUpdatingJDK"); public static void main(String[] args) throws Exception { @@ -90,6 +88,7 @@ private static void testLimitUpdates() throws Exception { opts.addJavaOpts("java.base/jdk.internal.platform=ALL-UNNAMED"); final OutputAnalyzer out[] = new OutputAnalyzer[1]; Thread t1 = new Thread() { + @Override public void run() { try { out[0] = DockerTestUtils.dockerRunJava(opts).shouldHaveExitValue(0); @@ -111,6 +110,7 @@ public void run() { final List containerCommand = getContainerUpdate(300_000, 100_000, "300m"); // Run the update command so as to increase resources once the container signaled it has started. Thread t2 = new Thread() { + @Override public void run() { try { DockerTestUtils.execute(containerCommand).shouldHaveExitValue(0); diff --git a/test/jdk/jdk/internal/platform/systemd/SystemdMemoryAwarenessTest.java b/test/jdk/jdk/internal/platform/systemd/SystemdMemoryAwarenessTest.java new file mode 100644 index 0000000000000..fbca2ba25e94d --- /dev/null +++ b/test/jdk/jdk/internal/platform/systemd/SystemdMemoryAwarenessTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Memory/CPU Metrics awareness for JDK-under-test inside a systemd slice. + * @requires systemd.support + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI SystemdMemoryAwarenessTest + */ +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; + +import jdk.test.lib.containers.systemd.SystemdRunOptions; +import jdk.test.lib.containers.systemd.SystemdTestUtils; +import jdk.test.lib.containers.systemd.SystemdTestUtils.ResultFiles; +import jdk.test.whitebox.WhiteBox; + + +public class SystemdMemoryAwarenessTest { + + private static final WhiteBox wb = WhiteBox.getWhiteBox(); + private static final int HUNDRED_THOUSEND = 100_000; + private static final int GB = 1024 * 1024 * 1024; + + public static void main(String[] args) throws Exception { + testSystemSettings(); + } + + private static void testSystemSettings() throws Exception { + SystemdRunOptions opts = SystemdTestUtils.newOpts("-version"); + opts.addJavaOpts("-XshowSettings:system"); + // 1 GB memory + opts.memoryLimit(String.format("%d", 1 * GB)); + int physicalCpus = wb.hostCPUs(); + if (physicalCpus < 3) { + System.err.println("WARNING: host system only has " + physicalCpus + " expected >= 3"); + } + // 1 or 2 cores limit depending on physical CPUs + int coreLimit = Math.min(physicalCpus, 2); + System.out.println("DEBUG: Running test with a CPU limit of " + coreLimit); + opts.cpuLimit(String.format("%d%%", coreLimit * 100)); + opts.sliceName(SystemdMemoryAwarenessTest.class.getSimpleName()); + + ResultFiles files = SystemdTestUtils.buildSystemdSlices(opts); + + try { + SystemdTestUtils.systemdRunJava(opts) + .shouldHaveExitValue(0) + .shouldContain("Operating System Metrics:") + .shouldContain("Effective CPU Count: " + coreLimit) + .shouldContain(String.format("CPU Period: %dus", HUNDRED_THOUSEND)) + .shouldContain(String.format("CPU Quota: %dus", (HUNDRED_THOUSEND * coreLimit))) + .shouldContain("Memory Limit: 1.00G"); + } finally { + try { + Files.delete(files.memory()); + Files.delete(files.cpu()); + } catch (NoSuchFileException e) { + // ignore + } + } + } + +}