Skip to content

Commit 5d65c23

Browse files
committed
8370492: [Linux] Update cpu shares to cpu.weight mapping function
Reviewed-by: cnorrbin, ayang, syan
1 parent 4cc655a commit 5d65c23

File tree

4 files changed

+107
-17
lines changed

4 files changed

+107
-17
lines changed

src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include "cgroupUtil_linux.hpp"
2727
#include "cgroupV2Subsystem_linux.hpp"
2828

29+
#include <math.h>
30+
2931
// Constructor
3032
CgroupV2Controller::CgroupV2Controller(char* mount_path,
3133
char *cgroup_path,
@@ -61,22 +63,39 @@ int CgroupV2CpuController::cpu_shares() {
6163
log_debug(os, container)("CPU Shares is: %d", -1);
6264
return -1;
6365
}
66+
// cg v2 values must be in range [1-10000]
67+
assert(shares_int >= 1 && shares_int <= 10000, "invariant");
6468

6569
// CPU shares (OCI) value needs to get translated into
6670
// a proper Cgroups v2 value. See:
67-
// https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller
71+
// https://github.com/containers/crun/blob/1.24/crun.1.md#cpu-controller
6872
//
6973
// Use the inverse of (x == OCI value, y == cgroupsv2 value):
70-
// ((262142 * y - 1)/9999) + 2 = x
74+
// y = 10^(log2(x)^2/612 + 125/612 * log2(x) - 7.0/34.0)
75+
//
76+
// By re-arranging it to the standard quadratic form:
77+
// log2(x)^2 + 125 * log2(x) - (126 + 612 * log_10(y)) = 0
78+
//
79+
// Therefore, log2(x) = (-125 + sqrt( 125^2 - 4 * (-(126 + 612 * log_10(y)))))/2
80+
//
81+
// As a result we have the inverse (we can discount substraction of the
82+
// square root value since those values result in very small numbers and the
83+
// cpu shares values - OCI - are in range [2,262144]):
84+
//
85+
// x = 2^((-125 + sqrt(16129 + 2448* log10(y)))/2)
7186
//
72-
int x = 262142 * shares_int - 1;
73-
double frac = x/9999.0;
74-
x = ((int)frac) + 2;
87+
double log_multiplicand = log10(shares_int);
88+
double discriminant = 16129 + 2448 * log_multiplicand;
89+
double square_root = sqrt(discriminant);
90+
double exponent = (-125 + square_root)/2;
91+
double scaled_val = pow(2, exponent);
92+
int x = (int) scaled_val;
7593
log_trace(os, container)("Scaled CPU shares value is: %d", x);
7694
// Since the scaled value is not precise, return the closest
7795
// multiple of PER_CPU_SHARES for a more conservative mapping
7896
if ( x <= PER_CPU_SHARES ) {
79-
// will always map to 1 CPU
97+
// Don't do the multiples of PER_CPU_SHARES mapping since we
98+
// have a value <= PER_CPU_SHARES
8099
log_debug(os, container)("CPU Shares is: %d", x);
81100
return x;
82101
}

src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,22 +156,39 @@ private long getFromCpuMax(int tokenIdx) {
156156
@Override
157157
public long getCpuShares() {
158158
long sharesRaw = getLongVal("cpu.weight");
159-
if (sharesRaw == 100 || sharesRaw <= 0) {
159+
// cg v2 value must be in range [1,10000]
160+
if (sharesRaw == 100 || sharesRaw <= 0 || sharesRaw > 10000) {
160161
return CgroupSubsystem.LONG_RETVAL_UNLIMITED;
161162
}
162163
int shares = (int)sharesRaw;
163164
// CPU shares (OCI) value needs to get translated into
164165
// a proper Cgroups v2 value. See:
165-
// https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller
166+
// https://github.com/containers/crun/blob/1.24/crun.1.md#cpu-controller
166167
//
167168
// Use the inverse of (x == OCI value, y == cgroupsv2 value):
168-
// ((262142 * y - 1)/9999) + 2 = x
169+
// y = 10^(log2(x)^2/612 + 125/612 * log2(x) - 7.0/34.0)
169170
//
170-
int x = 262142 * shares - 1;
171-
double frac = x/9999.0;
172-
x = ((int)frac) + 2;
171+
// By re-arranging it to the standard quadratic form:
172+
// log2(x)^2 + 125 * log2(x) - (126 + 612 * log_10(y)) = 0
173+
//
174+
// Therefore, log2(x) = (-125 + sqrt( 125^2 - 4 * (-(126 + 612 * log_10(y)))))/2
175+
//
176+
// As a result we have the inverse (we can discount substraction of the
177+
// square root value since those values result in very small numbers and the
178+
// cpu shares values - OCI - are in range [2-262144])
179+
//
180+
// x = 2^((-125 + sqrt(16129 + 2448* log10(y)))/2)
181+
//
182+
double logMultiplicand = Math.log10(shares);
183+
double discriminant = 16129 + 2448 * logMultiplicand;
184+
double squareRoot = Math.sqrt(discriminant);
185+
double exponent = (-125 + squareRoot)/2;
186+
double scaledValue = Math.pow(2, exponent);
187+
188+
int x = (int)scaledValue;
173189
if ( x <= PER_CPU_SHARES ) {
174-
return PER_CPU_SHARES; // mimic cgroups v1
190+
// Return the back-mapped value.
191+
return x;
175192
}
176193
int f = x/PER_CPU_SHARES;
177194
int lower_multiple = f * PER_CPU_SHARES;

test/hotspot/jtreg/containers/docker/TestMisc.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,24 @@
2929
* @requires !vm.asan
3030
* @library /test/lib
3131
* @modules java.base/jdk.internal.misc
32+
* java.base/jdk.internal.platform
3233
* java.management
3334
* jdk.jartool/sun.tools.jar
3435
* @build CheckContainerized jdk.test.whitebox.WhiteBox PrintContainerInfo
3536
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
3637
* @run driver TestMisc
3738
*/
39+
import jdk.internal.platform.Metrics;
3840
import jdk.test.lib.containers.docker.Common;
3941
import jdk.test.lib.containers.docker.DockerTestUtils;
4042
import jdk.test.lib.containers.docker.DockerRunOptions;
4143
import jdk.test.lib.process.OutputAnalyzer;
4244
import jdk.test.lib.process.ProcessTools;
45+
import jtreg.SkippedException;
4346

4447

4548
public class TestMisc {
49+
private static final Metrics metrics = Metrics.systemMetrics();
4650
private static final String imageName = Common.imageName("misc");
4751

4852
public static void main(String[] args) throws Exception {
@@ -58,6 +62,7 @@ public static void main(String[] args) throws Exception {
5862
testIsContainerized();
5963
testPrintContainerInfo();
6064
testPrintContainerInfoActiveProcessorCount();
65+
testPrintContainerInfoCPUShares();
6166
} finally {
6267
DockerTestUtils.removeDockerImage(imageName);
6368
}
@@ -94,8 +99,53 @@ private static void testPrintContainerInfo() throws Exception {
9499
checkContainerInfo(Common.run(opts));
95100
}
96101

102+
// Test the mapping function on cgroups v2. Should also pass on cgroups v1 as it's
103+
// a direct mapping there.
104+
private static void testPrintContainerInfoCPUShares() throws Exception {
105+
// Test won't work on cgv1 rootless podman since resource limits don't
106+
// work there.
107+
if ("cgroupv1".equals(metrics.getProvider()) &&
108+
DockerTestUtils.isPodman() &&
109+
DockerTestUtils.isRootless()) {
110+
throw new SkippedException("Resource limits required for testPrintContainerInfoCPUShares(). " +
111+
"This is cgv1 with podman in rootless mode. Test skipped.");
112+
}
113+
// Anything less than 1024 should return the back-mapped cpu-shares value without
114+
// rounding to next multiple of 1024 (on cg v2). Only ensure that we get
115+
// 'cpu_shares: <back-mapped-value>' over 'cpu_shares: no shares'.
116+
printContainerInfo(512, 1024, false);
117+
// Don't use 1024 exactly so as to avoid mapping to the unlimited/uset case.
118+
// Use a value > 100 post-mapping so as to hit the non-default branch: 1052 => 103
119+
printContainerInfo(1052, 1024, true);
120+
// need at least 2 CPU cores for this test to work
121+
if (Runtime.getRuntime().availableProcessors() >= 2) {
122+
printContainerInfo(2048, 2048, true);
123+
}
124+
}
125+
126+
private static void printContainerInfo(int cpuShares, int expected, boolean numberMatch) throws Exception {
127+
Common.logNewTestCase("Test print_container_info() - cpu shares - given: " + cpuShares + ", expected: " + expected);
128+
129+
DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo");
130+
Common.addWhiteBoxOpts(opts);
131+
opts.addDockerOpts("--cpu-shares", Integer.valueOf(cpuShares).toString());
132+
133+
OutputAnalyzer out = Common.run(opts);
134+
String str = out.getOutput();
135+
boolean isCgroupV2 = str.contains("cgroupv2");
136+
// cg v1 maps cpu shares values verbatim. Only cg v2 uses the
137+
// mapping function.
138+
if (numberMatch) {
139+
int valueExpected = isCgroupV2 ? expected : cpuShares;
140+
out.shouldContain("cpu_shares: " + valueExpected);
141+
} else {
142+
// must not print "no shares"
143+
out.shouldNotContain("cpu_shares: no shares");
144+
}
145+
}
146+
97147
private static void testPrintContainerInfoActiveProcessorCount() throws Exception {
98-
Common.logNewTestCase("Test print_container_info()");
148+
Common.logNewTestCase("Test print_container_info() - ActiveProcessorCount");
99149

100150
DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo").addJavaOpts("-XX:ActiveProcessorCount=2");
101151
Common.addWhiteBoxOpts(opts);

test/jdk/jdk/internal/platform/docker/MetricsCpuTester.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,13 @@ private static void testCpuSetMemNodes(String cpusetMems) {
145145
private static void testCpuShares(long shares) {
146146
Metrics metrics = Metrics.systemMetrics();
147147
if ("cgroupv2".equals(metrics.getProvider()) && shares < 1024) {
148-
// Adjust input shares for < 1024 cpu shares as the
149-
// impl. rounds up to the next multiple of 1024
150-
shares = 1024;
148+
// Don't assert for shares values less than 1024 as we don't
149+
// have a 1-to-1 mapping from the cgroup v2 value to the OCI
150+
// value.
151+
System.out.println("Debug: cgv2 - Got CPU shares of: " +
152+
metrics.getCpuShares() + " - Skipping assert.");
153+
System.out.println("TEST PASSED!!!");
154+
return;
151155
}
152156
long newShares = metrics.getCpuShares();
153157
if (newShares != shares) {

0 commit comments

Comments
 (0)