1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package gc.g1;
  25 
  26 /**
  27  * @test TestG1NUMATouchRegions
  28  * @summary Ensure the bottom of the given heap regions are properly touched with requested NUMA id.
  29  * @key gc
  30  * @requires vm.gc.G1
  31  * @requires os.family == "linux"
  32  * @library /test/lib
  33  * @modules java.base/jdk.internal.misc
  34  *          java.management
  35  * @build sun.hotspot.WhiteBox
  36  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
  37  * @run main/othervm -XX:+UseG1GC -Xbootclasspath/a:. -XX:+UseNUMA -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.g1.TestG1NUMATouchRegions
  38  */
  39 
  40 import jdk.test.lib.process.OutputAnalyzer;
  41 import jdk.test.lib.process.ProcessTools;
  42 import sun.hotspot.WhiteBox;
  43 
  44 public class TestG1NUMATouchRegions {
  45     enum NUMASupportStatus {
  46         NOT_CHECKED,
  47         SUPPORT,
  48         NOT_SUPPORT
  49     };
  50 
  51     static int G1HeapRegionSize1MB = 1;
  52     static int G1HeapRegionSize8MB = 8;
  53 
  54     static NUMASupportStatus status = NUMASupportStatus.NOT_CHECKED;
  55 
  56     public static void main(String[] args) throws Exception {
  57         // 1. Page size < G1HeapRegionSize
  58         //    Test default page with 1MB heap region size
  59         testMemoryTouch("-XX:-UseLargePages", G1HeapRegionSize1MB);
  60         // 2. Page size > G1HeapRegionSize
  61         //    Test large page with 1MB heap region size.
  62         testMemoryTouch("-XX:+UseLargePages", G1HeapRegionSize1MB);
  63         // 3. Page size < G1HeapRegionSize
  64         //    Test large page with 8MB heap region size.
  65         testMemoryTouch("-XX:+UseLargePages", G1HeapRegionSize8MB);
  66     }
  67 
  68     // On Linux, always UseNUMA is enabled if there is multiple active numa nodes.
  69     static NUMASupportStatus checkNUMAIsEnabled(OutputAnalyzer output) {
  70         boolean supportNUMA = Boolean.parseBoolean(output.firstMatch("\\bUseNUMA\\b.*?=.*?([a-z]+)", 1));
  71         System.out.println("supportNUMA=" + supportNUMA);
  72         return supportNUMA ? NUMASupportStatus.SUPPORT : NUMASupportStatus.NOT_SUPPORT;
  73     }
  74 
  75     static long parseSizeString(String size) {
  76         long multiplier = 1;
  77 
  78         if (size.endsWith("B")) {
  79             multiplier = 1;
  80         } else if (size.endsWith("K")) {
  81             multiplier = 1024;
  82         } else if (size.endsWith("M")) {
  83             multiplier = 1024 * 1024;
  84         } else if (size.endsWith("G")) {
  85             multiplier = 1024 * 1024 * 1024;
  86         } else {
  87             throw new IllegalArgumentException("Expected memory string '" + size + "'to end with either of: B, K, M, G");
  88         }
  89 
  90         long longSize = Long.parseUnsignedLong(size.substring(0, size.length() - 1));
  91 
  92         return longSize * multiplier;
  93     }
  94 
  95     static long heapPageSize(OutputAnalyzer output) {
  96         String HeapPageSizePattern = "Heap:  .*page_size=([^ ]+)";
  97         String str = output.firstMatch(HeapPageSizePattern, 1);
  98 
  99         if (str == null) {
 100             output.reportDiagnosticSummary();
 101             throw new RuntimeException("Match from '" + HeapPageSizePattern + "' got 'null'");
 102         }
 103 
 104         return parseSizeString(str);
 105     }
 106 
 107     // 1. -UseLargePages: default page, page size < G1HeapRegionSize
 108     //    +UseLargePages: large page size <= G1HeapRegionSize
 109     //
 110     //    Each 'int' represents a numa id of single HeapRegion (bottom page).
 111     //    e.g. heap region # : numa id of page
 112     //         0 : 00
 113     //         1 : 01
 114     static void checkCase1Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception {
 115         StringBuilder sb = new StringBuilder();
 116 
 117         // Append index which means heap region index.
 118         sb.append(String.format("%6d", index));
 119         sb.append(" : ");
 120 
 121         // Append page node id.
 122         sb.append(String.format("%02d", memoryNodeIds[index]));
 123 
 124         output.shouldContain(sb.toString());
 125     }
 126 
 127     // 3. +UseLargePages: large page size > G1HeapRegionSize
 128     //
 129     //    As a OS page is consist of multiple heap regions, log also should be
 130     //    printed multiple times for same numa id.
 131     //    e.g. 1MB heap region, 2MB page size
 132     //         heap region # (page #): numa id of page
 133     //         0 (0): 00
 134     //         1 (0): 00
 135     //         2 (1): 01
 136     //         3 (1): 01
 137     static void checkCase2Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception {
 138         StringBuilder sb = new StringBuilder();
 139 
 140         // Append page range.
 141         int lines_to_print = (int)(actualPageSize / g1HeapRegionSize);
 142         for (int i = 0; i < lines_to_print; i++) {
 143             // Append index which means heap region index.
 144             sb.append(String.format("%6d", index * lines_to_print + i));
 145             sb.append(" : ");
 146 
 147             // Append page node id.
 148             sb.append(String.format("%02d", memoryNodeIds[index]));
 149 
 150             output.shouldContain(sb.toString());
 151             sb.setLength(0);
 152         }
 153 
 154         output.shouldContain(sb.toString());
 155     }
 156 
 157     static void checkNUMALog(OutputAnalyzer output, int regionSizeInMB) throws Exception {
 158         WhiteBox wb = WhiteBox.getWhiteBox();
 159         long g1HeapRegionSize = regionSizeInMB * 1024 * 1024;
 160         long actualPageSize = heapPageSize(output);
 161         long defaultPageSize = (long)wb.getVMPageSize();
 162         int memoryNodeCount = wb.g1ActiveMemoryNodeCount();
 163         int[] memoryNodeIds = wb.g1MemoryNodeIds();
 164 
 165         System.out.println("node count=" + memoryNodeCount + ", actualPageSize=" + actualPageSize);
 166         // Check for the first one set of active numa nodes.
 167         for (int index = 0; index < memoryNodeCount; index++) {
 168             if (actualPageSize <= defaultPageSize) {
 169                 checkCase1Pattern(output, index, g1HeapRegionSize, actualPageSize, memoryNodeIds);
 170             } else {
 171                 checkCase2Pattern(output, index, g1HeapRegionSize, actualPageSize, memoryNodeIds);
 172             }
 173         }
 174     }
 175 
 176     static void testMemoryTouch(String largePagesSetting, int regionSizeInMB) throws Exception {
 177         // Skip testing with message.
 178         if (status == NUMASupportStatus.NOT_SUPPORT) {
 179             System.out.println("NUMA is not supported");
 180             return;
 181         }
 182 
 183         ProcessBuilder pb_enabled = ProcessTools.createJavaProcessBuilder(
 184                                               "-Xbootclasspath/a:.",
 185                                               "-Xlog:gc+heap+numa=trace,pagesize",
 186                                               "-XX:+UseG1GC",
 187                                               "-Xmx128m",
 188                                               "-Xms128m",
 189                                               "-XX:+UnlockDiagnosticVMOptions",
 190                                               "-XX:+WhiteBoxAPI",
 191                                               "-XX:+PrintFlagsFinal",
 192                                               "-XX:+UseNUMA",
 193                                               "-XX:+AlwaysPreTouch",
 194                                               largePagesSetting,
 195                                               "-XX:G1HeapRegionSize=" + regionSizeInMB + "m",
 196                                               "--version");
 197         OutputAnalyzer output = new OutputAnalyzer(pb_enabled.start());
 198 
 199         // Check NUMA availability.
 200         if (status == NUMASupportStatus.NOT_CHECKED) {
 201             status = checkNUMAIsEnabled(output);
 202         }
 203 
 204         if (status == NUMASupportStatus.SUPPORT) {
 205             checkNUMALog(output, regionSizeInMB);
 206         } else {
 207             // Exit with message for the first test.
 208             System.out.println("NUMA is not supported");
 209         }
 210     }
 211 }