jdk-24/test/hotspot/jtreg/runtime/NMT/NMTInitializationTest.java
Leo Korinth d52a995f35 8315097: Rename createJavaProcessBuilder
Reviewed-by: lmesnik, dholmes, rriggs, stefank
2023-10-27 08:47:26 +00:00

221 lines
9.3 KiB
Java

/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 2023, 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.
*/
/*
* This test tests the ability of NMT to work correctly when masses of allocations happen before NMT is initialized;
* That pre-NMT-init phase starts when the libjvm is loaded and the C++ dynamic initialization runs, and ends when
* NMT is initialized after the VM parsed its arguments in CreateJavaVM.
*
* During that phase, NMT is not yet initialized fully; C-heap allocations are kept in a special lookup table to
* be able to tell them apart from post-NMT-init initializations later. For details, see nmtPreInit.hpp.
*
* The size of this table is limited, and its load factor affects lookup time; that lookup time is paid throughout
* the VM life for all os::free() calls, regardless if NMT is on or not. Therefore we are interested in keeping the
* number of pre-NMT-init allocations low.
*
* Normally, the VM allocates about 500 surviving allocations (allocations which are not freed before NMT initialization
* finishes). The number is mainly influenced by the number of VM arguments, since those get strdup'ed around.
* Therefore, the only direct way to test pre-NMT-init allocations is by feeding the VM a lot of arguments, and this is
* what this test does.
*
*/
/**
* @test id=normal-off
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest normal off
*/
/**
* @test id=normal-detail
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest normal detail
*/
/**
* @test id=default_long-off
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest long off
*/
/**
* @test id=default_long-detail
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest long detail
*/
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NMTInitializationTest {
final static boolean debug = true;
static String randomString() {
Random r = new Random();
int len = r.nextInt(100) + 100;
StringBuilder bld = new StringBuilder();
for (int i = 0; i < len; i ++) {
bld.append(r.nextInt(26) + 'A');
}
return bld.toString();
}
static Path createCommandFile(int numlines) throws Exception {
String fileName = "commands_" + numlines + ".txt";
FileWriter fileWriter = new FileWriter(fileName);
PrintWriter printWriter = new PrintWriter(fileWriter);
String line = "-XX:ErrorFile=" + randomString();
for (long i = 0; i < numlines / 2; i++) {
printWriter.println(line);
}
printWriter.close();
return Paths.get(fileName);
}
enum TestMode {
// call the VM with a normal-ish command line (long but not oudlandishly so). We expect the lookup table after
// initialization to be sparsely populated and sport very short chain lengths.
mode_normal(30, 5),
// call the VM with an outlandishly long command line. We expect the lookup table after initialization
// to be densely populated but hopefully evenly distributed.
mode_long(20000, 20);
final int num_command_line_args;
final int expected_max_chain_len;
TestMode(int num_command_line_args, int expected_max_chain_len) {
this.num_command_line_args = num_command_line_args;
this.expected_max_chain_len = expected_max_chain_len;
}
};
enum NMTMode {
off, summary, detail
};
public static void main(String args[]) throws Exception {
TestMode testMode = TestMode.valueOf("mode_" + args[0]);
NMTMode nmtMode = NMTMode.valueOf(args[1]);
System.out.println("Test mode: " + testMode + ", NMT mode: " + nmtMode);
Path commandLineFile = createCommandFile(testMode.num_command_line_args);
ArrayList<String> vmArgs = new ArrayList<>();
vmArgs.add("-Xlog:nmt");
vmArgs.add("-XX:NativeMemoryTracking=" + nmtMode.name());
vmArgs.add("-XX:+UnlockDiagnosticVMOptions");
vmArgs.add("-XX:+PrintNMTStatistics");
if (commandLineFile != null) {
vmArgs.add("@" + commandLineFile.getFileName());
}
vmArgs.add("-version");
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(vmArgs);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
if (debug) {
output.reportDiagnosticSummary();
}
output.shouldHaveExitValue(0);
// Now evaluate the output of -Xlog:nmt
// We expect something like:
// [0.001s][info][nmt] NMT initialized: detail
// [0.001s][info][nmt] Preinit state:
// [0.001s][info][nmt] entries: 342 (primary: 342, empties: 7577), sum bytes: 12996, longest chain length: 1
// [0.001s][info][nmt] pre-init mallocs: 375, pre-init reallocs: 6, pre-init frees: 33, pre-to-post reallocs: 4, pre-to-post frees: 0
output.shouldContain("NMT initialized: " + nmtMode.name());
output.shouldContain("Preinit state:");
if (nmtMode != NMTMode.off) { // in OFF mode LU table is deleted after VM initialization, nothing to see there
String regex = ".*entries: (\\d+).*sum bytes: (\\d+).*longest chain length: (\\d+).*";
output.shouldMatch(regex);
String line = output.firstMatch(regex, 0);
if (line == null) {
throw new RuntimeException("expected: " + regex);
}
System.out.println(line);
Pattern p = Pattern.compile(regex);
Matcher mat = p.matcher(line);
mat.matches();
int entries = Integer.parseInt(mat.group(1));
int sum_bytes = Integer.parseInt(mat.group(2));
int longest_chain = Integer.parseInt(mat.group(3));
System.out.println("found: " + entries + " - " + sum_bytes + longest_chain + ".");
// Now we test the state of the internal lookup table, and through our assumptions about
// early pre-NMT-init allocations:
// The normal allocation count of surviving pre-init allocations is around 300-500, with the sum of allocated
// bytes of a few dozen KB. We check these boundaries (with a very generous overhead) to see if the numbers are
// way off. If they are, we may either have a leak or just a lot more allocations than we thought before
// NMT initialization. Both cases should be investigated. Even if the allocations are valid, too many of them
// stretches the limits of the lookup map, and therefore may cause slower lookup. We should then either change
// the coding, reducing the number of allocations. Or enlarge the lookup table.
// Apply some sensible assumptions
if (entries > testMode.num_command_line_args + 2000) { // Note: normal baseline is 400-500
throw new RuntimeException("Suspiciously high number of pre-init allocations.");
}
if (sum_bytes > 128 * 1024 * 1024) { // Note: normal baseline is ~30-40KB
throw new RuntimeException("Suspiciously high pre-init memory usage.");
}
if (longest_chain > testMode.expected_max_chain_len) {
// Under normal circumstances, load factor of the map should be about 0.1. With a good hash distribution, we
// should rarely see even a chain > 1. Warn if we see exceedingly long bucket chains, since this indicates
// either that the hash algorithm is inefficient or we have a bug somewhere.
throw new RuntimeException("Suspiciously long bucket chains in lookup table.");
}
// Finally, check that we see our final NMT report:
output.shouldContain("Native Memory Tracking:");
output.shouldMatch("Total: reserved=\\d+, committed=\\d+.*");
}
}
}