1 /* 2 * Copyright (c) 2021, 2023, 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 /** 25 * @test 26 * @summary Basic test for JFR jdk.VirtualThreadXXX events 27 * @requires vm.continuations 28 * @modules jdk.jfr java.base/java.lang:+open 29 * @run junit/othervm JfrEvents 30 */ 31 32 import java.io.IOException; 33 import java.nio.file.Path; 34 import java.time.Duration; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.concurrent.Executor; 38 import java.util.concurrent.ExecutorService; 39 import java.util.concurrent.Executors; 40 import java.util.concurrent.RejectedExecutionException; 41 import java.util.concurrent.ThreadFactory; 42 import java.util.concurrent.atomic.AtomicReference; 43 import java.util.concurrent.locks.LockSupport; 44 import java.util.stream.Collectors; 45 46 import jdk.jfr.EventType; 47 import jdk.jfr.Recording; 48 import jdk.jfr.consumer.RecordedEvent; 49 import jdk.jfr.consumer.RecordingFile; 50 51 import org.junit.jupiter.api.Test; 52 import static org.junit.jupiter.api.Assertions.*; 53 54 class JfrEvents { 55 private static final Object lock = new Object(); 56 57 /** 58 * Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events. 59 */ 60 @Test 61 void testVirtualThreadStartAndEnd() throws Exception { 62 try (Recording recording = new Recording()) { 63 recording.enable("jdk.VirtualThreadStart"); 64 recording.enable("jdk.VirtualThreadEnd"); 65 66 // execute 100 tasks, each in their own virtual thread 67 recording.start(); 68 ThreadFactory factory = Thread.ofVirtual().factory(); 69 try (var executor = Executors.newThreadPerTaskExecutor(factory)) { 70 for (int i = 0; i < 100; i++) { 71 executor.submit(() -> { }); 72 } 73 Thread.sleep(1000); // give time for thread end events to be recorded 74 } finally { 75 recording.stop(); 76 } 77 78 Map<String, Integer> events = sumEvents(recording); 79 System.err.println(events); 80 81 int startCount = events.getOrDefault("jdk.VirtualThreadStart", 0); 82 int endCount = events.getOrDefault("jdk.VirtualThreadEnd", 0); 83 assertEquals(100, startCount); 84 assertEquals(100, endCount); 85 } 86 } 87 88 /** 89 * Test jdk.VirtualThreadPinned event. 90 */ 91 @Test 92 void testVirtualThreadPinned() throws Exception { 93 Runnable[] parkers = new Runnable[] { 94 () -> LockSupport.park(), 95 () -> LockSupport.parkNanos(Duration.ofDays(1).toNanos()) 96 }; 97 98 try (Recording recording = new Recording()) { 99 recording.enable("jdk.VirtualThreadPinned"); 100 101 recording.start(); 102 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 103 for (Runnable parker : parkers) { 104 // execute parking task in virtual thread 105 var threadRef = new AtomicReference<Thread>(); 106 executor.submit(() -> { 107 threadRef.set(Thread.currentThread()); 108 synchronized (lock) { 109 parker.run(); // should pin carrier 110 } 111 }); 112 113 // wait for the task to start and the virtual thread to park 114 Thread thread; 115 while ((thread = threadRef.get()) == null) { 116 Thread.sleep(10); 117 } 118 try { 119 Thread.State state = thread.getState(); 120 while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) { 121 Thread.sleep(10); 122 state = thread.getState(); 123 } 124 } finally { 125 LockSupport.unpark(thread); 126 } 127 } 128 } finally { 129 recording.stop(); 130 } 131 132 Map<String, Integer> events = sumEvents(recording); 133 System.err.println(events); 134 135 // should have a pinned event for each park 136 int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0); 137 assertEquals(parkers.length, pinnedCount); 138 } 139 } 140 141 /** 142 * Test jdk.VirtualThreadSubmitFailed event. 143 */ 144 @Test 145 void testVirtualThreadSubmitFailed() throws Exception { 146 try (Recording recording = new Recording()) { 147 recording.enable("jdk.VirtualThreadSubmitFailed"); 148 149 recording.start(); 150 try (ExecutorService pool = Executors.newCachedThreadPool()) { 151 Executor scheduler = task -> pool.execute(task); 152 153 // create virtual thread that uses custom scheduler 154 ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler).factory(); 155 156 // start a thread 157 Thread thread = factory.newThread(LockSupport::park); 158 thread.start(); 159 160 // wait for thread to park 161 while (thread.getState() != Thread.State.WAITING) { 162 Thread.sleep(10); 163 } 164 165 // shutdown scheduler 166 pool.shutdown(); 167 168 // unpark, the submit should fail 169 try { 170 LockSupport.unpark(thread); 171 fail(); 172 } catch (RejectedExecutionException expected) { } 173 174 // start another thread, it should fail and an event should be recorded 175 try { 176 factory.newThread(LockSupport::park).start(); 177 throw new RuntimeException("RejectedExecutionException expected"); 178 } catch (RejectedExecutionException expected) { } 179 } finally { 180 recording.stop(); 181 } 182 183 Map<String, Integer> events = sumEvents(recording); 184 System.err.println(events); 185 186 int count = events.getOrDefault("jdk.VirtualThreadSubmitFailed", 0); 187 assertEquals(2, count); 188 } 189 } 190 191 /** 192 * Read the events from the recording and return a map of event name to count. 193 */ 194 private static Map<String, Integer> sumEvents(Recording recording) throws IOException { 195 Path recordingFile = recordingFile(recording); 196 List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile); 197 return events.stream() 198 .map(RecordedEvent::getEventType) 199 .collect(Collectors.groupingBy(EventType::getName, 200 Collectors.summingInt(x -> 1))); 201 } 202 203 /** 204 * Return the file path to the recording file. 205 */ 206 private static Path recordingFile(Recording recording) throws IOException { 207 Path recordingFile = recording.getDestination(); 208 if (recordingFile == null) { 209 ProcessHandle h = ProcessHandle.current(); 210 recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr"); 211 recording.dump(recordingFile); 212 } 213 return recordingFile; 214 } 215 }