From c2413f9271372b4f3ec3ddb69b00fe4ce967f76e Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 23 Jul 2025 16:04:29 -0400 Subject: [PATCH 1/3] Don't emit internal park events. Test. --- .../svm/core/jfr/events/ThreadParkEvent.java | 11 ++ .../test/jfr/TestOmitInternalParkEvents.java | 125 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestOmitInternalParkEvents.java 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..e166471e001a 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 @@ -37,10 +37,21 @@ 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.JavaMonitor; public class ThreadParkEvent { public static void emit(long startTicks, Object obj, boolean isAbsolute, long time) { if (HasJfrSupport.get()) { + /* + * Skip emission if corresponding JavaMonitorWait or JavaMonitorEnter events are already + * emitted (this is an internal park). + */ + if (obj != null) { + Class clazz = obj.getClass(); + if (clazz.equals(JavaMonitor.class) || (clazz.getEnclosingClass() != null && clazz.getEnclosingClass().isAssignableFrom(JavaMonitor.class))) { + return; + } + } emit0(startTicks, obj, isAbsolute, time); } } 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..71e3168b6b5b --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestOmitInternalParkEvents.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, 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 com.oracle.svm.core.jfr.JfrEvent; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.locks.LockSupport; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestOmitInternalParkEvents extends JfrRecordingTest { + private static final int MILLIS = 500; + private final Helper helper = new Helper(); + private Thread firstThread; + 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 + Runnable first = () -> { + try { + helper.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable second = () -> { + try { + passedCheckpoint = true; + helper.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + firstThread = new Thread(first); + secondThread = new Thread(second); + + firstThread.start(); + + firstThread.join(); + secondThread.join(); + + // Generate monitor wait events + try { + helper.timeout(); + } catch (InterruptedException e) { + org.junit.Assert.fail(e.getMessage()); + } + + // Generate thread park events + LockSupport.parkNanos(this, 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("com.oracle.svm.core.monitor")); + found = true; + } + } + } + assertTrue("Expected jdk.ThreadPark event not found", found); + } + + private final class Helper { + private synchronized void doWork() throws InterruptedException { + if (Thread.currentThread().equals(secondThread)) { + return; // second thread doesn't need to do work. + } + // ensure ordering of critical section entry + secondThread.start(); + + // spin until second thread blocks + while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) { + Thread.sleep(100); + } + Thread.sleep(MILLIS); + } + + private synchronized void timeout() throws InterruptedException { + wait(MILLIS); + } + } +} From 2cc0ccd13d3d4bb537b77bd786ea43551ef2a70a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 23 Jul 2025 16:51:23 -0400 Subject: [PATCH 2/3] copyright years --- .../src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java | 4 ++-- .../com/oracle/svm/test/jfr/TestOmitInternalParkEvents.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 e166471e001a..32db5018b43d 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 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 index 71e3168b6b5b..77af785939e7 100644 --- 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 @@ -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 From 9d0f64a4ef083060075ba76c3a9337c9c7f4f041 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 28 Aug 2025 09:07:23 +0200 Subject: [PATCH 3/3] Cleanups. --- .../svm/core/jfr/events/ThreadParkEvent.java | 35 ++++++---- .../JavaMonitorQueuedSynchronizer.java | 2 +- .../test/jfr/TestJavaMonitorEnterEvent.java | 11 ++-- .../test/jfr/TestJavaMonitorInflateEvent.java | 16 ++--- .../test/jfr/TestOmitInternalParkEvents.java | 64 ++++++++++--------- 5 files changed, 70 insertions(+), 58 deletions(-) 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 32db5018b43d..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 @@ -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,21 +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.JavaMonitor; +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()) { - /* - * Skip emission if corresponding JavaMonitorWait or JavaMonitorEnter events are already - * emitted (this is an internal park). - */ - if (obj != null) { - Class clazz = obj.getClass(); - if (clazz.equals(JavaMonitor.class) || (clazz.getEnclosingClass() != null && clazz.getEnclosingClass().isAssignableFrom(JavaMonitor.class))) { - return; - } - } + if (HasJfrSupport.get() && !isInternalPark(obj)) { emit0(startTicks, obj, isAbsolute, time); } } @@ -87,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 index 77af785939e7..fa26b9098894 100644 --- 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 @@ -26,22 +26,25 @@ package com.oracle.svm.test.jfr; -import com.oracle.svm.core.jfr.JfrEvent; -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedClass; -import jdk.jfr.consumer.RecordedEvent; -import org.junit.Test; +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 static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +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 = 500; - private final Helper helper = new Helper(); - private Thread firstThread; + private static final int MILLIS = 100; + private final MonitorHelper monitorHelper = new MonitorHelper(); private Thread secondThread; private volatile boolean passedCheckpoint; @@ -50,40 +53,39 @@ 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 - Runnable first = () -> { + /* Generate monitor enter events. */ + Thread firstThread = new Thread(() -> { try { - helper.doWork(); + monitorHelper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } - }; + }); - Runnable second = () -> { + secondThread = new Thread(() -> { try { passedCheckpoint = true; - helper.doWork(); + monitorHelper.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(); secondThread.join(); - // Generate monitor wait events + /* Generate monitor wait events. */ try { - helper.timeout(); + monitorHelper.waitUntilTimeout(); } catch (InterruptedException e) { - org.junit.Assert.fail(e.getMessage()); + fail(e.getMessage()); } - // Generate thread park events - LockSupport.parkNanos(this, MILLIS); + /* Generate thread park events. */ + LockSupport.parkNanos(this, TimeUtils.millisToNanos(MILLIS)); stopRecording(recording, this::validateEvents); } @@ -95,7 +97,7 @@ private void validateEvents(List events) { RecordedClass parkedClass = event.getValue("parkedClass"); if (parkedClass != null) { String parkedClassName = parkedClass.getName(); - assertFalse(parkedClassName.contains("com.oracle.svm.core.monitor")); + assertFalse(parkedClassName.contains("JavaMonitor")); found = true; } } @@ -103,22 +105,24 @@ private void validateEvents(List events) { assertTrue("Expected jdk.ThreadPark event not found", found); } - private final class Helper { + private final class MonitorHelper { private synchronized void doWork() throws InterruptedException { if (Thread.currentThread().equals(secondThread)) { - return; // second thread doesn't need to do work. + /* The second thread doesn't need to do any work. */ + return; } - // ensure ordering of critical section entry + + /* Start the second thread while this thread holds the lock. */ secondThread.start(); - // spin until second thread blocks + /* Spin until the second thread blocks. */ while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) { Thread.sleep(100); } Thread.sleep(MILLIS); } - private synchronized void timeout() throws InterruptedException { + private synchronized void waitUntilTimeout() throws InterruptedException { wait(MILLIS); } }