1 /*
2 * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
3 * Copyright (c) 2023, 2024, 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 package jdk.test.lib.os.linux;
26
27 import jdk.test.lib.process.OutputAnalyzer;
28
29 import java.io.*;
30 import java.util.*;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 // This class allows us to parse system hugepage config from
35 // - a) the Operating System (the truth)
36 // - b) the JVM log (-Xlog:pagesize)
37 // This is used e.g. in TestHugePageDetection to determine if the JVM detects the correct settings from the OS.
38 public class HugePageConfiguration {
39
40 public static class ExplicitHugePageConfig implements Comparable<ExplicitHugePageConfig> {
41 public long pageSize = -1;
42 public long nr_hugepages = -1;
43 public long nr_overcommit_hugepages = -1;
44
45 @Override
46 public int hashCode() {
47 return Objects.hash(pageSize);
48 }
49
50 @Override
51 public String toString() {
52 return "ExplicitHugePageConfig{" +
53 "pageSize=" + pageSize +
54 ", nr_hugepages=" + nr_hugepages +
55 ", nr_overcommit_hugepages=" + nr_overcommit_hugepages +
56 '}';
57 }
58
59 @Override
60 public int compareTo(ExplicitHugePageConfig o) {
61 return (int) (pageSize - o.pageSize);
62 }
63 }
64
65 Set<ExplicitHugePageConfig> _explicitHugePageConfigurations;
66 long _explicitDefaultHugePageSize = -1;
67 long _explicitAvailableHugePageNumber = -1;
68
69 public enum THPMode {always, never, madvise}
70 THPMode _thpMode;
71 long _thpPageSize;
72
73 public enum ShmemTHPMode {always, within_size, advise, never, deny, force, unknown}
74 ShmemTHPMode _shmemThpMode;
75
76 public Set<ExplicitHugePageConfig> getExplicitHugePageConfigurations() {
77 return _explicitHugePageConfigurations;
78 }
79
80 public long getExplicitDefaultHugePageSize() {
81 return _explicitDefaultHugePageSize;
82 }
83
84 public long getExplicitAvailableHugePageNumber() {
85 return _explicitAvailableHugePageNumber;
86 }
87
88 public THPMode getThpMode() {
89 return _thpMode;
90 }
91
92 public long getThpPageSize() {
93 return _thpPageSize;
94 }
95
96 // Returns the THP page size (if exposed by the kernel) or a guessed THP page size.
97 // Mimics HugePages::thp_pagesize_fallback() method in hotspot (must be kept in sync with it).
98 public long getThpPageSizeOrFallback() {
99 long pageSize = getThpPageSize();
100 if (pageSize != 0) {
101 return pageSize;
102 }
103 pageSize = getExplicitDefaultHugePageSize();
104 if (pageSize != 0) {
105 return Math.min(pageSize, 16 * 1024 * 1024);
106 }
107 return 2 * 1024 * 1024;
108 }
109
110 // Returns true if the THP support is enabled
111 public boolean supportsTHP() {
112 return _thpMode == THPMode.always || _thpMode == THPMode.madvise;
113 }
114
115 public ShmemTHPMode getShmemThpMode() {
116 return _shmemThpMode;
117 }
118
119 // Returns true if explicit huge pages are supported (whether or not we have configured the pools)
120 public boolean supportsExplicitHugePages() {
121 return _explicitDefaultHugePageSize > 0 && _explicitHugePageConfigurations.size() > 0;
122 }
123
124 public HugePageConfiguration(Set<ExplicitHugePageConfig> explicitHugePageConfigurations, long explicitDefaultHugePageSize, long explicitAvailableHugePageNumber, THPMode _thpMode, long _thpPageSize, ShmemTHPMode _shmemThpMode) {
125 this._explicitHugePageConfigurations = explicitHugePageConfigurations;
126 this._explicitDefaultHugePageSize = explicitDefaultHugePageSize;
127 this._explicitAvailableHugePageNumber = explicitAvailableHugePageNumber;
128 this._thpMode = _thpMode;
129 this._thpPageSize = _thpPageSize;
130 this._shmemThpMode = _shmemThpMode;
131 }
132
133 @Override
134 public String toString() {
135 return "Configuration{" +
136 "_explicitHugePageConfigurations=" + _explicitHugePageConfigurations +
137 ", _explicitDefaultHugePageSize=" + _explicitDefaultHugePageSize +
138 ", _explicitAvailableHugePageNumber=" + _explicitAvailableHugePageNumber +
139 ", _thpMode=" + _thpMode +
140 ", _thpPageSize=" + _thpPageSize +
141 ", _shmemThpMode=" + _shmemThpMode +
142 '}';
143 }
144
145 @Override
146 public int hashCode() {
147 return Objects.hash(_explicitDefaultHugePageSize,
148 _explicitAvailableHugePageNumber,
149 _thpPageSize,
150 _explicitHugePageConfigurations,
151 _thpMode,
152 _shmemThpMode);
153 }
154
155 @Override
156 public boolean equals(Object o) {
157 if (this == o) return true;
158 if (o == null || getClass() != o.getClass()) return false;
159 HugePageConfiguration that = (HugePageConfiguration) o;
160 // _explicitAvailableHugePageNumber is not compared here, because there is no direct counterpart on the JVM-side log.
161 return _explicitDefaultHugePageSize == that._explicitDefaultHugePageSize && _thpPageSize == that._thpPageSize &&
162 Objects.equals(_explicitHugePageConfigurations, that._explicitHugePageConfigurations) && _thpMode == that._thpMode &&
163 _shmemThpMode == that._shmemThpMode;
164 }
165
166 private static long readDefaultHugePageSizeFromOS() {
167 Pattern pat = Pattern.compile("Hugepagesize: *(\\d+) +kB");
168 try (Scanner scanner = new Scanner(new File("/proc/meminfo"))) {
169 while (scanner.hasNextLine()) {
170 Matcher mat = pat.matcher(scanner.nextLine());
171 if (mat.matches()) {
172 return Long.parseLong(mat.group(1)) * 1024;
173 }
174 }
175 } catch (FileNotFoundException e) {
176 System.out.println("Could not open /proc/meminfo");
177 }
178 return 0;
179 }
180
181 private static long readAvailableHugePageNumberFromOS() {
182 Pattern pat = Pattern.compile("HugePages_Free: *(\\d+)$");
183 try (Scanner scanner = new Scanner(new File("/proc/meminfo"))) {
184 while (scanner.hasNextLine()) {
185 Matcher mat = pat.matcher(scanner.nextLine());
186 if (mat.matches()) {
187 return Long.parseLong(mat.group(1));
188 }
189 }
190 } catch (FileNotFoundException e) {
191 System.out.println("Could not open /proc/meminfo");
192 }
193 return 0;
194 }
195
196 private static Set<ExplicitHugePageConfig> readSupportedHugePagesFromOS() throws IOException {
197 TreeSet<ExplicitHugePageConfig> hugePageConfigs = new TreeSet<>();
198 Pattern pat = Pattern.compile("hugepages-(\\d+)kB");
199 File[] subdirs = new File("/sys/kernel/mm/hugepages").listFiles();
200 if (subdirs != null) {
201 for (File subdir : subdirs) {
202 String name = subdir.getName();
203 Matcher mat = pat.matcher(name);
204 if (mat.matches()) {
205 ExplicitHugePageConfig config = new ExplicitHugePageConfig();
206 config.pageSize = Long.parseLong(mat.group(1)) * 1024;
207 try (FileReader fr = new FileReader(subdir.getAbsolutePath() + "/nr_hugepages");
208 BufferedReader reader = new BufferedReader(fr)) {
209 String s = reader.readLine();
210 config.nr_hugepages = Long.parseLong(s);
211 }
212 try (FileReader fr = new FileReader(subdir.getAbsolutePath() + "/nr_overcommit_hugepages");
213 BufferedReader reader = new BufferedReader(fr)) {
214 String s = reader.readLine();
215 config.nr_overcommit_hugepages = Long.parseLong(s);
216 }
217 hugePageConfigs.add(config);
218 }
219 }
220 }
221 return hugePageConfigs;
222 }
223
224 private static THPMode readTHPModeFromOS() {
225 THPMode mode = THPMode.never;
226 String file = "/sys/kernel/mm/transparent_hugepage/enabled";
227 try (FileReader fr = new FileReader(file);
228 BufferedReader reader = new BufferedReader(fr)) {
229 String s = reader.readLine();
230 if (s.contains("[never]")) {
231 mode = THPMode.never;
232 } else if (s.contains("[always]")) {
233 mode = THPMode.always;
234 } else if (s.contains("[madvise]")) {
235 mode = THPMode.madvise;
236 } else {
237 throw new RuntimeException("Unexpected content of " + file + ": " + s);
238 }
239 } catch (IOException e) {
240 System.out.println("Failed to read " + file);
241 // Happens when the kernel is not built to support THPs.
242 mode = THPMode.never;
243 }
244 return mode;
245 }
246
247 private static long readTHPPageSizeFromOS() {
248 long pagesize = 0;
249 String file = "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size";
250 try (FileReader fr = new FileReader(file);
251 BufferedReader reader = new BufferedReader(fr)) {
252 String s = reader.readLine();
253 pagesize = Long.parseLong(s);
254 } catch (IOException | NumberFormatException e) { } // ignored
255 return pagesize;
256 }
257
258 private static ShmemTHPMode readShmemTHPModeFromOS() {
259 ShmemTHPMode mode = ShmemTHPMode.unknown;
260 String file = "/sys/kernel/mm/transparent_hugepage/shmem_enabled";
261 try (FileReader fr = new FileReader(file);
262 BufferedReader reader = new BufferedReader(fr)) {
263 String s = reader.readLine();
264 if (s.contains("[always]")) {
265 mode = ShmemTHPMode.always;
266 } else if (s.contains("[within_size]")) {
267 mode = ShmemTHPMode.within_size;
268 } else if (s.contains("[advise]")) {
269 mode = ShmemTHPMode.advise;
270 } else if (s.contains("[never]")) {
271 mode = ShmemTHPMode.never;
272 } else if (s.contains("[deny]")) {
273 mode = ShmemTHPMode.deny;
274 } else if (s.contains("[force]")) {
275 mode = ShmemTHPMode.force;
276 } else {
277 throw new RuntimeException("Unexpected content of " + file + ": " + s);
278 }
279 } catch (IOException e) {
280 System.out.println("Failed to read " + file);
281 // Happens when the kernel is not built to support THPs.
282 }
283 return mode;
284 }
285
286 // Fill object with info read from proc file system
287 public static HugePageConfiguration readFromOS() throws IOException {
288 return new HugePageConfiguration(readSupportedHugePagesFromOS(),
289 readDefaultHugePageSizeFromOS(),
290 readAvailableHugePageNumberFromOS(),
291 readTHPModeFromOS(),
292 readTHPPageSizeFromOS(),
293 readShmemTHPModeFromOS());
294 }
295
296 public static long parseSIUnit(String num, String unit) {
297 long n = Long.parseLong(num);
298 return switch (unit) {
299 case "K" -> n * 1024;
300 case "M" -> n * 1024 * 1024;
301 case "G" -> n * 1024 * 1024 * 1024;
302 default -> throw new RuntimeException("Invalid unit " + unit);
303 };
304 }
305
306 public static HugePageConfiguration readFromJVMLog(OutputAnalyzer output) {
307 // Expects output from -Xlog:pagesize
308 // Example:
309 // [0.001s][info][pagesize] Explicit hugepage support:
310 // [0.001s][info][pagesize] hugepage size: 2M
311 // [0.001s][info][pagesize] hugepage size: 1G
312 // [0.001s][info][pagesize] default hugepage size: 2M
313 // [0.001s][info][pagesize] Transparent hugepage (THP) support:
314 // [0.001s][info][pagesize] THP mode: madvise
315 // [0.001s][info][pagesize] THP pagesize: 2M
316 // [0.001s][info][pagesize] Shared memory transparent hugepage (THP) support:
317 // [0.001s][info][pagesize] Shared memory THP mode: always
318 TreeSet<ExplicitHugePageConfig> explicitHugePageConfigs = new TreeSet<>();
319 long defaultHugepageSize = 0;
320 THPMode thpMode = THPMode.never;
321 ShmemTHPMode shmemThpMode = ShmemTHPMode.unknown;
322 long thpPageSize = 0;
323 Pattern patternHugepageSize = Pattern.compile(".*\\[pagesize] *hugepage size: (\\d+)([KMG])");
324 Pattern patternDefaultHugepageSize = Pattern.compile(".*\\[pagesize] *default hugepage size: (\\d+)([KMG]) *");
325 Pattern patternTHPPageSize = Pattern.compile(".*\\[pagesize] * THP pagesize: (\\d+)([KMG])");
326 Pattern patternTHPMode = Pattern.compile(".*\\[pagesize] * THP mode: (\\S+)");
327 Pattern patternShmemTHPMode = Pattern.compile(".*\\[pagesize] *Shared memory THP mode: (\\S+)");
328 List<String> lines = output.asLines();
329 for (String s : lines) {
330 Matcher mat = patternHugepageSize.matcher(s);
331 if (mat.matches()) {
332 ExplicitHugePageConfig config = new ExplicitHugePageConfig();
333 config.pageSize = parseSIUnit(mat.group(1), mat.group(2));
334 explicitHugePageConfigs.add(config);
335 continue;
336 }
337 if (defaultHugepageSize == 0) {
338 mat = patternDefaultHugepageSize.matcher(s);
339 if (mat.matches()) {
340 defaultHugepageSize = parseSIUnit(mat.group(1), mat.group(2));
341 continue;
342 }
343 }
344 if (thpPageSize == 0) {
345 mat = patternTHPPageSize.matcher(s);
346 if (mat.matches()) {
347 thpPageSize = parseSIUnit(mat.group(1), mat.group(2));
348 continue;
349 }
350 }
351 mat = patternTHPMode.matcher(s);
352 if (mat.matches()) {
353 thpMode = THPMode.valueOf(mat.group(1));
354 }
355 mat = patternShmemTHPMode.matcher(s);
356 if (mat.matches()) {
357 shmemThpMode = ShmemTHPMode.valueOf(mat.group(1));
358 }
359 }
360
361 return new HugePageConfiguration(explicitHugePageConfigs, defaultHugepageSize, -1, thpMode, thpPageSize, shmemThpMode);
362 }
363
364 }