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) Derive a class from TestConcurrentPhaseControlSupport, and
  29 // derive a class from TestConcurrentPhaseControlSupport.Executor,
  30 // providing definitions for the abstract methods.
  31 //
  32 // (2) The Executor subclass must provide a public static main
  33 // function which constructs an instance of that subclass and calls
  34 // its run() method.
  35 //
  36 // (3) The test subclass provides a public static main function which
  37 // constructs an instance of that subclass and calls its run() method.
  38 //
  39 // (4) The test program must provide access to WhiteBox, as it is used
  40 // by this support class.
  41 //
  42 // (5) The test program should be invoked as a driver.  The support
  43 // class's run() function will run the Executor subclass in a
  44 // subprocess, in order to capture it's output for analysis.
  45 
  46 package jdk.test.lib.gc;
  47 
  48 import sun.hotspot.WhiteBox;
  49 
  50 import java.util.Collections;
  51 import java.util.List;
  52 import java.util.ArrayList;
  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 abstract class TestConcurrentPhaseControlSupport {
  62     // Return an array of pairs of strings.  Each pair is a phase name
  63     // and a regex pattern for recognizing the associated log message.
  64     // The regex pattern can be null if no log message is associated
  65     // with the named phase.  The test will iterate through the array,
  66     // requesting each phase in turn.
  67     protected abstract String[][] gcPhases();
  68 
  69     // Command line options for invoking the desired collector and
  70     // logging options to produce output that can be matched against
  71     // the regex patterns in the gcPhases() pairs.
  72     protected abstract String[] gcOptions();
  73 
  74     // Name of the class that will execute the control requests.  It
  75     // should be a subclass of our Executor class below.
  76     protected abstract String executorClassName();
  77 
  78     // The name of the GC, logged as "Using <name>" near the beginning
  79     // of the log output.
  80     protected abstract String gcName();
  81 
  82     private static final String requestPrefix = "Requesting concurrent phase: ";
  83     private static final String reachedPrefix = "Reached concurrent phase: ";
  84 
  85     private static void fail(String message) throws Exception {
  86         throw new RuntimeException(message);
  87     }
  88 
  89     public String executeTest() throws Exception {
  90         System.out.println("\n---------- Testing ---------");
  91 
  92         final String[] wb_arguments = {
  93             "-Xbootclasspath/a:.",
  94             "-XX:+UnlockDiagnosticVMOptions",
  95             "-XX:+WhiteBoxAPI"
  96         };
  97 
  98         List<String> arglist = new ArrayList<String>();
  99         Collections.addAll(arglist, wb_arguments);
 100         Collections.addAll(arglist, gcOptions());
 101         Collections.addAll(arglist, executorClassName());
 102         String[] arguments = arglist.toArray(new String[arglist.size()]);
 103 
 104         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(arguments);
 105         OutputAnalyzer output = new OutputAnalyzer(pb.start());
 106 
 107         String messages = output.getStdout();
 108         System.out.println(messages);
 109 
 110         output.shouldHaveExitValue(0);
 111         output.shouldContain("Using " + gcName());
 112 
 113         return messages;
 114     }
 115 
 116     public void checkPhaseControl(String messages) throws Exception {
 117         // Iterate through the phase sequence for the test, verifying
 118         // output contains appropriate sequences of request message,
 119         // log message for phase, and request reached message.  Note
 120         // that a log message for a phase may occur later than the
 121         // associated request reached message, or even the following
 122         // request message.
 123 
 124         Pattern nextReqP = Pattern.compile(requestPrefix);
 125         Matcher nextReqM = nextReqP.matcher(messages);
 126 
 127         Pattern nextReachP = Pattern.compile(reachedPrefix);
 128         Matcher nextReachM = nextReachP.matcher(messages);
 129 
 130         String pendingPhaseMessage = null;
 131         int pendingPhaseMessagePosition = -1;
 132 
 133         int position = 0;
 134         for (String[] phase: gcPhases()) {
 135             String phaseName = phase[0];
 136             String phaseMsg = phase[1];
 137 
 138             System.out.println("Processing phase " + phaseName);
 139 
 140             // Update the "next" matchers to refer to the next
 141             // corresponding pair of request and reached messages.
 142             if (!nextReqM.find()) {
 143                 fail("Didn't find next phase request");
 144             } else if ((position != 0) && (nextReqM.start() < nextReachM.end())) {
 145                 fail("Next request before previous reached");
 146             } else if (!nextReachM.find()) {
 147                 fail("Didn't find next phase reached");
 148             } else if (nextReachM.start() <= nextReqM.end()) {
 149                 fail("Next request/reached misordered");
 150             }
 151 
 152             // Find the expected request message, and ensure it is the next.
 153             Pattern reqP = Pattern.compile(requestPrefix + phaseName);
 154             Matcher reqM = reqP.matcher(messages);
 155             if (!reqM.find(position)) {
 156                 fail("Didn't find request for " + phaseName);
 157             } else if (reqM.start() != nextReqM.start()) {
 158                 fail("Request mis-positioned for " + phaseName);
 159             }
 160 
 161             // Find the expected reached message, and ensure it is the next.
 162             Pattern reachP = Pattern.compile(reachedPrefix + phaseName);
 163             Matcher reachM = reachP.matcher(messages);
 164             if (!reachM.find(position)) {
 165                 fail("Didn't find reached for " + phaseName);
 166             } else if (reachM.start() != nextReachM.start()) {
 167                 fail("Reached mis-positioned for " + phaseName);
 168             }
 169 
 170             // If there is a pending log message (see below), ensure
 171             // it was before the current reached message.
 172             if (pendingPhaseMessage != null) {
 173                 if (pendingPhaseMessagePosition >= reachM.start()) {
 174                     fail("Log message after next reached message: " +
 175                          pendingPhaseMessage);
 176                 }
 177             }
 178 
 179             // If the phase has an associated logging message, verify
 180             // such a logging message is present following the
 181             // request, and otherwise positioned appropriately.  The
 182             // complication here is that the logging message
 183             // associated with a request might follow the reached
 184             // message, and even the next request message, if there is
 185             // a later request.  But it must preceed the next
 186             // logging message and the next reached message.
 187             boolean clearPendingPhaseMessage = true;
 188             if (phaseMsg != null) {
 189                 Pattern logP = Pattern.compile("GC\\(\\d+\\)\\s+" + phaseMsg);
 190                 Matcher logM = logP.matcher(messages);
 191                 if (!logM.find(reqM.end())) {
 192                     fail("Didn't find message " + phaseMsg);
 193                 }
 194 
 195                 if (pendingPhaseMessage != null) {
 196                     if (pendingPhaseMessagePosition >= logM.start()) {
 197                         fail("Log messages out of order: " +
 198                              pendingPhaseMessage + " should preceed " +
 199                              phaseMsg);
 200                     }
 201                 }
 202 
 203                 if (reachM.end() <= logM.start()) {
 204                     clearPendingPhaseMessage = false;
 205                     pendingPhaseMessage = phaseMsg;
 206                     pendingPhaseMessagePosition = logM.end();
 207                 }
 208             }
 209             if (clearPendingPhaseMessage) {
 210                 pendingPhaseMessage = null;
 211                 pendingPhaseMessagePosition = -1;
 212             }
 213 
 214             // Update position for start of next phase search.
 215             position = reachM.end();
 216         }
 217         // It's okay for there to be a leftover pending phase message.
 218         // We know it was found before the end of the log.
 219     }
 220 
 221     public void run() throws Exception {
 222         String messages = executeTest();
 223         checkPhaseControl(messages);
 224     }
 225 
 226     public static abstract class Executor {
 227         private static final WhiteBox WB = WhiteBox.getWhiteBox();
 228 
 229         private void step(String phase) {
 230             System.out.println(requestPrefix + phase);
 231             WB.requestConcurrentGCPhase(phase);
 232             System.out.println(reachedPrefix + phase);
 233         }
 234 
 235         public void run() throws Exception {
 236             // Iterate through test sequence of phases, requesting each.
 237             for (String[] phaseData: gcPhases()) {
 238                 step(phaseData[0]);
 239             }
 240             // Wait a little to allow a delayed logging message for
 241             // the final request/reached to be printed before exiting
 242             // the program.
 243             Thread.sleep(250);
 244         }
 245 
 246         // Return the same array of string pairs as returned by the
 247         // test support's method.
 248         protected abstract String[][] gcPhases();
 249     }
 250 }