1 /* 2 * Copyright (c) 2021, 2024, 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 jdk.management 29 * @library /test/lib 30 * @run junit/othervm --enable-native-access=ALL-UNNAMED JfrEvents 31 */ 32 33 import java.io.IOException; 34 import java.nio.file.Path; 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.AtomicBoolean; 43 import java.util.concurrent.locks.LockSupport; 44 import java.util.stream.Collectors; 45 import java.util.stream.Stream; 46 47 import jdk.jfr.EventType; 48 import jdk.jfr.Recording; 49 import jdk.jfr.consumer.RecordedEvent; 50 import jdk.jfr.consumer.RecordingFile; 51 52 import jdk.test.lib.thread.VThreadPinner; 53 import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management 54 import jdk.test.lib.thread.VThreadScheduler; 55 import org.junit.jupiter.api.Test; 56 import org.junit.jupiter.api.BeforeAll; 57 import org.junit.jupiter.params.ParameterizedTest; 58 import org.junit.jupiter.params.provider.ValueSource; 59 import static org.junit.jupiter.api.Assertions.*; 60 61 class JfrEvents { 62 63 @BeforeAll 64 static void setup() { 65 int minParallelism = 2; 66 if (Thread.currentThread().isVirtual()) { 67 minParallelism++; 68 } 69 VThreadRunner.ensureParallelism(minParallelism); 70 } 71 72 /** 73 * Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events. 74 */ 75 @Test 76 void testVirtualThreadStartAndEnd() throws Exception { 77 try (Recording recording = new Recording()) { 78 recording.enable("jdk.VirtualThreadStart"); 79 recording.enable("jdk.VirtualThreadEnd"); 80 81 // execute 100 tasks, each in their own virtual thread 82 recording.start(); 83 ThreadFactory factory = Thread.ofVirtual().factory(); 84 try (var executor = Executors.newThreadPerTaskExecutor(factory)) { 85 for (int i = 0; i < 100; i++) { 86 executor.submit(() -> { }); 87 } 88 Thread.sleep(1000); // give time for thread end events to be recorded 89 } finally { 90 recording.stop(); 91 } 92 93 Map<String, Integer> events = sumEvents(recording); 94 System.err.println(events); 95 96 int startCount = events.getOrDefault("jdk.VirtualThreadStart", 0); 97 int endCount = events.getOrDefault("jdk.VirtualThreadEnd", 0); 98 assertEquals(100, startCount); 99 assertEquals(100, endCount); 100 } 101 } 102 103 /** 104 * Test jdk.VirtualThreadPinned event when parking while pinned. 105 */ 106 @ParameterizedTest 107 @ValueSource(booleans = { true, false }) 108 void testParkWhenPinned(boolean timed) throws Exception { 109 try (Recording recording = new Recording()) { 110 recording.enable("jdk.VirtualThreadPinned"); 111 recording.start(); 112 113 var started = new AtomicBoolean(); 114 var done = new AtomicBoolean(); 115 var vthread = Thread.startVirtualThread(() -> { 116 VThreadPinner.runPinned(() -> { 117 started.set(true); 118 while (!done.get()) { 119 if (timed) { 120 LockSupport.parkNanos(Long.MAX_VALUE); 121 } else { 122 LockSupport.park(); 123 } 124 } 125 }); 126 }); 127 128 try { 129 // wait for thread to start and park 130 awaitTrue(started); 131 await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING); 132 } finally { 133 done.set(true); 134 LockSupport.unpark(vthread); 135 vthread.join(); 136 recording.stop(); 137 } 138 139 assertContainsPinnedEvent(recording, vthread); 140 } 141 } 142 143 /** 144 * Test jdk.VirtualThreadSubmitFailed event. 145 */ 146 @Test 147 void testVirtualThreadSubmitFailed() throws Exception { 148 try (Recording recording = new Recording()) { 149 recording.enable("jdk.VirtualThreadSubmitFailed"); 150 151 recording.start(); 152 try (ExecutorService pool = Executors.newCachedThreadPool()) { 153 Executor scheduler = task -> pool.execute(task); 154 155 // create virtual thread that uses custom scheduler 156 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler); 157 158 // start a thread 159 Thread thread = factory.newThread(LockSupport::park); 160 thread.start(); 161 162 // wait for thread to park 163 await(thread, Thread.State.WAITING); 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 List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed"); 184 System.err.println(submitFailedEvents); 185 assertTrue(submitFailedEvents.size() == 2, "Expected two events"); 186 } 187 } 188 189 /** 190 * Returns the list of events in the given recording with the given name. 191 */ 192 private static List<RecordedEvent> find(Recording recording, String name) throws IOException { 193 Path recordingFile = recordingFile(recording); 194 return RecordingFile.readAllEvents(recordingFile) 195 .stream() 196 .filter(e -> e.getEventType().getName().equals(name)) 197 .toList(); 198 } 199 200 /** 201 * Read the events from the recording and return a map of event name to count. 202 */ 203 private static Map<String, Integer> sumEvents(Recording recording) throws IOException { 204 Path recordingFile = recordingFile(recording); 205 List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile); 206 return events.stream() 207 .map(RecordedEvent::getEventType) 208 .collect(Collectors.groupingBy(EventType::getName, 209 Collectors.summingInt(x -> 1))); 210 } 211 212 /** 213 * Return the file path to the recording file. 214 */ 215 private static Path recordingFile(Recording recording) throws IOException { 216 Path recordingFile = recording.getDestination(); 217 if (recordingFile == null) { 218 ProcessHandle h = ProcessHandle.current(); 219 recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr"); 220 recording.dump(recordingFile); 221 } 222 return recordingFile; 223 } 224 225 /** 226 * Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread. 227 */ 228 private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException { 229 List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned"); 230 assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording"); 231 System.err.println(pinnedEvents); 232 233 long tid = thread.threadId(); 234 assertTrue(pinnedEvents.stream() 235 .anyMatch(e -> e.getThread().getJavaThreadId() == tid), 236 "jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found"); 237 } 238 239 /** 240 * Waits for the given boolean to be set to true. 241 */ 242 private void awaitTrue(AtomicBoolean b) throws InterruptedException { 243 while (!b.get()) { 244 Thread.sleep(10); 245 } 246 } 247 248 /** 249 * Waits for the given thread to reach a given state. 250 */ 251 private static void await(Thread thread, Thread.State expectedState) throws InterruptedException { 252 Thread.State state = thread.getState(); 253 while (state != expectedState) { 254 Thread.sleep(10); 255 state = thread.getState(); 256 } 257 } 258 }