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
24import java.io.File;
25import java.io.IOException;
26import java.net.URISyntaxException;
27import java.nio.file.Files;
28import java.nio.file.Path;
29import java.nio.file.Paths;
30import java.nio.file.StandardOpenOption;
31import java.util.ArrayList;
32import java.util.List;
33import java.util.Set;
34import java.util.UUID;
35import java.util.concurrent.Semaphore;
36
37import jdk.testlibrary.OutputAnalyzer;
38import jdk.testlibrary.ProcessTools;
39import sun.jvmstat.monitor.MonitorException;
40import sun.jvmstat.monitor.MonitoredHost;
41import sun.jvmstat.monitor.MonitoredVm;
42import sun.jvmstat.monitor.MonitoredVmUtil;
43import sun.jvmstat.monitor.VmIdentifier;
44import sun.jvmstat.monitor.event.HostEvent;
45import sun.jvmstat.monitor.event.HostListener;
46import 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 */
76public 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}
329