diff --git a/jdk/make/lib/CoreLibraries.gmk b/jdk/make/lib/CoreLibraries.gmk index 5164651d3b6..3fa7bd29ec2 100644 --- a/jdk/make/lib/CoreLibraries.gmk +++ b/jdk/make/lib/CoreLibraries.gmk @@ -165,7 +165,7 @@ endif # Make it possible to override this variable ifeq ($(OPENJDK_TARGET_OS), linux) - # Linux-only symbol Java_jdk_internal_platform_cgroupv1_Metrics_isUseContainerSupport + # Linux-only symbol Java_jdk_internal_platform_CgroupMetrics_isUseContainerSupport LIBJAVA_MAPFILE ?= $(JDK_TOPDIR)/make/mapfiles/libjava/mapfile-linux else LIBJAVA_MAPFILE ?= $(JDK_TOPDIR)/make/mapfiles/libjava/mapfile-vers diff --git a/jdk/make/mapfiles/libjava/mapfile-linux b/jdk/make/mapfiles/libjava/mapfile-linux index 509279cab9d..577ff8b62ee 100644 --- a/jdk/make/mapfiles/libjava/mapfile-linux +++ b/jdk/make/mapfiles/libjava/mapfile-linux @@ -278,7 +278,7 @@ SUNWprivate_1.1 { Java_sun_misc_VM_initialize; Java_sun_misc_VMSupport_initAgentProperties; Java_sun_misc_VMSupport_getVMTemporaryDirectory; - Java_jdk_internal_platform_cgroupv1_Metrics_isUseContainerSupport; + Java_jdk_internal_platform_CgroupMetrics_isUseContainerSupport; # ZipFile.c needs this one throwFileNotFoundException; diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupInfo.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupInfo.java new file mode 100644 index 00000000000..b436b625850 --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupInfo.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, 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; + +/** + * Data structure to hold info from /proc/self/cgroup + * + * man 7 cgroups + * + * @see CgroupSubsystemFactory + */ +class CgroupInfo { + + private final String name; + private final int hierarchyId; + private final boolean enabled; + + private CgroupInfo(String name, int hierarchyId, boolean enabled) { + this.name = name; + this.hierarchyId = hierarchyId; + this.enabled = enabled; + } + + String getName() { + return name; + } + + int getHierarchyId() { + return hierarchyId; + } + + boolean isEnabled() { + return enabled; + } + + static CgroupInfo fromCgroupsLine(String line) { + String[] tokens = line.split("\\s+"); + if (tokens.length != 4) { + return null; + } + // discard 3'rd field, num_cgroups + return new CgroupInfo(tokens[0] /* name */, + Integer.parseInt(tokens[1]) /* hierarchyId */, + (Integer.parseInt(tokens[3]) == 1) /* enabled */); + } + +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupMetrics.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupMetrics.java new file mode 100644 index 00000000000..12cb4b04444 --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupMetrics.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020, 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; + +import java.util.Objects; + +public class CgroupMetrics implements Metrics { + + private final CgroupSubsystem subsystem; + + CgroupMetrics(CgroupSubsystem subsystem) { + this.subsystem = Objects.requireNonNull(subsystem); + } + + @Override + public String getProvider() { + return subsystem.getProvider(); + } + + @Override + public long getCpuUsage() { + return subsystem.getCpuUsage(); + } + + @Override + public long[] getPerCpuUsage() { + return subsystem.getPerCpuUsage(); + } + + @Override + public long getCpuUserUsage() { + return subsystem.getCpuUserUsage(); + } + + @Override + public long getCpuSystemUsage() { + return subsystem.getCpuSystemUsage(); + } + + @Override + public long getCpuPeriod() { + return subsystem.getCpuPeriod(); + } + + @Override + public long getCpuQuota() { + return subsystem.getCpuQuota(); + } + + @Override + public long getCpuShares() { + return subsystem.getCpuShares(); + } + + @Override + public long getCpuNumPeriods() { + return subsystem.getCpuNumPeriods(); + } + + @Override + public long getCpuNumThrottled() { + return subsystem.getCpuNumThrottled(); + } + + @Override + public long getCpuThrottledTime() { + return subsystem.getCpuThrottledTime(); + } + + @Override + public long getEffectiveCpuCount() { + return subsystem.getEffectiveCpuCount(); + } + + @Override + public int[] getCpuSetCpus() { + return subsystem.getCpuSetCpus(); + } + + @Override + public int[] getEffectiveCpuSetCpus() { + return subsystem.getEffectiveCpuSetCpus(); + } + + @Override + public int[] getCpuSetMems() { + return subsystem.getCpuSetMems(); + } + + @Override + public int[] getEffectiveCpuSetMems() { + return subsystem.getEffectiveCpuSetMems(); + } + + public long getMemoryFailCount() { + return subsystem.getMemoryFailCount(); + } + + @Override + public long getMemoryLimit() { + return subsystem.getMemoryLimit(); + } + + @Override + public long getMemoryUsage() { + return subsystem.getMemoryUsage(); + } + + @Override + public long getTcpMemoryUsage() { + return subsystem.getTcpMemoryUsage(); + } + + @Override + public long getMemoryAndSwapLimit() { + return subsystem.getMemoryAndSwapLimit(); + } + + @Override + public long getMemoryAndSwapUsage() { + return subsystem.getMemoryAndSwapUsage(); + } + + @Override + public long getMemorySoftLimit() { + return subsystem.getMemorySoftLimit(); + } + + @Override + public long getBlkIOServiceCount() { + return subsystem.getBlkIOServiceCount(); + } + + @Override + public long getBlkIOServiced() { + return subsystem.getBlkIOServiced(); + } + + public static Metrics getInstance() { + if (!isUseContainerSupport()) { + // Return null on -XX:-UseContainerSupport + return null; + } + return CgroupSubsystemFactory.create(); + } + + private static native boolean isUseContainerSupport(); + +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystem.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystem.java new file mode 100644 index 00000000000..5013e9c378c --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystem.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020, 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; + +/** + * Marker interface for cgroup-based metrics + * + */ +public interface CgroupSubsystem extends Metrics { + + /** + * Returned for metrics of type long if the underlying implementation + * has determined that no limit is being imposed. + */ + public static final long LONG_RETVAL_UNLIMITED = -1; + +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystemController.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystemController.java new file mode 100644 index 00000000000..5045a07cd88 --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystemController.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2020, 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; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.math.BigInteger; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Cgroup version agnostic controller logic + * + */ +public interface CgroupSubsystemController { + + public static final String EMPTY_STR = ""; + + public String path(); + + /** + * getStringValue + * + * Return the first line of the file "param" argument from the controller. + * + * TODO: Consider using weak references for caching BufferedReader object. + * + * @param controller + * @param param + * @return Returns the contents of the file specified by param or null if + * an error occurs. + */ + public static String getStringValue(CgroupSubsystemController controller, String param) { + if (controller == null) return null; + + try { + return CgroupUtil.readStringValue(controller, param); + } + catch (IOException e) { + return null; + } + + } + + /** + * Get an entry from file "param" within the "controller" directory path + * which matches string "match". Applies "conversion" to the matching line. + * + * @param controller + * @param param + * @param match + * @param conversion + * @param defaultRetval + * @return The long value as derived by applying "conversion" to the matching + * line or "defaultRetval" if there was an error or no match found. + */ + public static long getLongValueMatchingLine(CgroupSubsystemController controller, + String param, + String match, + Function conversion, + long defaultRetval) { + long retval = defaultRetval; + if (controller == null) { + return retval; + } + try { + Path filePath = Paths.get(controller.path(), param); + List lines = CgroupUtil.readAllLinesPrivileged(filePath); + for (String line : lines) { + if (line.startsWith(match)) { + retval = conversion.apply(line); + break; + } + } + } catch (IOException e) { + // Ignore. Default is unlimited. + } + return retval; + } + + /** + * Get a long value from directory "controller" and file "param", by + * applying "conversion" to the string value within the file. + * + * @param controller + * @param param + * @param conversion + * @param defaultRetval + * @return The converted long value or "defaultRetval" if there was an + * error. + */ + public static long getLongValue(CgroupSubsystemController controller, + String param, + Function conversion, + long defaultRetval) { + String strval = getStringValue(controller, param); + if (strval == null) return defaultRetval; + return conversion.apply(strval); + } + + /** + * Get a double value from file "param" within "controller". + * + * @param controller + * @param param + * @param defaultRetval + * @return The double value or "defaultRetval" if there was an error. + */ + public static double getDoubleValue(CgroupSubsystemController controller, String param, double defaultRetval) { + String strval = getStringValue(controller, param); + + if (strval == null) return defaultRetval; + + double retval = Double.parseDouble(strval); + + return retval; + } + + /** + * getLongEntry + * + * Return the long value from the line containing the string "entryname" + * within file "param" in the "controller". + * + * TODO: Consider using weak references for caching BufferedReader object. + * + * @param controller + * @param param + * @param entryname + * @return long value or "defaultRetval" if there was an error or no match + * was found. + */ + public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname, long defaultRetval) { + if (controller == null) return defaultRetval; + + try (Stream lines = CgroupUtil.readFilePrivileged(Paths.get(controller.path(), param))) { + + Optional result = lines.map(line -> line.split(" ")) + .filter(line -> (line.length == 2 && + line[0].equals(entryname))) + .map(line -> line[1]) + .findFirst(); + + return result.isPresent() ? Long.parseLong(result.get()) : defaultRetval; + } catch (UncheckedIOException e) { + return defaultRetval; + } catch (IOException e) { + return defaultRetval; + } + } + + /** + * stringRangeToIntArray + * + * Convert a string in the form of 1,3-4,6 to an array of + * integers containing all the numbers in the range. + * + * @param range + * @return int[] containing a sorted list of numbers as represented by + * the string range. Returns null if there was an error or the input + * was an empty string. + */ + public static int[] stringRangeToIntArray(String range) { + if (range == null || EMPTY_STR.equals(range)) return null; + + ArrayList results = new ArrayList<>(); + String strs[] = range.split(","); + for (String str : strs) { + if (str.contains("-")) { + String lohi[] = str.split("-"); + // validate format + if (lohi.length != 2) { + continue; + } + int lo = Integer.parseInt(lohi[0]); + int hi = Integer.parseInt(lohi[1]); + for (int i = lo; i <= hi; i++) { + results.add(i); + } + } + else { + results.add(Integer.parseInt(str)); + } + } + + // sort results + results.sort(null); + + // convert ArrayList to primitive int array + int[] ints = new int[results.size()]; + int i = 0; + for (Integer n : results) { + ints[i++] = n; + } + + return ints; + } + + /** + * Convert a number from its string representation to a long. + * + * @param strval + * @param overflowRetval + * @param defaultRetval + * @return The converted long value. "overflowRetval" is returned if the + * string representation exceeds the range of type long. + * "defaultRetval" is returned if another type of error occurred + * during conversion. + */ + public static long convertStringToLong(String strval, long overflowRetval, long defaultRetval) { + long retval = defaultRetval; + if (strval == null) return retval; + + try { + retval = Long.parseLong(strval); + } catch (NumberFormatException e) { + // For some properties (e.g. memory.limit_in_bytes, cgroups v1) we may overflow + // the range of signed long. In this case, return overflowRetval + BigInteger b = new BigInteger(strval); + if (b.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + return overflowRetval; + } + } + return retval; + } + +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java new file mode 100644 index 00000000000..8d0d87ab795 --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020, 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; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jdk.internal.platform.cgroupv1.CgroupV1Subsystem; +import jdk.internal.platform.cgroupv2.CgroupV2Subsystem; + +class CgroupSubsystemFactory { + + private static final String CPU_CTRL = "cpu"; + private static final String CPUACCT_CTRL = "cpuacct"; + private static final String CPUSET_CTRL = "cpuset"; + private static final String BLKIO_CTRL = "blkio"; + private static final String MEMORY_CTRL = "memory"; + + static CgroupMetrics create() { + Map infos = new HashMap<>(); + try { + List lines = CgroupUtil.readAllLinesPrivileged(Paths.get("/proc/cgroups")); + for (String line : lines) { + if (line.startsWith("#")) { + continue; + } + CgroupInfo info = CgroupInfo.fromCgroupsLine(line); + switch (info.getName()) { + case CPU_CTRL: infos.put(CPU_CTRL, info); break; + case CPUACCT_CTRL: infos.put(CPUACCT_CTRL, info); break; + case CPUSET_CTRL: infos.put(CPUSET_CTRL, info); break; + case MEMORY_CTRL: infos.put(MEMORY_CTRL, info); break; + case BLKIO_CTRL: infos.put(BLKIO_CTRL, info); break; + } + } + } catch (IOException e) { + return null; + } + + // For cgroups v1 all controllers need to have non-zero hierarchy id + boolean isCgroupsV2 = true; + boolean anyControllersEnabled = false; + boolean anyCgroupsV2Controller = false; + boolean anyCgroupsV1Controller = false; + for (CgroupInfo info: infos.values()) { + anyCgroupsV1Controller = anyCgroupsV1Controller || info.getHierarchyId() != 0; + anyCgroupsV2Controller = anyCgroupsV2Controller || info.getHierarchyId() == 0; + isCgroupsV2 = isCgroupsV2 && info.getHierarchyId() == 0; + anyControllersEnabled = anyControllersEnabled || info.isEnabled(); + } + + // If no controller is enabled, return no metrics. + if (!anyControllersEnabled) { + return null; + } + // The code is not ready to deal with mixed cgroups v1 and cgroups v2 + // controllers on a per-controller basis. Return no metrics in that + // case + if (anyCgroupsV1Controller && anyCgroupsV2Controller) { + return null; + } + + if (isCgroupsV2) { + CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(); + return subsystem != null ? new CgroupMetrics(subsystem) : null; + } else { + CgroupV1Subsystem subsystem = CgroupV1Subsystem.getInstance(); + return subsystem != null ? new CgroupV1MetricsImpl(subsystem) : null; + } + } +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupUtil.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupUtil.java new file mode 100644 index 00000000000..d50aa6bbb6d --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020, 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; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.List; +import java.util.stream.Stream; + +public final class CgroupUtil { + + public static Stream readFilePrivileged(Path path) throws IOException { + try { + PrivilegedExceptionAction> pea = () -> Files.lines(path); + return AccessController.doPrivileged(pea); + } catch (PrivilegedActionException e) { + unwrapIOExceptionAndRethrow(e); + throw new InternalError(e.getCause()); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IOException { + Throwable x = pae.getCause(); + if (x instanceof IOException) + throw (IOException) x; + if (x instanceof RuntimeException) + throw (RuntimeException) x; + if (x instanceof Error) + throw (Error) x; + } + + static String readStringValue(CgroupSubsystemController controller, String param) throws IOException { + PrivilegedExceptionAction pea = () -> + Files.newBufferedReader(Paths.get(controller.path(), param)); + try (BufferedReader bufferedReader = + AccessController.doPrivileged(pea)) { + String line = bufferedReader.readLine(); + return line; + } catch (PrivilegedActionException e) { + unwrapIOExceptionAndRethrow(e); + throw new InternalError(e.getCause()); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + public static List readAllLinesPrivileged(Path path) throws IOException { + try { + PrivilegedExceptionAction> pea = () -> Files.readAllLines(path); + return AccessController.doPrivileged(pea); + } catch (PrivilegedActionException e) { + unwrapIOExceptionAndRethrow(e); + throw new InternalError(e.getCause()); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupV1Metrics.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupV1Metrics.java new file mode 100644 index 00000000000..cf8230b0bfc --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupV1Metrics.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * 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; + +/** + * + * Cgroup v1 extensions to the Metrics interface. Linux, only. + * + */ +public interface CgroupV1Metrics extends Metrics { + + /** + * Returns the largest amount of physical memory, in bytes, that + * have been allocated in the Isolation Group. + * + * @return The largest amount of memory in bytes or -1 if this + * metric is not available. Returns -2 if this metric is not + * supported. + * + */ + public long getMemoryMaxUsage(); + + /** + * Returns the number of times that kernel memory requests in the + * Isolation Group have exceeded the kernel memory limit. + * + * @return The number of exceeded requests or -1 if metric + * is not available. + * + */ + public long getKernelMemoryFailCount(); + + /** + * Returns the maximum amount of kernel physical memory, in bytes, that + * can be allocated in the Isolation Group. + * + * @return The maximum amount of memory in bytes or -1 if + * there is no limit set. + * + */ + public long getKernelMemoryLimit(); + + /** + * Returns the largest amount of kernel physical memory, in bytes, that + * have been allocated in the Isolation Group. + * + * @return The largest amount of memory in bytes or -1 if this + * metric is not available. + * + */ + public long getKernelMemoryMaxUsage(); + + /** + * Returns the amount of kernel physical memory, in bytes, that + * is currently allocated in the current Isolation Group. + * + * @return The amount of memory in bytes allocated or -1 if this + * metric is not available. + * + */ + public long getKernelMemoryUsage(); + + /** + * Returns the number of times that networking memory requests in the + * Isolation Group have exceeded the kernel memory limit. + * + * @return The number of exceeded requests or -1 if the metric + * is not available. + * + */ + public long getTcpMemoryFailCount(); + + /** + * Returns the maximum amount of networking physical memory, in bytes, + * that can be allocated in the Isolation Group. + * + * @return The maximum amount of memory in bytes or -1 if + * there is no limit. + * + */ + public long getTcpMemoryLimit(); + + /** + * Returns the largest amount of networking physical memory, in bytes, + * that have been allocated in the Isolation Group. + * + * @return The largest amount of memory in bytes or -1 if this + * metric is not available. + * + */ + public long getTcpMemoryMaxUsage(); + + /** + * Returns the number of times that user memory requests in the + * Isolation Group have exceeded the memory + swap limit. + * + * @return The number of exceeded requests or -1 if the metric + * is not available. + * + */ + public long getMemoryAndSwapFailCount(); + + /** + * Returns the largest amount of physical memory and swap space, + * in bytes, that have been allocated in the Isolation Group. + * + * @return The largest amount of memory in bytes or -1 if this + * metric is not available. + * + */ + public long getMemoryAndSwapMaxUsage(); + + /** + * Returns the state of the Operating System Out of Memory termination + * policy. + * + * @return Returns true if operating system will terminate processes + * in the Isolation Group that exceed the amount of available + * memory, otherwise false. null will be returned if this + * capability is not available on the current operating system. + * + */ + public Boolean isMemoryOOMKillEnabled(); + + /** + * Returns the (attempts per second * 1000), if enabled, that the + * operating system tries to satisfy a memory request for any + * process in the current Isolation Group when no free memory is + * readily available. Use {@link #isCpuSetMemoryPressureEnabled()} to + * determine if this support is enabled. + * + * @return Memory pressure or 0 if not enabled or -1 if metric is not + * available. + * + */ + public double getCpuSetMemoryPressure(); + + /** + * Returns the state of the memory pressure detection support. + * + * @return true if support is available and enabled. false otherwise. + * + */ + public Boolean isCpuSetMemoryPressureEnabled(); +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/CgroupV1MetricsImpl.java b/jdk/src/linux/classes/jdk/internal/platform/CgroupV1MetricsImpl.java new file mode 100644 index 00000000000..443b58f1f9d --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/CgroupV1MetricsImpl.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020, 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; + +/** + * Cgroup v1 Metrics extensions + * + */ +public class CgroupV1MetricsImpl extends CgroupMetrics implements CgroupV1Metrics { + + private final CgroupV1Metrics metrics; + + CgroupV1MetricsImpl(CgroupV1Metrics metrics) { + super((CgroupSubsystem)metrics); + this.metrics = metrics; + } + + @Override + public long getMemoryMaxUsage() { + return metrics.getMemoryMaxUsage(); + } + + @Override + public long getKernelMemoryFailCount() { + return metrics.getKernelMemoryFailCount(); + } + + @Override + public long getKernelMemoryLimit() { + return metrics.getKernelMemoryLimit(); + } + + @Override + public long getKernelMemoryMaxUsage() { + return metrics.getKernelMemoryMaxUsage(); + } + + @Override + public long getKernelMemoryUsage() { + return metrics.getKernelMemoryUsage(); + } + + @Override + public long getTcpMemoryFailCount() { + return metrics.getTcpMemoryFailCount(); + } + + @Override + public long getTcpMemoryLimit() { + return metrics.getTcpMemoryLimit(); + } + + @Override + public long getTcpMemoryMaxUsage() { + return metrics.getTcpMemoryMaxUsage(); + } + + @Override + public long getMemoryAndSwapFailCount() { + return metrics.getMemoryAndSwapFailCount(); + } + + @Override + public long getMemoryAndSwapMaxUsage() { + return metrics.getMemoryAndSwapMaxUsage(); + } + + @Override + public Boolean isMemoryOOMKillEnabled() { + return metrics.isMemoryOOMKillEnabled(); + } + + @Override + public double getCpuSetMemoryPressure() { + return metrics.getCpuSetMemoryPressure(); + } + + @Override + public Boolean isCpuSetMemoryPressureEnabled() { + return metrics.isCpuSetMemoryPressureEnabled(); + } + +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java new file mode 100644 index 00000000000..98bc78f6a2e --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1MemorySubSystemController.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, 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; + +public class CgroupV1MemorySubSystemController extends CgroupV1SubsystemController { + + private boolean hierarchical; + private boolean swapenabled; + + public CgroupV1MemorySubSystemController(String root, String mountPoint) { + super(root, mountPoint); + } + + boolean isHierarchical() { + return hierarchical; + } + + void setHierarchical(boolean hierarchical) { + this.hierarchical = hierarchical; + } + + boolean isSwapEnabled() { + return swapenabled; + } + + void setSwapEnabled(boolean swapenabled) { + this.swapenabled = swapenabled; + } +} \ No newline at end of file diff --git a/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/Metrics.java b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java similarity index 55% rename from jdk/src/linux/classes/jdk/internal/platform/cgroupv1/Metrics.java rename to jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java index 6b666ae5355..ca67138295d 100644 --- a/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/Metrics.java +++ b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1Subsystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -19,7 +19,6 @@ * 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. */ @@ -28,44 +27,37 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.stream.Stream; -import jdk.internal.platform.cgroupv1.SubSystem.MemorySubSystem; - -public class Metrics implements jdk.internal.platform.Metrics { - private MemorySubSystem memory; - private SubSystem cpu; - private SubSystem cpuacct; - private SubSystem cpuset; - private SubSystem blkio; +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 CgroupV1SubsystemController cpuacct; + private CgroupV1SubsystemController cpuset; + private CgroupV1SubsystemController blkio; private boolean activeSubSystems; - // Values returned larger than this number are unlimited. - static long unlimited_minimum = 0x7FFFFFFFFF000000L; - - private static final Metrics INSTANCE = initContainerSubSystems(); + private static final CgroupV1Subsystem INSTANCE = initSubSystem(); private static final String PROVIDER_NAME = "cgroupv1"; - private Metrics() { + private CgroupV1Subsystem() { activeSubSystems = false; } - public static Metrics getInstance() { + public static CgroupV1Subsystem getInstance() { return INSTANCE; } - private static Metrics initContainerSubSystems() { - if (!isUseContainerSupport()) { - return null; - } - Metrics metrics = new Metrics(); + private static CgroupV1Subsystem initSubSystem() { + CgroupV1Subsystem subsystem = new CgroupV1Subsystem(); /** * Find the cgroup mount points for subsystems @@ -78,16 +70,16 @@ private static Metrics initContainerSubSystems() { * 34 28 0:29 / /sys/fs/cgroup/MemorySubSystem rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,MemorySubSystem */ try (Stream lines = - readFilePrivileged(Paths.get("/proc/self/mountinfo"))) { + CgroupUtil.readFilePrivileged(Paths.get("/proc/self/mountinfo"))) { lines.filter(line -> line.contains(" - cgroup ")) .map(line -> line.split(" ")) - .forEach(entry -> createSubSystem(metrics, entry)); + .forEach(entry -> createSubSystemController(subsystem, entry)); - } catch (IOException e) { - return null; } catch (UncheckedIOException e) { return null; + } catch (IOException e) { + return null; } /** @@ -114,54 +106,33 @@ private static Metrics initContainerSubSystems() { * */ try (Stream lines = - readFilePrivileged(Paths.get("/proc/self/cgroup"))) { + CgroupUtil.readFilePrivileged(Paths.get("/proc/self/cgroup"))) { // The limit value of 3 is because /proc/self/cgroup contains three // colon-separated tokens per line. The last token, cgroup path, might // contain a ':'. lines.map(line -> line.split(":", 3)) .filter(line -> (line.length >= 3)) - .forEach(line -> setSubSystemPath(metrics, line)); + .forEach(line -> setSubSystemControllerPath(subsystem, line)); - } catch (IOException e) { - return null; } catch (UncheckedIOException e) { return null; + } catch (IOException e) { + return null; } // Return Metrics object if we found any subsystems. - if (metrics.activeSubSystems()) { - return metrics; + if (subsystem.activeSubSystems()) { + return subsystem; } return null; } - static Stream readFilePrivileged(Path path) throws IOException { - try { - PrivilegedExceptionAction> pea = () -> Files.lines(path); - return AccessController.doPrivileged(pea); - } catch (PrivilegedActionException e) { - unwrapIOExceptionAndRethrow(e); - throw new InternalError(e.getCause()); - } catch (UncheckedIOException e) { - throw e.getCause(); - } - } - - static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IOException { - Throwable x = pae.getCause(); - if (x instanceof IOException) - throw (IOException) x; - if (x instanceof RuntimeException) - throw (RuntimeException) x; - if (x instanceof Error) - throw (Error) x; - } /** * createSubSystem objects and initialize mount points */ - private static void createSubSystem(Metrics metric, String[] mountentry) { + private static void createSubSystemController(CgroupV1Subsystem subsystem, String[] mountentry) { if (mountentry.length < 5) return; Path p = Paths.get(mountentry[4]); @@ -170,19 +141,19 @@ private static void createSubSystem(Metrics metric, String[] mountentry) { for (String subsystemName: subsystemNames) { switch (subsystemName) { case "memory": - metric.setMemorySubSystem(new MemorySubSystem(mountentry[3], mountentry[4])); + subsystem.setMemorySubSystem(new CgroupV1MemorySubSystemController(mountentry[3], mountentry[4])); break; case "cpuset": - metric.setCpuSetSubSystem(new SubSystem(mountentry[3], mountentry[4])); + subsystem.setCpuSetController(new CgroupV1SubsystemController(mountentry[3], mountentry[4])); break; case "cpuacct": - metric.setCpuAcctSubSystem(new SubSystem(mountentry[3], mountentry[4])); + subsystem.setCpuAcctController(new CgroupV1SubsystemController(mountentry[3], mountentry[4])); break; case "cpu": - metric.setCpuSubSystem(new SubSystem(mountentry[3], mountentry[4])); + subsystem.setCpuController(new CgroupV1SubsystemController(mountentry[3], mountentry[4])); break; case "blkio": - metric.setBlkIOSubSystem(new SubSystem(mountentry[3], mountentry[4])); + subsystem.setBlkIOController(new CgroupV1SubsystemController(mountentry[3], mountentry[4])); break; default: // Ignore subsystems that we don't support @@ -194,26 +165,26 @@ private static void createSubSystem(Metrics metric, String[] mountentry) { /** * setSubSystemPath based on the contents of /proc/self/cgroup */ - private static void setSubSystemPath(Metrics metric, String[] entry) { + private static void setSubSystemControllerPath(CgroupV1Subsystem subsystem, String[] entry) { String controller = entry[1]; String base = entry[2]; if (controller != null && base != null) { for (String cName: controller.split(",")) { switch (cName) { case "memory": - setPath(metric, metric.MemorySubSystem(), base); + setPath(subsystem, subsystem.memoryController(), base); break; case "cpuset": - setPath(metric, metric.CpuSetSubSystem(), base); + setPath(subsystem, subsystem.cpuSetController(), base); break; case "cpuacct": - setPath(metric, metric.CpuAcctSubSystem(), base); + setPath(subsystem, subsystem.cpuController(), base); break; case "cpu": - setPath(metric, metric.CpuSubSystem(), base); + setPath(subsystem, subsystem.cpuAcctController(), base); break; case "blkio": - setPath(metric, metric.BlkIOSubSystem(), base); + setPath(subsystem, subsystem.blkIOController(), base); break; // Ignore subsystems that we don't support default: @@ -223,29 +194,30 @@ private static void setSubSystemPath(Metrics metric, String[] entry) { } } - private static void setPath(Metrics metric, SubSystem subsystem, String base) { - if (subsystem != null) { - subsystem.setPath(base); - if (subsystem instanceof MemorySubSystem) { - MemorySubSystem memorySubSystem = (MemorySubSystem)subsystem; + private static void setPath(CgroupV1Subsystem subsystem, CgroupV1SubsystemController controller, String base) { + if (controller != null) { + controller.setPath(base); + if (controller instanceof CgroupV1MemorySubSystemController) { + CgroupV1MemorySubSystemController memorySubSystem = (CgroupV1MemorySubSystemController)controller; boolean isHierarchial = getHierarchical(memorySubSystem); memorySubSystem.setHierarchical(isHierarchial); boolean isSwapEnabled = getSwapEnabled(memorySubSystem); memorySubSystem.setSwapEnabled(isSwapEnabled); } - metric.setActiveSubSystems(); + subsystem.setActiveSubSystems(); } } - private static boolean getHierarchical(MemorySubSystem subsystem) { - long hierarchical = SubSystem.getLongValue(subsystem, "memory.use_hierarchy"); - return hierarchical > 0; - } + private static boolean getSwapEnabled(CgroupV1MemorySubSystemController controller) { + long retval = getLongValue(controller, "memory.memsw.limit_in_bytes"); + return retval > 0; + } + - private static boolean getSwapEnabled(MemorySubSystem subsystem) { - long retval = SubSystem.getLongValue(subsystem, "memory.memsw.limit_in_bytes"); - return retval > 0; + private static boolean getHierarchical(CgroupV1MemorySubSystemController controller) { + long hierarchical = getLongValue(controller, "memory.use_hierarchy"); + return hierarchical > 0; } private void setActiveSubSystems() { @@ -256,46 +228,54 @@ private boolean activeSubSystems() { return activeSubSystems; } - private void setMemorySubSystem(MemorySubSystem memory) { + private void setMemorySubSystem(CgroupV1MemorySubSystemController memory) { this.memory = memory; } - private void setCpuSubSystem(SubSystem cpu) { + private void setCpuController(CgroupV1SubsystemController cpu) { this.cpu = cpu; } - private void setCpuAcctSubSystem(SubSystem cpuacct) { + private void setCpuAcctController(CgroupV1SubsystemController cpuacct) { this.cpuacct = cpuacct; } - private void setCpuSetSubSystem(SubSystem cpuset) { + private void setCpuSetController(CgroupV1SubsystemController cpuset) { this.cpuset = cpuset; } - private void setBlkIOSubSystem(SubSystem blkio) { + private void setBlkIOController(CgroupV1SubsystemController blkio) { this.blkio = blkio; } - private SubSystem MemorySubSystem() { + private CgroupV1SubsystemController memoryController() { return memory; } - private SubSystem CpuSubSystem() { + private CgroupV1SubsystemController cpuController() { return cpu; } - private SubSystem CpuAcctSubSystem() { + private CgroupV1SubsystemController cpuAcctController() { return cpuacct; } - private SubSystem CpuSetSubSystem() { + private CgroupV1SubsystemController cpuSetController() { return cpuset; } - private SubSystem BlkIOSubSystem() { + private CgroupV1SubsystemController blkIOController() { return blkio; } + private static long getLongValue(CgroupSubsystemController controller, + String parm) { + return CgroupSubsystemController.getLongValue(controller, + parm, + CgroupV1SubsystemController::convertStringToLong, + CgroupSubsystem.LONG_RETVAL_UNLIMITED); + } + public String getProvider() { return PROVIDER_NAME; } @@ -306,13 +286,13 @@ public String getProvider() { public long getCpuUsage() { - return SubSystem.getLongValue(cpuacct, "cpuacct.usage"); + return getLongValue(cpuacct, "cpuacct.usage"); } public long[] getPerCpuUsage() { - String usagelist = SubSystem.getStringValue(cpuacct, "cpuacct.usage_percpu"); + String usagelist = CgroupSubsystemController.getStringValue(cpuacct, "cpuacct.usage_percpu"); if (usagelist == null) { - return new long[0]; + return null; } String list[] = usagelist.split(" "); @@ -324,11 +304,11 @@ public long[] getPerCpuUsage() { } public long getCpuUserUsage() { - return SubSystem.getLongEntry(cpuacct, "cpuacct.stat", "user"); + return CgroupV1SubsystemController.getLongEntry(cpuacct, "cpuacct.stat", "user"); } public long getCpuSystemUsage() { - return SubSystem.getLongEntry(cpuacct, "cpuacct.stat", "system"); + return CgroupV1SubsystemController.getLongEntry(cpuacct, "cpuacct.stat", "system"); } @@ -338,31 +318,31 @@ public long getCpuSystemUsage() { public long getCpuPeriod() { - return SubSystem.getLongValue(cpuacct, "cpu.cfs_period_us"); + return getLongValue(cpuacct, "cpu.cfs_period_us"); } public long getCpuQuota() { - return SubSystem.getLongValue(cpuacct, "cpu.cfs_quota_us"); + return getLongValue(cpuacct, "cpu.cfs_quota_us"); } public long getCpuShares() { - long retval = SubSystem.getLongValue(cpuacct, "cpu.shares"); + long retval = getLongValue(cpuacct, "cpu.shares"); if (retval == 0 || retval == 1024) - return -1; + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; else return retval; } public long getCpuNumPeriods() { - return SubSystem.getLongEntry(cpuacct, "cpu.stat", "nr_periods"); + return CgroupV1SubsystemController.getLongEntry(cpu, "cpu.stat", "nr_periods"); } public long getCpuNumThrottled() { - return SubSystem.getLongEntry(cpuacct, "cpu.stat", "nr_throttled"); + return CgroupV1SubsystemController.getLongEntry(cpu, "cpu.stat", "nr_throttled"); } public long getCpuThrottledTime() { - return SubSystem.getLongEntry(cpuacct, "cpu.stat", "throttled_time"); + return CgroupV1SubsystemController.getLongEntry(cpu, "cpu.stat", "throttled_time"); } public long getEffectiveCpuCount() { @@ -375,27 +355,27 @@ public long getEffectiveCpuCount() { ****************************************************************/ public int[] getCpuSetCpus() { - return SubSystem.StringRangeToIntArray(SubSystem.getStringValue(cpuset, "cpuset.cpus")); + return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.cpus")); } public int[] getEffectiveCpuSetCpus() { - return SubSystem.StringRangeToIntArray(SubSystem.getStringValue(cpuset, "cpuset.effective_cpus")); + return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.effective_cpus")); } public int[] getCpuSetMems() { - return SubSystem.StringRangeToIntArray(SubSystem.getStringValue(cpuset, "cpuset.mems")); + return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.mems")); } public int[] getEffectiveCpuSetMems() { - return SubSystem.StringRangeToIntArray(SubSystem.getStringValue(cpuset, "cpuset.effective_mems")); + return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(cpuset, "cpuset.effective_mems")); } public double getCpuSetMemoryPressure() { - return SubSystem.getDoubleValue(cpuset, "cpuset.memory_pressure"); + return CgroupV1SubsystemController.getDoubleValue(cpuset, "cpuset.memory_pressure"); } - public boolean isCpuSetMemoryPressureEnabled() { - long val = SubSystem.getLongValue(cpuset, "cpuset.memory_pressure_enabled"); + public Boolean isCpuSetMemoryPressureEnabled() { + long val = getLongValue(cpuset, "cpuset.memory_pressure_enabled"); return (val == 1); } @@ -406,124 +386,110 @@ public boolean isCpuSetMemoryPressureEnabled() { public long getMemoryFailCount() { - return SubSystem.getLongValue(memory, "memory.failcnt"); + return getLongValue(memory, "memory.failcnt"); } public long getMemoryLimit() { - long retval = SubSystem.getLongValue(memory, "memory.limit_in_bytes"); - if (retval > unlimited_minimum) { + 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 = SubSystem.getLongValueMatchingLine(memory, + retval = CgroupV1SubsystemController.getLongValueMatchingLine(memory, "memory.stat", - match, - Metrics::convertHierachicalLimitLine); + match); } } - return retval > unlimited_minimum ? -1L : retval; - } - - public static long convertHierachicalLimitLine(String line) { - String[] tokens = line.split("\\s"); - if (tokens.length == 2) { - String strVal = tokens[1]; - return SubSystem.convertStringToLong(strVal); - } - return unlimited_minimum + 1; // unlimited + return CgroupV1SubsystemController.longValOrUnlimited(retval); } public long getMemoryMaxUsage() { - return SubSystem.getLongValue(memory, "memory.max_usage_in_bytes"); + return getLongValue(memory, "memory.max_usage_in_bytes"); } public long getMemoryUsage() { - return SubSystem.getLongValue(memory, "memory.usage_in_bytes"); + return getLongValue(memory, "memory.usage_in_bytes"); } public long getKernelMemoryFailCount() { - return SubSystem.getLongValue(memory, "memory.kmem.failcnt"); + return getLongValue(memory, "memory.kmem.failcnt"); } public long getKernelMemoryLimit() { - long retval = SubSystem.getLongValue(memory, "memory.kmem.limit_in_bytes"); - return retval > unlimited_minimum ? -1L : retval; + return CgroupV1SubsystemController.longValOrUnlimited(getLongValue(memory, "memory.kmem.limit_in_bytes")); } public long getKernelMemoryMaxUsage() { - return SubSystem.getLongValue(memory, "memory.kmem.max_usage_in_bytes"); + return getLongValue(memory, "memory.kmem.max_usage_in_bytes"); } public long getKernelMemoryUsage() { - return SubSystem.getLongValue(memory, "memory.kmem.usage_in_bytes"); + return getLongValue(memory, "memory.kmem.usage_in_bytes"); } public long getTcpMemoryFailCount() { - return SubSystem.getLongValue(memory, "memory.kmem.tcp.failcnt"); + return getLongValue(memory, "memory.kmem.tcp.failcnt"); } public long getTcpMemoryLimit() { - long retval = SubSystem.getLongValue(memory, "memory.kmem.tcp.limit_in_bytes"); - return retval > unlimited_minimum ? -1L : retval; + return CgroupV1SubsystemController.longValOrUnlimited(getLongValue(memory, "memory.kmem.tcp.limit_in_bytes")); } public long getTcpMemoryMaxUsage() { - return SubSystem.getLongValue(memory, "memory.kmem.tcp.max_usage_in_bytes"); + return getLongValue(memory, "memory.kmem.tcp.max_usage_in_bytes"); } public long getTcpMemoryUsage() { - return SubSystem.getLongValue(memory, "memory.kmem.tcp.usage_in_bytes"); + return getLongValue(memory, "memory.kmem.tcp.usage_in_bytes"); } public long getMemoryAndSwapFailCount() { if (memory != null && !memory.isSwapEnabled()) { return getMemoryFailCount(); } - return SubSystem.getLongValue(memory, "memory.memsw.failcnt"); + return getLongValue(memory, "memory.memsw.failcnt"); } public long getMemoryAndSwapLimit() { if (memory != null && !memory.isSwapEnabled()) { return getMemoryLimit(); } - long retval = SubSystem.getLongValue(memory, "memory.memsw.limit_in_bytes"); - if (retval > unlimited_minimum) { + 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 = SubSystem.getLongValueMatchingLine(memory, + retval = CgroupV1SubsystemController.getLongValueMatchingLine(memory, "memory.stat", - match, - Metrics::convertHierachicalLimitLine); + match); } } - return retval > unlimited_minimum ? -1L : retval; + return CgroupV1SubsystemController.longValOrUnlimited(retval); } public long getMemoryAndSwapMaxUsage() { if (memory != null && !memory.isSwapEnabled()) { return getMemoryMaxUsage(); } - return SubSystem.getLongValue(memory, "memory.memsw.max_usage_in_bytes"); + return getLongValue(memory, "memory.memsw.max_usage_in_bytes"); } public long getMemoryAndSwapUsage() { if (memory != null && !memory.isSwapEnabled()) { return getMemoryUsage(); } - return SubSystem.getLongValue(memory, "memory.memsw.usage_in_bytes"); + return getLongValue(memory, "memory.memsw.usage_in_bytes"); } - public boolean isMemoryOOMKillEnabled() { - long val = SubSystem.getLongEntry(memory, "memory.oom_control", "oom_kill_disable"); + public Boolean isMemoryOOMKillEnabled() { + long val = CgroupV1SubsystemController.getLongEntry(memory, "memory.oom_control", "oom_kill_disable"); return (val == 0); } public long getMemorySoftLimit() { - long retval = SubSystem.getLongValue(memory, "memory.soft_limit_in_bytes"); - return retval > unlimited_minimum ? -1L : retval; + return CgroupV1SubsystemController.longValOrUnlimited(getLongValue(memory, "memory.soft_limit_in_bytes")); } @@ -533,11 +499,11 @@ public long getMemorySoftLimit() { public long getBlkIOServiceCount() { - return SubSystem.getLongEntry(blkio, "blkio.throttle.io_service_bytes", "Total"); + return CgroupV1SubsystemController.getLongEntry(blkio, "blkio.throttle.io_service_bytes", "Total"); } public long getBlkIOServiced() { - return SubSystem.getLongEntry(blkio, "blkio.throttle.io_serviced", "Total"); + return CgroupV1SubsystemController.getLongEntry(blkio, "blkio.throttle.io_serviced", "Total"); } private static native boolean isUseContainerSupport(); diff --git a/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java new file mode 100644 index 00000000000..5bc3c297499 --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * 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.CgroupSubsystemController; + +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; + + public CgroupV1SubsystemController(String root, String mountPoint) { + this.root = root; + this.mountPoint = mountPoint; + } + + public void setPath(String cgroupPath) { + if (root != null && cgroupPath != null) { + if (root.equals("/")) { + if (!cgroupPath.equals("/")) { + path = mountPoint + cgroupPath; + } + else { + path = mountPoint; + } + } + else { + if (root.equals(cgroupPath)) { + path = mountPoint; + } + else { + if (cgroupPath.startsWith(root)) { + if (cgroupPath.length() > root.length()) { + String cgroupSubstr = cgroupPath.substring(root.length()); + path = mountPoint + cgroupSubstr; + } + } + } + } + } + } + + @Override + public String path() { + return path; + } + + public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname) { + return CgroupSubsystemController.getLongEntry(controller, + param, + entryname, + CgroupSubsystem.LONG_RETVAL_UNLIMITED /* retval on error */); + } + + public static double getDoubleValue(CgroupSubsystemController controller, String parm) { + return CgroupSubsystemController.getDoubleValue(controller, + parm, + DOUBLE_RETVAL_UNLIMITED /* retval on error */); + } + + public static long convertStringToLong(String strval) { + return CgroupSubsystemController.convertStringToLong(strval, + Long.MAX_VALUE /* overflow value */, + CgroupSubsystem.LONG_RETVAL_UNLIMITED /* retval on error */); + } + + public static long longValOrUnlimited(long value) { + return value > UNLIMITED_MIN ? CgroupSubsystem.LONG_RETVAL_UNLIMITED : value; + } + + public static long getLongValueMatchingLine(CgroupSubsystemController controller, + String param, + String match) { + return CgroupSubsystemController.getLongValueMatchingLine(controller, + param, + match, + CgroupV1SubsystemController::convertHierachicalLimitLine, + CgroupSubsystem.LONG_RETVAL_UNLIMITED); + } + + public static long convertHierachicalLimitLine(String line) { + String[] tokens = line.split("\\s"); + if (tokens.length == 2) { + String strVal = tokens[1]; + return CgroupV1SubsystemController.convertStringToLong(strVal); + } + return CgroupV1SubsystemController.UNLIMITED_MIN + 1; // unlimited + } + +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/SubSystem.java b/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/SubSystem.java deleted file mode 100644 index 251815523f1..00000000000 --- a/jdk/src/linux/classes/jdk/internal/platform/cgroupv1/SubSystem.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. - * 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 java.io.BufferedReader; -import java.io.IOException; -import java.math.BigInteger; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Stream; - -public class SubSystem { - String root; - String mountPoint; - String path; - - public SubSystem(String root, String mountPoint) { - this.root = root; - this.mountPoint = mountPoint; - } - - public void setPath(String cgroupPath) { - if (root != null && cgroupPath != null) { - if (root.equals("/")) { - if (!cgroupPath.equals("/")) { - path = mountPoint + cgroupPath; - } - else { - path = mountPoint; - } - } - else { - if (root.equals(cgroupPath)) { - path = mountPoint; - } - else { - if (cgroupPath.startsWith(root)) { - if (cgroupPath.length() > root.length()) { - String cgroupSubstr = cgroupPath.substring(root.length()); - path = mountPoint + cgroupSubstr; - } - } - } - } - } - } - - public String path() { - return path; - } - - /** - * getSubSystemStringValue - * - * Return the first line of the file "parm" argument from the subsystem. - * - * TODO: Consider using weak references for caching BufferedReader object. - * - * @param subsystem - * @param parm - * @return Returns the contents of the file specified by param. - */ - public static String getStringValue(SubSystem subsystem, String parm) { - if (subsystem == null) return null; - - try { - return subsystem.readStringValue(parm); - } catch (IOException e) { - return null; - } - } - - private String readStringValue(String param) throws IOException { - PrivilegedExceptionAction pea = () -> - Files.newBufferedReader(Paths.get(path(), param)); - try (BufferedReader bufferedReader = - AccessController.doPrivileged(pea)) { - String line = bufferedReader.readLine(); - return line; - } catch (PrivilegedActionException e) { - Metrics.unwrapIOExceptionAndRethrow(e); - throw new InternalError(e.getCause()); - } catch (UncheckedIOException e) { - throw e.getCause(); - } - } - - public static long getLongValueMatchingLine(SubSystem subsystem, - String param, - String match, - Function conversion) { - long retval = Metrics.unlimited_minimum + 1; // default unlimited - try { - List lines = subsystem.readMatchingLines(param); - for (String line: lines) { - if (line.contains(match)) { - retval = conversion.apply(line); - break; - } - } - } catch (IOException e) { - // Ignore. Default is unlimited. - } - return retval; - } - - private List readMatchingLines(String param) throws IOException { - try { - PrivilegedExceptionAction> pea = () -> - Files.readAllLines(Paths.get(path(), param)); - return AccessController.doPrivileged(pea); - } catch (PrivilegedActionException e) { - Metrics.unwrapIOExceptionAndRethrow(e); - throw new InternalError(e.getCause()); - } catch (UncheckedIOException e) { - throw e.getCause(); - } - } - - public static long getLongValue(SubSystem subsystem, String parm) { - String strval = getStringValue(subsystem, parm); - return convertStringToLong(strval); - } - - public static long convertStringToLong(String strval) { - if (strval == null) return 0L; - - long retval = 0; - - try { - retval = Long.parseLong(strval); - } catch (NumberFormatException e) { - // For some properties (e.g. memory.limit_in_bytes) we may overflow the range of signed long. - // In this case, return Long.max - BigInteger b = new BigInteger(strval); - if (b.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { - return Long.MAX_VALUE; - } - } - - return retval; - } - - public static double getDoubleValue(SubSystem subsystem, String parm) { - String strval = getStringValue(subsystem, parm); - - if (strval == null) return 0L; - - double retval = Double.parseDouble(strval); - - return retval; - } - - /** - * getSubSystemlongEntry - * - * Return the long value from the line containing the string "entryname" - * within file "parm" in the "subsystem". - * - * TODO: Consider using weak references for caching BufferedReader object. - * - * @param subsystem - * @param parm - * @param entryname - * @return long value - */ - public static long getLongEntry(SubSystem subsystem, String parm, String entryname) { - String val = null; - - if (subsystem == null) return 0L; - - try (Stream lines = Metrics.readFilePrivileged(Paths.get(subsystem.path(), parm))) { - - Optional result = lines.map(line -> line.split(" ")) - .filter(line -> (line.length == 2 && - line[0].equals(entryname))) - .map(line -> line[1]) - .findFirst(); - - return result.isPresent() ? Long.parseLong(result.get()) : 0L; - } catch (IOException e) { - return 0L; - } catch (UncheckedIOException e) { - return 0L; - } - } - - public static int getIntValue(SubSystem subsystem, String parm) { - String val = getStringValue(subsystem, parm); - - if (val == null) return 0; - - return Integer.parseInt(val); - } - - /** - * StringRangeToIntArray - * - * Convert a string in the form of 1,3-4,6 to an array of - * integers containing all the numbers in the range. - * - * @param range - * @return int[] containing a sorted list of processors or memory nodes - */ - public static int[] StringRangeToIntArray(String range) { - int[] ints = new int[0]; - - if (range == null) return ints; - - ArrayList results = new ArrayList<>(); - String strs[] = range.split(","); - for (String str : strs) { - if (str.contains("-")) { - String lohi[] = str.split("-"); - // validate format - if (lohi.length != 2) { - continue; - } - int lo = Integer.parseInt(lohi[0]); - int hi = Integer.parseInt(lohi[1]); - for (int i = lo; i <= hi; i++) { - results.add(i); - } - } - else { - results.add(Integer.parseInt(str)); - } - } - - // sort results - results.sort(null); - - // convert ArrayList to primitive int array - ints = new int[results.size()]; - int i = 0; - for (Integer n : results) { - ints[i++] = n; - } - - return ints; - } - - public static class MemorySubSystem extends SubSystem { - - private boolean hierarchical; - private boolean swapenabled; - - public MemorySubSystem(String root, String mountPoint) { - super(root, mountPoint); - } - - boolean isHierarchical() { - return hierarchical; - } - - void setHierarchical(boolean hierarchical) { - this.hierarchical = hierarchical; - } - - boolean isSwapEnabled() { - return swapenabled; - } - - void setSwapEnabled(boolean swapenabled) { - this.swapenabled = swapenabled; - } - - } -} diff --git a/jdk/src/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java b/jdk/src/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java new file mode 100644 index 00000000000..9be5ea3db8c --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2020, 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.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.CgroupSubsystemController; +import jdk.internal.platform.CgroupUtil; + +public class CgroupV2Subsystem implements CgroupSubsystem { + + private static final CgroupV2Subsystem INSTANCE = initSubsystem(); + 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 String MAX_VAL = "max"; + private static final Object EMPTY_STR = ""; + + private CgroupV2Subsystem(CgroupSubsystemController unified) { + this.unified = unified; + } + + private long getLongVal(String file) { + return CgroupSubsystemController.getLongValue(unified, + file, + CgroupV2SubsystemController::convertStringToLong, + CgroupSubsystem.LONG_RETVAL_UNLIMITED); + } + + private static CgroupV2Subsystem initSubsystem() { + // read mountinfo so as to determine root mount path + String mountPath = null; + try (Stream lines = + CgroupUtil.readFilePrivileged(Paths.get("/proc/self/mountinfo"))) { + + String l = lines.filter(line -> line.contains(" - cgroup2 ")) + .collect(Collectors.joining()); + String[] tokens = l.split(" "); + mountPath = tokens[4]; + } catch (UncheckedIOException e) { + return null; + } catch (IOException e) { + return null; + } + String cgroupPath = null; + try { + List lines = CgroupUtil.readAllLinesPrivileged(Paths.get("/proc/self/cgroup")); + for (String line: lines) { + String[] tokens = line.split(":"); + if (tokens.length != 3) { + return null; // something is not right. + } + if (!"0".equals(tokens[0])) { + // hierarchy must be zero for cgroups v2 + return null; + } + cgroupPath = tokens[2]; + break; + } + } catch (UncheckedIOException e) { + return null; + } catch (IOException e) { + return null; + } + CgroupSubsystemController unified = new CgroupV2SubsystemController( + mountPath, + cgroupPath); + return new CgroupV2Subsystem(unified); + } + + public static CgroupSubsystem getInstance() { + return INSTANCE; + } + + @Override + public String getProvider() { + return PROVIDER_NAME; + } + + @Override + public long getCpuUsage() { + long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "usage_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + @Override + public long[] getPerCpuUsage() { + return LONG_ARRAY_NOT_SUPPORTED; + } + + @Override + public long getCpuUserUsage() { + long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "user_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + @Override + public long getCpuSystemUsage() { + long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "system_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + @Override + public long getCpuPeriod() { + return getFromCpuMax(1 /* $PERIOD index */); + } + + @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 limitFromString(quota); + } + + private long limitFromString(String strVal) { + if (strVal == null || MAX_VAL.equals(strVal)) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + return Long.parseLong(strVal); + } + + @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; + } + + @Override + public long getCpuNumPeriods() { + return CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "nr_periods"); + } + + @Override + public long getCpuNumThrottled() { + return CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "nr_throttled"); + } + + @Override + public long getCpuThrottledTime() { + long micros = CgroupV2SubsystemController.getLongEntry(unified, "cpu.stat", "throttled_usec"); + if (micros < 0) { + return micros; + } + return TimeUnit.MICROSECONDS.toNanos(micros); + } + + @Override + public long getEffectiveCpuCount() { + return Runtime.getRuntime().availableProcessors(); + } + + @Override + public int[] getCpuSetCpus() { + String cpuSetVal = CgroupSubsystemController.getStringValue(unified, "cpuset.cpus"); + return getCpuSet(cpuSetVal); + } + + @Override + public int[] getEffectiveCpuSetCpus() { + String effCpuSetVal = CgroupSubsystemController.getStringValue(unified, "cpuset.cpus.effective"); + return getCpuSet(effCpuSetVal); + } + + @Override + public int[] getCpuSetMems() { + String cpuSetMems = CgroupSubsystemController.getStringValue(unified, "cpuset.mems"); + return getCpuSet(cpuSetMems); + } + + @Override + public int[] getEffectiveCpuSetMems() { + String effCpuSetMems = CgroupSubsystemController.getStringValue(unified, "cpuset.mems.effective"); + return getCpuSet(effCpuSetMems); + } + + private int[] getCpuSet(String cpuSetVal) { + if (cpuSetVal == null || EMPTY_STR.equals(cpuSetVal)) { + return INT_ARRAY_UNAVAILABLE; + } + return CgroupSubsystemController.stringRangeToIntArray(cpuSetVal); + } + + @Override + public long getMemoryFailCount() { + return CgroupV2SubsystemController.getLongEntry(unified, "memory.events", "max"); + } + + @Override + public long getMemoryLimit() { + String strVal = CgroupSubsystemController.getStringValue(unified, "memory.max"); + return limitFromString(strVal); + } + + @Override + public long getMemoryUsage() { + return getLongVal("memory.current"); + } + + @Override + public long getTcpMemoryUsage() { + return CgroupV2SubsystemController.getLongEntry(unified, "memory.stat", "sock"); + } + + @Override + public long getMemoryAndSwapLimit() { + String strVal = CgroupSubsystemController.getStringValue(unified, "memory.swap.max"); + return limitFromString(strVal); + } + + @Override + public long getMemoryAndSwapUsage() { + return getLongVal("memory.swap.current"); + } + + @Override + public long getMemorySoftLimit() { + String softLimitStr = CgroupSubsystemController.getStringValue(unified, "memory.high"); + return limitFromString(softLimitStr); + } + + @Override + public long getBlkIOServiceCount() { + return sumTokensIOStat(CgroupV2Subsystem::lineToRandWIOs); + } + + + @Override + public long getBlkIOServiced() { + return sumTokensIOStat(CgroupV2Subsystem::lineToRBytesAndWBytesIO); + } + + private long sumTokensIOStat(Function mapFunc) { + try { + return CgroupUtil.readFilePrivileged(Paths.get(unified.path(), "io.stat")) + .map(mapFunc) + .collect(Collectors.summingLong(e -> e)); + } catch (UncheckedIOException e) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } catch (IOException e) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + } + + private static String[] getRWIOMatchTokenNames() { + return new String[] { "rios", "wios" }; + } + + private static String[] getRWBytesIOMatchTokenNames() { + return new String[] { "rbytes", "wbytes" }; + } + + public static Long lineToRandWIOs(String line) { + String[] matchNames = getRWIOMatchTokenNames(); + return ioStatLineToLong(line, matchNames); + } + + public static Long lineToRBytesAndWBytesIO(String line) { + String[] matchNames = getRWBytesIOMatchTokenNames(); + return ioStatLineToLong(line, matchNames); + } + + private static Long ioStatLineToLong(String line, String[] matchNames) { + if (line == null || EMPTY_STR.equals(line)) { + return Long.valueOf(0); + } + String[] tokens = line.split("\\s+"); + long retval = 0; + for (String t: tokens) { + String[] valKeys = t.split("="); + if (valKeys.length != 2) { + // ignore device ids $MAJ:$MIN + continue; + } + for (String match: matchNames) { + if (match.equals(valKeys[0])) { + retval += longOrZero(valKeys[1]); + } + } + } + return Long.valueOf(retval); + } + + private static long longOrZero(String val) { + long lVal = 0; + try { + lVal = Long.parseLong(val); + } catch (NumberFormatException e) { + // keep at 0 + } + return lVal; + } +} diff --git a/jdk/src/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java b/jdk/src/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java new file mode 100644 index 00000000000..c9a3ff4f96f --- /dev/null +++ b/jdk/src/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2SubsystemController.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, 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.nio.file.Paths; + +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.CgroupSubsystemController; + +public class CgroupV2SubsystemController implements CgroupSubsystemController { + + private final String path; + + public CgroupV2SubsystemController(String mountPath, String cgroupPath) { + this.path = Paths.get(mountPath, cgroupPath).toString(); + } + + @Override + public String path() { + return path; + } + + public static long convertStringToLong(String strval) { + return CgroupSubsystemController.convertStringToLong(strval, + CgroupSubsystem.LONG_RETVAL_UNLIMITED /* overflow retval */, + CgroupSubsystem.LONG_RETVAL_UNLIMITED /* default retval on error */); + } + + public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname) { + return CgroupSubsystemController.getLongEntry(controller, + param, + entryname, + CgroupSubsystem.LONG_RETVAL_UNLIMITED /* retval on error */); + } +} diff --git a/jdk/src/linux/native/jdk/internal/platform/cgroupv1/Metrics.c b/jdk/src/linux/native/jdk/internal/platform/cgroupv1/CgroupMetrics.c similarity index 89% rename from jdk/src/linux/native/jdk/internal/platform/cgroupv1/Metrics.c rename to jdk/src/linux/native/jdk/internal/platform/cgroupv1/CgroupMetrics.c index 6979340d3bb..8c9a9dd7a7e 100644 --- a/jdk/src/linux/native/jdk/internal/platform/cgroupv1/Metrics.c +++ b/jdk/src/linux/native/jdk/internal/platform/cgroupv1/CgroupMetrics.c @@ -26,10 +26,10 @@ #include "jni.h" #include "jvm.h" -#include "jdk_internal_platform_cgroupv1_Metrics.h" +#include "jdk_internal_platform_CgroupMetrics.h" JNIEXPORT jboolean JNICALL -Java_jdk_internal_platform_cgroupv1_Metrics_isUseContainerSupport(JNIEnv *env, jclass ignored) +Java_jdk_internal_platform_CgroupMetrics_isUseContainerSupport(JNIEnv *env, jclass ignored) { return JVM_IsUseContainerSupport(); } diff --git a/jdk/src/share/classes/jdk/internal/platform/Metrics.java b/jdk/src/share/classes/jdk/internal/platform/Metrics.java index 4aa2f1f3c11..0607de921b4 100644 --- a/jdk/src/share/classes/jdk/internal/platform/Metrics.java +++ b/jdk/src/share/classes/jdk/internal/platform/Metrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,9 +55,7 @@ public interface Metrics { */ public static Metrics systemMetrics() { try { - // We currently only support cgroupv1 - Class c = Class.forName("jdk.internal.platform.cgroupv1.Metrics"); - @SuppressWarnings("unchecked") + Class c = Class.forName("jdk.internal.platform.CgroupMetrics"); Method m = c.getMethod("getInstance"); return (Metrics) m.invoke(null); } catch (ClassNotFoundException e) { @@ -73,7 +71,7 @@ public static Metrics systemMetrics() { * * @implNote * Metrics are currently only supported Linux. - * The provider for Linux is cgroupsv1. + * The provider for Linux is cgroups (version 1 or 2). * * @return The name of the provider. * @@ -89,7 +87,8 @@ public static Metrics systemMetrics() { * Returns the aggregate time, in nanoseconds, consumed by all * tasks in the Isolation Group. * - * @return Time in nanoseconds or 0L if metric is not available. + * @return Time in nanoseconds, -1 if unknown or + * -2 if the metric is not supported. * */ public long getCpuUsage(); @@ -105,7 +104,7 @@ public static Metrics systemMetrics() { * * @return long array of time values. The size of the array is equal * to the total number of physical processors in the system. If - * this metric is not available, a zero length array will be + * this metric is not supported or not available, null will be * returned. * */ @@ -115,7 +114,8 @@ public static Metrics systemMetrics() { * Returns the aggregate user time, in nanoseconds, consumed by all * tasks in the Isolation Group. * - * @return User time in nanoseconds or 0L if metric is not available. + * @return User time in nanoseconds, -1 if the metric is not available or + * -2 if the metric is not supported. * */ public long getCpuUserUsage(); @@ -124,7 +124,8 @@ public static Metrics systemMetrics() { * Returns the aggregate system time, in nanoseconds, consumed by * all tasks in the Isolation Group. * - * @return System time in nanoseconds or 0L if metric is not available. + * @return System time in nanoseconds, -1 if the metric is not available or + * -2 if the metric is not supported. * */ public long getCpuSystemUsage(); @@ -137,7 +138,8 @@ public static Metrics systemMetrics() { * Returns the length of the scheduling period, in * microseconds, for processes within the Isolation Group. * - * @return time in microseconds or 0L if metric is not available. + * @return time in microseconds, -1 if the metric is not available or + * -2 if the metric is not supported. * */ public long getCpuPeriod(); @@ -147,7 +149,8 @@ public static Metrics systemMetrics() { * during each scheduling period for all tasks in the Isolation * Group. * - * @return time in microseconds or -1 if the quota is unlimited. + * @return time in microseconds, -1 if the quota is unlimited or + * -2 if not supported. * */ public long getCpuQuota(); @@ -166,17 +169,18 @@ public static Metrics systemMetrics() { * each process. To request 2 CPUS worth of execution time, CPU shares * would be set to 2048. * - * @return shares value or -1 if no share set. + * @return shares value, -1 if the metric is not available or + * -2 if cpu shares are not supported. * */ public long getCpuShares(); /** * Returns the number of time-slice periods that have elapsed if - * a CPU quota has been setup for the Isolation Group; otherwise - * returns 0. + * a CPU quota has been setup for the Isolation Group * - * @return count of elapsed periods or 0 if the quota is unlimited. + * @return count of elapsed periods, -1 if the metric is not available + * or -2 if the metric is not supported. * */ public long getCpuNumPeriods(); @@ -186,7 +190,8 @@ public static Metrics systemMetrics() { * been throttled or limited due to the group exceeding its quota * if a CPU quota has been setup for the Isolation Group. * - * @return count of throttled periods or 0 if the quota is unlimited. + * @return count of throttled periods, -1 if the metric is not available or + * -2 if it is not supported. * */ public long getCpuNumThrottled(); @@ -196,7 +201,8 @@ public static Metrics systemMetrics() { * group has been throttled or limited due to the group exceeding * its quota if a CPU quota has been setup for the Isolation Group. * - * @return Throttled time in nanoseconds or 0 if the quota is unlimited. + * @return Throttled time in nanoseconds, -1 if the metric is not available + * or -2 if it is not supported. * */ public long getCpuThrottledTime(); @@ -228,8 +234,8 @@ public static Metrics systemMetrics() { * may be offline. To get the current online CPUs, use * {@link getEffectiveCpuSetCpus()}. * - * @return An array of available CPUs or a zero length array - * if the metric is not available. + * @return An array of available CPUs. Returns null if the metric is not + * available or the metric is not supported. * */ public int[] getCpuSetCpus(); @@ -240,8 +246,8 @@ public static Metrics systemMetrics() { * array is equal to the total number of CPUs and the elements in * the array are the physical CPU numbers. * - * @return An array of available and online CPUs or a zero length - * array if the metric is not available. + * @return An array of available and online CPUs. Returns null + * if the metric is not available or the metric is not supported. * */ public int[] getEffectiveCpuSetCpus(); @@ -254,8 +260,8 @@ public static Metrics systemMetrics() { * may be offline. To get the current online memory nodes, use * {@link getEffectiveCpuSetMems()}. * - * @return An array of available memory nodes or a zero length array - * if the metric is not available. + * @return An array of available memory nodes or null + * if the metric is not available or is not supported. * */ public int[] getCpuSetMems(); @@ -266,33 +272,12 @@ public static Metrics systemMetrics() { * array is equal to the total number of nodes and the elements in * the array are the physical node numbers. * - * @return An array of available and online nodes or a zero length - * array if the metric is not available. + * @return An array of available and online nodes or null + * if the metric is not available or is not supported. * */ public int[] getEffectiveCpuSetMems(); - /** - * Returns the (attempts per second * 1000), if enabled, that the - * operating system tries to satisfy a memory request for any - * process in the current Isolation Group when no free memory is - * readily available. Use {@link #isCpuSetMemoryPressureEnabled()} to - * to determine if this support is enabled. - * - * @return Memory pressure or 0 if not enabled or metric is not - * available. - * - */ - public double getCpuSetMemoryPressure(); - - /** - * Returns the state of the memory pressure detection support. - * - * @return true if the support is available and enabled, otherwise false. - * - */ - public boolean isCpuSetMemoryPressureEnabled(); - /***************************************************************** * Memory Subsystem ****************************************************************/ @@ -301,8 +286,9 @@ public static Metrics systemMetrics() { * Returns the number of times that user memory requests in the * Isolation Group have exceeded the memory limit. * - * @return The number of exceeded requests or 0 if none or metric - * is not available. + * @return The number of exceeded requests or -1 if the metric + * is not available. Returns -2 if the metric is not + * supported. * */ public long getMemoryFailCount(); @@ -311,164 +297,54 @@ public static Metrics systemMetrics() { * Returns the maximum amount of physical memory, in bytes, that * can be allocated in the Isolation Group. * - * @return The maximum amount of memory in bytes or -1 if either - * there is no limit set or this metric is not available. + * @return The maximum amount of memory in bytes or -1 if + * there is no limit or -2 if this metric is not supported. * */ public long getMemoryLimit(); - /** - * Returns the largest amount of physical memory, in bytes, that - * have been allocated in the Isolation Group. - * - * @return The largest amount of memory in bytes or or 0 if this - * metric is not available. - * - */ - public long getMemoryMaxUsage(); - /** * Returns the amount of physical memory, in bytes, that is currently * allocated in the current Isolation Group. * - * @return The amount of memory in bytes allocated or 0 if this - * metric is not available. + * @return The amount of memory in bytes allocated or -1 if + * the metric is not available or -2 if the metric is not + * supported. * */ public long getMemoryUsage(); - /** - * Returns the number of times that kernel memory requests in the - * Isolation Group have exceeded the kernel memory limit. - * - * @return The number of exceeded requests or 0 if none or metric - * is not available. - * - */ - public long getKernelMemoryFailCount(); - - /** - * Returns the maximum amount of kernel physical memory, in bytes, that - * can be allocated in the Isolation Group. - * - * @return The maximum amount of memory in bytes or -1 if either - * there is no limit set or this metric is not available. - * - */ - public long getKernelMemoryLimit(); - - /** - * Returns the largest amount of kernel physical memory, in bytes, that - * have been allocated in the Isolation Group. - * - * @return The largest amount of memory in bytes or or 0 if this - * metric is not available. - * - */ - public long getKernelMemoryMaxUsage(); - - /** - * Returns the amount of kernel physical memory, in bytes, that - * is currently allocated in the current Isolation Group. - * - * @return The amount of memory in bytes allocated or 0 if this - * metric is not available. - * - */ - public long getKernelMemoryUsage(); - - /** - * Returns the number of times that networking memory requests in the - * Isolation Group have exceeded the kernel memory limit. - * - * @return The number of exceeded requests or 0 if none or metric - * is not available. - * - */ - public long getTcpMemoryFailCount(); - - /** - * Returns the maximum amount of networking physical memory, in bytes, - * that can be allocated in the Isolation Group. - * - * @return The maximum amount of memory in bytes or -1 if either - * there is no limit set or this metric is not available. - * - */ - public long getTcpMemoryLimit(); - - /** - * Returns the largest amount of networking physical memory, in bytes, - * that have been allocated in the Isolation Group. - * - * @return The largest amount of memory in bytes or or 0 if this - * metric is not available. - * - */ - public long getTcpMemoryMaxUsage(); - /** * Returns the amount of networking physical memory, in bytes, that * is currently allocated in the current Isolation Group. * - * @return The amount of memory in bytes allocated or 0 if this - * metric is not available. + * @return The amount of memory in bytes allocated or -1 if the metric + * is not available. Returns -2 if this metric is not supported. * */ public long getTcpMemoryUsage(); - /** - * Returns the number of times that user memory requests in the - * Isolation Group have exceeded the memory + swap limit. - * - * @return The number of exceeded requests or 0 if none or metric - * is not available. - * - */ - public long getMemoryAndSwapFailCount(); - /** * Returns the maximum amount of physical memory and swap space, * in bytes, that can be allocated in the Isolation Group. * - * @return The maximum amount of memory in bytes or -1 if either - * there is no limit set or this metric is not available. + * @return The maximum amount of memory in bytes or -1 if + * there is no limit set or -2 if this metric is not supported. * */ public long getMemoryAndSwapLimit(); - /** - * Returns the largest amount of physical memory and swap space, - * in bytes, that have been allocated in the Isolation Group. - * - * @return The largest amount of memory in bytes or or 0 if this - * metric is not available. - * - */ - public long getMemoryAndSwapMaxUsage(); - /** * Returns the amount of physical memory and swap space, in bytes, * that is currently allocated in the current Isolation Group. * - * @return The amount of memory in bytes allocated or 0 if this - * metric is not available. + * @return The amount of memory in bytes allocated or -1 if + * the metric is not available. Returns -2 if this metric is not + * supported. * */ public long getMemoryAndSwapUsage(); - /** - * Returns the state of the Operating System Out of Memory termination - * policy. - * - * @return Returns true if operating system will terminate processes - * in the Isolation Group that exceed the amount of available - * memory, otherwise false. Flase will be returned if this - * capability is not available on the current operating system. - * - */ - public boolean isMemoryOOMKillEnabled(); - /** * Returns the hint to the operating system that allows groups * to specify the minimum amount of physical memory that they need to @@ -477,8 +353,8 @@ public static Metrics systemMetrics() { * * @return The minimum amount of physical memory, in bytes, that the * operating system will try to maintain under low memory - * conditions. If this metric is not available, 0 will be - * returned. + * conditions. If this metric is not available, -1 will be + * returned. Returns -2 if the metric is not supported. * */ public long getMemorySoftLimit(); @@ -491,7 +367,8 @@ public static Metrics systemMetrics() { * Returns the number of block I/O requests to the disk that have been * issued by the Isolation Group. * - * @return The count of requests or 0 if this metric is not available. + * @return The count of requests or -1 if the metric is not available. + * Returns -2 if this metric is not supported. * */ public long getBlkIOServiceCount(); @@ -500,7 +377,8 @@ public static Metrics systemMetrics() { * Returns the number of block I/O bytes that have been transferred * to/from the disk by the Isolation Group. * - * @return The number of bytes transferred or 0 if this metric is not available. + * @return The number of bytes transferred or -1 if the metric is not + * available. Returns -2 if this metric is not supported. * */ public long getBlkIOServiced(); diff --git a/jdk/src/share/classes/sun/launcher/LauncherHelper.java b/jdk/src/share/classes/sun/launcher/LauncherHelper.java index 0a9ec840570..4cd2a1a6eee 100644 --- a/jdk/src/share/classes/sun/launcher/LauncherHelper.java +++ b/jdk/src/share/classes/sun/launcher/LauncherHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,9 +51,9 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.text.Normalizer; import java.util.ResourceBundle; import java.text.MessageFormat; +import java.text.Normalizer; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -86,6 +86,7 @@ public enum LauncherHelper { private static final String defaultBundleName = "sun.launcher.resources.launcher"; + private static class ResourceBundleHolder { private static final ResourceBundle RB = ResourceBundle.getBundle(defaultBundleName); @@ -296,89 +297,108 @@ public static void printSystemMetrics() { return; } + final long longRetvalNotSupported = -2; + ostream.println(INDENT + "Provider: " + c.getProvider()); ostream.println(INDENT + "Effective CPU Count: " + c.getEffectiveCpuCount()); - ostream.println(INDENT + "CPU Period: " + c.getCpuPeriod() + - (c.getCpuPeriod() == -1 ? "" : "us")); - ostream.println(INDENT + "CPU Quota: " + c.getCpuQuota() + - (c.getCpuQuota() == -1 ? "" : "us")); - ostream.println(INDENT + "CPU Shares: " + c.getCpuShares()); + ostream.println(formatCpuVal(c.getCpuPeriod(), INDENT + "CPU Period: ", longRetvalNotSupported)); + ostream.println(formatCpuVal(c.getCpuQuota(), INDENT + "CPU Quota: ", longRetvalNotSupported)); + ostream.println(formatCpuVal(c.getCpuShares(), INDENT + "CPU Shares: ", longRetvalNotSupported)); int cpus[] = c.getCpuSetCpus(); - ostream.println(INDENT + "List of Processors, " - + cpus.length + " total: "); + if (cpus != null) { + ostream.println(INDENT + "List of Processors, " + + cpus.length + " total: "); - ostream.print(INDENT); - for (int i = 0; i < cpus.length; i++) { - ostream.print(cpus[i] + " "); - } - if (cpus.length > 0) { - ostream.println(""); + ostream.print(INDENT); + for (int i = 0; i < cpus.length; i++) { + ostream.print(cpus[i] + " "); + } + if (cpus.length > 0) { + ostream.println(""); + } + } else { + ostream.println(INDENT + "List of Processors: N/A"); } cpus = c.getEffectiveCpuSetCpus(); - ostream.println(INDENT + "List of Effective Processors, " - + cpus.length + " total: "); + if (cpus != null) { + ostream.println(INDENT + "List of Effective Processors, " + + cpus.length + " total: "); - ostream.print(INDENT); - for (int i = 0; i < cpus.length; i++) { - ostream.print(cpus[i] + " "); - } - if (cpus.length > 0) { - ostream.println(""); + ostream.print(INDENT); + for (int i = 0; i < cpus.length; i++) { + ostream.print(cpus[i] + " "); + } + if (cpus.length > 0) { + ostream.println(""); + } + } else { + ostream.println(INDENT + "List of Effective Processors: N/A"); } int mems[] = c.getCpuSetMems(); - ostream.println(INDENT + "List of Memory Nodes, " - + mems.length + " total: "); + if (mems != null) { + ostream.println(INDENT + "List of Memory Nodes, " + + mems.length + " total: "); - ostream.print(INDENT); - for (int i = 0; i < mems.length; i++) { - ostream.print(mems[i] + " "); - } - if (mems.length > 0) { - ostream.println(""); + ostream.print(INDENT); + for (int i = 0; i < mems.length; i++) { + ostream.print(mems[i] + " "); + } + if (mems.length > 0) { + ostream.println(""); + } + } else { + ostream.println(INDENT + "List of Memory Nodes: N/A"); } mems = c.getEffectiveCpuSetMems(); - ostream.println(INDENT + "List of Available Memory Nodes, " - + mems.length + " total: "); + if (mems != null) { + ostream.println(INDENT + "List of Available Memory Nodes, " + + mems.length + " total: "); - ostream.print(INDENT); - for (int i = 0; i < mems.length; i++) { - ostream.print(mems[i] + " "); - } - if (mems.length > 0) { - ostream.println(""); + ostream.print(INDENT); + for (int i = 0; i < mems.length; i++) { + ostream.print(mems[i] + " "); + } + if (mems.length > 0) { + ostream.println(""); + } + } else { + ostream.println(INDENT + "List of Available Memory Nodes: N/A"); } - ostream.println(INDENT + "CPUSet Memory Pressure Enabled: " - + c.isCpuSetMemoryPressureEnabled()); - long limit = c.getMemoryLimit(); - ostream.println(INDENT + "Memory Limit: " + - ((limit >= 0) ? SizePrefix.scaleValue(limit) : "Unlimited")); + ostream.println(formatLimitString(limit, INDENT + "Memory Limit: ", longRetvalNotSupported)); limit = c.getMemorySoftLimit(); - ostream.println(INDENT + "Memory Soft Limit: " + - ((limit >= 0) ? SizePrefix.scaleValue(limit) : "Unlimited")); + ostream.println(formatLimitString(limit, INDENT + "Memory Soft Limit: ", longRetvalNotSupported)); limit = c.getMemoryAndSwapLimit(); - ostream.println(INDENT + "Memory & Swap Limit: " + - ((limit >= 0) ? SizePrefix.scaleValue(limit) : "Unlimited")); + ostream.println(formatLimitString(limit, INDENT + "Memory & Swap Limit: ", longRetvalNotSupported)); - limit = c.getKernelMemoryLimit(); - ostream.println(INDENT + "Kernel Memory Limit: " + - ((limit >= 0) ? SizePrefix.scaleValue(limit) : "Unlimited")); - - limit = c.getTcpMemoryLimit(); - ostream.println(INDENT + "TCP Memory Limit: " + - ((limit >= 0) ? SizePrefix.scaleValue(limit) : "Unlimited")); + ostream.println(""); + } - ostream.println(INDENT + "Out Of Memory Killer Enabled: " - + c.isMemoryOOMKillEnabled()); + private static String formatLimitString(long limit, String prefix, long unavailable) { + if (limit >= 0) { + return prefix + SizePrefix.scaleValue(limit); + } else if (limit == unavailable) { + return prefix + "N/A"; + } else { + return prefix + "Unlimited"; + } + } - ostream.println(""); + private static String formatCpuVal(long cpuVal, String prefix, long unavailable) { + if (cpuVal >= 0) { + return prefix + cpuVal + "us"; + } else if (cpuVal == unavailable) { + return prefix + "N/A"; + } else { + return prefix + cpuVal; + } } private enum SizePrefix { diff --git a/jdk/src/solaris/classes/sun/management/OperatingSystemImpl.java b/jdk/src/solaris/classes/sun/management/OperatingSystemImpl.java index 8399cc061ce..4b8955f1276 100644 --- a/jdk/src/solaris/classes/sun/management/OperatingSystemImpl.java +++ b/jdk/src/solaris/classes/sun/management/OperatingSystemImpl.java @@ -281,7 +281,7 @@ public double getProcessCpuLoad() { } private boolean isCpuSetSameAsHostCpuSet() { - if (containerMetrics != null) { + if (containerMetrics != null && containerMetrics.getCpuSetCpus() != null) { return containerMetrics.getCpuSetCpus().length == getHostOnlineCpuCount0(); } return false; diff --git a/jdk/test/jdk/internal/platform/cgroup/TestCgroupMetrics.java b/jdk/test/jdk/internal/platform/cgroup/TestCgroupMetrics.java index 13751a0e530..f20e222fce3 100644 --- a/jdk/test/jdk/internal/platform/cgroup/TestCgroupMetrics.java +++ b/jdk/test/jdk/internal/platform/cgroup/TestCgroupMetrics.java @@ -41,14 +41,7 @@ public static void main(String[] args) throws Exception { } MetricsTester metricsTester = new MetricsTester(); - metricsTester.setup(); - metricsTester.testCpuAccounting(); - metricsTester.testCpuSchedulingMetrics(); - metricsTester.testCpuSets(); - metricsTester.testMemorySubsystem(); - metricsTester.testBlkIO(); - metricsTester.testCpuConsumption(); - metricsTester.testMemoryUsage(); + metricsTester.testAll(metrics); System.out.println("TEST PASSED!!!"); } diff --git a/jdk/test/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java b/jdk/test/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java new file mode 100644 index 00000000000..d11b2ff8d48 --- /dev/null +++ b/jdk/test/jdk/internal/platform/cgroup/TestCgroupSubsystemController.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2020, 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.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import jdk.internal.platform.CgroupSubsystemController; +import jdk.test.lib.Utils; +import jdk.testlibrary.FileUtils; + +/* + * @test + * @requires os.family == "linux" + * @modules java.base/jdk.internal.platform + * @library /lib + * @library /lib/testlibrary + * @run junit/othervm TestCgroupSubsystemController + */ + +/** + * + * Basic unit test for CgroupSubsystemController + * + */ +public class TestCgroupSubsystemController { + + private static final double DELTA = 0.01; + private Path existingDirectory; + private Path existingFile; + private String existingFileName = "test-controller-file"; + private String existingFileContents = "foobar"; + private String doubleValueContents = "1.5"; + private String longValueContents = "3000000000"; + private String longValueMatchingLineContents = "testme\n" + + "itemfoo 25"; + private String longEntryContents = "s 1\n" + + "t 2"; + private String longEntryName = "longEntry"; + private String longEntryMatchingLineName = "longMatchingLine"; + private String doubleValueName = "doubleValue"; + private String longValueName = "longValue"; + private CgroupSubsystemController mockController; + + @Before + public void setup() { + try { + existingDirectory = Utils.createTempDirectory(TestCgroupSubsystemController.class.getSimpleName()); + existingFile = Paths.get(existingDirectory.toString(), existingFileName); + Files.write(existingFile, existingFileContents.getBytes(StandardCharsets.UTF_8)); + Path longFile = Paths.get(existingDirectory.toString(), longValueName); + Files.write(longFile, longValueContents.getBytes()); + Path doubleFile = Paths.get(existingDirectory.toString(), doubleValueName); + Files.write(doubleFile, doubleValueContents.getBytes()); + Path longEntryFile = Paths.get(existingDirectory.toString(), longEntryName); + Files.write(longEntryFile, longEntryContents.getBytes()); + Path longMatchingLine = Paths.get(existingDirectory.toString(), longEntryMatchingLineName); + Files.write(longMatchingLine, longValueMatchingLineContents.getBytes()); + mockController = new MockCgroupSubsystemController(existingDirectory.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @After + public void teardown() { + try { + FileUtils.deleteFileTreeWithRetry(existingDirectory); + } catch (IOException e) { + System.err.println("Teardown failed. " + e.getMessage()); + } + } + + @Test + public void getStringValueNullController() { + String val = CgroupSubsystemController.getStringValue(null, "ignore"); + assertNull(val); + } + + @Test + public void getStringValueIOException() throws IOException { + String val = CgroupSubsystemController.getStringValue(mockController, "don-t-exist.txt"); + assertNull(val); + } + + @Test + public void getStringValueSuccess() { + String actual = CgroupSubsystemController.getStringValue(mockController, existingFileName); + assertEquals(existingFileContents, actual); + } + + @Test + public void convertStringToLong() { + String strVal = "1230"; + long longVal = Long.parseLong(strVal); + long actual = CgroupSubsystemController.convertStringToLong(strVal, -1L, 0); + assertEquals(longVal, actual); + + String overflowVal = "9223372036854775808"; // Long.MAX_VALUE + 1 + long overflowDefault = -1; + actual = CgroupSubsystemController.convertStringToLong(overflowVal, overflowDefault, 0); + assertEquals(overflowDefault, actual); + overflowDefault = Long.MAX_VALUE; + actual = CgroupSubsystemController.convertStringToLong(overflowVal, overflowDefault, 0); + assertEquals(overflowDefault, actual); + } + + @Test + public void convertStringRangeToIntArray() { + assertNull(CgroupSubsystemController.stringRangeToIntArray(null)); + assertNull(CgroupSubsystemController.stringRangeToIntArray("")); + String strRange = "2,4,6"; + int[] actual = CgroupSubsystemController.stringRangeToIntArray(strRange); + int[] expected = new int[] { 2, 4, 6 }; + assertTrue(Arrays.equals(expected, actual)); + strRange = "6,1-3"; + actual = CgroupSubsystemController.stringRangeToIntArray(strRange); + expected = new int[] { 1, 2, 3, 6 }; + assertTrue(Arrays.equals(expected, actual)); + } + + @Test + public void getDoubleValue() { + double defaultValue = -3; + double actual = CgroupSubsystemController.getDoubleValue(null, null, defaultValue); + assertEquals(defaultValue, actual, DELTA); + double expected = Double.parseDouble(doubleValueContents); + actual = CgroupSubsystemController.getDoubleValue(mockController, doubleValueName, defaultValue); + assertEquals(expected, actual, DELTA); + actual = CgroupSubsystemController.getDoubleValue(mockController, "don't-exist", defaultValue); + assertEquals(defaultValue, actual, DELTA); + } + + @Test + public void getLongValue() { + long defaultValue = -4; + long actual = CgroupSubsystemController.getLongValue(null, null, Long::parseLong, defaultValue); + assertEquals(defaultValue, actual); + actual = CgroupSubsystemController.getLongValue(mockController, "dont-exist", Long::parseLong, defaultValue); + assertEquals(defaultValue, actual); + long expected = Long.parseLong(longValueContents); + actual = CgroupSubsystemController.getLongValue(mockController, longValueName, Long::parseLong, defaultValue); + assertEquals(expected, actual); + } + + @Test + public void getLongEntry() { + long defaultValue = -5; + long actual = CgroupSubsystemController.getLongEntry(null, null, "no-matter", defaultValue); + assertEquals(defaultValue, actual); + actual = CgroupSubsystemController.getLongEntry(mockController, "dont-exist", "foo-bar", defaultValue); + assertEquals(defaultValue, actual); + actual = CgroupSubsystemController.getLongEntry(mockController, longEntryName, "t", defaultValue); + assertEquals(2, actual); + } + + @Test + public void getLongMatchingLine() { + long defaultValue = -6; + long actual = CgroupSubsystemController.getLongValueMatchingLine(null, null, "no-matter", Long::parseLong, defaultValue); + assertEquals(defaultValue, actual); + actual = CgroupSubsystemController.getLongValueMatchingLine(mockController, "dont-exist", "no-matter", Long::parseLong, defaultValue); + assertEquals(defaultValue, actual); + actual = CgroupSubsystemController.getLongValueMatchingLine(mockController, longEntryMatchingLineName, "item", TestCgroupSubsystemController::convertLong, defaultValue); + assertEquals(25, actual); + } + + public static long convertLong(String line) { + return Long.parseLong(line.split("\\s+")[1]); + } + + static class MockCgroupSubsystemController implements CgroupSubsystemController { + + private final String path; + + public MockCgroupSubsystemController(String path) { + this.path = path; + } + + @Override + public String path() { + return path; + } + + } + +} diff --git a/jdk/test/jdk/internal/platform/docker/MetricsCpuTester.java b/jdk/test/jdk/internal/platform/docker/MetricsCpuTester.java index b93c321b1cc..1ecbf1b25a1 100644 --- a/jdk/test/jdk/internal/platform/docker/MetricsCpuTester.java +++ b/jdk/test/jdk/internal/platform/docker/MetricsCpuTester.java @@ -143,6 +143,11 @@ private static void testCpuSetMemNodes(String cpusetMems) { private static void testCpuShares(long shares) { Metrics metrics = Metrics.systemMetrics(); + if ("cgroupv2".equals(metrics.getProvider()) && shares < 1024) { + // Adjust input shares for < 1024 cpu shares as the + // impl. rounds up to the next multiple of 1024 + shares = 1024; + } long newShares = metrics.getCpuShares(); if (newShares != shares) { throw new RuntimeException("CPU shares not equal, expected : [" diff --git a/jdk/test/jdk/internal/platform/docker/MetricsMemoryTester.java b/jdk/test/jdk/internal/platform/docker/MetricsMemoryTester.java index 27c8742cea2..79b7e37a3f5 100644 --- a/jdk/test/jdk/internal/platform/docker/MetricsMemoryTester.java +++ b/jdk/test/jdk/internal/platform/docker/MetricsMemoryTester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,9 +22,14 @@ */ import java.util.Arrays; + import jdk.internal.platform.Metrics; +import jdk.internal.platform.CgroupV1Metrics; public class MetricsMemoryTester { + + private static final long UNLIMITED = -1; + public static void main(String[] args) { System.out.println(Arrays.toString(args)); switch (args[0]) { @@ -72,9 +77,11 @@ private static void testMemoryFailCount() { // Allocate 512M of data byte[][] bytes = new byte[64][]; + boolean atLeastOneAllocationWorked = false; for (int i = 0; i < 64; i++) { try { bytes[i] = new byte[8 * 1024 * 1024]; + atLeastOneAllocationWorked = true; // Break out as soon as we see an increase in failcount // to avoid getting killed by the OOM killer. if (Metrics.systemMetrics().getMemoryFailCount() > count) { @@ -84,6 +91,12 @@ private static void testMemoryFailCount() { break; } } + if (!atLeastOneAllocationWorked) { + System.out.println("Allocation failed immediately. Ignoring test!"); + return; + } + // Be sure bytes allocations don't get optimized out + System.out.println("DEBUG: Bytes allocation length 1: " + bytes[0].length); if (Metrics.systemMetrics().getMemoryFailCount() <= count) { throw new RuntimeException("Memory fail count : new : [" + Metrics.systemMetrics().getMemoryFailCount() + "]" @@ -107,13 +120,23 @@ private static void testMemorySoftLimit(String softLimit) { } private static void testKernelMemoryLimit(String value) { - long limit = getMemoryValue(value); - if (limit != Metrics.systemMetrics().getKernelMemoryLimit()) { - throw new RuntimeException("Kernel Memory limit not equal, expected : [" - + limit + "]" + ", got : [" - + Metrics.systemMetrics().getKernelMemoryLimit() + "]"); + Metrics m = Metrics.systemMetrics(); + if (m instanceof CgroupV1Metrics) { + CgroupV1Metrics mCgroupV1 = (CgroupV1Metrics)m; + System.out.println("TEST PASSED!!!"); + long limit = getMemoryValue(value); + long kmemlimit = mCgroupV1.getKernelMemoryLimit(); + // Note that the kernel memory limit might get ignored by OCI runtimes + // This feature is deprecated. Only perform the check if we get an actual + // limit back. + if (kmemlimit != UNLIMITED && limit != kmemlimit) { + throw new RuntimeException("Kernel Memory limit not equal, expected : [" + + limit + "]" + ", got : [" + + kmemlimit + "]"); + } + } else { + throw new RuntimeException("oomKillFlag test not supported for cgroups v2"); } - System.out.println("TEST PASSED!!!"); } private static void testMemoryAndSwapLimit(String memory, String memAndSwap) { @@ -147,9 +170,17 @@ private static long getMemoryValue(String value) { } private static void testOomKillFlag(boolean oomKillFlag) { - if (!(oomKillFlag ^ Metrics.systemMetrics().isMemoryOOMKillEnabled())) { - throw new RuntimeException("oomKillFlag error"); + Metrics m = Metrics.systemMetrics(); + if (m instanceof CgroupV1Metrics) { + CgroupV1Metrics mCgroupV1 = (CgroupV1Metrics)m; + Boolean expected = Boolean.valueOf(oomKillFlag); + Boolean actual = mCgroupV1.isMemoryOOMKillEnabled(); + if (!(expected.equals(actual))) { + throw new RuntimeException("oomKillFlag error"); + } + System.out.println("TEST PASSED!!!"); + } else { + throw new RuntimeException("oomKillFlag test not supported for cgroups v2"); } - System.out.println("TEST PASSED!!!"); } } diff --git a/jdk/test/jdk/internal/platform/docker/TestDockerCpuMetrics.java b/jdk/test/jdk/internal/platform/docker/TestDockerCpuMetrics.java index 8b4b0d2f23d..2377a6b14eb 100644 --- a/jdk/test/jdk/internal/platform/docker/TestDockerCpuMetrics.java +++ b/jdk/test/jdk/internal/platform/docker/TestDockerCpuMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,10 +76,10 @@ public static void main(String[] args) throws Exception { int[] cpuSetMems = Metrics.systemMetrics().getCpuSetMems(); String memNodes = null; - if (cpuSetMems.length > 1) { + if (cpuSetMems != null && cpuSetMems.length > 1) { int endNode = (cpuSetMems[cpuSetMems.length - 1] - cpuSetMems[0]) / 2 + cpuSetMems[0]; memNodes = cpuSetMems[0] + "-" + endNode; - } else if (cpuSetMems.length == 1) { + } else if (cpuSetMems != null && cpuSetMems.length == 1) { memNodes = cpuSetMems[0] + ""; } diff --git a/jdk/test/jdk/internal/platform/docker/TestDockerMemoryMetrics.java b/jdk/test/jdk/internal/platform/docker/TestDockerMemoryMetrics.java index f7024ddfea0..f6035ccf374 100644 --- a/jdk/test/jdk/internal/platform/docker/TestDockerMemoryMetrics.java +++ b/jdk/test/jdk/internal/platform/docker/TestDockerMemoryMetrics.java @@ -21,10 +21,12 @@ * questions. */ +import jdk.internal.platform.Metrics; 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; +import jdk.test.lib.process.OutputAnalyzer; /* * @test @@ -55,10 +57,21 @@ public static void main(String[] args) throws Exception { testMemoryAndSwapLimit("200m", "1g"); testMemoryAndSwapLimit("100m", "200m"); - testKernelMemoryLimit("100m"); - testKernelMemoryLimit("1g"); - - testOomKillFlag("100m", false); + Metrics m = Metrics.systemMetrics(); + // kernel memory, '--kernel-memory' switch, and OOM killer, + // '--oom-kill-disable' switch, tests not supported by cgroupv2 + // runtimes + if (m != null) { + if ("cgroupv1".equals(m.getProvider())) { + testKernelMemoryLimit("100m"); + testKernelMemoryLimit("1g"); + + testOomKillFlag("100m", false); + } else { + System.out.println("kernel memory tests and OOM Kill flag tests not " + + "possible with cgroupv2."); + } + } testOomKillFlag("100m", true); testMemoryFailCount("64m"); @@ -66,7 +79,9 @@ public static void main(String[] args) throws Exception { testMemorySoftLimit("500m","200m"); } finally { - DockerTestUtils.removeDockerImage(imageName); + if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { + DockerTestUtils.removeDockerImage(imageName); + } } } @@ -127,7 +142,8 @@ private static void testOomKillFlag(String value, boolean oomKillFlag) throws Ex } opts.addJavaOpts("-cp", "/test-classes/") .addClassOptions("memory", value, oomKillFlag + ""); - DockerTestUtils.dockerRunJava(opts).shouldHaveExitValue(0).shouldContain("TEST PASSED!!!"); + OutputAnalyzer oa = DockerTestUtils.dockerRunJava(opts); + oa.shouldHaveExitValue(0).shouldContain("TEST PASSED!!!"); } private static void testMemorySoftLimit(String mem, String softLimit) throws Exception { diff --git a/jdk/test/lib/jdk/test/lib/containers/cgroup/CPUSetsReader.java b/jdk/test/lib/jdk/test/lib/containers/cgroup/CPUSetsReader.java index 5e0f6f0b686..c4e6e628236 100644 --- a/jdk/test/lib/jdk/test/lib/containers/cgroup/CPUSetsReader.java +++ b/jdk/test/lib/jdk/test/lib/containers/cgroup/CPUSetsReader.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; + import jdk.test.lib.Asserts; @@ -54,8 +55,7 @@ private static void assertParse(String cpuSet, String expectedResult) { public static int getNumCpus() { String path = "/proc/cpuinfo"; - try { - Stream stream = Files.lines(Paths.get(path)); + try (Stream stream = Files.lines(Paths.get(path))) { return (int) stream.filter(line -> line.startsWith("processor")).count(); } catch (IOException e) { return 0; diff --git a/jdk/test/lib/jdk/test/lib/containers/cgroup/CgroupMetricsTester.java b/jdk/test/lib/jdk/test/lib/containers/cgroup/CgroupMetricsTester.java new file mode 100644 index 00000000000..5e3695dbaa3 --- /dev/null +++ b/jdk/test/lib/jdk/test/lib/containers/cgroup/CgroupMetricsTester.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020, 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. + */ + +package jdk.test.lib.containers.cgroup; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +interface CgroupMetricsTester { + + public static final double ERROR_MARGIN = 0.1; + public static final String EMPTY_STR = ""; + + public void testMemorySubsystem(); + public void testCpuAccounting(); + public void testCpuSchedulingMetrics(); + public void testCpuSets(); + public void testCpuConsumption() throws IOException, InterruptedException; + public void testMemoryUsage() throws Exception; + public void testMisc(); + + public static long convertStringToLong(String strval, long overflowRetval) { + long retval = 0; + if (strval == null) return 0L; + + try { + retval = Long.parseLong(strval); + } catch (NumberFormatException e) { + // For some properties (e.g. memory.limit_in_bytes) we may overflow the range of signed long. + // In this case, return Long.MAX_VALUE + BigInteger b = new BigInteger(strval); + if (b.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + return overflowRetval; + } + } + return retval; + } + + public static boolean compareWithErrorMargin(long oldVal, long newVal) { + return Math.abs(oldVal - newVal) <= Math.abs(oldVal * ERROR_MARGIN); + } + + public static boolean compareWithErrorMargin(double oldVal, double newVal) { + return Math.abs(oldVal - newVal) <= Math.abs(oldVal * ERROR_MARGIN); + } + + public static void fail(String controller, String metric, long oldVal, long testVal) { + throw new RuntimeException("Test failed for - " + controller + ":" + + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); + } + + public static void fail(String controller, String metric, String oldVal, String testVal) { + throw new RuntimeException("Test failed for - " + controller + ":" + + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); + } + + public static void fail(String controller, String metric, double oldVal, double testVal) { + throw new RuntimeException("Test failed for - " + controller + ":" + + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); + } + + public static void fail(String controller, String metric, boolean oldVal, boolean testVal) { + throw new RuntimeException("Test failed for - " + controller + ":" + + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); + } + + public static void warn(String controller, String metric, long oldVal, long testVal) { + System.err.println("Warning - " + controller + ":" + metric + + ", expected [" + oldVal + "], got [" + testVal + "]"); + } + + public static Integer[] convertCpuSetsToArray(String cpusstr) { + if (cpusstr == null || EMPTY_STR.equals(cpusstr)) { + return new Integer[0]; + } + // Parse range string in the format 1,2-6,7 + Integer[] cpuSets = Stream.of(cpusstr.split(",")).flatMap(a -> { + if (a.contains("-")) { + String[] range = a.split("-"); + return IntStream.rangeClosed(Integer.parseInt(range[0]), + Integer.parseInt(range[1])).boxed(); + } else { + return Stream.of(Integer.parseInt(a)); + } + }).toArray(Integer[]::new); + return cpuSets; + } + +} diff --git a/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java b/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java index 78933adf32e..168f5bb29c6 100644 --- a/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java +++ b/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -21,593 +21,58 @@ * questions. */ + package jdk.test.lib.containers.cgroup; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.LongStream; -import java.util.stream.Stream; +import java.util.Objects; + import jdk.internal.platform.Metrics; +/** + * Cgroup version agnostic metrics tester + * + */ public class MetricsTester { - private static final double ERROR_MARGIN = 0.1; - private static long unlimited_minimum = 0x7FFFFFFFFF000000L; - long startSysVal; - long startUserVal; - long startUsage; - long startPerCpu[]; - - enum SubSystem { - MEMORY("memory"), - CPUSET("cpuset"), - CPU("cpu"), - CPUACCT("cpuacct"), - BLKIO("blkio"); - - private String value; - - SubSystem(String value) { - this.value = value; - } - - public String value() { - return value; - } - } - - private static final Set allowedSubSystems = - Stream.of(SubSystem.values()).map(SubSystem::value).collect(Collectors.toSet()); - - private static final Map subSystemPaths = new HashMap<>(); - - private static void setPath(String[] line) { - String cgroupPath = line[2]; - String[] subSystems = line[1].split(","); - - for (String subSystem : subSystems) { - if (allowedSubSystems.contains(subSystem)) { - String[] paths = subSystemPaths.get(subSystem); - String finalPath = ""; - String root = paths[0]; - String mountPoint = paths[1]; - if (root != null && cgroupPath != null) { - if (root.equals("/")) { - if (!cgroupPath.equals("/")) { - finalPath = mountPoint + cgroupPath; - } else { - finalPath = mountPoint; - } - } else { - if (root.equals(cgroupPath)) { - finalPath = mountPoint; - } else { - if (cgroupPath.startsWith(root)) { - if (cgroupPath.length() > root.length()) { - String cgroupSubstr = cgroupPath.substring(root.length()); - finalPath = mountPoint + cgroupSubstr; - } - } - } - } - } - subSystemPaths.put(subSystem, new String[]{finalPath, mountPoint}); - } - } - } - - private static void createSubsystems(String[] line) { - if (line.length < 5) return; - Path p = Paths.get(line[4]); - String subsystemName = p.getFileName().toString(); - if (subsystemName != null) { - for (String subSystem : subsystemName.split(",")) { - if (allowedSubSystems.contains(subSystem)) { - subSystemPaths.put(subSystem, new String[]{line[3], line[4]}); - } - } - } - } - - public void setup() { - Metrics metrics = Metrics.systemMetrics(); - // Initialize CPU usage metrics before we do any testing. - startSysVal = metrics.getCpuSystemUsage(); - startUserVal = metrics.getCpuUserUsage(); - startUsage = metrics.getCpuUsage(); - startPerCpu = metrics.getPerCpuUsage(); - - try { - Stream lines = Files.lines(Paths.get("/proc/self/mountinfo")); - lines.filter(line -> line.contains(" - cgroup cgroup ")) - .map(line -> line.split(" ")) - .forEach(MetricsTester::createSubsystems); - lines.close(); - - lines = Files.lines(Paths.get("/proc/self/cgroup")); - lines.map(line -> line.split(":")) - .filter(line -> (line.length >= 3)) - .forEach(MetricsTester::setPath); - lines.close(); - } catch (IOException e) { - } - } - - private static String getFileContents(SubSystem subSystem, String fileName) { - String fname = subSystemPaths.get(subSystem.value())[0] + File.separator + fileName; - try { - return new Scanner(new File(fname)).useDelimiter("\\Z").next(); - } catch (FileNotFoundException e) { - System.err.println("Unable to open : " + fname); - return ""; - } - } - - private static long getLongValueFromFile(SubSystem subSystem, String fileName) { - String data = getFileContents(subSystem, fileName); - return data.isEmpty() ? 0L : Long.parseLong(data); - } - - private static long getLongValueFromFile(SubSystem subSystem, String metric, String subMetric) { - String stats = getFileContents(subSystem, metric); - String[] tokens = stats.split("[\\r\\n]+"); - for (int i = 0; i < tokens.length; i++) { - if (tokens[i].startsWith(subMetric)) { - return Long.parseLong(tokens[i].split("\\s+")[1]); - } - } - return 0L; - } - - private static double getDoubleValueFromFile(SubSystem subSystem, String fileName) { - String data = getFileContents(subSystem, fileName); - return data.isEmpty() ? 0.0 : Double.parseDouble(data); - } - - private boolean compareWithErrorMargin(long oldVal, long newVal) { - return Math.abs(oldVal - newVal) <= Math.abs(oldVal * ERROR_MARGIN); - } - - private boolean compareWithErrorMargin(double oldVal, double newVal) { - return Math.abs(oldVal - newVal) <= Math.abs(oldVal * ERROR_MARGIN); - } - - private static void fail(SubSystem system, String metric, long oldVal, long testVal) { - throw new RuntimeException("Test failed for - " + system.value + ":" - + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); - } - - private static void fail(SubSystem system, String metric, String oldVal, String testVal) { - throw new RuntimeException("Test failed for - " + system.value + ":" - + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); - } - - private static void fail(SubSystem system, String metric, double oldVal, double testVal) { - throw new RuntimeException("Test failed for - " + system.value + ":" - + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); - } - - private static void fail(SubSystem system, String metric, boolean oldVal, boolean testVal) { - throw new RuntimeException("Test failed for - " + system.value + ":" - + metric + ", expected [" + oldVal + "], got [" + testVal + "]"); - } - - private static void warn(SubSystem system, String metric, long oldVal, long testVal) { - System.err.println("Warning - " + system.value + ":" + metric - + ", expected [" + oldVal + "], got [" + testVal + "]"); - } - - public void testMemorySubsystem() { - Metrics metrics = Metrics.systemMetrics(); - - // User Memory - long oldVal = metrics.getMemoryFailCount(); - long newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.failcnt"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.failcnt", oldVal, newVal); - } - - oldVal = metrics.getMemoryLimit(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.limit_in_bytes"); - newVal = newVal > unlimited_minimum ? -1L : newVal; - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.limit_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getMemoryMaxUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.max_usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.max_usage_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getMemoryUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.usage_in_bytes", oldVal, newVal); - } - - // Kernel memory - oldVal = metrics.getKernelMemoryFailCount(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.failcnt"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.failcnt", oldVal, newVal); - } - - oldVal = metrics.getKernelMemoryLimit(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.limit_in_bytes"); - newVal = newVal > unlimited_minimum ? -1L : newVal; - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.limit_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getKernelMemoryMaxUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.max_usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.max_usage_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getKernelMemoryUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.usage_in_bytes", oldVal, newVal); - } - - //TCP Memory - oldVal = metrics.getTcpMemoryFailCount(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.tcp.failcnt"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.tcp.failcnt", oldVal, newVal); - } - - oldVal = metrics.getTcpMemoryLimit(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.tcp.limit_in_bytes"); - newVal = newVal > unlimited_minimum ? -1L : newVal; - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.tcp.limit_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getTcpMemoryMaxUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.tcp.max_usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.tcp.max_usage_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getTcpMemoryUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.kmem.tcp.usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.kmem.tcp.usage_in_bytes", oldVal, newVal); - } - - // Memory and Swap - // Skip swap tests if no swap is configured. - if (metrics.getMemoryAndSwapLimit() > metrics.getMemoryLimit()) { - oldVal = metrics.getMemoryAndSwapFailCount(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.memsw.failcnt"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.memsw.failcnt", oldVal, newVal); - } - - oldVal = metrics.getMemoryAndSwapLimit(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.memsw.limit_in_bytes"); - newVal = newVal > unlimited_minimum ? -1L : newVal; - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.memsw.limit_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getMemoryAndSwapMaxUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.memsw.max_usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.memsw.max_usage_in_bytes", oldVal, newVal); - } - - oldVal = metrics.getMemoryAndSwapUsage(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.memsw.usage_in_bytes"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.memsw.usage_in_bytes", oldVal, newVal); - } - } - - oldVal = metrics.getMemorySoftLimit(); - newVal = getLongValueFromFile(SubSystem.MEMORY, "memory.soft_limit_in_bytes"); - newVal = newVal > unlimited_minimum ? -1L : newVal; - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.MEMORY, "memory.soft_limit_in_bytes", oldVal, newVal); - } - - boolean oomKillEnabled = metrics.isMemoryOOMKillEnabled(); - boolean newOomKillEnabled = getLongValueFromFile(SubSystem.MEMORY, - "memory.oom_control", "oom_kill_disable") == 0L ? true : false; - if (oomKillEnabled != newOomKillEnabled) { - throw new RuntimeException("Test failed for - " + SubSystem.MEMORY.value + ":" - + "memory.oom_control:oom_kill_disable" + ", expected [" - + oomKillEnabled + "], got [" + newOomKillEnabled + "]"); - } - } - - public void testCpuAccounting() { - Metrics metrics = Metrics.systemMetrics(); - long oldVal = metrics.getCpuUsage(); - long newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpuacct.usage"); - - if (!compareWithErrorMargin(oldVal, newVal)) { - warn(SubSystem.CPUACCT, "cpuacct.usage", oldVal, newVal); - } - - Long[] newVals = Stream.of(getFileContents(SubSystem.CPUACCT, "cpuacct.usage_percpu") - .split("\\s+")) - .map(Long::parseLong) - .toArray(Long[]::new); - Long[] oldVals = LongStream.of(metrics.getPerCpuUsage()).boxed().toArray(Long[]::new); - for (int i = 0; i < oldVals.length; i++) { - if (!compareWithErrorMargin(oldVals[i], newVals[i])) { - warn(SubSystem.CPUACCT, "cpuacct.usage_percpu", oldVals[i], newVals[i]); - } - } + private static final String CGROUP_V1 = "cgroupv1"; + private static final String CGROUP_V2 = "cgroupv2"; - oldVal = metrics.getCpuUserUsage(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpuacct.stat", "user"); - if (!compareWithErrorMargin(oldVal, newVal)) { - warn(SubSystem.CPUACCT, "cpuacct.usage - user", oldVal, newVal); - } - - oldVal = metrics.getCpuSystemUsage(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpuacct.stat", "system"); - if (!compareWithErrorMargin(oldVal, newVal)) { - warn(SubSystem.CPUACCT, "cpuacct.usage - system", oldVal, newVal); - } - } - - public void testCpuSchedulingMetrics() { - Metrics metrics = Metrics.systemMetrics(); - long oldVal = metrics.getCpuPeriod(); - long newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpu.cfs_period_us"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.CPUACCT, "cpu.cfs_period_us", oldVal, newVal); - } - - oldVal = metrics.getCpuQuota(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpu.cfs_quota_us"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.CPUACCT, "cpu.cfs_quota_us", oldVal, newVal); - } - - oldVal = metrics.getCpuShares(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpu.shares"); - if (newVal == 0 || newVal == 1024) newVal = -1; - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.CPUACCT, "cpu.shares", oldVal, newVal); - } - - oldVal = metrics.getCpuNumPeriods(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpu.stat", "nr_periods"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.CPUACCT, "cpu.stat - nr_periods", oldVal, newVal); - } - - oldVal = metrics.getCpuNumThrottled(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpu.stat", "nr_throttled"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.CPUACCT, "cpu.stat - nr_throttled", oldVal, newVal); - } - - oldVal = metrics.getCpuThrottledTime(); - newVal = getLongValueFromFile(SubSystem.CPUACCT, "cpu.stat", "throttled_time"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.CPUACCT, "cpu.stat - throttled_time", oldVal, newVal); + private static CgroupMetricsTester createInstance(Metrics m) { + Objects.requireNonNull(m); + if (CGROUP_V1.equals(m.getProvider())) { + MetricsTesterCgroupV1 t = new MetricsTesterCgroupV1(); + t.setup(); + return t; + } else if (CGROUP_V2.equals(m.getProvider())) { + return new MetricsTesterCgroupV2(); + } else { + System.err.println("WARNING: Metrics provider, '" + m.getProvider() + + "' is unknown!"); + return null; } } - public void testCpuSets() { - Metrics metrics = Metrics.systemMetrics(); - Integer[] oldVal = Arrays.stream(metrics.getCpuSetCpus()).boxed().toArray(Integer[]::new); - Arrays.sort(oldVal); - - String cpusstr = getFileContents(SubSystem.CPUSET, "cpuset.cpus"); - // Parse range string in the format 1,2-6,7 - Integer[] newVal = Stream.of(cpusstr.split(",")).flatMap(a -> { - if (a.contains("-")) { - String[] range = a.split("-"); - return IntStream.rangeClosed(Integer.parseInt(range[0]), - Integer.parseInt(range[1])).boxed(); - } else { - return Stream.of(Integer.parseInt(a)); - } - }).toArray(Integer[]::new); - Arrays.sort(newVal); - if (!Arrays.equals(oldVal, newVal)) { - fail(SubSystem.CPUSET, "cpuset.cpus", Arrays.toString(oldVal), - Arrays.toString(newVal)); - } - - int [] cpuSets = metrics.getEffectiveCpuSetCpus(); - - // Skip this test if this metric is supported on this platform - if (cpuSets.length != 0) { - oldVal = Arrays.stream(cpuSets).boxed().toArray(Integer[]::new); - Arrays.sort(oldVal); - cpusstr = getFileContents(SubSystem.CPUSET, "cpuset.effective_cpus"); - newVal = Stream.of(cpusstr.split(",")).flatMap(a -> { - if (a.contains("-")) { - String[] range = a.split("-"); - return IntStream.rangeClosed(Integer.parseInt(range[0]), - Integer.parseInt(range[1])).boxed(); - } else { - return Stream.of(Integer.parseInt(a)); - } - }).toArray(Integer[]::new); - Arrays.sort(newVal); - if (!Arrays.equals(oldVal, newVal)) { - fail(SubSystem.CPUSET, "cpuset.effective_cpus", Arrays.toString(oldVal), - Arrays.toString(newVal)); - } - } - - oldVal = Arrays.stream(metrics.getCpuSetMems()).boxed().toArray(Integer[]::new); - Arrays.sort(oldVal); - cpusstr = getFileContents(SubSystem.CPUSET, "cpuset.mems"); - newVal = Stream.of(cpusstr.split(",")).flatMap(a -> { - if (a.contains("-")) { - String[] range = a.split("-"); - return IntStream.rangeClosed(Integer.parseInt(range[0]), - Integer.parseInt(range[1])).boxed(); - } else { - return Stream.of(Integer.parseInt(a)); - } - }).toArray(Integer[]::new); - Arrays.sort(newVal); - if (!Arrays.equals(oldVal, newVal)) { - fail(SubSystem.CPUSET, "cpuset.mems", Arrays.toString(oldVal), - Arrays.toString(newVal)); - } - - int [] cpuSetMems = metrics.getEffectiveCpuSetMems(); - - // Skip this test if this metric is supported on this platform - if (cpuSetMems.length != 0) { - oldVal = Arrays.stream(cpuSetMems).boxed().toArray(Integer[]::new); - Arrays.sort(oldVal); - cpusstr = getFileContents(SubSystem.CPUSET, "cpuset.effective_mems"); - newVal = Stream.of(cpusstr.split(",")).flatMap(a -> { - if (a.contains("-")) { - String[] range = a.split("-"); - return IntStream.rangeClosed(Integer.parseInt(range[0]), - Integer.parseInt(range[1])).boxed(); - } else { - return Stream.of(Integer.parseInt(a)); - } - }).toArray(Integer[]::new); - Arrays.sort(newVal); - if (!Arrays.equals(oldVal, newVal)) { - fail(SubSystem.CPUSET, "cpuset.effective_mems", Arrays.toString(oldVal), - Arrays.toString(newVal)); - } - } - - double oldValue = metrics.getCpuSetMemoryPressure(); - double newValue = getDoubleValueFromFile(SubSystem.CPUSET, "cpuset.memory_pressure"); - if (!compareWithErrorMargin(oldValue, newValue)) { - fail(SubSystem.CPUSET, "cpuset.memory_pressure", oldValue, newValue); - } - - boolean oldV = metrics.isCpuSetMemoryPressureEnabled(); - boolean newV = getLongValueFromFile(SubSystem.CPUSET, - "cpuset.memory_pressure_enabled") == 1 ? true : false; - if (oldV != newV) { - fail(SubSystem.CPUSET, "cpuset.memory_pressure_enabled", oldV, newV); - } - } - - public void testBlkIO() { - Metrics metrics = Metrics.systemMetrics(); - long oldVal = metrics.getBlkIOServiceCount(); - long newVal = getLongValueFromFile(SubSystem.BLKIO, - "blkio.throttle.io_service_bytes", "Total"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.BLKIO, "blkio.throttle.io_service_bytes - Total", - oldVal, newVal); - } - - oldVal = metrics.getBlkIOServiced(); - newVal = getLongValueFromFile(SubSystem.BLKIO, "blkio.throttle.io_serviced", "Total"); - if (!compareWithErrorMargin(oldVal, newVal)) { - fail(SubSystem.BLKIO, "blkio.throttle.io_serviced - Total", oldVal, newVal); - } - } - - public void testCpuConsumption() throws IOException, InterruptedException { - Metrics metrics = Metrics.systemMetrics(); - // make system call - long newSysVal = metrics.getCpuSystemUsage(); - long newUserVal = metrics.getCpuUserUsage(); - long newUsage = metrics.getCpuUsage(); - long[] newPerCpu = metrics.getPerCpuUsage(); - - // system/user CPU usage counters may be slowly increasing. - // allow for equal values for a pass - if (newSysVal < startSysVal) { - fail(SubSystem.CPU, "getCpuSystemUsage", newSysVal, startSysVal); - } - - // system/user CPU usage counters may be slowly increasing. - // allow for equal values for a pass - if (newUserVal < startUserVal) { - fail(SubSystem.CPU, "getCpuUserUsage", newUserVal, startUserVal); - } - - if (newUsage <= startUsage) { - fail(SubSystem.CPU, "getCpuUsage", newUsage, startUsage); - } - - boolean success = false; - for (int i = 0; i < startPerCpu.length; i++) { - if (newPerCpu[i] > startPerCpu[i]) { - success = true; - break; - } - } - - if(!success) fail(SubSystem.CPU, "getPerCpuUsage", Arrays.toString(newPerCpu), - Arrays.toString(startPerCpu)); - } - - public void testMemoryUsage() throws Exception { - Metrics metrics = Metrics.systemMetrics(); - long memoryMaxUsage = metrics.getMemoryMaxUsage(); - long memoryUsage = metrics.getMemoryUsage(); - long newMemoryMaxUsage = 0, newMemoryUsage = 0; - - // allocate memory in a loop and check more than once for new values - // otherwise we might see seldom the effect of decreasing new memory values - // e.g. because the system could free up memory - byte[][] bytes = new byte[32][]; - for (int i = 0; i < 32; i++) { - bytes[i] = new byte[8*1024*1024]; - newMemoryUsage = metrics.getMemoryUsage(); - if (newMemoryUsage > memoryUsage) { - break; - } - } - newMemoryMaxUsage = metrics.getMemoryMaxUsage(); - - if (newMemoryMaxUsage < memoryMaxUsage) { - fail(SubSystem.MEMORY, "getMemoryMaxUsage", memoryMaxUsage, - newMemoryMaxUsage); - } - - if (newMemoryUsage < memoryUsage) { - fail(SubSystem.MEMORY, "getMemoryUsage", memoryUsage, newMemoryUsage); - } + public void testAll(Metrics m) throws Exception { + CgroupMetricsTester tester = createInstance(m); + tester.testCpuAccounting(); + tester.testCpuConsumption(); + tester.testCpuSchedulingMetrics(); + tester.testCpuSets(); + tester.testMemorySubsystem(); + tester.testMemoryUsage(); + tester.testMisc(); } public static void main(String[] args) throws Exception { + Metrics m = Metrics.systemMetrics(); // If cgroups is not configured, report success - Metrics metrics = Metrics.systemMetrics(); - if (metrics == null) { + if (m == null) { System.out.println("TEST PASSED!!!"); return; } MetricsTester metricsTester = new MetricsTester(); - metricsTester.setup(); - metricsTester.testCpuAccounting(); - metricsTester.testCpuSchedulingMetrics(); - metricsTester.testCpuSets(); - metricsTester.testMemorySubsystem(); - metricsTester.testBlkIO(); - metricsTester.testCpuConsumption(); - metricsTester.testMemoryUsage(); + metricsTester.testAll(m); System.out.println("TEST PASSED!!!"); } } diff --git a/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTesterCgroupV1.java b/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTesterCgroupV1.java new file mode 100644 index 00000000000..f618ee2b1e1 --- /dev/null +++ b/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTesterCgroupV1.java @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +package jdk.test.lib.containers.cgroup; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import jdk.internal.platform.Metrics; +import jdk.internal.platform.CgroupV1Metrics; + +public class MetricsTesterCgroupV1 implements CgroupMetricsTester { + + private static long unlimited_minimum = 0x7FFFFFFFFF000000L; + long startSysVal; + long startUserVal; + long startUsage; + long startPerCpu[]; + + enum Controller { + MEMORY("memory"), + CPUSET("cpuset"), + CPU("cpu"), + CPUACCT("cpuacct"), + BLKIO("blkio"); + + private String value; + + Controller(String value) { + this.value = value; + } + + public String value() { + return value; + } + } + + private static final Set allowedSubSystems = + Stream.of(Controller.values()).map(Controller::value).collect(Collectors.toSet()); + + private static final Map subSystemPaths = new HashMap<>(); + + private static void setPath(String[] line) { + String cgroupPath = line[2]; + String[] subSystems = line[1].split(","); + + for (String subSystem : subSystems) { + if (allowedSubSystems.contains(subSystem)) { + String[] paths = subSystemPaths.get(subSystem); + String finalPath = ""; + String root = paths[0]; + String mountPoint = paths[1]; + if (root != null && cgroupPath != null) { + if (root.equals("/")) { + if (!cgroupPath.equals("/")) { + finalPath = mountPoint + cgroupPath; + } else { + finalPath = mountPoint; + } + } else { + if (root.equals(cgroupPath)) { + finalPath = mountPoint; + } else { + if (cgroupPath.startsWith(root)) { + if (cgroupPath.length() > root.length()) { + String cgroupSubstr = cgroupPath.substring(root.length()); + finalPath = mountPoint + cgroupSubstr; + } + } + } + } + } + subSystemPaths.put(subSystem, new String[]{finalPath, mountPoint}); + } + } + } + + private static void createSubsystems(String[] line) { + if (line.length < 5) return; + Path p = Paths.get(line[4]); + String subsystemName = p.getFileName().toString(); + if (subsystemName != null) { + for (String subSystem : subsystemName.split(",")) { + if (allowedSubSystems.contains(subSystem)) { + subSystemPaths.put(subSystem, new String[]{line[3], line[4]}); + } + } + } + } + + public void setup() { + Metrics metrics = Metrics.systemMetrics(); + // Initialize CPU usage metrics before we do any testing. + startSysVal = metrics.getCpuSystemUsage(); + startUserVal = metrics.getCpuUserUsage(); + startUsage = metrics.getCpuUsage(); + startPerCpu = metrics.getPerCpuUsage(); + if (startPerCpu == null) { + startPerCpu = new long[0]; + } + + try { + Stream lines = Files.lines(Paths.get("/proc/self/mountinfo")); + lines.filter(line -> line.contains(" - cgroup cgroup ")) + .map(line -> line.split(" ")) + .forEach(MetricsTesterCgroupV1::createSubsystems); + lines.close(); + + lines = Files.lines(Paths.get("/proc/self/cgroup")); + lines.map(line -> line.split(":")) + .filter(line -> (line.length >= 3)) + .forEach(MetricsTesterCgroupV1::setPath); + lines.close(); + } catch (IOException e) { + } + } + + private static String getFileContents(Controller subSystem, String fileName) { + String fname = subSystemPaths.get(subSystem.value())[0] + File.separator + fileName; + try { + return new Scanner(new File(fname)).useDelimiter("\\Z").next(); + } catch (FileNotFoundException e) { + System.err.println("Unable to open : " + fname); + return null; + } + } + + private static long getLongValueFromFile(Controller subSystem, String fileName) { + String data = getFileContents(subSystem, fileName); + return (data == null || data.isEmpty()) ? 0L : convertStringToLong(data); + } + + private static long convertStringToLong(String strval) { + return CgroupMetricsTester.convertStringToLong(strval, Long.MAX_VALUE); + } + + private static long getLongValueFromFile(Controller subSystem, String metric, String subMetric) { + String stats = getFileContents(subSystem, metric); + String[] tokens = stats.split("[\\r\\n]+"); + for (int i = 0; i < tokens.length; i++) { + if (tokens[i].startsWith(subMetric)) { + String strval = tokens[i].split("\\s+")[1]; + return convertStringToLong(strval); + } + } + return 0L; + } + + private static double getDoubleValueFromFile(Controller subSystem, String fileName) { + String data = getFileContents(subSystem, fileName); + return data.isEmpty() ? 0.0 : Double.parseDouble(data); + } + + private static void fail(Controller system, String metric, long oldVal, long testVal) { + CgroupMetricsTester.fail(system.value, metric, oldVal, testVal); + } + + private static void fail(Controller system, String metric, String oldVal, String testVal) { + CgroupMetricsTester.fail(system.value, metric, oldVal, testVal); + } + + private static void fail(Controller system, String metric, double oldVal, double testVal) { + CgroupMetricsTester.fail(system.value, metric, oldVal, testVal); + } + + private static void fail(Controller system, String metric, boolean oldVal, boolean testVal) { + CgroupMetricsTester.fail(system.value, metric, oldVal, testVal); + } + + private static void warn(Controller system, String metric, long oldVal, long testVal) { + CgroupMetricsTester.warn(system.value, metric, oldVal, testVal); + } + + public void testMemorySubsystem() { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + + // User Memory + long oldVal = metrics.getMemoryFailCount(); + long newVal = getLongValueFromFile(Controller.MEMORY, "memory.failcnt"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.failcnt", oldVal, newVal); + } + + oldVal = metrics.getMemoryLimit(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.limit_in_bytes"); + newVal = newVal > unlimited_minimum ? -1L : newVal; + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.limit_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getMemoryMaxUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.max_usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.max_usage_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getMemoryUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.usage_in_bytes", oldVal, newVal); + } + + // Kernel memory + oldVal = metrics.getKernelMemoryFailCount(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.failcnt"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.failcnt", oldVal, newVal); + } + + oldVal = metrics.getKernelMemoryLimit(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.limit_in_bytes"); + newVal = newVal > unlimited_minimum ? -1L : newVal; + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.limit_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getKernelMemoryMaxUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.max_usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.max_usage_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getKernelMemoryUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.usage_in_bytes", oldVal, newVal); + } + + //TCP Memory + oldVal = metrics.getTcpMemoryFailCount(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.tcp.failcnt"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.tcp.failcnt", oldVal, newVal); + } + + oldVal = metrics.getTcpMemoryLimit(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.tcp.limit_in_bytes"); + newVal = newVal > unlimited_minimum ? -1L : newVal; + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.tcp.limit_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getTcpMemoryMaxUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.tcp.max_usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.tcp.max_usage_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getTcpMemoryUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.kmem.tcp.usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.kmem.tcp.usage_in_bytes", oldVal, newVal); + } + + // Memory and Swap + // Skip swap tests if no swap is configured. + if (metrics.getMemoryAndSwapLimit() > metrics.getMemoryLimit()) { + oldVal = metrics.getMemoryAndSwapFailCount(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.memsw.failcnt"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.memsw.failcnt", oldVal, newVal); + } + + oldVal = metrics.getMemoryAndSwapLimit(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.memsw.limit_in_bytes"); + newVal = newVal > unlimited_minimum ? -1L : newVal; + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.memsw.limit_in_bytes", oldVal, newVal); + } + + oldVal = metrics.getMemoryAndSwapMaxUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.memsw.max_usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.memsw.max_usage_in_bytes", oldVal, newVal); + } + + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.memsw.usage_in_bytes", oldVal, newVal); + oldVal = metrics.getMemoryAndSwapUsage(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.memsw.usage_in_bytes"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.memsw.usage_in_bytes", oldVal, newVal); + } + } + } + + oldVal = metrics.getMemorySoftLimit(); + newVal = getLongValueFromFile(Controller.MEMORY, "memory.soft_limit_in_bytes"); + newVal = newVal > unlimited_minimum ? -1L : newVal; + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.MEMORY, "memory.soft_limit_in_bytes", oldVal, newVal); + } + + boolean oomKillEnabled = metrics.isMemoryOOMKillEnabled(); + boolean newOomKillEnabled = getLongValueFromFile(Controller.MEMORY, + "memory.oom_control", "oom_kill_disable") == 0L ? true : false; + if (oomKillEnabled != newOomKillEnabled) { + throw new RuntimeException("Test failed for - " + Controller.MEMORY.value + ":" + + "memory.oom_control:oom_kill_disable" + ", expected [" + + oomKillEnabled + "], got [" + newOomKillEnabled + "]"); + } + } + + public void testCpuAccounting() { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + long oldVal = metrics.getCpuUsage(); + long newVal = getLongValueFromFile(Controller.CPUACCT, "cpuacct.usage"); + + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + warn(Controller.CPUACCT, "cpuacct.usage", oldVal, newVal); + } + + String newValsStr = getFileContents(Controller.CPUACCT, "cpuacct.usage_percpu"); + Long[] newVals = new Long[0]; + if (newValsStr != null) { + newVals = Stream.of(newValsStr + .split("\\s+")) + .map(Long::parseLong) + .toArray(Long[]::new); + } + long[] oldValsPrim = metrics.getPerCpuUsage(); + Long[] oldVals = LongStream.of(oldValsPrim == null ? new long[0] : oldValsPrim) + .boxed().toArray(Long[]::new); + for (int i = 0; i < oldVals.length; i++) { + if (!CgroupMetricsTester.compareWithErrorMargin(oldVals[i], newVals[i])) { + warn(Controller.CPUACCT, "cpuacct.usage_percpu", oldVals[i], newVals[i]); + } + } + + oldVal = metrics.getCpuUserUsage(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpuacct.stat", "user"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + warn(Controller.CPUACCT, "cpuacct.usage - user", oldVal, newVal); + } + + oldVal = metrics.getCpuSystemUsage(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpuacct.stat", "system"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + warn(Controller.CPUACCT, "cpuacct.usage - system", oldVal, newVal); + } + } + + public void testCpuSchedulingMetrics() { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + long oldVal = metrics.getCpuPeriod(); + long newVal = getLongValueFromFile(Controller.CPUACCT, "cpu.cfs_period_us"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.CPUACCT, "cpu.cfs_period_us", oldVal, newVal); + } + + oldVal = metrics.getCpuQuota(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpu.cfs_quota_us"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.CPUACCT, "cpu.cfs_quota_us", oldVal, newVal); + } + + oldVal = metrics.getCpuShares(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpu.shares"); + if (newVal == 0 || newVal == 1024) newVal = -1; + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.CPUACCT, "cpu.shares", oldVal, newVal); + } + + oldVal = metrics.getCpuNumPeriods(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpu.stat", "nr_periods"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.CPUACCT, "cpu.stat - nr_periods", oldVal, newVal); + } + + oldVal = metrics.getCpuNumThrottled(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpu.stat", "nr_throttled"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.CPUACCT, "cpu.stat - nr_throttled", oldVal, newVal); + } + + oldVal = metrics.getCpuThrottledTime(); + newVal = getLongValueFromFile(Controller.CPUACCT, "cpu.stat", "throttled_time"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.CPUACCT, "cpu.stat - throttled_time", oldVal, newVal); + } + } + + public void testCpuSets() { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + Integer[] oldVal = Arrays.stream(metrics.getCpuSetCpus()).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + + String cpusstr = getFileContents(Controller.CPUSET, "cpuset.cpus"); + // Parse range string in the format 1,2-6,7 + Integer[] newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail(Controller.CPUSET, "cpuset.cpus", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + + int [] cpuSets = metrics.getEffectiveCpuSetCpus(); + + // Skip this test if this metric is not supported on this platform + if (cpuSets.length != 0) { + oldVal = Arrays.stream(cpuSets).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + cpusstr = getFileContents(Controller.CPUSET, "cpuset.effective_cpus"); + newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail(Controller.CPUSET, "cpuset.effective_cpus", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + } + + oldVal = Arrays.stream(metrics.getCpuSetMems()).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + cpusstr = getFileContents(Controller.CPUSET, "cpuset.mems"); + newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail(Controller.CPUSET, "cpuset.mems", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + + int [] cpuSetMems = metrics.getEffectiveCpuSetMems(); + + // Skip this test if this metric is not supported on this platform + if (cpuSetMems.length != 0) { + oldVal = Arrays.stream(cpuSetMems).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + cpusstr = getFileContents(Controller.CPUSET, "cpuset.effective_mems"); + newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail(Controller.CPUSET, "cpuset.effective_mems", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + } + + double oldValue = metrics.getCpuSetMemoryPressure(); + double newValue = getDoubleValueFromFile(Controller.CPUSET, "cpuset.memory_pressure"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldValue, newValue)) { + fail(Controller.CPUSET, "cpuset.memory_pressure", oldValue, newValue); + } + + boolean oldV = metrics.isCpuSetMemoryPressureEnabled(); + boolean newV = getLongValueFromFile(Controller.CPUSET, + "cpuset.memory_pressure_enabled") == 1 ? true : false; + if (oldV != newV) { + fail(Controller.CPUSET, "cpuset.memory_pressure_enabled", oldV, newV); + } + } + + private void testBlkIO() { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + long oldVal = metrics.getBlkIOServiceCount(); + long newVal = getLongValueFromFile(Controller.BLKIO, + "blkio.throttle.io_service_bytes", "Total"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.BLKIO, "blkio.throttle.io_service_bytes - Total", + oldVal, newVal); + } + + oldVal = metrics.getBlkIOServiced(); + newVal = getLongValueFromFile(Controller.BLKIO, "blkio.throttle.io_serviced", "Total"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail(Controller.BLKIO, "blkio.throttle.io_serviced - Total", oldVal, newVal); + } + } + + public void testCpuConsumption() throws IOException, InterruptedException { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + // make system call + long newSysVal = metrics.getCpuSystemUsage(); + long newUserVal = metrics.getCpuUserUsage(); + long newUsage = metrics.getCpuUsage(); + long[] newPerCpu = metrics.getPerCpuUsage(); + if (newPerCpu == null) { + newPerCpu = new long[0]; + } + + // system/user CPU usage counters may be slowly increasing. + // allow for equal values for a pass + if (newSysVal < startSysVal) { + fail(Controller.CPU, "getCpuSystemUsage", newSysVal, startSysVal); + } + + // system/user CPU usage counters may be slowly increasing. + // allow for equal values for a pass + if (newUserVal < startUserVal) { + fail(Controller.CPU, "getCpuUserUsage", newUserVal, startUserVal); + } + + if (newUsage <= startUsage) { + fail(Controller.CPU, "getCpuUsage", newUsage, startUsage); + } + + boolean success = false; + for (int i = 0; i < startPerCpu.length; i++) { + if (newPerCpu[i] > startPerCpu[i]) { + success = true; + break; + } + } + + if(!success) fail(Controller.CPU, "getPerCpuUsage", Arrays.toString(newPerCpu), + Arrays.toString(startPerCpu)); + } + + public void testMemoryUsage() throws Exception { + CgroupV1Metrics metrics = (CgroupV1Metrics)Metrics.systemMetrics(); + long memoryMaxUsage = metrics.getMemoryMaxUsage(); + long memoryUsage = metrics.getMemoryUsage(); + long newMemoryMaxUsage = 0, newMemoryUsage = 0; + + // allocate memory in a loop and check more than once for new values + // otherwise we might see seldom the effect of decreasing new memory values + // e.g. because the system could free up memory + byte[][] bytes = new byte[32][]; + for (int i = 0; i < 32; i++) { + bytes[i] = new byte[8*1024*1024]; + newMemoryUsage = metrics.getMemoryUsage(); + if (newMemoryUsage > memoryUsage) { + break; + } + } + newMemoryMaxUsage = metrics.getMemoryMaxUsage(); + + if (newMemoryMaxUsage < memoryMaxUsage) { + fail(Controller.MEMORY, "getMemoryMaxUsage", memoryMaxUsage, + newMemoryMaxUsage); + } + + if (newMemoryUsage < memoryUsage) { + fail(Controller.MEMORY, "getMemoryUsage", memoryUsage, newMemoryUsage); + } + } + + @Override + public void testMisc() { + testBlkIO(); + } +} diff --git a/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTesterCgroupV2.java b/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTesterCgroupV2.java new file mode 100644 index 00000000000..54bd0a28e20 --- /dev/null +++ b/jdk/test/lib/jdk/test/lib/containers/cgroup/MetricsTesterCgroupV2.java @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2020, 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. + */ + +package jdk.test.lib.containers.cgroup; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import jdk.internal.platform.CgroupSubsystem; +import jdk.internal.platform.Metrics; + +public class MetricsTesterCgroupV2 implements CgroupMetricsTester { + + private static final long UNLIMITED = -1; + private static final UnifiedController UNIFIED = new UnifiedController(); + private static final String MAX = "max"; + private static final int PER_CPU_SHARES = 1024; + + private final long startSysVal; + private final long startUserVal; + private final long startUsage; + + static class UnifiedController { + + private static final String NAME = "unified"; + private final String path; + + UnifiedController() { + path = constructPath(); + } + + String getPath() { + return path; + } + + private static String constructPath() { + String mountPath; + String cgroupPath; + try { + List fifthTokens = Files.lines(Paths.get("/proc/self/mountinfo")) + .filter( l -> l.contains("- cgroup2")) + .map(UnifiedController::splitAndMountPath) + .collect(Collectors.toList()); + if (fifthTokens.size() != 1) { + throw new AssertionError("Expected only one cgroup2 line"); + } + mountPath = fifthTokens.get(0); + + List cgroupPaths = Files.lines(Paths.get("/proc/self/cgroup")) + .filter( l -> l.startsWith("0:")) + .map(UnifiedController::splitAndCgroupPath) + .collect(Collectors.toList()); + if (cgroupPaths.size() != 1) { + throw new AssertionError("Expected only one unified controller line"); + } + cgroupPath = cgroupPaths.get(0); + return Paths.get(mountPath, cgroupPath).toString(); + } catch (IOException e) { + return null; + } + } + + public static String splitAndMountPath(String input) { + String[] tokens = input.split("\\s+"); + return tokens[4]; // fifth entry is the mount path + } + + public static String splitAndCgroupPath(String input) { + String[] tokens = input.split(":"); + return tokens[2]; + } + } + + private long getLongLimitValueFromFile(String file) { + String strVal = getStringVal(file); + if (MAX.equals(strVal)) { + return UNLIMITED; + } + return convertStringToLong(strVal); + } + + public MetricsTesterCgroupV2() { + Metrics metrics = Metrics.systemMetrics(); + // Initialize CPU usage metrics before we do any testing. + startSysVal = metrics.getCpuSystemUsage(); + startUserVal = metrics.getCpuUserUsage(); + startUsage = metrics.getCpuUsage(); + } + + private long getLongValueFromFile(String file) { + return convertStringToLong(getStringVal(file)); + } + + private long getLongValueEntryFromFile(String file, String metric) { + Path filePath = Paths.get(UNIFIED.getPath(), file); + try { + String strVal = Files.lines(filePath).filter(l -> l.startsWith(metric)).collect(Collectors.joining()); + String[] keyValues = strVal.split("\\s+"); + String value = keyValues[1]; + return convertStringToLong(value); + } catch (IOException e) { + return 0; + } + } + + private String getStringVal(String file) { + Path filePath = Paths.get(UNIFIED.getPath(), file); + try { + return Files.lines(filePath).collect(Collectors.joining()); + } catch (IOException e) { + return null; + } + } + + private void fail(String metric, long oldVal, long newVal) { + CgroupMetricsTester.fail(UnifiedController.NAME, metric, oldVal, newVal); + } + + private void fail(String metric, String oldVal, String newVal) { + CgroupMetricsTester.fail(UnifiedController.NAME, metric, oldVal, newVal); + } + + private void warn(String metric, long oldVal, long newVal) { + CgroupMetricsTester.warn(UnifiedController.NAME, metric, oldVal, newVal); + } + + private long getCpuShares(String file) { + long rawVal = getLongValueFromFile(file); + if (rawVal == 0 || rawVal == 100) { + return UNLIMITED; + } + int shares = (int)rawVal; + // 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; + } + + private long getCpuMaxValueFromFile(String file) { + return getCpuValueFromFile(file, 0 /* $MAX index */); + } + + private long getCpuPeriodValueFromFile(String file) { + return getCpuValueFromFile(file, 1 /* $PERIOD index */); + } + + private long getCpuValueFromFile(String file, int index) { + String maxPeriod = getStringVal(file); + if (maxPeriod == null) { + return UNLIMITED; + } + String[] tokens = maxPeriod.split("\\s+"); + String val = tokens[index]; + if (MAX.equals(val)) { + return UNLIMITED; + } + return convertStringToLong(val); + } + + private long convertStringToLong(String val) { + return CgroupMetricsTester.convertStringToLong(val, UNLIMITED); + } + + @Override + public void testMemorySubsystem() { + Metrics metrics = Metrics.systemMetrics(); + + // User Memory + long oldVal = metrics.getMemoryFailCount(); + long newVal = getLongValueEntryFromFile("memory.events", "max"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.events[max]", oldVal, newVal); + } + + oldVal = metrics.getMemoryLimit(); + newVal = getLongLimitValueFromFile("memory.max"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.max", oldVal, newVal); + } + + oldVal = metrics.getMemoryUsage(); + newVal = getLongValueFromFile("memory.current"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.current", oldVal, newVal); + } + + oldVal = metrics.getTcpMemoryUsage(); + newVal = getLongValueEntryFromFile("memory.stat", "sock"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.stat[sock]", oldVal, newVal); + } + + oldVal = metrics.getMemoryAndSwapLimit(); + newVal = getLongLimitValueFromFile("memory.swap.max"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.swap.max", oldVal, newVal); + } + + oldVal = metrics.getMemoryAndSwapUsage(); + newVal = getLongValueFromFile("memory.swap.current"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.swap.current", oldVal, newVal); + } + + oldVal = metrics.getMemorySoftLimit(); + newVal = getLongLimitValueFromFile("memory.high"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("memory.high", oldVal, newVal); + } + + } + + @Override + public void testCpuAccounting() { + Metrics metrics = Metrics.systemMetrics(); + long oldVal = metrics.getCpuUsage(); + long newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "usage_usec")); + + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + warn("cpu.stat[usage_usec]", oldVal, newVal); + } + + oldVal = metrics.getCpuUserUsage(); + newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "user_usec")); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + warn("cpu.stat[user_usec]", oldVal, newVal); + } + + oldVal = metrics.getCpuSystemUsage(); + newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "system_usec")); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + warn("cpu.stat[system_usec]", oldVal, newVal); + } + } + + @Override + public void testCpuSchedulingMetrics() { + Metrics metrics = Metrics.systemMetrics(); + long oldVal = metrics.getCpuPeriod(); + long newVal = getCpuPeriodValueFromFile("cpu.max"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("cpu.max[$PERIOD]", oldVal, newVal); + } + + oldVal = metrics.getCpuQuota(); + newVal = getCpuMaxValueFromFile("cpu.max"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("cpu.max[$MAX]", oldVal, newVal); + } + + oldVal = metrics.getCpuShares(); + newVal = getCpuShares("cpu.weight"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("cpu.weight", oldVal, newVal); + } + + oldVal = metrics.getCpuNumPeriods(); + newVal = getLongValueEntryFromFile("cpu.stat", "nr_periods"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("cpu.stat[nr_periods]", oldVal, newVal); + } + + oldVal = metrics.getCpuNumThrottled(); + newVal = getLongValueEntryFromFile("cpu.stat", "nr_throttled"); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("cpu.stat[nr_throttled]", oldVal, newVal); + } + + oldVal = metrics.getCpuThrottledTime(); + newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "throttled_usec")); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("cpu.stat[throttled_usec]", oldVal, newVal); + } + } + + @Override + public void testCpuSets() { + Metrics metrics = Metrics.systemMetrics(); + int[] cpus = mapNullToEmpty(metrics.getCpuSetCpus()); + Integer[] oldVal = Arrays.stream(cpus).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + + String cpusstr = getStringVal("cpuset.cpus"); + // Parse range string in the format 1,2-6,7 + Integer[] newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail("cpuset.cpus", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + + cpus = mapNullToEmpty(metrics.getEffectiveCpuSetCpus()); + oldVal = Arrays.stream(cpus).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + cpusstr = getStringVal("cpuset.cpus.effective"); + newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail("cpuset.cpus.effective", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + + cpus = mapNullToEmpty(metrics.getCpuSetMems()); + oldVal = Arrays.stream(cpus).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + cpusstr = getStringVal("cpuset.mems"); + newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail("cpuset.mems", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + + cpus = mapNullToEmpty(metrics.getEffectiveCpuSetMems()); + oldVal = Arrays.stream(cpus).boxed().toArray(Integer[]::new); + Arrays.sort(oldVal); + cpusstr = getStringVal("cpuset.mems.effective"); + newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); + Arrays.sort(newVal); + if (!Arrays.equals(oldVal, newVal)) { + fail("cpuset.mems.effective", Arrays.toString(oldVal), + Arrays.toString(newVal)); + } + } + + private int[] mapNullToEmpty(int[] cpus) { + if (cpus == null) { + // Not available. For sake of testing continue with an + // empty array. + cpus = new int[0]; + } + return cpus; + } + + @Override + public void testCpuConsumption() { + Metrics metrics = Metrics.systemMetrics(); + // make system call + long newSysVal = metrics.getCpuSystemUsage(); + long newUserVal = metrics.getCpuUserUsage(); + long newUsage = metrics.getCpuUsage(); + + // system/user CPU usage counters may be slowly increasing. + // allow for equal values for a pass + if (newSysVal < startSysVal) { + fail("getCpuSystemUsage", newSysVal, startSysVal); + } + + // system/user CPU usage counters may be slowly increasing. + // allow for equal values for a pass + if (newUserVal < startUserVal) { + fail("getCpuUserUsage", newUserVal, startUserVal); + } + + if (newUsage <= startUsage) { + fail("getCpuUsage", newUsage, startUsage); + } + } + + @Override + public void testMemoryUsage() { + Metrics metrics = Metrics.systemMetrics(); + long memoryUsage = metrics.getMemoryUsage(); + long newMemoryUsage = 0; + + // allocate memory in a loop and check more than once for new values + // otherwise we might occasionally see the effect of decreasing new memory + // values. For example because the system could free up memory + byte[][] bytes = new byte[32][]; + for (int i = 0; i < 32; i++) { + bytes[i] = new byte[8*1024*1024]; + newMemoryUsage = metrics.getMemoryUsage(); + if (newMemoryUsage > memoryUsage) { + break; + } + } + + if (newMemoryUsage < memoryUsage) { + fail("getMemoryUsage", memoryUsage, newMemoryUsage); + } + } + + @Override + public void testMisc() { + testIOStat(); + } + + private void testIOStat() { + Metrics metrics = Metrics.systemMetrics(); + long oldVal = metrics.getBlkIOServiceCount(); + long newVal = getIoStatAccumulate(new String[] { "rios", "wios" }); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("io.stat->rios/wios: ", oldVal, newVal); + } + + oldVal = metrics.getBlkIOServiced(); + newVal = getIoStatAccumulate(new String[] { "rbytes", "wbytes" }); + if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { + fail("io.stat->rbytes/wbytes: ", oldVal, newVal); + } + } + + private long getIoStatAccumulate(String[] matchNames) { + try { + return Files.lines(Paths.get(UNIFIED.getPath(), "io.stat")) + .map(line -> { + long accumulator = 0; + String[] tokens = line.split("\\s+"); + for (String t: tokens) { + String[] keyVal = t.split("="); + if (keyVal.length != 2) { + continue; + } + for (String match: matchNames) { + if (match.equals(keyVal[0])) { + accumulator += Long.parseLong(keyVal[1]); + } + } + } + return accumulator; + }).collect(Collectors.summingLong(e -> e)); + } catch (IOException e) { + return CgroupSubsystem.LONG_RETVAL_UNLIMITED; + } + } +}