1 /*
   2  * Copyright (c) 2013, 2018, 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 import java.io.File;
  25 import java.net.UnknownHostException;
  26 import java.rmi.RemoteException;
  27 import java.rmi.registry.LocateRegistry;
  28 import java.rmi.registry.Registry;
  29 import java.util.Arrays;
  30 
  31 import jdk.test.lib.thread.ProcessThread;
  32 import static jdk.testlibrary.Asserts.*;
  33 import jdk.testlibrary.JDKToolLauncher;
  34 import jdk.testlibrary.Utils;
  35 import jdk.test.lib.process.OutputAnalyzer;
  36 import jdk.test.lib.process.ProcessTools;
  37 
  38 /**
  39  * The base class for tests of jstatd.
  40  *
  41  * The test sequence for TestJstatdDefaults for example is:
  42  * <pre>
  43  * {@code
  44  * // start jstatd process
  45  * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy
  46  *
  47  * // run jps and verify its output
  48  * jps -J-XX:+UsePerfData hostname
  49  *
  50  * // run jstat and verify its output
  51  * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname 250 5
  52  *
  53  * // stop jstatd process and verify that no unexpected exceptions have been thrown
  54  * }
  55  * </pre>
  56  */
  57 public final class JstatdTest {
  58 
  59     /**
  60      * jstat gcutil option: takes JSTAT_GCUTIL_SAMPLES samples at
  61      * JSTAT_GCUTIL_INTERVAL_MS millisecond intervals
  62      */
  63     private static final int JSTAT_GCUTIL_SAMPLES = 5;
  64     private static final int JSTAT_GCUTIL_INTERVAL_MS = 250;
  65     private static final String JPS_OUTPUT_REGEX = "^\\d+\\s*.*";
  66 
  67     private boolean useDefaultPort = true;
  68     private String port;
  69     private String serverName;
  70     private Long jstatdPid;
  71     private boolean withExternalRegistry = false;
  72 
  73     public void setServerName(String serverName) {
  74         this.serverName = serverName;
  75     }
  76 
  77     public void setUseDefaultPort(boolean useDefaultPort) {
  78         this.useDefaultPort = useDefaultPort;
  79     }
  80 
  81     public void setWithExternalRegistry(boolean withExternalRegistry) {
  82         this.withExternalRegistry = withExternalRegistry;
  83     }
  84 
  85     private Long waitOnTool(ProcessThread thread) throws Throwable {
  86         long pid = thread.getPid();
  87 
  88         Throwable t = thread.getUncaught();
  89         if (t != null) {
  90             if (t.getMessage().contains(
  91                     "java.rmi.server.ExportException: Port already in use")) {
  92                 System.out.println("Port already in use. Trying to restart with a new one...");
  93                 Thread.sleep(100);
  94                 return null;
  95             } else {
  96                 // Something unexpected has happened
  97                 throw new Throwable(t);
  98             }
  99         }
 100 
 101         System.out.println(thread.getName() + " pid: " + pid);
 102         return pid;
 103     }
 104 
 105     private void log(String caption, String... cmd) {
 106         System.out.println(Utils.NEW_LINE + caption + ":");
 107         System.out.println(Arrays.toString(cmd).replace(",", ""));
 108     }
 109 
 110     private String getDestination() throws UnknownHostException {
 111         String option = Utils.getHostname();
 112         if (port != null) {
 113             option += ":" + port;
 114         }
 115         if (serverName != null) {
 116             option += "/" + serverName;
 117         }
 118         return option;
 119     }
 120 
 121     /**
 122      * Depending on test settings command line can look like:
 123      *
 124      * jps -J-XX:+UsePerfData hostname
 125      * jps -J-XX:+UsePerfData hostname:port
 126      * jps -J-XX:+UsePerfData hostname/serverName
 127      * jps -J-XX:+UsePerfData hostname:port/serverName
 128      */
 129     private OutputAnalyzer runJps() throws Exception {
 130         JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jps");
 131         launcher.addVMArg("-XX:+UsePerfData");
 132         launcher.addToolArg(getDestination());
 133 
 134         String[] cmd = launcher.getCommand();
 135         log("Start jps", cmd);
 136 
 137         ProcessBuilder processBuilder = new ProcessBuilder(cmd);
 138         OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
 139         System.out.println(output.getOutput());
 140 
 141         return output;
 142     }
 143 
 144     /**
 145      * Verifies output form jps contains pids and programs' name information.
 146      * The function will discard any lines that come before the first line with pid.
 147      * This can happen if the JVM outputs a warning message for some reason
 148      * before running jps.
 149      *
 150      * The output can look like:
 151      * 35536 Jstatd
 152      * 35417 Main
 153      * 31103 org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar
 154      */
 155     private void verifyJpsOutput(OutputAnalyzer output) throws Exception {
 156         output.shouldHaveExitValue(0);
 157         assertFalse(output.getOutput().isEmpty(), "Output should not be empty");
 158 
 159         boolean foundFirstLineWithPid = false;
 160         String[] lines = output.getOutput().split(Utils.NEW_LINE);
 161         for (String line : lines) {
 162             if (!foundFirstLineWithPid) {
 163                 foundFirstLineWithPid = line.matches(JPS_OUTPUT_REGEX);
 164                 continue;
 165             }
 166             assertTrue(line.matches(JPS_OUTPUT_REGEX),
 167                     "Output does not match the pattern" + Utils.NEW_LINE + line);
 168         }
 169         assertTrue(foundFirstLineWithPid, "Invalid output");
 170     }
 171 
 172     /**
 173      * Depending on test settings command line can look like:
 174      *
 175      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname 250 5
 176      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname:port 250 5
 177      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname/serverName 250 5
 178      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname:port/serverName 250 5
 179      */
 180     private OutputAnalyzer runJstat() throws Exception {
 181         JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstat");
 182         launcher.addVMArg("-XX:+UsePerfData");
 183         launcher.addVMArg("-Duser.language=en");
 184         launcher.addToolArg("-gcutil");
 185         launcher.addToolArg(jstatdPid + "@" + getDestination());
 186         launcher.addToolArg(Integer.toString(JSTAT_GCUTIL_INTERVAL_MS));
 187         launcher.addToolArg(Integer.toString(JSTAT_GCUTIL_SAMPLES));
 188 
 189         String[] cmd = launcher.getCommand();
 190         log("Start jstat", cmd);
 191 
 192         ProcessBuilder processBuilder = new ProcessBuilder(cmd);
 193         OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
 194         System.out.println(output.getOutput());
 195 
 196         return output;
 197     }
 198 
 199     private void verifyJstatOutput(OutputAnalyzer output)
 200             throws Exception {
 201         output.shouldHaveExitValue(0);
 202         assertFalse(output.getOutput().isEmpty(), "Output should not be empty");
 203 
 204         JstatGCUtilParser gcUtilParser = new JstatGCUtilParser(
 205                 output.getOutput());
 206         gcUtilParser.parse(JSTAT_GCUTIL_SAMPLES);
 207     }
 208 
 209     private void runToolsAndVerify() throws Exception {
 210         OutputAnalyzer output = runJps();
 211         verifyJpsOutput(output);
 212 
 213         output = runJstat();
 214         verifyJstatOutput(output);
 215     }
 216 
 217     private Registry startRegistry()
 218             throws InterruptedException, RemoteException {
 219         Registry registry = null;
 220         try {
 221             System.out.println("Start rmiregistry on port " + port);
 222             registry = LocateRegistry
 223                     .createRegistry(Integer.parseInt(port));
 224         } catch (RemoteException e) {
 225             if (e.getMessage().contains("Port already in use")) {
 226                 System.out.println("Port already in use. Trying to restart with a new one...");
 227                 Thread.sleep(100);
 228                 return null;
 229             } else {
 230                 throw e;
 231             }
 232         }
 233         return registry;
 234     }
 235 
 236     private void cleanUpThread(ProcessThread thread) throws Throwable {
 237         if (thread != null) {
 238             thread.stopProcess();
 239             thread.joinAndThrow();
 240         }
 241     }
 242 
 243     /**
 244      * Depending on test settings command line can look like:
 245      *
 246      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy
 247      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -p port
 248      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -n serverName
 249      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -p port -n serverName
 250      */
 251     private String[] getJstatdCmd() throws Exception {
 252         JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstatd");
 253         launcher.addVMArg("-XX:+UsePerfData");
 254         String testSrc = System.getProperty("test.src");
 255         File policy = new File(testSrc, "all.policy");
 256         assertTrue(policy.exists() && policy.isFile(),
 257                 "Security policy " + policy.getAbsolutePath() + " does not exist or not a file");
 258         launcher.addVMArg("-Djava.security.policy=" + policy.getAbsolutePath());
 259         if (port != null) {
 260             launcher.addToolArg("-p");
 261             launcher.addToolArg(port);
 262         }
 263         if (serverName != null) {
 264             launcher.addToolArg("-n");
 265             launcher.addToolArg(serverName);
 266         }
 267         if (withExternalRegistry) {
 268             launcher.addToolArg("-nr");
 269         }
 270 
 271         String[] cmd = launcher.getCommand();
 272         log("Start jstatd", cmd);
 273         return cmd;
 274     }
 275 
 276     private ProcessThread tryToSetupJstatdProcess() throws Throwable {
 277         ProcessThread jstatdThread = new ProcessThread("Jstatd-Thread",
 278                 JstatdTest::isJstadReady, getJstatdCmd());
 279         try {
 280             jstatdThread.start();
 281             // Make sure jstatd is up and running
 282             jstatdPid = waitOnTool(jstatdThread);
 283             if (jstatdPid == null) {
 284                 // The port is already in use. Cancel and try with new one.
 285                 jstatdThread.stopProcess();
 286                 jstatdThread.join();
 287                 return null;
 288             }
 289         } catch (Throwable t) {
 290             // Something went wrong in the product - clean up!
 291             cleanUpThread(jstatdThread);
 292             throw t;
 293         }
 294 
 295         return jstatdThread;
 296     }
 297 
 298     private static boolean isJstadReady(String line) {
 299         return line.startsWith("jstatd started (bound to ");
 300     }
 301 
 302     public void doTest() throws Throwable {
 303         if (useDefaultPort) {
 304             verifyNoRmiRegistryOnDefaultPort();
 305         }
 306 
 307         ProcessThread jstatdThread = null;
 308         try {
 309             while (jstatdThread == null) {
 310                 if (!useDefaultPort) {
 311                     port = String.valueOf(Utils.getFreePort());
 312                 }
 313 
 314                 if (withExternalRegistry) {
 315                     Registry registry = startRegistry();
 316                     if (registry == null) {
 317                         // The port is already in use. Cancel and try with a new one.
 318                         continue;
 319                     }
 320                 }
 321 
 322                 jstatdThread = tryToSetupJstatdProcess();
 323             }
 324 
 325             runToolsAndVerify();
 326         } finally {
 327             cleanUpThread(jstatdThread);
 328         }
 329 
 330         // Verify output from jstatd
 331         OutputAnalyzer output = jstatdThread.getOutput();
 332         assertTrue(output.getOutput().isEmpty(),
 333                 "jstatd should get an empty output, got: "
 334                 + Utils.NEW_LINE + output.getOutput());
 335         assertNotEquals(output.getExitValue(), 0,
 336                 "jstatd process exited with unexpected exit code");
 337     }
 338 
 339     private void verifyNoRmiRegistryOnDefaultPort() throws Exception {
 340         try {
 341             Registry registry = LocateRegistry.getRegistry();
 342             registry.list();
 343             throw new Exception("There is already RMI registry on the default port: " + registry);
 344         } catch (RemoteException e) {
 345             // No RMI registry on default port is detected
 346         }
 347     }
 348 
 349 }