From e5bcf34c5b86bc73e5bdecd5232d2d1bfa46e07f Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 11 Dec 2024 13:38:09 -0500 Subject: [PATCH] fix old object constant bug --- .../UninterruptiblePriorityQueue.java | 5 ++ .../svm/core/jfr/JfrStackTraceRepository.java | 44 ++++++++++ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 13 +++ .../svm/core/jfr/oldobject/JfrOldObject.java | 70 ++++++++++++---- .../jfr/oldobject/JfrOldObjectProfiler.java | 5 ++ .../jfr/oldobject/JfrOldObjectSampler.java | 70 ++++++++++++++-- .../SamplerJfrStackTraceSerializer.java | 39 +++++---- .../svm/core/sampler/SamplerSampleWriter.java | 8 +- .../core/sampler/SamplerSampleWriterData.java | 9 +++ .../SamplerSampleWriterDataAccess.java | 29 +++++-- .../svm/test/jfr/TestThreadCPULoadEvent.java | 9 ++- .../com/oracle/svm/test/jfr/TestVolker.java | 54 +++++++++++++ .../jfr/oldobject/TestOldObjectProfiler.java | 2 +- .../oldobject/TestOldObjectSampleEvent.java | 81 +++++++++++++++++++ 14 files changed, 391 insertions(+), 47 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestVolker.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptiblePriorityQueue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptiblePriorityQueue.java index b9a52b06af7b..d1ec1d1ed078 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptiblePriorityQueue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptiblePriorityQueue.java @@ -51,6 +51,11 @@ public boolean isFull() { return size == queue.length; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isEmpty() { + return size == 0; + } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public boolean add(UninterruptibleComparable e) { return offer(e); 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 323b26dc48ad..e2bc46f58f30 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 @@ -52,6 +52,7 @@ import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.memory.NullableNativeMemory; import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.sampler.SamplerBuffer; import com.oracle.svm.core.sampler.SamplerSampleWriter; import com.oracle.svm.core.sampler.SamplerSampleWriterData; import com.oracle.svm.core.sampler.SamplerSampleWriterDataAccess; @@ -135,6 +136,49 @@ public long getStackTraceId(int skipCount) { } } + /** Don't store to the stacktrace repo here (it may not belong in this epoch). */ + @NeverInline("Starting a stack walk in the caller frame.") + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) + public boolean recordStackTraceDataToBuffer(int skipCount, SamplerBuffer rawStackTraceData) { + if (DeoptimizationSupport.enabled() || rawStackTraceData.isNull()) { + /* Stack traces are not supported if JIT compilation is used (GR-43686). */ + return false; + } + + /** + * The JFR sampler does not share the same buffers as the leak profiler. There is no risk of + * races between them. + */ + SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); + if (rawStackTraceData.isNull() || !SamplerSampleWriterDataAccess.initialize(data, skipCount, true, rawStackTraceData)) { + return false; + } + + assert SamplerSampleWriterDataAccess.verify(data); + assert data.getCurrentPos().unsignedRemainder(Long.BYTES).equal(0); + + /* + * Start a stack trace and do a stack walk. Note that the data will only be committed to the + * buffer if it is a new stack trace. + */ + SamplerSampleWriter.begin(data); + Pointer sp = KnownIntrinsics.readCallerStackPointer(); + CodePointer ip = FrameAccess.singleton().readReturnAddress(CurrentIsolate.getCurrentThread(), sp); + int errorCode = JfrStackWalker.walkCurrentThread(data, ip, sp, false); + + /* + * If writing the raw data to the buffer succeeded, commit it here. Do not attempt + * deduplication yet since the event data this stack trace corresponds to may not be emitted + * until a much later epoch. + */ + return switch (errorCode) { + case JfrStackWalker.NO_ERROR, JfrStackWalker.TRUNCATED -> SamplerSampleWriter.end(data, SamplerSampleWriter.JFR_STACK_TRACE_END); + case JfrStackWalker.BUFFER_SIZE_EXCEEDED -> false; + case JfrStackWalker.UNPARSEABLE_STACK -> throw VMError.shouldNotReachHere("Only the async sampler may encounter an unparseable stack."); + default -> throw VMError.shouldNotReachHere("Unexpected return value"); + }; + } + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) private long storeDeduplicatedStackTrace(SamplerSampleWriterData data) { if (SamplerSampleWriter.isValid(data)) { 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 d29ec2501445..c041dc8c6f80 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 @@ -43,6 +43,7 @@ import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.throttling.JfrEventThrottling; import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.sampler.SamplerBuffer; import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.sampler.SamplerBuffersAccess; import com.oracle.svm.core.sampler.SamplerStatistics; @@ -281,6 +282,18 @@ public long getStackTraceId(long eventTypeId, int skipCount) { } } + /* + * Buffers are not shared with the sampler or other JFR event types. This data will not yet be + * tied to a specific epoch until event emission. + */ + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean recordStackTraceDataToBuffer(JfrEvent eventType, int skipCount, SamplerBuffer rawStackTraceData) { + if (isRecording() && isStackTraceEnabled(eventType.getId())) { + return stackTraceRepo.recordStackTraceDataToBuffer(skipCount, rawStackTraceData); + } + return false; + } + /** * See {@link JVM#getStackTraceId}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java index 65453fc6976b..a8ba52c72e3a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java @@ -32,12 +32,18 @@ import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.collections.UninterruptibleComparable; import com.oracle.svm.core.collections.UninterruptibleLinkedList; import com.oracle.svm.core.heap.ReferenceInternals; import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.memory.NullableNativeMemory; +import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.sampler.SamplerBuffer; +import com.oracle.svm.core.sampler.SamplerBufferAccess; +import com.oracle.svm.core.jfr.SubstrateJVM; /** * Holds information about a sampled object. This data may only be accessed while holding the @@ -50,26 +56,29 @@ public final class JfrOldObject implements UninterruptibleComparable, Uninterrup private UnsignedWord span; private UnsignedWord objectSize; private long allocationTicks; - private long threadId; - private long stackTraceId; private UnsignedWord heapUsedAfterLastGC; private int arrayLength; + private SamplerBuffer rawStackTraceData; + private Thread thread; + private boolean stackTraceValid; JfrOldObject() { this.reference = new WeakReference<>(null); + this.rawStackTraceData = WordFactory.nullPointer(); } @SuppressWarnings("hiding") @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - void initialize(Object obj, UnsignedWord span, UnsignedWord allocatedSize, long threadId, long stackTraceId, UnsignedWord heapUsedAfterLastGC, int arrayLength) { + void initialize(Object obj, UnsignedWord span, UnsignedWord allocatedSize, Thread thread, UnsignedWord heapUsedAfterLastGC, int arrayLength) { ReferenceInternals.setReferent(reference, obj); this.span = span; this.objectSize = allocatedSize; this.allocationTicks = JfrTicks.elapsedTicks(); - this.threadId = threadId; - this.stackTraceId = stackTraceId; this.heapUsedAfterLastGC = heapUsedAfterLastGC; this.arrayLength = arrayLength; + this.thread = thread; + this.stackTraceValid = false; + this.rawStackTraceData = WordFactory.nullPointer(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -78,11 +87,12 @@ void reset() { this.span = Word.zero(); this.objectSize = Word.zero(); this.allocationTicks = 0L; - this.threadId = 0L; - this.stackTraceId = 0L; this.heapUsedAfterLastGC = Word.zero(); this.arrayLength = 0; this.next = null; + this.thread = null; + this.stackTraceValid = false; + freeRawStackTraceData(); } @Override @@ -118,23 +128,53 @@ public long getAllocationTicks() { } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - public long getThreadId() { - return threadId; + public UnsignedWord getHeapUsedAfterLastGC() { + return heapUsedAfterLastGC; } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - public long getStackTraceId() { - return stackTraceId; + public int getArrayLength() { + return arrayLength; } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - public UnsignedWord getHeapUsedAfterLastGC() { - return heapUsedAfterLastGC; + public Thread getThread() { + return thread; } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - public int getArrayLength() { - return arrayLength; + public boolean getStackTraceValid() { + return stackTraceValid; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void setStackTraceValid(boolean valid) { + stackTraceValid = valid; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public SamplerBuffer getRawStackTraceData() { + if (rawStackTraceData.isNull()) { + // TODO add OldObjectSample event skipcount here too. + long bufferDataSize = Long.SIZE * SubstrateJVM.getStackTraceRepo().getStackTraceDepth(); + rawStackTraceData = NullableNativeMemory.malloc(SamplerBufferAccess.getTotalBufferSize(WordFactory.unsigned(bufferDataSize)), NmtCategory.JFR); + if (rawStackTraceData.isNonNull()) { + rawStackTraceData.setSize(WordFactory.unsigned(bufferDataSize)); + rawStackTraceData.setNext(Word.nullPointer()); + SamplerBufferAccess.reinitialize(rawStackTraceData); + } else { + this.stackTraceValid = false; + } + } + return rawStackTraceData; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void freeRawStackTraceData() { + if (rawStackTraceData.isNonNull()) { + NullableNativeMemory.free(rawStackTraceData); + this.rawStackTraceData = WordFactory.nullPointer(); + } } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java index dad6556ea41f..f73ac28d4b23 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java @@ -64,10 +64,15 @@ public void configure(int oldObjectQueueSize) { } public void reset() { + if (this.sampler != null) { + this.sampler.reset(); + } this.sampler = new JfrOldObjectSampler(queueSize); } public void teardown() { + // Reset to free buffers. + this.sampler.reset(); this.sampler = null; } 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 ebb937bc31f1..513bd6567fd7 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 @@ -28,7 +28,9 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.Uninterruptible; @@ -39,6 +41,10 @@ import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.OldObjectSampleEvent; +import com.oracle.svm.core.sampler.SamplerBuffer; +import com.oracle.svm.core.sampler.SamplerBufferAccess; +import com.oracle.svm.core.sampler.SamplerJfrStackTraceSerializer; +import com.oracle.svm.core.sampler.SamplerSampleWriter; import com.oracle.svm.core.thread.JavaThreads; /** @@ -145,20 +151,21 @@ private void evict() { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private void release(JfrOldObject sample) { usedList.remove(sample); - freeList.append(sample); sample.reset(); + freeList.append(sample); } @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+26/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.cpp#L44-L53") @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) 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); UnsignedWord heapUsedAfterLastGC = Heap.getHeap().getUsedMemoryAfterLastGC(); JfrOldObject sample = (JfrOldObject) freeList.pop(); - sample.initialize(obj, span, allocatedSize, threadId, stackTraceId, heapUsedAfterLastGC, arrayLength); + sample.initialize(obj, span, allocatedSize, thread, heapUsedAfterLastGC, arrayLength); + // Allocate a raw stacktrace buffer and write to it. + boolean stackTraceValid = SubstrateJVM.get().recordStackTraceDataToBuffer(JfrEvent.OldObjectSample, 0, sample.getRawStackTraceData()); + sample.setStackTraceValid(stackTraceValid); queue.add(sample); usedList.append(sample); totalInQueue = totalInQueue.add(span); @@ -184,8 +191,16 @@ private void emitUnchained() { long objectId = SubstrateJVM.getOldObjectRepository().serializeOldObject(obj); UnsignedWord objectSize = cur.getObjectSize(); long allocationTicks = cur.getAllocationTicks(); - long threadId = cur.getThreadId(); - long stackTraceId = cur.getStackTraceId(); + Thread thread = cur.getThread(); + long threadId = 0L; + if (thread != null) { + threadId = JavaThreads.getThreadId(thread); + SubstrateJVM.getThreadRepo().registerThread(thread); + } + long stackTraceId = 0L; + if (cur.getStackTraceValid()) { + stackTraceId = deduplicateAndSerializeStackTrace(cur.getRawStackTraceData()); + } UnsignedWord heapUsedAfterLastGC = cur.getHeapUsedAfterLastGC(); int arrayLength = cur.getArrayLength(); @@ -196,6 +211,39 @@ private void emitUnchained() { } } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static long deduplicateAndSerializeStackTrace(SamplerBuffer buffer) { + if (buffer.isNull()) { + // Buffer may have failed allocation. + return 0L; + } + + // decode the buffer + Pointer bufferEnd = buffer.getPos(); + Pointer current = SamplerBufferAccess.getDataStart(buffer); + Pointer entryStart = current; + assert entryStart.unsignedRemainder(Long.BYTES).equal(0); + + /* Sample hash. */ + int sampleHash = current.readInt(0); + current = current.add(Integer.BYTES); + + /* Is truncated. */ + boolean isTruncated = current.readInt(0) == 1; + current = current.add(Integer.BYTES); + + /* Sample size, excluding the header and the end marker. */ + int sampleSize = current.readInt(0); + current = current.add(Integer.BYTES); + + /* skip over padding, ticks, event thread, and thread state. */ + current = current.add(Integer.BYTES).add(Long.BYTES * 3); + + assert current.subtract(entryStart).equal(SamplerSampleWriter.getHeaderSize()); + + return SamplerJfrStackTraceSerializer.deduplicateAndSerializeStackTrace(current, bufferEnd, sampleSize, sampleHash, isTruncated); + } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) JfrOldObject getOldestObject() { return (JfrOldObject) usedList.getHead(); @@ -205,4 +253,14 @@ JfrOldObject getOldestObject() { private JfrOldObject getObjectWithSmallestSpan() { return (JfrOldObject) queue.peek(); } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void reset() { + while (!queue.isEmpty()) { + evict(); + } + assert usedList.isEmpty(); + totalAllocated = WordFactory.unsigned(0); + totalInQueue = WordFactory.unsigned(0); + } } 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..819f269829fd 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 @@ -62,22 +62,7 @@ public final class SamplerJfrStackTraceSerializer implements SamplerStackTraceSe public Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int sampleSize, int sampleHash, boolean isTruncated, long sampleTick, long threadId, long threadState) { Pointer current = rawStackTrace; - CIntPointer statusPtr = StackValue.get(CIntPointer.class); - JfrStackTraceRepository.JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, Word.unsigned(sampleSize), sampleHash, statusPtr); - long stackTraceId = entry.isNull() ? 0 : entry.getId(); - - int status = statusPtr.read(); - 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); - if (serialized) { - SubstrateJVM.getStackTraceRepo().commitSerializedStackTrace(entry); - } - } else { - /* Processing is not needed: skip the rest of the data. */ - assert status == JfrStackTraceRepository.JfrStackTraceTableEntryStatus.EXISTING_SERIALIZED || status == JfrStackTraceRepository.JfrStackTraceTableEntryStatus.INSERT_FAILED; - } + long stackTraceId = deduplicateAndSerializeStackTrace(current, bufferEnd, sampleSize, sampleHash, isTruncated); current = current.add(sampleSize); /* @@ -99,6 +84,28 @@ public Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int return current; } + @Uninterruptible(reason = "Prevent JFR recording and epoch change.") + public static long deduplicateAndSerializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int sampleSize, int sampleHash, boolean isTruncated) { + Pointer current = rawStackTrace; + CIntPointer statusPtr = StackValue.get(CIntPointer.class); + JfrStackTraceRepository.JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, Word.unsigned(sampleSize), sampleHash, statusPtr); + long stackTraceId = entry.isNull() ? 0 : entry.getId(); + + int status = statusPtr.read(); + 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); + if (serialized) { + SubstrateJVM.getStackTraceRepo().commitSerializedStackTrace(entry); + } + } else { + /* Processing is not needed: skip the rest of the data. */ + assert status == JfrStackTraceRepository.JfrStackTraceTableEntryStatus.EXISTING_SERIALIZED || status == JfrStackTraceRepository.JfrStackTraceTableEntryStatus.INSERT_FAILED; + } + return stackTraceId; + } + @Uninterruptible(reason = "Prevent JFR recording and epoch change.") private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize, boolean isTruncated, long stackTraceId) { assert sampleSize % Long.BYTES == 0; 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..0817703666fe 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 @@ -155,7 +155,13 @@ private static boolean ensureSize(SamplerSampleWriterData data, int requested) { int totalRequested = requested + END_MARKER_SIZE; if (getAvailableSize(data).belowThan(totalRequested)) { - if (!accommodate(data, getUncommittedSize(data))) { + if (data.getLeakProfiler()) { + /* + * Leak profiler buffers correspond to a specific OldObject sample instance and + * should not draw from the sampler buffer pools. + */ + return false; + } else if (!accommodate(data, getUncommittedSize(data))) { assert !isValid(data); return false; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java index 0a05f039c21a..7c27858e28f8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java @@ -156,4 +156,13 @@ public interface SamplerSampleWriterData extends PointerBase { */ @RawField void setAllowBufferAllocation(boolean allowBufferAllocation); + + /** + * Buffers for the leak profiler should not use the buffer pools. + */ + @RawField + boolean getLeakProfiler(); + + @RawField + void setLeakProfiler(boolean leakProfiler); } 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 54aba8121936..907548c8df6a 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 @@ -31,6 +31,8 @@ import com.oracle.svm.core.jfr.JfrThreadLocal; import com.oracle.svm.core.jfr.SubstrateJVM; +import org.graalvm.word.WordFactory; + /** * Helper class that holds methods related to {@link SamplerSampleWriterData}. */ @@ -45,15 +47,28 @@ private SamplerSampleWriterDataAccess() { */ @Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true) public static boolean initialize(SamplerSampleWriterData data, int skipCount, boolean allowBufferAllocation) { - SamplerBuffer buffer = JfrThreadLocal.getSamplerBuffer(); - if (buffer.isNull()) { - buffer = SubstrateJVM.getSamplerBufferPool().acquireBuffer(allowBufferAllocation); + return initialize(data, skipCount, allowBufferAllocation, WordFactory.nullPointer()); + } + + @Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true) + public static boolean initialize(SamplerSampleWriterData data, int skipCount, boolean allowBufferAllocation, SamplerBuffer rawStackTraceData) { + SamplerBuffer buffer; + // Use the supplied buffer if one is provided by the leak profiler. + if (rawStackTraceData.isNonNull()) { + buffer = rawStackTraceData; + data.setLeakProfiler(true); + } else { + buffer = JfrThreadLocal.getSamplerBuffer(); if (buffer.isNull()) { - /* No buffer available. */ - JfrThreadLocal.increaseMissedSamples(); - return false; + buffer = SubstrateJVM.getSamplerBufferPool().acquireBuffer(allowBufferAllocation); + if (buffer.isNull()) { + /* No buffer available. */ + JfrThreadLocal.increaseMissedSamples(); + return false; + } + JfrThreadLocal.setSamplerBuffer(buffer); } - JfrThreadLocal.setSamplerBuffer(buffer); + data.setLeakProfiler(false); } initialize0(data, buffer, skipCount, SubstrateJVM.getStackTraceRepo().getStackTraceDepth(), allowBufferAllocation); return true; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadCPULoadEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadCPULoadEvent.java index 2e57f07c3677..01ef147e71b2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadCPULoadEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadCPULoadEvent.java @@ -53,7 +53,14 @@ public class TestThreadCPULoadEvent extends JfrRecordingTest { @Test public void test() throws Throwable { String[] events = new String[]{JfrEvent.ThreadCPULoad.getName()}; - Recording recording = startRecording(events); + Recording recording = prepareRecording(events, getDefaultConfiguration(), null, createTempJfrFile()); + /* + * TestThreadCPULoadEvent.waitUntilCollected checks the reference to a thread instance which + * is kept alive by the JFR old object sampler. Disable OldObjectSample events to avoid + * waiting forever. + */ + recording.disable("jdk.OldObjectSample"); + recording.start(); WeakReference thread1 = createAndStartBusyWaitThread(THREAD_NAME_1, 10, 250); WeakReference thread2 = createAndStartBusyWaitThread(THREAD_NAME_2, 250, 10); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestVolker.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestVolker.java new file mode 100644 index 000000000000..3407e4b39046 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestVolker.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.jfr; + +import com.oracle.svm.test.jfr.events.StringEvent; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class TestVolker extends JfrRecordingTest { + @Test + public void test() throws Throwable { + String[] events = new String[]{"com.jfr.String"}; + Recording recording = startRecording(events); + + StringEvent event = new StringEvent(); + event.message = "Event has been generated!"; + event.commit(); + System.out.println(com.oracle.svm.core.jfr.SubstrateJVM.get().getDumpPath()); + stopRecording(recording, TestVolker::validateEvents); + } + + private static void validateEvents(List events) { + assertEquals(1, events.size()); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java index 86b29c49a243..4448bc79698c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java @@ -227,7 +227,7 @@ private static void validate(JfrOldObjectProfiler profiler, List obj assertTrue(remainingObjects.remove(obj)); assertTrue(cur.getObjectSize().aboveThan(0)); assertTrue(cur.getAllocationTicks() > 0); - assertEquals(Thread.currentThread().threadId(), cur.getThreadId()); + assertEquals(Thread.currentThread(), cur.getThread()); assertEquals(Integer.MIN_VALUE, cur.getArrayLength()); } cur = (JfrOldObject) cur.getNext(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectSampleEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectSampleEvent.java index 7bf2d03bbdb0..2f38e3478f7b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectSampleEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectSampleEvent.java @@ -26,8 +26,25 @@ package com.oracle.svm.test.jfr.oldobject; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; + +import com.oracle.svm.core.jfr.HasJfrSupport; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.util.TimeUtils; +import org.graalvm.word.WordFactory; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + public class TestOldObjectSampleEvent extends JfrOldObjectTest { @Test @@ -47,4 +64,68 @@ public void testLargeArrayLeak() throws Throwable { int arrayLength = 1024 * 1024; testSampling(new TinyObject[arrayLength], arrayLength, events -> validateEvents(events, TinyObject.class.arrayType(), arrayLength)); } + + @Test + public void testConstantData() throws Throwable { + if (!HasJfrSupport.get()) { + /* Prevent the code below from being reachable on platforms that don't support JFR. */ + fail("JFR is not supported on this platform."); + return; + } + String threadName = "test_name"; + long tid; + int arrayLength = 1024 * 1024; + Object obj = new TinyObject[arrayLength]; + Recording recording = startRecording(); + + final boolean[] success = new boolean[1]; + Runnable runnable = () -> { + long endTime = System.currentTimeMillis() + TimeUtils.secondsToMillis(5); + do { + success[0] = SubstrateJVM.getOldObjectProfiler().sample(obj, WordFactory.unsigned(1024 * 1024 * 1024), arrayLength); + } while (!success[0] && System.currentTimeMillis() < endTime); + }; + + Thread worker = new Thread(runnable); + worker.setName(threadName); + tid = worker.threadId(); + worker.start(); + + worker.join(); + assertTrue("Timed out waiting for sampling to complete", success[0]); + + // Force a chunk rotation. + recording.dump(createTempJfrFile()); + + stopRecording(recording, events -> this.validateConstants(events, TinyObject.class.arrayType(), threadName, tid)); + } + + private List validateConstants(List events, Class expectedSampledType, String expectedName, long expectedId) { + assertTrue(events.size() > 0); + + ArrayList matchingEvents = new ArrayList<>(); + String expectedTypeName = expectedSampledType.getName(); + for (RecordedEvent event : events) { + RecordedObject object = event.getValue("object"); + assertNotNull(object); + + if (object.getClass("type").getName().equals(expectedTypeName)) { + // Check thread data + RecordedThread eventThread = event.getThread("eventThread"); + assertNotNull("No thread data found.", eventThread); + assertTrue("Event thread name is incorrect.", eventThread.getJavaName().equals(expectedName)); + assertTrue("Event thread ID is incorrect.", eventThread.getId() == expectedId); + + // Check stacktrace data + List frames = event.getStackTrace().getFrames(); + assertTrue("No stack trace data found.", frames.size() > 0); + assertTrue("Stack frames are incorrect.", frames.stream().anyMatch(e -> e.getMethod().getName().contains(testName.getMethodName()))); + + matchingEvents.add(event); + } + } + + assertTrue(matchingEvents.size() > 0); + return matchingEvents; + } }