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 }