8313781: Add regression tests for large page logging and user-facing error messages

Reviewed-by: sjohanss, dholmes
This commit is contained in:
Thomas Stuefe 2023-10-27 06:55:25 +00:00
parent 9123961aaa
commit abad0408e8
2 changed files with 235 additions and 29 deletions

View File

@ -30,17 +30,46 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// This class allows us to parse system hugepage config from
// - a) the Operating System (the truth)
// - b) the JVM log (-Xlog:pagesize)
// This is used e.g. in TestHugePageDetection to determine if the JVM detects the correct settings from the OS.
class HugePageConfiguration {
Set<Long> _staticHugePageSizes;
long _staticDefaultHugePageSize;
public static class StaticHugePageConfig implements Comparable<StaticHugePageConfig> {
public long pageSize = -1;
public long nr_hugepages = -1;
public long nr_overcommit_hugepages = -1;
enum THPMode {always, never, madvise, unknown}
@Override
public int hashCode() {
return Objects.hash(pageSize);
}
@Override
public String toString() {
return "StaticHugePageConfig{" +
"pageSize=" + pageSize +
", nr_hugepages=" + nr_hugepages +
", nr_overcommit_hugepages=" + nr_overcommit_hugepages +
'}';
}
@Override
public int compareTo(StaticHugePageConfig o) {
return (int) (pageSize - o.pageSize);
}
}
Set<StaticHugePageConfig> _staticHugePageConfigurations;
long _staticDefaultHugePageSize = -1;
enum THPMode {always, never, madvise}
THPMode _thpMode;
long _thpPageSize;
public Set<Long> getStaticHugePageSizes() {
return _staticHugePageSizes;
public Set<StaticHugePageConfig> getStaticHugePageConfigurations() {
return _staticHugePageConfigurations;
}
public long getStaticDefaultHugePageSize() {
@ -55,8 +84,18 @@ class HugePageConfiguration {
return _thpPageSize;
}
public HugePageConfiguration(Set<Long> _staticHugePageSizes, long _staticDefaultHugePageSize, THPMode _thpMode, long _thpPageSize) {
this._staticHugePageSizes = _staticHugePageSizes;
// Returns true if the THP support is enabled
public boolean supportsTHP() {
return _thpMode == THPMode.always || _thpMode == THPMode.madvise;
}
// Returns true if static huge pages are supported (whether or not we have configured the pools)
public boolean supportsStaticHugePages() {
return _staticDefaultHugePageSize > 0 && _staticHugePageConfigurations.size() > 0;
}
public HugePageConfiguration(Set<StaticHugePageConfig> _staticHugePageConfigurations, long _staticDefaultHugePageSize, THPMode _thpMode, long _thpPageSize) {
this._staticHugePageConfigurations = _staticHugePageConfigurations;
this._staticDefaultHugePageSize = _staticDefaultHugePageSize;
this._thpMode = _thpMode;
this._thpPageSize = _thpPageSize;
@ -65,7 +104,7 @@ class HugePageConfiguration {
@Override
public String toString() {
return "Configuration{" +
"_staticHugePageSizes=" + _staticHugePageSizes +
"_staticHugePageConfigurations=" + _staticHugePageConfigurations +
", _staticDefaultHugePageSize=" + _staticDefaultHugePageSize +
", _thpMode=" + _thpMode +
", _thpPageSize=" + _thpPageSize +
@ -77,12 +116,8 @@ class HugePageConfiguration {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HugePageConfiguration that = (HugePageConfiguration) o;
return _staticDefaultHugePageSize == that._staticDefaultHugePageSize && _thpPageSize == that._thpPageSize && Objects.equals(_staticHugePageSizes, that._staticHugePageSizes) && _thpMode == that._thpMode;
}
@Override
public int hashCode() {
return Objects.hash(_staticHugePageSizes, _staticDefaultHugePageSize, _thpMode, _thpPageSize);
return _staticDefaultHugePageSize == that._staticDefaultHugePageSize && _thpPageSize == that._thpPageSize &&
Objects.equals(_staticHugePageConfigurations, that._staticHugePageConfigurations) && _thpMode == that._thpMode;
}
private static long readDefaultHugePageSizeFromOS() {
@ -102,25 +137,36 @@ class HugePageConfiguration {
return 0;
}
private static Set<Long> readSupportedHugePagesFromOS() {
TreeSet<Long> pagesizes = new TreeSet<>();
private static Set<StaticHugePageConfig> readSupportedHugePagesFromOS() throws IOException {
TreeSet<StaticHugePageConfig> hugePageConfigs = new TreeSet<>();
Pattern pat = Pattern.compile("hugepages-(\\d+)kB");
File[] subdirs = new File("/sys/kernel/mm/hugepages").listFiles();
if (subdirs != null) {
for (File f : subdirs) {
String name = f.getName();
for (File subdir : subdirs) {
String name = subdir.getName();
Matcher mat = pat.matcher(name);
if (mat.matches()) {
long pagesize = Long.parseLong(mat.group(1)) * 1024;
pagesizes.add(pagesize);
StaticHugePageConfig config = new StaticHugePageConfig();
config.pageSize = Long.parseLong(mat.group(1)) * 1024;
try (FileReader fr = new FileReader(subdir.getAbsolutePath() + "/nr_hugepages");
BufferedReader reader = new BufferedReader(fr)) {
String s = reader.readLine();
config.nr_hugepages = Long.parseLong(s);
}
try (FileReader fr = new FileReader(subdir.getAbsolutePath() + "/nr_overcommit_hugepages");
BufferedReader reader = new BufferedReader(fr)) {
String s = reader.readLine();
config.nr_overcommit_hugepages = Long.parseLong(s);
}
hugePageConfigs.add(config);
}
}
}
return pagesizes;
return hugePageConfigs;
}
private static THPMode readTHPModeFromOS() {
THPMode mode = THPMode.unknown;
THPMode mode = THPMode.never;
String file = "/sys/kernel/mm/transparent_hugepage/enabled";
try (FileReader fr = new FileReader(file);
BufferedReader reader = new BufferedReader(fr)) {
@ -136,7 +182,8 @@ class HugePageConfiguration {
}
} catch (IOException e) {
System.out.println("Failed to read " + file);
mode = THPMode.unknown;
// Happens when the kernel is not built to support THPs.
mode = THPMode.never;
}
return mode;
}
@ -148,19 +195,19 @@ class HugePageConfiguration {
BufferedReader reader = new BufferedReader(fr)) {
String s = reader.readLine();
pagesize = Long.parseLong(s);
} catch (IOException | NumberFormatException e) { /* ignored */ }
} catch (IOException | NumberFormatException e) { } // ignored
return pagesize;
}
// Fill object with info read from proc file system
public static HugePageConfiguration readFromOS() {
public static HugePageConfiguration readFromOS() throws IOException {
return new HugePageConfiguration(readSupportedHugePagesFromOS(),
readDefaultHugePageSizeFromOS(),
readTHPModeFromOS(),
readTHPPageSizeFromOS());
}
private static long parseSIUnit(String num, String unit) {
public static long parseSIUnit(String num, String unit) {
long n = Long.parseLong(num);
return switch (unit) {
case "K" -> n * 1024;
@ -180,7 +227,7 @@ class HugePageConfiguration {
// [0.001s][info][pagesize] Transparent hugepage (THP) support:
// [0.001s][info][pagesize] THP mode: madvise
// [0.001s][info][pagesize] THP pagesize: 2M
TreeSet<Long> hugepages = new TreeSet<>();
TreeSet<StaticHugePageConfig> staticHugePageConfigs = new TreeSet<>();
long defaultHugepageSize = 0;
THPMode thpMode = THPMode.never;
long thpPageSize = 0;
@ -192,7 +239,9 @@ class HugePageConfiguration {
for (String s : lines) {
Matcher mat = patternHugepageSize.matcher(s);
if (mat.matches()) {
hugepages.add(parseSIUnit(mat.group(1), mat.group(2)));
StaticHugePageConfig config = new StaticHugePageConfig();
config.pageSize = parseSIUnit(mat.group(1), mat.group(2));
staticHugePageConfigs.add(config);
continue;
}
if (defaultHugepageSize == 0) {
@ -215,7 +264,7 @@ class HugePageConfiguration {
}
}
return new HugePageConfiguration(hugepages, defaultHugepageSize, thpMode, thpPageSize);
return new HugePageConfiguration(staticHugePageConfigs, defaultHugepageSize, thpMode, thpPageSize);
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 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.
*/
/*
* @test id=Default
* @summary Test JVM large page setup (default options)
* @library /test/lib
* @requires os.family == "linux"
* @modules java.base/jdk.internal.misc
* java.management
* @run driver TestHugePageDecisionsAtVMStartup
*/
/*
* @test id=LP_enabled
* @summary Test JVM large page setup (+LP)
* @library /test/lib
* @requires os.family == "linux"
* @modules java.base/jdk.internal.misc
* java.management
* @run driver TestHugePageDecisionsAtVMStartup -XX:+UseLargePages
*/
/*
* @test id=THP_enabled
* @summary Test JVM large page setup (+THP)
* @library /test/lib
* @requires os.family == "linux"
* @modules java.base/jdk.internal.misc
* java.management
* @run driver TestHugePageDecisionsAtVMStartup -XX:+UseTransparentHugePages
*/
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class TestHugePageDecisionsAtVMStartup {
// End user warnings, printing with Xlog:pagesize at warning level, should be unconditional
static final String warningNoTHP = "[warning][pagesize] UseTransparentHugePages disabled, transparent huge pages are not supported by the operating system.";
static final String warningNoLP = "[warning][pagesize] UseLargePages disabled, no large pages configured and available on the system.";
static final String buildSizeString(long l) {
String units[] = { "K", "M", "G" };
long factor = 1024 * 1024 * 1024;
for (int i = 2; i >= 0; i--) {
if (l >= factor) {
return Long.toString(l / factor) + units[i];
}
factor /= 1024;
}
return Long.toString(l) + "B";
}
static void testOutput(boolean useLP, boolean useTHP, OutputAnalyzer out, HugePageConfiguration configuration) {
// Note: If something goes wrong, the JVM warns but continues, so we should never see an exit value != 0
out.shouldHaveExitValue(0);
// Static hugepages:
// Let X = the default hugepage size of the system (the one in /proc/meminfo).
// The JVM will cycle through page sizes, starting at X, down to the smallest hugepage size.
//
// Example 1: a system with 1GB and 2MB pages, the default hugepage size is 1GB (can only be done
// via kernel parameter). the JVM should first attempt to use 1GB pages, failing that should try 2MB, failing
// that give up and disable -UseLargePages.
//
// Example 1: same system, but the default hugepage size is 2MB. The JVM should not attempt to use 1GB pages.
//
// This picture gets more complex with -XX:LargePageSizeInBytes, which overrides the default
// large page size; but we ignore this for now (feel free to extend the test to cover LBSiB too).
boolean haveUsableStaticHugePages = false;
if (configuration.supportsStaticHugePages()) {
long defaultLargePageSize = configuration.getStaticDefaultHugePageSize();
Set<HugePageConfiguration.StaticHugePageConfig> configs = configuration.getStaticHugePageConfigurations();
for (HugePageConfiguration.StaticHugePageConfig config: configs) {
if (config.pageSize <= defaultLargePageSize) {
if (config.nr_hugepages > 0 || config.nr_overcommit_hugepages > 0) {
haveUsableStaticHugePages = true; break;
}
}
}
}
if (useTHP && !useLP) {
useLP = true; // its implicit
}
if (!useLP) {
out.shouldContain("[info][pagesize] Large page support disabled");
} else if (useLP && !useTHP &&
(!configuration.supportsStaticHugePages() || !haveUsableStaticHugePages)) {
out.shouldContain(warningNoLP);
} else if (useLP && useTHP && !configuration.supportsTHP()) {
out.shouldContain(warningNoTHP);
} else if (useLP && !useTHP &&
configuration.supportsStaticHugePages() && haveUsableStaticHugePages) {
out.shouldContain("[info][pagesize] Using the default large page size: " + buildSizeString(configuration.getStaticDefaultHugePageSize()));
out.shouldContain("[info][pagesize] UseLargePages=1, UseTransparentHugePages=0");
out.shouldContain("[info][pagesize] Large page support enabled");
} else if (useLP && useTHP && configuration.supportsTHP()) {
String thpPageSizeString = buildSizeString(configuration.getThpPageSize());
// We expect to see exactly two "Usable page sizes" : the system page size and the THP page size. The system
// page size differs, but its always in KB).
out.shouldContain("[info][pagesize] UseLargePages=1, UseTransparentHugePages=1");
out.shouldMatch(".*\\[info]\\[pagesize] Large page support enabled. Usable page sizes: \\d+[kK], " + thpPageSizeString + ". Default large page size: " + thpPageSizeString + ".*");
}
}
public static void main(String[] extraOptions) throws Exception {
List<String> allOptions = new ArrayList<String>();
if (extraOptions != null) {
allOptions.addAll(Arrays.asList(extraOptions));
}
allOptions.add("-Xmx128m");
allOptions.add("-Xlog:pagesize");
allOptions.add("-version");
boolean useLP = allOptions.contains("-XX:+UseLargePages");
boolean useTHP = allOptions.contains("-XX:+UseTransparentHugePages");
System.out.println("useLP: " + useLP + " useTHP: " + useTHP);
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(allOptions.toArray(new String[0]));
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.reportDiagnosticSummary();
HugePageConfiguration configuration = HugePageConfiguration.readFromOS();
System.out.println("configuration read from OS:" + configuration);
testOutput(useLP, useTHP, output, configuration);
}
}