/* * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package gc.concurrent_phase_control; import sun.hotspot.WhiteBox; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import jdk.test.lib.Platform; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; /* * Utility class that provides method which implement verification of Concurrent Phase Control. * This method launches an extra VM and it is supposed to be invoked in the driver VM. */ public final class ConcurrentPhaseControlUtil { private static void fail(String message) throws Exception { throw new RuntimeException(message); } private static final String requestPrefix = "Requesting concurrent phase: "; private static final String reachedPrefix = "Reached concurrent phase: "; /** * The test iterates through the array of phases, requesting each phase in turn. * The test checks the gc log for expected messages to appear. * @param gcStepPhases An array of pairs of strings. Each pair is a phase name * and a regex pattern for recognizing the associated log message. * The regex pattern can be null if no log message is associated * with the named phase. * @param gcOptions Command line options for invoking the desired collector and * logging options to produce output that can be matched against * the regex patterns in the gcStepPhases pairs. * @param gcName The name of the GC, logged as "Using " near the beginning * of the log output */ public static void checkPhases(String[][] gcStepPhases, String[] gcOptions, String gcName) throws Exception { System.out.println("\n---------- Testing ---------"); final String[] wb_arguments = { "-Xbootclasspath/a:.", "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI" }; List arglist = new ArrayList(); Collections.addAll(arglist, wb_arguments); Collections.addAll(arglist, gcOptions); Collections.addAll(arglist, Executor.class.getName()); String[] arguments = arglist.toArray(new String[arglist.size()]); ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(arguments); OutputAnalyzer output = new OutputAnalyzer(pb.start()); String messages = output.getStdout(); System.out.println(messages); output.shouldHaveExitValue(0); output.shouldContain("Using " + gcName); checkPhaseControl(messages, gcStepPhases); } private static void checkPhaseControl(String messages, String[][] gcStepPhases) throws Exception { // Iterate through the phase sequence for the test, verifying // output contains appropriate sequences of request message, // log message for phase, and request reached message. Note // that a log message for a phase may occur later than the // associated request reached message, or even the following // request message. Pattern nextReqP = Pattern.compile(requestPrefix); Matcher nextReqM = nextReqP.matcher(messages); Pattern nextReachP = Pattern.compile(reachedPrefix); Matcher nextReachM = nextReachP.matcher(messages); String pendingPhaseMessage = null; int pendingPhaseMessagePosition = -1; int position = 0; for (String[] phase: gcStepPhases) { String phaseName = phase[0]; String phaseMsg = phase[1]; System.out.println("Processing phase " + phaseName); // Update the "next" matchers to refer to the next // corresponding pair of request and reached messages. if (!nextReqM.find()) { fail("Didn't find next phase request"); } else if ((position != 0) && (nextReqM.start() < nextReachM.end())) { fail("Next request before previous reached"); } else if (!nextReachM.find()) { fail("Didn't find next phase reached"); } else if (nextReachM.start() <= nextReqM.end()) { fail("Next request/reached misordered"); } // Find the expected request message, and ensure it is the next. Pattern reqP = Pattern.compile(requestPrefix + phaseName); Matcher reqM = reqP.matcher(messages); if (!reqM.find(position)) { fail("Didn't find request for " + phaseName); } else if (reqM.start() != nextReqM.start()) { fail("Request mis-positioned for " + phaseName); } // Find the expected reached message, and ensure it is the next. Pattern reachP = Pattern.compile(reachedPrefix + phaseName); Matcher reachM = reachP.matcher(messages); if (!reachM.find(position)) { fail("Didn't find reached for " + phaseName); } else if (reachM.start() != nextReachM.start()) { fail("Reached mis-positioned for " + phaseName); } // If there is a pending log message (see below), ensure // it was before the current reached message. if (pendingPhaseMessage != null) { if (pendingPhaseMessagePosition >= reachM.start()) { fail("Log message after next reached message: " + pendingPhaseMessage); } } // If the phase has an associated logging message, verify // such a logging message is present following the // request, and otherwise positioned appropriately. The // complication here is that the logging message // associated with a request might follow the reached // message, and even the next request message, if there is // a later request. But it must preceed the next // logging message and the next reached message. boolean clearPendingPhaseMessage = true; if (phaseMsg != null) { Pattern logP = Pattern.compile("GC\\(\\d+\\)\\s+" + phaseMsg); Matcher logM = logP.matcher(messages); if (!logM.find(reqM.end())) { fail("Didn't find message " + phaseMsg); } if (pendingPhaseMessage != null) { if (pendingPhaseMessagePosition >= logM.start()) { fail("Log messages out of order: " + pendingPhaseMessage + " should preceed " + phaseMsg); } } if (reachM.end() <= logM.start()) { clearPendingPhaseMessage = false; pendingPhaseMessage = phaseMsg; pendingPhaseMessagePosition = logM.end(); } } if (clearPendingPhaseMessage) { pendingPhaseMessage = null; pendingPhaseMessagePosition = -1; } // Update position for start of next phase search. position = reachM.end(); } // It's okay for there to be a leftover pending phase message. // We know it was found before the end of the log. } public static final class Executor { private static final WhiteBox WB = WhiteBox.getWhiteBox(); private static void step(String phase) { System.out.println(requestPrefix + phase); WB.requestConcurrentGCPhase(phase); System.out.println(reachedPrefix + phase); } public static void main(String[] args) throws Exception { if (!WB.supportsConcurrentGCPhaseControl()) { fail("GC doesn't support concurrent phase control"); } // Iterate through test sequence of phases, requesting each. for (String phase: args) { step(phase); } // Wait a little to allow a delayed logging message for // the final request/reached to be printed before exiting // the program. Thread.sleep(250); } } }