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