1 /*
   2  * Copyright (c) 2013, 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 static jdk.testlibrary.Asserts.*;
  32 import jdk.testlibrary.JDKToolLauncher;
  33 import jdk.testlibrary.OutputAnalyzer;
  34 import jdk.testlibrary.ProcessThread;
  35 import jdk.testlibrary.TestThread;
  36 import jdk.testlibrary.Utils;
  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 String 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     /**
  86      * Parse pid from jps output
  87      */
  88     private String parsePid(String tool, OutputAnalyzer output) throws Exception {
  89         String[] lines = output.getOutput().split(Utils.NEW_LINE);
  90         String pid = null;
  91         int count = 0;
  92         String processName = tool;
  93         if (tool == "rmiregistry") {
  94             processName = "registryimpl";
  95         }
  96         for (String line : lines) {
  97             if (line.toLowerCase().matches("^\\d+\\s{1}" + processName + "$")) {
  98                 pid = line.split(" ")[0];
  99                 count++;
 100             }
 101         }
 102         if (count > 1) {
 103             throw new Exception("Expected one " + tool
 104                     + " process, got " + count + ". Test will be canceled.");
 105         }
 106 
 107         return pid;
 108     }
 109 
 110     private String getToolPid(String tool)
 111             throws Exception {
 112         OutputAnalyzer output = runJps();
 113         return parsePid(tool, output);
 114     }
 115 
 116     private String waitOnTool(String tool, TestThread thread) throws Throwable {
 117         while (true) {
 118             String pid = getToolPid(tool);
 119 
 120             if (pid != null) {
 121                 System.out.println(tool + " pid: " + pid);
 122                 return pid;
 123             }
 124 
 125             Throwable t = thread.getUncaught();
 126             if (t != null) {
 127                 if (t.getMessage().contains(
 128                         "java.rmi.server.ExportException: Port already in use")) {
 129                     System.out.println("Port already in use. Trying to restart with a new one...");
 130                     Thread.sleep(100);
 131                     return null;
 132                 } else {
 133                     // Something unexpected has happened
 134                     throw new Throwable(t);
 135                 }
 136             }
 137 
 138             System.out.println("Waiting until " + tool + " is running...");
 139             Thread.sleep(100);
 140         }
 141     }
 142 
 143     private void log(String caption, String... cmd) {
 144         System.out.println(Utils.NEW_LINE + caption + ":");
 145         System.out.println(Arrays.toString(cmd).replace(",", ""));
 146     }
 147 
 148     private String getDestination() throws UnknownHostException {
 149         String option = Utils.getHostname();
 150         if (port != null) {
 151             option += ":" + port;
 152         }
 153         if (serverName != null) {
 154             option += "/" + serverName;
 155         }
 156         return option;
 157     }
 158 
 159     /**
 160      * Depending on test settings command line can look like:
 161      *
 162      * jps -J-XX:+UsePerfData hostname
 163      * jps -J-XX:+UsePerfData hostname:port
 164      * jps -J-XX:+UsePerfData hostname/serverName
 165      * jps -J-XX:+UsePerfData hostname:port/serverName
 166      */
 167     private OutputAnalyzer runJps() throws Exception {
 168         JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jps");
 169         launcher.addVMArg("-XX:+UsePerfData");
 170         launcher.addToolArg(getDestination());
 171 
 172         String[] cmd = launcher.getCommand();
 173         log("Start jps", cmd);
 174 
 175         ProcessBuilder processBuilder = new ProcessBuilder(cmd);
 176         OutputAnalyzer output = new OutputAnalyzer(processBuilder.start());
 177         System.out.println(output.getOutput());
 178 
 179         return output;
 180     }
 181 
 182     /**
 183      * Verifies output form jps contains pids and programs' name information.
 184      * The function will discard any lines that come before the first line with pid.
 185      * This can happen if the JVM outputs a warning message for some reason
 186      * before running jps.
 187      *
 188      * The output can look like:
 189      * 35536 Jstatd
 190      * 35417 Main
 191      * 31103 org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar
 192      */
 193     private void verifyJpsOutput(OutputAnalyzer output) throws Exception {
 194         output.shouldHaveExitValue(0);
 195         assertFalse(output.getOutput().isEmpty(), "Output should not be empty");
 196 
 197         boolean foundFirstLineWithPid = false;
 198         String[] lines = output.getOutput().split(Utils.NEW_LINE);
 199         for (String line : lines) {
 200             if (!foundFirstLineWithPid) {
 201                 foundFirstLineWithPid = line.matches(JPS_OUTPUT_REGEX);
 202                 continue;
 203             }
 204             assertTrue(line.matches(JPS_OUTPUT_REGEX),
 205                     "Output does not match the pattern" + Utils.NEW_LINE + line);
 206         }
 207         assertTrue(foundFirstLineWithPid, "Invalid output");
 208     }
 209 
 210     /**
 211      * Depending on test settings command line can look like:
 212      *
 213      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname 250 5
 214      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname:port 250 5
 215      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname/serverName 250 5
 216      * jstat -J-XX:+UsePerfData -J-Duser.language=en -gcutil pid@hostname:port/serverName 250 5
 217      */
 218     private OutputAnalyzer runJstat() throws Exception {
 219         JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstat");
 220         launcher.addVMArg("-XX:+UsePerfData");
 221         launcher.addVMArg("-Duser.language=en");
 222         launcher.addToolArg("-gcutil");
 223         String destination = "";
 224         if (jstatdPid != null) {
 225             destination = jstatdPid + "@";
 226         }
 227         destination += getDestination();
 228         launcher.addToolArg(destination);
 229         launcher.addToolArg(Integer.toString(JSTAT_GCUTIL_INTERVAL_MS));
 230         launcher.addToolArg(Integer.toString(JSTAT_GCUTIL_SAMPLES));
 231 
 232         String[] cmd = launcher.getCommand();
 233         log("Start jstat", cmd);
 234 
 235         ProcessBuilder processBuilder = new ProcessBuilder(cmd);
 236         OutputAnalyzer output = new OutputAnalyzer(processBuilder.start());
 237         System.out.println(output.getOutput());
 238 
 239         return output;
 240     }
 241 
 242     private void verifyJstatOutput(OutputAnalyzer output)
 243             throws Exception {
 244         output.shouldHaveExitValue(0);
 245         assertFalse(output.getOutput().isEmpty(), "Output should not be empty");
 246 
 247         JstatGCutilParser gcutilParser = new JstatGCutilParser(
 248                 output.getOutput());
 249         gcutilParser.parse(JSTAT_GCUTIL_SAMPLES);
 250     }
 251 
 252     private void runToolsAndVerify() throws Exception {
 253         OutputAnalyzer output = runJps();
 254         verifyJpsOutput(output);
 255         jstatdPid = parsePid("jstatd", output);
 256 
 257         output = runJstat();
 258         verifyJstatOutput(output);
 259     }
 260 
 261     private Registry startRegistry()
 262             throws InterruptedException, RemoteException {
 263         Registry registry = null;
 264         try {
 265             System.out.println("Start rmiregistry on port " + port);
 266             registry = LocateRegistry
 267                     .createRegistry(Integer.parseInt(port));
 268         } catch (RemoteException e) {
 269             if (e.getMessage().contains("Port already in use")) {
 270                 System.out.println("Port already in use. Trying to restart with a new one...");
 271                 Thread.sleep(100);
 272                 return null;
 273             } else {
 274                 throw e;
 275             }
 276         }
 277         return registry;
 278     }
 279 
 280     private void cleanUpThread(ProcessThread thread) throws Throwable {
 281         if (thread != null) {
 282             thread.stopProcess();
 283             thread.joinAndThrow();
 284         }
 285     }
 286 
 287     /**
 288      * Depending on test settings command line can look like:
 289      *
 290      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy
 291      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -p port
 292      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -n serverName
 293      * jstatd -J-XX:+UsePerfData -J-Djava.security.policy=all.policy -p port -n serverName
 294      */
 295     private String[] getJstatdCmd() throws UnknownHostException {
 296         JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jstatd");
 297         launcher.addVMArg("-XX:+UsePerfData");
 298         String testSrc = System.getProperty("test.src");
 299         File policy = new File(testSrc, "all.policy");
 300         launcher.addVMArg("-Djava.security.policy=" + policy.getAbsolutePath());
 301         if (port != null) {
 302             launcher.addToolArg("-p");
 303             launcher.addToolArg(port);
 304         }
 305         if (serverName != null) {
 306             launcher.addToolArg("-n");
 307             launcher.addToolArg(serverName);
 308         }
 309 
 310         String[] cmd = launcher.getCommand();
 311         log("Start jstatd", cmd);
 312         return cmd;
 313     }
 314 
 315     private ProcessThread tryToSetupJstatdProcess() throws Throwable {
 316         ProcessThread jstatdThread = new ProcessThread("Jstatd-Thread",
 317                 getJstatdCmd());
 318         try {
 319             jstatdThread.start();
 320             // Make sure jstatd is up and running
 321             if (waitOnTool("jstatd", jstatdThread) == null) {
 322                 // The port is already in use. Cancel and try with new one.
 323                 jstatdThread.stopProcess();
 324                 jstatdThread.join();
 325                 return null;
 326             }
 327         } catch (Throwable t) {
 328             // Something went wrong in the product - clean up!
 329             cleanUpThread(jstatdThread);
 330             throw t;
 331         }
 332 
 333         return jstatdThread;
 334     }
 335 
 336     public void doTest() throws Throwable {
 337         ProcessThread jstatdThread = null;
 338         try {
 339             while (jstatdThread == null) {
 340                 if (!useDefaultPort || withExternalRegistry) {
 341                     port = Integer.toString(Utils.getFreePort());
 342                 }
 343 
 344                 if (withExternalRegistry) {
 345                     Registry registry = startRegistry();
 346                     if (registry == null) {
 347                         // The port is already in use. Cancel and try with new one.
 348                         continue;
 349                     }
 350                 }
 351 
 352                 jstatdThread = tryToSetupJstatdProcess();
 353             }
 354 
 355             runToolsAndVerify();
 356         } finally {
 357             cleanUpThread(jstatdThread);
 358         }
 359     }
 360 
 361 }