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 }