1 /*
   2  * Copyright (c) 2004, 2017, 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.io.IOException;
  26 import java.net.URISyntaxException;
  27 import java.nio.file.Files;
  28 import java.nio.file.Path;
  29 import java.nio.file.Paths;
  30 import java.nio.file.StandardOpenOption;
  31 import java.util.ArrayList;
  32 import java.util.List;
  33 import java.util.Set;
  34 import java.util.UUID;
  35 import java.util.concurrent.Semaphore;
  36 
  37 import jdk.testlibrary.OutputAnalyzer;
  38 import jdk.testlibrary.ProcessTools;
  39 import sun.jvmstat.monitor.MonitorException;
  40 import sun.jvmstat.monitor.MonitoredHost;
  41 import sun.jvmstat.monitor.MonitoredVm;
  42 import sun.jvmstat.monitor.MonitoredVmUtil;
  43 import sun.jvmstat.monitor.VmIdentifier;
  44 import sun.jvmstat.monitor.event.HostEvent;
  45 import sun.jvmstat.monitor.event.HostListener;
  46 import sun.jvmstat.monitor.event.VmStatusChangeEvent;
  47 
  48 /*
  49 
  50  Test starts ten Java processes, each with a unique id.
  51 
  52  Each process creates a file named after the id and then it waits for
  53  the test to remove the file, at which the Java process exits.
  54 
  55  The processes are monitored by the test to make sure notifications
  56  are sent when they are started/terminated.
  57 
  58  To avoid Java processes being left behind, in case of an unexpected
  59  failure, shutdown hooks are installed that remove files when the test
  60  exits. If files are not removed, i.e. due to a JVM crash, the Java
  61  processes will exit themselves after 1000 s.
  62 
  63 */
  64 
  65 /*
  66  * @test
  67  * @bug 4990825
  68  * @summary attach to external but local JVM processes
  69  * @library /lib/testlibrary
  70  * @modules java.management
  71  *          jdk.internal.jvmstat/sun.jvmstat.monitor
  72  *          jdk.internal.jvmstat/sun.jvmstat.monitor.event
  73  * @build jdk.testlibrary.*
  74  * @run main/othervm MonitorVmStartTerminate
  75  */
  76 public final class MonitorVmStartTerminate {
  77 
  78     private static final int PROCESS_COUNT = 10;
  79 
  80     public static void main(String... args) throws Exception {
  81 
  82         MonitoredHost host = MonitoredHost.getMonitoredHost("localhost");
  83         host.setInterval(1); // 1 ms
  84 
  85         String id = UUID.randomUUID().toString();
  86 
  87         List<JavaProcess> javaProcesses = new ArrayList<>();
  88         for (int i = 0; i < PROCESS_COUNT; i++) {
  89             javaProcesses.add(new JavaProcess(id + "_" + i));
  90         }
  91 
  92         Listener listener = new Listener(host, javaProcesses);
  93         host.addHostListener(listener);
  94         for (JavaProcess javaProcess : javaProcesses) {
  95             javaProcess.start();
  96         }
  97 
  98         // Wait for all processes to start before terminating
  99         // them, so pids are not reused within a poll interval.
 100         System.out.println("Waiting for all processes to get started notification");
 101         listener.started.acquire(PROCESS_COUNT);
 102 
 103         for (JavaProcess javaProcess : javaProcesses) {
 104             javaProcess.terminate();
 105         }
 106         System.out.println("Waiting for all processes to get terminated notification");
 107         listener.terminated.acquire(PROCESS_COUNT);
 108 
 109         host.removeHostListener(listener);
 110     }
 111 
 112     private static final class Listener implements HostListener {
 113         private final Semaphore started = new Semaphore(0);
 114         private final Semaphore terminated = new Semaphore(0);
 115         private final MonitoredHost host;
 116         private final List<JavaProcess> processes;
 117 
 118         public Listener(MonitoredHost host, List<JavaProcess> processes) {
 119             this.host = host;
 120             this.processes = processes;
 121             printStatus();
 122         }
 123 
 124         @Override
 125         @SuppressWarnings("unchecked")
 126         public void vmStatusChanged(VmStatusChangeEvent event) {
 127             releaseStarted(event.getStarted());
 128             releaseTerminated(event.getTerminated());
 129             printStatus();
 130         }
 131 
 132         private void printStatus() {
 133             System.out.printf("started=%d, terminated=%d\n",
 134                     started.availablePermits(), terminated.availablePermits());
 135         }
 136 
 137         @Override
 138         public void disconnected(HostEvent arg0) {
 139             // ignore
 140         }
 141 
 142         private void releaseStarted(Set<Integer> ids) {
 143             System.out.println("realeaseStarted(" + ids + ")");
 144             for (Integer id : ids) {
 145                 releaseStarted(id);
 146             }
 147         }
 148 
 149         private void releaseStarted(Integer id) {
 150             for (JavaProcess jp : processes) {
 151                 if (hasMainArgs(id, jp.getMainArgsIdentifier())) {
 152                     // store id for terminated identification
 153                     jp.setId(id);
 154                     System.out.println("RELEASED (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")");
 155                     started.release();
 156                     return;
 157                 }
 158             }
 159         }
 160 
 161         private void releaseTerminated(Set<Integer> ids) {
 162             System.out.println("releaseTerminated(" + ids + ")");
 163             for (Integer id : ids) {
 164                 releaseTerminated(id);
 165             }
 166         }
 167 
 168         private void releaseTerminated(Integer id) {
 169             for (JavaProcess jp : processes) {
 170                 if (id.equals(jp.getId())) {
 171                     System.out.println("RELEASED (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")");
 172                     terminated.release();
 173                     return;
 174                 }
 175             }
 176         }
 177 
 178         private boolean hasMainArgs(Integer id, String args) {
 179             try {
 180                 VmIdentifier vmid = new VmIdentifier("//" + id.intValue());
 181                 MonitoredVm target = host.getMonitoredVm(vmid);
 182                 String monitoredArgs = MonitoredVmUtil.mainArgs(target);
 183                 if (monitoredArgs != null && monitoredArgs.contains(args)) {
 184                     return true;
 185                 }
 186             } catch (URISyntaxException | MonitorException e) {
 187                 // ok. process probably not running
 188             }
 189             return false;
 190         }
 191     }
 192 
 193     public final static class JavaProcess {
 194 
 195         private static final class ShutdownHook extends Thread {
 196             private final JavaProcess javaProcess;
 197 
 198             public ShutdownHook(JavaProcess javaProcess) {
 199                 this.javaProcess = javaProcess;
 200             }
 201 
 202             public void run() {
 203                 javaProcess.terminate();
 204             }
 205         }
 206 
 207         public static void main(String[] args) throws InterruptedException {
 208             try {
 209                 Path path = Paths.get(args[0]);
 210                 createFile(path);
 211                 waitForRemoval(path);
 212             } catch (Throwable t) {
 213                 t.printStackTrace();
 214                 System.exit(1);
 215             }
 216         }
 217 
 218         public Integer getId() {
 219             return id;
 220         }
 221 
 222         public void setId(Integer id) {
 223             this.id = id;
 224         }
 225 
 226         private static void createFile(Path path) throws IOException {
 227             Files.write(path, new byte[0], StandardOpenOption.CREATE);
 228         }
 229 
 230         private static void waitForRemoval(Path path) {
 231             String timeoutFactorText = System.getProperty("test.timeout.factor", "1.0");
 232             double timeoutFactor = Double.parseDouble(timeoutFactorText);
 233             long timeoutNanos = 1000_000_000L*(long)(1000*timeoutFactor);
 234             long start = System.nanoTime();
 235             while (true) {
 236                 long now = System.nanoTime();
 237                 long waited = now - start;
 238                 System.out.println("Waiting for " + path + " to be removed, " + waited + " ns");
 239                 if (!Files.exists(path)) {
 240                     return;
 241                 }
 242                 if (waited > timeoutNanos) {
 243                     System.out.println("Start: " + start);
 244                     System.out.println("Now: " + now);
 245                     System.out.println("Process timed out after " + waited + " ns. Abort.");
 246                     System.exit(1);
 247                 }
 248                 takeNap();
 249             }
 250         }
 251 
 252         private static void takeNap() {
 253             try {
 254                 Thread.sleep(100);
 255             } catch (InterruptedException e) {
 256                 // ignore
 257             }
 258         }
 259 
 260         private final String mainArgsIdentifier;
 261         private final ShutdownHook shutdownHook;
 262         private volatile Integer id;
 263 
 264         public JavaProcess(String mainArgsIdentifier) {
 265             this.mainArgsIdentifier = mainArgsIdentifier;
 266             this.shutdownHook = new ShutdownHook(this);
 267         }
 268 
 269         /**
 270          * Starts a Java process asynchronously.
 271          *
 272          * The process runs until {@link #stop()} is called. If test exits
 273          * unexpectedly the process will be cleaned up by a shutdown hook.
 274          *
 275          * @throws Exception
 276          */
 277         public void start() throws Exception {
 278             Runtime.getRuntime().addShutdownHook(shutdownHook);
 279             System.out.println("Starting " + getMainArgsIdentifier());
 280 
 281             Runnable r = new Runnable() {
 282                 @Override
 283                 public void run() {
 284                     try {
 285                         executeJava();
 286                     } catch (Throwable t) {
 287                         t.printStackTrace();
 288                     }
 289                 }
 290             };
 291             new Thread(r).start();
 292         }
 293 
 294         public void terminate() {
 295             try {
 296                 System.out.println("Terminating " + mainArgsIdentifier);
 297                 // File must be created before proceeding,
 298                 // otherwise Java process may loop forever
 299                 // waiting for file to be removed.
 300                 Path path = Paths.get(mainArgsIdentifier);
 301                 while (!Files.exists(path)) {
 302                     takeNap();
 303                 }
 304                 Files.delete(path);
 305             } catch (IOException e) {
 306                 e.printStackTrace();
 307             }
 308             Runtime.getRuntime().removeShutdownHook(shutdownHook);
 309         }
 310 
 311         private void executeJava() throws Throwable {
 312             String className = JavaProcess.class.getName();
 313             String classPath = System.getProperty("test.classes");
 314             ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
 315                 "-Dtest.timeout.factor=" + System.getProperty("test.timeout.factor", "1.0"),
 316                 "-cp", classPath, className, mainArgsIdentifier);
 317             OutputAnalyzer ob = ProcessTools.executeProcess(pb);
 318             System.out.println("Java Process " + getMainArgsIdentifier() + " stderr:"
 319                     + ob.getStderr());
 320             System.err.println("Java Process " + getMainArgsIdentifier() + " stdout:"
 321                     + ob.getStdout());
 322         }
 323 
 324         public String getMainArgsIdentifier() {
 325             return mainArgsIdentifier;
 326         }
 327     }
 328 }