8333446: Add tests for hierarchical container support
Reviewed-by: mbaesken, zzambers
This commit is contained in:
parent
bfe7f9205b
commit
d9fdf69c34
@ -2497,6 +2497,13 @@ WB_ENTRY(jint, WB_ValidateCgroup(JNIEnv* env,
|
||||
return ret;
|
||||
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))
|
||||
os::print_os_info(tty);
|
||||
WB_END
|
||||
@ -2938,6 +2945,7 @@ static JNINativeMethod methods[] = {
|
||||
(void*)&WB_ValidateCgroup },
|
||||
{CC"hostPhysicalMemory", CC"()J", (void*)&WB_HostPhysicalMemory },
|
||||
{CC"hostPhysicalSwap", CC"()J", (void*)&WB_HostPhysicalSwap },
|
||||
{CC"hostCPUs", CC"()I", (void*)&WB_HostCPUs },
|
||||
{CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo },
|
||||
{CC"disableElfSectionCache", CC"()V", (void*)&WB_DisableElfSectionCache },
|
||||
{CC"resolvedMethodItemsCount", CC"()J", (void*)&WB_ResolvedMethodItemsCount },
|
||||
|
@ -87,6 +87,7 @@ requires.properties= \
|
||||
vm.musl \
|
||||
vm.flagless \
|
||||
docker.support \
|
||||
systemd.support \
|
||||
jdk.containerized
|
||||
|
||||
# Minimum jtreg version
|
||||
|
28
test/hotspot/jtreg/containers/systemd/HelloSystemd.java
Normal file
28
test/hotspot/jtreg/containers/systemd/HelloSystemd.java
Normal 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");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -101,6 +101,7 @@ requires.properties= \
|
||||
vm.jvmti \
|
||||
vm.cpu.features \
|
||||
docker.support \
|
||||
systemd.support \
|
||||
release.implementor \
|
||||
jdk.containerized \
|
||||
jdk.foreign.linker
|
||||
|
@ -133,6 +133,7 @@ public class VMProps implements Callable<Map<String, String>> {
|
||||
map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
|
||||
map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
|
||||
map.put("docker.support", this::dockerSupport);
|
||||
map.put("systemd.support", this::systemdSupport);
|
||||
map.put("vm.musl", this::isMusl);
|
||||
map.put("release.implementor", this::implementor);
|
||||
map.put("jdk.containerized", this::jdkContainerized);
|
||||
@ -610,7 +611,7 @@ public class VMProps implements Callable<Map<String, String>> {
|
||||
|
||||
if (isSupported) {
|
||||
try {
|
||||
isSupported = checkDockerSupport();
|
||||
isSupported = checkProgramSupport("checkDockerSupport()", Container.ENGINE_COMMAND);
|
||||
} catch (Exception e) {
|
||||
isSupported = false;
|
||||
}
|
||||
@ -620,6 +621,27 @@ public class VMProps implements Callable<Map<String, String>> {
|
||||
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.
|
||||
// Returns file names for stdout and stderr.
|
||||
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 {
|
||||
log("checkDockerSupport(): entering");
|
||||
ProcessBuilder pb = new ProcessBuilder("which", Container.ENGINE_COMMAND);
|
||||
private boolean checkProgramSupport(String logString, String cmd) throws IOException, InterruptedException {
|
||||
log(logString + ": entering");
|
||||
ProcessBuilder pb = new ProcessBuilder("which", cmd);
|
||||
Map<String, String> logFileNames =
|
||||
redirectOutputToLogFile("checkDockerSupport(): which " + Container.ENGINE_COMMAND,
|
||||
pb, "which-container");
|
||||
redirectOutputToLogFile(logString + ": which " + cmd,
|
||||
pb, "which-cmd");
|
||||
Process p = pb.start();
|
||||
p.waitFor(10, TimeUnit.SECONDS);
|
||||
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) {
|
||||
printLogfileContent(logFileNames);
|
||||
}
|
||||
|
152
test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java
Normal file
152
test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java
Normal 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;
|
||||
}
|
||||
}
|
350
test/lib/jdk/test/lib/containers/systemd/SystemdTestUtils.java
Normal file
350
test/lib/jdk/test/lib/containers/systemd/SystemdTestUtils.java
Normal 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));
|
||||
}
|
||||
}
|
@ -769,6 +769,7 @@ public class WhiteBox {
|
||||
public native void printOsInfo();
|
||||
public native long hostPhysicalMemory();
|
||||
public native long hostPhysicalSwap();
|
||||
public native int hostCPUs();
|
||||
|
||||
// Decoder
|
||||
public native void disableElfSectionCache();
|
||||
|
Loading…
x
Reference in New Issue
Block a user