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 java.util.LinkedList;
  41 import jdk.test.lib.process.OutputAnalyzer;
  42 import jdk.test.lib.process.ProcessTools;
  43 import sun.hotspot.WhiteBox;
  44 
  45 public class TestG1NUMATouchRegions {
  46     enum NUMASupportStatus {
  47         NOT_CHECKED,
  48         SUPPORT,
  49         NOT_SUPPORT
  50     };
  51 
  52     static int G1HeapRegionSize1MB = 1;
  53     static int G1HeapRegionSize8MB = 8;
  54 
  55     static NUMASupportStatus status = NUMASupportStatus.NOT_CHECKED;
  56 
  57     public static void main(String[] args) throws Exception {
  58         // 1. Page size < G1HeapRegionSize
  59         //    Test default page with 1MB heap region size
  60         testMemoryTouch("-XX:-UseLargePages", G1HeapRegionSize1MB);
  61         // 2. Page size > G1HeapRegionSize
  62         //    Test large page with 1MB heap region size.
  63         testMemoryTouch("-XX:+UseLargePages", G1HeapRegionSize1MB);
  64         // 3. Page size < G1HeapRegionSize
  65         //    Test large page with 8MB heap region size.
  66         testMemoryTouch("-XX:+UseLargePages", G1HeapRegionSize8MB);
  67     }
  68 
  69     // On Linux, always UseNUMA is enabled if there is multiple active numa nodes.
  70     static NUMASupportStatus checkNUMAIsEnabled(OutputAnalyzer output) {
  71         boolean supportNUMA = Boolean.parseBoolean(output.firstMatch("\\bUseNUMA\\b.*?=.*?([a-z]+)", 1));
  72         System.out.println("supportNUMA=" + supportNUMA);
  73         return supportNUMA ? NUMASupportStatus.SUPPORT : NUMASupportStatus.NOT_SUPPORT;
  74     }
  75 
  76     static long parseSizeString(String size) {
  77         long multiplier = 1;
  78 
  79         if (size.endsWith("B")) {
  80             multiplier = 1;
  81         } else if (size.endsWith("K")) {
  82             multiplier = 1024;
  83         } else if (size.endsWith("M")) {
  84             multiplier = 1024 * 1024;
  85         } else if (size.endsWith("G")) {
  86             multiplier = 1024 * 1024 * 1024;
  87         } else {
  88             throw new IllegalArgumentException("Expected memory string '" + size + "'to end with either of: B, K, M, G");
  89         }
  90 
  91         long longSize = Long.parseUnsignedLong(size.substring(0, size.length() - 1));
  92 
  93         return longSize * multiplier;
  94     }
  95 
  96     static long heapPageSize(OutputAnalyzer output) {
  97         String HeapPageSizePattern = "Heap:  .*page_size=([^ ]+)";
  98         String str = output.firstMatch(HeapPageSizePattern, 1);
  99 
 100         if (str == null) {
 101             output.reportDiagnosticSummary();
 102             throw new RuntimeException("Match from '" + HeapPageSizePattern + "' got 'null'");
 103         }
 104 
 105         return parseSizeString(str);
 106     }
 107 
 108     // 1. -UseLargePages: default page, page size < G1HeapRegionSize
 109     //    +UseLargePages: large page size <= G1HeapRegionSize
 110     //
 111     //    Each 'int' represents a numa id of single HeapRegion (bottom page).
 112     //    e.g. 1MB heap region, 2MB page size and 2 NUMA nodes system
 113     //         Check the first set(2 regions)
 114     //         0| ...omitted..| 0
 115     //         1| ...omitted..| 1
 116     static void checkCase1Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception {
 117         StringBuilder sb = new StringBuilder();
 118 
 119         // Append index which means heap region index.
 120         sb.append(String.format("%6d", index));
 121         sb.append("| .* | ");
 122 
 123         // Append page node id.
 124         sb.append(memoryNodeIds[index]);
 125 
 126         output.shouldMatch(sb.toString());
 127     }
 128 
 129     // 3. +UseLargePages: large page size > G1HeapRegionSize
 130     //
 131     //    As a OS page is consist of multiple heap regions, log also should be
 132     //    printed multiple times for same numa id.
 133     //    e.g. 1MB heap region, 2MB page size and 2 NUMA nodes system
 134     //         Check the first set(4 regions)
 135     //         0| ...omitted..| 0
 136     //         1| ...omitted..| 0
 137     //         2| ...omitted..| 1
 138     //         3| ...omitted..| 1
 139     static void checkCase2Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception {
 140         StringBuilder sb = new StringBuilder();
 141 
 142         // Append page range.
 143         int lines_to_print = (int)(actualPageSize / g1HeapRegionSize);
 144         for (int i = 0; i < lines_to_print; i++) {
 145             // Append index which means heap region index.
 146             sb.append(String.format("%6d", index * lines_to_print + i));
 147             sb.append("| .* | ");
 148 
 149             // Append page node id.
 150             sb.append(memoryNodeIds[index]);
 151 
 152             output.shouldMatch(sb.toString());
 153             sb.setLength(0);
 154         }
 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 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:pagesize,gc+heap+region=trace",
 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                                               GCTest.class.getName());
 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 
 212   static class GCTest {
 213     public static final int M = 1024*1024;
 214     public static LinkedList<Object> garbageList = new LinkedList<Object>();
 215     // A large object referenced by a static.
 216     static int[] filler = new int[10 * M];
 217 
 218     public static void genGarbage() {
 219       for (int i = 0; i < 32*1024; i++) {
 220         garbageList.add(new int[100]);
 221       }
 222       garbageList.clear();
 223     }
 224 
 225     public static void main(String[] args) {
 226 
 227       int[] large = new int[M];
 228       Object ref = large;
 229 
 230       System.out.println("Creating garbage");
 231       for (int i = 0; i < 100; i++) {
 232         // A large object that will be reclaimed eagerly.
 233         large = new int[6*M];
 234         genGarbage();
 235         // Make sure that the compiler cannot completely remove
 236         // the allocation of the large object until here.
 237         System.out.println(large);
 238       }
 239 
 240       // Keep the reference to the first object alive.
 241       System.out.println(ref);
 242       System.out.println("Done");
 243     }
 244   }
 245 }