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 }