/* * Copyright (c) 2013, 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. */ import java.io.File; import java.net.UnknownHostException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.Arrays; import static jdk.testlibrary.Asserts.*; import jdk.testlibrary.JDKToolLauncher; import jdk.testlibrary.OutputAnalyzer; import jdk.testlibrary.ProcessThread; import jdk.testlibrary.TestThread; import jdk.testlibrary.Utils; /** * The base class for tests of jstatd. * * The test sequence for TestJstatdDefaults for example is: *
 * {@code
 * // start jstatd process
 * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy
 *
 * // run jps and verify its output
 * jps -J-XX:+UsePerfData hostname
 *
 * // run jstat and verify its output
 * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname 250 5
 *
 * // stop jstatd process and verify that no unexpected exceptions have been thrown
 * }
 * 
*/ public final class JstatdTest { /** * jstat gcutil option: takes JSTAT_GCUTIL_SAMPLES samples at * JSTAT_GCUTIL_INTERVAL_MS millisecond intervals */ private static final int JSTAT_GCUTIL_SAMPLES = 5; private static final int JSTAT_GCUTIL_INTERVAL_MS = 250; private static final String JPS_OUTPUT_REGEX = "^\\d+\\s*.*"; private boolean useDefaultPort = true; private String port; private String serverName; private String jstatdPid; private boolean withExternalRegistry = false; public void setServerName(String serverName) { this.serverName = serverName; } public void setUseDefaultPort(boolean useDefaultPort) { this.useDefaultPort = useDefaultPort; } public void setWithExternalRegistry(boolean withExternalRegistry) { this.withExternalRegistry = withExternalRegistry; } /** * Parse pid from jps output */ private String parsePid(String tool, OutputAnalyzer output) throws Exception { String[] lines = output.getOutput().split(Utils.NEW_LINE); String pid = null; int count = 0; String processName = tool; if (tool == "rmiregistry") { processName = "registryimpl"; } for (String line : lines) { if (line.toLowerCase().matches("^\\d+\\s{1}" + processName + "$")) { pid = line.split(" ")[0]; count++; } } if (count > 1) { throw new Exception("Expected one " + tool + " process, got " + count + ". Test will be canceled."); } return pid; } private String getToolPid(String tool) throws Exception { OutputAnalyzer output = runJps(); return parsePid(tool, output); } private String waitOnTool(String tool, TestThread thread) throws Throwable { while (true) { String pid = getToolPid(tool); if (pid != null) { System.out.println(tool + " pid: " + pid); return pid; } Throwable t = thread.getUncaught(); if (t != null) { if (t.getMessage().contains( "java.rmi.server.ExportException: Port already in use")) { System.out.println("Port already in use. Trying to restart with a new one..."); Thread.sleep(100); return null; } else { // Something unexpected has happened throw new Throwable(t); } } System.out.println("Waiting until " + tool + " is running..."); Thread.sleep(100); } } private void log(String caption, String... cmd) { System.out.println(Utils.NEW_LINE + caption + ":"); System.out.println(Arrays.toString(cmd).replace(",", "")); } private String getDestination() throws UnknownHostException { String option = Utils.getHostname(); if (port != null) { option += ":" + port; } if (serverName != null) { option += "/" + serverName; } return option; } /** * Depending on test settings command line can look like: * * jps -J-XX:+UsePerfData hostname * jps -J-XX:+UsePerfData hostname:port * jps -J-XX:+UsePerfData hostname/serverName * jps -J-XX:+UsePerfData hostname:port/serverName */ private OutputAnalyzer runJps() throws Exception { JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jps"); launcher.addVMArg("-XX:+UsePerfData"); launcher.addToolArg(getDestination()); String[] cmd = launcher.getCommand(); log("Start jps", cmd); ProcessBuilder processBuilder = new ProcessBuilder(cmd); OutputAnalyzer output = new OutputAnalyzer(processBuilder.start()); System.out.println(output.getOutput()); return output; } /** * Verifies output form jps contains pids and programs' name information. * The function will discard any lines that come before the first line with pid. * This can happen if the JVM outputs a warning message for some reason * before running jps. * * The output can look like: * 35536 Jstatd * 35417 Main * 31103 org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar */ private void verifyJpsOutput(OutputAnalyzer output) throws Exception { output.shouldHaveExitValue(0); assertFalse(output.getOutput().isEmpty(), "Output should not be empty"); boolean foundFirstLineWithPid = false; String[] lines = output.getOutput().split(Utils.NEW_LINE); for (String line : lines) { if (!foundFirstLineWithPid) { foundFirstLineWithPid = line.matches(JPS_OUTPUT_REGEX); continue; } assertTrue(line.matches(JPS_OUTPUT_REGEX), "Output does not match the pattern" + Utils.NEW_LINE + line); } assertTrue(foundFirstLineWithPid, "Invalid output"); } /** * Depending on test settings command line can look like: * * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname 250 5 * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname:port 250 5 * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname/serverName 250 5 * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname:port/serverName 250 5 */ private OutputAnalyzer runJstat() throws Exception { JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstat"); launcher.addVMArg("-XX:+UsePerfData"); launcher.addVMArg("-Duser.language=en"); launcher.addToolArg("-gcutil"); String destination = ""; if (jstatdPid != null) { destination = jstatdPid + "@"; } destination += getDestination(); launcher.addToolArg(destination); launcher.addToolArg(Integer.toString(JSTAT_GCUTIL_INTERVAL_MS)); launcher.addToolArg(Integer.toString(JSTAT_GCUTIL_SAMPLES)); String[] cmd = launcher.getCommand(); log("Start jstat", cmd); ProcessBuilder processBuilder = new ProcessBuilder(cmd); OutputAnalyzer output = new OutputAnalyzer(processBuilder.start()); System.out.println(output.getOutput()); return output; } private void verifyJstatOutput(OutputAnalyzer output) throws Exception { output.shouldHaveExitValue(0); assertFalse(output.getOutput().isEmpty(), "Output should not be empty"); JstatGCutilParser gcutilParser = new JstatGCutilParser( output.getOutput()); gcutilParser.parse(JSTAT_GCUTIL_SAMPLES); } private void runToolsAndVerify() throws Exception { OutputAnalyzer output = runJps(); verifyJpsOutput(output); jstatdPid = parsePid("jstatd", output); output = runJstat(); verifyJstatOutput(output); } private Registry startRegistry() throws InterruptedException, RemoteException { Registry registry = null; try { System.out.println("Start rmiregistry on port " + port); registry = LocateRegistry .createRegistry(Integer.parseInt(port)); } catch (RemoteException e) { if (e.getMessage().contains("Port already in use")) { System.out.println("Port already in use. Trying to restart with a new one..."); Thread.sleep(100); return null; } else { throw e; } } return registry; } private void cleanUpThread(ProcessThread thread) throws Throwable { if (thread != null) { thread.stopProcess(); thread.joinAndThrow(); } } /** * Depending on test settings command line can look like: * * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -p port * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -n serverName * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -p port -n serverName */ private String[] getJstatdCmd() throws UnknownHostException { JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstatd"); launcher.addVMArg("-XX:+UsePerfData"); String testSrc = System.getProperty("test.src"); File policy = new File(testSrc, "all.policy"); launcher.addVMArg("-Djava.security.policy=" + policy.getAbsolutePath()); if (port != null) { launcher.addToolArg("-p"); launcher.addToolArg(port); } if (serverName != null) { launcher.addToolArg("-n"); launcher.addToolArg(serverName); } String[] cmd = launcher.getCommand(); log("Start jstatd", cmd); return cmd; } private ProcessThread tryToSetupJstatdProcess() throws Throwable { ProcessThread jstatdThread = new ProcessThread("Jstatd-Thread", getJstatdCmd()); try { jstatdThread.start(); // Make sure jstatd is up and running if (waitOnTool("jstatd", jstatdThread) == null) { // The port is already in use. Cancel and try with new one. jstatdThread.stopProcess(); jstatdThread.join(); return null; } } catch (Throwable t) { // Something went wrong in the product - clean up! cleanUpThread(jstatdThread); throw t; } return jstatdThread; } public void doTest() throws Throwable { ProcessThread jstatdThread = null; try { while (jstatdThread == null) { if (!useDefaultPort || withExternalRegistry) { port = Integer.toString(Utils.getFreePort()); } if (withExternalRegistry) { Registry registry = startRegistry(); if (registry == null) { // The port is already in use. Cancel and try with new one. continue; } } jstatdThread = tryToSetupJstatdProcess(); } runToolsAndVerify(); } finally { cleanUpThread(jstatdThread); } } }