diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java index feb255c31711..d03c000c99f8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,6 @@ package com.oracle.svm.core.jfr.events; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.Uninterruptible; @@ -37,10 +36,13 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.monitor.JavaMonitorQueuedSynchronizer; + +import jdk.graal.compiler.word.Word; public class ThreadParkEvent { public static void emit(long startTicks, Object obj, boolean isAbsolute, long time) { - if (HasJfrSupport.get()) { + if (HasJfrSupport.get() && !isInternalPark(obj)) { emit0(startTicks, obj, isAbsolute, time); } } @@ -76,4 +78,22 @@ private static void emit0(long startTicks, Object obj, boolean isAbsolute, long JfrNativeEventWriter.endSmallEvent(data); } } + + /** + * Skip emission if this is an internal park ({@link JavaMonitorWaitEvent} or + * {@link JavaMonitorEnterEvent} will be emitted instead). + */ + private static boolean isInternalPark(Object obj) { + if (obj == null) { + return false; + } + + Class parkedClass = obj.getClass(); + if (JavaMonitorQueuedSynchronizer.class.isAssignableFrom(parkedClass)) { + return true; + } + + Class enclosingClass = parkedClass.getEnclosingClass(); + return enclosingClass != null && JavaMonitorQueuedSynchronizer.class.isAssignableFrom(enclosingClass); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java index 3ca87f848b20..547524d3de38 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java @@ -60,7 +60,7 @@ * */ @BasedOnJDKClass(AbstractQueuedLongSynchronizer.class) -abstract class JavaMonitorQueuedSynchronizer { +public abstract class JavaMonitorQueuedSynchronizer { // Node status bits, also used as argument and return values static final int WAITING = 1; // must be 1 static final int CANCELLED = 0x80000000; // must be negative diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java index 7eb3b3fb47a9..154d4c53af4a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java @@ -53,25 +53,24 @@ public void test() throws Throwable { String[] events = new String[]{JfrEvent.JavaMonitorEnter.getName()}; Recording recording = startRecording(events); - Runnable first = () -> { + firstThread = new Thread(() -> { try { helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } - }; + }); - Runnable second = () -> { + secondThread = new Thread(() -> { try { passedCheckpoint = true; helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } - }; - firstThread = new Thread(first); - secondThread = new Thread(second); + }); + /* Start the first thread so that it can then start the second thread. */ firstThread.start(); firstThread.join(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java index c30ed37cf606..eb93872e1070 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java @@ -73,7 +73,7 @@ private void test(Object obj) throws Throwable { } private void test(Object obj, String className) throws Throwable { - Runnable first = () -> { + firstThread = new Thread(() -> { try { synchronized (obj) { secondThread.start(); @@ -85,25 +85,25 @@ private void test(Object obj, String className) throws Throwable { } catch (InterruptedException e) { throw new RuntimeException(e); } - }; + }); - Runnable second = () -> { + secondThread = new Thread(() -> { passedCheckpoint = true; synchronized (obj) { GraalDirectives.blackhole(obj); } - }; + }); expectedClassName = className; - passedCheckpoint = false; - firstThread = new Thread(first); - secondThread = new Thread(second); /* Now that the data is prepared, start the JFR recording. */ String[] events = new String[]{JfrEvent.JavaMonitorInflate.getName()}; Recording recording = startRecording(events); - /* Generate event with "Monitor Enter" cause. */ + /* + * Generate event with "Monitor Enter" cause. Start the first thread so that it can then + * start the second thread. + */ firstThread.start(); firstThread.join(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestOmitInternalParkEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestOmitInternalParkEvents.java new file mode 100644 index 000000000000..fa26b9098894 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestOmitInternalParkEvents.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.jfr; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; +import java.util.concurrent.locks.LockSupport; + +import org.junit.Test; + +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.util.TimeUtils; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; + +public class TestOmitInternalParkEvents extends JfrRecordingTest { + private static final int MILLIS = 100; + private final MonitorHelper monitorHelper = new MonitorHelper(); + private Thread secondThread; + private volatile boolean passedCheckpoint; + + @Test + public void test() throws Throwable { + String[] events = new String[]{JfrEvent.JavaMonitorEnter.getName(), JfrEvent.JavaMonitorWait.getName(), JfrEvent.ThreadPark.getName()}; + Recording recording = startRecording(events); + + /* Generate monitor enter events. */ + Thread firstThread = new Thread(() -> { + try { + monitorHelper.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + secondThread = new Thread(() -> { + try { + passedCheckpoint = true; + monitorHelper.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + /* Start the first thread so that it can then start the second thread. */ + firstThread.start(); + + firstThread.join(); + secondThread.join(); + + /* Generate monitor wait events. */ + try { + monitorHelper.waitUntilTimeout(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + /* Generate thread park events. */ + LockSupport.parkNanos(this, TimeUtils.millisToNanos(MILLIS)); + + stopRecording(recording, this::validateEvents); + } + + private void validateEvents(List events) { + boolean found = false; + for (RecordedEvent event : events) { + if (event.getEventType().getName().equals(JfrEvent.ThreadPark.getName())) { + RecordedClass parkedClass = event.getValue("parkedClass"); + if (parkedClass != null) { + String parkedClassName = parkedClass.getName(); + assertFalse(parkedClassName.contains("JavaMonitor")); + found = true; + } + } + } + assertTrue("Expected jdk.ThreadPark event not found", found); + } + + private final class MonitorHelper { + private synchronized void doWork() throws InterruptedException { + if (Thread.currentThread().equals(secondThread)) { + /* The second thread doesn't need to do any work. */ + return; + } + + /* Start the second thread while this thread holds the lock. */ + secondThread.start(); + + /* Spin until the second thread blocks. */ + while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) { + Thread.sleep(100); + } + Thread.sleep(MILLIS); + } + + private synchronized void waitUntilTimeout() throws InterruptedException { + wait(MILLIS); + } + } +}