diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index e5fa7145ca65..2040f42bb78d 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -569,6 +569,7 @@
],
"requiresConcealed" : {
"java.base" : [
+ "jdk.internal.event",
"jdk.internal.misc",
"jdk.internal.vm.annotation",
"jdk.internal.org.objectweb.asm",
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
index 103787588b26..1f7eda4eeb75 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
@@ -25,6 +25,8 @@
package com.oracle.svm.core.posix;
+import java.util.function.BooleanSupplier;
+
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
@@ -47,7 +49,7 @@
import com.oracle.svm.core.posix.headers.Time;
import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
-@AutomaticallyRegisteredImageSingleton(SubstrateSigprofHandler.class)
+@AutomaticallyRegisteredImageSingleton(value = SubstrateSigprofHandler.class, onlyWith = LinuxOnly.class)
public class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler {
public static final long INTERVAL_S = 0;
@@ -134,3 +136,10 @@ protected IsolateThread getThreadLocalKeyValue(UnsignedWord key) {
return (IsolateThread) Pthread.pthread_getspecific((Pthread.pthread_key_t) key);
}
}
+
+class LinuxOnly implements BooleanSupplier {
+ @Override
+ public boolean getAsBoolean() {
+ return Platform.includedIn(Platform.LINUX.class);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
index e66728803603..1b063b855da2 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
@@ -41,6 +41,7 @@
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
import com.oracle.svm.core.os.RawFileOperationSupport;
import com.oracle.svm.core.sampler.SamplerBuffersAccess;
+import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
import com.oracle.svm.core.thread.JavaVMOperation;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMOperationControl;
@@ -384,8 +385,7 @@ protected void operate() {
*/
@Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.")
private void changeEpoch() {
- /* Process all unprocessed sampler buffers before changing the epoch. */
- SamplerBuffersAccess.processSamplerBuffers();
+ processSamplerBuffers();
// Write unflushed data from the thread local buffers but do *not* reinitialize them
// The thread local code will handle space reclamation on their own time
@@ -413,6 +413,24 @@ private void changeEpoch() {
// Now that the epoch changed, re-register all running threads for the new epoch.
SubstrateJVM.getThreadRepo().registerRunningThreads();
}
+
+ /**
+ * The VM is at a safepoint, so all other threads have a native state. However, the SIGPROF
+ * handler may still be executed at any time for any thread (including the current thread).
+ * To prevent races, we need to ensure that there are no threads that execute the SIGPROF
+ * handler while we are accessing the currently active buffers of other threads.
+ */
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private void processSamplerBuffers() {
+ SubstrateSigprofHandler.preventThreadsFromEnteringSigProfHandler();
+ try {
+ SubstrateSigprofHandler.waitUntilAllThreadsExitedSignalHandler();
+ SamplerBuffersAccess.processActiveBuffers();
+ SamplerBuffersAccess.processFullBuffers();
+ } finally {
+ SubstrateSigprofHandler.allowThreadsInSigProfHandler();
+ }
+ }
}
public long getChunkStartNanos() {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java
index 3635233cfd5c..01fb89611283 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java
@@ -24,18 +24,14 @@
*/
package com.oracle.svm.core.jfr;
-import java.util.function.BooleanSupplier;
-
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.annotate.TargetClass;
-import com.oracle.svm.core.jdk.JDK17OrEarlier;
-import com.oracle.svm.util.ReflectionUtil;
/**
- * The event IDs depend on the metadata.xml and therefore vary between JDK versions.
+ * This file contains the VM-level events that Native Image supports on all JDK versions. The event
+ * IDs depend on the JDK version-specific metadata.xml file.
*/
public final class JfrEvent {
public static final JfrEvent ThreadStart = create("jdk.ThreadStart");
@@ -60,7 +56,6 @@ public final class JfrEvent {
public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd");
public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation");
public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter");
- public static final JfrEvent ThreadSleep = create("jdk.ThreadSleep", JDK17OrEarlier.class);
public static final JfrEvent ThreadPark = create("jdk.ThreadPark");
public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait");
@@ -68,18 +63,8 @@ public final class JfrEvent {
private final String name;
@Platforms(Platform.HOSTED_ONLY.class)
- private static JfrEvent create(String name) {
- return create(name, TargetClass.AlwaysIncluded.class);
- }
-
- @Platforms(Platform.HOSTED_ONLY.class)
- private static JfrEvent create(String name, Class extends BooleanSupplier> onlyWith) {
- BooleanSupplier onlyWithProvider = ReflectionUtil.newInstance(onlyWith);
- if (onlyWithProvider.getAsBoolean()) {
- return new JfrEvent(name);
- } else {
- return null;
- }
+ public static JfrEvent create(String name) {
+ return new JfrEvent(name);
}
@Platforms(Platform.HOSTED_ONLY.class)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java
index 405cc7027057..caba4c62be17 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java
@@ -49,7 +49,6 @@
import com.sun.management.internal.PlatformMBeanProviderImpl;
import jdk.jfr.Configuration;
-import jdk.jfr.Event;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.jfc.JFC;
@@ -59,9 +58,9 @@
*
* There are two different kinds of JFR events:
*
- * - Java-level events are defined by a Java class that extends {@link Event} and that is
- * annotated with JFR-specific annotations. Those events are typically triggered by the Java
- * application and a Java {@code EventWriter} object is used when writing the event to a
+ *
- Java-level events are defined by a Java class that extends {@link jdk.internal.event.Event}
+ * and that is annotated with JFR-specific annotations. Those events are typically triggered by the
+ * Java application and a Java {@code EventWriter} object is used when writing the event to a
* buffer.
* - Native events are triggered by the JVM itself and are defined in the JFR metadata.xml file.
* For writing such an event to a buffer, we call into {@link JfrNativeEventWriter} and pass a
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java
index d0dabe6c6816..d62c88d9468e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java
@@ -26,7 +26,7 @@
import java.util.ArrayList;
import java.util.List;
-import jdk.jfr.Event;
+
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
@@ -34,14 +34,23 @@
* Holds all JFR Java-level event classes.
*/
public class JfrJavaEvents {
- private static final List> EVENT_CLASSES = new ArrayList<>();
+ private static final List> EVENT_CLASSES = new ArrayList<>();
+ private static final List> JFR_EVENT_CLASSES = new ArrayList<>();
@Platforms(Platform.HOSTED_ONLY.class)
- public static void registerEventClass(Class extends Event> eventClass) {
+ @SuppressWarnings("unchecked")
+ public static void registerEventClass(Class extends jdk.internal.event.Event> eventClass) {
EVENT_CLASSES.add(eventClass);
+ if (jdk.jfr.Event.class.isAssignableFrom(eventClass)) {
+ JFR_EVENT_CLASSES.add((Class extends jdk.jfr.Event>) eventClass);
+ }
}
- public static List> getAllEventClasses() {
+ public static List> getAllEventClasses() {
return EVENT_CLASSES;
}
+
+ public static List> getJfrEventClasses() {
+ return JFR_EVENT_CLASSES;
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java
index cfd5f8a3a6e8..84e31e4cde57 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java
@@ -116,7 +116,7 @@ private boolean await() {
private void run0() {
/* Process all unprocessed sampler buffers. */
- SamplerBuffersAccess.processSamplerBuffers();
+ SamplerBuffersAccess.processFullBuffers();
JfrChunkWriter chunkWriter = unlockedChunkWriter.lock();
try {
if (chunkWriter.hasOpenFile()) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
index 27725464a2ba..6780c1e26a43 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
@@ -106,27 +106,30 @@ public long getStackTraceId(int skipCount) {
/* Initialize stack walk. */
SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
- SamplerThreadLocal.setSignalHandlerLocallyDisabled(true);
- if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth)) {
- SamplerSampleWriter.begin(data);
- /* Walk the stack. */
- Pointer sp = KnownIntrinsics.readCallerStackPointer();
- CodePointer ip = FrameAccess.singleton().readReturnAddress(sp);
- if (JavaStackWalker.walkCurrentThread(sp, ip, SubstrateSigprofHandler.visitor()) || data.getTruncated()) {
- acquireLock();
- try {
- CIntPointer status = StackValue.get(CIntPointer.class);
- Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
- stackTraceId = getStackTraceId(start, data.getCurrentPos(), data.getHashCode(), status, false);
- if (JfrStackTraceTableEntryStatus.get(status, JfrStackTraceTableEntryStatus.NEW)) {
- SamplerSampleWriter.end(data);
+ SamplerThreadLocal.preventSigProfHandlerExecution();
+ try {
+ if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth, true)) {
+ SamplerSampleWriter.begin(data);
+ /* Walk the stack. */
+ Pointer sp = KnownIntrinsics.readCallerStackPointer();
+ CodePointer ip = FrameAccess.singleton().readReturnAddress(sp);
+ if (JavaStackWalker.walkCurrentThread(sp, ip, SubstrateSigprofHandler.visitor()) || data.getTruncated()) {
+ acquireLock();
+ try {
+ CIntPointer status = StackValue.get(CIntPointer.class);
+ Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
+ stackTraceId = getStackTraceId(start, data.getCurrentPos(), data.getHashCode(), status, false);
+ if (JfrStackTraceTableEntryStatus.get(status, JfrStackTraceTableEntryStatus.NEW)) {
+ SamplerSampleWriter.end(data);
+ }
+ } finally {
+ releaseLock();
}
- } finally {
- releaseLock();
}
}
+ } finally {
+ SamplerThreadLocal.allowSigProfHandlerExecution();
}
- SamplerThreadLocal.setSignalHandlerLocallyDisabled(false);
return stackTraceId;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
index d1ebc70da7fb..473d98277d23 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
@@ -292,7 +292,9 @@ public void storeMetadataDescriptor(byte[] bytes) {
/** See {@link JVM#beginRecording}. */
public void beginRecording() {
- assert !recording;
+ if (recording) {
+ return;
+ }
JfrChunkWriter chunkWriter = unlockedChunkWriter.lock();
try {
@@ -309,7 +311,10 @@ public void beginRecording() {
/** See {@link JVM#endRecording}. */
public void endRecording() {
- assert recording;
+ if (!recording) {
+ return;
+ }
+
JfrEndRecordingOperation vmOp = new JfrEndRecordingOperation();
vmOp.enqueue();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
index 6d5d9963e772..d512844793f3 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
@@ -97,10 +97,18 @@ public boolean isRecording() {
/** See {@link JVM#getAllEventClasses}. */
@Substitute
- public List> getAllEventClasses() {
+ @TargetElement(onlyWith = JDK17OrLater.class)
+ public List> getAllEventClasses() {
return JfrJavaEvents.getAllEventClasses();
}
+ /** See {@link JVM#getAllEventClasses}. */
+ @Substitute
+ @TargetElement(name = "getAllEventClasses", onlyWith = JDK11OrEarlier.class)
+ public List> getAllEventClassesJDK11() {
+ return JfrJavaEvents.getJfrEventClasses();
+ }
+
/** See {@link JVM#getUnloadedEventClassCount}. */
@Substitute
public long getUnloadedEventClassCount() {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java
similarity index 91%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java
index 2c779b5db58a..260124700ccf 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java
@@ -36,7 +36,8 @@
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.SubstrateJVM;
-public class ThreadSleepEvent {
+public class ThreadSleepEventJDK17 {
+ private static final JfrEvent ThreadSleep = JfrEvent.create("jdk.ThreadSleep");
public static void emit(long time, long startTicks) {
if (com.oracle.svm.core.jfr.HasJfrSupport.get()) {
@@ -46,15 +47,15 @@ public static void emit(long time, long startTicks) {
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emit0(long time, long startTicks) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadSleep)) {
+ if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(ThreadSleep)) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
- JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadSleep);
+ JfrNativeEventWriter.beginSmallEvent(data, ThreadSleep);
JfrNativeEventWriter.putLong(data, startTicks);
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks() - startTicks);
JfrNativeEventWriter.putEventThread(data);
- JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadSleep, 0));
+ JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(ThreadSleep, 0));
JfrNativeEventWriter.putLong(data, time);
JfrNativeEventWriter.endSmallEvent(data);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
index cce40c004189..7d2d545d4257 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
@@ -27,6 +27,7 @@
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.struct.SizeOf;
import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
import org.graalvm.word.Pointer;
@@ -35,6 +36,7 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.jfr.SubstrateJVM;
import com.oracle.svm.core.util.UnsignedUtils;
/**
@@ -57,6 +59,8 @@ public static SamplerBuffer allocate(UnsignedWord dataSize) {
if (result.isNonNull()) {
result.setSize(dataSize);
result.setFreeable(false);
+ result.setNext(WordFactory.nullPointer());
+ result.setOwner(0);
reinitialize(result);
}
return result;
@@ -71,7 +75,6 @@ public static void free(SamplerBuffer buffer) {
public static void reinitialize(SamplerBuffer buffer) {
Pointer dataStart = getDataStart(buffer);
buffer.setPos(dataStart);
- buffer.setOwner(0L);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -88,4 +91,9 @@ public static boolean isEmpty(SamplerBuffer buffer) {
public static Pointer getDataEnd(SamplerBuffer buffer) {
return getDataStart(buffer).add(buffer.getSize());
}
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static boolean isOwner(SamplerBuffer buffer, IsolateThread thread) {
+ return buffer.getOwner() == SubstrateJVM.getThreadId(thread);
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
index 45df346c78c9..de9be373c545 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
@@ -42,17 +42,27 @@ class SamplerBufferPool {
private static final VMMutex mutex = new VMMutex("SamplerBufferPool");
private static long bufferCount;
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static void releaseBufferAndAdjustCount(SamplerBuffer threadLocalBuffer) {
adjustBufferCount0(threadLocalBuffer);
}
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static void adjustBufferCount() {
adjustBufferCount0(WordFactory.nullPointer());
}
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
+ public static SamplerBuffer tryAllocateBuffer() {
+ mutex.lockNoTransition();
+ try {
+ return tryAllocateBuffer0();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) {
mutex.lockNoTransition();
try {
@@ -76,7 +86,7 @@ private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) {
}
}
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static void releaseThreadLocalBuffer(SamplerBuffer buffer) {
/*
* buffer is null if the thread is not running yet, or we did not perform the stack walk for
@@ -99,15 +109,22 @@ private static void releaseThreadLocalBuffer(SamplerBuffer buffer) {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean allocateAndPush() {
VMError.guarantee(bufferCount >= 0);
- JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal();
- SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize()));
+ SamplerBuffer buffer = tryAllocateBuffer0();
if (buffer.isNonNull()) {
SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer);
- bufferCount++;
return true;
- } else {
- return false;
}
+ return false;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static SamplerBuffer tryAllocateBuffer0() {
+ JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal();
+ SamplerBuffer result = SamplerBufferAccess.allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize()));
+ if (result.isNonNull()) {
+ bufferCount++;
+ }
+ return result;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -118,9 +135,8 @@ private static boolean popAndFree() {
SamplerBufferAccess.free(buffer);
bufferCount--;
return true;
- } else {
- return false;
}
+ return false;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
index 4f271398f32b..3bd0c98bc9c4 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
@@ -25,6 +25,7 @@
package com.oracle.svm.core.sampler;
+import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.c.type.CIntPointer;
@@ -38,10 +39,11 @@
import com.oracle.svm.core.code.CodeInfoTable;
import com.oracle.svm.core.code.FrameInfoQueryResult;
import com.oracle.svm.core.code.UntetheredCodeInfo;
-import com.oracle.svm.core.jfr.HasJfrSupport;
import com.oracle.svm.core.jfr.JfrStackTraceRepository;
import com.oracle.svm.core.jfr.SubstrateJVM;
import com.oracle.svm.core.jfr.events.ExecutionSampleEvent;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.VMError;
/**
@@ -54,29 +56,28 @@ private SamplerBuffersAccess() {
}
@Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
- public static void processSamplerBuffers() {
- if (!HasJfrSupport.get()) {
- /*
- * This method will become reachable on WINDOWS during the building of JFR tests via
- * com.oracle.svm.core.jfr.JfrChunkWriter.closeFile, and it will fail during
- * InvocationPlugin call if we do not have this check.
- *
- * Note that although we are building the JFR tests for Windows as well, they will not
- * be executed because of guard in com.oracle.svm.test.jfr.JfrTest.checkForJFR.
- *
- * Once we have support for Windows, this check will become obsolete.
- */
- return;
+ public static void processActiveBuffers() {
+ VMOperation.guaranteeInProgressAtSafepoint("Needed for iterating the threads");
+
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(thread);
+ if (buffer.isNonNull()) {
+ processSamplerBuffer(buffer);
+
+ assert SamplerBufferAccess.isOwner(buffer, thread);
+ assert SamplerThreadLocal.getThreadLocalBuffer(thread) == buffer;
+ }
}
+ }
- SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(true);
+ @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
+ public static void processFullBuffers() {
while (true) {
/* Pop top buffer from stack of full buffers. */
SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer();
if (buffer.isNull()) {
- /* No buffers to process. */
- SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false);
- return;
+ /* No remaining buffers. */
+ break;
}
/* Process the buffer. */
@@ -84,6 +85,7 @@ public static void processSamplerBuffers() {
if (buffer.getFreeable()) {
SamplerBufferAccess.free(buffer);
} else {
+ SamplerBufferAccess.reinitialize(buffer);
SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer);
}
}
@@ -91,6 +93,8 @@ public static void processSamplerBuffers() {
@Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
public static void processSamplerBuffer(SamplerBuffer buffer) {
+ assert buffer.isNonNull();
+
Pointer end = buffer.getPos();
Pointer current = SamplerBufferAccess.getDataStart(buffer);
while (current.belowThan(end)) {
@@ -119,8 +123,11 @@ public static void processSamplerBuffer(SamplerBuffer buffer) {
stackTraceRepo.acquireLock();
try {
long stackTraceId = stackTraceRepo.getStackTraceId(current, current.add(sampleSize), sampleHash, status, true);
+ long owner = buffer.getOwner();
+ assert owner != 0;
+
if (JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, JfrStackTraceRepository.JfrStackTraceTableEntryStatus.SERIALIZED)) {
- ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState);
+ ExecutionSampleEvent.writeExecutionSample(sampleTick, owner, stackTraceId, threadState);
/* Sample is already there, skip the rest of sample plus END_MARK symbol. */
current = current.add(sampleSize).add(SamplerSampleWriter.END_MARKER_SIZE);
} else {
@@ -130,7 +137,7 @@ public static void processSamplerBuffer(SamplerBuffer buffer) {
while (current.belowThan(end)) {
long ip = current.readLong(0);
if (ip == SamplerSampleWriter.END_MARKER) {
- ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState);
+ ExecutionSampleEvent.writeExecutionSample(sampleTick, owner, stackTraceId, threadState);
current = current.add(SamplerSampleWriter.END_MARKER_SIZE);
break;
} else {
@@ -143,6 +150,8 @@ public static void processSamplerBuffer(SamplerBuffer buffer) {
stackTraceRepo.releaseLock();
}
}
+
+ SamplerBufferAccess.reinitialize(buffer);
}
@Uninterruptible(reason = "The handle should only be accessed from uninterruptible code to prevent that the GC frees the CodeInfo.", callerMustBe = true)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
index 495773aa96ac..9e0ea6b07978 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
@@ -40,15 +40,21 @@ private SamplerSampleWriterDataAccess() {
* native buffer.
*/
@Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true)
- public static boolean initialize(SamplerSampleWriterData data, int skipCount, int maxDepth) {
+ public static boolean initialize(SamplerSampleWriterData data, int skipCount, int maxDepth, boolean allowBufferAllocation) {
SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer();
if (buffer.isNull()) {
/* Pop first free buffer from the pool. */
buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer();
if (buffer.isNull()) {
- /* No available buffers on the pool. Fallback! */
- SamplerThreadLocal.increaseMissedSamples();
- return false;
+ if (allowBufferAllocation) {
+ buffer = SamplerBufferPool.tryAllocateBuffer();
+ }
+
+ if (buffer.isNull()) {
+ /* No available buffers on the pool. Fallback! */
+ SamplerThreadLocal.increaseMissedSamples();
+ return false;
+ }
}
SamplerThreadLocal.setThreadLocalBuffer(buffer);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
index 1819cabab3b2..16cd489700b6 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
@@ -25,6 +25,7 @@
package com.oracle.svm.core.sampler;
+import com.oracle.svm.core.thread.VMOperation;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.word.UnsignedWord;
@@ -43,7 +44,7 @@ public class SamplerThreadLocal implements ThreadListener {
private static final FastThreadLocalWord localBuffer = FastThreadLocalFactory.createWord("SamplerThreadLocal.localBuffer");
private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("SamplerThreadLocal.missedSamples");
private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("SamplerThreadLocal.unparseableStacks");
- private static final FastThreadLocalInt isSignalHandlerLocallyDisabled = FastThreadLocalFactory.createInt("SamplerThreadLocal.isSignalHandlerLocallyDisabled");
+ private static final FastThreadLocalInt isSigProfHandlerDisabled = FastThreadLocalFactory.createInt("SamplerThreadLocal.isSignalHandlerDisabled");
/**
* The data that we are using during the stack walk, allocated on the stack.
*/
@@ -102,7 +103,13 @@ static void teardown(IsolateThread isolateThread) {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static SamplerBuffer getThreadLocalBuffer() {
- return localBuffer.get();
+ return getThreadLocalBuffer(CurrentIsolate.getCurrentThread());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static SamplerBuffer getThreadLocalBuffer(IsolateThread thread) {
+ assert CurrentIsolate.getCurrentThread() == thread || VMOperation.isInProgressAtSafepoint();
+ return localBuffer.get(thread);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -132,13 +139,22 @@ public static long getUnparseableStacks() {
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void setSignalHandlerLocallyDisabled(boolean isDisabled) {
- isSignalHandlerLocallyDisabled.set(isDisabled ? 1 : 0);
+ public static void preventSigProfHandlerExecution() {
+ int newValue = isSigProfHandlerDisabled.get() + 1;
+ assert newValue > 0;
+ isSigProfHandlerDisabled.set(newValue);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void allowSigProfHandlerExecution() {
+ int newValue = isSigProfHandlerDisabled.get() - 1;
+ assert newValue >= 0;
+ isSigProfHandlerDisabled.set(newValue);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static boolean isSignalHandlerLocallyDisabled() {
- return isSignalHandlerLocallyDisabled.get() == 1;
+ public static boolean isSignalHandlerDisabled() {
+ return isSigProfHandlerDisabled.get() >= 1;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
index 9bc7891a2724..36762a4fc853 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
@@ -56,6 +56,7 @@
import com.oracle.svm.core.graal.nodes.WriteHeapBaseNode;
import com.oracle.svm.core.heap.VMOperationInfos;
import com.oracle.svm.core.jdk.RuntimeSupport;
+import com.oracle.svm.core.jdk.UninterruptibleUtils;
import com.oracle.svm.core.jdk.management.ManagementFeature;
import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean;
import com.oracle.svm.core.jfr.HasJfrSupport;
@@ -73,7 +74,6 @@
import com.oracle.svm.core.util.VMError;
@AutomaticallyRegisteredFeature
-@SuppressWarnings("unused")
class SubstrateSigprofHandlerFeature implements InternalFeature {
@Override
@@ -89,23 +89,16 @@ public List> getRequiredFeatures() {
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
if (!SamplerHasSupport.get() && !HasJfrSupport.get()) {
- /* No Sampler and JFR support. */
return;
}
- /* The common initialization part between Sampler and JFR. */
-
- /* Create stack visitor. */
+ /* Needed for both JFR and the sampler. */
ImageSingletons.add(SamplerStackWalkVisitor.class, new SamplerStackWalkVisitor());
-
- /* Add thread listener. */
ThreadListenerSupport.get().register(new SamplerThreadLocal());
- /* Add startup hook. */
- RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook());
-
/* The Sampler initialization part. */
if (SamplerHasSupport.get()) {
+ RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook());
VMError.guarantee(ImageSingletons.contains(RegisterDumper.class));
/* Add isolate listener. */
@@ -166,10 +159,9 @@ public void execute(boolean isFirstIsolate) {
* @see SamplerBufferStack
*/
public abstract class SubstrateSigprofHandler {
-
public static class Options {
- @Option(help = "Allow sampling-based profiling. Default: disabled in execution.")//
- static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(Boolean.FALSE) {
+ @Option(help = "Allow sampling-based profiling.")//
+ static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(false) {
@Override
protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) {
if (newValue) {
@@ -179,7 +171,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o
}
};
- @SuppressWarnings("unused") @Option(help = "Start sampling-based profiling with options.")//
+ @Option(help = "Start sampling-based profiling with options.")//
public static final RuntimeOptionKey StartSamplingBasedProfiling = new RuntimeOptionKey<>("") {
@Override
protected void onValueUpdate(EconomicMap, Object> values, String oldValue, String newValue) {
@@ -192,15 +184,18 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol
}
private boolean enabled;
- private volatile boolean isSignalHandlerGloballyDisabled;
private final SamplerBufferStack availableBuffers;
private final SamplerBufferStack fullBuffers;
+ private final UninterruptibleUtils.AtomicInteger isSignalHandlerDisabledTemporarily;
+ private final UninterruptibleUtils.AtomicInteger threadsInSignalHandler;
private SubstrateThreadMXBean threadMXBean;
@Platforms(Platform.HOSTED_ONLY.class)
protected SubstrateSigprofHandler() {
this.availableBuffers = new SamplerBufferStack();
this.fullBuffers = new SamplerBufferStack();
+ this.isSignalHandlerDisabledTemporarily = new UninterruptibleUtils.AtomicInteger(0);
+ this.threadsInSignalHandler = new UninterruptibleUtils.AtomicInteger(0);
}
@Fold
@@ -208,28 +203,59 @@ public static SubstrateSigprofHandler singleton() {
return ImageSingletons.lookup(SubstrateSigprofHandler.class);
}
+ @Fold
+ static UninterruptibleUtils.AtomicInteger threadsInSignalHandler() {
+ return singleton().threadsInSignalHandler;
+ }
+
@Fold
public static SamplerStackWalkVisitor visitor() {
return ImageSingletons.lookup(SamplerStackWalkVisitor.class);
}
- private static boolean isOSSupported() {
- return Platform.includedIn(Platform.LINUX.class);
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void preventThreadsFromEnteringSigProfHandler() {
+ if (ImageSingletons.contains(SubstrateSigprofHandler.class)) {
+ singleton().preventThreadsFromEnteringSigProfHandler0();
+ }
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- boolean isProfilingEnabled() {
- return enabled;
+ public static void allowThreadsInSigProfHandler() {
+ if (ImageSingletons.contains(SubstrateSigprofHandler.class)) {
+ singleton().allowThreadsInSigProfHandler0();
+ }
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- private boolean isSignalHandlerDisabled() {
- return isSignalHandlerGloballyDisabled || SamplerThreadLocal.isSignalHandlerLocallyDisabled();
+ public static void waitUntilAllThreadsExitedSignalHandler() {
+ if (ImageSingletons.contains(SubstrateSigprofHandler.class)) {
+ singleton().waitUntilAllThreadsExitedSignalHandler0();
+ }
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public void setSignalHandlerGloballyDisabled(boolean isDisabled) {
- isSignalHandlerGloballyDisabled = isDisabled;
+ private void preventThreadsFromEnteringSigProfHandler0() {
+ int value = isSignalHandlerDisabledTemporarily.incrementAndGet();
+ assert value > 0;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private void allowThreadsInSigProfHandler0() {
+ int value = isSignalHandlerDisabledTemporarily.decrementAndGet();
+ assert value >= 0;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private void waitUntilAllThreadsExitedSignalHandler0() {
+ while (threadsInSignalHandler.get() > 0) {
+ VMThreads.singleton().yield();
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ boolean isProfilingEnabled() {
+ return enabled;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -251,20 +277,15 @@ SubstrateThreadMXBean substrateThreadMXBean() {
* Installs the platform dependent sigprof handler.
*/
void install() {
- if (JfrManager.isJFREnabled()) {
+ if (JfrManager.isJFREnabled() && Options.SamplingBasedProfiling.getValue()) {
threadMXBean = (SubstrateThreadMXBean) ManagementFactory.getThreadMXBean();
+
/* Call VM operation to initialize the sampler and the threads. */
InitializeSamplerOperation initializeSamplerOperation = new InitializeSamplerOperation();
initializeSamplerOperation.enqueue();
- if (Options.SamplingBasedProfiling.getValue()) {
- if (isOSSupported()) {
- /* After the VM operations finishes. Install handler and start profiling. */
- install0();
- } else {
- VMError.shouldNotReachHere("Sampling-based profiling is currently supported only on LINUX!");
- }
- }
+ /* After the VM operations finishes. Install handler and start profiling. */
+ install0();
}
}
@@ -293,60 +314,67 @@ private static boolean isIPInJavaCode(RegisterDumper.Context uContext) {
@Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
protected static void doUninterruptibleStackWalk(RegisterDumper.Context uContext) {
- CodePointer ip;
- Pointer sp;
- if (isIPInJavaCode(uContext)) {
- ip = (CodePointer) RegisterDumper.singleton().getIP(uContext);
- sp = (Pointer) RegisterDumper.singleton().getSP(uContext);
- } else {
- JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
- if (anchor.isNull()) {
- /*
- * The anchor is still null if the function is interrupted during prologue. See:
- * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet
- */
+ /*
+ * To prevent races, it is crucial that the thread count is incremented before doing any
+ * other checks.
+ */
+ threadsInSignalHandler().incrementAndGet();
+ try {
+ if (isProfilingDisallowed()) {
+ SamplerThreadLocal.increaseMissedSamples();
return;
}
- ip = anchor.getLastJavaIP();
- sp = anchor.getLastJavaSP();
- if (ip.isNull() || sp.isNull()) {
+ CodePointer ip;
+ Pointer sp;
+ if (isIPInJavaCode(uContext)) {
+ ip = (CodePointer) RegisterDumper.singleton().getIP(uContext);
+ sp = (Pointer) RegisterDumper.singleton().getSP(uContext);
+ } else {
+ JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
+ if (anchor.isNull()) {
+ /*
+ * The anchor is still null if the function is interrupted during prologue. See:
+ * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet
+ */
+ return;
+ }
+
+ ip = anchor.getLastJavaIP();
+ sp = anchor.getLastJavaSP();
+ if (ip.isNull() || sp.isNull()) {
+ /*
+ * It can happen that anchor is in list of all anchors, but its IP and SP are
+ * not filled yet.
+ */
+ return;
+ }
+ }
+
+ /* Initialize stack walk. */
+ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
+ /* Buffer size constrains stack walk size. */
+ if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE, false)) {
+ SamplerSampleWriter.begin(data);
/*
- * It can happen that anchor is in list of all anchors, but its IP and SP are not
- * filled yet.
+ * Walk the stack.
+ *
+ * We should commit the sample if: the stack walk was done successfully or the stack
+ * walk was interrupted because stack size exceeded given depth.
*/
- return;
+ if (JavaStackWalker.walkCurrentThread(sp, ip, visitor()) || data.getTruncated()) {
+ SamplerSampleWriter.end(data);
+ }
}
+ } finally {
+ threadsInSignalHandler().decrementAndGet();
}
+ }
- /* Test if the current thread's signal handler is disabled, or if holds the stack's lock. */
- if (singleton().isSignalHandlerDisabled() || singleton().availableBuffers().isLockedByCurrentThread() || singleton().fullBuffers().isLockedByCurrentThread()) {
- /*
- * The current thread already holds the stack's lock, so we can't access it. It's way
- * better to lose one sample, then potentially the whole buffer.
- *
- * In case of disabled signal handler, if we proceed forward it could pollute the JFR
- * output.
- */
- SamplerThreadLocal.increaseMissedSamples();
- return;
- }
-
- /* Initialize stack walk. */
- SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
- /* Buffer size constrains stack walk size. */
- if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE)) {
- SamplerSampleWriter.begin(data);
- /*
- * Walk the stack.
- *
- * We should commit the sample if: the stack walk was done successfully or the stack
- * walk was interrupted because stack size exceeded given depth.
- */
- if (JavaStackWalker.walkCurrentThread(sp, ip, visitor()) || data.getTruncated()) {
- SamplerSampleWriter.end(data);
- }
- }
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean isProfilingDisallowed() {
+ return singleton().isSignalHandlerDisabledTemporarily.get() > 0 || SamplerThreadLocal.isSignalHandlerDisabled() || singleton().availableBuffers().isLockedByCurrentThread() ||
+ singleton().fullBuffers().isLockedByCurrentThread();
}
/** Called from the platform dependent sigprof handler to enter isolate. */
@@ -400,7 +428,7 @@ protected void operate() {
@Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.")
private void initialize() {
/*
- * Iterate all over all thread and initialize the thread-local storage of each thread.
+ * Iterate over all threads and initialize the thread-local storage of each thread.
*/
for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
SamplerThreadLocal.initialize(thread);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
index 2c0f772b9fab..d634a359649c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
@@ -27,6 +27,7 @@
import java.lang.Thread.UncaughtExceptionHandler;
import java.security.AccessControlContext;
import java.security.AccessController;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -36,11 +37,14 @@
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.word.Pointer;
-import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.AlwaysInline;
import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.jfr.events.ThreadSleepEvent;
+import com.oracle.svm.core.annotate.Alias;
+import com.oracle.svm.core.annotate.TargetClass;
+import com.oracle.svm.core.jdk.JDK19OrLater;
+import com.oracle.svm.core.jfr.events.ThreadSleepEventJDK17;
import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.util.ReflectionUtil;
@@ -342,13 +346,33 @@ static void initNewThreadLocalsAndLoader(Target_java_lang_Thread tjlt, boolean a
}
static void sleep(long millis) throws InterruptedException {
- long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks();
+ /* Starting with JDK 19, the thread sleep event is implemented as a Java-level event. */
+ if (JavaVersionUtil.JAVA_SPEC >= 19) {
+ if (com.oracle.svm.core.jfr.HasJfrSupport.get() && Target_jdk_internal_event_ThreadSleepEvent.isTurnedOn()) {
+ Target_jdk_internal_event_ThreadSleepEvent event = new Target_jdk_internal_event_ThreadSleepEvent();
+ try {
+ event.time = TimeUnit.MILLISECONDS.toNanos(millis);
+ event.begin();
+ sleep0(millis);
+ } finally {
+ event.commit();
+ }
+ } else {
+ sleep0(millis);
+ }
+ } else {
+ long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks();
+ sleep0(millis);
+ ThreadSleepEventJDK17.emit(millis, startTicks);
+ }
+ }
+
+ private static void sleep0(long millis) throws InterruptedException {
if (supportsVirtual() && isVirtualDisallowLoom(Thread.currentThread()) && !LoomSupport.isEnabled()) {
VirtualThreads.singleton().sleepMillis(millis);
} else {
PlatformThreads.sleep(millis);
}
- ThreadSleepEvent.emit(millis, startTicks);
}
static boolean isAlive(Thread thread) {
@@ -384,3 +408,17 @@ public static void blockedOn(Target_sun_nio_ch_Interruptible b) {
}
}
}
+
+@TargetClass(className = "jdk.internal.event.ThreadSleepEvent", onlyWith = JDK19OrLater.class)
+final class Target_jdk_internal_event_ThreadSleepEvent {
+ @Alias public long time;
+
+ @Alias
+ public static native boolean isTurnedOn();
+
+ @Alias
+ public native void begin();
+
+ @Alias
+ public native void commit();
+}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java
index aa3d7984ea13..fbccdebb2bdd 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java
@@ -50,8 +50,9 @@
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.util.ModuleSupport;
+import com.oracle.svm.util.ReflectionUtil;
-import jdk.jfr.Event;
+import jdk.internal.event.Event;
import jdk.jfr.internal.JVM;
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -76,6 +77,7 @@ public void afterRegistration(AfterRegistrationAccess access) {
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventFeature.class, false, "jdk.jfr", "jdk.jfr.events");
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrFeature.class, false, "jdk.jfr", "jdk.jfr.events");
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventSubstitution.class, false, "jdk.internal.vm.ci", "jdk.vm.ci.hotspot");
+ ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventFeature.class, false, "java.base", "jdk.internal.event");
}
@Override
@@ -92,10 +94,8 @@ public void duringSetup(DuringSetupAccess c) {
@Override
public void beforeAnalysis(Feature.BeforeAnalysisAccess access) {
if (JavaVersionUtil.JAVA_SPEC < 19) {
- Class> eventClass = access.findClassByName("jdk.internal.event.Event");
- if (eventClass != null) {
- access.registerSubtypeReachabilityHandler(JfrEventFeature::eventSubtypeReachable, eventClass);
- }
+ /* In JDK 19+, events don't have an eventHandler field anymore. */
+ access.registerSubtypeReachabilityHandler(JfrEventFeature::eventSubtypeReachable, Event.class);
}
}
@@ -115,12 +115,13 @@ public void beforeCompilation(BeforeCompilationAccess a) {
// Off-set by one for error-catcher
JfrTraceId.assign(clazz, hub.getTypeID() + 1);
}
+
+ /* Store the event configuration in the dynamic hub companion. */
if (JavaVersionUtil.JAVA_SPEC >= 19) {
try {
FeatureImpl.CompilationAccessImpl accessImpl = ((FeatureImpl.CompilationAccessImpl) a);
Method getConfiguration = JVM.class.getDeclaredMethod("getConfiguration", Class.class);
for (var newEventClass : JfrJavaEvents.getAllEventClasses()) {
- /* Store the event configuration in the companion. */
Object ec = getConfiguration.invoke(JVM.getJVM(), newEventClass);
DynamicHub dynamicHub = accessImpl.getMetaAccess().lookupJavaType(newEventClass).getHub();
dynamicHub.setJrfEventConfiguration(ec);
@@ -132,23 +133,16 @@ public void beforeCompilation(BeforeCompilationAccess a) {
}
private static void eventSubtypeReachable(DuringAnalysisAccess a, Class> c) {
- DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;
- if (c.getCanonicalName().equals("jdk.jfr.Event") ||
- c.getCanonicalName().equals("jdk.internal.event.Event") ||
- c.getCanonicalName().equals("jdk.jfr.events.AbstractJDKEvent") ||
- c.getCanonicalName().equals("jdk.jfr.events.AbstractBufferStatisticsEvent") ||
- Modifier.isAbstract(c.getModifiers())) {
+ if (Modifier.isAbstract(c.getModifiers())) {
return;
}
- try {
- Field f = c.getDeclaredField("eventHandler");
- RuntimeReflection.register(f);
- access.rescanRoot(f);
- if (!access.concurrentReachabilityHandlers()) {
- access.requireAnalysisIteration();
- }
- } catch (Exception e) {
- throw VMError.shouldNotReachHere("Unable to register eventHandler for: " + c.getCanonicalName(), e);
+
+ DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;
+ Field f = ReflectionUtil.lookupField(c, "eventHandler");
+ RuntimeReflection.register(f);
+ access.rescanRoot(f);
+ if (!access.concurrentReachabilityHandlers()) {
+ access.requireAnalysisIteration();
}
}
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java
index 7bf6e1702a4f..4697eaa9924c 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java
@@ -24,11 +24,15 @@
*/
package com.oracle.svm.hosted.jfr;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap;
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
+import org.graalvm.nativeimage.AnnotationAccess;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
@@ -38,9 +42,9 @@
import com.oracle.svm.core.jfr.JfrEventWriterAccess;
import com.oracle.svm.core.jfr.JfrJavaEvents;
import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.util.ReflectionUtil;
import jdk.internal.misc.Unsafe;
-import jdk.jfr.Event;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.SecuritySupport;
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -50,31 +54,33 @@
import jdk.vm.ci.meta.Signature;
/**
- * This class triggers the class redefinition (see {@link JVM#retransformClasses}) for all
- * {@link Event} classes that are visited during static analysis.
+ * This class triggers the class redefinition (see {@link JVM#retransformClasses}) for all event
+ * classes that are visited during static analysis.
*/
@Platforms(Platform.HOSTED_ONLY.class)
public class JfrEventSubstitution extends SubstitutionProcessor {
- private final ResolvedJavaType jdkJfrEvent;
+ private final ResolvedJavaType baseEventType;
private final ConcurrentHashMap typeSubstitution;
private final ConcurrentHashMap methodSubstitutions;
private final ConcurrentHashMap fieldSubstitutions;
+ private final EconomicMap> mirrorEventMapping;
JfrEventSubstitution(MetaAccessProvider metaAccess) {
- jdkJfrEvent = metaAccess.lookupJavaType(Event.class);
+ baseEventType = metaAccess.lookupJavaType(jdk.internal.event.Event.class);
ResolvedJavaType jdkJfrEventWriter = metaAccess.lookupJavaType(JfrEventWriterAccess.getEventWriterClass());
changeWriterResetMethod(jdkJfrEventWriter);
typeSubstitution = new ConcurrentHashMap<>();
methodSubstitutions = new ConcurrentHashMap<>();
fieldSubstitutions = new ConcurrentHashMap<>();
+ mirrorEventMapping = createMirrorEventsMapping();
}
@Override
public ResolvedJavaField lookup(ResolvedJavaField field) {
ResolvedJavaType type = field.getDeclaringClass();
if (needsClassRedefinition(type)) {
- typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass);
+ typeSubstitution.computeIfAbsent(type, this::initEventClass);
return fieldSubstitutions.computeIfAbsent(field, JfrEventSubstitution::initEventField);
}
return field;
@@ -84,7 +90,7 @@ public ResolvedJavaField lookup(ResolvedJavaField field) {
public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
ResolvedJavaType type = method.getDeclaringClass();
if (needsClassRedefinition(type)) {
- typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass);
+ typeSubstitution.computeIfAbsent(type, this::initEventClass);
return methodSubstitutions.computeIfAbsent(method, JfrEventSubstitution::initEventMethod);
}
return method;
@@ -93,7 +99,7 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
@Override
public ResolvedJavaType lookup(ResolvedJavaType type) {
if (needsClassRedefinition(type)) {
- typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass);
+ typeSubstitution.computeIfAbsent(type, this::initEventClass);
}
return type;
}
@@ -139,14 +145,23 @@ private static ResolvedJavaMethod initEventMethod(ResolvedJavaMethod oldMethod)
throw VMError.shouldNotReachHere("Could not re-resolve method: " + oldMethod);
}
- private static Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException {
+ private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException {
try {
- Class extends Event> newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType).asSubclass(Event.class);
+ Class extends jdk.internal.event.Event> newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType)
+ .asSubclass(jdk.internal.event.Event.class);
eventType.initialize();
+
+ // It is crucial that mirror event are registered before the actual events.
+ Class extends jdk.jfr.Event> mirrorEventClass = mirrorEventMapping.get(newEventClass.getName());
+ if (mirrorEventClass != null) {
+ SecuritySupport.registerMirror(mirrorEventClass);
+ }
+
SecuritySupport.registerEvent(newEventClass);
+
JfrJavaEvents.registerEventClass(newEventClass);
// the reflection registration for the event handler field is delayed to the JfrFeature
- // duringAnalysis callback so it does not not race/interfere with other retransforms
+ // duringAnalysis callback so it does not race/interfere with other retransforms
JVM.getJVM().retransformClasses(new Class>[]{newEventClass});
return Boolean.TRUE;
} catch (Throwable ex) {
@@ -155,7 +170,7 @@ private static Boolean initEventClass(ResolvedJavaType eventType) throws Runtime
}
private boolean needsClassRedefinition(ResolvedJavaType type) {
- return !type.isAbstract() && jdkJfrEvent.isAssignableFrom(type) && !jdkJfrEvent.equals(type);
+ return !type.isAbstract() && baseEventType.isAssignableFrom(type) && !baseEventType.equals(type);
}
/**
@@ -205,4 +220,31 @@ private static Method getMethodToFetchMetaspaceMethod(Class> method) throws No
}
}
}
+
+ /*
+ * Mirror events contain the JFR-specific annotations. The mirrored event does not have any
+ * dependency on JFR-specific classes. If the mirrored event is used, we must ensure that the
+ * mirror event is registered as well. Otherwise, incorrect JFR metadata would be emitted.
+ */
+ @SuppressWarnings("unchecked")
+ private static EconomicMap> createMirrorEventsMapping() {
+ EconomicMap> result = EconomicMap.create();
+ if (JavaVersionUtil.JAVA_SPEC >= 17) {
+ Class extends Annotation> mirrorEventAnnotationClass = (Class extends Annotation>) ReflectionUtil.lookupClass(false, "jdk.jfr.internal.MirrorEvent");
+ Class> jdkEventsClass = ReflectionUtil.lookupClass(false, "jdk.jfr.internal.instrument.JDKEvents");
+ Class>[] mirrorEventClasses = ReflectionUtil.readStaticField(jdkEventsClass, "mirrorEventClasses");
+ for (int i = 0; i < mirrorEventClasses.length; i++) {
+ Class extends jdk.jfr.Event> mirrorEventClass = (Class extends jdk.jfr.Event>) mirrorEventClasses[i];
+ Annotation mirrorEvent = AnnotationAccess.getAnnotation(mirrorEventClass, mirrorEventAnnotationClass);
+ Method m = ReflectionUtil.lookupMethod(mirrorEventAnnotationClass, "className");
+ try {
+ String className = (String) m.invoke(mirrorEvent);
+ result.put(className, mirrorEventClass);
+ } catch (Exception e) {
+ throw VMError.shouldNotReachHere(e);
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index c2f285091684..b6f2de9ffb23 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -28,7 +28,14 @@
import static org.junit.Assume.assumeTrue;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
+import java.util.List;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.nativeimage.hosted.Feature;
@@ -41,6 +48,7 @@
import com.oracle.svm.test.jfr.utils.Jfr;
import com.oracle.svm.test.jfr.utils.JfrFileParser;
import com.oracle.svm.test.jfr.utils.LocalJfr;
+import com.oracle.svm.util.ClassUtil;
import com.oracle.svm.util.ModuleSupport;
import jdk.jfr.Recording;
@@ -58,30 +66,21 @@ public static void checkForJFR() {
}
@Before
- public void startRecording() {
- try {
- jfr = new LocalJfr();
- recording = jfr.createRecording(getClass().getName());
- enableEvents();
- jfr.startRecording(recording);
- } catch (Exception e) {
- Assert.fail("Fail to start recording! Cause: " + e.getMessage());
- }
+ public void startRecording() throws Throwable {
+ jfr = new LocalJfr();
+ recording = jfr.createRecording(getClass().getName());
+ enableEvents();
+ jfr.startRecording(recording);
}
@After
- public void endRecording() {
+ public void endRecording() throws Throwable {
try {
jfr.endRecording(recording);
checkRecording();
- } catch (Exception e) {
- Assert.fail("Fail to stop recording! Cause: " + e.getMessage());
+ validateEvents();
} finally {
- try {
- jfr.cleanupRecording(recording);
- } catch (Exception e) {
- Assert.fail("Fail to cleanup recording! Cause: " + e.getMessage());
- }
+ jfr.cleanupRecording(recording);
}
}
@@ -96,6 +95,9 @@ private void enableEvents() {
}
}
+ public void validateEvents() throws Throwable {
+ }
+
private void checkEvents() {
HashSet seenEvents = new HashSet<>();
try (RecordingFile recordingFile = new RecordingFile(recording.getDestination())) {
@@ -125,6 +127,32 @@ private void checkRecording() throws AssertionError {
Assert.fail("Failed to parse recording: " + e.getMessage());
}
}
+
+ private static class ChronologicalComparator implements Comparator {
+ @Override
+ public int compare(RecordedEvent e1, RecordedEvent e2) {
+ return e1.getEndTime().compareTo(e2.getEndTime());
+ }
+ }
+
+ private Path makeCopy(String testName) throws IOException { // from jdk 19
+ Path p = recording.getDestination();
+ if (p == null) {
+ File directory = new File(".");
+ p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName + ".jfr").toPath();
+ recording.dump(p);
+ }
+ return p;
+ }
+
+ protected List getEvents() throws IOException {
+ Path p = makeCopy(ClassUtil.getUnqualifiedName(getClass()));
+ List events = RecordingFile.readAllEvents(p);
+ Collections.sort(events, new ChronologicalComparator());
+ // remove events that are not in the list of tested events
+ events.removeIf(event -> (Arrays.stream(getTestedEvents()).noneMatch(testedEvent -> (testedEvent.equals(event.getEventType().getName())))));
+ return events;
+ }
}
class JfrTestFeature implements Feature {
@@ -135,15 +163,5 @@ public void afterRegistration(AfterRegistrationAccess access) {
* com.oracle.svm.test.jfr.utils.poolparsers.ClassConstantPoolParser.parse
*/
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrTestFeature.class, false, "jdk.internal.vm.compiler", "org.graalvm.compiler.serviceprovider");
-
- /*
- * Use of com.oracle.svm.core.sampler.SamplerBuffer,
- * com.oracle.svm.core.sampler.SamplerBufferAccess.allocate,
- * com.oracle.svm.core.sampler.SamplerBufferAccess.free,
- * com.oracle.svm.core.sampler.SamplerBuffersAccess.processSamplerBuffer and
- * com.oracle.svm.core.sampler.SamplerThreadLocal.setThreadLocalBuffer in
- * com.oracle.svm.test.jfr.TestStackTraceEvent.test.
- */
- ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrTestFeature.class, false, "org.graalvm.nativeimage.builder", "com.oracle.svm.core.sampler");
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
index 04f5f9234afc..88f0f1e30aa3 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
@@ -26,9 +26,10 @@
package com.oracle.svm.test.jfr;
-import com.oracle.svm.test.jfr.events.ClassEvent;
import org.junit.Test;
+import com.oracle.svm.test.jfr.events.ClassEvent;
+
public class TestClassEvent extends JfrTest {
@Override
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
new file mode 100644
index 000000000000..fa14c37c90f9
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
@@ -0,0 +1,107 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorEnterEvent extends JfrTest {
+ private static final int MILLIS = 60;
+
+ private final Helper helper = new Helper();
+ private Thread firstThread;
+ private Thread secondThread;
+ private volatile boolean passedCheckpoint;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorEnter"};
+ }
+
+ @Override
+ public void validateEvents() throws Throwable {
+ boolean found = false;
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ if (event. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) {
+ // verify previous owner
+ assertTrue("Previous owner is wrong", event. getValue("previousOwner").getJavaName().equals(firstThread.getName()));
+ found = true;
+ break;
+ }
+ }
+ assertTrue("Expected monitor blocked event not found", found);
+ }
+
+ @Test
+ public void test() throws Exception {
+ 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();
+ }
+
+ private 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(10);
+ }
+ Thread.sleep(MILLIS);
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
new file mode 100644
index 000000000000..e9521c824c7d
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
@@ -0,0 +1,141 @@
+/*
+ * 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 static java.lang.Math.abs;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitEvent extends JfrTest {
+ private static final int MILLIS = 50;
+ private static final int COUNT = 10;
+
+ private final Helper helper = new Helper();
+ private String producerName;
+ private String consumerName;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+
+ @Override
+ public void validateEvents() throws Throwable {
+ int prodCount = 0;
+ int consCount = 0;
+ String lastEventThreadName = null; // should alternate if buffer is 1
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
+ assertTrue("No event thread", eventThread != null);
+ if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) ||
+ !event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+
+ assertTrue("Wrong event duration", event.getDuration().toMillis() >= MILLIS);
+ assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue());
+
+ if (lastEventThreadName == null) {
+ lastEventThreadName = notifThread;
+ }
+ assertTrue("Not alternating", lastEventThreadName.equals(notifThread));
+ if (eventThread.equals(producerName)) {
+ prodCount++;
+ assertTrue("Wrong notifier", notifThread.equals(consumerName));
+ } else if (eventThread.equals(consumerName)) {
+ consCount++;
+ assertTrue("Wrong notifier", notifThread.equals(producerName));
+ }
+ lastEventThreadName = eventThread;
+ }
+ assertFalse("Wrong number of events: " + prodCount + " " + consCount,
+ abs(prodCount - consCount) > 1 || abs(consCount - COUNT) > 1);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Runnable consumer = () -> {
+ try {
+ helper.consume();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable producer = () -> {
+ try {
+ helper.produce();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ Thread tc = new Thread(consumer);
+ Thread tp = new Thread(producer);
+ producerName = tp.getName();
+ consumerName = tc.getName();
+
+ tp.start();
+ tc.start();
+
+ tp.join();
+ tc.join();
+ }
+
+ private class Helper {
+ private int count = 0;
+ private final int bufferSize = 1;
+
+ public synchronized void produce() throws InterruptedException {
+ for (int i = 0; i < COUNT; i++) {
+ while (count >= bufferSize) {
+ wait();
+ }
+ Thread.sleep(MILLIS);
+ count++;
+ notify();
+ }
+ }
+
+ public synchronized void consume() throws InterruptedException {
+ for (int i = 0; i < COUNT; i++) {
+ while (count == 0) {
+ wait();
+ }
+ Thread.sleep(MILLIS);
+ count--;
+ notify();
+ }
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
new file mode 100644
index 000000000000..99c82f4dd0d4
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
@@ -0,0 +1,164 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitInterruptEvent extends JfrTest {
+ private static final int MILLIS = 50;
+
+ private Helper helper = new Helper();
+ private Thread interruptedThread;
+ private Thread interrupterThread;
+ private Thread simpleWaitThread;
+ private Thread simpleNotifyThread;
+ private boolean interruptedFound;
+ private boolean simpleWaitFound;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+
+ @Override
+ public void validateEvents() throws Throwable {
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(interrupterThread.getName()) &&
+ !eventThread.equals(interruptedThread.getName()) &&
+ !eventThread.equals(simpleNotifyThread.getName()) &&
+ !eventThread.equals(simpleWaitThread.getName())) {
+ continue;
+ }
+ if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+ assertTrue("Event is wrong duration." + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
+ assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue());
+
+ if (eventThread.equals(interruptedThread.getName())) {
+ assertTrue("Notifier of interrupted thread should be null", notifThread == null);
+ interruptedFound = true;
+ } else if (eventThread.equals(simpleWaitThread.getName())) {
+ assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyThread.getName(), notifThread.equals(simpleNotifyThread.getName()));
+ simpleWaitFound = true;
+ }
+ }
+ assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " interrupted: " + interruptedFound,
+ simpleWaitFound && interruptedFound);
+ }
+
+ private void testInterruption() throws Exception {
+ Runnable interrupted = () -> {
+ try {
+ helper.interrupt(); // must enter first
+ throw new RuntimeException("Was not interrupted!!");
+ } catch (InterruptedException e) {
+ // should get interrupted
+ }
+ };
+ interruptedThread = new Thread(interrupted);
+
+ Runnable interrupter = () -> {
+ try {
+ helper.interrupt();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
+ };
+
+ interrupterThread = new Thread(interrupter);
+ interruptedThread.start();
+ interruptedThread.join();
+ interrupterThread.join();
+ }
+
+ private void testWaitNotify() throws Exception {
+ Runnable simpleWaiter = () -> {
+ try {
+ helper.simpleNotify();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
+ };
+
+ Runnable simpleNotifier = () -> {
+ try {
+ helper.simpleNotify();
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+ };
+
+ simpleWaitThread = new Thread(simpleWaiter);
+ simpleNotifyThread = new Thread(simpleNotifier);
+
+ simpleWaitThread.start();
+
+ simpleWaitThread.join();
+ simpleNotifyThread.join();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testInterruption();
+ testWaitNotify();
+ }
+
+ private class Helper {
+ public synchronized void interrupt() throws InterruptedException {
+ if (Thread.currentThread().equals(interruptedThread)) {
+ // Ensure T1 enters critical section first
+ interrupterThread.start();
+ wait(); // allow T2 to enter section
+ } else if (Thread.currentThread().equals(interrupterThread)) {
+ // If T2 is in the critical section T1 is already waiting.
+ Thread.sleep(MILLIS);
+ interruptedThread.interrupt();
+ }
+ }
+
+ public synchronized void simpleNotify() throws InterruptedException {
+ if (Thread.currentThread().equals(simpleWaitThread)) {
+ simpleNotifyThread.start();
+ wait();
+ } else if (Thread.currentThread().equals(simpleNotifyThread)) {
+ Thread.sleep(MILLIS);
+ notify();
+ }
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
new file mode 100644
index 000000000000..5a70dbd44b2b
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitNotifyAllEvent extends JfrTest {
+ private static final int MILLIS = 50;
+
+ private Helper helper = new Helper();
+ private Thread producerThread1;
+ private Thread producerThread2;
+ private Thread consumerThread;
+ private boolean notifierFound;
+ private int waitersFound;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+
+ @Override
+ public void validateEvents() throws Throwable {
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(producerThread1.getName()) &&
+ !eventThread.equals(producerThread2.getName()) &&
+ !eventThread.equals(consumerThread.getName())) {
+ continue;
+ }
+ if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+
+ assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS);
+ if (eventThread.equals(consumerThread.getName())) {
+ assertTrue("Should have timed out.", event. getValue("timedOut").booleanValue());
+ notifierFound = true;
+ } else {
+ assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue());
+ assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName()));
+ waitersFound++;
+ }
+
+ }
+ assertTrue("Couldn't find expected wait events. NotifierFound: " + notifierFound + " waitersFound: " + waitersFound,
+ notifierFound && waitersFound == 2);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Runnable producer = () -> {
+ try {
+ helper.doWork();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable consumer = () -> {
+ try {
+ helper.doWork();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ producerThread1 = new Thread(producer);
+ producerThread2 = new Thread(producer);
+ consumerThread = new Thread(consumer);
+
+ producerThread1.start();
+
+ consumerThread.join();
+ producerThread1.join();
+ producerThread2.join();
+ }
+
+ private class Helper {
+ public synchronized void doWork() throws InterruptedException {
+ if (Thread.currentThread().equals(consumerThread)) {
+ wait(MILLIS);
+ notifyAll(); // should wake up both producers
+ } else if (Thread.currentThread().equals(producerThread1)) {
+ producerThread2.start();
+ wait();
+ } else if (Thread.currentThread().equals(producerThread2)) {
+ consumerThread.start();
+ wait();
+ }
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
new file mode 100644
index 000000000000..908a0db77b3a
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
@@ -0,0 +1,159 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitTimeoutEvent extends JfrTest {
+ private static final int MILLIS = 50;
+
+ private final Helper helper = new Helper();
+ private Thread unheardNotifierThread;
+ private Thread timeoutThread;
+ private Thread simpleWaitThread;
+ private Thread simpleNotifyThread;
+ private boolean timeoutFound;
+ private boolean simpleWaitFound;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+
+ @Override
+ public void validateEvents() throws Throwable {
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(unheardNotifierThread.getName()) &&
+ !eventThread.equals(timeoutThread.getName()) &&
+ !eventThread.equals(simpleNotifyThread.getName()) &&
+ !eventThread.equals(simpleWaitThread.getName())) {
+ continue;
+ }
+ if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+ assertTrue("Event is wrong duration:" + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
+ if (eventThread.equals(timeoutThread.getName())) {
+ assertTrue("Notifier of timeout thread should be null", notifThread == null);
+ assertTrue("Should have timed out.", event. getValue("timedOut").booleanValue());
+ timeoutFound = true;
+ } else if (eventThread.equals(simpleWaitThread.getName())) {
+ assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyThread.getName()));
+ simpleWaitFound = true;
+ }
+
+ }
+ assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " timeout: " + timeoutFound,
+ simpleWaitFound && timeoutFound);
+ }
+
+ private void testTimeout() throws InterruptedException {
+ Runnable unheardNotifier = () -> {
+ helper.unheardNotify();
+ };
+
+ Runnable timouter = () -> {
+ try {
+ helper.timeout();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
+ };
+
+ unheardNotifierThread = new Thread(unheardNotifier);
+ timeoutThread = new Thread(timouter);
+
+ timeoutThread.start();
+ timeoutThread.join();
+
+ // wait for timeout before trying to notify
+ unheardNotifierThread.start();
+ unheardNotifierThread.join();
+
+ }
+
+ private void testWaitNotify() throws Exception {
+ Runnable simpleWaiter = () -> {
+ try {
+ helper.simpleNotify();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
+ };
+
+ Runnable simpleNotifier = () -> {
+ try {
+ helper.simpleNotify();
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+ };
+
+ simpleWaitThread = new Thread(simpleWaiter);
+ simpleNotifyThread = new Thread(simpleNotifier);
+
+ simpleWaitThread.start();
+
+ simpleWaitThread.join();
+ simpleNotifyThread.join();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testTimeout();
+ testWaitNotify();
+ }
+
+ private class Helper {
+ public synchronized void timeout() throws InterruptedException {
+ wait(MILLIS);
+ }
+
+ public synchronized void unheardNotify() {
+ notify();
+ }
+
+ public synchronized void simpleNotify() throws InterruptedException {
+ if (Thread.currentThread().equals(simpleWaitThread)) {
+ simpleNotifyThread.start();
+ wait();
+ } else if (Thread.currentThread().equals(simpleNotifyThread)) {
+ Thread.sleep(MILLIS);
+ notify();
+ }
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
index 12d440eec701..5c6e64027120 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
@@ -25,22 +25,14 @@
package com.oracle.svm.test.jfr;
-import org.graalvm.word.WordFactory;
import org.junit.Test;
-import com.oracle.svm.core.jfr.HasJfrSupport;
-import com.oracle.svm.core.sampler.SamplerBuffer;
-import com.oracle.svm.core.sampler.SamplerBufferAccess;
-import com.oracle.svm.core.sampler.SamplerBuffersAccess;
-import com.oracle.svm.core.sampler.SamplerThreadLocal;
import com.oracle.svm.test.jfr.events.StackTraceEvent;
/**
* Test if event ({@link StackTraceEvent}) with stacktrace payload is working.
*/
public class TestStackTraceEvent extends JfrTest {
- private static final int LOCAL_BUFFER_SIZE = 1024;
-
@Override
public String[] getTestedEvents() {
return new String[]{
@@ -50,37 +42,11 @@ public String[] getTestedEvents() {
@Test
public void test() throws Exception {
- if (!HasJfrSupport.get()) {
- /*
- * The static analysis will find reachable the com.oracle.svm.core.jfr.SubstrateJVM via
- * processSamplerBuffer call. Since we are not supporting JFR on Windows yet, JfrFeature
- * will not add the SubstrateJVM to the list of all image singletons and therefore
- * InvocationPlugin will throw an exception while folding the SubstrateJVM (see
- * SubstrateJVM.get).
- *
- * Note that although we are building this JFR test for Windows as well, it will not be
- * executed because of guard in com.oracle.svm.test.jfr.JfrTest.checkForJFR.
- *
- * Once we have support for Windows, this check will become obsolete.
- */
- return;
- }
-
- /* Set thread-local buffer before stack walk. */
- SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(LOCAL_BUFFER_SIZE));
- SamplerThreadLocal.setThreadLocalBuffer(buffer);
-
/*
* Create and commit an event. This will trigger
* com.oracle.svm.core.jfr.JfrStackTraceRepository.getStackTraceId(int) call and stack walk.
*/
StackTraceEvent event = new StackTraceEvent();
event.commit();
-
- /* Call manually buffer processing. */
- SamplerBuffersAccess.processSamplerBuffer(buffer);
-
- /* We need to free memory manually as well afterward. */
- SamplerBufferAccess.free(buffer);
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
index 8585ff196f73..5bf127a9c185 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
@@ -26,9 +26,10 @@
package com.oracle.svm.test.jfr;
-import com.oracle.svm.test.jfr.events.StringEvent;
import org.junit.Test;
+import com.oracle.svm.test.jfr.events.StringEvent;
+
public class TestStringEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java
new file mode 100644
index 000000000000..650886322d89
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java
@@ -0,0 +1,70 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestThreadSleepEvent extends JfrTest {
+ private static final int MILLIS = 50;
+
+ private Thread sleepingThread;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.ThreadSleep"};
+ }
+
+ @Override
+ public void validateEvents() throws IOException {
+ boolean foundSleepEvent = false;
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ if (!eventThread.equals(sleepingThread.getName())) {
+ continue;
+ }
+ if (event.getDuration().toMillis() < MILLIS) {
+ continue;
+ }
+ foundSleepEvent = true;
+ break;
+ }
+ assertTrue("Sleep event not found.", foundSleepEvent);
+ }
+
+ @Test
+ public void test() throws Exception {
+ sleepingThread = Thread.currentThread();
+ Thread.sleep(MILLIS);
+ }
+}