Skip to content

Commit

Permalink
8313781: Add regression tests for large page logging and user-facing …
Browse files Browse the repository at this point in the history
…error messages

Backport-of: 99d260c0ccadea60a48dc8c933972615bcd86b65
  • Loading branch information
RealCLanger committed Nov 23, 2023
1 parent 5531ca5 commit 1d7bc8f
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 29 deletions.
107 changes: 78 additions & 29 deletions test/hotspot/jtreg/runtime/os/HugePageConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,46 @@
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() {
Expand All @@ -55,8 +84,18 @@ public long getThpPageSize() {
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;
Expand All @@ -65,7 +104,7 @@ public HugePageConfiguration(Set<Long> _staticHugePageSizes, long _staticDefault
@Override
public String toString() {
return "Configuration{" +
"_staticHugePageSizes=" + _staticHugePageSizes +
"_staticHugePageConfigurations=" + _staticHugePageConfigurations +
", _staticDefaultHugePageSize=" + _staticDefaultHugePageSize +
", _thpMode=" + _thpMode +
", _thpPageSize=" + _thpPageSize +
Expand All @@ -77,12 +116,8 @@ public boolean equals(Object o) {
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() {
Expand All @@ -102,25 +137,36 @@ private static long readDefaultHugePageSizeFromOS() {
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)) {
Expand All @@ -136,7 +182,8 @@ private static THPMode readTHPModeFromOS() {
}
} 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;
}
Expand All @@ -148,19 +195,19 @@ private static long readTHPPageSizeFromOS() {
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;
Expand All @@ -180,7 +227,7 @@ public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
// [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;
Expand All @@ -192,7 +239,9 @@ public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
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) {
Expand All @@ -215,7 +264,7 @@ public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
}
}

return new HugePageConfiguration(hugepages, defaultHugepageSize, thpMode, thpPageSize);
return new HugePageConfiguration(staticHugePageConfigs, defaultHugepageSize, thpMode, thpPageSize);
}

}
157 changes: 157 additions & 0 deletions test/hotspot/jtreg/runtime/os/TestHugePageDecisionsAtVMStartup.java
Original file line number Diff line number Diff line change
@@ -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);
}
}

1 comment on commit 1d7bc8f

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.