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 49c4355ae3a8..fff97e050cd6 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 @@ -38,7 +38,7 @@ * IDs depend on the JDK version (see metadata.xml file) and are computed at image build time. */ public final class JfrEvent { - public static final JfrEvent ThreadStart = create("jdk.ThreadStart"); + public static final JfrEvent ThreadStart = create("jdk.ThreadStart", 4); public static final JfrEvent ThreadEnd = create("jdk.ThreadEnd"); public static final JfrEvent ThreadCPULoad = create("jdk.ThreadCPULoad"); public static final JfrEvent DataLoss = create("jdk.DataLoss"); @@ -60,34 +60,41 @@ public final class JfrEvent { public static final JfrEvent SafepointBegin = create("jdk.SafepointBegin", JfrEventFlags.HasDuration); public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd", JfrEventFlags.HasDuration); public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation", JfrEventFlags.HasDuration); - public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", JfrEventFlags.HasDuration); - public static final JfrEvent ThreadPark = create("jdk.ThreadPark", JfrEventFlags.HasDuration); - public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", JfrEventFlags.HasDuration); - public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", JfrEventFlags.HasDuration); - public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB"); + public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", 5, JfrEventFlags.HasDuration); + public static final JfrEvent ThreadPark = create("jdk.ThreadPark", 5, JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", 5, JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", 5, JfrEventFlags.HasDuration); + public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB", 5); public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary"); public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics"); - public static final JfrEvent SystemGC = create("jdk.SystemGC", JfrEventFlags.HasDuration); - public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC"); - public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample"); - public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", JfrEventFlags.SupportsThrottling); + public static final JfrEvent SystemGC = create("jdk.SystemGC", 5, JfrEventFlags.HasDuration); + public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC", 5); + public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample", 7); + public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", 5, JfrEventFlags.SupportsThrottling); public static final JfrEvent NativeMemoryUsage = create("jdk.NativeMemoryUsage"); public static final JfrEvent NativeMemoryUsageTotal = create("jdk.NativeMemoryUsageTotal"); private final long id; private final String name; private final int flags; + private final int skipCount; @Platforms(Platform.HOSTED_ONLY.class) - public static JfrEvent create(String name, JfrEventFlags... flags) { - return new JfrEvent(name, flags); + private static JfrEvent create(String name, JfrEventFlags... flags) { + return new JfrEvent(name, -1, flags); } @Platforms(Platform.HOSTED_ONLY.class) - private JfrEvent(String name, JfrEventFlags... flags) { + private static JfrEvent create(String name, int skipCount, JfrEventFlags... flags) { + return new JfrEvent(name, skipCount, flags); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private JfrEvent(String name, int skipCount, JfrEventFlags... flags) { this.id = JfrMetadataTypeLibrary.lookupPlatformEvent(name); this.name = name; this.flags = EnumBitmask.computeBitmask(flags); + this.skipCount = skipCount; } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -95,6 +102,12 @@ public long getId() { return id; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public int getSkipCount() { + assert skipCount >= 0 : "method may only be called for events that need a stack trace"; + return skipCount; + } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public String getName() { return name; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java index f3c4cbd467e6..24dac35b1040 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java @@ -272,9 +272,7 @@ private static int recordIp(SamplerSampleWriterData data, CodePointer ip) { /* Increment the number of seen frames. */ data.setSeenFrames(data.getSeenFrames() + 1); - if (shouldSkipFrame(data)) { - return NO_ERROR; - } else if (shouldTruncate(data)) { + if (shouldTruncate(data)) { return TRUNCATED; } @@ -288,15 +286,10 @@ private static int recordIp(SamplerSampleWriterData data, CodePointer ip) { return BUFFER_SIZE_EXCEEDED; } - @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - private static boolean shouldSkipFrame(SamplerSampleWriterData data) { - return data.getSeenFrames() <= data.getSkipCount(); - } - @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static boolean shouldTruncate(SamplerSampleWriterData data) { - int numFrames = data.getSeenFrames() - data.getSkipCount(); - if (numFrames > data.getMaxDepth()) { + int maxFrames = data.getMaxDepth() + data.getSkipCount(); + if (data.getSeenFrames() > maxFrames) { /* The stack size exceeds given depth. Stop walk! */ data.setTruncated(true); return true; 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 0f7eb7fab89f..2d6f977d5a06 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 @@ -294,8 +294,8 @@ public long getStackTraceId(int skipCount) { } @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) - public long getStackTraceId(JfrEvent eventType, int skipCount) { - return getStackTraceId(eventType.getId(), skipCount); + public long getStackTraceId(JfrEvent eventType) { + return getStackTraceId(eventType.getId(), eventType.getSkipCount()); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/AllocationRequiringGCEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/AllocationRequiringGCEvent.java index 1357b40ae495..0f98edbfdb40 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/AllocationRequiringGCEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/AllocationRequiringGCEvent.java @@ -54,7 +54,7 @@ private static void emit0(UnsignedWord gcId, UnsignedWord size) { JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.AllocationRequiringGC); JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.AllocationRequiringGC, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.AllocationRequiringGC)); JfrNativeEventWriter.putLong(data, gcId.rawValue()); JfrNativeEventWriter.putLong(data, size.rawValue()); JfrNativeEventWriter.endSmallEvent(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java index 671924ba1a48..ff1b6de5428d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java @@ -56,7 +56,7 @@ public static void emit0(Object obj, long previousOwnerTid, long startTicks) { JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putLong(data, duration); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter)); JfrNativeEventWriter.putClass(data, obj.getClass()); JfrNativeEventWriter.putLong(data, previousOwnerTid); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java index d663c70fd559..cd8a8396f4e0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java @@ -26,7 +26,6 @@ package com.oracle.svm.core.jfr.events; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.Uninterruptible; @@ -39,6 +38,8 @@ import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.monitor.MonitorInflationCause; +import jdk.graal.compiler.word.Word; + public class JavaMonitorInflateEvent { public static void emit(Object obj, long startTicks, MonitorInflationCause cause) { if (HasJfrSupport.get()) { @@ -47,7 +48,7 @@ public static void emit(Object obj, long startTicks, MonitorInflationCause cause } @Uninterruptible(reason = "Accesses a JFR buffer.") - public static void emit0(Object obj, long startTicks, MonitorInflationCause cause) { + private static void emit0(Object obj, long startTicks, MonitorInflationCause cause) { long duration = JfrTicks.duration(startTicks); if (JfrEvent.JavaMonitorInflate.shouldEmit(duration)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); @@ -57,7 +58,7 @@ public static void emit0(Object obj, long startTicks, MonitorInflationCause caus JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putLong(data, duration); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorInflate, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorInflate)); JfrNativeEventWriter.putClass(data, obj.getClass()); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); JfrNativeEventWriter.putLong(data, getId(cause)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java index 27322780e18a..67018d1183d4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java @@ -58,7 +58,7 @@ private static void emit0(long startTicks, Object obj, long notifier, long timeo JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putLong(data, duration); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait)); JfrNativeEventWriter.putClass(data, obj.getClass()); JfrNativeEventWriter.putLong(data, notifier); JfrNativeEventWriter.putLong(data, timeout); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index 28a4ca1f6df5..5f0a23bb3af5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java @@ -68,7 +68,7 @@ private static void emitObjectAllocationInNewTLAB(long startTicks, DynamicHub hu JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationInNewTLAB); JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationInNewTLAB, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationInNewTLAB)); JfrNativeEventWriter.putClass(data, DynamicHub.toClass(hub)); JfrNativeEventWriter.putLong(data, allocationSize.rawValue()); JfrNativeEventWriter.putLong(data, tlabSize.rawValue()); @@ -88,7 +88,7 @@ private static void emitObjectAllocationSample(long startTicks, DynamicHub hub) JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample)); JfrNativeEventWriter.putClass(data, DynamicHub.toClass(hub)); JfrNativeEventWriter.putLong(data, weight); JfrNativeEventWriter.endSmallEvent(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SystemGCEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SystemGCEvent.java index 8123b992f3ca..444d07ae1183 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SystemGCEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SystemGCEvent.java @@ -55,7 +55,7 @@ private static void emit0(long startTicks, boolean invokedConcurrent) { JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putLong(data, duration); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.SystemGC, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.SystemGC)); JfrNativeEventWriter.putBoolean(data, invokedConcurrent); JfrNativeEventWriter.endSmallEvent(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java index da810978b7ed..feb255c31711 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java @@ -68,7 +68,7 @@ private static void emit0(long startTicks, Object obj, boolean isAbsolute, long JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putLong(data, duration); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadPark, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadPark)); JfrNativeEventWriter.putClass(data, parkedClass); JfrNativeEventWriter.putLong(data, timeout); JfrNativeEventWriter.putLong(data, until); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java index 1eb36418442e..b829f32335a5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java @@ -45,7 +45,7 @@ public static void emit(Thread thread) { JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadStart); JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadStart, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadStart)); JfrNativeEventWriter.putThread(data, thread); JfrNativeEventWriter.putLong(data, JavaThreads.getParentThreadId(thread)); JfrNativeEventWriter.endSmallEvent(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java index 19258c9da4a3..f79188681600 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java @@ -148,7 +148,7 @@ private void release(JfrOldObject sample) { private void store(Object obj, UnsignedWord span, UnsignedWord allocatedSize, int arrayLength) { Thread thread = JavaThreads.getCurrentThreadOrNull(); long threadId = thread == null ? 0L : JavaThreads.getThreadId(thread); - long stackTraceId = thread == null ? 0L : SubstrateJVM.get().getStackTraceId(JfrEvent.OldObjectSample, 0); + long stackTraceId = thread == null ? 0L : SubstrateJVM.get().getStackTraceId(JfrEvent.OldObjectSample); UnsignedWord heapUsedAfterLastGC = Heap.getHeap().getUsedMemoryAfterLastGC(); JfrOldObject sample = (JfrOldObject) freeList.pop(); 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 d10a6c9569ba..d8f1444e4bd9 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 @@ -115,7 +115,8 @@ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer) { int sampleSize = current.readInt(0); current = current.add(Integer.BYTES); - /* Padding. */ + /* Number of vframes that should be skipped at the top of the stack trace. */ + int skipCount = current.readInt(0); current = current.add(Integer.BYTES); /* Tick. */ @@ -132,7 +133,7 @@ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer) { assert current.subtract(entryStart).equal(SamplerSampleWriter.getHeaderSize()); - current = serializeStackTrace(current, end, sampleSize, sampleHash, isTruncated, sampleTick, threadId, threadState); + current = serializeStackTrace(current, end, sampleSize, sampleHash, isTruncated, sampleTick, threadId, threadState, skipCount); } SamplerBufferAccess.reinitialize(rawStackTraceBuffer); @@ -140,8 +141,8 @@ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer) { @Uninterruptible(reason = "Wraps the call to the possibly interruptible serializer.", calleeMustBe = false) private static Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int sampleSize, int sampleHash, - boolean isTruncated, long sampleTick, long threadId, long threadState) { + boolean isTruncated, long sampleTick, long threadId, long threadState, int skipCount) { return SamplerStackTraceSerializer.singleton().serializeStackTrace(rawStackTrace, bufferEnd, sampleSize, - sampleHash, isTruncated, sampleTick, threadId, threadState); + sampleHash, isTruncated, sampleTick, threadId, threadState, skipCount); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java index e5b929a73350..3af4829df2b9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java @@ -25,7 +25,8 @@ package com.oracle.svm.core.sampler; -import jdk.graal.compiler.word.Word; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.type.CIntPointer; @@ -49,18 +50,23 @@ import com.oracle.svm.core.jfr.events.ExecutionSampleEvent; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; + /** * A concrete implementation of {@link SamplerStackTraceSerializer} designed for JFR stack trace * serialization. + * + * All static mutable state in this class is preallocated and reused by multiple threads. This is + * safe because only one thread at a time may serialize stack traces. */ public final class SamplerJfrStackTraceSerializer implements SamplerStackTraceSerializer { - /** This value is used by multiple threads but only by a single thread at a time. */ private static final CodeInfoDecoder.FrameInfoCursor FRAME_INFO_CURSOR = new CodeInfoDecoder.FrameInfoCursor(); + private static final StackTraceVisitorData VISITOR_DATA = new StackTraceVisitorData(); @Override @Uninterruptible(reason = "Prevent JFR recording and epoch change.") public Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int sampleSize, int sampleHash, - boolean isTruncated, long sampleTick, long threadId, long threadState) { + boolean isTruncated, long sampleTick, long threadId, long threadState, int skipCount) { Pointer current = rawStackTrace; CIntPointer statusPtr = StackValue.get(CIntPointer.class); JfrStackTraceRepository.JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, Word.unsigned(sampleSize), sampleHash, statusPtr); @@ -70,7 +76,7 @@ public Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int if (status == JfrStackTraceRepository.JfrStackTraceTableEntryStatus.INSERTED || status == JfrStackTraceRepository.JfrStackTraceTableEntryStatus.EXISTING_RAW) { /* Walk the IPs and serialize the stacktrace. */ assert current.add(sampleSize).belowThan(bufferEnd); - boolean serialized = serializeStackTrace(current, sampleSize, isTruncated, stackTraceId); + boolean serialized = serializeStackTrace(current, sampleSize, isTruncated, stackTraceId, skipCount); if (serialized) { SubstrateJVM.getStackTraceRepo().commitSerializedStackTrace(entry); } @@ -100,7 +106,7 @@ public Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int } @Uninterruptible(reason = "Prevent JFR recording and epoch change.") - private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize, boolean isTruncated, long stackTraceId) { + private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize, boolean isTruncated, long stackTraceId, int skipCount) { assert sampleSize % Long.BYTES == 0; JfrBuffer targetBuffer = SubstrateJVM.getStackTraceRepo().getCurrentBuffer(); @@ -111,19 +117,20 @@ private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize /* * One IP may correspond to multiple Java-level stack frames. We need to precompute the * number of stack trace elements because the count can't be patched later on - * (JfrNativeEventWriter.putInt() would not necessarily reserve enough bytes). + * (JfrNativeEventWriter.putInt() would not necessarily reserve enough bytes). We also + * precompute if the stack trace was truncated. */ - int numStackTraceElements = visitRawStackTrace(rawStackTrace, sampleSize, Word.nullPointer()); - if (numStackTraceElements == 0) { + visitRawStackTrace(rawStackTrace, sampleSize, Word.nullPointer(), skipCount); + if (VISITOR_DATA.getUsedFrames() == 0) { return false; } JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initialize(data, targetBuffer); JfrNativeEventWriter.putLong(data, stackTraceId); - JfrNativeEventWriter.putBoolean(data, isTruncated); - JfrNativeEventWriter.putInt(data, numStackTraceElements); - visitRawStackTrace(rawStackTrace, sampleSize, data); + JfrNativeEventWriter.putBoolean(data, isTruncated || VISITOR_DATA.isTruncated()); + JfrNativeEventWriter.putInt(data, VISITOR_DATA.getUsedFrames()); + visitRawStackTrace(rawStackTrace, sampleSize, data, skipCount); boolean success = JfrNativeEventWriter.commit(data); /* Buffer can get replaced with a larger one. */ @@ -132,20 +139,20 @@ private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize } @Uninterruptible(reason = "Prevent JFR recording and epoch change.") - private static int visitRawStackTrace(Pointer rawStackTrace, int sampleSize, JfrNativeEventWriterData data) { - int numStackTraceElements = 0; - Pointer rawStackTraceEnd = rawStackTrace.add(sampleSize); + private static void visitRawStackTrace(Pointer rawStackTrace, int sampleSize, JfrNativeEventWriterData data, int skipCount) { + VISITOR_DATA.reset(skipCount); + Pointer ipPtr = rawStackTrace; + Pointer rawStackTraceEnd = rawStackTrace.add(sampleSize); while (ipPtr.belowThan(rawStackTraceEnd)) { long ip = ipPtr.readLong(0); - numStackTraceElements += visitFrame(data, ip); + visitFrame(data, ip); ipPtr = ipPtr.add(Long.BYTES); } - return numStackTraceElements; } @Uninterruptible(reason = "Prevent JFR recording, epoch change, and that the GC frees the CodeInfo.") - private static int visitFrame(JfrNativeEventWriterData data, long address) { + private static void visitFrame(JfrNativeEventWriterData data, long address) { CodePointer ip = Word.pointer(address); UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip); if (untetheredInfo.isNull()) { @@ -156,24 +163,30 @@ private static int visitFrame(JfrNativeEventWriterData data, long address) { Object tether = CodeInfoAccess.acquireTether(untetheredInfo); try { CodeInfo tetheredCodeInfo = CodeInfoAccess.convert(untetheredInfo, tether); - return visitFrame(data, tetheredCodeInfo, ip); + visitFrame(data, tetheredCodeInfo, ip); } finally { CodeInfoAccess.releaseTether(untetheredInfo, tether); } } @Uninterruptible(reason = "Prevent JFR recording and epoch change.") - private static int visitFrame(JfrNativeEventWriterData data, CodeInfo codeInfo, CodePointer ip) { - int numStackTraceElements = 0; + private static void visitFrame(JfrNativeEventWriterData data, CodeInfo codeInfo, CodePointer ip) { FRAME_INFO_CURSOR.initialize(codeInfo, ip, false); while (FRAME_INFO_CURSOR.advance()) { + if (VISITOR_DATA.shouldSkipFrame()) { + VISITOR_DATA.incrementSkippedFrames(); + continue; + } else if (VISITOR_DATA.shouldTruncate()) { + VISITOR_DATA.setTruncated(); + break; + } + + VISITOR_DATA.incrementUsedFrames(); if (data.isNonNull()) { FrameInfoQueryResult frame = FRAME_INFO_CURSOR.get(); serializeStackTraceElement(data, frame); } - numStackTraceElements++; } - return numStackTraceElements; } @Uninterruptible(reason = "Prevent JFR recording and epoch change.") @@ -185,4 +198,54 @@ private static void serializeStackTraceElement(JfrNativeEventWriterData data, Fr JfrNativeEventWriter.putInt(data, stackTraceElement.getBci()); JfrNativeEventWriter.putLong(data, JfrFrameType.FRAME_AOT_COMPILED.getId()); } + + private static final class StackTraceVisitorData { + private int framesToSkip; + private int skippedFrames; + private int usedFrames; + private boolean truncated; + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void reset(int skipCount) { + framesToSkip = skipCount; + skippedFrames = 0; + usedFrames = 0; + truncated = false; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) // + public boolean shouldSkipFrame() { + return skippedFrames < framesToSkip; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void incrementSkippedFrames() { + skippedFrames++; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) // + public boolean shouldTruncate() { + return usedFrames >= SubstrateJVM.getStackTraceRepo().getStackTraceDepth(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void setTruncated() { + truncated = true; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isTruncated() { + return truncated; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void incrementUsedFrames() { + usedFrames++; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public int getUsedFrames() { + return usedFrames; + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java index 322b3bceac11..087cd075cac9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java @@ -25,8 +25,6 @@ package com.oracle.svm.core.sampler; -import jdk.graal.compiler.api.replacements.Fold; -import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -37,6 +35,9 @@ import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; + public final class SamplerSampleWriter { public static final long JFR_STACK_TRACE_END = -1; public static final long EXECUTION_SAMPLE_END = -2; @@ -62,9 +63,8 @@ public static void begin(SamplerSampleWriterData data) { SamplerSampleWriter.putInt(data, 0); /* Sample size. (will be patched later) */ SamplerSampleWriter.putInt(data, 0); - /* Padding so that the long values below are aligned. */ - SamplerSampleWriter.putInt(data, 0); + SamplerSampleWriter.putInt(data, data.getSkipCount()); SamplerSampleWriter.putLong(data, JfrTicks.elapsedTicks()); SamplerSampleWriter.putLong(data, SubstrateJVM.getCurrentThreadId()); SamplerSampleWriter.putLong(data, JfrThreadState.getId(Thread.State.RUNNABLE)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackTraceSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackTraceSerializer.java index f69df38bb144..001107433a1b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackTraceSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackTraceSerializer.java @@ -52,8 +52,9 @@ static SamplerStackTraceSerializer singleton() { * @param sampleTick The timestamp of the sample. * @param threadId A unique identifier for the sampled thread. * @param threadState The state of the sampled thread. + * @param skipCount The number of top frames to omit. * @return A pointer to the next stack trace entry or the end of the buffer. */ Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int sampleSize, int sampleHash, - boolean isTruncated, long sampleTick, long threadId, long threadState); + boolean isTruncated, long sampleTick, long threadId, long threadState, int skipCount); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java index 231b14bdf8ac..af8fa7804838 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java @@ -26,18 +26,27 @@ package com.oracle.svm.test.jfr; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + import java.io.IOException; import java.nio.file.Path; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.junit.After; +import com.oracle.svm.core.log.StringBuilderLog; + import jdk.jfr.Configuration; import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; /** Base class for JFR unit tests. */ public abstract class JfrRecordingTest extends AbstractJfrTest { @@ -83,6 +92,27 @@ protected Recording prepareRecording(String[] events, Configuration config, Map< return recording; } + protected static void checkTopStackFrame(RecordedEvent event, String... expectedTopMethod) { + List frames = event.getStackTrace().getFrames(); + assertFalse(frames.isEmpty()); + + String topMethod = frames.getFirst().getMethod().getName(); + for (String expected : expectedTopMethod) { + if (expected.equals(topMethod)) { + return; + } + } + + StringBuilderLog log = new StringBuilderLog(); + log.string("Expected one of the following methods at the top of the event stack trace: ").string(Arrays.toString(expectedTopMethod)).newline(); + log.string("but found: ").indent(true); + for (var frame : frames) { + log.string(frame.getMethod().getType().getName()).string(".").string(frame.getMethod().getName()).string(frame.getMethod().getDescriptor()).newline(); + } + log.indent(false); + fail(log.getResult()); + } + private static Recording createRecording(Configuration config) { if (config == null) { return new Recording(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestAllocationRequiringGCEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestAllocationRequiringGCEvent.java index a3f675115d8d..8a2cfe4a93d8 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestAllocationRequiringGCEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestAllocationRequiringGCEvent.java @@ -26,7 +26,7 @@ package com.oracle.svm.test.jfr; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import java.util.List; @@ -53,7 +53,10 @@ public void test() throws Throwable { } private static void validateEvents(List events) { - assertTrue(events.size() > 0); + assertFalse(events.isEmpty()); + for (RecordedEvent event : events) { + checkTopStackFrame(event, "maybeCollectOnAllocation"); + } } @NeverInline("Prevent escape analysis.") diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java index ef6aba8397dc..7eb3b3fb47a9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java @@ -90,6 +90,8 @@ private void validateEvents(List events) { found = true; break; } + + checkTopStackFrame(event, "monitorEnter"); } assertTrue("Expected monitor blocked event not found", found); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java index 30a80adc5072..c30ed37cf606 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java @@ -32,44 +32,77 @@ import org.junit.Test; +import com.oracle.svm.core.hub.DynamicHubCompanion; import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.monitor.MonitorInflationCause; +import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; public class TestJavaMonitorInflateEvent extends JfrRecordingTest { - private final EnterHelper enterHelper = new EnterHelper(); private Thread firstThread; private Thread secondThread; + private String expectedClassName; + private volatile boolean passedCheckpoint; @Test - public void test() throws Throwable { - String[] events = new String[]{JfrEvent.JavaMonitorInflate.getName()}; - Recording recording = startRecording(events); + public void testObject() throws Throwable { + test(new MonitorTester()); + } + + @Test + public void testArray() throws Throwable { + test(new MonitorTester[5]); + } + + @Test + public void testClass() throws Throwable { + test(MonitorTester.class, DynamicHubCompanion.class.getName()); + } + + @Test + public void testString() throws Throwable { + test("ABC"); + } + + private void test(Object obj) throws Throwable { + test(obj, obj.getClass().getName()); + } + private void test(Object obj, String className) throws Throwable { Runnable first = () -> { try { - enterHelper.doWork(); + synchronized (obj) { + secondThread.start(); + /* Spin until second thread blocks. */ + while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) { + Thread.sleep(10); + } + } } catch (InterruptedException e) { throw new RuntimeException(e); } }; Runnable second = () -> { - try { - enterHelper.passedCheckpoint = true; - enterHelper.doWork(); - } catch (InterruptedException e) { - throw new RuntimeException(e); + passedCheckpoint = true; + synchronized (obj) { + GraalDirectives.blackhole(obj); } }; + expectedClassName = className; + passedCheckpoint = false; firstThread = new Thread(first); secondThread = new Thread(second); + /* Now that the data is prepared, start the JFR recording. */ + String[] events = new String[]{JfrEvent.JavaMonitorInflate.getName()}; + Recording recording = startRecording(events); + /* Generate event with "Monitor Enter" cause. */ firstThread.start(); @@ -85,34 +118,17 @@ private void validateEvents(List events) { String eventThread = event. getValue("eventThread").getJavaName(); String monitorClass = event. getValue("monitorClass").getName(); String cause = event.getValue("cause"); - if (monitorClass.equals(EnterHelper.class.getName()) && - cause.equals(MonitorInflationCause.MONITOR_ENTER.getText()) && + + if (monitorClass.equals(expectedClassName) && cause.equals(MonitorInflationCause.MONITOR_ENTER.getText()) && (eventThread.equals(firstThread.getName()) || eventThread.equals(secondThread.getName()))) { foundCauseEnter = true; } + + checkTopStackFrame(event, "monitorEnter", "createMonitorAndAddToMap", "getOrCreateMonitorFromObject"); } assertTrue("Expected monitor inflate event not found.", foundCauseEnter); } - private final class EnterHelper { - volatile boolean passedCheckpoint = false; - - synchronized void doWork() throws InterruptedException { - if (Thread.currentThread().equals(secondThread)) { - /* - * The second thread only needs to enter the critical section but doesn't need to do - * work. - */ - return; - } - // 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(60); - } + private static final class MonitorTester { } } 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 index 3e8ec8df90ef..7c62ef424211 100644 --- 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 @@ -113,6 +113,8 @@ private void validateEvents(List events) { assertEquals("Wrong notifier", notifThread, producerName); } lastEventThreadName = eventThread; + + checkTopStackFrame(event, "await"); } assertFalse("Wrong number of events: " + prodCount + " " + consCount, abs(prodCount - consCount) > 1 || abs(consCount - COUNT) > 1); 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 index 56f054c0e9a8..0f54bac645b0 100644 --- 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 @@ -129,6 +129,7 @@ private void validateEvents(List events) { 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()); @@ -139,6 +140,8 @@ private void validateEvents(List events) { assertEquals("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyThread.getName(), notifThread, simpleNotifyThread.getName()); simpleWaitFound = true; } + + checkTopStackFrame(event, "await"); } assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " interrupted: " + interruptedFound, simpleWaitFound && interruptedFound); 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 index 6731315a287b..372178a8378a 100644 --- 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 @@ -108,6 +108,7 @@ private void validateEvents(List events) { waitersFound++; } + checkTopStackFrame(event, "await"); } assertTrue("Couldn't find expected wait events. NotifierFound: " + notifierFound + " waitersFound: " + waitersFound, notifierFound && waitersFound == 2); 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 index c1df85eb621c..b7e8712291d2 100644 --- 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 @@ -124,6 +124,7 @@ private void validateEvents(List events) { 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())) { assertNull("Notifier of timeout thread should be null", notifThread); @@ -134,6 +135,7 @@ private void validateEvents(List events) { simpleWaitFound = true; } + checkTopStackFrame(event, "await"); } assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " timeout: " + timeoutFound, simpleWaitFound && timeoutFound); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java index a002310c53b5..3be0c4f80650 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java @@ -95,10 +95,13 @@ private static void validateEvents(List events) { } else if (className.equals(byte[].class.getName())) { foundBigByteArray = true; } + checkTopStackFrame(event, "slowPathNewArrayLikeObject0"); } else if (allocationSize >= K && tlabSize == alignedHeapChunkSize && className.equals(byte[].class.getName())) { foundSmallByteArray = true; + checkTopStackFrame(event, "slowPathNewArrayLikeObject0"); } else if (tlabSize == alignedHeapChunkSize && className.equals(Helper.class.getName())) { foundInstance = true; + checkTopStackFrame(event, "slowPathNewInstanceWithoutAllocating"); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationSampleEvent.java new file mode 100644 index 000000000000..b279d1e06a0d --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationSampleEvent.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2025, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.jfr; + +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.genscavenge.HeapParameters; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.util.UnsignedUtils; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; + +public class TestObjectAllocationSampleEvent extends JfrRecordingTest { + @Test + public void test() throws Throwable { + String[] events = new String[]{JfrEvent.ObjectAllocationSample.getName()}; + Recording recording = startRecording(events); + + int alignedHeapChunkSize = UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); + + // Allocate large arrays (always need a new TLAB). + allocateByteArray(2 * alignedHeapChunkSize); + allocateCharArray(alignedHeapChunkSize); + + stopRecording(recording, TestObjectAllocationSampleEvent::validateEvents); + } + + private static void validateEvents(List events) { + long alignedHeapChunkSize = HeapParameters.getAlignedHeapChunkSize().rawValue(); + + boolean foundByteArray = false; + boolean foundCharArray = false; + + for (RecordedEvent event : events) { + String eventThread = event. getValue("eventThread").getJavaName(); + if (!eventThread.equals("main")) { + continue; + } + + long allocationSize = event. getValue("weight"); + String className = event. getValue("objectClass").getName(); + + // >= To account for size of reference + if (allocationSize >= 2 * alignedHeapChunkSize) { + // verify previous owner + if (className.equals(char[].class.getName())) { + foundCharArray = true; + checkTopStackFrame(event, "slowPathNewArrayLikeObject0"); + } else if (className.equals(byte[].class.getName())) { + foundByteArray = true; + checkTopStackFrame(event, "slowPathNewArrayLikeObject0"); + } + } + } + + assertTrue(foundCharArray); + assertTrue(foundByteArray); + } + + @NeverInline("Prevent escape analysis.") + private static byte[] allocateByteArray(int length) { + return new byte[length]; + } + + @NeverInline("Prevent escape analysis.") + private static char[] allocateCharArray(int length) { + return new char[length]; + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestSystemGCEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestSystemGCEvent.java index 655607f23bf7..184255a5240f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestSystemGCEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestSystemGCEvent.java @@ -57,6 +57,8 @@ private static void validateEvents(List events) { assertNotNull(event.getStartTime()); assertTrue(event.getStartTime().toEpochMilli() <= System.currentTimeMillis()); assertFalse(event.getBoolean("invokedConcurrent")); + + checkTopStackFrame(event, "gc"); } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadParkEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadParkEvents.java index 0ac39bae0408..43a6aab1ca0d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadParkEvents.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadParkEvents.java @@ -96,6 +96,8 @@ private static void validateEvents(List events) { parkUntilFound = true; } } + + checkTopStackFrame(event, "park"); } assertTrue(parkNanosFound); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadStartEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadStartEvents.java new file mode 100644 index 000000000000..4221973e2985 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadStartEvents.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2025, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.jfr; + +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.concurrent.locks.LockSupport; + +import org.junit.Test; + +import com.oracle.svm.core.jfr.JfrEvent; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; + +public class TestThreadStartEvents extends JfrRecordingTest { + private static final String THREAD_NAME = "TestThreadStartEvents-worker"; + + @Test + public void test() throws Throwable { + String[] events = new String[]{JfrEvent.ThreadStart.getName()}; + Recording recording = startRecording(events); + + Runnable work = () -> LockSupport.parkNanos(1); + Thread worker = new Thread(work, THREAD_NAME); + worker.start(); + worker.join(); + + stopRecording(recording, TestThreadStartEvents::validateEvents); + } + + private static void validateEvents(List events) { + boolean foundEvent = false; + for (RecordedEvent event : events) { + RecordedThread eventThread = event.getThread("eventThread"); + if (eventThread.getJavaName().equals(THREAD_NAME)) { + foundEvent = true; + } + + checkTopStackFrame(event, "beforeThreadStart"); + } + assertTrue(foundEvent); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java index a873ee5d79c3..c5799567ede5 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java @@ -27,6 +27,7 @@ package com.oracle.svm.test.jfr.oldobject; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -36,7 +37,6 @@ import java.util.List; import java.util.Map; -import jdk.graal.compiler.word.Word; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -48,6 +48,7 @@ import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.test.jfr.JfrRecordingTest; +import jdk.graal.compiler.word.Word; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedFrame; @@ -94,7 +95,7 @@ protected void testSampling(Object obj, int arrayLength, EventValidator validato } protected List validateEvents(List events, Class expectedSampledType, int expectedArrayLength) { - assertTrue(events.size() > 0); + assertFalse(events.isEmpty()); ArrayList matchingEvents = new ArrayList<>(); String expectedTypeName = expectedSampledType.getName(); @@ -112,9 +113,11 @@ protected List validateEvents(List events, Class frames = event.getStackTrace().getFrames(); - assertTrue(frames.size() > 0); + assertFalse(frames.isEmpty()); assertTrue(frames.stream().anyMatch(e -> testName.getMethodName().equals(e.getMethod().getName()))); + checkTopStackFrame(event, "testSampling"); + long allocationTime = event.getLong("allocationTime"); assertTrue(allocationTime > 0); assertTrue(allocationTime <= startTime); @@ -131,7 +134,7 @@ protected List validateEvents(List events, Class 0); + assertFalse(matchingEvents.isEmpty()); return matchingEvents; } }