jdk-24/test/hotspot/jtreg/runtime/os/TestTrimNative.java
Thomas Stuefe d31fd78d96 8325496: Make TrimNativeHeapInterval a product switch
Reviewed-by: dholmes, mbaesken
2024-02-21 04:39:09 +00:00

349 lines
15 KiB
Java

/*
* Copyright (c) 2023 SAP SE. All rights reserved.
* Copyright (c) 2023, 2024, Red Hat, Inc. All rights reserved.
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* 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.
*
*/
/*
* @test id=trimNative
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run driver TestTrimNative trimNative
*/
/*
* @test id=trimNativeStrict
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/manual TestTrimNative trimNativeStrict
*/
/*
* @test id=trimNativeHighInterval
* @summary High interval trimming should not even kick in for short program runtimes
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run driver TestTrimNative trimNativeHighInterval
*/
/*
* @test id=trimNativeLowInterval
* @summary Very low (sub-second) interval, nothing should explode
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run driver TestTrimNative trimNativeLowInterval
*/
/*
* @test id=trimNativeLowIntervalStrict
* @summary Very low (sub-second) interval, nothing should explode (stricter test, manual mode)
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/manual TestTrimNative trimNativeLowIntervalStrict
*/
/*
* @test id=testOffByDefault
* @summary Test that trimming is disabled by default
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run driver TestTrimNative testOffByDefault
*/
/*
* @test id=testOffExplicit
* @summary Test that trimming can be disabled explicitly
* @requires vm.flagless
* @requires (os.family=="linux") & !vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run driver TestTrimNative testOffExplicit
*/
/*
* @test id=testOffOnNonCompliantPlatforms
* @summary Test that trimming is correctly reported as unavailable if unavailable
* @requires vm.flagless
* @requires (os.family!="linux") | vm.musl
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run driver TestTrimNative testOffOnNonCompliantPlatforms
*/
import jdk.test.lib.Platform;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.test.whitebox.WhiteBox;
public class TestTrimNative {
// Actual RSS increase is a lot larger than 4 MB. Depends on glibc overhead, and NMT malloc headers in debug VMs.
// We need small-grained allocations to make sure they actually increase RSS (all touched) and to see the
// glibc-retaining-memory effect.
static final int szAllocations = 128;
static final int totalAllocationsSize = 128 * 1024 * 1024; // 128 MB total
static final int numAllocations = totalAllocationsSize / szAllocations;
static long[] ptrs = new long[numAllocations];
enum Unit {
B(1), K(1024), M(1024*1024), G(1024*1024*1024);
public final long size;
Unit(long size) { this.size = size; }
}
private static String[] prepareOptions(String[] extraVMOptions, String[] programOptions) {
List<String> allOptions = new ArrayList<String>();
if (extraVMOptions != null) {
allOptions.addAll(Arrays.asList(extraVMOptions));
}
allOptions.add("-Xmx128m");
allOptions.add("-Xms128m"); // Stabilize RSS
allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS
allOptions.add("-XX:+UnlockDiagnosticVMOptions"); // For whitebox
allOptions.add("-XX:+WhiteBoxAPI");
allOptions.add("-Xbootclasspath/a:.");
allOptions.add("-XX:-ExplicitGCInvokesConcurrent"); // Invoke explicit GC on System.gc
allOptions.add("-Xlog:trimnative=debug");
allOptions.add("--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED");
if (programOptions != null) {
allOptions.addAll(Arrays.asList(programOptions));
}
return allOptions.toArray(new String[0]);
}
private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] programOptions) throws IOException {
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(prepareOptions(extraOptions, programOptions));
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldHaveExitValue(0);
return output;
}
private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expectEnabled,
int expectedInterval) {
if (expectEnabled) {
output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + " ms");
output.shouldContain("Native heap trimmer start");
} else {
output.shouldNotContain("Periodic native trim enabled");
}
}
/**
* Given JVM output, look for one or more log lines that describes a successful negative trim. The total amount
* of trims should be matching about what the test program allocated.
* @param output
* @param minTrimsExpected min number of periodic trim lines expected in UL log
* @param maxTrimsExpected min number of periodic trim lines expected in UL log
* @param strict: if true, expect RSS to go down; if false, just look for trims without looking at RSS.
*/
private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minTrimsExpected,
int maxTrimsExpected, boolean strict) {
output.reportDiagnosticSummary();
List<String> lines = output.asLines();
Pattern pat = Pattern.compile(".*\\[trimnative\\] Periodic Trim \\(\\d+\\): (\\d+)([BKMG])->(\\d+)([BKMG]).*");
int numTrimsFound = 0;
long rssReductionTotal = 0;
for (String line : lines) {
Matcher mat = pat.matcher(line);
if (mat.matches()) {
long rss1 = Long.parseLong(mat.group(1)) * Unit.valueOf(mat.group(2)).size;
long rss2 = Long.parseLong(mat.group(3)) * Unit.valueOf(mat.group(4)).size;
if (rss1 > rss2) {
rssReductionTotal += (rss1 - rss2);
}
numTrimsFound ++;
}
if (numTrimsFound > maxTrimsExpected) {
throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxTrimsExpected +
"). Does the interval setting not work?");
}
}
if (numTrimsFound < minTrimsExpected) {
throw new RuntimeException("We found fewer (periodic) trim lines in UL log than expected (expected at least " + minTrimsExpected +
", found " + numTrimsFound + ").");
}
System.out.println("Found " + numTrimsFound + " trims. Ok.");
if (strict && maxTrimsExpected > 0) {
// This is very fuzzy. Test program malloced X bytes, then freed them again and trimmed. But the log line prints change in RSS.
// Which, of course, is influenced by a lot of other factors. But we expect to see *some* reasonable reduction in RSS
// due to trimming.
float fudge = 0.5f;
// On ppc, we see a vastly diminished return (~3M reduction instead of ~200), I suspect because of the underlying
// 64k pages lead to a different geometry. Manual tests with larger reclaim sizes show that autotrim works. For
// this test, we just reduce the fudge factor.
if (Platform.isPPC()) { // le and be both
fudge = 0.01f;
}
long expectedMinimalReduction = (long) (totalAllocationsSize * fudge);
if (rssReductionTotal < expectedMinimalReduction) {
throw new RuntimeException("We did not see the expected RSS reduction in the UL log. Expected (with fudge)" +
" to see at least a combined reduction of " + expectedMinimalReduction + ".");
} else {
System.out.println("Found high enough RSS reduction from trims: " + rssReductionTotal);
}
}
}
static class Tester {
public static void main(String[] args) throws Exception {
long sleeptime = Long.parseLong(args[0]);
System.out.println("Will spike now...");
WhiteBox wb = WhiteBox.getWhiteBox();
for (int i = 0; i < numAllocations; i++) {
ptrs[i] = wb.NMTMalloc(szAllocations);
wb.preTouchMemory(ptrs[i], szAllocations);
}
for (int i = 0; i < numAllocations; i++) {
wb.NMTFree(ptrs[i]);
}
System.out.println("Done spiking.");
System.out.println("GC...");
System.gc();
// give GC time to react
System.out.println("Sleeping for " + sleeptime + " ms...");
Thread.sleep(sleeptime);
System.out.println("Done.");
}
}
public static void main(String[] args) throws Exception {
if (args.length == 0) {
throw new RuntimeException("Argument error");
}
boolean strictTesting = args[0].endsWith("Strict");
switch (args[0]) {
case "trimNative":
case "trimNativeStrict": {
long trimInterval = 500; // twice per second
long ms1 = System.currentTimeMillis();
OutputAnalyzer output = runTestWithOptions(
new String[] { "-XX:TrimNativeHeapInterval=" + trimInterval },
new String[] { TestTrimNative.Tester.class.getName(), "5000" }
);
long ms2 = System.currentTimeMillis();
long runtime_ms = ms2 - ms1;
checkExpectedLogMessages(output, true, 500);
long maxTrimsExpected = runtime_ms / trimInterval;
long minTrimsExpected = maxTrimsExpected / 2;
parseOutputAndLookForNegativeTrim(output, (int) minTrimsExpected, (int) maxTrimsExpected, strictTesting);
} break;
case "trimNativeHighInterval": {
OutputAnalyzer output = runTestWithOptions(
new String[] { "-XX:TrimNativeHeapInterval=" + Integer.MAX_VALUE },
new String[] { TestTrimNative.Tester.class.getName(), "5000" }
);
checkExpectedLogMessages(output, true, Integer.MAX_VALUE);
// We should not see any trims since the interval would prevent them
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
} break;
case "trimNativeLowInterval":
case "trimNativeLowIntervalStrict": {
long ms1 = System.currentTimeMillis();
OutputAnalyzer output = runTestWithOptions(
new String[] { "-XX:TrimNativeHeapInterval=1" },
new String[] { TestTrimNative.Tester.class.getName(), "0" }
);
long ms2 = System.currentTimeMillis();
int maxTrimsExpected = (int)(ms2 - ms1); // 1ms trim interval
checkExpectedLogMessages(output, true, 1);
parseOutputAndLookForNegativeTrim(output, 1, (int)maxTrimsExpected, strictTesting);
} break;
case "testOffOnNonCompliantPlatforms": {
OutputAnalyzer output = runTestWithOptions(
new String[] { "-XX:TrimNativeHeapInterval=1" },
new String[] { "-version" }
);
checkExpectedLogMessages(output, false, 0);
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
// The following output is expected to be printed with warning level, so it should not need -Xlog
output.shouldContain("[warning][trimnative] Native heap trim is not supported on this platform");
} break;
case "testOffExplicit": {
OutputAnalyzer output = runTestWithOptions(
new String[] { "-XX:TrimNativeHeapInterval=0" },
new String[] { "-version" }
);
checkExpectedLogMessages(output, false, 0);
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
} break;
case "testOffByDefault": {
OutputAnalyzer output = runTestWithOptions(null, new String[] { "-version" } );
checkExpectedLogMessages(output, false, 0);
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
} break;
default:
throw new RuntimeException("Invalid test " + args[0]);
}
}
}