Skip to content

Commit abad040

Browse files
committed
8313781: Add regression tests for large page logging and user-facing error messages
Reviewed-by: sjohanss, dholmes
1 parent 9123961 commit abad040

File tree

2 files changed

+235
-29
lines changed

2 files changed

+235
-29
lines changed

test/hotspot/jtreg/runtime/os/HugePageConfiguration.java

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,46 @@
3030
import java.util.regex.Matcher;
3131
import java.util.regex.Pattern;
3232

33+
// This class allows us to parse system hugepage config from
34+
// - a) the Operating System (the truth)
35+
// - b) the JVM log (-Xlog:pagesize)
36+
// This is used e.g. in TestHugePageDetection to determine if the JVM detects the correct settings from the OS.
3337
class HugePageConfiguration {
3438

35-
Set<Long> _staticHugePageSizes;
36-
long _staticDefaultHugePageSize;
39+
public static class StaticHugePageConfig implements Comparable<StaticHugePageConfig> {
40+
public long pageSize = -1;
41+
public long nr_hugepages = -1;
42+
public long nr_overcommit_hugepages = -1;
3743

38-
enum THPMode {always, never, madvise, unknown}
44+
@Override
45+
public int hashCode() {
46+
return Objects.hash(pageSize);
47+
}
48+
49+
@Override
50+
public String toString() {
51+
return "StaticHugePageConfig{" +
52+
"pageSize=" + pageSize +
53+
", nr_hugepages=" + nr_hugepages +
54+
", nr_overcommit_hugepages=" + nr_overcommit_hugepages +
55+
'}';
56+
}
57+
58+
@Override
59+
public int compareTo(StaticHugePageConfig o) {
60+
return (int) (pageSize - o.pageSize);
61+
}
62+
}
63+
64+
Set<StaticHugePageConfig> _staticHugePageConfigurations;
65+
long _staticDefaultHugePageSize = -1;
66+
67+
enum THPMode {always, never, madvise}
3968
THPMode _thpMode;
4069
long _thpPageSize;
4170

42-
public Set<Long> getStaticHugePageSizes() {
43-
return _staticHugePageSizes;
71+
public Set<StaticHugePageConfig> getStaticHugePageConfigurations() {
72+
return _staticHugePageConfigurations;
4473
}
4574

4675
public long getStaticDefaultHugePageSize() {
@@ -55,8 +84,18 @@ public long getThpPageSize() {
5584
return _thpPageSize;
5685
}
5786

58-
public HugePageConfiguration(Set<Long> _staticHugePageSizes, long _staticDefaultHugePageSize, THPMode _thpMode, long _thpPageSize) {
59-
this._staticHugePageSizes = _staticHugePageSizes;
87+
// Returns true if the THP support is enabled
88+
public boolean supportsTHP() {
89+
return _thpMode == THPMode.always || _thpMode == THPMode.madvise;
90+
}
91+
92+
// Returns true if static huge pages are supported (whether or not we have configured the pools)
93+
public boolean supportsStaticHugePages() {
94+
return _staticDefaultHugePageSize > 0 && _staticHugePageConfigurations.size() > 0;
95+
}
96+
97+
public HugePageConfiguration(Set<StaticHugePageConfig> _staticHugePageConfigurations, long _staticDefaultHugePageSize, THPMode _thpMode, long _thpPageSize) {
98+
this._staticHugePageConfigurations = _staticHugePageConfigurations;
6099
this._staticDefaultHugePageSize = _staticDefaultHugePageSize;
61100
this._thpMode = _thpMode;
62101
this._thpPageSize = _thpPageSize;
@@ -65,7 +104,7 @@ public HugePageConfiguration(Set<Long> _staticHugePageSizes, long _staticDefault
65104
@Override
66105
public String toString() {
67106
return "Configuration{" +
68-
"_staticHugePageSizes=" + _staticHugePageSizes +
107+
"_staticHugePageConfigurations=" + _staticHugePageConfigurations +
69108
", _staticDefaultHugePageSize=" + _staticDefaultHugePageSize +
70109
", _thpMode=" + _thpMode +
71110
", _thpPageSize=" + _thpPageSize +
@@ -77,12 +116,8 @@ public boolean equals(Object o) {
77116
if (this == o) return true;
78117
if (o == null || getClass() != o.getClass()) return false;
79118
HugePageConfiguration that = (HugePageConfiguration) o;
80-
return _staticDefaultHugePageSize == that._staticDefaultHugePageSize && _thpPageSize == that._thpPageSize && Objects.equals(_staticHugePageSizes, that._staticHugePageSizes) && _thpMode == that._thpMode;
81-
}
82-
83-
@Override
84-
public int hashCode() {
85-
return Objects.hash(_staticHugePageSizes, _staticDefaultHugePageSize, _thpMode, _thpPageSize);
119+
return _staticDefaultHugePageSize == that._staticDefaultHugePageSize && _thpPageSize == that._thpPageSize &&
120+
Objects.equals(_staticHugePageConfigurations, that._staticHugePageConfigurations) && _thpMode == that._thpMode;
86121
}
87122

88123
private static long readDefaultHugePageSizeFromOS() {
@@ -102,25 +137,36 @@ private static long readDefaultHugePageSizeFromOS() {
102137
return 0;
103138
}
104139

105-
private static Set<Long> readSupportedHugePagesFromOS() {
106-
TreeSet<Long> pagesizes = new TreeSet<>();
140+
private static Set<StaticHugePageConfig> readSupportedHugePagesFromOS() throws IOException {
141+
TreeSet<StaticHugePageConfig> hugePageConfigs = new TreeSet<>();
107142
Pattern pat = Pattern.compile("hugepages-(\\d+)kB");
108143
File[] subdirs = new File("/sys/kernel/mm/hugepages").listFiles();
109144
if (subdirs != null) {
110-
for (File f : subdirs) {
111-
String name = f.getName();
145+
for (File subdir : subdirs) {
146+
String name = subdir.getName();
112147
Matcher mat = pat.matcher(name);
113148
if (mat.matches()) {
114-
long pagesize = Long.parseLong(mat.group(1)) * 1024;
115-
pagesizes.add(pagesize);
149+
StaticHugePageConfig config = new StaticHugePageConfig();
150+
config.pageSize = Long.parseLong(mat.group(1)) * 1024;
151+
try (FileReader fr = new FileReader(subdir.getAbsolutePath() + "/nr_hugepages");
152+
BufferedReader reader = new BufferedReader(fr)) {
153+
String s = reader.readLine();
154+
config.nr_hugepages = Long.parseLong(s);
155+
}
156+
try (FileReader fr = new FileReader(subdir.getAbsolutePath() + "/nr_overcommit_hugepages");
157+
BufferedReader reader = new BufferedReader(fr)) {
158+
String s = reader.readLine();
159+
config.nr_overcommit_hugepages = Long.parseLong(s);
160+
}
161+
hugePageConfigs.add(config);
116162
}
117163
}
118164
}
119-
return pagesizes;
165+
return hugePageConfigs;
120166
}
121167

122168
private static THPMode readTHPModeFromOS() {
123-
THPMode mode = THPMode.unknown;
169+
THPMode mode = THPMode.never;
124170
String file = "/sys/kernel/mm/transparent_hugepage/enabled";
125171
try (FileReader fr = new FileReader(file);
126172
BufferedReader reader = new BufferedReader(fr)) {
@@ -136,7 +182,8 @@ private static THPMode readTHPModeFromOS() {
136182
}
137183
} catch (IOException e) {
138184
System.out.println("Failed to read " + file);
139-
mode = THPMode.unknown;
185+
// Happens when the kernel is not built to support THPs.
186+
mode = THPMode.never;
140187
}
141188
return mode;
142189
}
@@ -148,19 +195,19 @@ private static long readTHPPageSizeFromOS() {
148195
BufferedReader reader = new BufferedReader(fr)) {
149196
String s = reader.readLine();
150197
pagesize = Long.parseLong(s);
151-
} catch (IOException | NumberFormatException e) { /* ignored */ }
198+
} catch (IOException | NumberFormatException e) { } // ignored
152199
return pagesize;
153200
}
154201

155202
// Fill object with info read from proc file system
156-
public static HugePageConfiguration readFromOS() {
203+
public static HugePageConfiguration readFromOS() throws IOException {
157204
return new HugePageConfiguration(readSupportedHugePagesFromOS(),
158205
readDefaultHugePageSizeFromOS(),
159206
readTHPModeFromOS(),
160207
readTHPPageSizeFromOS());
161208
}
162209

163-
private static long parseSIUnit(String num, String unit) {
210+
public static long parseSIUnit(String num, String unit) {
164211
long n = Long.parseLong(num);
165212
return switch (unit) {
166213
case "K" -> n * 1024;
@@ -180,7 +227,7 @@ public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
180227
// [0.001s][info][pagesize] Transparent hugepage (THP) support:
181228
// [0.001s][info][pagesize] THP mode: madvise
182229
// [0.001s][info][pagesize] THP pagesize: 2M
183-
TreeSet<Long> hugepages = new TreeSet<>();
230+
TreeSet<StaticHugePageConfig> staticHugePageConfigs = new TreeSet<>();
184231
long defaultHugepageSize = 0;
185232
THPMode thpMode = THPMode.never;
186233
long thpPageSize = 0;
@@ -192,7 +239,9 @@ public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
192239
for (String s : lines) {
193240
Matcher mat = patternHugepageSize.matcher(s);
194241
if (mat.matches()) {
195-
hugepages.add(parseSIUnit(mat.group(1), mat.group(2)));
242+
StaticHugePageConfig config = new StaticHugePageConfig();
243+
config.pageSize = parseSIUnit(mat.group(1), mat.group(2));
244+
staticHugePageConfigs.add(config);
196245
continue;
197246
}
198247
if (defaultHugepageSize == 0) {
@@ -215,7 +264,7 @@ public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
215264
}
216265
}
217266

218-
return new HugePageConfiguration(hugepages, defaultHugepageSize, thpMode, thpPageSize);
267+
return new HugePageConfiguration(staticHugePageConfigs, defaultHugepageSize, thpMode, thpPageSize);
219268
}
220269

221270
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2023, Red Hat Inc.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21+
* or visit www.oracle.com if you need additional information or have any
22+
* questions.
23+
*/
24+
25+
/*
26+
* @test id=Default
27+
* @summary Test JVM large page setup (default options)
28+
* @library /test/lib
29+
* @requires os.family == "linux"
30+
* @modules java.base/jdk.internal.misc
31+
* java.management
32+
* @run driver TestHugePageDecisionsAtVMStartup
33+
*/
34+
35+
/*
36+
* @test id=LP_enabled
37+
* @summary Test JVM large page setup (+LP)
38+
* @library /test/lib
39+
* @requires os.family == "linux"
40+
* @modules java.base/jdk.internal.misc
41+
* java.management
42+
* @run driver TestHugePageDecisionsAtVMStartup -XX:+UseLargePages
43+
*/
44+
45+
/*
46+
* @test id=THP_enabled
47+
* @summary Test JVM large page setup (+THP)
48+
* @library /test/lib
49+
* @requires os.family == "linux"
50+
* @modules java.base/jdk.internal.misc
51+
* java.management
52+
* @run driver TestHugePageDecisionsAtVMStartup -XX:+UseTransparentHugePages
53+
*/
54+
55+
import jdk.test.lib.process.OutputAnalyzer;
56+
import jdk.test.lib.process.ProcessTools;
57+
import java.util.ArrayList;
58+
import java.util.Arrays;
59+
import java.util.List;
60+
import java.util.Set;
61+
62+
public class TestHugePageDecisionsAtVMStartup {
63+
64+
// End user warnings, printing with Xlog:pagesize at warning level, should be unconditional
65+
static final String warningNoTHP = "[warning][pagesize] UseTransparentHugePages disabled, transparent huge pages are not supported by the operating system.";
66+
static final String warningNoLP = "[warning][pagesize] UseLargePages disabled, no large pages configured and available on the system.";
67+
68+
static final String buildSizeString(long l) {
69+
String units[] = { "K", "M", "G" };
70+
long factor = 1024 * 1024 * 1024;
71+
for (int i = 2; i >= 0; i--) {
72+
if (l >= factor) {
73+
return Long.toString(l / factor) + units[i];
74+
}
75+
factor /= 1024;
76+
}
77+
return Long.toString(l) + "B";
78+
}
79+
80+
static void testOutput(boolean useLP, boolean useTHP, OutputAnalyzer out, HugePageConfiguration configuration) {
81+
82+
// Note: If something goes wrong, the JVM warns but continues, so we should never see an exit value != 0
83+
out.shouldHaveExitValue(0);
84+
85+
// Static hugepages:
86+
// Let X = the default hugepage size of the system (the one in /proc/meminfo).
87+
// The JVM will cycle through page sizes, starting at X, down to the smallest hugepage size.
88+
//
89+
// Example 1: a system with 1GB and 2MB pages, the default hugepage size is 1GB (can only be done
90+
// via kernel parameter). the JVM should first attempt to use 1GB pages, failing that should try 2MB, failing
91+
// that give up and disable -UseLargePages.
92+
//
93+
// Example 1: same system, but the default hugepage size is 2MB. The JVM should not attempt to use 1GB pages.
94+
//
95+
// This picture gets more complex with -XX:LargePageSizeInBytes, which overrides the default
96+
// large page size; but we ignore this for now (feel free to extend the test to cover LBSiB too).
97+
98+
boolean haveUsableStaticHugePages = false;
99+
if (configuration.supportsStaticHugePages()) {
100+
long defaultLargePageSize = configuration.getStaticDefaultHugePageSize();
101+
Set<HugePageConfiguration.StaticHugePageConfig> configs = configuration.getStaticHugePageConfigurations();
102+
for (HugePageConfiguration.StaticHugePageConfig config: configs) {
103+
if (config.pageSize <= defaultLargePageSize) {
104+
if (config.nr_hugepages > 0 || config.nr_overcommit_hugepages > 0) {
105+
haveUsableStaticHugePages = true; break;
106+
}
107+
}
108+
}
109+
}
110+
111+
if (useTHP && !useLP) {
112+
useLP = true; // its implicit
113+
}
114+
115+
if (!useLP) {
116+
out.shouldContain("[info][pagesize] Large page support disabled");
117+
} else if (useLP && !useTHP &&
118+
(!configuration.supportsStaticHugePages() || !haveUsableStaticHugePages)) {
119+
out.shouldContain(warningNoLP);
120+
} else if (useLP && useTHP && !configuration.supportsTHP()) {
121+
out.shouldContain(warningNoTHP);
122+
} else if (useLP && !useTHP &&
123+
configuration.supportsStaticHugePages() && haveUsableStaticHugePages) {
124+
out.shouldContain("[info][pagesize] Using the default large page size: " + buildSizeString(configuration.getStaticDefaultHugePageSize()));
125+
out.shouldContain("[info][pagesize] UseLargePages=1, UseTransparentHugePages=0");
126+
out.shouldContain("[info][pagesize] Large page support enabled");
127+
} else if (useLP && useTHP && configuration.supportsTHP()) {
128+
String thpPageSizeString = buildSizeString(configuration.getThpPageSize());
129+
// We expect to see exactly two "Usable page sizes" : the system page size and the THP page size. The system
130+
// page size differs, but its always in KB).
131+
out.shouldContain("[info][pagesize] UseLargePages=1, UseTransparentHugePages=1");
132+
out.shouldMatch(".*\\[info]\\[pagesize] Large page support enabled. Usable page sizes: \\d+[kK], " + thpPageSizeString + ". Default large page size: " + thpPageSizeString + ".*");
133+
}
134+
}
135+
136+
public static void main(String[] extraOptions) throws Exception {
137+
List<String> allOptions = new ArrayList<String>();
138+
if (extraOptions != null) {
139+
allOptions.addAll(Arrays.asList(extraOptions));
140+
}
141+
allOptions.add("-Xmx128m");
142+
allOptions.add("-Xlog:pagesize");
143+
allOptions.add("-version");
144+
145+
boolean useLP = allOptions.contains("-XX:+UseLargePages");
146+
boolean useTHP = allOptions.contains("-XX:+UseTransparentHugePages");
147+
System.out.println("useLP: " + useLP + " useTHP: " + useTHP);
148+
149+
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(allOptions.toArray(new String[0]));
150+
OutputAnalyzer output = new OutputAnalyzer(pb.start());
151+
output.reportDiagnosticSummary();
152+
HugePageConfiguration configuration = HugePageConfiguration.readFromOS();
153+
System.out.println("configuration read from OS:" + configuration);
154+
155+
testOutput(useLP, useTHP, output, configuration);
156+
}
157+
}

0 commit comments

Comments
 (0)