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

Reviewed-by: sjohanss, dholmes
  • Loading branch information
tstuefe committed Oct 27, 2023
1 parent 9123961 commit abad040
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);
}
}

3 comments on commit abad040

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

@RealCLanger
Copy link
Contributor

Choose a reason for hiding this comment

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

/backport jdk21u

@openjdk
Copy link

@openjdk openjdk bot commented on abad040 Nov 13, 2023

Choose a reason for hiding this comment

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

@RealCLanger the backport was successfully created on the branch RealCLanger-backport-abad0408 in my personal fork of openjdk/jdk21u. To create a pull request with this backport targeting openjdk/jdk21u:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit abad0408 from the openjdk/jdk repository.

The commit being backported was authored by Thomas Stuefe on 27 Oct 2023 and was reviewed by Stefan Johansson and David Holmes.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk21u:

$ git fetch https://github.com/openjdk-bots/jdk21u.git RealCLanger-backport-abad0408:RealCLanger-backport-abad0408
$ git checkout RealCLanger-backport-abad0408
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk21u.git RealCLanger-backport-abad0408

Please sign in to comment.