1 /* 2 * Copyright (c) 2017, 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 // Support library for TestConcurrentPhaseControl variants. 25 // 26 // To use: 27 // 28 // (1) The main test class provides a subclass for stepping through 29 // the phases. That phase stepper class has a main function which 30 // constructs and runs an instance of this support class's Executor. 31 // 32 // (2) The main test class has a main function which constructs and 33 // runs an instance of the support class. The first argument is the 34 // name of the main class's phase stepper class. 35 // 36 // (3) The test program must provide access to WhiteBox, as it is used 37 // by this support class. 38 // 39 // (4) The test program should be invoked as a driver. The support 40 // class's run() function will run the phase stepper in a subprocess, 41 // in order to capture it's output for analysis. 42 43 package jdk.test.lib.gc; 44 45 import sun.hotspot.WhiteBox; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.HashSet; 51 import java.util.List; 52 import java.util.Set; 53 54 import java.util.regex.Matcher; 55 import java.util.regex.Pattern; 56 57 import jdk.test.lib.Platform; 58 import jdk.test.lib.process.OutputAnalyzer; 59 import jdk.test.lib.process.ProcessTools; 60 61 public final class ConcurrentPhaseControlUtil { 62 // The name of the class to "invoke" in the subprocess. 63 private final String phaseStepperName; 64 65 // An array of pairs of strings. Each pair is a phase name 66 // and a regex pattern for recognizing the associated log message. 67 // The regex pattern can be null if no log message is associated 68 // with the named phase. The test will iterate through the array, 69 // requesting each phase in turn. 70 private final String[][] gcStepPhases; 71 72 // Command line options for invoking the desired collector and 73 // logging options to produce output that can be matched against 74 // the regex patterns in the gcStepPhases pairs. 75 private final String[] gcOptions; 76 77 // The name of the GC, logged as "Using <name>" near the beginning 78 // of the log output. 79 private final String gcName; 80 81 public ConcurrentPhaseControlUtil(String phaseStepperName, 82 String[][] gcStepPhases, 83 String[] gcOptions, 84 String gcName) { 85 this.phaseStepperName = phaseStepperName; 86 this.gcStepPhases = gcStepPhases; 87 this.gcOptions = gcOptions; 88 this.gcName = gcName; 89 } 90 91 private static void fail(String message) throws Exception { 92 throw new RuntimeException(message); 93 } 94 95 private static final String requestPrefix = "Requesting concurrent phase: "; 96 private static final String reachedPrefix = "Reached concurrent phase: "; 97 98 public String executeTest() throws Exception { 99 System.out.println("\n---------- Testing ---------"); 100 101 final String[] wb_arguments = { 102 "-Xbootclasspath/a:.", 103 "-XX:+UnlockDiagnosticVMOptions", 104 "-XX:+WhiteBoxAPI" 105 }; 106 107 List<String> arglist = new ArrayList<String>(); 108 Collections.addAll(arglist, wb_arguments); 109 Collections.addAll(arglist, gcOptions); 110 Collections.addAll(arglist, phaseStepperName); 111 String[] arguments = arglist.toArray(new String[arglist.size()]); 112 113 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(arguments); 114 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 115 116 String messages = output.getStdout(); 117 System.out.println(messages); 118 119 output.shouldHaveExitValue(0); 120 output.shouldContain("Using " + gcName); 121 122 return messages; 123 } 124 125 public void checkPhaseControl(String messages) throws Exception { 126 // Iterate through the phase sequence for the test, verifying 127 // output contains appropriate sequences of request message, 128 // log message for phase, and request reached message. Note 129 // that a log message for a phase may occur later than the 130 // associated request reached message, or even the following 131 // request message. 132 133 Pattern nextReqP = Pattern.compile(requestPrefix); 134 Matcher nextReqM = nextReqP.matcher(messages); 135 136 Pattern nextReachP = Pattern.compile(reachedPrefix); 137 Matcher nextReachM = nextReachP.matcher(messages); 138 139 String pendingPhaseMessage = null; 140 int pendingPhaseMessagePosition = -1; 141 142 int position = 0; 143 for (String[] phase: gcStepPhases) { 144 String phaseName = phase[0]; 145 String phaseMsg = phase[1]; 146 147 System.out.println("Processing phase " + phaseName); 148 149 // Update the "next" matchers to refer to the next 150 // corresponding pair of request and reached messages. 151 if (!nextReqM.find()) { 152 fail("Didn't find next phase request"); 153 } else if ((position != 0) && (nextReqM.start() < nextReachM.end())) { 154 fail("Next request before previous reached"); 155 } else if (!nextReachM.find()) { 156 fail("Didn't find next phase reached"); 157 } else if (nextReachM.start() <= nextReqM.end()) { 158 fail("Next request/reached misordered"); 159 } 160 161 // Find the expected request message, and ensure it is the next. 162 Pattern reqP = Pattern.compile(requestPrefix + phaseName); 163 Matcher reqM = reqP.matcher(messages); 164 if (!reqM.find(position)) { 165 fail("Didn't find request for " + phaseName); 166 } else if (reqM.start() != nextReqM.start()) { 167 fail("Request mis-positioned for " + phaseName); 168 } 169 170 // Find the expected reached message, and ensure it is the next. 171 Pattern reachP = Pattern.compile(reachedPrefix + phaseName); 172 Matcher reachM = reachP.matcher(messages); 173 if (!reachM.find(position)) { 174 fail("Didn't find reached for " + phaseName); 175 } else if (reachM.start() != nextReachM.start()) { 176 fail("Reached mis-positioned for " + phaseName); 177 } 178 179 // If there is a pending log message (see below), ensure 180 // it was before the current reached message. 181 if (pendingPhaseMessage != null) { 182 if (pendingPhaseMessagePosition >= reachM.start()) { 183 fail("Log message after next reached message: " + 184 pendingPhaseMessage); 185 } 186 } 187 188 // If the phase has an associated logging message, verify 189 // such a logging message is present following the 190 // request, and otherwise positioned appropriately. The 191 // complication here is that the logging message 192 // associated with a request might follow the reached 193 // message, and even the next request message, if there is 194 // a later request. But it must preceed the next 195 // logging message and the next reached message. 196 boolean clearPendingPhaseMessage = true; 197 if (phaseMsg != null) { 198 Pattern logP = Pattern.compile("GC\\(\\d+\\)\\s+" + phaseMsg); 199 Matcher logM = logP.matcher(messages); 200 if (!logM.find(reqM.end())) { 201 fail("Didn't find message " + phaseMsg); 202 } 203 204 if (pendingPhaseMessage != null) { 205 if (pendingPhaseMessagePosition >= logM.start()) { 206 fail("Log messages out of order: " + 207 pendingPhaseMessage + " should preceed " + 208 phaseMsg); 209 } 210 } 211 212 if (reachM.end() <= logM.start()) { 213 clearPendingPhaseMessage = false; 214 pendingPhaseMessage = phaseMsg; 215 pendingPhaseMessagePosition = logM.end(); 216 } 217 } 218 if (clearPendingPhaseMessage) { 219 pendingPhaseMessage = null; 220 pendingPhaseMessagePosition = -1; 221 } 222 223 // Update position for start of next phase search. 224 position = reachM.end(); 225 } 226 // It's okay for there to be a leftover pending phase message. 227 // We know it was found before the end of the log. 228 } 229 230 public void run() throws Exception { 231 String messages = executeTest(); 232 checkPhaseControl(messages); 233 } 234 235 public static final class Executor { 236 private static final WhiteBox WB = WhiteBox.getWhiteBox(); 237 238 // An array of all the concurrent GC phases expected to be 239 // provided by the collector. 240 private final String[] gcAllPhases; 241 242 private final String[][] gcStepPhases; 243 244 public Executor(String[] gcAllPhases, String[][] gcStepPhases) { 245 this.gcAllPhases = gcAllPhases; 246 this.gcStepPhases = gcStepPhases; 247 } 248 249 private void checkAllPhases() throws Exception { 250 System.out.println("Checking phase set is as expected"); 251 252 String[] phases = WB.getConcurrentGCPhases(); 253 254 List<String> expectedList = Arrays.asList(gcAllPhases); 255 List<String> actualList = Arrays.asList(phases); 256 257 Set<String> expected = new HashSet<String>(expectedList); 258 Set<String> actual = new HashSet<String>(actualList); 259 260 expected.removeAll(actualList); 261 actual.removeAll(expectedList); 262 263 boolean match = true; 264 if (!expected.isEmpty()) { 265 match = false; 266 System.out.println("Unexpected phases:"); 267 for (String s: expected) { 268 System.out.println(" " + s); 269 } 270 } 271 if (!actual.isEmpty()) { 272 match = false; 273 System.out.println("Expected but missing phases:"); 274 for (String s: actual) { 275 System.out.println(" " + s); 276 } 277 } 278 if (!match) { 279 fail("Mismatch between expected and actual phases"); 280 } 281 } 282 283 private void step(String phase) { 284 System.out.println(requestPrefix + phase); 285 WB.requestConcurrentGCPhase(phase); 286 System.out.println(reachedPrefix + phase); 287 } 288 289 public void run() throws Exception { 290 if (!WB.supportsConcurrentGCPhaseControl()) { 291 fail("GC doesn't support concurrent phase control"); 292 } 293 294 checkAllPhases(); 295 296 // Iterate through test sequence of phases, requesting each. 297 for (String[] phaseData: gcStepPhases) { 298 step(phaseData[0]); 299 } 300 // Wait a little to allow a delayed logging message for 301 // the final request/reached to be printed before exiting 302 // the program. 303 Thread.sleep(250); 304 } 305 } 306 }