Skip to content

Commit

Permalink
8336881: [Linux] Support for hierarchical limits for Metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
jerboaa committed Jul 22, 2024
1 parent fc17369 commit 179791a
Show file tree
Hide file tree
Showing 24 changed files with 1,359 additions and 251 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public int[] getEffectiveCpuSetMems() {
return subsystem.getEffectiveCpuSetMems();
}

@Override
public long getMemoryFailCount() {
return subsystem.getMemoryFailCount();
}
Expand Down Expand Up @@ -200,7 +201,8 @@ public static Metrics getInstance() {

private static native boolean isUseContainerSupport();
private static native boolean isContainerized0();
private static native long getTotalMemorySize0();
private static native long getTotalSwapSize0();
public static native long getTotalMemorySize0();
public static native long getTotalSwapSize0();
public static native int getTotalCpuCount0();

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,30 @@ public interface CgroupSubsystemController {

public static final String EMPTY_STR = "";

/**
* The controller path
*
* @return The path to the interface files for this controller.
*/
public String path();

public void setPath(String cgroupPath);

public String getCgroupPath();

/**
* Some systems don't have the interface files at the leaf of the controller
* path. That is, the hierarchy up to the root needs to get traversed looking
* for a lower limit at any of the paths. This is not needed for OCI containers
* which have the limits set at the leaf.
*
* @return {@code true} for any controller that needs adjusting, {@code false}
* otherwise.
*/
public default boolean needsAdjustment() {
return false;
}

/**
* getStringValue
*
Expand All @@ -59,7 +81,9 @@ public interface CgroupSubsystemController {
* an error occurs.
*/
public static String getStringValue(CgroupSubsystemController controller, String param) {
if (controller == null) return null;
if (controller == null) {
return null;
}

try {
return CgroupUtil.readStringValue(controller, param);
Expand Down Expand Up @@ -122,7 +146,9 @@ public static long getLongValue(CgroupSubsystemController controller,
Function<String, Long> conversion,
long defaultRetval) {
String strval = getStringValue(controller, param);
if (strval == null) return defaultRetval;
if (strval == null) {
return defaultRetval;
}
return conversion.apply(strval);
}

Expand All @@ -137,7 +163,9 @@ public static long getLongValue(CgroupSubsystemController controller,
public static double getDoubleValue(CgroupSubsystemController controller, String param, double defaultRetval) {
String strval = getStringValue(controller, param);

if (strval == null) return defaultRetval;
if (strval == null) {
return defaultRetval;
}

double retval = Double.parseDouble(strval);

Expand All @@ -159,7 +187,9 @@ public static double getDoubleValue(CgroupSubsystemController controller, String
* was found.
*/
public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname, long defaultRetval) {
if (controller == null) return defaultRetval;
if (controller == null) {
return defaultRetval;
}

try (Stream<String> lines = CgroupUtil.readFilePrivileged(Paths.get(controller.path(), param))) {

Expand Down Expand Up @@ -187,7 +217,9 @@ public static long getLongEntry(CgroupSubsystemController controller, String par
* was an empty string.
*/
public static int[] stringRangeToIntArray(String range) {
if (range == null || EMPTY_STR.equals(range)) return null;
if (range == null || EMPTY_STR.equals(range)) {
return null;
}

ArrayList<Integer> results = new ArrayList<>();
String strs[] = range.split(",");
Expand Down Expand Up @@ -235,7 +267,9 @@ public static int[] stringRangeToIntArray(String range) {
*/
public static long convertStringToLong(String strval, long overflowRetval, long defaultRetval) {
long retval = defaultRetval;
if (strval == null) return retval;
if (strval == null) {
return retval;
}

try {
retval = Long.parseLong(strval);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2024, Red Hat Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package jdk.internal.platform;

public interface CgroupSubsystemCpuController extends CgroupSubsystemController {

public long getCpuPeriod();

public long getCpuQuota();

public long getCpuShares();

public long getCpuNumPeriods();

public long getCpuNumThrottled();

public long getCpuThrottledTime();

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -111,9 +110,7 @@ public static CgroupMetrics create(Optional<CgroupTypeResult> optResult) {

Map<String, CgroupInfo> infos = result.getInfos();
if (result.isCgroupV2()) {
// For unified it doesn't matter which controller we pick.
CgroupInfo anyController = infos.values().iterator().next();
CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(Objects.requireNonNull(anyController));
CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(infos);
return new CgroupMetrics(subsystem);
} else {
CgroupV1Subsystem subsystem = CgroupV1Subsystem.getInstance(infos);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2024, Red Hat Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package jdk.internal.platform;

public interface CgroupSubsystemMemoryController extends CgroupSubsystemController {

public long getMemoryLimit(long physicalMemory);

public long getMemoryUsage();

public long getTcpMemoryUsage();

public long getMemoryAndSwapLimit(long hostMemory, long hostSwap);

public long getMemoryAndSwapUsage();

public long getMemorySoftLimit(long hostMemory);

public long getMemoryFailCount();

}
73 changes: 70 additions & 3 deletions src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,15 @@ public static Stream<String> readFilePrivileged(Path path) throws IOException {

static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IOException {
Throwable x = pae.getCause();
if (x instanceof IOException)
if (x instanceof IOException) {
throw (IOException) x;
if (x instanceof RuntimeException)
}
if (x instanceof RuntimeException) {
throw (RuntimeException) x;
if (x instanceof Error)
}
if (x instanceof Error) {
throw (Error) x;
}
}

static String readStringValue(CgroupSubsystemController controller, String param) throws IOException {
Expand Down Expand Up @@ -89,4 +92,68 @@ public static List<String> readAllLinesPrivileged(Path path) throws IOException
throw e.getCause();
}
}

/**
* Calculate the processor count based on the host CPUs and set cpu quota.
*
* @param cpu The cpu controller to read the quota values from.
* @param hostCpus The physical host CPUs
* @return The minimum of host CPUs and the configured cpu quota, never
* negative.
*/
static int processorCount(CgroupSubsystemCpuController cpu, int hostCpus) {
int limit = hostCpus;
long quota = cpu.getCpuQuota();
long period = cpu.getCpuPeriod();
int quotaCount = 0;

if (quota > CgroupSubsystem.LONG_RETVAL_UNLIMITED && period > 0) {
quotaCount = (int) Math.ceilDiv(quota, period);
}
if (quotaCount != 0) {
limit = quotaCount;
}
return Math.min(hostCpus, limit);
}

public static void adjustController(CgroupSubsystemCpuController cpu) {
if (!cpu.needsAdjustment()) {
return;
}
String origCgroupPath = cpu.getCgroupPath();
Path workingPath = Path.of(origCgroupPath);
boolean adjustmentDone = false;
int hostCpus = CgroupMetrics.getTotalCpuCount0();

int limit = CgroupUtil.processorCount(cpu, hostCpus);
while (limit == hostCpus && ((workingPath = workingPath.getParent()) != null)) {
cpu.setPath(workingPath.toString()); // adjust path
limit = CgroupUtil.processorCount(cpu, hostCpus);
adjustmentDone = true;
}
if (adjustmentDone && limit == hostCpus) {
// No lower limit found adjust to original path
cpu.setPath(origCgroupPath);
}
}

public static void adjustController(CgroupSubsystemMemoryController memory) {
if (!memory.needsAdjustment()) {
return;
}
long physicalMemory = CgroupMetrics.getTotalMemorySize0();
String origCgroupPath = memory.getCgroupPath();
Path workingPath = Path.of(origCgroupPath);
boolean adjustmentDone = false;
long limit = memory.getMemoryLimit(physicalMemory);
while (limit < 0 && ((workingPath = workingPath.getParent()) != null)) {
memory.setPath(workingPath.toString()); // adjust path
limit = memory.getMemoryLimit(physicalMemory);
adjustmentDone = true;
}
if (adjustmentDone && limit < 0) {
// No lower limit found adjust to original path
memory.setPath(origCgroupPath);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024, Red Hat Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package jdk.internal.platform.cgroupv1;

import jdk.internal.platform.CgroupSubsystem;
import jdk.internal.platform.CgroupSubsystemCpuController;

public class CgroupV1CpuSubSystemController extends CgroupV1SubsystemController
implements CgroupSubsystemCpuController {

public CgroupV1CpuSubSystemController(String root, String mountPoint) {
super(root, mountPoint);
}

@Override
public long getCpuPeriod() {
return CgroupV1Subsystem.getLongValue(this, "cpu.cfs_period_us");
}

@Override
public long getCpuQuota() {
return CgroupV1Subsystem.getLongValue(this, "cpu.cfs_quota_us");
}

@Override
public long getCpuShares() {
long retval = CgroupV1Subsystem.getLongValue(this, "cpu.shares");
if (retval == 0 || retval == 1024) {
return CgroupSubsystem.LONG_RETVAL_UNLIMITED;
} else {
return retval;
}
}

@Override
public long getCpuNumPeriods() {
return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "nr_periods");
}

@Override
public long getCpuNumThrottled() {
return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "nr_throttled");
}

@Override
public long getCpuThrottledTime() {
return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "throttled_time");
}

@Override
public boolean needsAdjustment() {
// Container frameworks have them equal; we skip adjustment for them
return !getRoot().equals(getCgroupPath());
}

}
Loading

0 comments on commit 179791a

Please sign in to comment.