/* * Copyright (c) 2013, 2015, 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.Utils; import jdk.testlibrary.ProcessTools; /** * The base class for tests of jstatd. * * The test sequence for TestJstatdDefaults for example is: * <pre> * {@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 * } * </pre> */ 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 Long 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; } private Long waitOnTool(ProcessThread thread) throws Throwable { long pid = thread.getPid(); 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(thread.getName() + " pid: " + pid); return pid; } 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 = ProcessTools.executeProcess(processBuilder); 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"); launcher.addToolArg(jstatdPid + "@" + getDestination()); 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 = ProcessTools.executeProcess(processBuilder); 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); 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 Exception { JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstatd"); launcher.addVMArg("-XX:+UsePerfData"); String testSrc = System.getProperty("test.src"); File policy = new File(testSrc, "all.policy"); assertTrue(policy.exists() && policy.isFile(), "Security policy " + policy.getAbsolutePath() + " does not exist or not a file"); 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); } if (withExternalRegistry) { launcher.addToolArg("-nr"); } String[] cmd = launcher.getCommand(); log("Start jstatd", cmd); return cmd; } private ProcessThread tryToSetupJstatdProcess() throws Throwable { ProcessThread jstatdThread = new ProcessThread("Jstatd-Thread", JstatdTest::isJstadReady, getJstatdCmd()); try { jstatdThread.start(); // Make sure jstatd is up and running jstatdPid = waitOnTool(jstatdThread); if (jstatdPid == 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; } private static boolean isJstadReady(String line) { return line.startsWith("jstatd started (bound to "); } public void doTest() throws Throwable { if (useDefaultPort) { verifyNoRmiRegistryOnDefaultPort(); } ProcessThread jstatdThread = null; try { while (jstatdThread == null) { if (!useDefaultPort) { port = String.valueOf(Utils.getFreePort()); } if (withExternalRegistry) { Registry registry = startRegistry(); if (registry == null) { // The port is already in use. Cancel and try with a new one. continue; } } jstatdThread = tryToSetupJstatdProcess(); } runToolsAndVerify(); } finally { cleanUpThread(jstatdThread); } // Verify output from jstatd OutputAnalyzer output = jstatdThread.getOutput(); assertTrue(output.getOutput().isEmpty(), "jstatd should get an empty output, got: " + Utils.NEW_LINE + output.getOutput()); assertNotEquals(output.getExitValue(), 0, "jstatd process exited with unexpected exit code"); } private void verifyNoRmiRegistryOnDefaultPort() throws Exception { try { Registry registry = LocateRegistry.getRegistry(); registry.list(); throw new Exception("There is already RMI registry on the default port: " + registry); } catch (RemoteException e) { // No RMI registry on default port is detected } } }