Skip to content

Commit f0c775b

Browse files
committed
8336881: [Linux] Support for hierarchical limits for Metrics
1 parent af7dfa2 commit f0c775b

24 files changed

+1359
-251
lines changed

src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public int[] getEffectiveCpuSetMems() {
120120
return subsystem.getEffectiveCpuSetMems();
121121
}
122122

123+
@Override
123124
public long getMemoryFailCount() {
124125
return subsystem.getMemoryFailCount();
125126
}
@@ -200,7 +201,8 @@ public static Metrics getInstance() {
200201

201202
private static native boolean isUseContainerSupport();
202203
private static native boolean isContainerized0();
203-
private static native long getTotalMemorySize0();
204-
private static native long getTotalSwapSize0();
204+
public static native long getTotalMemorySize0();
205+
public static native long getTotalSwapSize0();
206+
public static native int getTotalCpuCount0();
205207

206208
}

src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,30 @@ public interface CgroupSubsystemController {
4444

4545
public static final String EMPTY_STR = "";
4646

47+
/**
48+
* The controller path
49+
*
50+
* @return The path to the interface files for this controller.
51+
*/
4752
public String path();
4853

54+
public void setPath(String cgroupPath);
55+
56+
public String getCgroupPath();
57+
58+
/**
59+
* Some systems don't have the interface files at the leaf of the controller
60+
* path. That is, the hierarchy up to the root needs to get traversed looking
61+
* for a lower limit at any of the paths. This is not needed for OCI containers
62+
* which have the limits set at the leaf.
63+
*
64+
* @return {@code true} for any controller that needs adjusting, {@code false}
65+
* otherwise.
66+
*/
67+
public default boolean needsAdjustment() {
68+
return false;
69+
}
70+
4971
/**
5072
* getStringValue
5173
*
@@ -59,7 +81,9 @@ public interface CgroupSubsystemController {
5981
* an error occurs.
6082
*/
6183
public static String getStringValue(CgroupSubsystemController controller, String param) {
62-
if (controller == null) return null;
84+
if (controller == null) {
85+
return null;
86+
}
6387

6488
try {
6589
return CgroupUtil.readStringValue(controller, param);
@@ -122,7 +146,9 @@ public static long getLongValue(CgroupSubsystemController controller,
122146
Function<String, Long> conversion,
123147
long defaultRetval) {
124148
String strval = getStringValue(controller, param);
125-
if (strval == null) return defaultRetval;
149+
if (strval == null) {
150+
return defaultRetval;
151+
}
126152
return conversion.apply(strval);
127153
}
128154

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

140-
if (strval == null) return defaultRetval;
166+
if (strval == null) {
167+
return defaultRetval;
168+
}
141169

142170
double retval = Double.parseDouble(strval);
143171

@@ -159,7 +187,9 @@ public static double getDoubleValue(CgroupSubsystemController controller, String
159187
* was found.
160188
*/
161189
public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname, long defaultRetval) {
162-
if (controller == null) return defaultRetval;
190+
if (controller == null) {
191+
return defaultRetval;
192+
}
163193

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

@@ -187,7 +217,9 @@ public static long getLongEntry(CgroupSubsystemController controller, String par
187217
* was an empty string.
188218
*/
189219
public static int[] stringRangeToIntArray(String range) {
190-
if (range == null || EMPTY_STR.equals(range)) return null;
220+
if (range == null || EMPTY_STR.equals(range)) {
221+
return null;
222+
}
191223

192224
ArrayList<Integer> results = new ArrayList<>();
193225
String strs[] = range.split(",");
@@ -235,7 +267,9 @@ public static int[] stringRangeToIntArray(String range) {
235267
*/
236268
public static long convertStringToLong(String strval, long overflowRetval, long defaultRetval) {
237269
long retval = defaultRetval;
238-
if (strval == null) return retval;
270+
if (strval == null) {
271+
return retval;
272+
}
239273

240274
try {
241275
retval = Long.parseLong(strval);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2024, Red Hat Inc.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package jdk.internal.platform;
27+
28+
public interface CgroupSubsystemCpuController extends CgroupSubsystemController {
29+
30+
public long getCpuPeriod();
31+
32+
public long getCpuQuota();
33+
34+
public long getCpuShares();
35+
36+
public long getCpuNumPeriods();
37+
38+
public long getCpuNumThrottled();
39+
40+
public long getCpuThrottledTime();
41+
42+
}

src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import java.util.List;
3737
import java.util.Map;
3838
import java.util.Optional;
39-
import java.util.Objects;
4039
import java.util.function.Consumer;
4140
import java.util.regex.Matcher;
4241
import java.util.regex.Pattern;
@@ -111,9 +110,7 @@ public static CgroupMetrics create(Optional<CgroupTypeResult> optResult) {
111110

112111
Map<String, CgroupInfo> infos = result.getInfos();
113112
if (result.isCgroupV2()) {
114-
// For unified it doesn't matter which controller we pick.
115-
CgroupInfo anyController = infos.values().iterator().next();
116-
CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(Objects.requireNonNull(anyController));
113+
CgroupSubsystem subsystem = CgroupV2Subsystem.getInstance(infos);
117114
return new CgroupMetrics(subsystem);
118115
} else {
119116
CgroupV1Subsystem subsystem = CgroupV1Subsystem.getInstance(infos);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2024, Red Hat Inc.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package jdk.internal.platform;
27+
28+
public interface CgroupSubsystemMemoryController extends CgroupSubsystemController {
29+
30+
public long getMemoryLimit(long physicalMemory);
31+
32+
public long getMemoryUsage();
33+
34+
public long getTcpMemoryUsage();
35+
36+
public long getMemoryAndSwapLimit(long hostMemory, long hostSwap);
37+
38+
public long getMemoryAndSwapUsage();
39+
40+
public long getMemorySoftLimit(long hostMemory);
41+
42+
public long getMemoryFailCount();
43+
44+
}

src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,15 @@ public static Stream<String> readFilePrivileged(Path path) throws IOException {
5454

5555
static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IOException {
5656
Throwable x = pae.getCause();
57-
if (x instanceof IOException)
57+
if (x instanceof IOException) {
5858
throw (IOException) x;
59-
if (x instanceof RuntimeException)
59+
}
60+
if (x instanceof RuntimeException) {
6061
throw (RuntimeException) x;
61-
if (x instanceof Error)
62+
}
63+
if (x instanceof Error) {
6264
throw (Error) x;
65+
}
6366
}
6467

6568
static String readStringValue(CgroupSubsystemController controller, String param) throws IOException {
@@ -89,4 +92,68 @@ public static List<String> readAllLinesPrivileged(Path path) throws IOException
8992
throw e.getCause();
9093
}
9194
}
95+
96+
/**
97+
* Calculate the processor count based on the host CPUs and set cpu quota.
98+
*
99+
* @param cpu The cpu controller to read the quota values from.
100+
* @param hostCpus The physical host CPUs
101+
* @return The minimum of host CPUs and the configured cpu quota, never
102+
* negative.
103+
*/
104+
static int processorCount(CgroupSubsystemCpuController cpu, int hostCpus) {
105+
int limit = hostCpus;
106+
long quota = cpu.getCpuQuota();
107+
long period = cpu.getCpuPeriod();
108+
int quotaCount = 0;
109+
110+
if (quota > CgroupSubsystem.LONG_RETVAL_UNLIMITED && period > 0) {
111+
quotaCount = (int) Math.ceilDiv(quota, period);
112+
}
113+
if (quotaCount != 0) {
114+
limit = quotaCount;
115+
}
116+
return Math.min(hostCpus, limit);
117+
}
118+
119+
public static void adjustController(CgroupSubsystemCpuController cpu) {
120+
if (!cpu.needsAdjustment()) {
121+
return;
122+
}
123+
String origCgroupPath = cpu.getCgroupPath();
124+
Path workingPath = Path.of(origCgroupPath);
125+
boolean adjustmentDone = false;
126+
int hostCpus = CgroupMetrics.getTotalCpuCount0();
127+
128+
int limit = CgroupUtil.processorCount(cpu, hostCpus);
129+
while (limit == hostCpus && ((workingPath = workingPath.getParent()) != null)) {
130+
cpu.setPath(workingPath.toString()); // adjust path
131+
limit = CgroupUtil.processorCount(cpu, hostCpus);
132+
adjustmentDone = true;
133+
}
134+
if (adjustmentDone && limit == hostCpus) {
135+
// No lower limit found adjust to original path
136+
cpu.setPath(origCgroupPath);
137+
}
138+
}
139+
140+
public static void adjustController(CgroupSubsystemMemoryController memory) {
141+
if (!memory.needsAdjustment()) {
142+
return;
143+
}
144+
long physicalMemory = CgroupMetrics.getTotalMemorySize0();
145+
String origCgroupPath = memory.getCgroupPath();
146+
Path workingPath = Path.of(origCgroupPath);
147+
boolean adjustmentDone = false;
148+
long limit = memory.getMemoryLimit(physicalMemory);
149+
while (limit < 0 && ((workingPath = workingPath.getParent()) != null)) {
150+
memory.setPath(workingPath.toString()); // adjust path
151+
limit = memory.getMemoryLimit(physicalMemory);
152+
adjustmentDone = true;
153+
}
154+
if (adjustmentDone && limit < 0) {
155+
// No lower limit found adjust to original path
156+
memory.setPath(origCgroupPath);
157+
}
158+
}
92159
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2024, Red Hat Inc.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package jdk.internal.platform.cgroupv1;
27+
28+
import jdk.internal.platform.CgroupSubsystem;
29+
import jdk.internal.platform.CgroupSubsystemCpuController;
30+
31+
public class CgroupV1CpuSubSystemController extends CgroupV1SubsystemController
32+
implements CgroupSubsystemCpuController {
33+
34+
public CgroupV1CpuSubSystemController(String root, String mountPoint) {
35+
super(root, mountPoint);
36+
}
37+
38+
@Override
39+
public long getCpuPeriod() {
40+
return CgroupV1Subsystem.getLongValue(this, "cpu.cfs_period_us");
41+
}
42+
43+
@Override
44+
public long getCpuQuota() {
45+
return CgroupV1Subsystem.getLongValue(this, "cpu.cfs_quota_us");
46+
}
47+
48+
@Override
49+
public long getCpuShares() {
50+
long retval = CgroupV1Subsystem.getLongValue(this, "cpu.shares");
51+
if (retval == 0 || retval == 1024) {
52+
return CgroupSubsystem.LONG_RETVAL_UNLIMITED;
53+
} else {
54+
return retval;
55+
}
56+
}
57+
58+
@Override
59+
public long getCpuNumPeriods() {
60+
return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "nr_periods");
61+
}
62+
63+
@Override
64+
public long getCpuNumThrottled() {
65+
return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "nr_throttled");
66+
}
67+
68+
@Override
69+
public long getCpuThrottledTime() {
70+
return CgroupV1SubsystemController.getLongEntry(this, "cpu.stat", "throttled_time");
71+
}
72+
73+
@Override
74+
public boolean needsAdjustment() {
75+
// Container frameworks have them equal; we skip adjustment for them
76+
return !getRoot().equals(getCgroupPath());
77+
}
78+
79+
}

0 commit comments

Comments
 (0)