Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include "cgroupV2Subsystem_linux.hpp"
#include "cgroupUtil_linux.hpp"

#include <math.h>

// Constructor
CgroupV2Controller::CgroupV2Controller(char* mount_path,
char *cgroup_path,
Expand Down Expand Up @@ -60,22 +62,39 @@ int CgroupV2CpuController::cpu_shares() {
log_debug(os, container)("CPU Shares is: %d", -1);
return -1;
}
// cg v2 values must be in range [1-10000]
assert(shares_int >= 1 && shares_int <= 10000, "invariant");

// 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
// https://github.com/containers/crun/blob/1.24/crun.1.md#cpu-controller
//
// Use the inverse of (x == OCI value, y == cgroupsv2 value):
// ((262142 * y - 1)/9999) + 2 = x
// y = 10^(log2(x)^2/612 + 125/612 * log2(x) - 7.0/34.0)
//
// By re-arranging it to the standard quadratic form:
// log2(x)^2 + 125 * log2(x) - (126 + 612 * log_10(y)) = 0
//
// Therefore, log2(x) = (-125 + sqrt( 125^2 - 4 * (-(126 + 612 * log_10(y)))))/2
//
// As a result we have the inverse (we can discount substraction of the
// square root value since those values result in very small numbers and the
// cpu shares values - OCI - are in range [2,262144]):
//
// x = 2^((-125 + sqrt(16129 + 2448* log10(y)))/2)
//
int x = 262142 * shares_int - 1;
double frac = x/9999.0;
x = ((int)frac) + 2;
double log_multiplicand = log10(shares_int);
double discriminant = 16129 + 2448 * log_multiplicand;
double square_root = sqrt(discriminant);
double exponent = (-125 + square_root)/2;
double scaled_val = pow(2, exponent);
int x = (int) scaled_val;
log_trace(os, container)("Scaled CPU shares value is: %d", x);
// Since the scaled value is not precise, return the closest
// multiple of PER_CPU_SHARES for a more conservative mapping
if ( x <= PER_CPU_SHARES ) {
// will always map to 1 CPU
// Don't do the multiples of PER_CPU_SHARES mapping since we
// have a value <= PER_CPU_SHARES
log_debug(os, container)("CPU Shares is: %d", x);
return x;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,39 @@ private long getFromCpuMax(int tokenIdx) {
@Override
public long getCpuShares() {
long sharesRaw = getLongVal("cpu.weight");
if (sharesRaw == 100 || sharesRaw <= 0) {
// cg v2 value must be in range [1,10000]
if (sharesRaw == 100 || sharesRaw <= 0 || sharesRaw > 10000) {
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
// https://github.com/containers/crun/blob/1.24/crun.1.md#cpu-controller
//
// Use the inverse of (x == OCI value, y == cgroupsv2 value):
// ((262142 * y - 1)/9999) + 2 = x
// y = 10^(log2(x)^2/612 + 125/612 * log2(x) - 7.0/34.0)
//
int x = 262142 * shares - 1;
double frac = x/9999.0;
x = ((int)frac) + 2;
// By re-arranging it to the standard quadratic form:
// log2(x)^2 + 125 * log2(x) - (126 + 612 * log_10(y)) = 0
//
// Therefore, log2(x) = (-125 + sqrt( 125^2 - 4 * (-(126 + 612 * log_10(y)))))/2
//
// As a result we have the inverse (we can discount substraction of the
// square root value since those values result in very small numbers and the
// cpu shares values - OCI - are in range [2-262144])
//
// x = 2^((-125 + sqrt(16129 + 2448* log10(y)))/2)
//
double logMultiplicand = Math.log10(shares);
double discriminant = 16129 + 2448 * logMultiplicand;
double squareRoot = Math.sqrt(discriminant);
double exponent = (-125 + squareRoot)/2;
double scaledValue = Math.pow(2, exponent);

int x = (int)scaledValue;
if ( x <= PER_CPU_SHARES ) {
return PER_CPU_SHARES; // mimic cgroups v1
// Return the back-mapped value.
return x;
}
int f = x/PER_CPU_SHARES;
int lower_multiple = f * PER_CPU_SHARES;
Expand Down
52 changes: 51 additions & 1 deletion test/hotspot/jtreg/containers/docker/TestMisc.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,24 @@
* @requires !vm.asan
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.base/jdk.internal.platform
* java.management
* jdk.jartool/sun.tools.jar
* @build CheckContainerized jdk.test.whitebox.WhiteBox PrintContainerInfo
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
* @run driver TestMisc
*/
import jdk.internal.platform.Metrics;
import jdk.test.lib.containers.docker.Common;
import jdk.test.lib.containers.docker.DockerTestUtils;
import jdk.test.lib.containers.docker.DockerRunOptions;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jtreg.SkippedException;


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

public static void main(String[] args) throws Exception {
Expand All @@ -58,6 +62,7 @@ public static void main(String[] args) throws Exception {
testIsContainerized();
testPrintContainerInfo();
testPrintContainerInfoActiveProcessorCount();
testPrintContainerInfoCPUShares();
} finally {
DockerTestUtils.removeDockerImage(imageName);
}
Expand Down Expand Up @@ -94,8 +99,53 @@ private static void testPrintContainerInfo() throws Exception {
checkContainerInfo(Common.run(opts));
}

// Test the mapping function on cgroups v2. Should also pass on cgroups v1 as it's
// a direct mapping there.
private static void testPrintContainerInfoCPUShares() throws Exception {
// Test won't work on cgv1 rootless podman since resource limits don't
// work there.
if ("cgroupv1".equals(metrics.getProvider()) &&
DockerTestUtils.isPodman() &&
DockerTestUtils.isRootless()) {
throw new SkippedException("Resource limits required for testPrintContainerInfoCPUShares(). " +
"This is cgv1 with podman in rootless mode. Test skipped.");
}
// Anything less than 1024 should return the back-mapped cpu-shares value without
// rounding to next multiple of 1024 (on cg v2). Only ensure that we get
// 'cpu_shares: <back-mapped-value>' over 'cpu_shares: no shares'.
printContainerInfo(512, 1024, false);
// Don't use 1024 exactly so as to avoid mapping to the unlimited/uset case.
// Use a value > 100 post-mapping so as to hit the non-default branch: 1052 => 103
printContainerInfo(1052, 1024, true);
// need at least 2 CPU cores for this test to work
if (Runtime.getRuntime().availableProcessors() >= 2) {
printContainerInfo(2048, 2048, true);
}
}

private static void printContainerInfo(int cpuShares, int expected, boolean numberMatch) throws Exception {
Common.logNewTestCase("Test print_container_info() - cpu shares - given: " + cpuShares + ", expected: " + expected);

DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo");
Common.addWhiteBoxOpts(opts);
opts.addDockerOpts("--cpu-shares", Integer.valueOf(cpuShares).toString());

OutputAnalyzer out = Common.run(opts);
String str = out.getOutput();
boolean isCgroupV2 = str.contains("cgroupv2");
// cg v1 maps cpu shares values verbatim. Only cg v2 uses the
// mapping function.
if (numberMatch) {
int valueExpected = isCgroupV2 ? expected : cpuShares;
out.shouldContain("cpu_shares: " + valueExpected);
} else {
// must not print "no shares"
out.shouldNotContain("cpu_shares: no shares");
}
}

private static void testPrintContainerInfoActiveProcessorCount() throws Exception {
Common.logNewTestCase("Test print_container_info()");
Common.logNewTestCase("Test print_container_info() - ActiveProcessorCount");

DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo").addJavaOpts("-XX:ActiveProcessorCount=2");
Common.addWhiteBoxOpts(opts);
Expand Down
10 changes: 7 additions & 3 deletions test/jdk/jdk/internal/platform/docker/MetricsCpuTester.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,13 @@ 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;
// Don't assert for shares values less than 1024 as we don't
// have a 1-to-1 mapping from the cgroup v2 value to the OCI
// value.
System.out.println("Debug: cgv2 - Got CPU shares of: " +
metrics.getCpuShares() + " - Skipping assert.");
System.out.println("TEST PASSED!!!");
return;
}
long newShares = metrics.getCpuShares();
if (newShares != shares) {
Expand Down