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