diff --git a/test/hotspot/jtreg/runtime/os/HugePageConfiguration.java b/test/hotspot/jtreg/runtime/os/HugePageConfiguration.java index a0590b7739d..f475af4c2de 100644 --- a/test/hotspot/jtreg/runtime/os/HugePageConfiguration.java +++ b/test/hotspot/jtreg/runtime/os/HugePageConfiguration.java @@ -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 _staticHugePageSizes; - long _staticDefaultHugePageSize; + public static class StaticHugePageConfig implements Comparable { + 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 _staticHugePageConfigurations; + long _staticDefaultHugePageSize = -1; + + enum THPMode {always, never, madvise} THPMode _thpMode; long _thpPageSize; - public Set getStaticHugePageSizes() { - return _staticHugePageSizes; + public Set getStaticHugePageConfigurations() { + return _staticHugePageConfigurations; } public long getStaticDefaultHugePageSize() { @@ -55,8 +84,18 @@ class HugePageConfiguration { return _thpPageSize; } - public HugePageConfiguration(Set _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 _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 readSupportedHugePagesFromOS() { - TreeSet pagesizes = new TreeSet<>(); + private static Set readSupportedHugePagesFromOS() throws IOException { + TreeSet 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 hugepages = new TreeSet<>(); + TreeSet 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); } } diff --git a/test/hotspot/jtreg/runtime/os/TestHugePageDecisionsAtVMStartup.java b/test/hotspot/jtreg/runtime/os/TestHugePageDecisionsAtVMStartup.java new file mode 100644 index 00000000000..e93309c0b00 --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/TestHugePageDecisionsAtVMStartup.java @@ -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 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 allOptions = new ArrayList(); + 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); + } +}