8333446: Add tests for hierarchical container support

Reviewed-by: mbaesken, zzambers
This commit is contained in:
Severin Gehwolf 2024-09-11 16:57:13 +00:00
parent bfe7f9205b
commit d9fdf69c34
9 changed files with 655 additions and 7 deletions

View File

@ -2497,6 +2497,13 @@ WB_ENTRY(jint, WB_ValidateCgroup(JNIEnv* env,
return ret; return ret;
WB_END WB_END
// Available cpus of the host machine, Linux only.
// Used in container testing.
WB_ENTRY(jint, WB_HostCPUs(JNIEnv* env, jobject o))
LINUX_ONLY(return os::Linux::active_processor_count();)
return -1; // Not used/implemented on other platforms
WB_END
WB_ENTRY(void, WB_PrintOsInfo(JNIEnv* env, jobject o)) WB_ENTRY(void, WB_PrintOsInfo(JNIEnv* env, jobject o))
os::print_os_info(tty); os::print_os_info(tty);
WB_END WB_END
@ -2938,6 +2945,7 @@ static JNINativeMethod methods[] = {
(void*)&WB_ValidateCgroup }, (void*)&WB_ValidateCgroup },
{CC"hostPhysicalMemory", CC"()J", (void*)&WB_HostPhysicalMemory }, {CC"hostPhysicalMemory", CC"()J", (void*)&WB_HostPhysicalMemory },
{CC"hostPhysicalSwap", CC"()J", (void*)&WB_HostPhysicalSwap }, {CC"hostPhysicalSwap", CC"()J", (void*)&WB_HostPhysicalSwap },
{CC"hostCPUs", CC"()I", (void*)&WB_HostCPUs },
{CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo }, {CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo },
{CC"disableElfSectionCache", CC"()V", (void*)&WB_DisableElfSectionCache }, {CC"disableElfSectionCache", CC"()V", (void*)&WB_DisableElfSectionCache },
{CC"resolvedMethodItemsCount", CC"()J", (void*)&WB_ResolvedMethodItemsCount }, {CC"resolvedMethodItemsCount", CC"()J", (void*)&WB_ResolvedMethodItemsCount },

View File

@ -87,6 +87,7 @@ requires.properties= \
vm.musl \ vm.musl \
vm.flagless \ vm.flagless \
docker.support \ docker.support \
systemd.support \
jdk.containerized jdk.containerized
# Minimum jtreg version # Minimum jtreg version

View File

@ -0,0 +1,28 @@
/*
* 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.
*
* 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.
*/
public class HelloSystemd {
public static void main(String args[]) {
System.out.println("Hello Systemd");
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.
*
* 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 jdk.test.lib.containers.systemd.SystemdRunOptions;
import jdk.test.lib.containers.systemd.SystemdTestUtils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.whitebox.WhiteBox;
import jtreg.SkippedException;
/*
* @test
* @bug 8322420 8217338
* @summary Memory/CPU awareness test for JDK-under-test inside a systemd slice.
* @requires systemd.support
* @library /test/lib
* @modules java.base/jdk.internal.platform
* @build HelloSystemd jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
* @run main/othervm -Xbootclasspath/a:whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI SystemdMemoryAwarenessTest
*/
public class SystemdMemoryAwarenessTest {
private static final int MB = 1024 * 1024;
private static final WhiteBox wb = WhiteBox.getWhiteBox();
private static final String TEST_SLICE_NAME = SystemdMemoryAwarenessTest.class.getSimpleName() + "HS";
public static void main(String[] args) throws Exception {
testHelloSystemd();
}
private static void testHelloSystemd() throws Exception {
SystemdRunOptions opts = SystemdTestUtils.newOpts("HelloSystemd");
// 1 GB memory, but the limit in the lower hierarchy is 512M
opts.memoryLimit("1024M");
int expectedMemLimit = 512;
// expected detected limit we test for, 512MB
opts.sliceDMemoryLimit(String.format("%dM", expectedMemLimit));
int physicalCpus = wb.hostCPUs();
if (physicalCpus < 2) {
System.err.println("WARNING: host system only has " + physicalCpus + " cpus. Expected >= 2");
System.err.println("The active_processor_count assertion will trivially pass.");
}
// Use a CPU core limit of 1 for best coverage
int coreLimit = 1;
System.out.println("DEBUG: Running test with a CPU limit of " + coreLimit);
opts.cpuLimit(String.format("%d%%", coreLimit * 100));
opts.sliceName(TEST_SLICE_NAME);
OutputAnalyzer out = SystemdTestUtils.buildAndRunSystemdJava(opts);
out.shouldHaveExitValue(0)
.shouldContain("Hello Systemd")
.shouldContain(String.format("Memory Limit is: %d", (expectedMemLimit * MB)));
try {
out.shouldContain("OSContainer::active_processor_count: " + coreLimit);
} catch (RuntimeException e) {
// CPU delegation needs to be enabled when run as user on cg v2
if (SystemdTestUtils.RUN_AS_USER) {
String hint = "When run as user on cg v2 cpu delegation needs to be configured!";
throw new SkippedException(hint);
}
throw e;
}
}
}

View File

@ -101,6 +101,7 @@ requires.properties= \
vm.jvmti \ vm.jvmti \
vm.cpu.features \ vm.cpu.features \
docker.support \ docker.support \
systemd.support \
release.implementor \ release.implementor \
jdk.containerized \ jdk.containerized \
jdk.foreign.linker jdk.foreign.linker

View File

@ -133,6 +133,7 @@ public class VMProps implements Callable<Map<String, String>> {
map.put("vm.compiler1.enabled", this::isCompiler1Enabled); map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
map.put("vm.compiler2.enabled", this::isCompiler2Enabled); map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
map.put("docker.support", this::dockerSupport); map.put("docker.support", this::dockerSupport);
map.put("systemd.support", this::systemdSupport);
map.put("vm.musl", this::isMusl); map.put("vm.musl", this::isMusl);
map.put("release.implementor", this::implementor); map.put("release.implementor", this::implementor);
map.put("jdk.containerized", this::jdkContainerized); map.put("jdk.containerized", this::jdkContainerized);
@ -610,7 +611,7 @@ public class VMProps implements Callable<Map<String, String>> {
if (isSupported) { if (isSupported) {
try { try {
isSupported = checkDockerSupport(); isSupported = checkProgramSupport("checkDockerSupport()", Container.ENGINE_COMMAND);
} catch (Exception e) { } catch (Exception e) {
isSupported = false; isSupported = false;
} }
@ -620,6 +621,27 @@ public class VMProps implements Callable<Map<String, String>> {
return "" + isSupported; return "" + isSupported;
} }
/**
* A simple check for systemd support
*
* @return true if systemd is supported in a given environment
*/
protected String systemdSupport() {
log("Entering systemdSupport()");
boolean isSupported = Platform.isLinux();
if (isSupported) {
try {
isSupported = checkProgramSupport("checkSystemdSupport()", "systemd-run");
} catch (Exception e) {
isSupported = false;
}
}
log("systemdSupport(): returning isSupported = " + isSupported);
return "" + isSupported;
}
// Configures process builder to redirect process stdout and stderr to a file. // Configures process builder to redirect process stdout and stderr to a file.
// Returns file names for stdout and stderr. // Returns file names for stdout and stderr.
private Map<String, String> redirectOutputToLogFile(String msg, ProcessBuilder pb, String fileNameBase) { private Map<String, String> redirectOutputToLogFile(String msg, ProcessBuilder pb, String fileNameBase) {
@ -654,17 +676,17 @@ public class VMProps implements Callable<Map<String, String>> {
}); });
} }
private boolean checkDockerSupport() throws IOException, InterruptedException { private boolean checkProgramSupport(String logString, String cmd) throws IOException, InterruptedException {
log("checkDockerSupport(): entering"); log(logString + ": entering");
ProcessBuilder pb = new ProcessBuilder("which", Container.ENGINE_COMMAND); ProcessBuilder pb = new ProcessBuilder("which", cmd);
Map<String, String> logFileNames = Map<String, String> logFileNames =
redirectOutputToLogFile("checkDockerSupport(): which " + Container.ENGINE_COMMAND, redirectOutputToLogFile(logString + ": which " + cmd,
pb, "which-container"); pb, "which-cmd");
Process p = pb.start(); Process p = pb.start();
p.waitFor(10, TimeUnit.SECONDS); p.waitFor(10, TimeUnit.SECONDS);
int exitValue = p.exitValue(); int exitValue = p.exitValue();
log(String.format("checkDockerSupport(): exitValue = %s, pid = %s", exitValue, p.pid())); log(String.format("%s: exitValue = %s, pid = %s", logString, exitValue, p.pid()));
if (exitValue != 0) { if (exitValue != 0) {
printLogfileContent(logFileNames); printLogfileContent(logFileNames);
} }

View File

@ -0,0 +1,152 @@
/*
* 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.
*
* 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.systemd;
import static jdk.test.lib.Asserts.assertNotNull;
import java.util.ArrayList;
import java.util.Collections;
// This class represents options for running java inside systemd slices
// in test environment.
public class SystemdRunOptions {
public ArrayList<String> javaOpts = new ArrayList<>();
public String classToRun; // class or "-version"
public ArrayList<String> classParams = new ArrayList<>();
public String memoryLimit; // used in slice for MemoryLimit property
public String cpuLimit; // used in slice for CPUQuota property
public String sliceName; // name of the slice (nests CPU in memory)
public String sliceDMemoryLimit; // used in jdk_internal.slice.d
public String sliceDCpuLimit; // used in jdk_internal.slice.d
/**
* Convenience constructor for most common use cases in testing.
* @param classToRun a class to run, or "-version"
* @param javaOpts java options to use
*
* @return Default docker run options
*/
public SystemdRunOptions(String classToRun, String... javaOpts) {
this.classToRun = classToRun;
Collections.addAll(this.javaOpts, javaOpts);
this.sliceName = defaultSliceName();
}
private static String defaultSliceName() {
// Create a unique name for a systemd slice
// jtreg guarantees that test.name is unique among all concurrently executing
// tests. For example, if you have two test roots:
//
// $ find test -type f
// test/foo/TEST.ROOT
// test/foo/my/TestCase.java
// test/bar/TEST.ROOT
// test/bar/my/TestCase.java
// $ jtreg -concur:2 test/foo test/bar
//
// jtreg will first run all the tests under test/foo. When they are all finished, then
// jtreg will run all the tests under test/bar. So you will never have two concurrent
// test cases whose test.name is "my/TestCase.java"
String testname = System.getProperty("test.name");
assertNotNull(testname, "must be set by jtreg");
testname = testname.replace(".java", "");
testname = testname.replace("/", "_");
testname = testname.replace("\\", "_");
testname = testname.replace("-", "_");
// Example:
// Memory: "test_containers_systemd_TestMemoryAwareness"
// CPU: "test_containers_systemd_TestMemoryAwareness-cpu" => derived
return testname;
}
/**
* The memory limit set with a .slice file in the systemd
* config directory.
*
* @param memLimit The memory limit to set (e.g. 1000M).
* @return The run options.
*/
public SystemdRunOptions memoryLimit(String memLimit) {
this.memoryLimit = memLimit;
return this;
}
/**
* The memory limit to set in the top-level jdk_internal.slice.d
* systemd config directory.
*
* @param memoryLimit The memory limit to set.
* @return The run options.
*/
public SystemdRunOptions sliceDMemoryLimit(String memoryLimit) {
this.sliceDMemoryLimit = memoryLimit;
return this;
}
/**
* The CPU limit set with a .slice file in the systemd
* config directory.
*
* @param cpuLimit
* @return The run options.
*/
public SystemdRunOptions cpuLimit(String cpuLimit) {
this.cpuLimit = cpuLimit;
return this;
}
/**
* The Cpu limit set in the top-level jdk_internal.slice.d
* systemd config directory.
*
* @param cpuLimit The CPU limit to set to.
* @return The run options.
*/
public SystemdRunOptions sliceDCpuLimit(String cpuLimit) {
this.sliceDCpuLimit = cpuLimit;
return this;
}
public SystemdRunOptions sliceName(String name) {
this.sliceName = name;
return this;
}
public SystemdRunOptions addJavaOpts(String... opts) {
Collections.addAll(javaOpts, opts);
return this;
}
public SystemdRunOptions addClassOptions(String... opts) {
Collections.addAll(classParams,opts);
return this;
}
public boolean hasSliceDLimit() {
return this.sliceDMemoryLimit != null ||
this.sliceDCpuLimit != null;
}
}

View File

@ -0,0 +1,350 @@
/*
* 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.
*
* 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.systemd;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jdk.internal.platform.Metrics;
import jdk.test.lib.Platform;
import jdk.test.lib.Utils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.util.FileUtils;
import jtreg.SkippedException;
public class SystemdTestUtils {
private static final String CGROUPS_PROVIDER = Metrics.systemMetrics().getProvider();
private static boolean CGROUPS_V2 = "cgroupv2".equals(CGROUPS_PROVIDER);
public static boolean RUN_AS_USER = !Platform.isRoot() && CGROUPS_V2;
private static final String SLICE_NAMESPACE_PREFIX = "jdk_internal";
private static final String SLICE_D_MEM_CONFIG_FILE = "memory-limit.conf";
private static final String SLICE_D_CPU_CONFIG_FILE = "cpu-limit.conf";
private static final String USER_HOME = System.getProperty("user.home");
private static final Path SYSTEMD_CONFIG_HOME_ROOT = Path.of("/", "etc", "systemd", "system");
private static final Path SYSTEMD_CONFIG_HOME_USER = Path.of(USER_HOME, ".config", "systemd", "user");
private static final Path SYSTEMD_CONFIG_HOME = Platform.isRoot() ? SYSTEMD_CONFIG_HOME_ROOT : SYSTEMD_CONFIG_HOME_USER;
// Specifies how many lines to copy from child STDOUT to main test output.
// Having too many lines in the main test output will result
// in JT harness trimming the output, and can lead to loss of useful
// diagnostic information.
private static final int MAX_LINES_TO_COPY_FOR_CHILD_STDOUT = 100;
public record ResultFiles(Path memory, Path cpu, Path sliceDotDDir) {}
/**
* Create commonly used options with the class to be launched inside the
* systemd slice
*
* @param testClass The test class or {@code -version}
* @return The basic options.
*/
public static SystemdRunOptions newOpts(String testClass) {
return new SystemdRunOptions(testClass,
"-Xlog:os+container=trace",
"-cp",
Utils.TEST_CLASSES);
}
/**
* Run Java inside a systemd slice with specified parameters and options.
*
* @param opts The systemd slice options when running java
* @return An OutputAnalyzer of the output of the command than ran.
* @throws Exception If something went wrong.
* @throws SkippedException If the test cannot be run (i.e. non-root user
* on cgroups v1).
*/
public static OutputAnalyzer buildAndRunSystemdJava(SystemdRunOptions opts) throws Exception, SkippedException {
if (!Platform.isRoot() && !CGROUPS_V2) {
throw new SkippedException("Systemd tests require root on cgroup v1. Test skipped!");
}
ResultFiles files = SystemdTestUtils.buildSystemdSlices(opts);
try {
return SystemdTestUtils.systemdRunJava(opts);
} finally {
try {
if (files.memory() != null) {
Files.delete(files.memory());
}
if (files.cpu() != null) {
Files.delete(files.cpu());
}
if (files.sliceDotDDir() != null) {
FileUtils.deleteFileTreeUnchecked(files.sliceDotDDir());
}
} catch (NoSuchFileException e) {
// ignore
}
}
}
private static OutputAnalyzer systemdRunJava(SystemdRunOptions opts) throws Exception {
return execute(buildJavaCommand(opts));
}
/**
* Create systemd slice files under /etc/systemd/system.
*
* The JDK will then run within that slice as provided by the SystemdRunOptions.
*
* @param runOpts The systemd slice options to use when running the test.
* @return The systemd slice files (for cleanup-purposes later).
* @throws Exception
*/
private static ResultFiles buildSystemdSlices(SystemdRunOptions runOpts) throws Exception {
String sliceName = sliceName(runOpts);
String sliceNameCpu = sliceNameCpu(runOpts);
// Generate systemd slices for cpu/memory
String memorySliceContent = getMemorySlice(runOpts, sliceName);
String cpuSliceContent = getCpuSlice(runOpts, sliceName);
// Ensure base directory exists
Files.createDirectories(SYSTEMD_CONFIG_HOME);
Path sliceDotDDir = null;
if (runOpts.hasSliceDLimit()) {
String dirName = String.format("%s.slice.d", SLICE_NAMESPACE_PREFIX);
sliceDotDDir = SYSTEMD_CONFIG_HOME.resolve(Path.of(dirName));
Files.createDirectory(sliceDotDDir);
if (runOpts.sliceDMemoryLimit != null) {
Path memoryConfig = sliceDotDDir.resolve(Path.of(SLICE_D_MEM_CONFIG_FILE));
Files.writeString(memoryConfig, getMemoryDSliceContent(runOpts));
}
if (runOpts.sliceDCpuLimit != null) {
Path cpuConfig = sliceDotDDir.resolve(Path.of(SLICE_D_CPU_CONFIG_FILE));
Files.writeString(cpuConfig, getCPUDSliceContent(runOpts));
}
}
Path memory, cpu;
try {
// memory slice
memory = SYSTEMD_CONFIG_HOME.resolve(Path.of(sliceFileName(sliceName)));
// cpu slice nested in memory
cpu = SYSTEMD_CONFIG_HOME.resolve(Path.of(sliceFileName(sliceNameCpu)));
Files.writeString(memory, memorySliceContent);
Files.writeString(cpu, cpuSliceContent);
} catch (IOException e) {
throw new AssertionError("Failed to write systemd slice files");
}
systemdDaemonReload(cpu);
return new ResultFiles(memory, cpu, sliceDotDDir);
}
private static String sliceName(SystemdRunOptions runOpts) {
// Slice name may include '-' which is a hierarchical slice indicator.
// Replace '-' with '_' to avoid side-effects.
return SLICE_NAMESPACE_PREFIX + "-" + runOpts.sliceName.replace("-", "_");
}
private static String sliceNameCpu(SystemdRunOptions runOpts) {
String slice = sliceName(runOpts);
return String.format("%s-cpu", slice);
}
private static void systemdDaemonReload(Path cpu) throws Exception {
List<String> daemonReload = systemCtl();
daemonReload.add("daemon-reload");
if (execute(daemonReload).getExitValue() != 0) {
throw new AssertionError("Failed to reload systemd daemon");
}
}
private static List<String> systemCtl() {
return commandWithUser("systemctl");
}
/**
* 'baseCommand' or 'baseCommand --user' as list, depending on the cgroups
* version and running user.
*
* @return 'baseCommand' if we are the root user, 'systemctl --user' if
* the current user is non-root and we are on cgroups v2. Note:
* Cgroups v1 and non-root is not possible as tests are skipped then.
*/
private static List<String> commandWithUser(String baseCommand) {
List<String> command = new ArrayList<>();
command.add(baseCommand);
if (RUN_AS_USER) {
command.add("--user");
}
return command;
}
private static String getCpuSlice(SystemdRunOptions runOpts, String sliceName) {
String basicSliceFormat = getBasicSliceFormat();
return String.format(basicSliceFormat, sliceName, getCPUSliceContent(runOpts));
}
private static String getCPUSliceContent(SystemdRunOptions runOpts) {
String format = basicCPUContentFormat();
return String.format(format, runOpts.cpuLimit);
}
private static String getMemorySlice(SystemdRunOptions runOpts, String sliceName) {
String basicSliceFormat = getBasicSliceFormat();
return String.format(basicSliceFormat, sliceName, getMemorySliceContent(runOpts));
}
private static String getMemoryDSliceContent(SystemdRunOptions runOpts) {
String format = "[Slice]\n" + basicMemoryContentFormat();
return String.format(format, runOpts.sliceDMemoryLimit);
}
private static String getCPUDSliceContent(SystemdRunOptions runOpts) {
String format = "[Slice]\n" + basicCPUContentFormat();
return String.format(format, runOpts.sliceDCpuLimit);
}
private static String basicCPUContentFormat() {
return """
CPUAccounting=true
CPUQuota=%s
""";
}
private static String basicMemoryContentFormat() {
return """
MemoryAccounting=true
MemoryLimit=%s
""";
}
private static String getMemorySliceContent(SystemdRunOptions runOpts) {
String format = basicMemoryContentFormat();
return String.format(format, runOpts.memoryLimit);
}
private static String getBasicSliceFormat() {
return """
[Unit]
Description=OpenJDK Tests Slice for %s
Before=slices.target
[Slice]
%s
""";
}
private static String sliceFileName(String sliceName) {
return String.format("%s.slice", sliceName);
}
/**
* Build the java command to run inside a systemd slice
*
* @param SystemdRunOptions options for running the systemd slice test
*
* @return command
* @throws Exception
*/
private static List<String> buildJavaCommand(SystemdRunOptions opts) throws Exception {
// systemd-run [--user] --slice <slice-name>.slice --scope <java>
List<String> javaCmd = systemdRun();
javaCmd.add("--slice");
javaCmd.add(sliceFileName(sliceNameCpu(opts)));
javaCmd.add("--scope");
javaCmd.add(Path.of(Utils.TEST_JDK, "bin", "java").toString());
javaCmd.addAll(opts.javaOpts);
javaCmd.add(opts.classToRun);
javaCmd.addAll(opts.classParams);
return javaCmd;
}
private static List<String> systemdRun() {
return commandWithUser("systemd-run");
}
/**
* Execute a specified command in a process, report diagnostic info.
*
* @param command to be executed
* @return The output from the process
* @throws Exception
*/
private static OutputAnalyzer execute(List<String> command) throws Exception {
return execute(command.toArray(String[]::new));
}
/**
* Execute a specified command in a process, report diagnostic info.
*
* @param command to be executed
* @return The output from the process
* @throws Exception
*/
private static OutputAnalyzer execute(String... command) throws Exception {
ProcessBuilder pb = new ProcessBuilder(command);
System.out.println("[COMMAND]\n" + Utils.getCommandLine(pb));
Process p = pb.start();
long pid = p.pid();
OutputAnalyzer output = new OutputAnalyzer(p);
int max = MAX_LINES_TO_COPY_FOR_CHILD_STDOUT;
String stdout = output.getStdout();
String stdoutLimited = limitLines(stdout, max);
System.out.println("[STDERR]\n" + output.getStderr());
System.out.println("[STDOUT]\n" + stdoutLimited);
if (stdout != stdoutLimited) {
System.out.printf("Child process STDOUT is limited to %d lines\n",
max);
}
String stdoutLogFile = String.format("systemd-stdout-%d.log", pid);
writeOutputToFile(stdout, stdoutLogFile);
System.out.println("Full child process STDOUT was saved to " + stdoutLogFile);
return output;
}
private static void writeOutputToFile(String output, String fileName) throws Exception {
try (FileWriter fw = new FileWriter(fileName)) {
fw.write(output, 0, output.length());
}
}
private static String limitLines(String buffer, int nrOfLines) {
List<String> l = Arrays.asList(buffer.split("\\R"));
if (l.size() < nrOfLines) {
return buffer;
}
return String.join("\n", l.subList(0, nrOfLines));
}
}

View File

@ -769,6 +769,7 @@ public class WhiteBox {
public native void printOsInfo(); public native void printOsInfo();
public native long hostPhysicalMemory(); public native long hostPhysicalMemory();
public native long hostPhysicalSwap(); public native long hostPhysicalSwap();
public native int hostCPUs();
// Decoder // Decoder
public native void disableElfSectionCache(); public native void disableElfSectionCache();