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 }