From 71c62ee81214f3b1877a4587932a357921ee714c Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 7 Oct 2022 09:00:44 -0400 Subject: [PATCH 01/72] wip. patch file header to not finish chunk, dont repeat metadata, dont change epoch --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 119 +++++++++++++++--- .../oracle/svm/core/jfr/JfrConstantPool.java | 4 +- .../svm/core/jfr/JfrFrameTypeSerializer.java | 2 +- .../svm/core/jfr/JfrMethodRepository.java | 2 +- .../svm/core/jfr/JfrStackTraceRepository.java | 2 +- .../svm/core/jfr/JfrSymbolRepository.java | 14 ++- .../svm/core/jfr/JfrThreadRepository.java | 8 +- .../core/jfr/JfrThreadStateSerializer.java | 2 +- .../svm/core/jfr/JfrTypeRepository.java | 13 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 25 +++- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 8 ++ .../svm/core/jfr/traceid/JfrTraceId.java | 5 + 12 files changed, 166 insertions(+), 38 deletions(-) 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 aa9a8e2e80a3..4294536fc191 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 @@ -59,6 +59,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { public static final short JFR_VERSION_MAJOR = 2; public static final short JFR_VERSION_MINOR = 0; private static final int CHUNK_SIZE_OFFSET = 8; + private static final int FILE_STATE_OFFSET = 64; public static final long METADATA_TYPE_ID = 0; public static final long CONSTANT_POOL_TYPE_ID = 1; @@ -72,6 +73,18 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private RawFileOperationSupport.RawFileDescriptor fd; private long chunkStartTicks; private long chunkStartNanos; + private byte generation; + private static final byte COMPLETE = 0; + private static final byte MAX_BYTE = 127; + + private long lastCheckpointOffset = 0; + + private int lastMetadataId = 0; + private int currentMetadataId = 0; + + public void setCurrentMetadataId(){ + currentMetadataId++; + } @Platforms(Platform.HOSTED_ONLY.class) public JfrChunkWriter(JfrGlobalMemory globalMemory) { @@ -116,15 +129,18 @@ public void maybeOpenFile() { public boolean openFile(String outputFile) { assert lock.isHeldByCurrentThread(); + System.out.println("*** ChunkWriter openfile"); chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); writeFileHeader(); + generation = 1; + lastCheckpointOffset = 0;// must reset this on new chunk return true; } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") + @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") // *** top pointer (of the buffer) like if it gets reinit. public boolean write(JfrBuffer buffer) { assert JfrBufferAccess.isAcquired(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); @@ -138,6 +154,7 @@ public boolean write(JfrBuffer buffer) { // We lost some data because the write failed. return false; } + // need to save pointer position so we can return to it when writing next return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); } @@ -146,12 +163,12 @@ public boolean write(JfrBuffer buffer) { */ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) { assert lock.isHeldByCurrentThread(); - + System.out.println("*** rotating chunk: closeFile"); /* * Switch to a new epoch. This is done at a safepoint to ensure that we end up with * consistent data, even if multiple threads have JFR events in progress. */ - JfrChangeEpochOperation op = new JfrChangeEpochOperation(); + JfrChangeEpochOperation op = new JfrChangeEpochOperation(false); op.enqueue(); /* @@ -159,19 +176,39 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) * data structures of the new epoch. This guarantees that the data in the old epoch can be * persisted to a file without a safepoint. */ - SignedWord constantPoolPosition = writeCheckpointEvent(repositories); + SignedWord constantPoolPosition = writeCheckpointEvent(repositories, false); SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); - patchFileHeader(constantPoolPosition, metadataPosition); +// _constantPoolPosition = constantPoolPosition; + _metadataPosition = metadataPosition; + patchFileHeader(constantPoolPosition, metadataPosition); // write of header doesn't have to be uninterruptible because closefile() already has the lock. It can get interrupted by safepoint but it'll just resume later. getFileSupport().close(fd); filename = null; fd = WordFactory.nullPointer(); } +// private SignedWord _constantPoolPosition; + private SignedWord _metadataPosition; + public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories) { + assert lock.isHeldByCurrentThread();// fd should always be correct because its cleared and set within locked critical section + + JfrChangeEpochOperation op = new JfrChangeEpochOperation(true); + op.enqueue(); + + SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); // WILL get written again when the chunk closes and overwrite what we write here. In that case we shouldn't wipe the repos right? How does hotspot handle it? + SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); + + SignedWord currentPos = getFileSupport().position(fd); +// patchFileHeader(_constantPoolPosition, metadataPosition, true); + patchFileHeader(constantPoolPosition, metadataPosition, true); + getFileSupport().seek(fd,currentPos); + // unlike rotate chunk, don't close file. + + } private void writeFileHeader() { // Write the header - some data gets patched later on. - getFileSupport().write(fd, FILE_MAGIC); - getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); + getFileSupport().write(fd, FILE_MAGIC); //magic + getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); // version getFileSupport().writeShort(fd, JFR_VERSION_MINOR); assert getFileSupport().position(fd).equal(CHUNK_SIZE_OFFSET); getFileSupport().writeLong(fd, 0L); // chunk size @@ -179,12 +216,22 @@ private void writeFileHeader() { getFileSupport().writeLong(fd, 0L); // metadata position getFileSupport().writeLong(fd, 0L); // startNanos getFileSupport().writeLong(fd, 0L); // durationNanos - getFileSupport().writeLong(fd, chunkStartTicks); + getFileSupport().writeLong(fd, chunkStartTicks); // *** only changed after a chunk rotation is complete (after header is patched) getFileSupport().writeLong(fd, JfrTicks.getTicksFrequency()); - getFileSupport().writeInt(fd, compressedInts ? 1 : 0); + getFileSupport().writeByte(fd, nextGeneration()); // in hotspot a 1 byte generation is written + getFileSupport().writeByte(fd, (byte) 0 ); // in hotspot 1 byte PAD padding + getFileSupport().writeShort(fd, compressedInts ? (short) 1 : 0 ); // seems like only 2 bytes of the flags are written after the 1 byte generation + +// getFileSupport().writeInt(fd, compressedInts ? 1 : 0); +// getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); +// getFileSupport().writeByte(fd, nextGeneration()); } public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition) { + patchFileHeader(constantPoolPosition, metadataPosition, false); + } + + private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition, boolean flushpoint) { long chunkSize = getFileSupport().position(fd).rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); @@ -193,39 +240,68 @@ public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadata getFileSupport().writeLong(fd, metadataPosition.rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); + // *** i guess they didn't write anything else because nothing else changes + getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); + if (flushpoint) { + //chunk is not finished + getFileSupport().writeByte(fd, nextGeneration()); // there are 4 bytes at the end. The first byte is the finished flag. + } else { + getFileSupport().writeByte(fd, COMPLETE); + } + //need to move pointer back to correct position for next write + } + + private byte nextGeneration(){ + if (generation==MAX_BYTE){ + // similar to Hotspot, restart counter if required. + generation = 1; + return MAX_BYTE; + } + return generation++; } - private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories) { + + private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { +// Exception e = new Exception(); +// e.printStackTrace(); + System.out.println("*** Checkpoint"); SignedWord start = beginEvent(); writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(0); // deltaToNext + writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext //*** need to implement this! writeBoolean(true); // flush SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. JfrConstantPool[] serializers = JfrSerializerSupport.get().getSerializers(); - int poolCount = writeConstantPools(serializers) + writeConstantPools(repositories); + int poolCount = writeConstantPools(serializers, flush) + writeConstantPools(repositories, flush); SignedWord currentPos = getFileSupport().position(fd); - getFileSupport().seek(fd, poolCountPos); + getFileSupport().seek(fd, poolCountPos); // *** write number of constant pools written getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); endEvent(start); + lastCheckpointOffset = start.rawValue(); return start; } - private int writeConstantPools(JfrConstantPool[] constantPools) { + private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { int count = 0; for (JfrConstantPool constantPool : constantPools) { - int poolCount = constantPool.write(this); + int poolCount = constantPool.write(this, flush); count += poolCount; } return count; } private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { + // *** works to prevent duplicate metadata from being written to disk + if (currentMetadataId != lastMetadataId) { + lastMetadataId = currentMetadataId; + } else { + return _metadataPosition; + } SignedWord start = beginEvent(); writeCompressedLong(METADATA_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); @@ -233,6 +309,7 @@ private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { writeCompressedLong(0); // metadata id writeBytes(metadataDescriptor); // payload endEvent(start); + _metadataPosition = start; return start; } @@ -366,8 +443,10 @@ public enum StringEncoding { } private class JfrChangeEpochOperation extends JavaVMOperation { - protected JfrChangeEpochOperation() { + private boolean flush; + protected JfrChangeEpochOperation(boolean flush) { super(VMOperationInfos.get(JfrChangeEpochOperation.class, "JFR change epoch", SystemEffect.SAFEPOINT)); + this.flush = flush; } @Override @@ -404,10 +483,12 @@ private void changeEpoch() { write(buffer); JfrBufferAccess.reinitialize(buffer); } - JfrTraceIdEpoch.getInstance().changeEpoch(); + if (!flush) { + JfrTraceIdEpoch.getInstance().changeEpoch(); // *** this needs to be changed for flushes - // Now that the epoch changed, re-register all running threads for the new epoch. - SubstrateJVM.getThreadRepo().registerRunningThreads(); + // Now that the epoch changed, re-register all running threads for the new epoch. + SubstrateJVM.getThreadRepo().registerRunningThreads(); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java index 476a7d91d206..374c4d7f5a18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java @@ -48,7 +48,7 @@ public interface JfrConstantPool { /** * Persists the data of the previous epoch. May only be called at a safepoint, after the epoch - * changed. + * changed. [*** change this comment] */ - int write(JfrChunkWriter writer); + int write(JfrChunkWriter writer, boolean flush); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java index 9cc21d99e06e..b857ecb7e880 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java @@ -36,7 +36,7 @@ public JfrFrameTypeSerializer() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedLong(JfrType.FrameType.getId()); JfrFrameType[] values = JfrFrameType.values(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 79bf96233350..13a3948f6b0d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -37,7 +37,7 @@ public void teardown() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { return EMPTY; } } 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 f8a225bf2533..38fd219a11b2 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 @@ -58,7 +58,7 @@ public void setStackTraceDepth(int depth) { } @Override - public int write(@SuppressWarnings("unused") JfrChunkWriter writer) { + public int write(@SuppressWarnings("unused") JfrChunkWriter writer, boolean flush) { return EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 17259b3cfd99..04607992dac1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -113,8 +113,14 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r } @Override - public int write(JfrChunkWriter writer) { - JfrSymbolHashtable table = getTable(true); + public int write(JfrChunkWriter writer, boolean flush) { + JfrSymbolHashtable table; + if (flush) { + table = getTable(false); + }else { + table = getTable(true); + } + if (table.getSize() == 0) { return EMPTY; } @@ -131,7 +137,9 @@ public int write(JfrChunkWriter writer) { } } } - table.clear(); + if (!flush) { + table.clear(); + } return NON_EMPTY; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index c36f18c944ba..c15342fab039 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -183,11 +183,13 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { } @Override - public int write(JfrChunkWriter writer) { - JfrThreadEpochData epochData = getEpochData(true); + public int write(JfrChunkWriter writer, boolean flush) { + JfrThreadEpochData epochData = getEpochData(!flush); int count = writeThreads(writer, epochData); count += writeThreadGroups(writer, epochData); - epochData.clear(); + if (!flush) { + epochData.clear(); + } return count; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java index ad48bd60765d..93d252b7892e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java @@ -37,7 +37,7 @@ public JfrThreadStateSerializer() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedLong(JfrType.ThreadState.getId()); JfrThreadState[] threadStates = JfrThreadState.values(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index f9af125ba8b9..34b3dc318c55 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -54,10 +54,10 @@ public long getClassId(Class clazz) { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { // Visit all used classes, and collect their packages, modules, classloaders and possibly // referenced classes. - TypeInfo typeInfo = collectTypeInfo(); + TypeInfo typeInfo = collectTypeInfo(flush); // The order of writing matters as following types can be tagged during the write process int count = writeClasses(writer, typeInfo); @@ -70,10 +70,15 @@ public int write(JfrChunkWriter writer) { return count; } - private TypeInfo collectTypeInfo() { + private TypeInfo collectTypeInfo(boolean flush) { TypeInfo typeInfo = new TypeInfo(); for (Class clazz : Heap.getHeap().getLoadedClasses()) { - if (JfrTraceId.isUsedPreviousEpoch(clazz)) { + if (flush) { + if (JfrTraceId.isUsedCurrentEpoch(clazz)) { + visitClass(typeInfo, clazz); + } + } + else if (JfrTraceId.isUsedPreviousEpoch(clazz)) { JfrTraceId.clearUsedPreviousEpoch(clazz); visitClass(typeInfo, clazz); } 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 f5c459896408..7dd4e6ce823d 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 @@ -282,6 +282,9 @@ public static long getThreadId(IsolateThread isolateThread) { /** See {@link JVM#storeMetadataDescriptor}. */ public void storeMetadataDescriptor(byte[] bytes) { metadataDescriptor = bytes; + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); + chunkWriter.setCurrentMetadataId(); + chunkWriter.unlock(); } /** See {@link JVM#beginRecording}. */ @@ -331,13 +334,13 @@ public void setOutput(String file) { if (existingFile) { chunkWriter.closeFile(metadataDescriptor, repositories); } - + System.out.println("*** setOutput: "+ file); if (file != null) { - chunkWriter.openFile(file); + chunkWriter.openFile(file);// *** open up the new file // If in-memory recording was active so far, we should notify the recorder // thread because the global memory buffers could be rather full. if (!existingFile) { - recorderThread.signal(); + recorderThread.signal();// *** do this if there was no existing file } } } else { @@ -451,6 +454,22 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted return false; } +// @Uninterruptible(reason = "Accesses a JFR buffer.") + public void flush() { + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); //does this make it a safepoint? [NO] + try { + if (recording) { + boolean existingFile = chunkWriter.hasOpenFile(); + if (existingFile) { + chunkWriter.flush(metadataDescriptor, repositories); + System.out.println("*** Flushed"); + } + } + } finally { + chunkWriter.unlock(); + } + } + /** See {@link JVM#setRepositoryLocation}. */ public void setRepositoryLocation(@SuppressWarnings("unused") String dirText) { // Would only be used in case of an emergency dump, which is not supported at the moment. 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 3e349d1e5c96..8f6f097945c2 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 @@ -391,24 +391,31 @@ public boolean shouldRotateDisk() { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void flush() { + System.out.println("*** Flush called"); + SubstrateJVM.get().flush(); // Temporarily do nothing. This is used for JFR streaming. } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void include(Thread thread) { + System.out.println("*** include called"); // Temporarily do nothing. This is used for JFR streaming. } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void exclude(Thread thread) { + Exception e = new Exception(); + e.printStackTrace(); + System.out.println("*** exclude called"); // Temporarily do nothing. This is used for JFR streaming. } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public boolean isExcluded(Thread thread) { + System.out.println("*** isExcluded called"); // Temporarily do nothing. This is used for JFR streaming. return false; } @@ -442,6 +449,7 @@ public boolean isInstrumented(Class eventCla @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void markChunkFinal() { + System.out.println("*** markChunkFinal called"); // Temporarily do nothing. This is used for JFR streaming. } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java index 7e39e07f4a23..dfb6a0837184 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java @@ -58,6 +58,11 @@ public static boolean isUsedPreviousEpoch(Class clazz) { long predicate = JfrTraceIdEpoch.getInstance().previousEpochBit(); return predicate(clazz, predicate); } + @Uninterruptible(reason = "Epoch must not change.") + public static boolean isUsedCurrentEpoch(Class clazz) { + long predicate = JfrTraceIdEpoch.getInstance().thisEpochBit(); + return predicate(clazz, predicate); + } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long getTraceIdRaw(Class clazz) { From 0ce364ed8cfd4ddc90c4b2d61dea678dcdfbbe6b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 17 Oct 2022 11:18:24 -0400 Subject: [PATCH 02/72] wip. Fixed dumping format error with thread repo. Fixed symbol table writing. --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 73 ++++++++++++++++--- .../svm/core/jfr/JfrSymbolRepository.java | 7 +- .../svm/core/jfr/JfrThreadRepository.java | 24 ++++-- .../svm/core/jfr/JfrTypeRepository.java | 44 +++++------ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 2 +- 5 files changed, 107 insertions(+), 43 deletions(-) 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 4294536fc191..296f6be59d4c 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 @@ -81,6 +81,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private int lastMetadataId = 0; private int currentMetadataId = 0; + private boolean staticConstantsSerialized = false; public void setCurrentMetadataId(){ currentMetadataId++; @@ -136,12 +137,16 @@ public boolean openFile(String outputFile) { fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); writeFileHeader(); generation = 1; - lastCheckpointOffset = 0;// must reset this on new chunk + lastCheckpointOffset = -1;// must reset this on new chunk + currentMetadataId = -1; return true; } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") // *** top pointer (of the buffer) like if it gets reinit. public boolean write(JfrBuffer buffer) { + return write(buffer, true); + } + @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") // *** top pointer (of the buffer) like if it gets reinit. + public boolean write(JfrBuffer buffer, boolean reset) { assert JfrBufferAccess.isAcquired(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { @@ -149,12 +154,14 @@ public boolean write(JfrBuffer buffer) { } boolean success = getFileSupport().write(fd, buffer.getTop(), unflushedSize); - JfrBufferAccess.increaseTop(buffer, unflushedSize); + if(reset) { + JfrBufferAccess.increaseTop(buffer, unflushedSize); + } if (!success) { // We lost some data because the write failed. return false; } - // need to save pointer position so we can return to it when writing next + // *** need to save pointer position so we can return to it when writing next return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); } @@ -186,21 +193,22 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) filename = null; fd = WordFactory.nullPointer(); } -// private SignedWord _constantPoolPosition; + private SignedWord _constantPoolPosition; private SignedWord _metadataPosition; - public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories) { + public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrConstantPool threadRepo) { assert lock.isHeldByCurrentThread();// fd should always be correct because its cleared and set within locked critical section JfrChangeEpochOperation op = new JfrChangeEpochOperation(true); op.enqueue(); - +//WordFactory.signed(0); // +// writeThreadCheckpointEvent(threadRepo, true); SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); // WILL get written again when the chunk closes and overwrite what we write here. In that case we shouldn't wipe the repos right? How does hotspot handle it? SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); - SignedWord currentPos = getFileSupport().position(fd); + // patchFileHeader(_constantPoolPosition, metadataPosition, true); patchFileHeader(constantPoolPosition, metadataPosition, true); - getFileSupport().seek(fd,currentPos); + // unlike rotate chunk, don't close file. } @@ -232,6 +240,7 @@ public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadata } private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition, boolean flushpoint) { + SignedWord currentPos = getFileSupport().position(fd); long chunkSize = getFileSupport().position(fd).rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); @@ -249,6 +258,7 @@ private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadat getFileSupport().writeByte(fd, COMPLETE); } //need to move pointer back to correct position for next write + getFileSupport().seek(fd,currentPos); } private byte nextGeneration(){ @@ -260,12 +270,44 @@ private byte nextGeneration(){ return generation++; } + private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolean flush) { + + SignedWord start = beginEvent(); + + if (lastCheckpointOffset < 0) { + lastCheckpointOffset = start.rawValue(); + } + writeCompressedLong(CONSTANT_POOL_TYPE_ID); + writeCompressedLong(JfrTicks.elapsedTicks()); + writeCompressedLong(0); // duration + writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext + writeCompressedLong(8); // flush + writeCompressedLong(1); + writeCompressedLong(170); + + SignedWord poolCountPos = getFileSupport().position(fd); + getFileSupport().writeInt(fd, 0); // We'll patch this later. + + int poolCount = threadRepo.write(this, flush); + + SignedWord currentPos = getFileSupport().position(fd); + getFileSupport().seek(fd, poolCountPos); // *** write number of constant pools written + getFileSupport().writeInt(fd, makePaddedInt(poolCount)); + getFileSupport().seek(fd, currentPos); + endEvent(start); + lastCheckpointOffset = start.rawValue(); + return start; + } private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { // Exception e = new Exception(); // e.printStackTrace(); System.out.println("*** Checkpoint"); SignedWord start = beginEvent(); + + if (lastCheckpointOffset < 0) { + lastCheckpointOffset = start.rawValue(); + } writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration @@ -275,7 +317,15 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. JfrConstantPool[] serializers = JfrSerializerSupport.get().getSerializers(); - int poolCount = writeConstantPools(serializers, flush) + writeConstantPools(repositories, flush); + + int poolCount; + if (!staticConstantsSerialized) { + poolCount = writeConstantPools(serializers, flush) + writeConstantPools(repositories, flush); + staticConstantsSerialized = true; + } else { + poolCount = writeConstantPools(repositories, flush); + } +// int poolCount = writeConstantPools(serializers, flush) + writeConstantPools(repositories, flush); SignedWord currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); // *** write number of constant pools written getFileSupport().writeInt(fd, makePaddedInt(poolCount)); @@ -289,6 +339,9 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { int count = 0; for (JfrConstantPool constantPool : constantPools) { +// if (flush && constantPool instanceof com.oracle.svm.core.jfr.JfrThreadRepository) { +// continue; +// } int poolCount = constantPool.write(this, flush); count += poolCount; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 04607992dac1..d494deed0490 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -122,6 +122,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } if (table.getSize() == 0) { + System.out.println("*** ---- empty symbol table"); return EMPTY; } writer.writeCompressedLong(JfrType.Symbol.getId()); @@ -137,9 +138,9 @@ public int write(JfrChunkWriter writer, boolean flush) { } } } - if (!flush) { +// if (!flush) { table.clear(); - } +// } return NON_EMPTY; } @@ -147,7 +148,7 @@ private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { writer.writeCompressedLong(symbol.getId()); writer.writeByte(JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); byte[] value = symbol.getValue().getBytes(StandardCharsets.UTF_8); - if (symbol.getReplaceDotWithSlash()) { + if (symbol.getReplaceDotWithSlash()) { // *** this is where java.lang.Object gets converted replaceDotWithSlash(value); } writer.writeCompressedInt(value.length); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index c15342fab039..11a4b53a0eaf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -113,7 +113,7 @@ private void registerThread0(Thread thread) { } JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); + JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); // *** set up event writer to write to thread buffer JfrNativeEventWriter.putLong(data, JavaThreads.getThreadId(thread)); // JFR trace id JfrNativeEventWriter.putString(data, thread.getName()); // Java or native thread name @@ -131,7 +131,7 @@ private void registerThread0(Thread thread) { } JfrNativeEventWriter.commit(data); - // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we + // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we // *** underlying buffer of epochData.threadBuffer may have been replaced // need to update the repository pointer as well. epochData.threadBuffer = data.getJfrBuffer(); } @@ -184,26 +184,34 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { @Override public int write(JfrChunkWriter writer, boolean flush) { + if (flush){ + mutex.lock(); // probably dont need this anymore + } JfrThreadEpochData epochData = getEpochData(!flush); - int count = writeThreads(writer, epochData); - count += writeThreadGroups(writer, epochData); + int count = writeThreads(writer, epochData, flush); + count += writeThreadGroups(writer, epochData, flush); if (!flush) { epochData.clear(); } + + if (flush){ + mutex.unlock(); + } + return count; } - private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { + private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); writer.writeCompressedLong(JfrType.Thread.getId()); writer.writeCompressedInt(epochData.visitedThreads.getSize()); - writer.write(epochData.threadBuffer); + writer.write(epochData.threadBuffer, !flush); return NON_EMPTY; } - private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { + private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { int threadGroupCount = epochData.visitedThreadGroups.getSize(); if (threadGroupCount == 0) { return EMPTY; @@ -211,7 +219,7 @@ private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData e writer.writeCompressedLong(JfrType.ThreadGroup.getId()); writer.writeCompressedInt(threadGroupCount); - writer.write(epochData.threadGroupBuffer); + writer.write(epochData.threadGroupBuffer, !flush); return NON_EMPTY; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 34b3dc318c55..2bf87991e954 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -57,13 +57,14 @@ public long getClassId(Class clazz) { public int write(JfrChunkWriter writer, boolean flush) { // Visit all used classes, and collect their packages, modules, classloaders and possibly // referenced classes. + TypeInfo typeInfo = collectTypeInfo(flush); // The order of writing matters as following types can be tagged during the write process - int count = writeClasses(writer, typeInfo); - count += writePackages(writer, typeInfo); - count += writeModules(writer, typeInfo); - count += writeClassLoaders(writer, typeInfo); + int count = writeClasses(writer, typeInfo, flush); + count += writePackages(writer, typeInfo, flush); + count += writeModules(writer, typeInfo, flush); + count += writeClassLoaders(writer, typeInfo, flush); count += writeGCCauses(writer); count += writeGCNames(writer); count += writeVMOperations(writer); @@ -112,7 +113,7 @@ private void visitClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { } } - public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo) { + public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.getClasses().isEmpty()) { return EMPTY; } @@ -120,16 +121,17 @@ public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo) { writer.writeCompressedInt(typeInfo.getClasses().size()); for (Class clazz : typeInfo.getClasses()) { - writeClass(writer, typeInfo, clazz); + writeClass(writer, typeInfo, clazz, flush); } return NON_EMPTY; } - private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class clazz) { + private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class clazz, boolean flush) { + System.out.println("*** --- Writing Class:"+clazz.getName()); JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); // key writer.writeCompressedLong(typeInfo.getClassLoaderId(clazz.getClassLoader())); - writer.writeCompressedLong(symbolRepo.getSymbolId(clazz.getName(), true, true)); + writer.writeCompressedLong(symbolRepo.getSymbolId(clazz.getName(), !flush, true)); writer.writeCompressedLong(typeInfo.getPackageId(clazz.getPackage())); writer.writeCompressedLong(clazz.getModifiers()); if (JavaVersionUtil.JAVA_SPEC >= 17) { @@ -137,7 +139,7 @@ private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class packages = typeInfo.getPackages(); if (packages.isEmpty()) { return EMPTY; @@ -146,20 +148,20 @@ private static int writePackages(JfrChunkWriter writer, TypeInfo typeInfo) { writer.writeCompressedInt(packages.size()); for (Map.Entry pkgInfo : packages.entrySet()) { - writePackage(writer, typeInfo, pkgInfo.getKey(), pkgInfo.getValue()); + writePackage(writer, typeInfo, pkgInfo.getKey(), pkgInfo.getValue(), flush); } return NON_EMPTY; } - private static void writePackage(JfrChunkWriter writer, TypeInfo typeInfo, String pkgName, PackageInfo pkgInfo) { + private static void writePackage(JfrChunkWriter writer, TypeInfo typeInfo, String pkgName, PackageInfo pkgInfo, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(pkgInfo.id); // id - writer.writeCompressedLong(symbolRepo.getSymbolId(pkgName, true, true)); + writer.writeCompressedLong(symbolRepo.getSymbolId(pkgName, !flush, true)); writer.writeCompressedLong(typeInfo.getModuleId(pkgInfo.module)); writer.writeBoolean(false); // exported } - private static int writeModules(JfrChunkWriter writer, TypeInfo typeInfo) { + private static int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { Map modules = typeInfo.getModules(); if (modules.isEmpty()) { return EMPTY; @@ -168,21 +170,21 @@ private static int writeModules(JfrChunkWriter writer, TypeInfo typeInfo) { writer.writeCompressedInt(modules.size()); for (Map.Entry modInfo : modules.entrySet()) { - writeModule(writer, typeInfo, modInfo.getKey(), modInfo.getValue()); + writeModule(writer, typeInfo, modInfo.getKey(), modInfo.getValue(), flush); } return NON_EMPTY; } - private static void writeModule(JfrChunkWriter writer, TypeInfo typeInfo, Module module, long id) { + private static void writeModule(JfrChunkWriter writer, TypeInfo typeInfo, Module module, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); - writer.writeCompressedLong(symbolRepo.getSymbolId(module.getName(), true)); + writer.writeCompressedLong(symbolRepo.getSymbolId(module.getName(), !flush)); writer.writeCompressedLong(0); // Version, e.g. "11.0.10-internal" writer.writeCompressedLong(0); // Location, e.g. "jrt:/java.base" writer.writeCompressedLong(typeInfo.getClassLoaderId(module.getClassLoader())); } - private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo) { + private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { Map classLoaders = typeInfo.getClassLoaders(); if (classLoaders.isEmpty()) { return EMPTY; @@ -191,7 +193,7 @@ private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo) { writer.writeCompressedInt(classLoaders.size()); for (Map.Entry clInfo : classLoaders.entrySet()) { - writeClassLoader(writer, clInfo.getKey(), clInfo.getValue()); + writeClassLoader(writer, clInfo.getKey(), clInfo.getValue(), flush); } return NON_EMPTY; } @@ -245,15 +247,15 @@ private static int writeVMOperations(JfrChunkWriter writer) { return NON_EMPTY; } - private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id) { + private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); if (cl == null) { writer.writeCompressedLong(0); - writer.writeCompressedLong(symbolRepo.getSymbolId("bootstrap", true)); + writer.writeCompressedLong(symbolRepo.getSymbolId("bootstrap", !flush)); } else { writer.writeCompressedLong(JfrTraceId.getTraceId(cl.getClass())); - writer.writeCompressedLong(symbolRepo.getSymbolId(cl.getName(), true)); + writer.writeCompressedLong(symbolRepo.getSymbolId(cl.getName(), !flush)); } } 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 7dd4e6ce823d..a0c28c8141ac 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 @@ -461,7 +461,7 @@ public void flush() { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.flush(metadataDescriptor, repositories); + chunkWriter.flush(metadataDescriptor, repositories, threadRepo); System.out.println("*** Flushed"); } } From 866ea81ac9557dbbf5b1de01719d5310732b05e7 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 18 Oct 2022 09:39:42 -0400 Subject: [PATCH 03/72] fix thread pool writing. Write only when dirty. Clear everytime. --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 21 +++++++----- .../svm/core/jfr/JfrThreadRepository.java | 33 +++++++++++++------ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 4 +-- 3 files changed, 37 insertions(+), 21 deletions(-) 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 296f6be59d4c..b993f2e006e7 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 @@ -77,7 +77,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private static final byte COMPLETE = 0; private static final byte MAX_BYTE = 127; - private long lastCheckpointOffset = 0; + public long lastCheckpointOffset = 0; private int lastMetadataId = 0; private int currentMetadataId = 0; @@ -168,7 +168,7 @@ public boolean write(JfrBuffer buffer, boolean reset) { /** * Write all the in-memory data to the file. */ - public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) { + public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread(); System.out.println("*** rotating chunk: closeFile"); /* @@ -183,6 +183,9 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) * data structures of the new epoch. This guarantees that the data in the old epoch can be * persisted to a file without a safepoint. */ + if (threadRepo.isDirty(false)){ + writeThreadCheckpointEvent(threadRepo, false); + } SignedWord constantPoolPosition = writeCheckpointEvent(repositories, false); SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); // _constantPoolPosition = constantPoolPosition; @@ -195,13 +198,14 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) } private SignedWord _constantPoolPosition; private SignedWord _metadataPosition; - public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrConstantPool threadRepo) { + public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread();// fd should always be correct because its cleared and set within locked critical section JfrChangeEpochOperation op = new JfrChangeEpochOperation(true); op.enqueue(); -//WordFactory.signed(0); // -// writeThreadCheckpointEvent(threadRepo, true); + if (threadRepo.isDirty(true)){ + writeThreadCheckpointEvent(threadRepo, true); + } SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); // WILL get written again when the chunk closes and overwrite what we write here. In that case we shouldn't wipe the repos right? How does hotspot handle it? SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); @@ -281,9 +285,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext - writeCompressedLong(8); // flush - writeCompressedLong(1); - writeCompressedLong(170); + writeCompressedLong(8); // *** Threads SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. @@ -339,7 +341,8 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { int count = 0; for (JfrConstantPool constantPool : constantPools) { -// if (flush && constantPool instanceof com.oracle.svm.core.jfr.JfrThreadRepository) { +// if (constantPool instanceof com.oracle.svm.core.jfr.JfrThreadRepository) { +// System.out.println("*** Skipping thread repo"); // continue; // } int poolCount = constantPool.write(this, flush); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 11a4b53a0eaf..2b0313398d22 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -47,6 +47,10 @@ import jdk.jfr.internal.Options; +import static com.oracle.svm.core.jfr.JfrChunkWriter.CONSTANT_POOL_TYPE_ID; +import static com.oracle.svm.core.jfr.JfrChunkWriter.getFileSupport; +import static com.oracle.svm.core.jfr.JfrNativeEventWriter.makePaddedInt; + /** * Repository that collects all metadata about threads and thread groups. */ @@ -134,6 +138,7 @@ private void registerThread0(Thread thread) { // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we // *** underlying buffer of epochData.threadBuffer may have been replaced // need to update the repository pointer as well. epochData.threadBuffer = data.getJfrBuffer(); + epochData.isDirty = true; } @Uninterruptible(reason = "Epoch must not change while in this method.") @@ -184,42 +189,49 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { @Override public int write(JfrChunkWriter writer, boolean flush) { + JfrThreadEpochData epochData = getEpochData(!flush); if (flush){ - mutex.lock(); // probably dont need this anymore + mutex.lock(); //only required when possibility of read/write same epoch data } - JfrThreadEpochData epochData = getEpochData(!flush); int count = writeThreads(writer, epochData, flush); count += writeThreadGroups(writer, epochData, flush); - if (!flush) { - epochData.clear(); - } + + epochData.clear(); // *** can clear this because we only write when dirty + + epochData.isDirty = false; if (flush){ mutex.unlock(); } - return count; } + public boolean isDirty(boolean flush){ + //***no lock needed bc flag cannot be unset unless in epoch change or in flush. This is the only thread that can unset it bc of lock on chunkwriter. + // *** Race to read vs set flag is not a problem + JfrThreadEpochData epochData = getEpochData(!flush); + return epochData.isDirty; + } + private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); writer.writeCompressedLong(JfrType.Thread.getId()); - writer.writeCompressedInt(epochData.visitedThreads.getSize()); - writer.write(epochData.threadBuffer, !flush); + writer.writeCompressedInt(epochData.visitedThreads.getSize()); // *** This needs to be cleared (as well as the buffers) + writer.write(epochData.threadBuffer, true); return NON_EMPTY; } private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { - int threadGroupCount = epochData.visitedThreadGroups.getSize(); + int threadGroupCount = epochData.visitedThreadGroups.getSize(); // *** This needs to be cleared (as well as the buffers) if (threadGroupCount == 0) { return EMPTY; } writer.writeCompressedLong(JfrType.ThreadGroup.getId()); writer.writeCompressedInt(threadGroupCount); - writer.write(epochData.threadGroupBuffer, !flush); + writer.write(epochData.threadGroupBuffer, true); return NON_EMPTY; } @@ -274,6 +286,7 @@ private static class JfrThreadEpochData { * only invoked once per thread (there can be races when re-registering already running * threads). */ + private boolean isDirty = false; private final JfrVisitedTable visitedThreads; private final JfrVisitedTable visitedThreadGroups; 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 a0c28c8141ac..7794ad3b2382 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 @@ -101,7 +101,7 @@ public SubstrateJVM(List configurations) { * Current rules: 1. methodRepo should be after stackTraceRepo; 2. typeRepo should be after * methodRepo and stackTraceRepo; 3. symbolRepo should be on end. */ - repositories = new JfrConstantPool[]{stackTraceRepo, methodRepo, typeRepo, threadRepo, symbolRepo}; + repositories = new JfrConstantPool[]{stackTraceRepo, methodRepo, typeRepo, symbolRepo}; threadLocal = new JfrThreadLocal(); globalMemory = new JfrGlobalMemory(); @@ -332,7 +332,7 @@ public void setOutput(String file) { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.closeFile(metadataDescriptor, repositories); + chunkWriter.closeFile(metadataDescriptor, repositories, threadRepo); } System.out.println("*** setOutput: "+ file); if (file != null) { From be8f179e4f4931ab69542444cde8d642fbe739b7 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 27 Oct 2022 11:40:16 -0400 Subject: [PATCH 04/72] flushing/rotate thread does all cleanup. Reuse JFR buffer lock --- .../oracle/svm/core/jfr/JfrBufferAccess.java | 2 +- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 102 ++++++++ .../oracle/svm/core/jfr/JfrChunkWriter.java | 226 ++++++++++++++++-- .../svm/core/jfr/JfrNativeEventWriter.java | 6 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 200 +++++++++++++--- .../svm/core/jfr/JfrTypeRepository.java | 2 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 8 +- .../svm/core/jfr/events/ThreadSleepEvent.java | 4 +- 8 files changed, 487 insertions(+), 63 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 4a7876fcd088..814141ffe0e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -88,7 +88,7 @@ public static boolean acquire(JfrBuffer buffer) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void release(JfrBuffer buffer) { - assert buffer.getAcquired() == ACQUIRED; + com.oracle.svm.core.util.VMError.guarantee(buffer.getAcquired() == ACQUIRED, "^^^21");////assert buffer.getAcquired() == ACQUIRED; buffer.setAcquired(NOT_ACQUIRED); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java new file mode 100644 index 000000000000..9e91e6a46d5d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -0,0 +1,102 @@ +package com.oracle.svm.core.jfr; + +import com.oracle.svm.core.jfr.JfrThreadLocal; +import com.oracle.svm.core.Uninterruptible; +import org.graalvm.word.WordFactory; +import org.graalvm.compiler.nodes.NamedLocationIdentity; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; + +public class JfrBufferNodeLinkedList { + private JfrThreadLocal.JfrBufferNode head; + JfrThreadLocal.JfrBufferNode lock; // TODO: remember to clean this up + @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) + public boolean isAcquired() { + return isAcquired(lock); + } + @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) + public boolean acquire() { +// return lock.logicCompareAndSwapWord(0, WordFactory.nullPointer(), WordFactory.pointer(1), NamedLocationIdentity.OFF_HEAP_LOCATION); + return acquire(lock); + } + @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) + public void release() { +// com.oracle.svm.core.util.VMError.guarantee(!acquire(), "^^^13");//assert !acquire(); + com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1, "^^^26"); + release(lock); +// boolean result = lock.logicCompareAndSwapWord(0, WordFactory.pointer(1), WordFactory.nullPointer(), NamedLocationIdentity.OFF_HEAP_LOCATION); + } + + @Uninterruptible(reason = "Called from uninterruptible code.") + public JfrThreadLocal.JfrBufferNode getHead() { + com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1|| com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^12");//assert !acquire(); + return head; + } + + @Uninterruptible(reason = "Called from uninterruptible code.") + private void setHead(JfrThreadLocal.JfrBufferNode node) { + com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1 || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^11");//assert !acquire(); + head = node; + } + + private static org.graalvm.word.UnsignedWord getHeaderSize() { +// return com.oracle.svm.core.util.UnsignedUtils.roundUp(WordFactory.unsigned(1), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrThreadLocal.JfrBufferNode.class), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + } + public JfrBufferNodeLinkedList(){ + head = WordFactory.nullPointer(); + lock = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); +// lock = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(getHeaderSize()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.") + public void removeNode(JfrThreadLocal.JfrBufferNode prev, JfrThreadLocal.JfrBufferNode node){ + + JfrThreadLocal.JfrBufferNode next = node.getNext(); // next could be null if node is tail + com.oracle.svm.core.util.VMError.guarantee(head.isNonNull(), "^^^8");//assert head.isNonNull(); + if (node == head) { + com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1 || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^22");//assert !acquire(); + assert prev.isNull(); + setHead(next); // head could now be null if there was only one node in the list + } else { + prev.setNext(next); // prev could now be "tail" if current was tail + } + + // Free LL node holding buffer + com.oracle.svm.core.util.VMError.guarantee(node.getValue().isNonNull(), "^^^9");//assert node.getValue().isNonNull(); + JfrBufferAccess.free(node.getValue()); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public void addNode(JfrThreadLocal.JfrBufferNode node){ + int count =0; + while(!acquire()) { // *** need infinite tries. + count++; + com.oracle.svm.core.util.VMError.guarantee(count < 1000, "^^^23"); + } + if (head.isNull()){ + node.setNext(WordFactory.nullPointer()); + head = node; + release(); + return; + } + node.setNext(head); + head = node; + release(); + } + + @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + public static boolean acquire(JfrThreadLocal.JfrBufferNode buffer) { + return ((org.graalvm.word.Pointer) buffer).logicCompareAndSwapInt(JfrThreadLocal.JfrBufferNode.offsetOfAcquired(), 0, 1, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); + } + + @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + public static void release(JfrThreadLocal.JfrBufferNode buffer) { + com.oracle.svm.core.util.VMError.guarantee(buffer.getAcquired() == 1, "^^^10");//assert buffer.getAcquired() == 1; + buffer.setAcquired(0); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isAcquired(JfrThreadLocal.JfrBufferNode buffer) { + return buffer.getAcquired() == 1; + } +} \ No newline at end of file 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 b993f2e006e7..75cfd5e237fb 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 @@ -30,7 +30,6 @@ import com.oracle.svm.core.heap.VMOperationInfos; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.core.common.NumUtil; -import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.SignedWord; @@ -42,9 +41,11 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMOperationControl; -import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; +import com.oracle.svm.core.jfr.JfrThreadLocal.JfrBufferNode; +import com.oracle.svm.core.util.VMError; + /** * This class is used when writing the in-memory JFR data to a file. For all operations, except * those listed in {@link JfrUnlockedChunkWriter}, it is necessary to acquire the {@link #lock} @@ -82,6 +83,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private int lastMetadataId = 0; private int currentMetadataId = 0; private boolean staticConstantsSerialized = false; + private boolean newChunk = true; public void setCurrentMetadataId(){ currentMetadataId++; @@ -130,15 +132,15 @@ public void maybeOpenFile() { public boolean openFile(String outputFile) { assert lock.isHeldByCurrentThread(); + generation = 1; + newChunk = true; System.out.println("*** ChunkWriter openfile"); chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); writeFileHeader(); - generation = 1; lastCheckpointOffset = -1;// must reset this on new chunk - currentMetadataId = -1; return true; } @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") // *** top pointer (of the buffer) like if it gets reinit. @@ -161,7 +163,6 @@ public boolean write(JfrBuffer buffer, boolean reset) { // We lost some data because the write failed. return false; } - // *** need to save pointer position so we can return to it when writing next return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); } @@ -175,8 +176,10 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, * Switch to a new epoch. This is done at a safepoint to ensure that we end up with * consistent data, even if multiple threads have JFR events in progress. */ + System.out.println("*** safepoint start"); JfrChangeEpochOperation op = new JfrChangeEpochOperation(false); op.enqueue(); + System.out.println("*** safepoint end"); /* * After changing the epoch, all subsequently triggered JFR events will be recorded into the @@ -201,8 +204,10 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread();// fd should always be correct because its cleared and set within locked critical section - JfrChangeEpochOperation op = new JfrChangeEpochOperation(true); - op.enqueue(); +// JfrChangeEpochOperation op = new JfrChangeEpochOperation(true); +// op.enqueue(); + flushStorage(); + if (threadRepo.isDirty(true)){ writeThreadCheckpointEvent(threadRepo, true); } @@ -353,8 +358,9 @@ private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { // *** works to prevent duplicate metadata from being written to disk - if (currentMetadataId != lastMetadataId) { + if (currentMetadataId != lastMetadataId || newChunk) { lastMetadataId = currentMetadataId; + newChunk = false; //always write metadata on a new chunk! } else { return _metadataPosition; } @@ -520,27 +526,34 @@ protected void operate() { private void changeEpoch() { // 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 - for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { - JfrBuffer buffer = JfrThreadLocal.getJavaBuffer(thread); - if (buffer.isNonNull()) { - write(buffer); - JfrThreadLocal.notifyEventWriter(thread); - } - buffer = JfrThreadLocal.getNativeBuffer(thread); - if (buffer.isNonNull()) { - write(buffer); - } - } +// for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { +// JfrBuffer buffer = JfrThreadLocal.getJavaBuffer(thread); +// if (buffer.isNonNull()) { +// write(buffer); +// JfrThreadLocal.notifyEventWriter(thread); +// } +// buffer = JfrThreadLocal.getNativeBuffer(thread); +// if (buffer.isNonNull()) { +// write(buffer); +// } +// } + + JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); + JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); + + traverseList(javaBuffers, true, true); + + traverseList(nativeBuffers, false, true); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { JfrBuffer buffer = buffers.addressOf(i).read(); - assert !JfrBufferAccess.isAcquired(buffer); + VMError.guarantee(!JfrBufferAccess.isAcquired(buffer), "^^^6");//assert !JfrBufferAccess.isAcquired(buffer); write(buffer); JfrBufferAccess.reinitialize(buffer); } if (!flush) { - JfrTraceIdEpoch.getInstance().changeEpoch(); // *** this needs to be changed for flushes + JfrTraceIdEpoch.getInstance().changeEpoch(); // Now that the epoch changed, re-register all running threads for the new epoch. SubstrateJVM.getThreadRepo().registerRunningThreads(); @@ -548,6 +561,177 @@ private void changeEpoch() { } } + @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") + private void flushStorage() { + JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); + JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); +// int count = javaBuffers.getSize(); // in case other threads are adding nodes + + traverseList(javaBuffers, true, false); + traverseList(nativeBuffers, false, false); + + JfrBuffers buffers = globalMemory.getBuffers(); + for (int i = 0; i < globalMemory.getBufferCount(); i++) { + JfrBuffer buffer = buffers.addressOf(i).read(); + if(!JfrBufferAccess.acquire(buffer)){ // one attempt + continue; + } +// assert !JfrBufferAccess.isAcquired(buffer); // *** need to deal with this too + write(buffer); + JfrBufferAccess.reinitialize(buffer); + JfrBufferAccess.release(buffer); + } + } +// @Uninterruptible(reason = "Called from uninterruptible code.") +// private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { +// // Try to lock list +// if (!safepoint) { +// for (int retry = 0; retry < 100; retry++) { +// if (linkedList.acquire()) { +// break; +// } +// } +// if (!linkedList.isAcquired()) { +// VMError.guarantee(!safepoint, "^^^4");//assert !safepoint; // if safepoint, no one else should hold the lock on the LL. +// return; // wasn't able to get the lock +// } +// } +// +// JfrBufferNode node = linkedList.getHead(); +// JfrBufferNode prev = WordFactory.nullPointer(); +// int count = 0; +// +// while (node.isNonNull()) { +// count++; +// VMError.guarantee(count < 1000, "^^^26"); +// +// // An optimization +// if (linkedList.isAcquired()) { // evaluate this first +// if (node != linkedList.getHead()) { +// // only need lock when dealing with head. Because other threads add nodes in direction opposite to traversal. +// VMError.guarantee(prev.isNonNull(), "^^^2");//assert prev.isNonNull(); +// linkedList.release(); +// } +// } +// +// JfrBufferNode next = node.getNext(); +// +// // Try to get node if not in safepoint +// if (!safepoint && !JfrBufferNodeLinkedList.acquire(node)) { //make one attempt +// VMError.guarantee(!safepoint, "^^^1"); //if safepoint, no one else should hold the lock on the LL node. TODO: causes error when checked at safepoint (common) +// prev = node; +// node = next; +// continue; +// } +// +// // Try to write to disk. (If thread doesn't flush at death, this is always safe to do because we remove nodes afterward) +// JfrBuffer buffer = node.getValue(); +// VMError.guarantee(buffer.isNonNull(), "^^^3");//assert buffer.isNonNull(); +// if (!safepoint && JfrBufferAccess.acquire(buffer)) { +// VMError.guarantee(JfrBufferAccess.isAcquired(buffer), "^^^5"); // TODO: causes error when checked at safepoint +// write(buffer); +// JfrBufferAccess.release(buffer); +// }else { +// write(buffer); +// } +// if (java) { +// VMError.guarantee(node.getThread().isNonNull(), "^^^20"); +// JfrThreadLocal.notifyEventWriter(node.getThread()); +// } +// +// if (!safepoint) { +// JfrBufferNodeLinkedList.release(node); +// } +// +// // Try to remove if needed (If thread doesn't flush at death, this must be after flushing to disk block) +// if (!node.getAlive()){ +// linkedList.removeNode(prev, node); +// // don't update previous here! +// node = next; +// continue; +// } +// +// prev = node; // prev is always the last node still in the list before the current node. Prev may not be alive. +// node = next; +// } +// +// if (linkedList.isAcquired()) { +// linkedList.release(); +// } +// } + @Uninterruptible(reason = "Called from uninterruptible code.") + private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { + // Try to lock list + if (!safepoint) { + for (int retry = 0; retry < 100; retry++) { + if (linkedList.acquire()) { + break; + } + } + if (!linkedList.isAcquired()) { + VMError.guarantee(!safepoint, "^^^4");//assert !safepoint; // if safepoint, no one else should hold the lock on the LL. + return; // wasn't able to get the lock + } + } + + JfrBufferNode node = linkedList.getHead(); + JfrBufferNode prev = WordFactory.nullPointer(); + int count = 0; + + while (node.isNonNull()) { + count++; + VMError.guarantee(count < 1000, "^^^26"); + + // An optimization + if (linkedList.isAcquired()) { // evaluate this first + if (node != linkedList.getHead()) { + // only need lock when dealing with head. Because other threads add nodes in direction opposite to traversal. + VMError.guarantee(prev.isNonNull(), "^^^2");//assert prev.isNonNull(); + linkedList.release(); + } + } + + JfrBufferNode next = node.getNext(); + JfrBuffer buffer = node.getValue(); + VMError.guarantee(buffer.isNonNull(), "^^^3");//assert buffer.isNonNull(); + + // Try to get BUFFER if not in safepoint + if (!safepoint && !JfrBufferAccess.acquire(buffer)) { //make one attempt + VMError.guarantee(!safepoint, "^^^1"); //if safepoint, no one else should hold the lock on the LL node. TODO: causes error when checked at safepoint (common) + prev = node; + node = next; + continue; + } + VMError.guarantee(JfrBufferAccess.isAcquired(buffer) || safepoint, "^^^5"); + write(buffer); + + // Try to write to disk. (If thread doesn't flush at death, this is always safe to do because we remove nodes afterward) + if (!safepoint) { + JfrBufferAccess.release(buffer); + } + + if (java) { + VMError.guarantee(node.getThread().isNonNull(), "^^^20"); + JfrThreadLocal.notifyEventWriter(node.getThread()); + } + + + // Try to remove if needed (If thread doesn't flush at death, this must be after flushing to disk block) + if (!node.getAlive()){ + linkedList.removeNode(prev, node); + // don't update previous here! + node = next; + continue; + } + + prev = node; // prev is always the last node still in the list before the current node. Prev may not be alive. + node = next; + } + + if (linkedList.isAcquired()) { + linkedList.release(); + } +} public long getChunkStartNanos() { return chunkStartNanos; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index cb47d42e1151..0d1c809d1fd9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -292,7 +292,11 @@ private static JfrBuffer accomodate0(JfrNativeEventWriterData data, UnsignedWord JfrBuffer oldBuffer = data.getJfrBuffer(); switch (oldBuffer.getBufferType()) { case THREAD_LOCAL_NATIVE: - return JfrThreadLocal.flush(oldBuffer, uncommitted, requested); +// JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); +// jfrThreadLocal.lockNative(); + com.oracle.svm.core.jfr.JfrBuffer buffer = JfrThreadLocal.flush(oldBuffer, uncommitted, requested); +// jfrThreadLocal.unlockNative(); + return buffer; case C_HEAP: return reuseOrReallocateBuffer(oldBuffer, uncommitted, requested); default: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 3615b59985ef..60a44c4b3c8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -49,6 +49,10 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; + /** * This class holds various JFR-specific thread local values. * @@ -66,22 +70,89 @@ * modify the buffers of other threads). */ public class JfrThreadLocal implements ThreadListener { + @RawStructure + public interface JfrBufferNode extends com.oracle.svm.core.jdk.UninterruptibleEntry { + @RawField + JfrBuffer getValue(); + @RawField + void setValue(JfrBuffer value); + + @RawField + IsolateThread getThread(); + @RawField + void setThread(IsolateThread thread); + + @RawField + boolean getAlive(); + @RawField + void setAlive(boolean alive); + @RawField + int getAcquired(); + + @RawField + void setAcquired(int value); + + @org.graalvm.nativeimage.c.struct.RawFieldOffset + static int offsetOfAcquired() { + throw VMError.unimplemented(); // replaced + } + } + private static final FastThreadLocalObject javaEventWriter = FastThreadLocalFactory.createObject(Target_jdk_jfr_internal_EventWriter.class, "JfrThreadLocal.javaEventWriter"); - private static final FastThreadLocalWord javaBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBuffer"); - private static final FastThreadLocalWord nativeBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBuffer"); + // *** holds a pointer to the buffer that's on the heap +// private static final FastThreadLocalWord javaBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBuffer"); + private static final FastThreadLocalWord javaBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBufferNode"); +// private static final FastThreadLocalWord nativeBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBuffer"); + private static final FastThreadLocalWord nativeBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBufferNode"); private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId"); private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId"); private long threadLocalBufferSize; + private static JfrBufferNodeLinkedList javaBufferList; + private static JfrBufferNodeLinkedList nativeBufferList; + @Uninterruptible(reason = "Called from uninterruptible code.") + public static JfrBufferNodeLinkedList getNativeBufferList(){ + return nativeBufferList; + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public static JfrBufferNodeLinkedList getJavaBufferList(){ + return javaBufferList; + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public static void lockNative(){ + int count = 0; + while(!JfrBufferNodeLinkedList.acquire(nativeBufferNode.get())){ + count++; + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^24"); + } + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public static void unlockNative(){ + JfrBufferNodeLinkedList.release(nativeBufferNode.get()); + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public static void lockJava(){ + int count =0; + while(!JfrBufferNodeLinkedList.acquire(javaBufferNode.get())){ + count++; + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^25"); + } + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public static void unlockJava(){ + JfrBufferNodeLinkedList.release(javaBufferNode.get()); + } @Platforms(Platform.HOSTED_ONLY.class) public JfrThreadLocal() { } - public void initialize(long bufferSize) { + public void initialize(long bufferSize) { // *** at runtime? this.threadLocalBufferSize = bufferSize; + javaBufferList = new JfrBufferNodeLinkedList(); + nativeBufferList = new JfrBufferNodeLinkedList(); } @Uninterruptible(reason = "Accesses a JFR buffer.") @@ -107,33 +178,57 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { @Override public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { + /** + * *** Why do we need locks here? Avoid simultaneously flushing to disk TLB and promoting it (here). + * // *** no blocking on java buffer bc it is null! When guarantee nonNull, it blocks! doesn't seem like adding the isolateThread param makes a diff + * Still not sure why the JFR buffer spinlock was blocking + */ + // Emit ThreadEnd event after thread.run() finishes. ThreadEndEvent.emit(isolateThread); + JfrBufferNode jbn = javaBufferNode.get(isolateThread); + JfrBufferNode nbn = nativeBufferNode.get(isolateThread); + + if (jbn.isNonNull()) { + JfrBuffer jb = jbn.getValue(); + assert jb.isNonNull() && jbn.getAlive(); +// while(!JfrBufferNodeLinkedList.acquire(jbn)); + +// if (SubstrateJVM.isRecording()) { +// if (jb.isNonNull()) { +// flush(jb, WordFactory.unsigned(0), 0); +// } +// } + jbn.setAlive(false); // TODO: should this be atomic? +// JfrBufferAccess.free(jb); +// JfrBufferNodeLinkedList.release(jbn); + } - // Flush all buffers if necessary. - if (SubstrateJVM.isRecording()) { - JfrBuffer jb = javaBuffer.get(isolateThread); - if (jb.isNonNull()) { - flush(jb, WordFactory.unsigned(0), 0); - } - JfrBuffer nb = nativeBuffer.get(isolateThread); - if (nb.isNonNull()) { - flush(nb, WordFactory.unsigned(0), 0); - } + if (nbn.isNonNull()) { + JfrBuffer nb = nbn.getValue(); + assert nb.isNonNull() && nbn.getAlive(); +// while(!JfrBufferNodeLinkedList.acquire(nbn)); + + // Flush all buffers if necessary. +// if (SubstrateJVM.isRecording()) { +// if (nb.isNonNull()) { +// flush(nb, WordFactory.unsigned(0), 0); +// } +// } + nbn.setAlive(false); +// JfrBufferAccess.free(nb); +// JfrBufferNodeLinkedList.release(nbn); } + // Free and reset all data. threadId.set(isolateThread, 0); parentThreadId.set(isolateThread, 0); dataLost.set(isolateThread, WordFactory.unsigned(0)); javaEventWriter.set(isolateThread, null); - - freeBuffer(javaBuffer.get(isolateThread)); - javaBuffer.set(isolateThread, WordFactory.nullPointer()); - - freeBuffer(nativeBuffer.get(isolateThread)); - nativeBuffer.set(isolateThread, WordFactory.nullPointer()); + javaBufferNode.set(isolateThread, WordFactory.nullPointer()); + nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -155,7 +250,8 @@ public Target_jdk_jfr_internal_EventWriter getEventWriter() { // uninterruptible. public Target_jdk_jfr_internal_EventWriter newEventWriter() { assert javaEventWriter.get() == null; - assert javaBuffer.get().isNull(); +// assert javaBuffer.get().isNull(); + assert javaBufferNode.get().isNull(); JfrBuffer buffer = getJavaBuffer(); if (buffer.isNull()) { @@ -177,39 +273,64 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { return result; } + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static UnsignedWord getHeaderSize() { + return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + } + @Uninterruptible(reason = "Called from uninterruptible code.") + private static JfrBufferNode allocate(com.oracle.svm.core.jfr.JfrBuffer buffer) { + JfrBufferNode node = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); + VMError.guarantee(node.isNonNull()); + node.setValue(buffer); + node.setAlive(true); + return node; + } @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { VMError.guarantee(threadId.get() > 0, "Thread local JFR data must be initialized"); - JfrBuffer result = javaBuffer.get(); +// JfrBuffer result = javaBuffer.get(); + JfrBufferNode result = javaBufferNode.get(); if (result.isNull()) { - result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); - javaBuffer.set(result); + JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); +// javaBuffer.set(result); + result = allocate(buffer); + result.setThread(CurrentIsolate.getCurrentThread()); + javaBufferNode.set(result); + javaBufferList.addNode(result); } - return result; + return result.getValue(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public JfrBuffer getNativeBuffer() { VMError.guarantee(threadId.get() > 0, "Thread local JFR data must be initialized"); - JfrBuffer result = nativeBuffer.get(); +// JfrBuffer result = nativeBuffer.get(); + JfrBufferNode result = nativeBufferNode.get(); if (result.isNull()) { - result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); - nativeBuffer.set(result); + JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); +// nativeBuffer.set(result); + result = allocate(buffer); + nativeBufferNode.set(result); + nativeBufferList.addNode(result); } - return result; + return result.getValue(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public static JfrBuffer getJavaBuffer(IsolateThread thread) { assert (VMOperation.isInProgressAtSafepoint()); - return javaBuffer.get(thread); + JfrBufferNode result = javaBufferNode.get(thread); +// return javaBuffer.get(thread); + return result.getValue(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public static JfrBuffer getNativeBuffer(IsolateThread thread) { assert (VMOperation.isInProgressAtSafepoint()); - return nativeBuffer.get(thread); + JfrBufferNode result = nativeBufferNode.get(thread); +// return nativeBuffer.get(thread); + return result.getValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -218,17 +339,28 @@ public static void notifyEventWriter(IsolateThread thread) { javaEventWriter.get(thread).notified = true; } } - + @Uninterruptible(reason = "Called from uninterruptible code.") + private static boolean someNodeLocked() { + if (javaBufferNode.get().isNull()) { + return nativeBufferNode.get().getAcquired() == 1; + } else if (nativeBufferNode.get().isNull()) { + return javaBufferNode.get().getAcquired() == 1; + } else { + return (javaBufferNode.get().getAcquired() + nativeBufferNode.get().getAcquired() > 0); // One of the locks must be held + } + } @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { - assert threadLocalBuffer.isNonNull(); - +// com.oracle.svm.core.util.VMError.guarantee(someNodeLocked(), "^^^14");//assert someNodeLocked(); // new + com.oracle.svm.core.util.VMError.guarantee(threadLocalBuffer.isNonNull(), "^^^15");//assert threadLocalBuffer.isNonNull(); + while(!JfrBufferAccess.acquire(threadLocalBuffer)); // new UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); if (!globalMemory.write(threadLocalBuffer, unflushedSize)) { JfrBufferAccess.reinitialize(threadLocalBuffer); writeDataLoss(threadLocalBuffer, unflushedSize); + JfrBufferAccess.release(threadLocalBuffer);// new return WordFactory.nullPointer(); } } @@ -241,8 +373,10 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit JfrBufferAccess.reinitialize(threadLocalBuffer); assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { + JfrBufferAccess.release(threadLocalBuffer);// new return threadLocalBuffer; } + JfrBufferAccess.release(threadLocalBuffer);// new return WordFactory.nullPointer(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 2bf87991e954..850de14ead6b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -127,7 +127,7 @@ public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) } private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class clazz, boolean flush) { - System.out.println("*** --- Writing Class:"+clazz.getName()); +// System.out.println("*** --- Writing Class:"+clazz.getName()); JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); // key writer.writeCompressedLong(typeInfo.getClassLoaderId(clazz.getClassLoader())); 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 7794ad3b2382..d9ec5658af04 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 @@ -424,14 +424,15 @@ public void setThreadBufferSize(long size) { /** See {@link JVM#flush}. */ @Uninterruptible(reason = "Accesses a JFR buffer.") - public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommittedSize, int requestedSize) { + public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommittedSize, int requestedSize) { // *** seems like its for the java buffers assert writer != null; assert uncommittedSize >= 0; JfrBuffer oldBuffer = threadLocal.getJavaBuffer(); assert oldBuffer.isNonNull(); - +// threadLocal.lockJava(); JfrBuffer newBuffer = JfrThreadLocal.flush(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); +// threadLocal.unlockJava(); if (newBuffer.isNull()) { // The flush failed for some reason, so mark the EventWriter as invalid for this write // attempt. @@ -454,7 +455,6 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted return false; } -// @Uninterruptible(reason = "Accesses a JFR buffer.") public void flush() { JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); //does this make it a safepoint? [NO] try { @@ -462,7 +462,7 @@ public void flush() { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { chunkWriter.flush(metadataDescriptor, repositories, threadRepo); - System.out.println("*** Flushed"); + System.out.println("*** Done Flush"); } } } finally { 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/ThreadSleepEvent.java index 2c779b5db58a..00587874e859 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/ThreadSleepEvent.java @@ -48,7 +48,7 @@ public static void emit(long time, long startTicks) { private static void emit0(long time, long startTicks) { if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadSleep)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); // *** would need to lock here JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadSleep); JfrNativeEventWriter.putLong(data, startTicks); @@ -56,7 +56,7 @@ private static void emit0(long time, long startTicks) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadSleep, 0)); JfrNativeEventWriter.putLong(data, time); - JfrNativeEventWriter.endSmallEvent(data); + JfrNativeEventWriter.endSmallEvent(data); // // *** can unlock here } } } From 324caa068cb1677d7c095edd6acfba6098a745ca Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 31 Oct 2022 10:29:14 -0400 Subject: [PATCH 05/72] working on hang up --- .../src/com/oracle/svm/core/jfr/JfrChunkWriter.java | 10 +++++++--- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 13 +++++++++++-- .../com/oracle/svm/core/jfr/JfrTypeRepository.java | 1 - 3 files changed, 18 insertions(+), 6 deletions(-) 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 75cfd5e237fb..fc879fde95f3 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 @@ -192,7 +192,6 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, SignedWord constantPoolPosition = writeCheckpointEvent(repositories, false); SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); // _constantPoolPosition = constantPoolPosition; - _metadataPosition = metadataPosition; patchFileHeader(constantPoolPosition, metadataPosition); // write of header doesn't have to be uninterruptible because closefile() already has the lock. It can get interrupted by safepoint but it'll just resume later. getFileSupport().close(fd); @@ -211,9 +210,10 @@ public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, Jfr if (threadRepo.isDirty(true)){ writeThreadCheckpointEvent(threadRepo, true); } + assert lock.isHeldByCurrentThread(); SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); // WILL get written again when the chunk closes and overwrite what we write here. In that case we shouldn't wipe the repos right? How does hotspot handle it? SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); - + assert lock.isHeldByCurrentThread(); // patchFileHeader(_constantPoolPosition, metadataPosition, true); patchFileHeader(constantPoolPosition, metadataPosition, true); @@ -249,6 +249,8 @@ public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadata } private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition, boolean flushpoint) { + assert lock.isHeldByCurrentThread(); + System.out.println("*** patchFileHeader"); SignedWord currentPos = getFileSupport().position(fd); long chunkSize = getFileSupport().position(fd).rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; @@ -307,6 +309,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea return start; } private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { + assert lock.isHeldByCurrentThread(); // Exception e = new Exception(); // e.printStackTrace(); System.out.println("*** Checkpoint"); @@ -357,6 +360,7 @@ private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { } private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { + assert lock.isHeldByCurrentThread(); // *** works to prevent duplicate metadata from being written to disk if (currentMetadataId != lastMetadataId || newChunk) { lastMetadataId = currentMetadataId; @@ -364,6 +368,7 @@ private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { } else { return _metadataPosition; } + System.out.println("*** Metadata event"); SignedWord start = beginEvent(); writeCompressedLong(METADATA_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); @@ -697,7 +702,6 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool // Try to get BUFFER if not in safepoint if (!safepoint && !JfrBufferAccess.acquire(buffer)) { //make one attempt - VMError.guarantee(!safepoint, "^^^1"); //if safepoint, no one else should hold the lock on the LL node. TODO: causes error when checked at safepoint (common) prev = node; node = next; continue; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 60a44c4b3c8c..bd56b00368f2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -353,12 +353,20 @@ private static boolean someNodeLocked() { public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { // com.oracle.svm.core.util.VMError.guarantee(someNodeLocked(), "^^^14");//assert someNodeLocked(); // new com.oracle.svm.core.util.VMError.guarantee(threadLocalBuffer.isNonNull(), "^^^15");//assert threadLocalBuffer.isNonNull(); - while(!JfrBufferAccess.acquire(threadLocalBuffer)); // new + com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^70");//assert !acquire(); + + int count =0; + while(!JfrBufferAccess.acquire(threadLocalBuffer)); {// new + count++; + VMError.guarantee(count < 20000, "^^^60"); + } + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); if (!globalMemory.write(threadLocalBuffer, unflushedSize)) { JfrBufferAccess.reinitialize(threadLocalBuffer); + VMError.guarantee(false, "^^^71"); writeDataLoss(threadLocalBuffer, unflushedSize); JfrBufferAccess.release(threadLocalBuffer);// new return WordFactory.nullPointer(); @@ -372,11 +380,12 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit } JfrBufferAccess.reinitialize(threadLocalBuffer); assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); - if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { + if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { // *** do we have enough space now? JfrBufferAccess.release(threadLocalBuffer);// new return threadLocalBuffer; } JfrBufferAccess.release(threadLocalBuffer);// new + VMError.guarantee(false, "^^^72"); return WordFactory.nullPointer(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 850de14ead6b..99b1cff83321 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -57,7 +57,6 @@ public long getClassId(Class clazz) { public int write(JfrChunkWriter writer, boolean flush) { // Visit all used classes, and collect their packages, modules, classloaders and possibly // referenced classes. - TypeInfo typeInfo = collectTypeInfo(flush); // The order of writing matters as following types can be tagged during the write process From 668abb747eb09ba88b56fb67f169968a8ec19394 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 1 Nov 2022 14:37:27 -0400 Subject: [PATCH 06/72] wip. Fixed problem with parser reading stale files. MarkChunkFinal. LL teardown() --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 20 +-- .../oracle/svm/core/jfr/JfrChunkWriter.java | 123 ++++-------------- .../svm/core/jfr/JfrMethodRepository.java | 30 ++++- .../svm/core/jfr/JfrStackTraceRepository.java | 21 ++- .../oracle/svm/core/jfr/JfrThreadLocal.java | 54 +++++--- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 15 +++ .../core/jfr/Target_jdk_jfr_internal_JVM.java | 4 +- .../core/sampler/SamplerBuffersAccess.java | 4 + 8 files changed, 126 insertions(+), 145 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 9e91e6a46d5d..0ccbd5d92d7e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -9,7 +9,7 @@ public class JfrBufferNodeLinkedList { private JfrThreadLocal.JfrBufferNode head; - JfrThreadLocal.JfrBufferNode lock; // TODO: remember to clean this up + JfrThreadLocal.JfrBufferNode lock; @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) public boolean isAcquired() { return isAcquired(lock); @@ -46,9 +46,11 @@ private static org.graalvm.word.UnsignedWord getHeaderSize() { public JfrBufferNodeLinkedList(){ head = WordFactory.nullPointer(); lock = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); -// lock = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(getHeaderSize()); } + public void teardown(){ + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(lock); + } @Uninterruptible(reason = "Called from uninterruptible code.") public void removeNode(JfrThreadLocal.JfrBufferNode prev, JfrThreadLocal.JfrBufferNode node){ @@ -72,14 +74,14 @@ public void addNode(JfrThreadLocal.JfrBufferNode node){ int count =0; while(!acquire()) { // *** need infinite tries. count++; - com.oracle.svm.core.util.VMError.guarantee(count < 1000, "^^^23"); + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^23"); + } + if (head.isNull()){ + node.setNext(WordFactory.nullPointer()); + head = node; + release(); + return; } - if (head.isNull()){ - node.setNext(WordFactory.nullPointer()); - head = node; - release(); - return; - } node.setNext(head); head = node; release(); 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 4b06f0c413d7..27fd16c8458f 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 @@ -43,11 +43,8 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMOperationControl; -<<<<<<< HEAD import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; -======= import com.oracle.svm.core.thread.VMThreads; ->>>>>>> ab06bc56240c7574f95ee67bc02e3c44f8b6a6e2 import com.oracle.svm.core.jfr.JfrThreadLocal.JfrBufferNode; import com.oracle.svm.core.util.VMError; @@ -90,6 +87,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private int currentMetadataId = 0; private boolean staticConstantsSerialized = false; private boolean newChunk = true; + private boolean isFinal = false; public void setCurrentMetadataId(){ currentMetadataId++; @@ -136,8 +134,14 @@ public void maybeOpenFile() { } } + public void markChunkFinal(){ + assert lock.isHeldByCurrentThread(); + isFinal = true; + } + public boolean openFile(String outputFile) { assert lock.isHeldByCurrentThread(); + isFinal = false; generation = 1; newChunk = true; System.out.println("*** ChunkWriter openfile"); @@ -237,13 +241,17 @@ private void writeFileHeader() { getFileSupport().writeLong(fd, 0L); // chunk size getFileSupport().writeLong(fd, 0L); // last checkpoint offset getFileSupport().writeLong(fd, 0L); // metadata position - getFileSupport().writeLong(fd, 0L); // startNanos + getFileSupport().writeLong(fd, chunkStartNanos); // startNanos getFileSupport().writeLong(fd, 0L); // durationNanos getFileSupport().writeLong(fd, chunkStartTicks); // *** only changed after a chunk rotation is complete (after header is patched) getFileSupport().writeLong(fd, JfrTicks.getTicksFrequency()); getFileSupport().writeByte(fd, nextGeneration()); // in hotspot a 1 byte generation is written getFileSupport().writeByte(fd, (byte) 0 ); // in hotspot 1 byte PAD padding - getFileSupport().writeShort(fd, compressedInts ? (short) 1 : 0 ); // seems like only 2 bytes of the flags are written after the 1 byte generation + short flags = 0; + flags += compressedInts ? 1 : 0; + flags += isFinal ? 1*2 : 0; + + getFileSupport().writeShort(fd, flags); // seems like only 2 bytes of the flags are written after the 1 byte generation // getFileSupport().writeInt(fd, compressedInts ? 1 : 0); // getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); @@ -266,7 +274,7 @@ private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadat getFileSupport().writeLong(fd, metadataPosition.rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); - // *** i guess they didn't write anything else because nothing else changes + // *** I guess they didn't write anything else because nothing else changes getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); if (flushpoint) { //chunk is not finished @@ -274,6 +282,13 @@ private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadat } else { getFileSupport().writeByte(fd, COMPLETE); } + getFileSupport().writeByte(fd, (byte) 0 ); // in hotspot 1 byte PAD padding + short flags = 0; + flags += compressedInts ? 1 : 0; + flags += isFinal ? 1*2 : 0; + + getFileSupport().writeShort(fd, flags); // seems like only 2 bytes of the flags are written after the 1 byte generation + //need to move pointer back to correct position for next write getFileSupport().seek(fd,currentPos); } @@ -316,8 +331,6 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea } private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { assert lock.isHeldByCurrentThread(); -// Exception e = new Exception(); -// e.printStackTrace(); System.out.println("*** Checkpoint"); SignedWord start = beginEvent(); @@ -540,17 +553,6 @@ private void changeEpoch() { // 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 -// for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { -// JfrBuffer buffer = JfrThreadLocal.getJavaBuffer(thread); -// if (buffer.isNonNull()) { -// write(buffer); -// JfrThreadLocal.notifyEventWriter(thread); -// } -// buffer = JfrThreadLocal.getNativeBuffer(thread); -// if (buffer.isNonNull()) { -// write(buffer); -// } -// } JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); @@ -590,89 +592,12 @@ private void flushStorage() { if(!JfrBufferAccess.acquire(buffer)){ // one attempt continue; } -// assert !JfrBufferAccess.isAcquired(buffer); // *** need to deal with this too write(buffer); JfrBufferAccess.reinitialize(buffer); JfrBufferAccess.release(buffer); } } -// @Uninterruptible(reason = "Called from uninterruptible code.") -// private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { -// // Try to lock list -// if (!safepoint) { -// for (int retry = 0; retry < 100; retry++) { -// if (linkedList.acquire()) { -// break; -// } -// } -// if (!linkedList.isAcquired()) { -// VMError.guarantee(!safepoint, "^^^4");//assert !safepoint; // if safepoint, no one else should hold the lock on the LL. -// return; // wasn't able to get the lock -// } -// } -// -// JfrBufferNode node = linkedList.getHead(); -// JfrBufferNode prev = WordFactory.nullPointer(); -// int count = 0; -// -// while (node.isNonNull()) { -// count++; -// VMError.guarantee(count < 1000, "^^^26"); -// -// // An optimization -// if (linkedList.isAcquired()) { // evaluate this first -// if (node != linkedList.getHead()) { -// // only need lock when dealing with head. Because other threads add nodes in direction opposite to traversal. -// VMError.guarantee(prev.isNonNull(), "^^^2");//assert prev.isNonNull(); -// linkedList.release(); -// } -// } -// -// JfrBufferNode next = node.getNext(); -// -// // Try to get node if not in safepoint -// if (!safepoint && !JfrBufferNodeLinkedList.acquire(node)) { //make one attempt -// VMError.guarantee(!safepoint, "^^^1"); //if safepoint, no one else should hold the lock on the LL node. TODO: causes error when checked at safepoint (common) -// prev = node; -// node = next; -// continue; -// } -// -// // Try to write to disk. (If thread doesn't flush at death, this is always safe to do because we remove nodes afterward) -// JfrBuffer buffer = node.getValue(); -// VMError.guarantee(buffer.isNonNull(), "^^^3");//assert buffer.isNonNull(); -// if (!safepoint && JfrBufferAccess.acquire(buffer)) { -// VMError.guarantee(JfrBufferAccess.isAcquired(buffer), "^^^5"); // TODO: causes error when checked at safepoint -// write(buffer); -// JfrBufferAccess.release(buffer); -// }else { -// write(buffer); -// } -// if (java) { -// VMError.guarantee(node.getThread().isNonNull(), "^^^20"); -// JfrThreadLocal.notifyEventWriter(node.getThread()); -// } -// -// if (!safepoint) { -// JfrBufferNodeLinkedList.release(node); -// } -// -// // Try to remove if needed (If thread doesn't flush at death, this must be after flushing to disk block) -// if (!node.getAlive()){ -// linkedList.removeNode(prev, node); -// // don't update previous here! -// node = next; -// continue; -// } -// -// prev = node; // prev is always the last node still in the list before the current node. Prev may not be alive. -// node = next; -// } -// -// if (linkedList.isAcquired()) { -// linkedList.release(); -// } -// } + @Uninterruptible(reason = "Called from uninterruptible code.") private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { // Try to lock list @@ -690,11 +615,9 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool JfrBufferNode node = linkedList.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); - int count = 0; while (node.isNonNull()) { - count++; - VMError.guarantee(count < 1000, "^^^26"); + // An optimization if (linkedList.isAcquired()) { // evaluate this first diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 50424ef4508a..957325027aad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -45,7 +45,15 @@ public class JfrMethodRepository implements JfrConstantPool { private final VMMutex mutex; private final JfrMethodEpochData epochData0; private final JfrMethodEpochData epochData1; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void acquireLock() { + mutex.lockNoTransition(); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void releaseLock() { + mutex.unlock(); + } @Platforms(Platform.HOSTED_ONLY.class) public JfrMethodRepository() { this.epochData0 = new JfrMethodEpochData(); @@ -112,22 +120,32 @@ private static long getMethodId0(FrameInfoQueryResult stackTraceElement, JfrMeth } @Override - public int write(JfrChunkWriter writer) { - JfrMethodEpochData epochData = getEpochData(true); - int count = writeMethods(writer, epochData); - epochData.clear(); + public int write(JfrChunkWriter writer, boolean flush) { + if (flush) { + acquireLock(); + } + JfrMethodEpochData epochData = getEpochData(!flush); + System.out.println("writing methods"); + int count = writeMethods(writer, epochData, flush); + if (!flush) { + epochData.clear(); + }else{ + releaseLock(); + } return count; } - private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData) { + private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData, boolean flush) { int numberOfMethods = epochData.visitedMethods.getSize(); if (numberOfMethods == 0) { + System.out.println(" NO methods to write"); return EMPTY; } + System.out.println(" YES methods to write"); writer.writeCompressedLong(JfrType.Method.getId()); writer.writeCompressedInt(numberOfMethods); - writer.write(epochData.methodBuffer); + writer.write(epochData.methodBuffer, !flush); return NON_EMPTY; } 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..16284e0db1b1 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 @@ -224,21 +224,30 @@ private void serializeStackTraceElement0(long methodId, int sourceLineNumber, in } @Override - public int write(JfrChunkWriter writer) { - JfrStackTraceEpochData epochData = getEpochData(true); - int count = writeStackTraces(writer, epochData); - epochData.clear(); + public int write(JfrChunkWriter writer, boolean flush) { + if (flush) { + acquireLock(); + } + JfrStackTraceEpochData epochData = getEpochData(!flush); + int count = writeStackTraces(writer, epochData, flush); + if (!flush) { + epochData.clear(); + } else { + releaseLock(); + } return count; } - private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData) { + private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData, boolean flush) { if (epochData.numberOfSerializedStackTraces == 0) { +// System.out.println("no stacktraces to write"); return EMPTY; } +// System.out.println(" YES stacktraces to write"); writer.writeCompressedLong(JfrType.StackTrace.getId()); writer.writeCompressedInt(epochData.numberOfSerializedStackTraces); - writer.write(epochData.stackTraceBuffer); + writer.write(epochData.stackTraceBuffer, !flush); return NON_EMPTY; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 978eee036495..7309b6641e8f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -53,6 +53,8 @@ import org.graalvm.nativeimage.c.struct.RawStructure; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; +import static com.oracle.svm.core.thread.PlatformThreads.getIsolateThread; + /** * This class holds various JFR-specific thread local values. * @@ -108,6 +110,7 @@ static int offsetOfAcquired() { private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId"); private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId"); + private static final com.oracle.svm.core.threadlocal.FastThreadLocalInt isExcluded = FastThreadLocalFactory.createInt("JfrThreadLocal.enabled"); private long threadLocalBufferSize; private static JfrBufferNodeLinkedList javaBufferList; @@ -155,6 +158,9 @@ public void initialize(long bufferSize) { // *** at runtime? nativeBufferList = new JfrBufferNodeLinkedList(); } + public void exclude(Thread javaThread) { + getIsolateThread(javaThread); + } @Uninterruptible(reason = "Accesses a JFR buffer.") @Override public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { @@ -163,6 +169,7 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { // Java object. Target_java_lang_Thread t = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class); threadId.set(isolateThread, t.getId()); + isExcluded.set(isolateThread,0); parentThreadId.set(isolateThread, JavaThreads.getParentThreadId(javaThread)); SubstrateJVM.getThreadRepo().registerThread(javaThread); @@ -293,11 +300,9 @@ private static JfrBufferNode allocate(com.oracle.svm.core.jfr.JfrBuffer buffer) @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { VMError.guarantee(threadId.get() > 0, "Thread local JFR data must be initialized"); -// JfrBuffer result = javaBuffer.get(); JfrBufferNode result = javaBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); -// javaBuffer.set(result); result = allocate(buffer); result.setThread(CurrentIsolate.getCurrentThread()); javaBufferNode.set(result); @@ -309,11 +314,9 @@ public JfrBuffer getJavaBuffer() { @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public JfrBuffer getNativeBuffer() { VMError.guarantee(threadId.get() > 0, "Thread local JFR data must be initialized"); -// JfrBuffer result = nativeBuffer.get(); JfrBufferNode result = nativeBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); -// nativeBuffer.set(result); result = allocate(buffer); nativeBufferNode.set(result); nativeBufferList.addNode(result); @@ -325,7 +328,6 @@ public JfrBuffer getNativeBuffer() { public static JfrBuffer getJavaBuffer(IsolateThread thread) { assert (VMOperation.isInProgressAtSafepoint()); JfrBufferNode result = javaBufferNode.get(thread); -// return javaBuffer.get(thread); return result.getValue(); } @@ -333,7 +335,6 @@ public static JfrBuffer getJavaBuffer(IsolateThread thread) { public static JfrBuffer getNativeBuffer(IsolateThread thread) { assert (VMOperation.isInProgressAtSafepoint()); JfrBufferNode result = nativeBufferNode.get(thread); -// return nativeBuffer.get(thread); return result.getValue(); } @@ -343,34 +344,35 @@ public static void notifyEventWriter(IsolateThread thread) { javaEventWriter.get(thread).notified = true; } } - @Uninterruptible(reason = "Called from uninterruptible code.") - private static boolean someNodeLocked() { - if (javaBufferNode.get().isNull()) { - return nativeBufferNode.get().getAcquired() == 1; - } else if (nativeBufferNode.get().isNull()) { - return javaBufferNode.get().getAcquired() == 1; - } else { - return (javaBufferNode.get().getAcquired() + nativeBufferNode.get().getAcquired() > 0); // One of the locks must be held + @Uninterruptible(reason = "Epoch must not change while in this method.") + private static boolean acquireBufferWithRetry(JfrBuffer buffer) { + for (int retry = 0; retry < 100; retry++) { + if (JfrBufferAccess.acquire(buffer)) { + return true; + } } + return false; } @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { -// com.oracle.svm.core.util.VMError.guarantee(someNodeLocked(), "^^^14");//assert someNodeLocked(); // new com.oracle.svm.core.util.VMError.guarantee(threadLocalBuffer.isNonNull(), "^^^15");//assert threadLocalBuffer.isNonNull(); com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^70");//assert !acquire(); - int count =0; - while(!JfrBufferAccess.acquire(threadLocalBuffer)); {// new - count++; - VMError.guarantee(count < 20000, "^^^60"); + if (!acquireBufferWithRetry(threadLocalBuffer)) { + com.oracle.svm.core.util.VMError.guarantee(false , "^^^80 unable to promote. Lost event data");//assert !acquire(); + return WordFactory.nullPointer(); } +// int count =0; +// while(!JfrBufferAccess.acquire(threadLocalBuffer)); {// new +// count++; +// VMError.guarantee(count < 20000, "^^^60"); +// } UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); if (!globalMemory.write(threadLocalBuffer, unflushedSize)) { JfrBufferAccess.reinitialize(threadLocalBuffer); - VMError.guarantee(false, "^^^71"); writeDataLoss(threadLocalBuffer, unflushedSize); JfrBufferAccess.release(threadLocalBuffer);// new return WordFactory.nullPointer(); @@ -389,7 +391,6 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit return threadLocalBuffer; } JfrBufferAccess.release(threadLocalBuffer);// new - VMError.guarantee(false, "^^^72"); return WordFactory.nullPointer(); } @@ -421,4 +422,15 @@ private static UnsignedWord increaseDataLost(UnsignedWord delta) { private static void freeBuffer(JfrBuffer buffer) { JfrBufferAccess.free(buffer); } + + public void teardown() { + JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); + if (nativeBuffers != null) { + nativeBuffers.teardown(); + } + JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); + if (javaBuffers != null) { + javaBuffers.teardown(); + } + } } 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 2df65ba64bed..fec194e6421c 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 @@ -242,6 +242,7 @@ public boolean destroyJFR() { threadRepo.teardown(); stackTraceRepo.teardown(); methodRepo.teardown(); + threadLocal.teardown(); initialized = false; return true; @@ -476,6 +477,20 @@ public void flush() { } } + public void markChunkFinal() { + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); //does this make it a safepoint? [NO] + try { + if (recording) { + boolean existingFile = chunkWriter.hasOpenFile(); + if (existingFile) { + chunkWriter.markChunkFinal(); + } + } + } finally { + chunkWriter.unlock(); + } + } + /** See {@link JVM#setRepositoryLocation}. */ public void setRepositoryLocation(@SuppressWarnings("unused") String dirText) { // Would only be used in case of an emergency dump, which is not supported at the moment. 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 db76d0fd2787..6b6fda6230d7 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 @@ -408,7 +408,6 @@ public boolean shouldRotateDisk() { public void flush() { System.out.println("*** Flush called"); SubstrateJVM.get().flush(); - // Temporarily do nothing. This is used for JFR streaming. } @Substitute @@ -470,7 +469,6 @@ public boolean isContainerized() { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void markChunkFinal() { - System.out.println("*** markChunkFinal called"); - // Temporarily do nothing. This is used for JFR streaming. + SubstrateJVM.get().markChunkFinal(); } } 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..f505788d3d00 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 @@ -75,6 +75,7 @@ public static void processSamplerBuffers() { SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); if (buffer.isNull()) { /* No buffers to process. */ +// com.oracle.svm.core.util.VMError.guarantee(false, "no buffers to process -----"); SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); return; } @@ -123,9 +124,11 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState); /* Sample is already there, skip the rest of sample plus END_MARK symbol. */ current = current.add(sampleSize).add(SamplerSampleWriter.END_MARKER_SIZE); +// com.oracle.svm.core.util.VMError.guarantee(false, "if"); } else { assert JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, JfrStackTraceRepository.JfrStackTraceTableEntryStatus.SHOULD_SERIALIZE); /* Sample is not there. Start walking a stacktrace. */ +// com.oracle.svm.core.util.VMError.guarantee(false, "else"); stackTraceRepo.serializeStackTraceHeader(stackTraceId, isTruncated, sampleSize / SamplerSampleWriter.IP_SIZE); while (current.belowThan(end)) { long ip = current.readLong(0); @@ -139,6 +142,7 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { } } } + } finally { stackTraceRepo.releaseLock(); } From 35423758040c60d1a906ef67ef1d2cae384fefa1 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 3 Nov 2022 15:39:02 -0400 Subject: [PATCH 07/72] fix deadlock issue. Change acquire and release ints --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 232 +++++++++++++----- .../oracle/svm/core/jfr/JfrChunkWriter.java | 127 +++++----- .../oracle/svm/core/jfr/JfrThreadLocal.java | 149 +++++------ 3 files changed, 303 insertions(+), 205 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 0ccbd5d92d7e..e1101981b793 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -6,99 +6,221 @@ import org.graalvm.compiler.nodes.NamedLocationIdentity; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; - +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.RawFieldOffset; public class JfrBufferNodeLinkedList { - private JfrThreadLocal.JfrBufferNode head; - JfrThreadLocal.JfrBufferNode lock; - @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) - public boolean isAcquired() { - return isAcquired(lock); + @RawStructure + public interface JfrBufferNode extends com.oracle.svm.core.jdk.UninterruptibleEntry { + @RawField + JfrBuffer getValue(); + @RawField + void setValue(JfrBuffer value); + + @RawField + org.graalvm.nativeimage.IsolateThread getThread(); + @RawField + void setThread(org.graalvm.nativeimage.IsolateThread thread); + + @RawField + boolean getAlive(); + @RawField + void setAlive(boolean alive); + @RawField + int getAcquired(); + + @RawField + void setAcquired(int value); + + @RawFieldOffset + static int offsetOfAcquired() { + throw com.oracle.svm.core.util.VMError.unimplemented(); // replaced + } + @RawField + T getPrev(); + + @RawField + void setPrev(com.oracle.svm.core.jdk.UninterruptibleEntry value); } - @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) - public boolean acquire() { -// return lock.logicCompareAndSwapWord(0, WordFactory.nullPointer(), WordFactory.pointer(1), NamedLocationIdentity.OFF_HEAP_LOCATION); - return acquire(lock); + private volatile JfrBufferNode head; + private JfrBufferNode tail; // this never gets deleted + + @Uninterruptible(reason = "Called from uninterruptible code.") + public JfrBufferNode getAndLockTail() { + //TODO: we could limit retries and skip flush if needed + com.oracle.svm.core.util.VMError.guarantee(tail.isNonNull(), "^^116"); + int count =0 ; + while(!acquire(tail)) { + count++; +// if (count >1000){return WordFactory.nullPointer();} + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^93"); + } + return tail; } - @Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true) - public void release() { -// com.oracle.svm.core.util.VMError.guarantee(!acquire(), "^^^13");//assert !acquire(); - com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1, "^^^26"); - release(lock); -// boolean result = lock.logicCompareAndSwapWord(0, WordFactory.pointer(1), WordFactory.nullPointer(), NamedLocationIdentity.OFF_HEAP_LOCATION); + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean isTail(JfrBufferNode node) { + return node == tail; } - @Uninterruptible(reason = "Called from uninterruptible code.") - public JfrThreadLocal.JfrBufferNode getHead() { - com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1|| com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^12");//assert !acquire(); - return head; + public boolean isHead(JfrBufferNode node) { + return node == head; } - @Uninterruptible(reason = "Called from uninterruptible code.") - private void setHead(JfrThreadLocal.JfrBufferNode node) { - com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1 || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^11");//assert !acquire(); + private void setHead(JfrBufferNode node) { + com.oracle.svm.core.util.VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^11");//assert !acquire(); head = node; } - + @Uninterruptible(reason = "Called from uninterruptible code.") private static org.graalvm.word.UnsignedWord getHeaderSize() { // return com.oracle.svm.core.util.UnsignedUtils.roundUp(WordFactory.unsigned(1), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); - return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrThreadLocal.JfrBufferNode.class), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public static JfrBufferNode createNode(com.oracle.svm.core.jfr.JfrBuffer buffer, org.graalvm.nativeimage.IsolateThread thread){ + JfrBufferNode node = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); + node.setAlive(true); + node.setValue(buffer); + node.setThread(thread); + node.setPrev(WordFactory.nullPointer()); + node.setNext(WordFactory.nullPointer()); + node.setAcquired(2); + return node; } public JfrBufferNodeLinkedList(){ - head = WordFactory.nullPointer(); - lock = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); + tail = createNode(WordFactory.nullPointer(), WordFactory.nullPointer()); + head = tail; } public void teardown(){ - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(lock); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(tail); + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean lockSection(JfrBufferNode target) { + com.oracle.svm.core.util.VMError.guarantee(target.isNonNull(), "^^^84"); + // acquire target and adjacent nodes + if(acquire(target)){ + if (target.getPrev().isNull() || acquire(target.getPrev())) { + if (target.getNext().isNull() || acquire(target.getNext())) { + return true; + } + // couldn't acquire all three locks. So release all of them. + if (target.getPrev().isNonNull()) { + release(target.getPrev()); + } + } + release(target); + } + return false; + } + + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean lockAdjacent(JfrBufferNode target) { + com.oracle.svm.core.util.VMError.guarantee(target.isNonNull(), "^^^85"); + // acquire target and adjacent nodes + + if (target.getPrev().isNull() || acquire(target.getPrev())) { + if (target.getNext().isNull() || acquire(target.getNext())) { + return true; + } + // couldn't acquire all three locks. So release all of them. + if (target.getPrev().isNonNull()) { + release(target.getPrev()); + } + } + + return false; + } + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean unlockSection(JfrBufferNode target) { + com.oracle.svm.core.util.VMError.guarantee(target.isNonNull(), "^^^84"); + if(target.getNext().isNonNull()) { + release(target.getNext()); + } + if (target.getPrev().isNonNull()) { + release(target.getPrev()); + } + + release(target); + + return false; } + @Uninterruptible(reason = "Called from uninterruptible code.") - public void removeNode(JfrThreadLocal.JfrBufferNode prev, JfrThreadLocal.JfrBufferNode node){ - - JfrThreadLocal.JfrBufferNode next = node.getNext(); // next could be null if node is tail - com.oracle.svm.core.util.VMError.guarantee(head.isNonNull(), "^^^8");//assert head.isNonNull(); - if (node == head) { - com.oracle.svm.core.util.VMError.guarantee(lock.getAcquired()==1 || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^22");//assert !acquire(); - assert prev.isNull(); - setHead(next); // head could now be null if there was only one node in the list + public boolean removeNode(JfrBufferNode node, boolean flushing){ + JfrBufferNode next = node.getNext(); // next can never be null + JfrBufferNode prev = node.getPrev(); + com.oracle.svm.core.util.VMError.guarantee(next.isNonNull(), "^^^89"); //tail must always exist until torn down + com.oracle.svm.core.util.VMError.guarantee(head.isNonNull(), "^^^8"); + + // make one attempt to get all the locks. If flushing, only target nodes already acquired. + if (flushing && !com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() && !lockAdjacent(node)) { + return false; + } + com.oracle.svm.core.util.VMError.guarantee((isAcquired(node) && isAcquired(next)) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^22");//assert !acquire(); + if (isHead(node)) { + com.oracle.svm.core.util.VMError.guarantee(prev.isNull(), "^^^96"); + setHead(next); // head could now be tail if there was only one node in the list + com.oracle.svm.core.util.VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^97"); + head.setPrev(WordFactory.nullPointer()); } else { - prev.setNext(next); // prev could now be "tail" if current was tail + com.oracle.svm.core.util.VMError.guarantee( isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^90");//assert !acquire(); + prev.setNext(next); + next.setPrev(prev); } + com.oracle.svm.core.util.VMError.guarantee(prev != next, "^^^92"); // Free LL node holding buffer - com.oracle.svm.core.util.VMError.guarantee(node.getValue().isNonNull(), "^^^9");//assert node.getValue().isNonNull(); - JfrBufferAccess.free(node.getValue()); + com.oracle.svm.core.util.VMError.guarantee(node.getValue().isNonNull(), "^^^9");//TODO: uncomment + JfrBufferAccess.free(node.getValue()); //TODO: uncomment + release(node); //is this necessary? ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); + + // release existing locks + release(next); + if (prev.isNonNull()) { + release(prev); + } + return true; } @Uninterruptible(reason = "Called from uninterruptible code.") - public void addNode(JfrThreadLocal.JfrBufferNode node){ + public void addNode(JfrBufferNode node){ +// com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^127"); //TODO : uncomment int count =0; - while(!acquire()) { // *** need infinite tries. + while(!acquire(head)) { // *** need infinite tries. count++; com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^23"); } - if (head.isNull()){ - node.setNext(WordFactory.nullPointer()); - head = node; - release(); - return; - } + JfrBufferNode oldHead = head; + node.setPrev(WordFactory.nullPointer()); + + com.oracle.svm.core.util.VMError.guarantee(head.getPrev().isNull(), "^^^83"); node.setNext(head); + head.setPrev(node); head = node; - release(); + com.oracle.svm.core.util.VMError.guarantee(oldHead == head.getNext(), "^^^114"); + release(head.getNext()); } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static boolean acquire(JfrThreadLocal.JfrBufferNode buffer) { - return ((org.graalvm.word.Pointer) buffer).logicCompareAndSwapInt(JfrThreadLocal.JfrBufferNode.offsetOfAcquired(), 0, 1, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); + public static boolean acquire(JfrBufferNode node) { + if (com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint()) { + com.oracle.svm.core.util.VMError.guarantee(!isAcquired(node), "^^^100"); + return true; + } + return ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), 2, 3, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static void release(JfrThreadLocal.JfrBufferNode buffer) { - com.oracle.svm.core.util.VMError.guarantee(buffer.getAcquired() == 1, "^^^10");//assert buffer.getAcquired() == 1; - buffer.setAcquired(0); + public static void release(JfrBufferNode node) { + if (com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint()) { + com.oracle.svm.core.util.VMError.guarantee(!isAcquired(node), "^^^101"); + return; + } + com.oracle.svm.core.util.VMError.guarantee(isAcquired(node), "^^^10"); + node.setAcquired(2); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isAcquired(JfrThreadLocal.JfrBufferNode buffer) { - return buffer.getAcquired() == 1; + public static boolean isAcquired(JfrBufferNode node) { + return node.getAcquired() == 3; } } \ No newline at end of file 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 27fd16c8458f..8e401e553d51 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 @@ -46,9 +46,10 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.jfr.JfrThreadLocal.JfrBufferNode; import com.oracle.svm.core.util.VMError; +import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.*; + /** * This class is used when writing the in-memory JFR data to a file. For all operations, except * those listed in {@link JfrUnlockedChunkWriter}, it is necessary to acquire the {@link #lock} @@ -558,7 +559,6 @@ private void changeEpoch() { JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); traverseList(javaBuffers, true, true); - traverseList(nativeBuffers, false, true); JfrBuffers buffers = globalMemory.getBuffers(); @@ -579,9 +579,13 @@ private void changeEpoch() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage() { - JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); - JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); -// int count = javaBuffers.getSize(); // in case other threads are adding nodes + com.oracle.svm.core.jfr.JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); + com.oracle.svm.core.jfr.JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); + +// javaBuffers.addNode(javaBuffers.createNode()); +// javaBuffers.addNode(javaBuffers.createNode()); +// nativeBuffers.addNode(nativeBuffers.createNode()); +// nativeBuffers.addNode(nativeBuffers.createNode()); traverseList(javaBuffers, true, false); traverseList(nativeBuffers, false, false); @@ -599,73 +603,78 @@ private void flushStorage() { } @Uninterruptible(reason = "Called from uninterruptible code.") - private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { - // Try to lock list - if (!safepoint) { - for (int retry = 0; retry < 100; retry++) { - if (linkedList.acquire()) { - break; - } - } - if (!linkedList.isAcquired()) { - VMError.guarantee(!safepoint, "^^^4");//assert !safepoint; // if safepoint, no one else should hold the lock on the LL. - return; // wasn't able to get the lock - } - } + private void traverseList(com.oracle.svm.core.jfr.JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { + // Traverse back to front to minimize conflict with threads adding new nodes. Which could effectively block the traversal - JfrBufferNode node = linkedList.getHead(); - JfrBufferNode prev = WordFactory.nullPointer(); + JfrBufferNode node = linkedList.getAndLockTail(); + if (node.isNull()) { + return; + } while (node.isNonNull()) { + com.oracle.svm.core.util.VMError.guarantee(isAcquired(node) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^98"); + JfrBufferNode prev = node.getPrev(); - - // An optimization - if (linkedList.isAcquired()) { // evaluate this first - if (node != linkedList.getHead()) { - // only need lock when dealing with head. Because other threads add nodes in direction opposite to traversal. - VMError.guarantee(prev.isNonNull(), "^^^2");//assert prev.isNonNull(); - linkedList.release(); +// Try to remove + if (!node.getAlive()){ + int count = 0; + if (linkedList.removeNode(node, true)) { + // able to remove node + while (prev.isNonNull() && !acquire(prev)) { + count++; + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^110"); + } + } else { + // unable to remove node + while (prev.isNonNull() && !acquire(prev)) { + count++; + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^111"); + } + release(node); } - } - - JfrBufferNode next = node.getNext(); - JfrBuffer buffer = node.getValue(); - VMError.guarantee(buffer.isNonNull(), "^^^3");//assert buffer.isNonNull(); - - // Try to get BUFFER if not in safepoint - if (!safepoint && !JfrBufferAccess.acquire(buffer)) { //make one attempt - prev = node; - node = next; + node = prev; continue; } - VMError.guarantee(JfrBufferAccess.isAcquired(buffer) || safepoint, "^^^5"); - write(buffer); - // Try to write to disk. (If thread doesn't flush at death, this is always safe to do because we remove nodes afterward) - if (!safepoint) { - JfrBufferAccess.release(buffer); - } - - if (java) { - VMError.guarantee(node.getThread().isNonNull(), "^^^20"); - JfrThreadLocal.notifyEventWriter(node.getThread()); - } +// flush buffer to disk -------------------------------------- + if (!linkedList.isTail(node)) { + JfrBuffer buffer = node.getValue(); + VMError.guarantee(buffer.isNonNull(), "^^^3"); // assert buffer.isNonNull(); + // Try to get BUFFER if not in safepoint + if (!safepoint && !JfrBufferAccess.acquire(buffer)) { //make one attempt + release(node); + node = prev; + continue; + } + VMError.guarantee(JfrBufferAccess.isAcquired(buffer) || safepoint, "^^^5"); + if (safepoint) { + VMError.guarantee(!JfrBufferAccess.isAcquired(buffer), "^^^102"); + } + write(buffer); - // Try to remove if needed (If thread doesn't flush at death, this must be after flushing to disk block) - if (!node.getAlive()){ - linkedList.removeNode(prev, node); - // don't update previous here! - node = next; - continue; + if (!safepoint) { + JfrBufferAccess.release(buffer); + } + if (java) { + VMError.guarantee(node.getThread().isNonNull(), "^^^20"); + JfrThreadLocal.notifyEventWriter(node.getThread()); + } } +// done flush buffer to disk -------------------------------------- - prev = node; // prev is always the last node still in the list before the current node. Prev may not be alive. - node = next; - } + int count =0; + while (prev.isNonNull() && !acquire(prev)) { - if (linkedList.isAcquired()) { - linkedList.release(); + if (!isAcquired(prev)){ + count++;// increase count if we couldnt acquire, but noone else holds the lock. + } + com.oracle.svm.core.util.VMError.guarantee(prev !=node && prev!=node.getNext(), "^^^120"); + com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^94"); + }//acquire the next node. Hand-over-hand traversal. + com.oracle.svm.core.util.VMError.guarantee(prev.isNull() || isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^119"); + release(node); + node = prev; // new target is already locked } } public long getChunkStartNanos() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 7309b6641e8f..4570950a4694 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -49,9 +49,8 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.struct.RawStructure; -import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; + +import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; import static com.oracle.svm.core.thread.PlatformThreads.getIsolateThread; @@ -72,34 +71,6 @@ * modify the buffers of other threads). */ public class JfrThreadLocal implements ThreadListener { - @RawStructure - public interface JfrBufferNode extends com.oracle.svm.core.jdk.UninterruptibleEntry { - @RawField - JfrBuffer getValue(); - @RawField - void setValue(JfrBuffer value); - - @RawField - IsolateThread getThread(); - @RawField - void setThread(IsolateThread thread); - - @RawField - boolean getAlive(); - @RawField - void setAlive(boolean alive); - @RawField - int getAcquired(); - - @RawField - void setAcquired(int value); - - @org.graalvm.nativeimage.c.struct.RawFieldOffset - static int offsetOfAcquired() { - throw VMError.unimplemented(); // replaced - } - } - private static final FastThreadLocalObject javaEventWriter = FastThreadLocalFactory.createObject(Target_jdk_jfr_internal_EventWriter.class, "JfrThreadLocal.javaEventWriter"); // *** holds a pointer to the buffer that's on the heap @@ -123,30 +94,30 @@ public static JfrBufferNodeLinkedList getNativeBufferList(){ public static JfrBufferNodeLinkedList getJavaBufferList(){ return javaBufferList; } - @Uninterruptible(reason = "Called from uninterruptible code.") - public static void lockNative(){ - int count = 0; - while(!JfrBufferNodeLinkedList.acquire(nativeBufferNode.get())){ - count++; - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^24"); - } - } - @Uninterruptible(reason = "Called from uninterruptible code.") - public static void unlockNative(){ - JfrBufferNodeLinkedList.release(nativeBufferNode.get()); - } - @Uninterruptible(reason = "Called from uninterruptible code.") - public static void lockJava(){ - int count =0; - while(!JfrBufferNodeLinkedList.acquire(javaBufferNode.get())){ - count++; - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^25"); - } - } - @Uninterruptible(reason = "Called from uninterruptible code.") - public static void unlockJava(){ - JfrBufferNodeLinkedList.release(javaBufferNode.get()); - } +// @Uninterruptible(reason = "Called from uninterruptible code.") +// public static void lockNative(){ +// int count = 0; +// while(!JfrBufferNodeLinkedList.acquire(nativeBufferNode.get())){ +// count++; +// com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^24"); +// } +// } +// @Uninterruptible(reason = "Called from uninterruptible code.") +// public static void unlockNative(){ +// JfrBufferNodeLinkedList.release(nativeBufferNode.get()); +// } +// @Uninterruptible(reason = "Called from uninterruptible code.") +// public static void lockJava(){ +// int count =0; +// while(!JfrBufferNodeLinkedList.acquire(javaBufferNode.get())){ +// count++; +// com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^25"); +// } +// } +// @Uninterruptible(reason = "Called from uninterruptible code.") +// public static void unlockJava(){ +// JfrBufferNodeLinkedList.release(javaBufferNode.get()); +// } @Platforms(Platform.HOSTED_ONLY.class) public JfrThreadLocal() { @@ -184,50 +155,47 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { @Uninterruptible(reason = "Accesses a JFR buffer.") @Override public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { - /** - * *** Why do we need locks here? Avoid simultaneously flushing to disk TLB and promoting it (here). - * // *** no blocking on java buffer bc it is null! When guarantee nonNull, it blocks! doesn't seem like adding the isolateThread param makes a diff - * Still not sure why the JFR buffer spinlock was blocking - */ - // Emit ThreadEnd event after thread.run() finishes. ThreadEndEvent.emit(isolateThread); JfrBufferNode jbn = javaBufferNode.get(isolateThread); JfrBufferNode nbn = nativeBufferNode.get(isolateThread); if (jbn.isNonNull()) { - JfrBuffer jb = jbn.getValue(); - assert jb.isNonNull() && jbn.getAlive(); -// while(!JfrBufferNodeLinkedList.acquire(jbn)); - -// if (SubstrateJVM.isRecording()) { -// if (jb.isNonNull()) { -// flush(jb, WordFactory.unsigned(0), 0); -// } -// } - jbn.setAlive(false); // TODO: should this be atomic? -// JfrBufferAccess.free(jb); -// JfrBufferNodeLinkedList.release(jbn); - } + if (getJavaBufferList().lockSection(jbn)) { + JfrBuffer jb = jbn.getValue(); + assert jb.isNonNull() && jbn.getAlive(); + + if (SubstrateJVM.isRecording()) { + if (jb.isNonNull()) { + flush(jb, WordFactory.unsigned(0), 0); + } + } +// getJavaBufferList().unlockSection(jbn); + boolean ret = getJavaBufferList().removeNode(jbn, false); //also releases locks + com.oracle.svm.core.util.VMError.guarantee( ret, "^^^112");//assert !acquire(); + } else { + jbn.setAlive(false); + } + } if (nbn.isNonNull()) { - JfrBuffer nb = nbn.getValue(); - assert nb.isNonNull() && nbn.getAlive(); -// while(!JfrBufferNodeLinkedList.acquire(nbn)); - - // Flush all buffers if necessary. -// if (SubstrateJVM.isRecording()) { -// if (nb.isNonNull()) { -// flush(nb, WordFactory.unsigned(0), 0); -// } -// } - nbn.setAlive(false); -// JfrBufferAccess.free(nb); -// JfrBufferNodeLinkedList.release(nbn); + if (getNativeBufferList().lockSection(nbn)) { + JfrBuffer nb = nbn.getValue(); + assert nb.isNonNull() && nbn.getAlive(); + if (SubstrateJVM.isRecording()) { + if (nb.isNonNull()) { + flush(nb, WordFactory.unsigned(0), 0); + } + } +// getNativeBufferList().unlockSection(nbn); + boolean ret = getNativeBufferList().removeNode(nbn, false); + com.oracle.svm.core.util.VMError.guarantee( ret, "^^^113");//assert !acquire(); + } else { + nbn.setAlive(false); + } } - // Free and reset all data. threadId.set(isolateThread, 0); parentThreadId.set(isolateThread, 0); @@ -303,8 +271,7 @@ public JfrBuffer getJavaBuffer() { JfrBufferNode result = javaBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); - result = allocate(buffer); - result.setThread(CurrentIsolate.getCurrentThread()); + result = JfrBufferNodeLinkedList.createNode(buffer, CurrentIsolate.getCurrentThread()); javaBufferNode.set(result); javaBufferList.addNode(result); } @@ -317,7 +284,7 @@ public JfrBuffer getNativeBuffer() { JfrBufferNode result = nativeBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); - result = allocate(buffer); + result = JfrBufferNodeLinkedList.createNode(buffer, CurrentIsolate.getCurrentThread()); nativeBufferNode.set(result); nativeBufferList.addNode(result); } From 8949bd1198b3d049048c3f2d9a698591f2e94852 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 3 Nov 2022 16:31:28 -0400 Subject: [PATCH 08/72] cleanup and refactor --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 36 ++++++++------ .../oracle/svm/core/jfr/JfrChunkWriter.java | 49 +++++++++++-------- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index e1101981b793..abc84ed7af95 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -47,16 +47,24 @@ static int offsetOfAcquired() { @Uninterruptible(reason = "Called from uninterruptible code.") public JfrBufferNode getAndLockTail() { - //TODO: we could limit retries and skip flush if needed com.oracle.svm.core.util.VMError.guarantee(tail.isNonNull(), "^^116"); - int count =0 ; - while(!acquire(tail)) { - count++; -// if (count >1000){return WordFactory.nullPointer();} - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^93"); + if (tryAcquire(tail)) { + return tail; } - return tail; + return WordFactory.nullPointer(); } + + @Uninterruptible(reason = "Called from uninterruptible code.") + public static boolean tryAcquire(JfrBufferNode node) { + for (int retry = 0; retry < 10000; retry++){ + if (node.isNull() || acquire(node)) { + return true; + } + } + com.oracle.svm.core.util.VMError.guarantee(false, "^^^111"); + return false; + } + @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isTail(JfrBufferNode node) { return node == tail; @@ -83,7 +91,7 @@ public static JfrBufferNode createNode(com.oracle.svm.core.jfr.JfrBuffer buffer, node.setThread(thread); node.setPrev(WordFactory.nullPointer()); node.setNext(WordFactory.nullPointer()); - node.setAcquired(2); + node.setAcquired(0); return node; } public JfrBufferNodeLinkedList(){ @@ -170,8 +178,8 @@ public boolean removeNode(JfrBufferNode node, boolean flushing){ com.oracle.svm.core.util.VMError.guarantee(prev != next, "^^^92"); // Free LL node holding buffer - com.oracle.svm.core.util.VMError.guarantee(node.getValue().isNonNull(), "^^^9");//TODO: uncomment - JfrBufferAccess.free(node.getValue()); //TODO: uncomment + com.oracle.svm.core.util.VMError.guarantee(node.getValue().isNonNull(), "^^^9"); + JfrBufferAccess.free(node.getValue()); release(node); //is this necessary? ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); @@ -184,7 +192,7 @@ public boolean removeNode(JfrBufferNode node, boolean flushing){ } @Uninterruptible(reason = "Called from uninterruptible code.") public void addNode(JfrBufferNode node){ -// com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^127"); //TODO : uncomment + com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^127"); int count =0; while(!acquire(head)) { // *** need infinite tries. count++; @@ -207,7 +215,7 @@ public static boolean acquire(JfrBufferNode node) { com.oracle.svm.core.util.VMError.guarantee(!isAcquired(node), "^^^100"); return true; } - return ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), 2, 3, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); + return ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), 0, 1, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) @@ -217,10 +225,10 @@ public static void release(JfrBufferNode node) { return; } com.oracle.svm.core.util.VMError.guarantee(isAcquired(node), "^^^10"); - node.setAcquired(2); + node.setAcquired(0); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isAcquired(JfrBufferNode node) { - return node.getAcquired() == 3; + return node.getAcquired() == 1; } } \ No newline at end of file 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 8e401e553d51..5d9c77ad420c 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 @@ -48,7 +48,14 @@ import com.oracle.svm.core.util.VMError; -import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.*; +import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; +import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; +import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.createNode; +import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.release; +import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.acquire; +import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.tryAcquire; +import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.isAcquired; + /** * This class is used when writing the in-memory JFR data to a file. For all operations, except @@ -603,11 +610,12 @@ private void flushStorage() { } @Uninterruptible(reason = "Called from uninterruptible code.") - private void traverseList(com.oracle.svm.core.jfr.JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { + private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { // Traverse back to front to minimize conflict with threads adding new nodes. Which could effectively block the traversal JfrBufferNode node = linkedList.getAndLockTail(); if (node.isNull()) { + // If we couldn't acquire the tail. Give up, and try again next time. return; } @@ -617,23 +625,25 @@ private void traverseList(com.oracle.svm.core.jfr.JfrBufferNodeLinkedList linked // Try to remove if (!node.getAlive()){ - int count = 0; if (linkedList.removeNode(node, true)) { // able to remove node - while (prev.isNonNull() && !acquire(prev)) { - count++; - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^110"); + if (tryAcquire(prev)){ + node = prev; + continue; } + // Failed to get next node. Give up on flush and try again later + return; } else { // unable to remove node - while (prev.isNonNull() && !acquire(prev)) { - count++; - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^111"); + if (tryAcquire(prev)){ + release(node); + node = prev; + continue; } + // Failed to get next node. Give up on flush and try again later release(node); + return; } - node = prev; - continue; } // flush buffer to disk -------------------------------------- @@ -662,18 +672,17 @@ private void traverseList(com.oracle.svm.core.jfr.JfrBufferNodeLinkedList linked } } // done flush buffer to disk -------------------------------------- + com.oracle.svm.core.util.VMError.guarantee( prev!=node.getNext() || (linkedList.isHead(node) && linkedList.isTail(node) && prev.isNull()), "^^^120"); + com.oracle.svm.core.util.VMError.guarantee( prev !=node, "^^^200"); - int count =0; - while (prev.isNonNull() && !acquire(prev)) { + boolean prevAcquired = tryAcquire(prev); + release(node); + + if (!prevAcquired){ + return; + } - if (!isAcquired(prev)){ - count++;// increase count if we couldnt acquire, but noone else holds the lock. - } - com.oracle.svm.core.util.VMError.guarantee(prev !=node && prev!=node.getNext(), "^^^120"); - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^94"); - }//acquire the next node. Hand-over-hand traversal. com.oracle.svm.core.util.VMError.guarantee(prev.isNull() || isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^119"); - release(node); node = prev; // new target is already locked } } From aa62cfb4d27043e0a2c8d1980c85682eb0730152 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 10:25:23 -0500 Subject: [PATCH 09/72] refactor --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 81 ++++++++----------- .../oracle/svm/core/jfr/JfrChunkWriter.java | 46 ++++------- .../oracle/svm/core/jfr/JfrThreadLocal.java | 13 +-- 3 files changed, 55 insertions(+), 85 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index abc84ed7af95..aa9f7820b41a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -9,18 +9,24 @@ import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.RawFieldOffset; +import org.graalvm.nativeimage.c.struct.SizeOf; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.jdk.UninterruptibleEntry; +import org.graalvm.nativeimage.IsolateThread; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.UnsignedUtils; public class JfrBufferNodeLinkedList { @RawStructure - public interface JfrBufferNode extends com.oracle.svm.core.jdk.UninterruptibleEntry { + public interface JfrBufferNode extends UninterruptibleEntry { @RawField JfrBuffer getValue(); @RawField void setValue(JfrBuffer value); @RawField - org.graalvm.nativeimage.IsolateThread getThread(); + IsolateThread getThread(); @RawField - void setThread(org.graalvm.nativeimage.IsolateThread thread); + void setThread(IsolateThread thread); @RawField boolean getAlive(); @@ -34,20 +40,20 @@ public interface JfrBufferNode extends com.oracle.svm.core.jdk.UninterruptibleEn @RawFieldOffset static int offsetOfAcquired() { - throw com.oracle.svm.core.util.VMError.unimplemented(); // replaced + throw VMError.unimplemented(); // replaced } @RawField - T getPrev(); + T getPrev(); @RawField - void setPrev(com.oracle.svm.core.jdk.UninterruptibleEntry value); + void setPrev(UninterruptibleEntry value); } private volatile JfrBufferNode head; private JfrBufferNode tail; // this never gets deleted @Uninterruptible(reason = "Called from uninterruptible code.") public JfrBufferNode getAndLockTail() { - com.oracle.svm.core.util.VMError.guarantee(tail.isNonNull(), "^^116"); + VMError.guarantee(tail.isNonNull(), "^^116"); if (tryAcquire(tail)) { return tail; } @@ -61,7 +67,7 @@ public static boolean tryAcquire(JfrBufferNode node) { return true; } } - com.oracle.svm.core.util.VMError.guarantee(false, "^^^111"); + VMError.guarantee(false, "^^^111"); return false; } @@ -75,17 +81,16 @@ public boolean isHead(JfrBufferNode node) { } @Uninterruptible(reason = "Called from uninterruptible code.") private void setHead(JfrBufferNode node) { - com.oracle.svm.core.util.VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^11");//assert !acquire(); + VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^11");//assert !acquire(); head = node; } @Uninterruptible(reason = "Called from uninterruptible code.") private static org.graalvm.word.UnsignedWord getHeaderSize() { -// return com.oracle.svm.core.util.UnsignedUtils.roundUp(WordFactory.unsigned(1), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); - return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); } @Uninterruptible(reason = "Called from uninterruptible code.") - public static JfrBufferNode createNode(com.oracle.svm.core.jfr.JfrBuffer buffer, org.graalvm.nativeimage.IsolateThread thread){ - JfrBufferNode node = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); + public static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread){ + JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(getHeaderSize()); node.setAlive(true); node.setValue(buffer); node.setThread(thread); @@ -104,7 +109,7 @@ public void teardown(){ } @Uninterruptible(reason = "Called from uninterruptible code.") public boolean lockSection(JfrBufferNode target) { - com.oracle.svm.core.util.VMError.guarantee(target.isNonNull(), "^^^84"); + VMError.guarantee(target.isNonNull(), "^^^84"); // acquire target and adjacent nodes if(acquire(target)){ if (target.getPrev().isNull() || acquire(target.getPrev())) { @@ -123,9 +128,8 @@ public boolean lockSection(JfrBufferNode target) { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean lockAdjacent(JfrBufferNode target) { - com.oracle.svm.core.util.VMError.guarantee(target.isNonNull(), "^^^85"); + VMError.guarantee(target.isNonNull(), "^^^85"); // acquire target and adjacent nodes - if (target.getPrev().isNull() || acquire(target.getPrev())) { if (target.getNext().isNull() || acquire(target.getNext())) { return true; @@ -135,21 +139,6 @@ public boolean lockAdjacent(JfrBufferNode target) { release(target.getPrev()); } } - - return false; - } - @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean unlockSection(JfrBufferNode target) { - com.oracle.svm.core.util.VMError.guarantee(target.isNonNull(), "^^^84"); - if(target.getNext().isNonNull()) { - release(target.getNext()); - } - if (target.getPrev().isNonNull()) { - release(target.getPrev()); - } - - release(target); - return false; } @@ -157,28 +146,28 @@ public boolean unlockSection(JfrBufferNode target) { public boolean removeNode(JfrBufferNode node, boolean flushing){ JfrBufferNode next = node.getNext(); // next can never be null JfrBufferNode prev = node.getPrev(); - com.oracle.svm.core.util.VMError.guarantee(next.isNonNull(), "^^^89"); //tail must always exist until torn down - com.oracle.svm.core.util.VMError.guarantee(head.isNonNull(), "^^^8"); + VMError.guarantee(next.isNonNull(), "^^^89"); //tail must always exist until torn down + VMError.guarantee(head.isNonNull(), "^^^8"); // make one attempt to get all the locks. If flushing, only target nodes already acquired. if (flushing && !com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() && !lockAdjacent(node)) { return false; } - com.oracle.svm.core.util.VMError.guarantee((isAcquired(node) && isAcquired(next)) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^22");//assert !acquire(); + VMError.guarantee((isAcquired(node) && isAcquired(next)) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^22");//assert !acquire(); if (isHead(node)) { - com.oracle.svm.core.util.VMError.guarantee(prev.isNull(), "^^^96"); + VMError.guarantee(prev.isNull(), "^^^96"); setHead(next); // head could now be tail if there was only one node in the list - com.oracle.svm.core.util.VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^97"); + VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^97"); head.setPrev(WordFactory.nullPointer()); } else { - com.oracle.svm.core.util.VMError.guarantee( isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^90");//assert !acquire(); + VMError.guarantee( isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^90");//assert !acquire(); prev.setNext(next); next.setPrev(prev); } - com.oracle.svm.core.util.VMError.guarantee(prev != next, "^^^92"); + VMError.guarantee(prev != next, "^^^92"); // Free LL node holding buffer - com.oracle.svm.core.util.VMError.guarantee(node.getValue().isNonNull(), "^^^9"); + VMError.guarantee(node.getValue().isNonNull(), "^^^9"); JfrBufferAccess.free(node.getValue()); release(node); //is this necessary? ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); @@ -192,27 +181,27 @@ public boolean removeNode(JfrBufferNode node, boolean flushing){ } @Uninterruptible(reason = "Called from uninterruptible code.") public void addNode(JfrBufferNode node){ - com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^127"); + VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^127"); int count =0; while(!acquire(head)) { // *** need infinite tries. count++; - com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^23"); + VMError.guarantee(count < 100000, "^^^23"); } JfrBufferNode oldHead = head; node.setPrev(WordFactory.nullPointer()); - com.oracle.svm.core.util.VMError.guarantee(head.getPrev().isNull(), "^^^83"); + VMError.guarantee(head.getPrev().isNull(), "^^^83"); node.setNext(head); head.setPrev(node); head = node; - com.oracle.svm.core.util.VMError.guarantee(oldHead == head.getNext(), "^^^114"); + VMError.guarantee(oldHead == head.getNext(), "^^^114"); release(head.getNext()); } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static boolean acquire(JfrBufferNode node) { if (com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint()) { - com.oracle.svm.core.util.VMError.guarantee(!isAcquired(node), "^^^100"); + VMError.guarantee(!isAcquired(node), "^^^100"); return true; } return ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), 0, 1, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); @@ -221,10 +210,10 @@ public static boolean acquire(JfrBufferNode node) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void release(JfrBufferNode node) { if (com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint()) { - com.oracle.svm.core.util.VMError.guarantee(!isAcquired(node), "^^^101"); + VMError.guarantee(!isAcquired(node), "^^^101"); return; } - com.oracle.svm.core.util.VMError.guarantee(isAcquired(node), "^^^10"); + VMError.guarantee(isAcquired(node), "^^^10"); node.setAcquired(0); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) 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 5d9c77ad420c..3c91ee2a96a1 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 @@ -50,11 +50,11 @@ import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; -import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.createNode; import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.release; -import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.acquire; import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.tryAcquire; import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.isAcquired; +import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; +import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; /** @@ -93,9 +93,10 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private int lastMetadataId = 0; private int currentMetadataId = 0; - private boolean staticConstantsSerialized = false; private boolean newChunk = true; private boolean isFinal = false; + private SignedWord _metadataPosition; + public void setCurrentMetadataId(){ currentMetadataId++; @@ -215,29 +216,23 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, filename = null; fd = WordFactory.nullPointer(); + newChunk = false; } - private SignedWord _constantPoolPosition; - private SignedWord _metadataPosition; public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread();// fd should always be correct because its cleared and set within locked critical section - -// JfrChangeEpochOperation op = new JfrChangeEpochOperation(true); -// op.enqueue(); flushStorage(); if (threadRepo.isDirty(true)){ writeThreadCheckpointEvent(threadRepo, true); } assert lock.isHeldByCurrentThread(); - SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); // WILL get written again when the chunk closes and overwrite what we write here. In that case we shouldn't wipe the repos right? How does hotspot handle it? + SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); assert lock.isHeldByCurrentThread(); -// patchFileHeader(_constantPoolPosition, metadataPosition, true); patchFileHeader(constantPoolPosition, metadataPosition, true); - + newChunk = false; // unlike rotate chunk, don't close file. - } private void writeFileHeader() { @@ -355,14 +350,12 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean getFileSupport().writeInt(fd, 0); // We'll patch this later. JfrConstantPool[] serializers = JfrSerializerSupport.get().getSerializers(); - int poolCount; - if (!staticConstantsSerialized) { - poolCount = writeConstantPools(serializers, flush) + writeConstantPools(repositories, flush); - staticConstantsSerialized = true; - } else { - poolCount = writeConstantPools(repositories, flush); + int poolCount = 0; + if (newChunk) { + poolCount = writeConstantPools(serializers, flush); } -// int poolCount = writeConstantPools(serializers, flush) + writeConstantPools(repositories, flush); + poolCount += writeConstantPools(repositories, flush); + SignedWord currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); // *** write number of constant pools written getFileSupport().writeInt(fd, makePaddedInt(poolCount)); @@ -376,10 +369,6 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { int count = 0; for (JfrConstantPool constantPool : constantPools) { -// if (constantPool instanceof com.oracle.svm.core.jfr.JfrThreadRepository) { -// System.out.println("*** Skipping thread repo"); -// continue; -// } int poolCount = constantPool.write(this, flush); count += poolCount; } @@ -388,10 +377,9 @@ private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { assert lock.isHeldByCurrentThread(); - // *** works to prevent duplicate metadata from being written to disk + //always write metadata on a new chunk! if (currentMetadataId != lastMetadataId || newChunk) { lastMetadataId = currentMetadataId; - newChunk = false; //always write metadata on a new chunk! } else { return _metadataPosition; } @@ -562,8 +550,8 @@ private void changeEpoch() { // 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 - JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); - JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); + JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); + JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); traverseList(javaBuffers, true, true); traverseList(nativeBuffers, false, true); @@ -586,8 +574,8 @@ private void changeEpoch() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage() { - com.oracle.svm.core.jfr.JfrBufferNodeLinkedList javaBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList(); - com.oracle.svm.core.jfr.JfrBufferNodeLinkedList nativeBuffers = com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList(); + JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); + JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); // javaBuffers.addNode(javaBuffers.createNode()); // javaBuffers.addNode(javaBuffers.createNode()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 4570950a4694..c6485ace3c00 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -170,7 +170,6 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { flush(jb, WordFactory.unsigned(0), 0); } } -// getJavaBufferList().unlockSection(jbn); boolean ret = getJavaBufferList().removeNode(jbn, false); //also releases locks com.oracle.svm.core.util.VMError.guarantee( ret, "^^^112");//assert !acquire(); } else { @@ -188,7 +187,6 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { flush(nb, WordFactory.unsigned(0), 0); } } -// getNativeBufferList().unlockSection(nbn); boolean ret = getNativeBufferList().removeNode(nbn, false); com.oracle.svm.core.util.VMError.guarantee( ret, "^^^113");//assert !acquire(); } else { @@ -322,18 +320,13 @@ private static boolean acquireBufferWithRetry(JfrBuffer buffer) { } @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { - com.oracle.svm.core.util.VMError.guarantee(threadLocalBuffer.isNonNull(), "^^^15");//assert threadLocalBuffer.isNonNull(); - com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^70");//assert !acquire(); + com.oracle.svm.core.util.VMError.guarantee(threadLocalBuffer.isNonNull(), "^^^15"); + com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^70"); if (!acquireBufferWithRetry(threadLocalBuffer)) { - com.oracle.svm.core.util.VMError.guarantee(false , "^^^80 unable to promote. Lost event data");//assert !acquire(); + com.oracle.svm.core.util.VMError.guarantee(false , "^^^80 unable to promote. Lost event data"); return WordFactory.nullPointer(); } -// int count =0; -// while(!JfrBufferAccess.acquire(threadLocalBuffer)); {// new -// count++; -// VMError.guarantee(count < 20000, "^^^60"); -// } UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { From 94e515538f4a0e682555f95495bea0e9772895b9 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 11:22:33 -0500 Subject: [PATCH 10/72] checkstyle refactor format --- .../oracle/svm/core/jfr/JfrBufferAccess.java | 1 - .../svm/core/jfr/JfrBufferNodeLinkedList.java | 111 ++++++--- .../oracle/svm/core/jfr/JfrChunkWriter.java | 235 ++++++++---------- .../oracle/svm/core/jfr/JfrConstantPool.java | 2 +- .../svm/core/jfr/JfrMethodRepository.java | 7 +- .../svm/core/jfr/JfrNativeEventWriter.java | 6 +- .../svm/core/jfr/JfrSymbolRepository.java | 9 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 65 ++--- .../svm/core/jfr/JfrThreadRepository.java | 26 +- .../svm/core/jfr/JfrTypeRepository.java | 4 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 14 +- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 4 - .../svm/core/jfr/events/ThreadSleepEvent.java | 4 +- 13 files changed, 222 insertions(+), 266 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 2e26743166f1..a556adb3a098 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -94,7 +94,6 @@ public static boolean acquire(JfrBuffer buffer) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void release(JfrBuffer buffer) { - com.oracle.svm.core.util.VMError.guarantee(buffer.getAcquired() == ACQUIRED, "^^^21");////assert buffer.getAcquired() == ACQUIRED; buffer.setAcquired(NOT_ACQUIRED); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index aa9f7820b41a..2c1989a542fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -1,9 +1,33 @@ +/* + * 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.core.jfr; -import com.oracle.svm.core.jfr.JfrThreadLocal; import com.oracle.svm.core.Uninterruptible; import org.graalvm.word.WordFactory; -import org.graalvm.compiler.nodes.NamedLocationIdentity; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.nativeimage.c.struct.RawField; @@ -15,23 +39,29 @@ import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.util.UnsignedUtils; +import com.oracle.svm.core.thread.VMOperation; + public class JfrBufferNodeLinkedList { @RawStructure public interface JfrBufferNode extends UninterruptibleEntry { @RawField JfrBuffer getValue(); + @RawField void setValue(JfrBuffer value); @RawField IsolateThread getThread(); + @RawField void setThread(IsolateThread thread); @RawField boolean getAlive(); + @RawField void setAlive(boolean alive); + @RawField int getAcquired(); @@ -42,14 +72,16 @@ public interface JfrBufferNode extends UninterruptibleEntry { static int offsetOfAcquired() { throw VMError.unimplemented(); // replaced } + @RawField T getPrev(); @RawField void setPrev(UninterruptibleEntry value); } + private volatile JfrBufferNode head; - private JfrBufferNode tail; // this never gets deleted + private JfrBufferNode tail; // this never gets deleted until torn down @Uninterruptible(reason = "Called from uninterruptible code.") public JfrBufferNode getAndLockTail() { @@ -62,12 +94,11 @@ public JfrBufferNode getAndLockTail() { @Uninterruptible(reason = "Called from uninterruptible code.") public static boolean tryAcquire(JfrBufferNode node) { - for (int retry = 0; retry < 10000; retry++){ + for (int retry = 0; retry < 10000; retry++) { if (node.isNull() || acquire(node)) { return true; } } - VMError.guarantee(false, "^^^111"); return false; } @@ -75,21 +106,25 @@ public static boolean tryAcquire(JfrBufferNode node) { public boolean isTail(JfrBufferNode node) { return node == tail; } + @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isHead(JfrBufferNode node) { return node == head; } + @Uninterruptible(reason = "Called from uninterruptible code.") private void setHead(JfrBufferNode node) { - VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^11");//assert !acquire(); + VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "Cannot set JfrBufferNodeLinkedList head before acquiring."); head = node; } + @Uninterruptible(reason = "Called from uninterruptible code.") private static org.graalvm.word.UnsignedWord getHeaderSize() { return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); } + @Uninterruptible(reason = "Called from uninterruptible code.") - public static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread){ + public static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(getHeaderSize()); node.setAlive(true); node.setValue(buffer); @@ -99,19 +134,21 @@ public static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread){ node.setAcquired(0); return node; } - public JfrBufferNodeLinkedList(){ + + public JfrBufferNodeLinkedList() { tail = createNode(WordFactory.nullPointer(), WordFactory.nullPointer()); head = tail; } - public void teardown(){ + public void teardown() { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(tail); } + @Uninterruptible(reason = "Called from uninterruptible code.") public boolean lockSection(JfrBufferNode target) { - VMError.guarantee(target.isNonNull(), "^^^84"); + VMError.guarantee(target.isNonNull(), "Attempted to lock buffer node that is null."); // acquire target and adjacent nodes - if(acquire(target)){ + if (acquire(target)) { if (target.getPrev().isNull() || acquire(target.getPrev())) { if (target.getNext().isNull() || acquire(target.getNext())) { return true; @@ -128,7 +165,7 @@ public boolean lockSection(JfrBufferNode target) { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean lockAdjacent(JfrBufferNode target) { - VMError.guarantee(target.isNonNull(), "^^^85"); + VMError.guarantee(target.isNonNull(), "Attempted to lock buffer node that is null."); // acquire target and adjacent nodes if (target.getPrev().isNull() || acquire(target.getPrev())) { if (target.getNext().isNull() || acquire(target.getNext())) { @@ -143,65 +180,58 @@ public boolean lockAdjacent(JfrBufferNode target) { } @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean removeNode(JfrBufferNode node, boolean flushing){ + public boolean removeNode(JfrBufferNode node, boolean flushing) { JfrBufferNode next = node.getNext(); // next can never be null JfrBufferNode prev = node.getPrev(); - VMError.guarantee(next.isNonNull(), "^^^89"); //tail must always exist until torn down - VMError.guarantee(head.isNonNull(), "^^^8"); + // tail must always exist until torn down + VMError.guarantee(next.isNonNull(), "Attmpted to remove tail node from JfrBufferNodeLinkedList"); + VMError.guarantee(head.isNonNull(), "Head of JfrBufferNodeLinkedList must always exist."); // make one attempt to get all the locks. If flushing, only target nodes already acquired. - if (flushing && !com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() && !lockAdjacent(node)) { + if (flushing && !VMOperation.isInProgressAtSafepoint() && !lockAdjacent(node)) { return false; } - VMError.guarantee((isAcquired(node) && isAcquired(next)) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^22");//assert !acquire(); + VMError.guarantee((isAcquired(node) && isAcquired(next)) || VMOperation.isInProgressAtSafepoint(), "Cannot remove JfrBufferNodeLinkedList node outside safepoint without acquiring section."); if (isHead(node)) { - VMError.guarantee(prev.isNull(), "^^^96"); + VMError.guarantee(prev.isNull(), "Head should be first node in JfrBufferNodeLinkedList."); setHead(next); // head could now be tail if there was only one node in the list - VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^97"); head.setPrev(WordFactory.nullPointer()); - } else { - VMError.guarantee( isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^90");//assert !acquire(); + } else { + VMError.guarantee(isAcquired(prev) || VMOperation.isInProgressAtSafepoint(), "Cannot remove JfrBufferNodeLinkedList node outside safepoint without acquiring prev."); prev.setNext(next); next.setPrev(prev); } - VMError.guarantee(prev != next, "^^^92"); - // Free LL node holding buffer - VMError.guarantee(node.getValue().isNonNull(), "^^^9"); + VMError.guarantee(node.getValue().isNonNull(), "JFR buffer should always exist until removal of respective JfrBufferNodeLinkedList node."); JfrBufferAccess.free(node.getValue()); - release(node); //is this necessary? + release(node); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); - // release existing locks release(next); if (prev.isNonNull()) { release(prev); } return true; } + @Uninterruptible(reason = "Called from uninterruptible code.") - public void addNode(JfrBufferNode node){ - VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^127"); - int count =0; - while(!acquire(head)) { // *** need infinite tries. - count++; - VMError.guarantee(count < 100000, "^^^23"); + public void addNode(JfrBufferNode node) { + while (!acquire(head)) { + // spin until we acquire } - JfrBufferNode oldHead = head; node.setPrev(WordFactory.nullPointer()); - VMError.guarantee(head.getPrev().isNull(), "^^^83"); + VMError.guarantee(head.getPrev().isNull(), "Adding node: Head should be first node in JfrBufferNodeLinkedList."); node.setNext(head); head.setPrev(node); head = node; - VMError.guarantee(oldHead == head.getNext(), "^^^114"); release(head.getNext()); } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static boolean acquire(JfrBufferNode node) { - if (com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint()) { - VMError.guarantee(!isAcquired(node), "^^^100"); + if (VMOperation.isInProgressAtSafepoint()) { + VMError.guarantee(!isAcquired(node), "JfrBufferNodes should not be in acquired state when entering safepoints."); return true; } return ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), 0, 1, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); @@ -209,15 +239,14 @@ public static boolean acquire(JfrBufferNode node) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void release(JfrBufferNode node) { - if (com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint()) { - VMError.guarantee(!isAcquired(node), "^^^101"); + if (VMOperation.isInProgressAtSafepoint()) { return; } - VMError.guarantee(isAcquired(node), "^^^10"); node.setAcquired(0); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isAcquired(JfrBufferNode node) { return node.getAcquired() == 1; } -} \ No newline at end of file +} 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 3c91ee2a96a1..2973c8933225 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 @@ -43,12 +43,9 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMOperationControl; -import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; -import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.release; import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.tryAcquire; @@ -56,7 +53,6 @@ import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; - /** * This class is used when writing the in-memory JFR data to a file. For all operations, except * those listed in {@link JfrUnlockedChunkWriter}, it is necessary to acquire the {@link #lock} @@ -95,10 +91,9 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private int currentMetadataId = 0; private boolean newChunk = true; private boolean isFinal = false; - private SignedWord _metadataPosition; - + private SignedWord metadataPosition; - public void setCurrentMetadataId(){ + public void setCurrentMetadataId() { currentMetadataId++; } @@ -143,7 +138,7 @@ public void maybeOpenFile() { } } - public void markChunkFinal(){ + public void markChunkFinal() { assert lock.isHeldByCurrentThread(); isFinal = true; } @@ -153,20 +148,21 @@ public boolean openFile(String outputFile) { isFinal = false; generation = 1; newChunk = true; - System.out.println("*** ChunkWriter openfile"); chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); writeFileHeader(); - lastCheckpointOffset = -1;// must reset this on new chunk + lastCheckpointOffset = -1; // must reset this on new chunk return true; } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") // *** top pointer (of the buffer) like if it gets reinit. + + @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") public boolean write(JfrBuffer buffer) { return write(buffer, true); } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") // *** top pointer (of the buffer) like if it gets reinit. + + @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") public boolean write(JfrBuffer buffer, boolean reset) { assert JfrBufferAccess.isAcquired(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); @@ -175,7 +171,7 @@ public boolean write(JfrBuffer buffer, boolean reset) { } boolean success = getFileSupport().write(fd, buffer.getTop(), unflushedSize); - if(reset) { + if (reset) { JfrBufferAccess.increaseTop(buffer, unflushedSize); } if (!success) { @@ -190,54 +186,51 @@ public boolean write(JfrBuffer buffer, boolean reset) { */ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread(); - System.out.println("*** rotating chunk: closeFile"); /* * Switch to a new epoch. This is done at a safepoint to ensure that we end up with * consistent data, even if multiple threads have JFR events in progress. */ - System.out.println("*** safepoint start"); JfrChangeEpochOperation op = new JfrChangeEpochOperation(false); op.enqueue(); - System.out.println("*** safepoint end"); /* * After changing the epoch, all subsequently triggered JFR events will be recorded into the * data structures of the new epoch. This guarantees that the data in the old epoch can be * persisted to a file without a safepoint. */ - if (threadRepo.isDirty(false)){ + if (threadRepo.isDirty(false)) { writeThreadCheckpointEvent(threadRepo, false); } SignedWord constantPoolPosition = writeCheckpointEvent(repositories, false); - SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); -// _constantPoolPosition = constantPoolPosition; - patchFileHeader(constantPoolPosition, metadataPosition); // write of header doesn't have to be uninterruptible because closefile() already has the lock. It can get interrupted by safepoint but it'll just resume later. + writeMetadataEvent(metadataDescriptor); + patchFileHeader(constantPoolPosition); getFileSupport().close(fd); filename = null; fd = WordFactory.nullPointer(); newChunk = false; } + public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { - assert lock.isHeldByCurrentThread();// fd should always be correct because its cleared and set within locked critical section + assert lock.isHeldByCurrentThread(); flushStorage(); - if (threadRepo.isDirty(true)){ + if (threadRepo.isDirty(true)) { writeThreadCheckpointEvent(threadRepo, true); } assert lock.isHeldByCurrentThread(); SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); - SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); + writeMetadataEvent(metadataDescriptor); assert lock.isHeldByCurrentThread(); - patchFileHeader(constantPoolPosition, metadataPosition, true); + patchFileHeader(constantPoolPosition, true); newChunk = false; // unlike rotate chunk, don't close file. } private void writeFileHeader() { // Write the header - some data gets patched later on. - getFileSupport().write(fd, FILE_MAGIC); //magic + getFileSupport().write(fd, FILE_MAGIC); // magic getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); // version getFileSupport().writeShort(fd, JFR_VERSION_MINOR); assert getFileSupport().position(fd).equal(CHUNK_SIZE_OFFSET); @@ -246,28 +239,24 @@ private void writeFileHeader() { getFileSupport().writeLong(fd, 0L); // metadata position getFileSupport().writeLong(fd, chunkStartNanos); // startNanos getFileSupport().writeLong(fd, 0L); // durationNanos - getFileSupport().writeLong(fd, chunkStartTicks); // *** only changed after a chunk rotation is complete (after header is patched) + getFileSupport().writeLong(fd, chunkStartTicks); getFileSupport().writeLong(fd, JfrTicks.getTicksFrequency()); - getFileSupport().writeByte(fd, nextGeneration()); // in hotspot a 1 byte generation is written - getFileSupport().writeByte(fd, (byte) 0 ); // in hotspot 1 byte PAD padding + getFileSupport().writeByte(fd, nextGeneration()); // in hotspot a 1 byte generation is + // written + getFileSupport().writeByte(fd, (byte) 0); // in hotspot 1 byte PAD padding short flags = 0; - flags += compressedInts ? 1 : 0; - flags += isFinal ? 1*2 : 0; - - getFileSupport().writeShort(fd, flags); // seems like only 2 bytes of the flags are written after the 1 byte generation + flags += compressedInts ? 1 : 0; + flags += isFinal ? 1 * 2 : 0; -// getFileSupport().writeInt(fd, compressedInts ? 1 : 0); -// getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); -// getFileSupport().writeByte(fd, nextGeneration()); + getFileSupport().writeShort(fd, flags); } - public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition) { - patchFileHeader(constantPoolPosition, metadataPosition, false); + public void patchFileHeader(SignedWord constantPoolPosition) { + patchFileHeader(constantPoolPosition, false); } - private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition, boolean flushpoint) { + private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint) { assert lock.isHeldByCurrentThread(); - System.out.println("*** patchFileHeader"); SignedWord currentPos = getFileSupport().position(fd); long chunkSize = getFileSupport().position(fd).rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; @@ -277,27 +266,27 @@ private void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadat getFileSupport().writeLong(fd, metadataPosition.rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); - // *** I guess they didn't write anything else because nothing else changes getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); if (flushpoint) { - //chunk is not finished - getFileSupport().writeByte(fd, nextGeneration()); // there are 4 bytes at the end. The first byte is the finished flag. + // chunk is not finished + getFileSupport().writeByte(fd, nextGeneration()); // there are 4 bytes at the end. The + // first byte is the finished flag. } else { getFileSupport().writeByte(fd, COMPLETE); } - getFileSupport().writeByte(fd, (byte) 0 ); // in hotspot 1 byte PAD padding + getFileSupport().writeByte(fd, (byte) 0); short flags = 0; - flags += compressedInts ? 1 : 0; - flags += isFinal ? 1*2 : 0; + flags += compressedInts ? 1 : 0; + flags += isFinal ? 1 * 2 : 0; - getFileSupport().writeShort(fd, flags); // seems like only 2 bytes of the flags are written after the 1 byte generation + getFileSupport().writeShort(fd, flags); - //need to move pointer back to correct position for next write - getFileSupport().seek(fd,currentPos); + // need to move pointer back to correct position for next write + getFileSupport().seek(fd, currentPos); } - private byte nextGeneration(){ - if (generation==MAX_BYTE){ + private byte nextGeneration() { + if (generation == MAX_BYTE) { // similar to Hotspot, restart counter if required. generation = 1; return MAX_BYTE; @@ -316,7 +305,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext - writeCompressedLong(8); // *** Threads + writeCompressedLong(8); SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. @@ -324,7 +313,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea int poolCount = threadRepo.write(this, flush); SignedWord currentPos = getFileSupport().position(fd); - getFileSupport().seek(fd, poolCountPos); // *** write number of constant pools written + getFileSupport().seek(fd, poolCountPos); getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); endEvent(start); @@ -332,9 +321,9 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea return start; } + private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { assert lock.isHeldByCurrentThread(); - System.out.println("*** Checkpoint"); SignedWord start = beginEvent(); if (lastCheckpointOffset < 0) { @@ -343,7 +332,7 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext //*** need to implement this! + writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext writeBoolean(true); // flush SignedWord poolCountPos = getFileSupport().position(fd); @@ -357,7 +346,7 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean poolCount += writeConstantPools(repositories, flush); SignedWord currentPos = getFileSupport().position(fd); - getFileSupport().seek(fd, poolCountPos); // *** write number of constant pools written + getFileSupport().seek(fd, poolCountPos); getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); endEvent(start); @@ -375,15 +364,14 @@ private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { return count; } - private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { + private void writeMetadataEvent(byte[] metadataDescriptor) { assert lock.isHeldByCurrentThread(); - //always write metadata on a new chunk! + // always write metadata on a new chunk! if (currentMetadataId != lastMetadataId || newChunk) { lastMetadataId = currentMetadataId; } else { - return _metadataPosition; + return; } - System.out.println("*** Metadata event"); SignedWord start = beginEvent(); writeCompressedLong(METADATA_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); @@ -391,8 +379,7 @@ private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { writeCompressedLong(0); // metadata id writeBytes(metadataDescriptor); // payload endEvent(start); - _metadataPosition = start; - return start; + metadataPosition = start; } public boolean shouldRotateDisk() { @@ -526,6 +513,7 @@ public enum StringEncoding { private class JfrChangeEpochOperation extends JavaVMOperation { private boolean flush; + protected JfrChangeEpochOperation(boolean flush) { super(VMOperationInfos.get(JfrChangeEpochOperation.class, "JFR change epoch", SystemEffect.SAFEPOINT)); this.flush = flush; @@ -559,7 +547,7 @@ private void changeEpoch() { JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { JfrBuffer buffer = buffers.addressOf(i).read(); - VMError.guarantee(!JfrBufferAccess.isAcquired(buffer), "^^^6");//assert !JfrBufferAccess.isAcquired(buffer); + assert !JfrBufferAccess.isAcquired(buffer); write(buffer); JfrBufferAccess.reinitialize(buffer); } @@ -574,21 +562,16 @@ private void changeEpoch() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage() { - JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); + JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); -// javaBuffers.addNode(javaBuffers.createNode()); -// javaBuffers.addNode(javaBuffers.createNode()); -// nativeBuffers.addNode(nativeBuffers.createNode()); -// nativeBuffers.addNode(nativeBuffers.createNode()); - traverseList(javaBuffers, true, false); traverseList(nativeBuffers, false, false); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { JfrBuffer buffer = buffers.addressOf(i).read(); - if(!JfrBufferAccess.acquire(buffer)){ // one attempt + if (!JfrBufferAccess.acquire(buffer)) { // one attempt continue; } write(buffer); @@ -599,81 +582,75 @@ private void flushStorage() { @Uninterruptible(reason = "Called from uninterruptible code.") private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { - // Traverse back to front to minimize conflict with threads adding new nodes. Which could effectively block the traversal - - JfrBufferNode node = linkedList.getAndLockTail(); - if (node.isNull()) { - // If we couldn't acquire the tail. Give up, and try again next time. - return; - } + // Traverse back to front to minimize conflict with threads adding new nodes. Which could + // possibly block traversal at very beginning. - while (node.isNonNull()) { - com.oracle.svm.core.util.VMError.guarantee(isAcquired(node) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^98"); - JfrBufferNode prev = node.getPrev(); + JfrBufferNode node = linkedList.getAndLockTail(); + if (node.isNull()) { + // If we couldn't acquire the tail. Give up, and try again next time. + return; + } -// Try to remove - if (!node.getAlive()){ - if (linkedList.removeNode(node, true)) { - // able to remove node - if (tryAcquire(prev)){ - node = prev; - continue; + while (node.isNonNull()) { + VMError.guarantee(isAcquired(node) || VMOperation.isInProgressAtSafepoint(), "Cannot traverse JfrBufferNodeLinkedList outside safepoint without acquiring nodes."); + JfrBufferNode prev = node.getPrev(); + + // Try to remove + if (!node.getAlive()) { + if (linkedList.removeNode(node, true)) { + // able to remove node + if (tryAcquire(prev)) { + node = prev; + continue; + } + // Failed to get next node. Give up on flush and try again later + return; + } else { + // unable to remove node + if (tryAcquire(prev)) { + release(node); + node = prev; + continue; + } + // Failed to get next node. Give up on flush and try again later + release(node); + return; } - // Failed to get next node. Give up on flush and try again later - return; - } else { - // unable to remove node - if (tryAcquire(prev)){ + } + + if (!linkedList.isTail(node)) { + JfrBuffer buffer = node.getValue(); + VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); + + // Try to get BUFFER if not in safepoint + if (!safepoint && !JfrBufferAccess.acquire(buffer)) { // make one attempt release(node); node = prev; continue; } - // Failed to get next node. Give up on flush and try again later - release(node); - return; - } - } -// flush buffer to disk -------------------------------------- - if (!linkedList.isTail(node)) { - JfrBuffer buffer = node.getValue(); - VMError.guarantee(buffer.isNonNull(), "^^^3"); // assert buffer.isNonNull(); + write(buffer); - // Try to get BUFFER if not in safepoint - if (!safepoint && !JfrBufferAccess.acquire(buffer)) { //make one attempt - release(node); - node = prev; - continue; - } - VMError.guarantee(JfrBufferAccess.isAcquired(buffer) || safepoint, "^^^5"); - if (safepoint) { - VMError.guarantee(!JfrBufferAccess.isAcquired(buffer), "^^^102"); + if (!safepoint) { + JfrBufferAccess.release(buffer); + } + if (java) { + JfrThreadLocal.notifyEventWriter(node.getThread()); + } } - write(buffer); - if (!safepoint) { - JfrBufferAccess.release(buffer); - } - if (java) { - VMError.guarantee(node.getThread().isNonNull(), "^^^20"); - JfrThreadLocal.notifyEventWriter(node.getThread()); - } - } -// done flush buffer to disk -------------------------------------- - com.oracle.svm.core.util.VMError.guarantee( prev!=node.getNext() || (linkedList.isHead(node) && linkedList.isTail(node) && prev.isNull()), "^^^120"); - com.oracle.svm.core.util.VMError.guarantee( prev !=node, "^^^200"); + boolean prevAcquired = tryAcquire(prev); + release(node); - boolean prevAcquired = tryAcquire(prev); - release(node); + if (!prevAcquired) { + return; + } - if (!prevAcquired){ - return; + VMError.guarantee(prev.isNull() || isAcquired(prev) || VMOperation.isInProgressAtSafepoint(), "Cannot advance in JfrBufferNodeLinkedList traversal before acquiring next node."); + node = prev; // new target is already locked } - - com.oracle.svm.core.util.VMError.guarantee(prev.isNull() || isAcquired(prev) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "^^^119"); - node = prev; // new target is already locked } -} + public long getChunkStartNanos() { return chunkStartNanos; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java index 374c4d7f5a18..1912380b2b40 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java @@ -48,7 +48,7 @@ public interface JfrConstantPool { /** * Persists the data of the previous epoch. May only be called at a safepoint, after the epoch - * changed. [*** change this comment] + * changed, or during flush. */ int write(JfrChunkWriter writer, boolean flush); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 957325027aad..245deb317afc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -45,6 +45,7 @@ public class JfrMethodRepository implements JfrConstantPool { private final VMMutex mutex; private final JfrMethodEpochData epochData0; private final JfrMethodEpochData epochData1; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void acquireLock() { mutex.lockNoTransition(); @@ -54,6 +55,7 @@ private void acquireLock() { private void releaseLock() { mutex.unlock(); } + @Platforms(Platform.HOSTED_ONLY.class) public JfrMethodRepository() { this.epochData0 = new JfrMethodEpochData(); @@ -125,11 +127,10 @@ public int write(JfrChunkWriter writer, boolean flush) { acquireLock(); } JfrMethodEpochData epochData = getEpochData(!flush); - System.out.println("writing methods"); int count = writeMethods(writer, epochData, flush); if (!flush) { epochData.clear(); - }else{ + } else { releaseLock(); } return count; @@ -138,10 +139,8 @@ public int write(JfrChunkWriter writer, boolean flush) { private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData, boolean flush) { int numberOfMethods = epochData.visitedMethods.getSize(); if (numberOfMethods == 0) { - System.out.println(" NO methods to write"); return EMPTY; } - System.out.println(" YES methods to write"); writer.writeCompressedLong(JfrType.Method.getId()); writer.writeCompressedInt(numberOfMethods); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index da4579bacfb8..ba9b13452588 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -292,11 +292,7 @@ private static JfrBuffer accommodate0(JfrNativeEventWriterData data, UnsignedWor JfrBuffer oldBuffer = data.getJfrBuffer(); switch (oldBuffer.getBufferType()) { case THREAD_LOCAL_NATIVE: -// JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); -// jfrThreadLocal.lockNative(); - com.oracle.svm.core.jfr.JfrBuffer buffer = JfrThreadLocal.flush(oldBuffer, uncommitted, requested); -// jfrThreadLocal.unlockNative(); - return buffer; + return JfrThreadLocal.flush(oldBuffer, uncommitted, requested); case C_HEAP: return reuseOrReallocateBuffer(oldBuffer, uncommitted, requested); default: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index d494deed0490..6db2d422b4a6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -117,12 +117,11 @@ public int write(JfrChunkWriter writer, boolean flush) { JfrSymbolHashtable table; if (flush) { table = getTable(false); - }else { + } else { table = getTable(true); } if (table.getSize() == 0) { - System.out.println("*** ---- empty symbol table"); return EMPTY; } writer.writeCompressedLong(JfrType.Symbol.getId()); @@ -138,9 +137,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } } -// if (!flush) { - table.clear(); -// } + table.clear(); return NON_EMPTY; } @@ -148,7 +145,7 @@ private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { writer.writeCompressedLong(symbol.getId()); writer.writeByte(JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); byte[] value = symbol.getValue().getBytes(StandardCharsets.UTF_8); - if (symbol.getReplaceDotWithSlash()) { // *** this is where java.lang.Object gets converted + if (symbol.getReplaceDotWithSlash()) { replaceDotWithSlash(value); } writer.writeCompressedInt(value.length); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index c6485ace3c00..9a6acb5cc24a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -49,7 +49,6 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; - import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; import static com.oracle.svm.core.thread.PlatformThreads.getIsolateThread; @@ -73,10 +72,7 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalObject javaEventWriter = FastThreadLocalFactory.createObject(Target_jdk_jfr_internal_EventWriter.class, "JfrThreadLocal.javaEventWriter"); - // *** holds a pointer to the buffer that's on the heap -// private static final FastThreadLocalWord javaBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBuffer"); private static final FastThreadLocalWord javaBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBufferNode"); -// private static final FastThreadLocalWord nativeBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBuffer"); private static final FastThreadLocalWord nativeBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBufferNode"); private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId"); @@ -86,44 +82,22 @@ public class JfrThreadLocal implements ThreadListener { private long threadLocalBufferSize; private static JfrBufferNodeLinkedList javaBufferList; private static JfrBufferNodeLinkedList nativeBufferList; + @Uninterruptible(reason = "Called from uninterruptible code.") - public static JfrBufferNodeLinkedList getNativeBufferList(){ + public static JfrBufferNodeLinkedList getNativeBufferList() { return nativeBufferList; } + @Uninterruptible(reason = "Called from uninterruptible code.") - public static JfrBufferNodeLinkedList getJavaBufferList(){ + public static JfrBufferNodeLinkedList getJavaBufferList() { return javaBufferList; } -// @Uninterruptible(reason = "Called from uninterruptible code.") -// public static void lockNative(){ -// int count = 0; -// while(!JfrBufferNodeLinkedList.acquire(nativeBufferNode.get())){ -// count++; -// com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^24"); -// } -// } -// @Uninterruptible(reason = "Called from uninterruptible code.") -// public static void unlockNative(){ -// JfrBufferNodeLinkedList.release(nativeBufferNode.get()); -// } -// @Uninterruptible(reason = "Called from uninterruptible code.") -// public static void lockJava(){ -// int count =0; -// while(!JfrBufferNodeLinkedList.acquire(javaBufferNode.get())){ -// count++; -// com.oracle.svm.core.util.VMError.guarantee(count < 100000, "^^^25"); -// } -// } -// @Uninterruptible(reason = "Called from uninterruptible code.") -// public static void unlockJava(){ -// JfrBufferNodeLinkedList.release(javaBufferNode.get()); -// } @Platforms(Platform.HOSTED_ONLY.class) public JfrThreadLocal() { } - public void initialize(long bufferSize) { // *** at runtime? + public void initialize(long bufferSize) { this.threadLocalBufferSize = bufferSize; javaBufferList = new JfrBufferNodeLinkedList(); nativeBufferList = new JfrBufferNodeLinkedList(); @@ -132,6 +106,7 @@ public void initialize(long bufferSize) { // *** at runtime? public void exclude(Thread javaThread) { getIsolateThread(javaThread); } + @Uninterruptible(reason = "Accesses a JFR buffer.") @Override public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { @@ -140,7 +115,7 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { // Java object. Target_java_lang_Thread t = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class); threadId.set(isolateThread, t.getId()); - isExcluded.set(isolateThread,0); + isExcluded.set(isolateThread, 0); parentThreadId.set(isolateThread, JavaThreads.getParentThreadId(javaThread)); SubstrateJVM.getThreadRepo().registerThread(javaThread); @@ -170,8 +145,7 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { flush(jb, WordFactory.unsigned(0), 0); } } - boolean ret = getJavaBufferList().removeNode(jbn, false); //also releases locks - com.oracle.svm.core.util.VMError.guarantee( ret, "^^^112");//assert !acquire(); + boolean ret = getJavaBufferList().removeNode(jbn, false); // also releases locks } else { jbn.setAlive(false); } @@ -188,7 +162,6 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { } } boolean ret = getNativeBufferList().removeNode(nbn, false); - com.oracle.svm.core.util.VMError.guarantee( ret, "^^^113");//assert !acquire(); } else { nbn.setAlive(false); } @@ -227,7 +200,7 @@ public Target_jdk_jfr_internal_EventWriter getEventWriter() { // uninterruptible. public Target_jdk_jfr_internal_EventWriter newEventWriter() { assert javaEventWriter.get() == null; -// assert javaBuffer.get().isNull(); +// assert javaBuffer.get().isNull(); assert javaBufferNode.get().isNull(); JfrBuffer buffer = getJavaBuffer(); @@ -250,10 +223,13 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { return result; } + @Uninterruptible(reason = "Accesses a JFR buffer.") private static UnsignedWord getHeaderSize() { - return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); + return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrBufferNode.class), + WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); } + @Uninterruptible(reason = "Called from uninterruptible code.") private static JfrBufferNode allocate(com.oracle.svm.core.jfr.JfrBuffer buffer) { JfrBufferNode node = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); @@ -309,6 +285,7 @@ public static void notifyEventWriter(IsolateThread thread) { javaEventWriter.get(thread).notified = true; } } + @Uninterruptible(reason = "Epoch must not change while in this method.") private static boolean acquireBufferWithRetry(JfrBuffer buffer) { for (int retry = 0; retry < 100; retry++) { @@ -318,13 +295,13 @@ private static boolean acquireBufferWithRetry(JfrBuffer buffer) { } return false; } + @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { - com.oracle.svm.core.util.VMError.guarantee(threadLocalBuffer.isNonNull(), "^^^15"); - com.oracle.svm.core.util.VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint() , "^^^70"); + VMError.guarantee(threadLocalBuffer.isNonNull(), "TLB cannot be null if promoting."); + VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "Should not be promoting if at safepoint. "); if (!acquireBufferWithRetry(threadLocalBuffer)) { - com.oracle.svm.core.util.VMError.guarantee(false , "^^^80 unable to promote. Lost event data"); return WordFactory.nullPointer(); } @@ -334,7 +311,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit if (!globalMemory.write(threadLocalBuffer, unflushedSize)) { JfrBufferAccess.reinitialize(threadLocalBuffer); writeDataLoss(threadLocalBuffer, unflushedSize); - JfrBufferAccess.release(threadLocalBuffer);// new + JfrBufferAccess.release(threadLocalBuffer); return WordFactory.nullPointer(); } } @@ -346,11 +323,11 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit } JfrBufferAccess.reinitialize(threadLocalBuffer); assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); - if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { // *** do we have enough space now? - JfrBufferAccess.release(threadLocalBuffer);// new + if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { + JfrBufferAccess.release(threadLocalBuffer); return threadLocalBuffer; } - JfrBufferAccess.release(threadLocalBuffer);// new + JfrBufferAccess.release(threadLocalBuffer); return WordFactory.nullPointer(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 73b023b910ea..efe5fa80ca10 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -43,12 +43,6 @@ import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; -import jdk.jfr.internal.Options; - -import static com.oracle.svm.core.jfr.JfrChunkWriter.CONSTANT_POOL_TYPE_ID; -import static com.oracle.svm.core.jfr.JfrChunkWriter.getFileSupport; -import static com.oracle.svm.core.jfr.JfrNativeEventWriter.makePaddedInt; - /** * Repository that collects all metadata about threads and thread groups. @@ -114,7 +108,7 @@ private void registerThread0(Thread thread) { } JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); // *** set up event writer to write to thread buffer + JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); // needs to be in sync with JfrThreadConstant::serialize boolean isVirtual = JavaThreads.isVirtual(thread); @@ -136,7 +130,7 @@ private void registerThread0(Thread thread) { } JfrNativeEventWriter.commit(data); - // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we // *** underlying buffer of epochData.threadBuffer may have been replaced + // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we // need to update the repository pointer as well. epochData.threadBuffer = data.getJfrBuffer(); epochData.isDirty = true; @@ -203,25 +197,23 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { @Override public int write(JfrChunkWriter writer, boolean flush) { JfrThreadEpochData epochData = getEpochData(!flush); - if (flush){ - mutex.lock(); //only required when possibility of read/write same epoch data + if (flush) { + mutex.lock(); // only required when possibility of read/write same epoch data } int count = writeThreads(writer, epochData, flush); count += writeThreadGroups(writer, epochData, flush); - epochData.clear(); // *** can clear this because we only write when dirty + epochData.clear(); epochData.isDirty = false; - if (flush){ + if (flush) { mutex.unlock(); } return count; } - public boolean isDirty(boolean flush){ - //***no lock needed bc flag cannot be unset unless in epoch change or in flush. This is the only thread that can unset it bc of lock on chunkwriter. - // *** Race to read vs set flag is not a problem + public boolean isDirty(boolean flush) { JfrThreadEpochData epochData = getEpochData(!flush); return epochData.isDirty; } @@ -230,14 +222,14 @@ private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochD VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); writer.writeCompressedLong(JfrType.Thread.getId()); - writer.writeCompressedInt(epochData.visitedThreads.getSize()); // *** This needs to be cleared (as well as the buffers) + writer.writeCompressedInt(epochData.visitedThreads.getSize()); writer.write(epochData.threadBuffer, true); return NON_EMPTY; } private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { - int threadGroupCount = epochData.visitedThreadGroups.getSize(); // *** This needs to be cleared (as well as the buffers) + int threadGroupCount = epochData.visitedThreadGroups.getSize(); if (threadGroupCount == 0) { return EMPTY; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 99b1cff83321..aa30d8145292 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -77,8 +77,7 @@ private TypeInfo collectTypeInfo(boolean flush) { if (JfrTraceId.isUsedCurrentEpoch(clazz)) { visitClass(typeInfo, clazz); } - } - else if (JfrTraceId.isUsedPreviousEpoch(clazz)) { + } else if (JfrTraceId.isUsedPreviousEpoch(clazz)) { JfrTraceId.clearUsedPreviousEpoch(clazz); visitClass(typeInfo, clazz); } @@ -126,7 +125,6 @@ public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) } private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class clazz, boolean flush) { -// System.out.println("*** --- Writing Class:"+clazz.getName()); JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); // key writer.writeCompressedLong(typeInfo.getClassLoaderId(clazz.getClassLoader())); 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 fec194e6421c..ddf31492a25e 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 @@ -341,13 +341,12 @@ public void setOutput(String file) { if (existingFile) { chunkWriter.closeFile(metadataDescriptor, repositories, threadRepo); } - System.out.println("*** setOutput: "+ file); if (file != null) { - chunkWriter.openFile(file);// *** open up the new file + chunkWriter.openFile(file); // If in-memory recording was active so far, we should notify the recorder // thread because the global memory buffers could be rather full. if (!existingFile) { - recorderThread.signal();// *** do this if there was no existing file + recorderThread.signal(); } } } else { @@ -431,15 +430,13 @@ public void setThreadBufferSize(long size) { /** See {@link JVM#flush}. */ @Uninterruptible(reason = "Accesses a JFR buffer.") - public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommittedSize, int requestedSize) { // *** seems like its for the java buffers + public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommittedSize, int requestedSize) { assert writer != null; assert uncommittedSize >= 0; JfrBuffer oldBuffer = threadLocal.getJavaBuffer(); assert oldBuffer.isNonNull(); -// threadLocal.lockJava(); JfrBuffer newBuffer = JfrThreadLocal.flush(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); -// threadLocal.unlockJava(); if (newBuffer.isNull()) { // The flush failed for some reason, so mark the EventWriter as invalid for this write // attempt. @@ -463,13 +460,12 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted } public void flush() { - JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); //does this make it a safepoint? [NO] + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); try { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { chunkWriter.flush(metadataDescriptor, repositories, threadRepo); - System.out.println("*** Done Flush"); } } } finally { @@ -478,7 +474,7 @@ public void flush() { } public void markChunkFinal() { - JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); //does this make it a safepoint? [NO] + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); try { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); 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 6b6fda6230d7..20db1c82d015 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 @@ -406,14 +406,12 @@ public boolean shouldRotateDisk() { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void flush() { - System.out.println("*** Flush called"); SubstrateJVM.get().flush(); } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void include(Thread thread) { - System.out.println("*** include called"); // Temporarily do nothing. This is used for JFR streaming. } @@ -422,14 +420,12 @@ public void include(Thread thread) { public void exclude(Thread thread) { Exception e = new Exception(); e.printStackTrace(); - System.out.println("*** exclude called"); // Temporarily do nothing. This is used for JFR streaming. } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public boolean isExcluded(Thread thread) { - System.out.println("*** isExcluded called"); // Temporarily do nothing. This is used for JFR streaming. return false; } 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/ThreadSleepEvent.java index 00587874e859..2c779b5db58a 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/ThreadSleepEvent.java @@ -48,7 +48,7 @@ public static void emit(long time, long startTicks) { private static void emit0(long time, long startTicks) { if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadSleep)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); // *** would need to lock here + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadSleep); JfrNativeEventWriter.putLong(data, startTicks); @@ -56,7 +56,7 @@ private static void emit0(long time, long startTicks) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadSleep, 0)); JfrNativeEventWriter.putLong(data, time); - JfrNativeEventWriter.endSmallEvent(data); // // *** can unlock here + JfrNativeEventWriter.endSmallEvent(data); } } } From 137170ac78f184a369604be788d00ccdc32d4c52 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 12:02:17 -0500 Subject: [PATCH 11/72] clean diff --- .../src/com/oracle/svm/core/jfr/JfrBufferAccess.java | 1 + .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 1 - .../src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java | 4 ---- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index a556adb3a098..3321e245aa99 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -94,6 +94,7 @@ public static boolean acquire(JfrBuffer buffer) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void release(JfrBuffer buffer) { + assert buffer.getAcquired() == ACQUIRED; buffer.setAcquired(NOT_ACQUIRED); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 9a6acb5cc24a..c4a528f6ca0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -200,7 +200,6 @@ public Target_jdk_jfr_internal_EventWriter getEventWriter() { // uninterruptible. public Target_jdk_jfr_internal_EventWriter newEventWriter() { assert javaEventWriter.get() == null; -// assert javaBuffer.get().isNull(); assert javaBufferNode.get().isNull(); JfrBuffer buffer = getJavaBuffer(); 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 f505788d3d00..4f271398f32b 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 @@ -75,7 +75,6 @@ public static void processSamplerBuffers() { SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); if (buffer.isNull()) { /* No buffers to process. */ -// com.oracle.svm.core.util.VMError.guarantee(false, "no buffers to process -----"); SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); return; } @@ -124,11 +123,9 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState); /* Sample is already there, skip the rest of sample plus END_MARK symbol. */ current = current.add(sampleSize).add(SamplerSampleWriter.END_MARKER_SIZE); -// com.oracle.svm.core.util.VMError.guarantee(false, "if"); } else { assert JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, JfrStackTraceRepository.JfrStackTraceTableEntryStatus.SHOULD_SERIALIZE); /* Sample is not there. Start walking a stacktrace. */ -// com.oracle.svm.core.util.VMError.guarantee(false, "else"); stackTraceRepo.serializeStackTraceHeader(stackTraceId, isTruncated, sampleSize / SamplerSampleWriter.IP_SIZE); while (current.belowThan(end)) { long ip = current.readLong(0); @@ -142,7 +139,6 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { } } } - } finally { stackTraceRepo.releaseLock(); } From d4733002c8e286d04ce39d29cf9a01434e1f21a4 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 12:47:18 -0500 Subject: [PATCH 12/72] clean up. remove debug stacktrace print --- .../com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java | 2 -- 1 file changed, 2 deletions(-) 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 20db1c82d015..f1ecc5bb318d 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 @@ -418,8 +418,6 @@ public void include(Thread thread) { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void exclude(Thread thread) { - Exception e = new Exception(); - e.printStackTrace(); // Temporarily do nothing. This is used for JFR streaming. } From f234b27d038093742459672abc608aa4ae398ee4 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 13:17:26 -0500 Subject: [PATCH 13/72] fix mx gate errors --- .../src/com/oracle/svm/core/jfr/JfrConstantPool.java | 4 ++-- .../com/oracle/svm/core/jfr/JfrSymbolRepository.java | 7 +------ .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 11 +++-------- .../com/oracle/svm/core/jfr/JfrThreadRepository.java | 9 ++++----- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java index 1912380b2b40..532990102ac3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java @@ -35,13 +35,13 @@ public interface JfrConstantPool { /** - * If constant pool is empty, the {@link JfrConstantPool#write(JfrChunkWriter)} function returns + * If constant pool is empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} function returns * this value. */ int EMPTY = 0; /** - * If constant pool is not empty, the {@link JfrConstantPool#write(JfrChunkWriter)} function + * If constant pool is not empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} function * returns this value. */ int NON_EMPTY = 1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 6db2d422b4a6..101787e30265 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -114,12 +114,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r @Override public int write(JfrChunkWriter writer, boolean flush) { - JfrSymbolHashtable table; - if (flush) { - table = getTable(false); - } else { - table = getTable(true); - } + JfrSymbolHashtable table = getTable(!flush); if (table.getSize() == 0) { return EMPTY; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index c4a528f6ca0c..c970cb177b32 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -77,7 +77,6 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId"); private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId"); - private static final com.oracle.svm.core.threadlocal.FastThreadLocalInt isExcluded = FastThreadLocalFactory.createInt("JfrThreadLocal.enabled"); private long threadLocalBufferSize; private static JfrBufferNodeLinkedList javaBufferList; @@ -103,9 +102,6 @@ public void initialize(long bufferSize) { nativeBufferList = new JfrBufferNodeLinkedList(); } - public void exclude(Thread javaThread) { - getIsolateThread(javaThread); - } @Uninterruptible(reason = "Accesses a JFR buffer.") @Override @@ -115,7 +111,6 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { // Java object. Target_java_lang_Thread t = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class); threadId.set(isolateThread, t.getId()); - isExcluded.set(isolateThread, 0); parentThreadId.set(isolateThread, JavaThreads.getParentThreadId(javaThread)); SubstrateJVM.getThreadRepo().registerThread(javaThread); @@ -145,7 +140,7 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { flush(jb, WordFactory.unsigned(0), 0); } } - boolean ret = getJavaBufferList().removeNode(jbn, false); // also releases locks + getJavaBufferList().removeNode(jbn, false); // also releases locks } else { jbn.setAlive(false); } @@ -161,7 +156,7 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { flush(nb, WordFactory.unsigned(0), 0); } } - boolean ret = getNativeBufferList().removeNode(nbn, false); + getNativeBufferList().removeNode(nbn, false); } else { nbn.setAlive(false); } @@ -298,7 +293,7 @@ private static boolean acquireBufferWithRetry(JfrBuffer buffer) { @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { VMError.guarantee(threadLocalBuffer.isNonNull(), "TLB cannot be null if promoting."); - VMError.guarantee(!com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "Should not be promoting if at safepoint. "); + VMError.guarantee(!VMOperation.isInProgressAtSafepoint(), "Should not be promoting if at safepoint. "); if (!acquireBufferWithRetry(threadLocalBuffer)) { return WordFactory.nullPointer(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index efe5fa80ca10..4f875ae6a7d2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -43,7 +43,6 @@ import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; - /** * Repository that collects all metadata about threads and thread groups. */ @@ -200,8 +199,8 @@ public int write(JfrChunkWriter writer, boolean flush) { if (flush) { mutex.lock(); // only required when possibility of read/write same epoch data } - int count = writeThreads(writer, epochData, flush); - count += writeThreadGroups(writer, epochData, flush); + int count = writeThreads(writer, epochData); + count += writeThreadGroups(writer, epochData); epochData.clear(); @@ -218,7 +217,7 @@ public boolean isDirty(boolean flush) { return epochData.isDirty; } - private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { + private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); writer.writeCompressedLong(JfrType.Thread.getId()); @@ -228,7 +227,7 @@ private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochD return NON_EMPTY; } - private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData, boolean flush) { + private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { int threadGroupCount = epochData.visitedThreadGroups.getSize(); if (threadGroupCount == 0) { return EMPTY; From 3e33ee4a48b775320b8cebea9ca42d0238d29121 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 16:44:45 -0500 Subject: [PATCH 14/72] Avoid indeterminisim by setting JFR buffer to be unaquired when created. Will pass tests now --- .../src/com/oracle/svm/core/jfr/JfrBufferAccess.java | 1 + .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 3321e245aa99..8f7608668fc3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -65,6 +65,7 @@ public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType if (result.isNonNull()) { result.setSize(dataSize); result.setBufferType(bufferType); + result.setAcquired(NOT_ACQUIRED); reinitialize(result); } return result; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index c970cb177b32..2ae6232aeff6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -282,7 +282,7 @@ public static void notifyEventWriter(IsolateThread thread) { @Uninterruptible(reason = "Epoch must not change while in this method.") private static boolean acquireBufferWithRetry(JfrBuffer buffer) { - for (int retry = 0; retry < 100; retry++) { + for (int retry = 0; retry < 100000; retry++) { if (JfrBufferAccess.acquire(buffer)) { return true; } From 4d91ed88fa0cb3c8bb59722c79f89a6811eacf06 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 16:45:26 -0500 Subject: [PATCH 15/72] fix for gate tests --- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 2ae6232aeff6..f9c02ab2b8e7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -51,8 +51,6 @@ import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; -import static com.oracle.svm.core.thread.PlatformThreads.getIsolateThread; - /** * This class holds various JFR-specific thread local values. * From 62df18ae7ff0ed9d72d73e3a2850b1a4ebace0f0 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 7 Nov 2022 17:08:45 -0500 Subject: [PATCH 16/72] style --- .../src/com/oracle/svm/core/jfr/JfrConstantPool.java | 8 ++++---- .../src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java | 2 +- .../com/oracle/svm/core/jfr/JfrStackTraceRepository.java | 2 -- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 1 - .../src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java | 1 + 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java index 532990102ac3..505c65d04e3e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java @@ -35,14 +35,14 @@ public interface JfrConstantPool { /** - * If constant pool is empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} function returns - * this value. + * If constant pool is empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} + * function returns this value. */ int EMPTY = 0; /** - * If constant pool is not empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} function - * returns this value. + * If constant pool is not empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} + * function returns this value. */ int NON_EMPTY = 1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index ba9b13452588..9a400e8a7028 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -292,7 +292,7 @@ private static JfrBuffer accommodate0(JfrNativeEventWriterData data, UnsignedWor JfrBuffer oldBuffer = data.getJfrBuffer(); switch (oldBuffer.getBufferType()) { case THREAD_LOCAL_NATIVE: - return JfrThreadLocal.flush(oldBuffer, uncommitted, requested); + return JfrThreadLocal.flush(oldBuffer, uncommitted, requested); case C_HEAP: return reuseOrReallocateBuffer(oldBuffer, uncommitted, requested); default: 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 16284e0db1b1..e5e08636d988 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 @@ -240,10 +240,8 @@ public int write(JfrChunkWriter writer, boolean flush) { private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData, boolean flush) { if (epochData.numberOfSerializedStackTraces == 0) { -// System.out.println("no stacktraces to write"); return EMPTY; } -// System.out.println(" YES stacktraces to write"); writer.writeCompressedLong(JfrType.StackTrace.getId()); writer.writeCompressedInt(epochData.numberOfSerializedStackTraces); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index f9c02ab2b8e7..c1c8caa3f116 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -100,7 +100,6 @@ public void initialize(long bufferSize) { nativeBufferList = new JfrBufferNodeLinkedList(); } - @Uninterruptible(reason = "Accesses a JFR buffer.") @Override public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java index dfb6a0837184..5ee94008c7c7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java @@ -58,6 +58,7 @@ public static boolean isUsedPreviousEpoch(Class clazz) { long predicate = JfrTraceIdEpoch.getInstance().previousEpochBit(); return predicate(clazz, predicate); } + @Uninterruptible(reason = "Epoch must not change.") public static boolean isUsedCurrentEpoch(Class clazz) { long predicate = JfrTraceIdEpoch.getInstance().thisEpochBit(); From c41931fa3e2634d678b014cc06f7e1dc9e4af627 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 14 Nov 2022 13:30:29 -0500 Subject: [PATCH 17/72] make initialize synchronized --- .../com/oracle/svm/core/jfr/JfrThreadLocal.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index c1c8caa3f116..cd26a2c78bae 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -77,8 +77,8 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId"); private long threadLocalBufferSize; - private static JfrBufferNodeLinkedList javaBufferList; - private static JfrBufferNodeLinkedList nativeBufferList; + private static JfrBufferNodeLinkedList javaBufferList = null; + private static JfrBufferNodeLinkedList nativeBufferList = null; @Uninterruptible(reason = "Called from uninterruptible code.") public static JfrBufferNodeLinkedList getNativeBufferList() { @@ -94,10 +94,14 @@ public static JfrBufferNodeLinkedList getJavaBufferList() { public JfrThreadLocal() { } - public void initialize(long bufferSize) { + public synchronized void initialize(long bufferSize) { this.threadLocalBufferSize = bufferSize; - javaBufferList = new JfrBufferNodeLinkedList(); - nativeBufferList = new JfrBufferNodeLinkedList(); + if (javaBufferList == null) { + javaBufferList = new JfrBufferNodeLinkedList(); + } + if (nativeBufferList == null) { + nativeBufferList = new JfrBufferNodeLinkedList(); + } } @Uninterruptible(reason = "Accesses a JFR buffer.") From 2d955a37620a15bca8fe2a16f5c24944ddaa4fbc Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 14 Nov 2022 14:12:15 -0500 Subject: [PATCH 18/72] factor out buffer list initialization --- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index cd26a2c78bae..c58b9a9bedf1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -94,8 +94,12 @@ public static JfrBufferNodeLinkedList getJavaBufferList() { public JfrThreadLocal() { } - public synchronized void initialize(long bufferSize) { + public void initialize(long bufferSize) { this.threadLocalBufferSize = bufferSize; + initializeBufferLists(); + } + + public static synchronized void initializeBufferLists() { if (javaBufferList == null) { javaBufferList = new JfrBufferNodeLinkedList(); } From c4832f65d3b759942d404c49f5d118ab177cb448 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 5 Dec 2022 12:00:49 -0500 Subject: [PATCH 19/72] Support exclusion of current thread. Fix bug by reordering node removal and disk writting. --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 8 ++- .../oracle/svm/core/jfr/JfrChunkWriter.java | 67 ++++++++----------- .../oracle/svm/core/jfr/JfrThreadLocal.java | 34 ++++++++++ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 24 +++++++ .../Target_jdk_jfr_internal_EventWriter.java | 6 +- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 13 ++-- .../jfr/events/ExecuteVMOperationEvent.java | 2 +- .../jfr/events/JavaMonitorEnterEvent.java | 2 +- .../core/jfr/events/JavaMonitorWaitEvent.java | 2 +- .../core/jfr/events/SafepointBeginEvent.java | 2 +- .../core/jfr/events/SafepointEndEvent.java | 2 +- .../svm/core/jfr/events/ThreadEndEvent.java | 2 +- .../svm/core/jfr/events/ThreadParkEvent.java | 2 +- .../svm/core/jfr/events/ThreadSleepEvent.java | 2 +- .../svm/core/jfr/events/ThreadStartEvent.java | 2 +- .../core/sampler/SubstrateSigprofHandler.java | 8 ++- 16 files changed, 120 insertions(+), 58 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 2c1989a542fb..fad82c36f07d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -85,7 +85,7 @@ static int offsetOfAcquired() { @Uninterruptible(reason = "Called from uninterruptible code.") public JfrBufferNode getAndLockTail() { - VMError.guarantee(tail.isNonNull(), "^^116"); + VMError.guarantee(tail.isNonNull(), "Tail Node should never be null"); if (tryAcquire(tail)) { return tail; } @@ -166,7 +166,8 @@ public boolean lockSection(JfrBufferNode target) { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean lockAdjacent(JfrBufferNode target) { VMError.guarantee(target.isNonNull(), "Attempted to lock buffer node that is null."); - // acquire target and adjacent nodes + VMError.guarantee(isAcquired(target), "Target node should be acquired when locking adjacent nodes."); + // adjacent nodes if (target.getPrev().isNull() || acquire(target.getPrev())) { if (target.getNext().isNull() || acquire(target.getNext())) { return true; @@ -219,13 +220,14 @@ public void addNode(JfrBufferNode node) { while (!acquire(head)) { // spin until we acquire } + JfrBufferNode oldHead = head; node.setPrev(WordFactory.nullPointer()); VMError.guarantee(head.getPrev().isNull(), "Adding node: Head should be first node in JfrBufferNodeLinkedList."); node.setNext(head); head.setPrev(node); head = node; - release(head.getNext()); + release(oldHead); } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) 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 2973c8933225..1e5b9ca002a4 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 @@ -190,7 +190,7 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, * Switch to a new epoch. This is done at a safepoint to ensure that we end up with * consistent data, even if multiple threads have JFR events in progress. */ - JfrChangeEpochOperation op = new JfrChangeEpochOperation(false); + JfrChangeEpochOperation op = new JfrChangeEpochOperation(); op.enqueue(); /* @@ -512,11 +512,9 @@ public enum StringEncoding { } private class JfrChangeEpochOperation extends JavaVMOperation { - private boolean flush; - protected JfrChangeEpochOperation(boolean flush) { + protected JfrChangeEpochOperation() { super(VMOperationInfos.get(JfrChangeEpochOperation.class, "JFR change epoch", SystemEffect.SAFEPOINT)); - this.flush = flush; } @Override @@ -551,12 +549,10 @@ private void changeEpoch() { write(buffer); JfrBufferAccess.reinitialize(buffer); } - if (!flush) { - JfrTraceIdEpoch.getInstance().changeEpoch(); + JfrTraceIdEpoch.getInstance().changeEpoch(); - // Now that the epoch changed, re-register all running threads for the new epoch. - SubstrateJVM.getThreadRepo().registerRunningThreads(); - } + // Now that the epoch changed, re-register all running threads for the new epoch. + SubstrateJVM.getThreadRepo().registerRunningThreads(); } } @@ -586,48 +582,27 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool // possibly block traversal at very beginning. JfrBufferNode node = linkedList.getAndLockTail(); - if (node.isNull()) { - // If we couldn't acquire the tail. Give up, and try again next time. - return; - } + // If we couldn't acquire the tail. node is null. Give up, and try again next time. while (node.isNonNull()) { VMError.guarantee(isAcquired(node) || VMOperation.isInProgressAtSafepoint(), "Cannot traverse JfrBufferNodeLinkedList outside safepoint without acquiring nodes."); JfrBufferNode prev = node.getPrev(); - // Try to remove - if (!node.getAlive()) { - if (linkedList.removeNode(node, true)) { - // able to remove node - if (tryAcquire(prev)) { - node = prev; - continue; - } - // Failed to get next node. Give up on flush and try again later - return; - } else { - // unable to remove node + if (!linkedList.isTail(node)) { + JfrBuffer buffer = node.getValue(); + VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); + + // Try to get BUFFER if not in safepoint + // make one attempt + if (!safepoint && !JfrBufferAccess.acquire(buffer)) { if (tryAcquire(prev)) { release(node); node = prev; continue; } - // Failed to get next node. Give up on flush and try again later release(node); return; } - } - - if (!linkedList.isTail(node)) { - JfrBuffer buffer = node.getValue(); - VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); - - // Try to get BUFFER if not in safepoint - if (!safepoint && !JfrBufferAccess.acquire(buffer)) { // make one attempt - release(node); - node = prev; - continue; - } write(buffer); @@ -639,6 +614,22 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool } } + // If a node has been marked as dead, it means the thread was unable to flush upon + // death. + // So we need to flush the buffer above first, before attempting to remove the node + // here. + if (!node.getAlive()) { + if (linkedList.removeNode(node, true)) { + // able to remove node + if (tryAcquire(prev)) { + node = prev; + continue; + } + // Failed to get next node. Give up and try again later + return; + } + } + boolean prevAcquired = tryAcquire(prev); release(node); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index c58b9a9bedf1..8997b91776c2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -47,6 +47,7 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.threadlocal.FastThreadLocalObject; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; +import com.oracle.svm.core.threadlocal.FastThreadLocalInt; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; @@ -75,6 +76,7 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId"); private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId"); + private static final FastThreadLocalInt excluded = FastThreadLocalFactory.createInt("JfrThreadLocal.excluded"); private long threadLocalBufferSize; private static JfrBufferNodeLinkedList javaBufferList = null; @@ -117,6 +119,7 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { Target_java_lang_Thread t = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class); threadId.set(isolateThread, t.getId()); parentThreadId.set(isolateThread, JavaThreads.getParentThreadId(javaThread)); + excluded.set(0); SubstrateJVM.getThreadRepo().registerThread(javaThread); @@ -300,6 +303,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit VMError.guarantee(threadLocalBuffer.isNonNull(), "TLB cannot be null if promoting."); VMError.guarantee(!VMOperation.isInProgressAtSafepoint(), "Should not be promoting if at safepoint. "); + // Needed for race between streaming flush and promotion if (!acquireBufferWithRetry(threadLocalBuffer)) { return WordFactory.nullPointer(); } @@ -369,4 +373,34 @@ public void teardown() { javaBuffers.teardown(); } } + + public void exclude(Thread thread) { + if (!thread.equals(Thread.currentThread())) { + return; + } + IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); + excluded.set(currentIsolateThread, 1); + + if (javaEventWriter.get(currentIsolateThread) != null && JavaVersionUtil.JAVA_SPEC >= 19) { + javaEventWriter.get(currentIsolateThread).excluded = true; + } + } + + public void include(Thread thread) { + if (!thread.equals(Thread.currentThread())) { + return; + } + IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); + + excluded.set(currentIsolateThread, 0); + + if (javaEventWriter.get(currentIsolateThread) != null && JavaVersionUtil.JAVA_SPEC >= 19) { + javaEventWriter.get(currentIsolateThread).excluded = false; + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean isCurrentThreadExcluded() { + return excluded.get() == 1 ? true : false; + } } 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 ddf31492a25e..cd53efb625b5 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 @@ -590,6 +590,30 @@ public Object getConfiguration(Class eventClass) { return DynamicHub.fromClass(eventClass).getJfrEventConfiguration(); } + public void exclude(Thread thread) { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); + jfrThreadLocal.exclude(thread); + } + + public void include(Thread thread) { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); + jfrThreadLocal.include(thread); + } + + public boolean isExcluded(Thread thread) { + // in jdk 17 and jdk19 the argument is only ever the current thread. + if (!thread.equals(Thread.currentThread())) { + return false; + } + return isCurrentThreadExcluded(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean isCurrentThreadExcluded() { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); + return jfrThreadLocal.isCurrentThreadExcluded(); + } + private static class JfrBeginRecordingOperation extends JavaVMOperation { JfrBeginRecordingOperation() { super(VMOperationInfos.get(JfrBeginRecordingOperation.class, "JFR begin recording", SystemEffect.SAFEPOINT)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java index 16291c10bdd2..d33e8be8619d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java @@ -32,7 +32,11 @@ @TargetClass(className = "EventWriter", classNameProvider = Package_jdk_jfr_internal_event_helper.class, onlyWith = HasJfrSupport.class) public final class Target_jdk_jfr_internal_EventWriter { - @Alias @SuppressWarnings("unused") boolean notified; + @Alias // + @SuppressWarnings("unused") boolean notified; + + @Alias // + @TargetElement(onlyWith = JDK19OrLater.class) boolean excluded; @Alias @SuppressWarnings("unused") 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 f1ecc5bb318d..5c69618243a7 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 @@ -52,10 +52,12 @@ public final class Target_jdk_jfr_internal_JVM { @Alias static Object FILE_DELTA_CHANGE; // Checkstyle: resume - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private volatile boolean nativeOK; - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // @TargetElement(onlyWith = JDK11OrEarlier.class) // private volatile boolean recording; @@ -412,20 +414,19 @@ public void flush() { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void include(Thread thread) { - // Temporarily do nothing. This is used for JFR streaming. + SubstrateJVM.get().include(thread); } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void exclude(Thread thread) { - // Temporarily do nothing. This is used for JFR streaming. + SubstrateJVM.get().exclude(thread); } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public boolean isExcluded(Thread thread) { - // Temporarily do nothing. This is used for JFR streaming. - return false; + return SubstrateJVM.get().isExcluded(thread); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java index decbfb57af3c..6a5a21d3ded5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java @@ -47,7 +47,7 @@ public static void emit(VMOperation vmOperation, IsolateThread requestingThread, return; } - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ExecuteVMOperation)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ExecuteVMOperation) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(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 b2a20731fe80..fafd0836528b 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 @@ -47,7 +47,7 @@ public static void emit(Object obj, long previousOwnerTid, long startTicks) { @Uninterruptible(reason = "Accesses a JFR buffer.") public static void emit0(Object obj, long previousOwnerTid, long startTicks) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaMonitorEnter)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaMonitorEnter) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); 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 6d95c2ed6abd..be70ecf5ce98 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 @@ -45,7 +45,7 @@ public static void emit(long startTicks, Object obj, long notifier, long timeout @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaMonitorWait)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaMonitorWait) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.JavaMonitorWait); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java index 34b359c6837e..fc1ca993af1d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java @@ -53,7 +53,7 @@ public static void emit(UnsignedWord safepointId, int numJavaThreads, long start */ @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(UnsignedWord safepointId, int numJavaThreads, long startTicks) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointBegin)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointBegin) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java index 04ab808d39db..4060b86f588b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java @@ -48,7 +48,7 @@ public static void emit(UnsignedWord safepointId, long startTick) { @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(UnsignedWord safepointId, long startTick) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointEnd)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointEnd) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java index 7f8bd13eee94..dcb9052d5f4d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java @@ -39,7 +39,7 @@ public class ThreadEndEvent { @Uninterruptible(reason = "Accesses a JFR buffer.") public static void emit(IsolateThread isolateThread) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadEnd)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadEnd) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(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 2ee102f436d5..d3453ad7b770 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 @@ -46,7 +46,7 @@ public static void emit(long startTicks, Object obj, long timeout, long until) { @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(long startTicks, Object obj, long timeout, long until) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadPark)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadPark) && !SubstrateJVM.get().isCurrentThreadExcluded()) { Class parkedClass = null; if (obj != null) { parkedClass = obj.getClass(); 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/ThreadSleepEvent.java index 2c779b5db58a..29c0912abec1 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/ThreadSleepEvent.java @@ -46,7 +46,7 @@ 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(JfrEvent.ThreadSleep) && !SubstrateJVM.get().isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); 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 462de3d381e9..d0b3b06a3eb7 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 @@ -40,7 +40,7 @@ public class ThreadStartEvent { @Uninterruptible(reason = "Accesses a JFR buffer.") public static void emit(IsolateThread isolateThread) { SubstrateJVM svm = SubstrateJVM.get(); - if (SubstrateJVM.isRecording() && svm.isEnabled(JfrEvent.ThreadStart)) { + if (SubstrateJVM.isRecording() && svm.isEnabled(JfrEvent.ThreadStart) && !svm.isCurrentThreadExcluded()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); 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 aa2efa86f2e9..477c8289c9ac 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 @@ -61,6 +61,7 @@ import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.core.jfr.JfrFeature; import com.oracle.svm.core.jfr.JfrRecorderThread; +import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.stack.JavaFrameAnchor; import com.oracle.svm.core.stack.JavaFrameAnchors; @@ -178,7 +179,8 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o } }; - @SuppressWarnings("unused") @Option(help = "Start sampling-based profiling with options.")// + @SuppressWarnings("unused") // + @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) { @@ -292,6 +294,10 @@ private static boolean isIPInJavaCode(RegisterDumper.Context uContext) { @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true) protected static void doUninterruptibleStackWalk(RegisterDumper.Context uContext) { + if (SubstrateJVM.get().isCurrentThreadExcluded()) { + return; + } + CodePointer ip; Pointer sp; if (isIPInJavaCode(uContext)) { From 8bea0836fbb947dc92cdd13c94f8172f88b13cb1 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 7 Dec 2022 11:41:23 -0500 Subject: [PATCH 20/72] fix head visibility bug --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index fad82c36f07d..468a7bda78d3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -40,6 +40,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.util.UnsignedUtils; import com.oracle.svm.core.thread.VMOperation; +import org.graalvm.nativeimage.CurrentIsolate; public class JfrBufferNodeLinkedList { @RawStructure @@ -56,6 +57,12 @@ public interface JfrBufferNode extends UninterruptibleEntry { @RawField void setThread(IsolateThread thread); + @RawField + IsolateThread getLockOwner(); + + @RawField + void setLockOwner(IsolateThread owner); + @RawField boolean getAlive(); @@ -80,6 +87,8 @@ static int offsetOfAcquired() { void setPrev(UninterruptibleEntry value); } + private static final int ACQUIRED = 1; + private static final int NOT_ACQUIRED = 0; private volatile JfrBufferNode head; private JfrBufferNode tail; // this never gets deleted until torn down @@ -186,9 +195,8 @@ public boolean removeNode(JfrBufferNode node, boolean flushing) { JfrBufferNode prev = node.getPrev(); // tail must always exist until torn down VMError.guarantee(next.isNonNull(), "Attmpted to remove tail node from JfrBufferNodeLinkedList"); - VMError.guarantee(head.isNonNull(), "Head of JfrBufferNodeLinkedList must always exist."); - // make one attempt to get all the locks. If flushing, only target nodes already acquired. + // make one attempt to get all the locks. If flushing, must acquire adjacent nodes if (flushing && !VMOperation.isInProgressAtSafepoint() && !lockAdjacent(node)) { return false; } @@ -217,16 +225,27 @@ public boolean removeNode(JfrBufferNode node, boolean flushing) { @Uninterruptible(reason = "Called from uninterruptible code.") public void addNode(JfrBufferNode node) { - while (!acquire(head)) { - // spin until we acquire + JfrBufferNode oldHead; + // spin until we acquire + while (true) { + // update value from main memory + oldHead = head; + if (!acquire(oldHead)) { + continue; + } + if (!isHead(oldHead)) { + // head in main memory may have changed between setting and acquiring oldHead + release(oldHead); + continue; + } + break; } - JfrBufferNode oldHead = head; + VMError.guarantee(oldHead.getPrev().isNull(), "Adding node: Head should be first node in JfrBufferNodeLinkedList."); node.setPrev(WordFactory.nullPointer()); + node.setNext(oldHead); + oldHead.setPrev(node); - VMError.guarantee(head.getPrev().isNull(), "Adding node: Head should be first node in JfrBufferNodeLinkedList."); - node.setNext(head); - head.setPrev(node); - head = node; + setHead(node); release(oldHead); } @@ -236,7 +255,12 @@ public static boolean acquire(JfrBufferNode node) { VMError.guarantee(!isAcquired(node), "JfrBufferNodes should not be in acquired state when entering safepoints."); return true; } - return ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), 0, 1, org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); + boolean success = ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), NOT_ACQUIRED, ACQUIRED, + org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); + if (success) { + node.setLockOwner(CurrentIsolate.getCurrentThread()); + } + return success; } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) @@ -244,11 +268,13 @@ public static void release(JfrBufferNode node) { if (VMOperation.isInProgressAtSafepoint()) { return; } - node.setAcquired(0); + VMError.guarantee(node.getLockOwner() == CurrentIsolate.getCurrentThread(), "Only the lock owner can release the lock"); + VMError.guarantee(isAcquired(node), "JfrBufferNodes should only be released when in acquired state."); + node.setAcquired(NOT_ACQUIRED); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isAcquired(JfrBufferNode node) { - return node.getAcquired() == 1; + return node.getAcquired() == ACQUIRED; } } From 2162d2b93fe19bdb059dc4ff6852736fb97f0877 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 3 Jan 2023 15:54:47 -0500 Subject: [PATCH 21/72] Simplifying touch ups. Comments. --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 8 ++++--- .../oracle/svm/core/jfr/JfrChunkWriter.java | 21 ++++++------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 468a7bda78d3..93114053530e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -41,6 +41,7 @@ import com.oracle.svm.core.util.UnsignedUtils; import com.oracle.svm.core.thread.VMOperation; import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.compiler.nodes.NamedLocationIdentity; public class JfrBufferNodeLinkedList { @RawStructure @@ -91,6 +92,7 @@ static int offsetOfAcquired() { private static final int NOT_ACQUIRED = 0; private volatile JfrBufferNode head; private JfrBufferNode tail; // this never gets deleted until torn down + private static final int ACQUIRE_RETRY_COUNT = 10000; @Uninterruptible(reason = "Called from uninterruptible code.") public JfrBufferNode getAndLockTail() { @@ -103,7 +105,7 @@ public JfrBufferNode getAndLockTail() { @Uninterruptible(reason = "Called from uninterruptible code.") public static boolean tryAcquire(JfrBufferNode node) { - for (int retry = 0; retry < 10000; retry++) { + for (int retry = 0; retry < ACQUIRE_RETRY_COUNT; retry++) { if (node.isNull() || acquire(node)) { return true; } @@ -234,7 +236,7 @@ public void addNode(JfrBufferNode node) { continue; } if (!isHead(oldHead)) { - // head in main memory may have changed between setting and acquiring oldHead + // head may have been written sometime between setting and acquiring oldHead release(oldHead); continue; } @@ -256,7 +258,7 @@ public static boolean acquire(JfrBufferNode node) { return true; } boolean success = ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), NOT_ACQUIRED, ACQUIRED, - org.graalvm.compiler.nodes.NamedLocationIdentity.OFF_HEAP_LOCATION); + NamedLocationIdentity.OFF_HEAP_LOCATION); if (success) { node.setLockOwner(CurrentIsolate.getCurrentThread()); } 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 1e5b9ca002a4..f323409a00fd 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 @@ -218,10 +218,8 @@ public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, Jfr if (threadRepo.isDirty(true)) { writeThreadCheckpointEvent(threadRepo, true); } - assert lock.isHeldByCurrentThread(); SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); writeMetadataEvent(metadataDescriptor); - assert lock.isHeldByCurrentThread(); patchFileHeader(constantPoolPosition, true); newChunk = false; @@ -305,7 +303,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext - writeCompressedLong(8); + writeCompressedLong(8); // Checkpoint type is "THREADS" SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. @@ -535,12 +533,8 @@ private void changeEpoch() { // 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 - - JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); - JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); - - traverseList(javaBuffers, true, true); - traverseList(nativeBuffers, false, true); + traverseList(getJavaBufferList(), true, true); + traverseList(getNativeBufferList(), false, true); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -558,11 +552,8 @@ private void changeEpoch() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage() { - JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); - JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); - - traverseList(javaBuffers, true, false); - traverseList(nativeBuffers, false, false); + traverseList(getJavaBufferList(), true, false); + traverseList(getNativeBufferList(), false, false); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -638,7 +629,7 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool } VMError.guarantee(prev.isNull() || isAcquired(prev) || VMOperation.isInProgressAtSafepoint(), "Cannot advance in JfrBufferNodeLinkedList traversal before acquiring next node."); - node = prev; // new target is already locked + node = prev; } } From 4c6488ac6c29c6d9d266f5bdb3758ac0790900bb Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 31 Jan 2023 13:52:48 -0500 Subject: [PATCH 22/72] more fixes related to bringing up to date w master --- .../com/oracle/svm/core/jfr/JfrStackTraceRepository.java | 4 ++-- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 4 ++-- .../oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java | 7 +------ .../com/oracle/svm/core/jfr/events/SafepointEndEvent.java | 4 ---- 4 files changed, 5 insertions(+), 14 deletions(-) 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 bb9dc05cb054..932037f70d28 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 @@ -233,14 +233,14 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { @Override public int write(JfrChunkWriter writer, boolean flush) { if (flush) { - acquireLock(); + mutex.lock(); } JfrStackTraceEpochData epochData = getEpochData(!flush); int count = writeStackTraces(writer, epochData, flush); if (!flush) { epochData.clear(); } else { - releaseLock(); + mutex.unlock(); } return count; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 19a84768aa77..71c4b759768e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -152,7 +152,7 @@ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffe JfrBuffer jb = jbn.getValue(); assert jb.isNonNull() && jbn.getAlive(); - if (SubstrateJVM.isRecording()) { + if (SubstrateJVM.get().isRecording()) { if (jb.isNonNull()) { flush(jb, WordFactory.unsigned(0), 0); } @@ -166,7 +166,7 @@ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffe if (getNativeBufferList().lockSection(nbn)) { JfrBuffer nb = nbn.getValue(); assert nb.isNonNull() && nbn.getAlive(); - if (SubstrateJVM.isRecording()) { + if (SubstrateJVM.get().isRecording()) { if (nb.isNonNull()) { flush(nb, WordFactory.unsigned(0), 0); } 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 4d93c10a277d..b9efcb878a18 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 @@ -353,12 +353,6 @@ public static boolean flush(Target_jdk_jfr_internal_EventWriter writer, int unco return SubstrateJVM.get().flush(writer, uncommittedSize, requestedSize); } - /** See {@link JVM#flush}. */ - @Substitute - @TargetElement(onlyWith = JDK17OrLater.class) // - public void flush() { - // Temporarily do nothing. This is used for JFR streaming. - } /** See {@link JVM#setRepositoryLocation}. */ @Substitute @@ -507,6 +501,7 @@ public boolean isContainerized() { public void markChunkFinal() { SubstrateJVM.get().markChunkFinal(); } + @Substitute @TargetElement(onlyWith = JDK20OrLater.class) // public long hostTotalMemory() { /* Not implemented at the moment. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java index 9a532c3e729a..a84090fe11af 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java @@ -47,11 +47,7 @@ public static void emit(UnsignedWord safepointId, long startTick) { @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(UnsignedWord safepointId, long startTick) { -<<<<<<< HEAD - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointEnd) && !SubstrateJVM.get().isCurrentThreadExcluded()) { -======= if (JfrEvent.SafepointEnd.shouldEmit()) { ->>>>>>> 74db4491f2a7a81eee4828c22da73d8217457191 JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); From 08416fbcad1609ab189d4e5def5cd28848d6706e Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 31 Jan 2023 14:14:18 -0500 Subject: [PATCH 23/72] style and remove unused code --- .../oracle/svm/core/jfr/JfrThreadLocal.java | 21 ++++++++----------- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 1 - .../jfr/events/ExecuteVMOperationEvent.java | 1 + .../core/sampler/SubstrateSigprofHandler.java | 2 -- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 71c4b759768e..ade481d4cd8a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -48,7 +48,6 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalInt; import com.oracle.svm.core.util.VMError; - import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; /** @@ -80,7 +79,6 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalWord javaBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBufferNode"); private static final FastThreadLocalWord nativeBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBufferNode"); private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); - private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId"); private static final FastThreadLocalInt excluded = FastThreadLocalFactory.createInt("JfrThreadLocal.excluded"); /* Stacktrace-related thread-locals. */ @@ -147,30 +145,29 @@ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffe JfrBufferNode jbn = javaBufferNode.get(isolateThread); JfrBufferNode nbn = nativeBufferNode.get(isolateThread); - if (jbn.isNonNull()&& flushBuffers) { + if (jbn.isNonNull() && flushBuffers) { if (getJavaBufferList().lockSection(jbn)) { JfrBuffer jb = jbn.getValue(); assert jb.isNonNull() && jbn.getAlive(); - if (SubstrateJVM.get().isRecording()) { - if (jb.isNonNull()) { - flush(jb, WordFactory.unsigned(0), 0); - } + if (jb.isNonNull()) { + flush(jb, WordFactory.unsigned(0), 0); } + getJavaBufferList().removeNode(jbn, false); // also releases locks } else { jbn.setAlive(false); } } - if (nbn.isNonNull()&& flushBuffers) { + if (nbn.isNonNull() && flushBuffers) { if (getNativeBufferList().lockSection(nbn)) { JfrBuffer nb = nbn.getValue(); assert nb.isNonNull() && nbn.getAlive(); - if (SubstrateJVM.get().isRecording()) { - if (nb.isNonNull()) { - flush(nb, WordFactory.unsigned(0), 0); - } + + if (nb.isNonNull()) { + flush(nb, WordFactory.unsigned(0), 0); } + getNativeBufferList().removeNode(nbn, false); } else { nbn.setAlive(false); 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 5032b3c2c2c4..bf25ca537926 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 @@ -540,7 +540,6 @@ public void markChunkFinal() { } } - /** * See {@link JVM#setRepositoryLocation}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java index 30876135cb55..931ad313e278 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java @@ -43,6 +43,7 @@ public static void emit(VMOperation vmOperation, long requestingThreadId, long s if (!HasJfrSupport.get()) { return; } + emit0(vmOperation, requestingThreadId, startTicks); } 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 14a89ddd0992..6a3ce192bc00 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 @@ -42,7 +42,6 @@ import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode; import com.oracle.svm.core.graal.nodes.WriteHeapBaseNode; - import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.sampler.AbstractJfrExecutionSampler; import com.oracle.svm.core.thread.ThreadListener; @@ -186,7 +185,6 @@ private static void uninstall(IsolateThread thread) { protected abstract void setNativeThreadLocalValue(UnsignedWord key, IsolateThread value); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected abstract IsolateThread getNativeThreadLocalValue(UnsignedWord key); /** From 3602040541b6adb10850e6521788376ade3bc943 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 31 Jan 2023 21:54:01 -0500 Subject: [PATCH 24/72] style fix intermittent segfault due to notifying event writer --- .../com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java | 3 +-- .../src/com/oracle/svm/core/jfr/JfrChunkWriter.java | 8 +++++--- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 5 ----- .../oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java | 8 +++++--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 93114053530e..c1fd29785d1f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -270,13 +270,12 @@ public static void release(JfrBufferNode node) { if (VMOperation.isInProgressAtSafepoint()) { return; } - VMError.guarantee(node.getLockOwner() == CurrentIsolate.getCurrentThread(), "Only the lock owner can release the lock"); VMError.guarantee(isAcquired(node), "JfrBufferNodes should only be released when in acquired state."); node.setAcquired(NOT_ACQUIRED); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isAcquired(JfrBufferNode node) { - return node.getAcquired() == ACQUIRED; + return node.getAcquired() == ACQUIRED && node.getLockOwner() == CurrentIsolate.getCurrentThread(); } } 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 6a6b5ca19dcd..8594019613ce 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 @@ -610,6 +610,7 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool // If we couldn't acquire the tail. node is null. Give up, and try again next time. while (node.isNonNull()) { + boolean writeSuccess = false; VMError.guarantee(isAcquired(node) || VMOperation.isInProgressAtSafepoint(), "Cannot traverse JfrBufferNodeLinkedList outside safepoint without acquiring nodes."); JfrBufferNode prev = node.getPrev(); @@ -630,13 +631,11 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool } write(buffer); + writeSuccess = true; if (!safepoint) { JfrBufferAccess.release(buffer); } - if (java) { - JfrThreadLocal.notifyEventWriter(node.getThread()); - } } // If a node has been marked as dead, it means the thread was unable to flush upon @@ -653,6 +652,9 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool // Failed to get next node. Give up and try again later return; } + } else if (writeSuccess && java) { + // Only notify Java event writer of flush if the thread is still alive + JfrThreadLocal.notifyEventWriter(node.getThread()); } boolean prevAcquired = tryAcquire(prev); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index ade481d4cd8a..0fb31bb56468 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -362,11 +362,6 @@ private static UnsignedWord increaseDataLost(UnsignedWord delta) { return result; } - @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void freeBuffer(JfrBuffer buffer) { - JfrBufferAccess.free(buffer); - } - public void teardown() { JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); if (nativeBuffers != null) { 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 b9efcb878a18..a47631789b77 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 @@ -52,10 +52,12 @@ @TargetClass(value = jdk.jfr.internal.JVM.class, onlyWith = HasJfrSupport.class) public final class Target_jdk_jfr_internal_JVM { // Checkstyle: stop - @Alias @TargetElement(onlyWith = JDK20OrLater.class) // + @Alias + @TargetElement(onlyWith = JDK20OrLater.class) // static Object CHUNK_ROTATION_MONITOR; - @Alias @TargetElement(onlyWith = JDK19OrEarlier.class) // + @Alias + @TargetElement(onlyWith = JDK19OrEarlier.class) // static Object FILE_DELTA_CHANGE; // Checkstyle: resume @@ -353,7 +355,6 @@ public static boolean flush(Target_jdk_jfr_internal_EventWriter writer, int unco return SubstrateJVM.get().flush(writer, uncommittedSize, requestedSize); } - /** See {@link JVM#setRepositoryLocation}. */ @Substitute public void setRepositoryLocation(String dirText) { @@ -501,6 +502,7 @@ public boolean isContainerized() { public void markChunkFinal() { SubstrateJVM.get().markChunkFinal(); } + @Substitute @TargetElement(onlyWith = JDK20OrLater.class) // public long hostTotalMemory() { From 1fcceaa87ad0ed32900afbfffec096224bbbde49 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 1 Feb 2023 09:47:31 -0500 Subject: [PATCH 25/72] style, formatting, comments --- .../com/oracle/svm/core/jfr/JfrChunkWriter.java | 14 +++++--------- .../com/oracle/svm/core/jfr/JfrThreadLocal.java | 4 ---- .../svm/core/jfr/Target_jdk_jfr_internal_JVM.java | 4 ++-- 3 files changed, 7 insertions(+), 15 deletions(-) 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 8594019613ce..4ed9f6582f1b 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 @@ -603,8 +603,8 @@ private void flushStorage() { @Uninterruptible(reason = "Called from uninterruptible code.") private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { - // Traverse back to front to minimize conflict with threads adding new nodes. Which could - // possibly block traversal at very beginning. + // Traverse back to front to minimize conflict with threads adding new nodes. + // Which could possibly block traversal early at the head. JfrBufferNode node = linkedList.getAndLockTail(); // If we couldn't acquire the tail. node is null. Give up, and try again next time. @@ -629,19 +629,15 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool release(node); return; } - write(buffer); writeSuccess = true; - if (!safepoint) { JfrBufferAccess.release(buffer); } } - // If a node has been marked as dead, it means the thread was unable to flush upon - // death. - // So we need to flush the buffer above first, before attempting to remove the node - // here. + // If a node has been marked dead, the thread was unable to flush upon death. + // So we need to flush the buffer above before attempting to remove the node here. if (!node.getAlive()) { if (linkedList.removeNode(node, true)) { // able to remove node @@ -653,7 +649,7 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool return; } } else if (writeSuccess && java) { - // Only notify Java event writer of flush if the thread is still alive + // Only notify event writer of flush if the thread (and its locals) is still alive JfrThreadLocal.notifyEventWriter(node.getThread()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 0fb31bb56468..19167f652bb8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -149,11 +149,9 @@ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffe if (getJavaBufferList().lockSection(jbn)) { JfrBuffer jb = jbn.getValue(); assert jb.isNonNull() && jbn.getAlive(); - if (jb.isNonNull()) { flush(jb, WordFactory.unsigned(0), 0); } - getJavaBufferList().removeNode(jbn, false); // also releases locks } else { jbn.setAlive(false); @@ -163,11 +161,9 @@ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffe if (getNativeBufferList().lockSection(nbn)) { JfrBuffer nb = nbn.getValue(); assert nb.isNonNull() && nbn.getAlive(); - if (nb.isNonNull()) { flush(nb, WordFactory.unsigned(0), 0); } - getNativeBufferList().removeNode(nbn, false); } else { nbn.setAlive(false); 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 a47631789b77..cc0adf551dc8 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 @@ -52,11 +52,11 @@ @TargetClass(value = jdk.jfr.internal.JVM.class, onlyWith = HasJfrSupport.class) public final class Target_jdk_jfr_internal_JVM { // Checkstyle: stop - @Alias + @Alias // @TargetElement(onlyWith = JDK20OrLater.class) // static Object CHUNK_ROTATION_MONITOR; - @Alias + @Alias // @TargetElement(onlyWith = JDK19OrEarlier.class) // static Object FILE_DELTA_CHANGE; // Checkstyle: resume From d029bfbf1dcfb34bafc76da3707dbf4cd19305a0 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 7 Feb 2023 11:12:07 -0500 Subject: [PATCH 26/72] change LL to be less complex --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 198 ++++-------------- .../oracle/svm/core/jfr/JfrChunkWriter.java | 95 ++++----- .../svm/core/jfr/JfrMethodRepository.java | 42 ++-- .../svm/core/jfr/JfrStackTraceRepository.java | 28 ++- .../svm/core/jfr/JfrSymbolRepository.java | 51 +++-- .../oracle/svm/core/jfr/JfrThreadLocal.java | 57 ++--- .../svm/core/jfr/JfrThreadRepository.java | 31 ++- 7 files changed, 187 insertions(+), 315 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index c1fd29785d1f..ae63316d73e4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -27,42 +27,40 @@ package com.oracle.svm.core.jfr; import com.oracle.svm.core.Uninterruptible; +import jdk.internal.misc.Unsafe; import org.graalvm.word.WordFactory; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.RawFieldOffset; import org.graalvm.nativeimage.c.struct.SizeOf; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.jdk.UninterruptibleEntry; import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.util.UnsignedUtils; -import com.oracle.svm.core.thread.VMOperation; -import org.graalvm.nativeimage.CurrentIsolate; -import org.graalvm.compiler.nodes.NamedLocationIdentity; +import org.graalvm.word.PointerBase; +import com.oracle.svm.core.thread.SpinLockUtils; public class JfrBufferNodeLinkedList { @RawStructure - public interface JfrBufferNode extends UninterruptibleEntry { + public interface JfrBufferNode extends PointerBase { @RawField - JfrBuffer getValue(); + JfrBufferNode getNext(); @RawField - void setValue(JfrBuffer value); + void setNext(JfrBufferNode value); @RawField - IsolateThread getThread(); + JfrBuffer getValue(); @RawField - void setThread(IsolateThread thread); + void setValue(JfrBuffer value); @RawField - IsolateThread getLockOwner(); + IsolateThread getThread(); @RawField - void setLockOwner(IsolateThread owner); + void setThread(IsolateThread thread); @RawField boolean getAlive(); @@ -70,53 +68,19 @@ public interface JfrBufferNode extends UninterruptibleEntry { @RawField void setAlive(boolean alive); - @RawField - int getAcquired(); - - @RawField - void setAcquired(int value); - - @RawFieldOffset - static int offsetOfAcquired() { - throw VMError.unimplemented(); // replaced - } - - @RawField - T getPrev(); - - @RawField - void setPrev(UninterruptibleEntry value); } - private static final int ACQUIRED = 1; - private static final int NOT_ACQUIRED = 0; - private volatile JfrBufferNode head; - private JfrBufferNode tail; // this never gets deleted until torn down - private static final int ACQUIRE_RETRY_COUNT = 10000; - - @Uninterruptible(reason = "Called from uninterruptible code.") - public JfrBufferNode getAndLockTail() { - VMError.guarantee(tail.isNonNull(), "Tail Node should never be null"); - if (tryAcquire(tail)) { - return tail; - } - return WordFactory.nullPointer(); - } + private static final long LOCK_OFFSET; - @Uninterruptible(reason = "Called from uninterruptible code.") - public static boolean tryAcquire(JfrBufferNode node) { - for (int retry = 0; retry < ACQUIRE_RETRY_COUNT; retry++) { - if (node.isNull() || acquire(node)) { - return true; - } + static { + try { + LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(JfrBufferNodeLinkedList.class.getDeclaredField("lock")); + } catch (Throwable ex) { + throw VMError.shouldNotReachHere(ex); } - return false; - } - - @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean isTail(JfrBufferNode node) { - return node == tail; } + private volatile int lock; + private volatile JfrBufferNode head; @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isHead(JfrBufferNode node) { @@ -125,7 +89,6 @@ public boolean isHead(JfrBufferNode node) { @Uninterruptible(reason = "Called from uninterruptible code.") private void setHead(JfrBufferNode node) { - VMError.guarantee(isAcquired(head) || com.oracle.svm.core.thread.VMOperation.isInProgressAtSafepoint(), "Cannot set JfrBufferNodeLinkedList head before acquiring."); head = node; } @@ -140,142 +103,65 @@ public static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { node.setAlive(true); node.setValue(buffer); node.setThread(thread); - node.setPrev(WordFactory.nullPointer()); node.setNext(WordFactory.nullPointer()); - node.setAcquired(0); return node; } public JfrBufferNodeLinkedList() { - tail = createNode(WordFactory.nullPointer(), WordFactory.nullPointer()); - head = tail; + head = WordFactory.nullPointer(); } public void teardown() { - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(tail); - } - - @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean lockSection(JfrBufferNode target) { - VMError.guarantee(target.isNonNull(), "Attempted to lock buffer node that is null."); - // acquire target and adjacent nodes - if (acquire(target)) { - if (target.getPrev().isNull() || acquire(target.getPrev())) { - if (target.getNext().isNull() || acquire(target.getNext())) { - return true; - } - // couldn't acquire all three locks. So release all of them. - if (target.getPrev().isNonNull()) { - release(target.getPrev()); - } - } - release(target); - } - return false; + // TODO: maybe iterate list freeing nodes, just in case. } @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean lockAdjacent(JfrBufferNode target) { - VMError.guarantee(target.isNonNull(), "Attempted to lock buffer node that is null."); - VMError.guarantee(isAcquired(target), "Target node should be acquired when locking adjacent nodes."); - // adjacent nodes - if (target.getPrev().isNull() || acquire(target.getPrev())) { - if (target.getNext().isNull() || acquire(target.getNext())) { - return true; - } - // couldn't acquire all three locks. So release all of them. - if (target.getPrev().isNonNull()) { - release(target.getPrev()); - } - } - return false; + public JfrBufferNode getAndLockHead() { + acquireList(); + return head; } @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean removeNode(JfrBufferNode node, boolean flushing) { + public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { JfrBufferNode next = node.getNext(); // next can never be null - JfrBufferNode prev = node.getPrev(); - // tail must always exist until torn down - VMError.guarantee(next.isNonNull(), "Attmpted to remove tail node from JfrBufferNodeLinkedList"); - // make one attempt to get all the locks. If flushing, must acquire adjacent nodes - if (flushing && !VMOperation.isInProgressAtSafepoint() && !lockAdjacent(node)) { - return false; - } - VMError.guarantee((isAcquired(node) && isAcquired(next)) || VMOperation.isInProgressAtSafepoint(), "Cannot remove JfrBufferNodeLinkedList node outside safepoint without acquiring section."); if (isHead(node)) { - VMError.guarantee(prev.isNull(), "Head should be first node in JfrBufferNodeLinkedList."); + VMError.guarantee(prev.isNull(), "If head, prev should be null "); setHead(next); // head could now be tail if there was only one node in the list - head.setPrev(WordFactory.nullPointer()); } else { - VMError.guarantee(isAcquired(prev) || VMOperation.isInProgressAtSafepoint(), "Cannot remove JfrBufferNodeLinkedList node outside safepoint without acquiring prev."); + VMError.guarantee(prev.isNonNull(), "If not head, prev should be non-null "); prev.setNext(next); - next.setPrev(prev); } VMError.guarantee(node.getValue().isNonNull(), "JFR buffer should always exist until removal of respective JfrBufferNodeLinkedList node."); JfrBufferAccess.free(node.getValue()); - release(node); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); - - release(next); - if (prev.isNonNull()) { - release(prev); - } return true; } @Uninterruptible(reason = "Called from uninterruptible code.") public void addNode(JfrBufferNode node) { - JfrBufferNode oldHead; - // spin until we acquire - while (true) { - // update value from main memory - oldHead = head; - if (!acquire(oldHead)) { - continue; + acquireList(); + try { + JfrBufferNode oldHead = head; + if (oldHead.isNull()) { + node.setNext(WordFactory.nullPointer()); + setHead(node); } - if (!isHead(oldHead)) { - // head may have been written sometime between setting and acquiring oldHead - release(oldHead); - continue; - } - break; - } - VMError.guarantee(oldHead.getPrev().isNull(), "Adding node: Head should be first node in JfrBufferNodeLinkedList."); - node.setPrev(WordFactory.nullPointer()); - node.setNext(oldHead); - oldHead.setPrev(node); - - setHead(node); - release(oldHead); - } - - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static boolean acquire(JfrBufferNode node) { - if (VMOperation.isInProgressAtSafepoint()) { - VMError.guarantee(!isAcquired(node), "JfrBufferNodes should not be in acquired state when entering safepoints."); - return true; - } - boolean success = ((org.graalvm.word.Pointer) node).logicCompareAndSwapInt(JfrBufferNode.offsetOfAcquired(), NOT_ACQUIRED, ACQUIRED, - NamedLocationIdentity.OFF_HEAP_LOCATION); - if (success) { - node.setLockOwner(CurrentIsolate.getCurrentThread()); + node.setNext(oldHead); + setHead(node); + } finally { + releaseList(); } - return success; } - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static void release(JfrBufferNode node) { - if (VMOperation.isInProgressAtSafepoint()) { - return; - } - VMError.guarantee(isAcquired(node), "JfrBufferNodes should only be released when in acquired state."); - node.setAcquired(NOT_ACQUIRED); + @Uninterruptible(reason = "Called from uninterruptible code.") + private void acquireList() { + SpinLockUtils.lockNoTransition(this, LOCK_OFFSET); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isAcquired(JfrBufferNode node) { - return node.getAcquired() == ACQUIRED && node.getLockOwner() == CurrentIsolate.getCurrentThread(); + @Uninterruptible(reason = "Called from uninterruptible code.") + public void releaseList() { + SpinLockUtils.unlock(this, LOCK_OFFSET); } } 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 4ed9f6582f1b..e56ecf24ced6 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 @@ -50,9 +50,6 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; -import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.release; -import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.tryAcquire; -import static com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.isAcquired; import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; @@ -74,7 +71,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { public static final long METADATA_TYPE_ID = 0; public static final long CONSTANT_POOL_TYPE_ID = 1; - + private static final byte COMPLETE = 0; private final JfrGlobalMemory globalMemory; private final ReentrantLock lock; private final boolean compressedInts; @@ -85,9 +82,6 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private long chunkStartTicks; private long chunkStartNanos; private byte generation; - private static final byte COMPLETE = 0; - private static final byte MAX_BYTE = 127; - public long lastCheckpointOffset = 0; private int lastMetadataId = 0; @@ -287,10 +281,10 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint } private byte nextGeneration() { - if (generation == MAX_BYTE) { + if (generation == Byte.MAX_VALUE) { // similar to Hotspot, restart counter if required. generation = 1; - return MAX_BYTE; + return Byte.MAX_VALUE; } return generation++; } @@ -538,8 +532,8 @@ private void changeEpoch() { * *not* reinitialize the thread-local buffers as the individual threads will handle * space reclamation on their own time. */ - traverseList(getJavaBufferList(), true, true); - traverseList(getNativeBufferList(), false, true); + traverseList(getJavaBufferList(), true); + traverseList(getNativeBufferList(), false); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -586,8 +580,8 @@ private void processSamplerBuffers0() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage() { - traverseList(getJavaBufferList(), true, false); - traverseList(getNativeBufferList(), false, false); + traverseList(getJavaBufferList(), true); + traverseList(getNativeBufferList(), false); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -602,66 +596,49 @@ private void flushStorage() { } @Uninterruptible(reason = "Called from uninterruptible code.") - private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { + private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java) { // Traverse back to front to minimize conflict with threads adding new nodes. // Which could possibly block traversal early at the head. - JfrBufferNode node = linkedList.getAndLockTail(); - // If we couldn't acquire the tail. node is null. Give up, and try again next time. + boolean firstIteration = true; + JfrBufferNode node = linkedList.getAndLockHead(); + JfrBufferNode prev = WordFactory.nullPointer(); while (node.isNonNull()) { - boolean writeSuccess = false; - VMError.guarantee(isAcquired(node) || VMOperation.isInProgressAtSafepoint(), "Cannot traverse JfrBufferNodeLinkedList outside safepoint without acquiring nodes."); - JfrBufferNode prev = node.getPrev(); - - if (!linkedList.isTail(node)) { + try { + JfrBufferNode next = node.getNext(); JfrBuffer buffer = node.getValue(); VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); - // Try to get BUFFER if not in safepoint - // make one attempt - if (!safepoint && !JfrBufferAccess.acquire(buffer)) { - if (tryAcquire(prev)) { - release(node); - node = prev; - continue; - } - release(node); - return; + // Try to get BUFFER with one attempt + if (!JfrBufferAccess.acquire(buffer)) { + prev = node; + node = next; } write(buffer); - writeSuccess = true; - if (!safepoint) { - JfrBufferAccess.release(buffer); - } - } - // If a node has been marked dead, the thread was unable to flush upon death. - // So we need to flush the buffer above before attempting to remove the node here. - if (!node.getAlive()) { - if (linkedList.removeNode(node, true)) { - // able to remove node - if (tryAcquire(prev)) { - node = prev; - continue; - } - // Failed to get next node. Give up and try again later - return; - } - } else if (writeSuccess && java) { - // Only notify event writer of flush if the thread (and its locals) is still alive - JfrThreadLocal.notifyEventWriter(node.getThread()); - } + JfrBufferAccess.release(buffer); - boolean prevAcquired = tryAcquire(prev); - release(node); - - if (!prevAcquired) { - return; + // If a node has been marked dead, the thread was unable to flush upon death. + // So we need to flush the buffer above before attempting to remove the node here. + if (!node.getAlive()) { + linkedList.removeNode(node, prev); + // if removed current node, should not update prev. + } else { + prev = node; + } + node = next; + } finally { + if (firstIteration) { + linkedList.releaseList(); // hold onto lock until done with head. + firstIteration = false; + } } + } - VMError.guarantee(prev.isNull() || isAcquired(prev) || VMOperation.isInProgressAtSafepoint(), "Cannot advance in JfrBufferNodeLinkedList traversal before acquiring next node."); - node = prev; + // we may never have entered the while loop if the list is empty. + if (firstIteration) { + linkedList.releaseList(); // hold onto lock until done with head. } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 78862f14fdf4..c12e3c7a98c2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -43,16 +43,6 @@ public class JfrMethodRepository implements JfrConstantPool { private final JfrMethodEpochData epochData0; private final JfrMethodEpochData epochData1; - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void acquireLock() { - mutex.lockNoTransition(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void releaseLock() { - mutex.unlock(); - } - @Platforms(Platform.HOSTED_ONLY.class) public JfrMethodRepository() { this.epochData0 = new JfrMethodEpochData(); @@ -117,19 +107,31 @@ private long getMethodId0(Class clazz, String methodName, int methodId) { return methodId; } - @Override - public int write(JfrChunkWriter writer, boolean flush) { + private void maybeLock(boolean flush) { if (flush) { - acquireLock(); + mutex.lock(); } - JfrMethodEpochData epochData = getEpochData(!flush); - int count = writeMethods(writer, epochData, flush); - if (!flush) { - epochData.clear(); - } else { - releaseLock(); + } + + private void maybeUnlock(boolean flush) { + if (flush) { + mutex.unlock(); + } + } + + @Override + public int write(JfrChunkWriter writer, boolean flush) { + maybeLock(flush); + try { + JfrMethodEpochData epochData = getEpochData(!flush); + int count = writeMethods(writer, epochData, flush); + if (!flush) { + epochData.clear(); + } + return count; + } finally { + maybeUnlock(flush); } - return count; } private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData, boolean flush) { 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 932037f70d28..e15ca4bdb7aa 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 @@ -230,19 +230,31 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { } } - @Override - public int write(JfrChunkWriter writer, boolean flush) { + private void maybeLock(boolean flush) { if (flush) { mutex.lock(); } - JfrStackTraceEpochData epochData = getEpochData(!flush); - int count = writeStackTraces(writer, epochData, flush); - if (!flush) { - epochData.clear(); - } else { + } + + private void maybeUnlock(boolean flush) { + if (flush) { mutex.unlock(); } - return count; + } + + @Override + public int write(JfrChunkWriter writer, boolean flush) { + maybeLock(flush); + try { + JfrStackTraceEpochData epochData = getEpochData(!flush); + int count = writeStackTraces(writer, epochData, flush); + if (!flush) { + epochData.clear(); + } + return count; + } finally { + maybeUnlock(flush); + } } private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData, boolean flush) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 101787e30265..dd374f41e4f8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -112,31 +112,46 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r return 0; } + private void maybeLock(boolean flush) { + if (flush) { + mutex.lock(); + } + } + private void maybeUnlock(boolean flush) { + if (flush) { + mutex.unlock(); + } + } + @Override public int write(JfrChunkWriter writer, boolean flush) { JfrSymbolHashtable table = getTable(!flush); - - if (table.getSize() == 0) { - return EMPTY; - } - writer.writeCompressedLong(JfrType.Symbol.getId()); - writer.writeCompressedLong(table.getSize()); - - JfrSymbol[] entries = table.getTable(); - for (int i = 0; i < entries.length; i++) { - JfrSymbol entry = entries[i]; - if (entry.isNonNull()) { - while (entry.isNonNull()) { - writeSymbol(writer, entry); - entry = entry.getNext(); + maybeLock(flush); + try { + if (table.getSize() == 0) { + return EMPTY; + } + writer.writeCompressedLong(JfrType.Symbol.getId()); + writer.writeCompressedLong(table.getSize()); + + JfrSymbol[] entries = table.getTable(); + for (int i = 0; i < entries.length; i++) { + JfrSymbol entry = entries[i]; + if (entry.isNonNull()) { + while (entry.isNonNull()) { + writeSymbol(writer, entry); + entry = entry.getNext(); + } } } + table.clear(); // *** should be cleared only after epoch change + return NON_EMPTY; + } finally { + maybeUnlock(flush); } - table.clear(); - return NON_EMPTY; } - private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { + private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { // *** only write if not serialized before. Set serialized writer.writeCompressedLong(symbol.getId()); writer.writeByte(JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); byte[] value = symbol.getValue().getBytes(StandardCharsets.UTF_8); @@ -156,7 +171,7 @@ private static void replaceDotWithSlash(byte[] utf8String) { } @RawStructure - private interface JfrSymbol extends UninterruptibleEntry { + private interface JfrSymbol extends UninterruptibleEntry { // *** add field for isSerialized? @RawField long getId(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 19167f652bb8..4dfd6415a474 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -146,28 +146,17 @@ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffe JfrBufferNode nbn = nativeBufferNode.get(isolateThread); if (jbn.isNonNull() && flushBuffers) { - if (getJavaBufferList().lockSection(jbn)) { - JfrBuffer jb = jbn.getValue(); - assert jb.isNonNull() && jbn.getAlive(); - if (jb.isNonNull()) { - flush(jb, WordFactory.unsigned(0), 0); - } - getJavaBufferList().removeNode(jbn, false); // also releases locks - } else { - jbn.setAlive(false); - } + JfrBuffer jb = jbn.getValue(); + assert jb.isNonNull() && jbn.getAlive(); + flush(jb, WordFactory.unsigned(0), 0); + jbn.setAlive(false); + } if (nbn.isNonNull() && flushBuffers) { - if (getNativeBufferList().lockSection(nbn)) { - JfrBuffer nb = nbn.getValue(); - assert nb.isNonNull() && nbn.getAlive(); - if (nb.isNonNull()) { - flush(nb, WordFactory.unsigned(0), 0); - } - getNativeBufferList().removeNode(nbn, false); - } else { - nbn.setAlive(false); - } + JfrBuffer nb = nbn.getValue(); + assert nb.isNonNull() && nbn.getAlive(); + flush(nb, WordFactory.unsigned(0), 0); + nbn.setAlive(false); } /* Clear event-related thread-locals. */ @@ -227,21 +216,6 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { return result; } - @Uninterruptible(reason = "Accesses a JFR buffer.") - private static UnsignedWord getHeaderSize() { - return com.oracle.svm.core.util.UnsignedUtils.roundUp(org.graalvm.nativeimage.c.struct.SizeOf.unsigned(JfrBufferNode.class), - WordFactory.unsigned(com.oracle.svm.core.config.ConfigurationValues.getTarget().wordSize)); - } - - @Uninterruptible(reason = "Called from uninterruptible code.") - private static JfrBufferNode allocate(com.oracle.svm.core.jfr.JfrBuffer buffer) { - JfrBufferNode node = org.graalvm.nativeimage.ImageSingletons.lookup(org.graalvm.nativeimage.impl.UnmanagedMemorySupport.class).malloc(getHeaderSize()); - VMError.guarantee(node.isNonNull()); - node.setValue(buffer); - node.setAlive(true); - return node; - } - @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { JfrBufferNode result = javaBufferNode.get(); @@ -289,13 +263,10 @@ public static void notifyEventWriter(IsolateThread thread) { } @Uninterruptible(reason = "Epoch must not change while in this method.") - private static boolean acquireBufferWithRetry(JfrBuffer buffer) { - for (int retry = 0; retry < 100000; retry++) { - if (JfrBufferAccess.acquire(buffer)) { - return true; - } + private static void acquireBufferWithRetry(JfrBuffer buffer) { + while (!JfrBufferAccess.acquire(buffer)) { + } - return false; } @Uninterruptible(reason = "Accesses a JFR buffer.") @@ -304,9 +275,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit VMError.guarantee(!VMOperation.isInProgressAtSafepoint(), "Should not be promoting if at safepoint. "); // Needed for race between streaming flush and promotion - if (!acquireBufferWithRetry(threadLocalBuffer)) { - return WordFactory.nullPointer(); - } + acquireBufferWithRetry(threadLocalBuffer); UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index d3b736fac1ba..3f24c492a517 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -188,23 +188,34 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { return epoch ? epochData0 : epochData1; } + private void maybeLock(boolean flush) { + if (flush) { + mutex.lock(); + } + } + + private void maybeUnlock(boolean flush) { + if (flush) { + mutex.unlock(); + } + } + @Override public int write(JfrChunkWriter writer, boolean flush) { JfrThreadEpochData epochData = getEpochData(!flush); - if (flush) { - mutex.lock(); // only required when possibility of read/write same epoch data - } - int count = writeThreads(writer, epochData); - count += writeThreadGroups(writer, epochData); + maybeLock(flush); + try { + int count = writeThreads(writer, epochData); + count += writeThreadGroups(writer, epochData); - epochData.clear(); + epochData.clear(); - epochData.isDirty = false; + epochData.isDirty = false; - if (flush) { - mutex.unlock(); + return count; + } finally { + maybeUnlock(flush); } - return count; } public boolean isDirty(boolean flush) { From f39b69d91c8c91a1c108f606b09aab76ec4e0c2c Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 7 Feb 2023 12:54:13 -0500 Subject: [PATCH 27/72] add JfrMetadata class to simplify things --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 64 ++++++++----------- .../com/oracle/svm/core/jfr/JfrMetadata.java | 40 ++++++++++++ .../oracle/svm/core/jfr/JfrThreadLocal.java | 2 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 15 ++--- 4 files changed, 75 insertions(+), 46 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java 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 e56ecf24ced6..65fcb043505d 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 @@ -75,6 +75,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private final JfrGlobalMemory globalMemory; private final ReentrantLock lock; private final boolean compressedInts; + private final JfrMetadata metadata; private long notificationThreshold; private String filename; @@ -82,23 +83,16 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private long chunkStartTicks; private long chunkStartNanos; private byte generation; - public long lastCheckpointOffset = 0; - - private int lastMetadataId = 0; - private int currentMetadataId = 0; + public SignedWord lastCheckpointOffset; private boolean newChunk = true; private boolean isFinal = false; - private SignedWord metadataPosition; - - public void setCurrentMetadataId() { - currentMetadataId++; - } @Platforms(Platform.HOSTED_ONLY.class) - public JfrChunkWriter(JfrGlobalMemory globalMemory) { + public JfrChunkWriter(JfrGlobalMemory globalMemory, JfrMetadata metadata) { this.lock = new ReentrantLock(); this.compressedInts = true; this.globalMemory = globalMemory; + this.metadata = metadata; } @Override @@ -135,11 +129,6 @@ public void maybeOpenFile() { } } - public void markChunkFinal() { - assert lock.isHeldByCurrentThread(); - isFinal = true; - } - public boolean openFile(String outputFile) { assert lock.isHeldByCurrentThread(); isFinal = false; @@ -150,7 +139,7 @@ public boolean openFile(String outputFile) { filename = outputFile; fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); writeFileHeader(); - lastCheckpointOffset = -1; // must reset this on new chunk + lastCheckpointOffset = WordFactory.signed(-1); // must reset this on new chunk return true; } @@ -181,7 +170,7 @@ public boolean write(JfrBuffer buffer, boolean reset) { /** * Write all the in-memory data to the file. */ - public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { + public void closeFile(JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread(); /* * Switch to a new epoch. This is done at a safepoint to ensure that we end up with @@ -199,7 +188,7 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, writeThreadCheckpointEvent(threadRepo, false); } SignedWord constantPoolPosition = writeCheckpointEvent(repositories, false); - writeMetadataEvent(metadataDescriptor); + writeMetadataEvent(); patchFileHeader(constantPoolPosition); getFileSupport().close(fd); @@ -208,7 +197,7 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories, newChunk = false; } - public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { + public void flush(JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { assert lock.isHeldByCurrentThread(); flushStorage(); @@ -216,7 +205,7 @@ public void flush(byte[] metadataDescriptor, JfrConstantPool[] repositories, Jfr writeThreadCheckpointEvent(threadRepo, true); } SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); - writeMetadataEvent(metadataDescriptor); + writeMetadataEvent(); patchFileHeader(constantPoolPosition, true); newChunk = false; @@ -258,7 +247,7 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); getFileSupport().writeLong(fd, chunkSize); getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); - getFileSupport().writeLong(fd, metadataPosition.rawValue()); + getFileSupport().writeLong(fd, metadata.getMetadataPosition().rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); @@ -293,13 +282,13 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea SignedWord start = beginEvent(); - if (lastCheckpointOffset < 0) { - lastCheckpointOffset = start.rawValue(); + if (lastCheckpointOffset.lessThan(0)) { + lastCheckpointOffset = start; } writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext + writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); // deltaToNext writeCompressedLong(8); // Checkpoint type is "THREADS" SignedWord poolCountPos = getFileSupport().position(fd); @@ -312,7 +301,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); endEvent(start); - lastCheckpointOffset = start.rawValue(); + lastCheckpointOffset = start; return start; } @@ -321,13 +310,13 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean assert lock.isHeldByCurrentThread(); SignedWord start = beginEvent(); - if (lastCheckpointOffset < 0) { - lastCheckpointOffset = start.rawValue(); + if (lastCheckpointOffset.lessThan(0)) { + lastCheckpointOffset = start; } writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(lastCheckpointOffset - start.rawValue()); // deltaToNext + writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); // deltaToNext writeBoolean(true); // flush SignedWord poolCountPos = getFileSupport().position(fd); @@ -345,7 +334,7 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); endEvent(start); - lastCheckpointOffset = start.rawValue(); + lastCheckpointOffset = start; return start; } @@ -359,22 +348,20 @@ private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { return count; } - private void writeMetadataEvent(byte[] metadataDescriptor) { + private void writeMetadataEvent() { assert lock.isHeldByCurrentThread(); // always write metadata on a new chunk! - if (currentMetadataId != lastMetadataId || newChunk) { - lastMetadataId = currentMetadataId; - } else { + if (!metadata.isDirty() && !newChunk) { return; } SignedWord start = beginEvent(); writeCompressedLong(METADATA_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(0); // metadata id - writeBytes(metadataDescriptor); // payload + writeCompressedLong(metadata.getCurrentMetadataId()); // metadata id + writeBytes(metadata.getDescriptorAndClearDirtyFlag()); // payload endEvent(start); - metadataPosition = start; + metadata.setMetadataPosition(start); } public boolean shouldRotateDisk() { @@ -645,4 +632,9 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java) { public long getChunkStartNanos() { return chunkStartNanos; } + + public void markChunkFinal() { + assert lock.isHeldByCurrentThread(); + isFinal = true; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java new file mode 100644 index 000000000000..37d13b906b21 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -0,0 +1,40 @@ +package com.oracle.svm.core.jfr; + +import org.graalvm.word.SignedWord; +public class JfrMetadata { + private volatile long currentMetadataId; + private volatile byte[] metadataDescriptor; + private volatile boolean isDirty; + private SignedWord metadataPosition; + public JfrMetadata(byte[] bytes){ + metadataDescriptor = bytes; + currentMetadataId = 0; + isDirty = false; + } + + public void setDescriptor(byte[] bytes) { + metadataDescriptor = bytes; + currentMetadataId++; + isDirty = true; + } + + public byte[] getDescriptorAndClearDirtyFlag() { + isDirty = false; + return metadataDescriptor; + } + public boolean isDirty() { + return isDirty; + } + + public void setMetadataPosition(SignedWord metadataPosition) { + this.metadataPosition = metadataPosition; + } + + public SignedWord getMetadataPosition() { + return metadataPosition; + } + + public long getCurrentMetadataId(){ + return currentMetadataId; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 4dfd6415a474..18588f9e724d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -365,7 +365,7 @@ public void include(Thread thread) { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isCurrentThreadExcluded() { - return excluded.get() == 1 ? true : false; + return excluded.get() == 1; } @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = 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 bf25ca537926..eca010da125b 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 @@ -92,7 +92,7 @@ public class SubstrateJVM { * in). */ private volatile boolean recording; - private byte[] metadataDescriptor; + private JfrMetadata metadata; private String dumpPath; @Platforms(Platform.HOSTED_ONLY.class) @@ -122,14 +122,14 @@ public SubstrateJVM(List configurations) { threadLocal = new JfrThreadLocal(); globalMemory = new JfrGlobalMemory(); samplerBufferPool = new SamplerBufferPool(); - unlockedChunkWriter = new JfrChunkWriter(globalMemory); + metadata = new JfrMetadata(null); + unlockedChunkWriter = new JfrChunkWriter(globalMemory, metadata); recorderThread = new JfrRecorderThread(globalMemory, unlockedChunkWriter); jfrLogging = new JfrLogging(); initialized = false; recording = false; - metadataDescriptor = null; } @Fold @@ -300,10 +300,7 @@ public static long getCurrentThreadId() { * See {@link JVM#storeMetadataDescriptor}. */ public void storeMetadataDescriptor(byte[] bytes) { - metadataDescriptor = bytes; - JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); - chunkWriter.setCurrentMetadataId(); - chunkWriter.unlock(); + metadata.setDescriptor(bytes); } /** @@ -357,7 +354,7 @@ public void setOutput(String file) { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.closeFile(metadataDescriptor, repositories, threadRepo); + chunkWriter.closeFile(repositories, threadRepo); } if (file != null) { chunkWriter.openFile(file); @@ -518,7 +515,7 @@ public void flush() { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.flush(metadataDescriptor, repositories, threadRepo); + chunkWriter.flush(repositories, threadRepo); } } } finally { From 5bbb52ab4dc458c7a2715c00b1f1b4abfa95110a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 8 Feb 2023 09:51:44 -0500 Subject: [PATCH 28/72] make constant pool write methods uninterruptable to prevent single threaded races --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 41 +++++++------ .../com/oracle/svm/core/jfr/JfrMetadata.java | 1 + .../svm/core/jfr/JfrMethodRepository.java | 7 ++- .../svm/core/jfr/JfrStackTraceRepository.java | 7 ++- .../svm/core/jfr/JfrSymbolRepository.java | 61 +++++++++++++++++-- .../svm/core/jfr/JfrThreadRepository.java | 22 +++++-- .../src/com/oracle/svm/core/jfr/JfrType.java | 4 +- .../com/oracle/svm/core/locks/VMMutex.java | 4 ++ 8 files changed, 116 insertions(+), 31 deletions(-) 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 65fcb043505d..e62a739a5940 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 @@ -25,7 +25,6 @@ package com.oracle.svm.core.jfr; import java.nio.charset.StandardCharsets; -import java.util.concurrent.locks.ReentrantLock; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.core.common.NumUtil; @@ -46,6 +45,7 @@ import com.oracle.svm.core.thread.ThreadingSupportImpl; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMOperationControl; +import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.util.VMError; @@ -73,7 +73,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { public static final long CONSTANT_POOL_TYPE_ID = 1; private static final byte COMPLETE = 0; private final JfrGlobalMemory globalMemory; - private final ReentrantLock lock; + private final VMMutex lock; private final boolean compressedInts; private final JfrMetadata metadata; private long notificationThreshold; @@ -89,7 +89,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { @Platforms(Platform.HOSTED_ONLY.class) public JfrChunkWriter(JfrGlobalMemory globalMemory, JfrMetadata metadata) { - this.lock = new ReentrantLock(); + this.lock = new VMMutex("JfrChunkWriter"); this.compressedInts = true; this.globalMemory = globalMemory; this.metadata = metadata; @@ -118,19 +118,19 @@ public boolean hasOpenFile() { } public void setFilename(String filename) { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); this.filename = filename; } public void maybeOpenFile() { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); if (filename != null) { openFile(filename); } } public boolean openFile(String outputFile) { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); isFinal = false; generation = 1; newChunk = true; @@ -171,7 +171,7 @@ public boolean write(JfrBuffer buffer, boolean reset) { * Write all the in-memory data to the file. */ public void closeFile(JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); /* * Switch to a new epoch. This is done at a safepoint to ensure that we end up with * consistent data, even if multiple threads have JFR events in progress. @@ -198,7 +198,7 @@ public void closeFile(JfrConstantPool[] repositories, JfrThreadRepository thread } public void flush(JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); flushStorage(); if (threadRepo.isDirty(true)) { @@ -240,7 +240,7 @@ public void patchFileHeader(SignedWord constantPoolPosition) { } private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint) { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); SignedWord currentPos = getFileSupport().position(fd); long chunkSize = getFileSupport().position(fd).rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; @@ -307,7 +307,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea } private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); SignedWord start = beginEvent(); if (lastCheckpointOffset.lessThan(0)) { @@ -349,7 +349,7 @@ private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { } private void writeMetadataEvent() { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); // always write metadata on a new chunk! if (!metadata.isDirty() && !newChunk) { return; @@ -365,7 +365,7 @@ private void writeMetadataEvent() { } public boolean shouldRotateDisk() { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); return getFileSupport().isValid(fd) && getFileSupport().size(fd).greaterThan(WordFactory.signed(notificationThreshold)); } @@ -384,28 +384,33 @@ public void endEvent(SignedWord start) { getFileSupport().seek(fd, end); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeBoolean(boolean value) { - assert lock.isHeldByCurrentThread() || VMOperationControl.isDedicatedVMOperationThread() && lock.isLocked(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); writeByte((byte) (value ? 1 : 0)); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeByte(byte value) { - assert lock.isHeldByCurrentThread() || VMOperationControl.isDedicatedVMOperationThread() && lock.isLocked(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); getFileSupport().writeByte(fd, value); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeBytes(byte[] values) { - assert lock.isHeldByCurrentThread() || VMOperationControl.isDedicatedVMOperationThread() && lock.isLocked(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); getFileSupport().write(fd, values); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeCompressedInt(int value) { - assert lock.isHeldByCurrentThread() || VMOperationControl.isDedicatedVMOperationThread() && lock.isLocked(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); writeCompressedLong(value & 0xFFFFFFFFL); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeCompressedLong(long value) { - assert lock.isHeldByCurrentThread() || VMOperationControl.isDedicatedVMOperationThread() && lock.isLocked(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); long v = value; if ((v & ~0x7FL) == 0L) { getFileSupport().writeByte(fd, (byte) v); // 0-6 @@ -634,7 +639,7 @@ public long getChunkStartNanos() { } public void markChunkFinal() { - assert lock.isHeldByCurrentThread(); + assert lock.isOwner(); isFinal = true; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index 37d13b906b21..b8ee16fbd436 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -1,3 +1,4 @@ + package com.oracle.svm.core.jfr; import org.graalvm.word.SignedWord; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index c12e3c7a98c2..bb61a2c00b9e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -107,12 +107,14 @@ private long getMethodId0(Class clazz, String methodName, int methodId) { return methodId; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeLock(boolean flush) { if (flush) { - mutex.lock(); + mutex.lockNoTransition(); } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); @@ -120,6 +122,7 @@ private void maybeUnlock(boolean flush) { } @Override + @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { maybeLock(flush); try { @@ -134,6 +137,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } + @Uninterruptible(reason = "May write current epoch data.") private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData, boolean flush) { int numberOfMethods = epochData.visitedMethods.getSize(); if (numberOfMethods == 0) { @@ -171,6 +175,7 @@ void teardown() { methodBuffer = WordFactory.nullPointer(); } + @Uninterruptible(reason = "May write current epoch data.") void clear() { visitedMethods.clear(); if (methodBuffer.isNonNull()) { 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 e15ca4bdb7aa..d409452baef0 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 @@ -230,12 +230,14 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeLock(boolean flush) { if (flush) { - mutex.lock(); + mutex.lockNoTransition(); } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); @@ -243,6 +245,7 @@ private void maybeUnlock(boolean flush) { } @Override + @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { maybeLock(flush); try { @@ -257,6 +260,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } + @Uninterruptible(reason = "May write current epoch data.") private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData, boolean flush) { if (epochData.numberOfSerializedStackTraces == 0) { return EMPTY; @@ -377,6 +381,7 @@ private static class JfrStackTraceEpochData { this.visitedStackTraces = new JfrStackTraceTable(); } + @Uninterruptible(reason = "May write current epoch data.") void clear() { visitedStackTraces.clear(); numberOfSerializedStackTraces = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index dd374f41e4f8..4d4ce6200989 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -93,6 +93,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r long rawPointerValue = Word.objectToUntrackedPointer(imageHeapString).rawValue(); int hashcode = (int) (rawPointerValue ^ (rawPointerValue >>> 32)); symbol.setHash(hashcode); + symbol.setBytes(null); mutex.lockNoTransition(); try { @@ -112,21 +113,56 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r return 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeLock(boolean flush) { if (flush) { - mutex.lock(); + mutex.lockNoTransition(); } } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); } } + /** + * The purpose of this method is so that String.getBytes() can be used outside uninterruptible + * code. This is not an ideal solution because it results in having to scan the table twice. + * + * This method does not need to be locked during flushes. It can race with other threads adding + * entries because in the worst case, the new entries are missed until the next flush. + * + * This method can be interrupted because there is no locking (no risk of deadlocks), and if it + * is interrupted for an operation that results in new pool entries, in the worst case, those + * entries will be missed until the next flush. + * + * Any missed new entries should not be needed in the chunk because their corresponding event + * data will not be flushed during this flush operation. This is due to the flushing order: + * event data then constant pools. + */ @Override public int write(JfrChunkWriter writer, boolean flush) { JfrSymbolHashtable table = getTable(!flush); - maybeLock(flush); + // compute byte arrays + JfrSymbol[] entries = table.getTable(); + for (int i = 0; i < entries.length; i++) { + JfrSymbol entry = entries[i]; + if (entry.isNonNull()) { + while (entry.isNonNull()) { + entry.setBytes(entry.getValue().getBytes(StandardCharsets.UTF_8)); + entry = entry.getNext(); + } + } + } + return doWrite(writer, flush, table); + } + + @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") + private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable table) { + maybeLock(flush);// *** locking is needed so that other thread's concurrent additions don't + // get cleared try { if (table.getSize() == 0) { return EMPTY; @@ -151,10 +187,18 @@ public int write(JfrChunkWriter writer, boolean flush) { } } - private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { // *** only write if not serialized before. Set serialized + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { // *** only write if + // not serialized + // before. Set + // serialized + byte[] value = symbol.getBytes(); + if (value == null) { + return; + } writer.writeCompressedLong(symbol.getId()); writer.writeByte(JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); - byte[] value = symbol.getValue().getBytes(StandardCharsets.UTF_8); + if (symbol.getReplaceDotWithSlash()) { replaceDotWithSlash(value); } @@ -162,6 +206,7 @@ private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { // ** writer.writeBytes(value); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void replaceDotWithSlash(byte[] utf8String) { for (int i = 0; i < utf8String.length; i++) { if (utf8String[i] == '.') { @@ -186,6 +231,14 @@ private interface JfrSymbol extends UninterruptibleEntry { // *** add field for @RawField void setValue(String value); + @PinnedObjectField + @RawField + byte[] getBytes(); + + @PinnedObjectField + @RawField + void setBytes(byte[] bytes); + @RawField boolean getReplaceDotWithSlash(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 3f24c492a517..cdd5154e2f8a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -188,12 +188,14 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { return epoch ? epochData0 : epochData1; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeLock(boolean flush) { if (flush) { - mutex.lock(); + mutex.lockNoTransition(); } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); @@ -201,6 +203,7 @@ private void maybeUnlock(boolean flush) { } @Override + @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { JfrThreadEpochData epochData = getEpochData(!flush); maybeLock(flush); @@ -208,10 +211,13 @@ public int write(JfrChunkWriter writer, boolean flush) { int count = writeThreads(writer, epochData); count += writeThreadGroups(writer, epochData); + /* + * Thread repository epoch data may be cleared after a flush because threads are only + * registered once with fixed IDs. There is no need to do a lookup in this repo for a + * thread's ID. + */ epochData.clear(); - epochData.isDirty = false; - return count; } finally { maybeUnlock(flush); @@ -223,16 +229,18 @@ public boolean isDirty(boolean flush) { return epochData.isDirty; } + @Uninterruptible(reason = "May write current epoch data.") private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); writer.writeCompressedLong(JfrType.Thread.getId()); writer.writeCompressedInt(epochData.visitedThreads.getSize()); - writer.write(epochData.threadBuffer, true); + writer.write(epochData.threadBuffer); return NON_EMPTY; } + @Uninterruptible(reason = "May write current epoch data.") private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { int threadGroupCount = epochData.visitedThreadGroups.getSize(); if (threadGroupCount == 0) { @@ -241,7 +249,7 @@ private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData e writer.writeCompressedLong(JfrType.ThreadGroup.getId()); writer.writeCompressedInt(threadGroupCount); - writer.write(epochData.threadGroupBuffer, true); + writer.write(epochData.threadGroupBuffer); return NON_EMPTY; } @@ -258,12 +266,12 @@ private static class JfrThreadEpochData { * only invoked once per thread (there can be races when re-registering already running * threads). */ - private boolean isDirty = false; private final JfrVisitedTable visitedThreads; private final JfrVisitedTable visitedThreadGroups; private JfrBuffer threadBuffer; private JfrBuffer threadGroupBuffer; + private boolean isDirty = false; @Platforms(Platform.HOSTED_ONLY.class) JfrThreadEpochData() { @@ -271,12 +279,14 @@ private static class JfrThreadEpochData { this.visitedThreadGroups = new JfrVisitedTable(); } + @Uninterruptible(reason = "May write current epoch data.") public void clear() { visitedThreads.clear(); visitedThreadGroups.clear(); JfrBufferAccess.reinitialize(threadBuffer); JfrBufferAccess.reinitialize(threadGroupBuffer); + isDirty = false; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java index b6294998bd17..b1e79e9fd760 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; + /** * Maps JFR types against their IDs in the JDK. */ @@ -49,7 +51,7 @@ public enum JfrType { JfrType(String name) { this.id = JfrMetadataTypeLibrary.lookupType(name); } - + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long getId() { return id; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java index 29a382fe896e..343f1be00604 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java @@ -191,4 +191,8 @@ public void clearUnspecifiedOwner() { assert owner == (IsolateThread) UNSPECIFIED_OWNER; owner = WordFactory.nullPointer(); } + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean isOwned() { + return owner.isNonNull(); + } } From cd8212757d27dd05b75fb25c5e8cdea0840f772e Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 8 Feb 2023 10:37:54 -0500 Subject: [PATCH 29/72] write repositories individually. Get rid of old array --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 33 +++++++++++++------ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 12 ++----- 2 files changed, 25 insertions(+), 20 deletions(-) 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 e62a739a5940..603ebae6261a 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 @@ -170,7 +170,7 @@ public boolean write(JfrBuffer buffer, boolean reset) { /** * Write all the in-memory data to the file. */ - public void closeFile(JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { + public void closeFile(JfrThreadRepository threadRepo) { assert lock.isOwner(); /* * Switch to a new epoch. This is done at a safepoint to ensure that we end up with @@ -187,7 +187,7 @@ public void closeFile(JfrConstantPool[] repositories, JfrThreadRepository thread if (threadRepo.isDirty(false)) { writeThreadCheckpointEvent(threadRepo, false); } - SignedWord constantPoolPosition = writeCheckpointEvent(repositories, false); + SignedWord constantPoolPosition = writeCheckpointEvent(false); writeMetadataEvent(); patchFileHeader(constantPoolPosition); getFileSupport().close(fd); @@ -197,14 +197,14 @@ public void closeFile(JfrConstantPool[] repositories, JfrThreadRepository thread newChunk = false; } - public void flush(JfrConstantPool[] repositories, JfrThreadRepository threadRepo) { + public void flush(JfrThreadRepository threadRepo) { assert lock.isOwner(); flushStorage(); if (threadRepo.isDirty(true)) { writeThreadCheckpointEvent(threadRepo, true); } - SignedWord constantPoolPosition = writeCheckpointEvent(repositories, true); + SignedWord constantPoolPosition = writeCheckpointEvent(true); writeMetadataEvent(); patchFileHeader(constantPoolPosition, true); @@ -306,7 +306,7 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea return start; } - private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean flush) { + private SignedWord writeCheckpointEvent(boolean flush) { assert lock.isOwner(); SignedWord start = beginEvent(); @@ -321,13 +321,12 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. - JfrConstantPool[] serializers = JfrSerializerSupport.get().getSerializers(); int poolCount = 0; if (newChunk) { - poolCount = writeConstantPools(serializers, flush); + poolCount = writeSerializers(flush); } - poolCount += writeConstantPools(repositories, flush); + poolCount += writeRepositories(flush); SignedWord currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); @@ -339,9 +338,23 @@ private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories, boolean return start; } - private int writeConstantPools(JfrConstantPool[] constantPools, boolean flush) { + /** + * Repositories earlier in the write order may reference entries of repositories later in the + * write order. This ordering is required to prevent races during flushing without changing + * epoch. + */ + private int writeRepositories(boolean flush) { + int count = 0; + count += com.oracle.svm.core.jfr.SubstrateJVM.getStackTraceRepo().write(this, flush); + count += com.oracle.svm.core.jfr.SubstrateJVM.getMethodRepo().write(this, flush); + count += com.oracle.svm.core.jfr.SubstrateJVM.getTypeRepository().write(this, flush); + count += com.oracle.svm.core.jfr.SubstrateJVM.getSymbolRepository().write(this, flush); + return count; + } + + private int writeSerializers(boolean flush) { int count = 0; - for (JfrConstantPool constantPool : constantPools) { + for (JfrConstantPool constantPool : JfrSerializerSupport.get().getSerializers()) { int poolCount = constantPool.write(this, flush); count += poolCount; } 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 eca010da125b..2c5f1d365863 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 @@ -75,8 +75,6 @@ public class SubstrateJVM { private final JfrThreadRepository threadRepo; private final JfrStackTraceRepository stackTraceRepo; private final JfrMethodRepository methodRepo; - private final JfrConstantPool[] repositories; - private final JfrThreadLocal threadLocal; private final JfrGlobalMemory globalMemory; private final SamplerBufferPool samplerBufferPool; @@ -112,12 +110,6 @@ public SubstrateJVM(List configurations) { typeRepo = new JfrTypeRepository(); threadRepo = new JfrThreadRepository(); methodRepo = new JfrMethodRepository(); - /* - * The ordering in the array dictates the writing order of constant pools in the recording. - * Current rules: 1. methodRepo should be after stackTraceRepo; 2. typeRepo should be after - * methodRepo and stackTraceRepo; 3. symbolRepo should be on end. - */ - repositories = new JfrConstantPool[]{stackTraceRepo, methodRepo, typeRepo, symbolRepo}; threadLocal = new JfrThreadLocal(); globalMemory = new JfrGlobalMemory(); @@ -354,7 +346,7 @@ public void setOutput(String file) { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.closeFile(repositories, threadRepo); + chunkWriter.closeFile(threadRepo); } if (file != null) { chunkWriter.openFile(file); @@ -515,7 +507,7 @@ public void flush() { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.flush(repositories, threadRepo); + chunkWriter.flush(threadRepo); } } } finally { From 35adfcbd8582cd0fcf6237dea453cdbda2000b2b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 8 Feb 2023 11:33:07 -0500 Subject: [PATCH 30/72] fix bug in traverse list. Notify event writer during epoch change --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 603ebae6261a..5ec03a18fcde 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 @@ -537,8 +537,8 @@ private void changeEpoch() { * *not* reinitialize the thread-local buffers as the individual threads will handle * space reclamation on their own time. */ - traverseList(getJavaBufferList(), true); - traverseList(getNativeBufferList(), false); + traverseList(getJavaBufferList(), true, true); + traverseList(getNativeBufferList(), false, true); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -585,8 +585,8 @@ private void processSamplerBuffers0() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage() { - traverseList(getJavaBufferList(), true); - traverseList(getNativeBufferList(), false); + traverseList(getJavaBufferList(), true, false); + traverseList(getNativeBufferList(), false, false); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -601,9 +601,7 @@ private void flushStorage() { } @Uninterruptible(reason = "Called from uninterruptible code.") - private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java) { - // Traverse back to front to minimize conflict with threads adding new nodes. - // Which could possibly block traversal early at the head. + private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { boolean firstIteration = true; JfrBufferNode node = linkedList.getAndLockHead(); @@ -619,17 +617,20 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java) { if (!JfrBufferAccess.acquire(buffer)) { prev = node; node = next; + continue; } write(buffer); JfrBufferAccess.release(buffer); - // If a node has been marked dead, the thread was unable to flush upon death. - // So we need to flush the buffer above before attempting to remove the node here. if (!node.getAlive()) { linkedList.removeNode(node, prev); // if removed current node, should not update prev. } else { + // Only notify java event writer if thread is still alive and we are at an epoch change. + if (safepoint && java) { + JfrThreadLocal.notifyEventWriter(node.getThread()); + } prev = node; } node = next; From 3751fc0aad045f0eb0ae628cb2e07fcf8d94dec5 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 8 Feb 2023 13:43:05 -0500 Subject: [PATCH 31/72] flush to global buffers first instead of disk --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 32 +++++++------------ .../oracle/svm/core/jfr/JfrGlobalMemory.java | 5 +-- .../oracle/svm/core/jfr/JfrThreadLocal.java | 24 ++++++++++++-- 3 files changed, 36 insertions(+), 25 deletions(-) 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 5ec03a18fcde..5dc57acbce57 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 @@ -199,7 +199,7 @@ public void closeFile(JfrThreadRepository threadRepo) { public void flush(JfrThreadRepository threadRepo) { assert lock.isOwner(); - flushStorage(); + flushStorage(false); if (threadRepo.isDirty(true)) { writeThreadCheckpointEvent(threadRepo, true); @@ -537,16 +537,7 @@ private void changeEpoch() { * *not* reinitialize the thread-local buffers as the individual threads will handle * space reclamation on their own time. */ - traverseList(getJavaBufferList(), true, true); - traverseList(getNativeBufferList(), false, true); - - JfrBuffers buffers = globalMemory.getBuffers(); - for (int i = 0; i < globalMemory.getBufferCount(); i++) { - JfrBuffer buffer = buffers.addressOf(i).read(); - assert !JfrBufferAccess.isAcquired(buffer); - write(buffer); - JfrBufferAccess.reinitialize(buffer); - } + flushStorage(true); JfrTraceIdEpoch.getInstance().changeEpoch(); @@ -584,9 +575,9 @@ private void processSamplerBuffers0() { } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") - private void flushStorage() { - traverseList(getJavaBufferList(), true, false); - traverseList(getNativeBufferList(), false, false); + private void flushStorage(boolean safepoint) { + traverseList(getJavaBufferList(), true, safepoint); + traverseList(getNativeBufferList(), false, safepoint); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -604,6 +595,7 @@ private void flushStorage() { private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { boolean firstIteration = true; + // hold onto lock until done with the head node. JfrBufferNode node = linkedList.getAndLockHead(); JfrBufferNode prev = WordFactory.nullPointer(); @@ -613,15 +605,13 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool JfrBuffer buffer = node.getValue(); VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); - // Try to get BUFFER with one attempt - if (!JfrBufferAccess.acquire(buffer)) { + /* I/O operation may be slow, so this flushes to the global buffers instead of writing to disk directly. + * This mitigates the risk of acquiring the TLBs for too long.*/ + if (!JfrThreadLocal.flushNoReset(buffer)) { prev = node; node = next; continue; } - write(buffer); - - JfrBufferAccess.release(buffer); if (!node.getAlive()) { linkedList.removeNode(node, prev); @@ -636,7 +626,7 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool node = next; } finally { if (firstIteration) { - linkedList.releaseList(); // hold onto lock until done with head. + linkedList.releaseList(); firstIteration = false; } } @@ -644,7 +634,7 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool // we may never have entered the while loop if the list is empty. if (firstIteration) { - linkedList.releaseList(); // hold onto lock until done with head. + linkedList.releaseList(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 6f5e1b4e9f98..975a39a6f00b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -88,7 +88,7 @@ public long getBufferCount() { } @Uninterruptible(reason = "Epoch must not change while in this method.") - public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize) { + public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, boolean doingFlush) { JfrBuffer promotionBuffer = acquireBufferWithRetry(unflushedSize, PROMOTION_RETRY_COUNT); if (promotionBuffer.isNull()) { return false; @@ -106,7 +106,8 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize) { } JfrBufferAccess.increaseTop(threadLocalBuffer, unflushedSize); // Notify the thread that writes the global memory to disk. - if (shouldSignal) { + // If we're flushing, the global buffers are about to get persisted anyway + if (shouldSignal && !doingFlush) { recorderThread.signal(); } return true; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 18588f9e724d..0568cd63139f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -219,7 +219,6 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { JfrBufferNode result = javaBufferNode.get(); - if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); result = JfrBufferNodeLinkedList.createNode(buffer, CurrentIsolate.getCurrentThread()); @@ -268,6 +267,27 @@ private static void acquireBufferWithRetry(JfrBuffer buffer) { } } + /** + * This method only copies the JFR buffer's unflushed data to the global buffers. This can be used outside a safepoint + * from the flushing thread while other threads continue writing events.*/ + @Uninterruptible(reason = "Accesses a JFR buffer.") + public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { + // Try to get BUFFER with one attempt + if (!JfrBufferAccess.acquire(threadLocalBuffer)) { + return false; + } + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); + if (unflushedSize.aboveThan(0)) { + JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); + // Top is increased in JfrGlobalMemory.write + if (globalMemory.write(threadLocalBuffer, unflushedSize, true)) { + JfrBufferAccess.release(threadLocalBuffer); + return true; + } + } + JfrBufferAccess.release(threadLocalBuffer); + return false; + } @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { @@ -280,7 +300,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); - if (!globalMemory.write(threadLocalBuffer, unflushedSize)) { + if (!globalMemory.write(threadLocalBuffer, unflushedSize, false)) { JfrBufferAccess.reinitialize(threadLocalBuffer); writeDataLoss(threadLocalBuffer, unflushedSize); JfrBufferAccess.release(threadLocalBuffer); From 92ff04931927c8cdb49aacd913d8e2e7cb42990a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 8 Feb 2023 14:19:50 -0500 Subject: [PATCH 32/72] refactoring to resolve PR comments --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 3 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 35 +++--- .../com/oracle/svm/core/jfr/JfrMetadata.java | 33 +++++- .../svm/core/jfr/JfrSymbolRepository.java | 4 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 104 +++++++++--------- 5 files changed, 107 insertions(+), 72 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index ae63316d73e4..03f78ac088e2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -39,6 +39,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.util.UnsignedUtils; import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; import com.oracle.svm.core.thread.SpinLockUtils; public class JfrBufferNodeLinkedList { @@ -93,7 +94,7 @@ private void setHead(JfrBufferNode node) { } @Uninterruptible(reason = "Called from uninterruptible code.") - private static org.graalvm.word.UnsignedWord getHeaderSize() { + private static UnsignedWord getHeaderSize() { return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); } 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 5dc57acbce57..c29a57bba88e 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 @@ -134,6 +134,7 @@ public boolean openFile(String outputFile) { isFinal = false; generation = 1; newChunk = true; + metadata.setDirty(); chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; @@ -149,7 +150,7 @@ public boolean write(JfrBuffer buffer) { } @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") - public boolean write(JfrBuffer buffer, boolean reset) { + public boolean write(JfrBuffer buffer, boolean increaseTop) { assert JfrBufferAccess.isAcquired(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { @@ -157,7 +158,7 @@ public boolean write(JfrBuffer buffer, boolean reset) { } boolean success = getFileSupport().write(fd, buffer.getTop(), unflushedSize); - if (reset) { + if (increaseTop) { JfrBufferAccess.increaseTop(buffer, unflushedSize); } if (!success) { @@ -189,7 +190,7 @@ public void closeFile(JfrThreadRepository threadRepo) { } SignedWord constantPoolPosition = writeCheckpointEvent(false); writeMetadataEvent(); - patchFileHeader(constantPoolPosition); + patchFileHeader(constantPoolPosition, false); getFileSupport().close(fd); filename = null; @@ -235,10 +236,6 @@ private void writeFileHeader() { getFileSupport().writeShort(fd, flags); } - public void patchFileHeader(SignedWord constantPoolPosition) { - patchFileHeader(constantPoolPosition, false); - } - private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint) { assert lock.isOwner(); SignedWord currentPos = getFileSupport().position(fd); @@ -253,14 +250,14 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); if (flushpoint) { // chunk is not finished - getFileSupport().writeByte(fd, nextGeneration()); // there are 4 bytes at the end. The - // first byte is the finished flag. + getFileSupport().writeByte(fd, nextGeneration()); } else { getFileSupport().writeByte(fd, COMPLETE); } getFileSupport().writeByte(fd, (byte) 0); short flags = 0; flags += compressedInts ? 1 : 0; + // Chunks are marked final when the recording has stopped flags += isFinal ? 1 * 2 : 0; getFileSupport().writeShort(fd, flags); @@ -345,10 +342,10 @@ private SignedWord writeCheckpointEvent(boolean flush) { */ private int writeRepositories(boolean flush) { int count = 0; - count += com.oracle.svm.core.jfr.SubstrateJVM.getStackTraceRepo().write(this, flush); - count += com.oracle.svm.core.jfr.SubstrateJVM.getMethodRepo().write(this, flush); - count += com.oracle.svm.core.jfr.SubstrateJVM.getTypeRepository().write(this, flush); - count += com.oracle.svm.core.jfr.SubstrateJVM.getSymbolRepository().write(this, flush); + count += SubstrateJVM.getStackTraceRepo().write(this, flush); + count += SubstrateJVM.getMethodRepo().write(this, flush); + count += SubstrateJVM.getTypeRepository().write(this, flush); + count += SubstrateJVM.getSymbolRepository().write(this, flush); return count; } @@ -364,7 +361,7 @@ private int writeSerializers(boolean flush) { private void writeMetadataEvent() { assert lock.isOwner(); // always write metadata on a new chunk! - if (!metadata.isDirty() && !newChunk) { + if (!metadata.isDirty()) { return; } SignedWord start = beginEvent(); @@ -605,8 +602,11 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool JfrBuffer buffer = node.getValue(); VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); - /* I/O operation may be slow, so this flushes to the global buffers instead of writing to disk directly. - * This mitigates the risk of acquiring the TLBs for too long.*/ + /* + * I/O operation may be slow, so this flushes to the global buffers instead of + * writing to disk directly. This mitigates the risk of acquiring the TLBs for too + * long. + */ if (!JfrThreadLocal.flushNoReset(buffer)) { prev = node; node = next; @@ -617,7 +617,8 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool linkedList.removeNode(node, prev); // if removed current node, should not update prev. } else { - // Only notify java event writer if thread is still alive and we are at an epoch change. + // Only notify java event writer if thread is still alive and we are at an epoch + // change. if (safepoint && java) { JfrThreadLocal.notifyEventWriter(node.getThread()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index b8ee16fbd436..f7813139ad35 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -1,4 +1,28 @@ - +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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.core.jfr; import org.graalvm.word.SignedWord; @@ -7,7 +31,7 @@ public class JfrMetadata { private volatile byte[] metadataDescriptor; private volatile boolean isDirty; private SignedWord metadataPosition; - public JfrMetadata(byte[] bytes){ + public JfrMetadata(byte[] bytes) { metadataDescriptor = bytes; currentMetadataId = 0; isDirty = false; @@ -26,6 +50,9 @@ public byte[] getDescriptorAndClearDirtyFlag() { public boolean isDirty() { return isDirty; } + public void setDirty() { + isDirty = true; + } public void setMetadataPosition(SignedWord metadataPosition) { this.metadataPosition = metadataPosition; @@ -35,7 +62,7 @@ public SignedWord getMetadataPosition() { return metadataPosition; } - public long getCurrentMetadataId(){ + public long getCurrentMetadataId() { return currentMetadataId; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 4d4ce6200989..121dc85e775d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -161,8 +161,8 @@ public int write(JfrChunkWriter writer, boolean flush) { @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable table) { - maybeLock(flush);// *** locking is needed so that other thread's concurrent additions don't - // get cleared + maybeLock(flush); // *** locking is needed so that other thread's concurrent additions don't + // get cleared try { if (table.getSize() == 0) { return EMPTY; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 0568cd63139f..5e80e0c63a0a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -142,21 +142,23 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { public static void stopRecording(IsolateThread isolateThread, boolean flushBuffers) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ - JfrBufferNode jbn = javaBufferNode.get(isolateThread); - JfrBufferNode nbn = nativeBufferNode.get(isolateThread); + if (flushBuffers) { + JfrBufferNode jbn = javaBufferNode.get(isolateThread); + JfrBufferNode nbn = nativeBufferNode.get(isolateThread); - if (jbn.isNonNull() && flushBuffers) { - JfrBuffer jb = jbn.getValue(); - assert jb.isNonNull() && jbn.getAlive(); - flush(jb, WordFactory.unsigned(0), 0); - jbn.setAlive(false); + if (jbn.isNonNull()) { + JfrBuffer jb = jbn.getValue(); + assert jb.isNonNull() && jbn.getAlive(); + flush(jb, WordFactory.unsigned(0), 0); + jbn.setAlive(false); - } - if (nbn.isNonNull() && flushBuffers) { - JfrBuffer nb = nbn.getValue(); - assert nb.isNonNull() && nbn.getAlive(); - flush(nb, WordFactory.unsigned(0), 0); - nbn.setAlive(false); + } + if (nbn.isNonNull()) { + JfrBuffer nb = nbn.getValue(); + assert nb.isNonNull() && nbn.getAlive(); + flush(nb, WordFactory.unsigned(0), 0); + nbn.setAlive(false); + } } /* Clear event-related thread-locals. */ @@ -261,32 +263,34 @@ public static void notifyEventWriter(IsolateThread thread) { } } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Accesses a JFR buffer.") private static void acquireBufferWithRetry(JfrBuffer buffer) { while (!JfrBufferAccess.acquire(buffer)) { } } + /** - * This method only copies the JFR buffer's unflushed data to the global buffers. This can be used outside a safepoint - * from the flushing thread while other threads continue writing events.*/ + * This method only copies the JFR buffer's unflushed data to the global buffers. This can be + * used outside a safepoint from the flushing thread while other threads continue writing + * events. + */ @Uninterruptible(reason = "Accesses a JFR buffer.") public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { - // Try to get BUFFER with one attempt - if (!JfrBufferAccess.acquire(threadLocalBuffer)) { - return false; - } - UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); - if (unflushedSize.aboveThan(0)) { - JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); - // Top is increased in JfrGlobalMemory.write - if (globalMemory.write(threadLocalBuffer, unflushedSize, true)) { - JfrBufferAccess.release(threadLocalBuffer); - return true; + acquireBufferWithRetry(threadLocalBuffer); + try { + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); + if (unflushedSize.aboveThan(0)) { + JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); + // Top is increased in JfrGlobalMemory.write + if (!globalMemory.write(threadLocalBuffer, unflushedSize, true)) { + return false; + } } + return true; + } finally { + JfrBufferAccess.release(threadLocalBuffer); } - JfrBufferAccess.release(threadLocalBuffer); - return false; } @Uninterruptible(reason = "Accesses a JFR buffer.") @@ -296,31 +300,33 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit // Needed for race between streaming flush and promotion acquireBufferWithRetry(threadLocalBuffer); + try { + + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); + if (unflushedSize.aboveThan(0)) { + JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); + if (!globalMemory.write(threadLocalBuffer, unflushedSize, false)) { + JfrBufferAccess.reinitialize(threadLocalBuffer); + writeDataLoss(threadLocalBuffer, unflushedSize); + return WordFactory.nullPointer(); + } + } - UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); - if (unflushedSize.aboveThan(0)) { - JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); - if (!globalMemory.write(threadLocalBuffer, unflushedSize, false)) { - JfrBufferAccess.reinitialize(threadLocalBuffer); - writeDataLoss(threadLocalBuffer, unflushedSize); - JfrBufferAccess.release(threadLocalBuffer); - return WordFactory.nullPointer(); + if (uncommitted.aboveThan(0)) { + /* Copy all uncommitted memory to the start of the thread local buffer. */ + assert JfrBufferAccess.getDataStart(threadLocalBuffer).add(uncommitted).belowOrEqual(JfrBufferAccess.getDataEnd(threadLocalBuffer)); + UnmanagedMemoryUtil.copy(threadLocalBuffer.getPos(), JfrBufferAccess.getDataStart(threadLocalBuffer), uncommitted); + } + JfrBufferAccess.reinitialize(threadLocalBuffer); + assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); + if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { + return threadLocalBuffer; } - } - if (uncommitted.aboveThan(0)) { - /* Copy all uncommitted memory to the start of the thread local buffer. */ - assert JfrBufferAccess.getDataStart(threadLocalBuffer).add(uncommitted).belowOrEqual(JfrBufferAccess.getDataEnd(threadLocalBuffer)); - UnmanagedMemoryUtil.copy(threadLocalBuffer.getPos(), JfrBufferAccess.getDataStart(threadLocalBuffer), uncommitted); - } - JfrBufferAccess.reinitialize(threadLocalBuffer); - assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); - if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { + return WordFactory.nullPointer(); + } finally { JfrBufferAccess.release(threadLocalBuffer); - return threadLocalBuffer; } - JfrBufferAccess.release(threadLocalBuffer); - return WordFactory.nullPointer(); } @Uninterruptible(reason = "Accesses a JFR buffer.") From e564a8ce4820c1ed1fbc7d8c0057c042ac8866ba Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 9 Feb 2023 16:34:03 -0500 Subject: [PATCH 33/72] comments, docs, small fixes --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 39 +++++++-------- .../oracle/svm/core/jfr/JfrChunkWriter.java | 35 ++++++++------ .../oracle/svm/core/jfr/JfrConstantPool.java | 5 +- .../com/oracle/svm/core/jfr/JfrMetadata.java | 2 + .../svm/core/jfr/JfrSerializerSupport.java | 1 + .../oracle/svm/core/jfr/JfrThreadLocal.java | 47 ++++++++++--------- 6 files changed, 68 insertions(+), 61 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 03f78ac088e2..629fded500b8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -34,12 +34,9 @@ import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; -import com.oracle.svm.core.config.ConfigurationValues; import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.core.util.UnsignedUtils; import org.graalvm.word.PointerBase; -import org.graalvm.word.UnsignedWord; import com.oracle.svm.core.thread.SpinLockUtils; public class JfrBufferNodeLinkedList { @@ -94,22 +91,21 @@ private void setHead(JfrBufferNode node) { } @Uninterruptible(reason = "Called from uninterruptible code.") - private static UnsignedWord getHeaderSize() { - return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBufferNode.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); - } - - @Uninterruptible(reason = "Called from uninterruptible code.") - public static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { - JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(getHeaderSize()); - node.setAlive(true); - node.setValue(buffer); - node.setThread(thread); - node.setNext(WordFactory.nullPointer()); + private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { + if (buffer.isNull()) { + return WordFactory.nullPointer(); + } + JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(JfrBufferNode.class)); + if (node.isNonNull()) { + node.setAlive(true); + node.setValue(buffer); + node.setThread(thread); + node.setNext(WordFactory.nullPointer()); + } return node; } public JfrBufferNodeLinkedList() { - head = WordFactory.nullPointer(); } public void teardown() { @@ -141,16 +137,15 @@ public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { } @Uninterruptible(reason = "Called from uninterruptible code.") - public void addNode(JfrBufferNode node) { + public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { + JfrBufferNode newNode = createNode(buffer, thread); acquireList(); try { + // Old head could be null JfrBufferNode oldHead = head; - if (oldHead.isNull()) { - node.setNext(WordFactory.nullPointer()); - setHead(node); - } - node.setNext(oldHead); - setHead(node); + newNode.setNext(oldHead); + setHead(newNode); + return newNode; } finally { releaseList(); } 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 c29a57bba88e..ba9e73d10684 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 @@ -72,6 +72,8 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { public static final long METADATA_TYPE_ID = 0; public static final long CONSTANT_POOL_TYPE_ID = 1; private static final byte COMPLETE = 0; + private static final short FLAG_COMPRESSED_INTS = 0b01; + private static final short FLAG_CHUNK_FINAL = 0b10; private final JfrGlobalMemory globalMemory; private final VMMutex lock; private final boolean compressedInts; @@ -226,14 +228,10 @@ private void writeFileHeader() { getFileSupport().writeLong(fd, 0L); // durationNanos getFileSupport().writeLong(fd, chunkStartTicks); getFileSupport().writeLong(fd, JfrTicks.getTicksFrequency()); - getFileSupport().writeByte(fd, nextGeneration()); // in hotspot a 1 byte generation is - // written - getFileSupport().writeByte(fd, (byte) 0); // in hotspot 1 byte PAD padding - short flags = 0; - flags += compressedInts ? 1 : 0; - flags += isFinal ? 1 * 2 : 0; - - getFileSupport().writeShort(fd, flags); + assert getFileSupport().position(fd).equal(FILE_STATE_OFFSET); + getFileSupport().writeByte(fd, nextGeneration()); // A 1 byte generation is written + getFileSupport().writeByte(fd, (byte) 0); // A 1 byte padding + getFileSupport().writeShort(fd, computeHeaderFlags()); } private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint) { @@ -244,6 +242,7 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); getFileSupport().writeLong(fd, chunkSize); getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); + assert metadata.getMetadataPosition().greaterThan(0); getFileSupport().writeLong(fd, metadata.getMetadataPosition().rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); @@ -255,20 +254,26 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint getFileSupport().writeByte(fd, COMPLETE); } getFileSupport().writeByte(fd, (byte) 0); - short flags = 0; - flags += compressedInts ? 1 : 0; - // Chunks are marked final when the recording has stopped - flags += isFinal ? 1 * 2 : 0; - - getFileSupport().writeShort(fd, flags); + getFileSupport().writeShort(fd, computeHeaderFlags()); // need to move pointer back to correct position for next write getFileSupport().seek(fd, currentPos); } + private short computeHeaderFlags() { + short flags = 0; + if (compressedInts) { + flags |= FLAG_COMPRESSED_INTS; + } + if (isFinal) { + flags |= FLAG_CHUNK_FINAL; + } + return flags; + } + private byte nextGeneration() { if (generation == Byte.MAX_VALUE) { - // similar to Hotspot, restart counter if required. + // Restart counter if required. generation = 1; return Byte.MAX_VALUE; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java index 505c65d04e3e..2560ef3cf057 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java @@ -47,8 +47,9 @@ public interface JfrConstantPool { int NON_EMPTY = 1; /** - * Persists the data of the previous epoch. May only be called at a safepoint, after the epoch - * changed, or during flush. + * Persists the data of the previous/current epoch. + * + * @param flush Determines whether the current or previous epoch is used. */ int write(JfrChunkWriter writer, boolean flush); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index f7813139ad35..1710779a5fa8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -26,6 +26,7 @@ package com.oracle.svm.core.jfr; import org.graalvm.word.SignedWord; +import org.graalvm.word.WordFactory; public class JfrMetadata { private volatile long currentMetadataId; private volatile byte[] metadataDescriptor; @@ -35,6 +36,7 @@ public JfrMetadata(byte[] bytes) { metadataDescriptor = bytes; currentMetadataId = 0; isDirty = false; + metadataPosition = WordFactory.signed(-1); } public void setDescriptor(byte[] bytes) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java index 2ca63966965e..80d18d0ad6d0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java @@ -33,6 +33,7 @@ /** * Support for registering and querying {@link JfrConstantPool}s that serialize data. + * Serializers are only written upon a new chunk. */ public class JfrSerializerSupport { private JfrConstantPool[] serializers; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 5e80e0c63a0a..9ed33a4ace2e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -33,6 +33,7 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import org.graalvm.compiler.api.replacements.Fold; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.jfr.events.ThreadEndEvent; @@ -86,16 +87,17 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("JfrThreadLocal.missedSamples"); private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("JfrThreadLocal.unparseableStacks"); private static final FastThreadLocalWord samplerWriterData = FastThreadLocalFactory.createWord("JfrThreadLocal.samplerWriterData"); - + private static final JfrBufferNodeLinkedList javaBufferList = new JfrBufferNodeLinkedList(); + private static final JfrBufferNodeLinkedList nativeBufferList = new JfrBufferNodeLinkedList(); private long threadLocalBufferSize; - private static JfrBufferNodeLinkedList javaBufferList = null; - private static JfrBufferNodeLinkedList nativeBufferList = null; + @Fold @Uninterruptible(reason = "Called from uninterruptible code.") public static JfrBufferNodeLinkedList getNativeBufferList() { return nativeBufferList; } + @Fold @Uninterruptible(reason = "Called from uninterruptible code.") public static JfrBufferNodeLinkedList getJavaBufferList() { return javaBufferList; @@ -107,16 +109,6 @@ public JfrThreadLocal() { public void initialize(long bufferSize) { this.threadLocalBufferSize = bufferSize; - initializeBufferLists(); - } - - public static synchronized void initializeBufferLists() { - if (javaBufferList == null) { - javaBufferList = new JfrBufferNodeLinkedList(); - } - if (nativeBufferList == null) { - nativeBufferList = new JfrBufferNodeLinkedList(); - } } @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.") @@ -223,11 +215,14 @@ public JfrBuffer getJavaBuffer() { JfrBufferNode result = javaBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); - result = JfrBufferNodeLinkedList.createNode(buffer, CurrentIsolate.getCurrentThread()); + result = javaBufferList.addNode(buffer, CurrentIsolate.getCurrentThread()); javaBufferNode.set(result); - javaBufferList.addNode(result); } - return result.getValue(); + // result can still be null if allocation of a node or JFR buffer fails. + if (result.isNonNull()) { + return result.getValue(); + } + return WordFactory.nullPointer(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) @@ -235,25 +230,34 @@ public JfrBuffer getNativeBuffer() { JfrBufferNode result = nativeBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); - result = JfrBufferNodeLinkedList.createNode(buffer, CurrentIsolate.getCurrentThread()); + result = nativeBufferList.addNode(buffer, CurrentIsolate.getCurrentThread()); nativeBufferNode.set(result); - nativeBufferList.addNode(result); } - return result.getValue(); + // result can still be null if allocation of a node or JFR buffer fails. + if (result.isNonNull()) { + return result.getValue(); + } + return WordFactory.nullPointer(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public static JfrBuffer getJavaBuffer(IsolateThread thread) { assert VMOperation.isInProgressAtSafepoint(); JfrBufferNode result = javaBufferNode.get(thread); - return result.getValue(); + if (result.isNonNull()) { + return result.getValue(); + } + return WordFactory.nullPointer(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public static JfrBuffer getNativeBuffer(IsolateThread thread) { assert VMOperation.isInProgressAtSafepoint(); JfrBufferNode result = nativeBufferNode.get(thread); - return result.getValue(); + if (result.isNonNull()) { + return result.getValue(); + } + return WordFactory.nullPointer(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -301,7 +305,6 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit // Needed for race between streaming flush and promotion acquireBufferWithRetry(threadLocalBuffer); try { - UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); From 806602b1c44de9ef42f3bc4ba26eb12290322373 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 10 Feb 2023 13:22:36 -0500 Subject: [PATCH 34/72] types get epoch constant IDs. Only serialize new Types --- .../svm/core/jfr/JfrTypeRepository.java | 297 ++++++++++-------- 1 file changed, 170 insertions(+), 127 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index aa30d8145292..77a611304d11 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -42,8 +42,25 @@ /** * Repository that collects and writes used classes, packages, modules, and classloaders. + * Only one thread should ever access this repository at a time. It is only used during flushes and chunk rotations. + * This means that the maps in this repository will be entirely used and cleared with respect to the current epoch before they + * are used for the subsequent epoch. + * + * The "old" maps hold records with respect to and entire epoch, + * while the "new" maps are with respect to the current flush / chunk rotation. */ public class JfrTypeRepository implements JfrConstantPool { + private static final Set> oldClasses = new HashSet<>(); + private static final Map oldPackages = new HashMap<>(); + private static final Map oldModules = new HashMap<>(); + private static final Map oldClassLoaders = new HashMap<>(); + private static final Set> newClasses = new HashSet<>(); + private static final Map newPackages = new HashMap<>(); + private static final Map newModules = new HashMap<>(); + private static final Map newClassLoaders = new HashMap<>(); + private static long currentPackageId = 0; + private static long currentModuleId = 0; + private static long currentClassLoaderId = 0; @Platforms(Platform.HOSTED_ONLY.class) public JfrTypeRepository() { } @@ -54,147 +71,165 @@ public long getClassId(Class clazz) { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public int write(JfrChunkWriter writer, boolean flush) { // Visit all used classes, and collect their packages, modules, classloaders and possibly // referenced classes. - TypeInfo typeInfo = collectTypeInfo(flush); + collectTypeInfo(flush); // The order of writing matters as following types can be tagged during the write process - int count = writeClasses(writer, typeInfo, flush); - count += writePackages(writer, typeInfo, flush); - count += writeModules(writer, typeInfo, flush); - count += writeClassLoaders(writer, typeInfo, flush); + int count = writeClasses(writer, flush); + count += writePackages(writer, flush); + count += writeModules(writer, flush); + count += writeClassLoaders(writer, flush); count += writeGCCauses(writer); count += writeGCNames(writer); count += writeVMOperations(writer); + + if (flush) { + clearFlush(); + } else { + clearEpochChange(); + } return count; } - private TypeInfo collectTypeInfo(boolean flush) { - TypeInfo typeInfo = new TypeInfo(); + private void collectTypeInfo(boolean flush) { + for (Class clazz : Heap.getHeap().getLoadedClasses()) { if (flush) { if (JfrTraceId.isUsedCurrentEpoch(clazz)) { - visitClass(typeInfo, clazz); + visitClass(clazz); } } else if (JfrTraceId.isUsedPreviousEpoch(clazz)) { JfrTraceId.clearUsedPreviousEpoch(clazz); - visitClass(typeInfo, clazz); + visitClass(clazz); } } - return typeInfo; } - private void visitClass(TypeInfo typeInfo, Class clazz) { - if (clazz != null && typeInfo.addClass(clazz)) { - visitPackage(typeInfo, clazz.getPackage(), clazz.getModule()); - visitClass(typeInfo, clazz.getSuperclass()); + private static void visitClass(Class clazz) { + if (clazz != null && addClass(clazz)) { + visitPackage(clazz.getPackage(), clazz.getModule()); + visitClass(clazz.getSuperclass()); } } - private void visitPackage(TypeInfo typeInfo, Package pkg, Module module) { - if (pkg != null && typeInfo.addPackage(pkg, module)) { - visitModule(typeInfo, module); + private static void visitPackage(Package pkg, Module module) { + if (pkg != null && addPackage(pkg, module)) { + visitModule(module); } } - private void visitModule(TypeInfo typeInfo, Module module) { - if (module != null && typeInfo.addModule(module)) { - visitClassLoader(typeInfo, module.getClassLoader()); + private static void visitModule(Module module) { + if (module != null && addModule(module)) { + visitClassLoader(module.getClassLoader()); } } - private void visitClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { + private static void visitClassLoader(ClassLoader classLoader) { // The null class-loader is serialized as the "bootstrap" class-loader. - if (classLoader != null && typeInfo.addClassLoader(classLoader)) { - visitClass(typeInfo, classLoader.getClass()); + if (classLoader != null && addClassLoader(classLoader)) { + visitClass(classLoader.getClass()); } } - public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { - if (typeInfo.getClasses().isEmpty()) { + public static int writeClasses(JfrChunkWriter writer, boolean flush) { + if (newClasses.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.Class.getId()); - writer.writeCompressedInt(typeInfo.getClasses().size()); + writer.writeCompressedInt(newClasses.size()); - for (Class clazz : typeInfo.getClasses()) { - writeClass(writer, typeInfo, clazz, flush); + for (Class clazz : newClasses) { + writeClass(writer, clazz, flush); + oldClasses.add(clazz); } return NON_EMPTY; } - private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class clazz, boolean flush) { + private static void writeClass(JfrChunkWriter writer, Class clazz, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); // key - writer.writeCompressedLong(typeInfo.getClassLoaderId(clazz.getClassLoader())); + writer.writeCompressedLong(getClassLoaderId(clazz.getClassLoader())); writer.writeCompressedLong(symbolRepo.getSymbolId(clazz.getName(), !flush, true)); - writer.writeCompressedLong(typeInfo.getPackageId(clazz.getPackage())); + writer.writeCompressedLong(getPackageId(clazz.getPackage())); writer.writeCompressedLong(clazz.getModifiers()); if (JavaVersionUtil.JAVA_SPEC >= 17) { writer.writeBoolean(SubstrateUtil.isHiddenClass(clazz)); } } - private static int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { - Map packages = typeInfo.getPackages(); - if (packages.isEmpty()) { + private static int writePackages(JfrChunkWriter writer, boolean flush) { + if (newPackages.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.Package.getId()); - writer.writeCompressedInt(packages.size()); + writer.writeCompressedInt(newPackages.size()); - for (Map.Entry pkgInfo : packages.entrySet()) { - writePackage(writer, typeInfo, pkgInfo.getKey(), pkgInfo.getValue(), flush); + for (Map.Entry pkgInfo : newPackages.entrySet()) { + writePackage(writer, pkgInfo.getKey(), pkgInfo.getValue(), flush); + oldPackages.put(pkgInfo.getKey(),pkgInfo.getValue()); } return NON_EMPTY; } - private static void writePackage(JfrChunkWriter writer, TypeInfo typeInfo, String pkgName, PackageInfo pkgInfo, boolean flush) { + private static void writePackage(JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(pkgInfo.id); // id writer.writeCompressedLong(symbolRepo.getSymbolId(pkgName, !flush, true)); - writer.writeCompressedLong(typeInfo.getModuleId(pkgInfo.module)); + writer.writeCompressedLong(getModuleId(pkgInfo.module)); writer.writeBoolean(false); // exported } - private static int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { - Map modules = typeInfo.getModules(); - if (modules.isEmpty()) { + private static int writeModules(JfrChunkWriter writer, boolean flush) { + if (newModules.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.Module.getId()); - writer.writeCompressedInt(modules.size()); + writer.writeCompressedInt(newModules.size()); - for (Map.Entry modInfo : modules.entrySet()) { - writeModule(writer, typeInfo, modInfo.getKey(), modInfo.getValue(), flush); + for (Map.Entry modInfo : newModules.entrySet()) { + writeModule(writer, modInfo.getKey(), modInfo.getValue(), flush); + oldModules.put(modInfo.getKey(), modInfo.getValue()); } return NON_EMPTY; } - private static void writeModule(JfrChunkWriter writer, TypeInfo typeInfo, Module module, long id, boolean flush) { + private static void writeModule(JfrChunkWriter writer, Module module, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); writer.writeCompressedLong(symbolRepo.getSymbolId(module.getName(), !flush)); writer.writeCompressedLong(0); // Version, e.g. "11.0.10-internal" writer.writeCompressedLong(0); // Location, e.g. "jrt:/java.base" - writer.writeCompressedLong(typeInfo.getClassLoaderId(module.getClassLoader())); + writer.writeCompressedLong(getClassLoaderId(module.getClassLoader())); } - private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { - Map classLoaders = typeInfo.getClassLoaders(); - if (classLoaders.isEmpty()) { + private static int writeClassLoaders(JfrChunkWriter writer, boolean flush) { + if (newClassLoaders.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.ClassLoader.getId()); - writer.writeCompressedInt(classLoaders.size()); + writer.writeCompressedInt(newClassLoaders.size()); - for (Map.Entry clInfo : classLoaders.entrySet()) { + for (Map.Entry clInfo : newClassLoaders.entrySet()) { writeClassLoader(writer, clInfo.getKey(), clInfo.getValue(), flush); + oldClassLoaders.put(clInfo.getKey(), clInfo.getValue()); } return NON_EMPTY; } + private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { + JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); + writer.writeCompressedLong(id); + if (cl == null) { + writer.writeCompressedLong(0); + writer.writeCompressedLong(symbolRepo.getSymbolId("bootstrap", !flush)); + } else { + writer.writeCompressedLong(JfrTraceId.getTraceId(cl.getClass())); + writer.writeCompressedLong(symbolRepo.getSymbolId(cl.getName(), !flush)); + } + } + private static int writeGCCauses(JfrChunkWriter writer) { // GCCauses has null entries GCCause[] causes = GCCause.getGCCauses(); @@ -244,18 +279,6 @@ private static int writeVMOperations(JfrChunkWriter writer) { return NON_EMPTY; } - private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { - JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); - writer.writeCompressedLong(id); - if (cl == null) { - writer.writeCompressedLong(0); - writer.writeCompressedLong(symbolRepo.getSymbolId("bootstrap", !flush)); - } else { - writer.writeCompressedLong(JfrTraceId.getTraceId(cl.getClass())); - writer.writeCompressedLong(symbolRepo.getSymbolId(cl.getName(), !flush)); - } - } - private static class PackageInfo { private final long id; private final Module module; @@ -266,87 +289,107 @@ private static class PackageInfo { } } - private static class TypeInfo { - private final Set> classes = new HashSet<>(); - private final Map packages = new HashMap<>(); - private final Map modules = new HashMap<>(); - private final Map classLoaders = new HashMap<>(); - private long currentPackageId = 0; - private long currentModuleId = 0; - private long currentClassLoaderId = 0; - - boolean addClass(Class clazz) { - return classes.add(clazz); + static boolean addClass(Class clazz) { + if (isClassVisited(clazz)){ + return false; } + return newClasses.add(clazz); + } - Set> getClasses() { - return classes; - } + static boolean isClassVisited(Class clazz){ + return newClasses.contains(clazz) || oldClasses.contains(clazz); + } - boolean addPackage(Package pkg, Module module) { - if (!packages.containsKey(pkg.getName())) { - // The empty package represented by "" is always traced with id 0 - long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; - packages.put(pkg.getName(), new PackageInfo(id, module)); - return true; - } else { - assert module == packages.get(pkg.getName()).module; - return false; - } - } - Map getPackages() { - return packages; + static boolean addPackage(Package pkg, Module module) { + if (!isPackageVisited(pkg)) { + // The empty package represented by "" is always traced with id 0 + long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; + newPackages.put(pkg.getName(), new PackageInfo(id, module)); + return true; + } else { + assert oldPackages.containsKey(pkg.getName()) ? module == oldPackages.get(pkg.getName()).module : module == newPackages.get(pkg.getName()).module; + return false; } + } - long getPackageId(Package pkg) { - if (pkg != null) { - return packages.get(pkg.getName()).id; - } else { - return 0; - } - } + static boolean isPackageVisited(Package pkg){ + return oldPackages.containsKey(pkg.getName()) || newPackages.containsKey(pkg.getName()); + } - boolean addModule(Module module) { - if (!modules.containsKey(module)) { - modules.put(module, ++currentModuleId); - return true; - } else { - return false; + + static long getPackageId(Package pkg) { + if (pkg != null) { + if (oldPackages.containsKey(pkg.getName())) { + return oldPackages.get(pkg.getName()).id; } + return newPackages.get(pkg.getName()).id; + } else { + return 0; } + } - Map getModules() { - return modules; + static boolean addModule(Module module) { + if (!isModuleVisited(module)) { + newModules.put(module, ++currentModuleId); + return true; + } else { + return false; } + } + static boolean isModuleVisited(Module module){ + return newModules.containsKey(module) || oldModules.containsKey(module); + } - long getModuleId(Module module) { - if (module != null) { - return modules.get(module); - } else { - return 0; + static long getModuleId(Module module) { + if (module != null) { + if (oldModules.containsKey(module)) { + return oldModules.get(module); } + return newModules.get(module); + } else { + return 0; } + } - boolean addClassLoader(ClassLoader classLoader) { - if (!classLoaders.containsKey(classLoader)) { - classLoaders.put(classLoader, ++currentClassLoaderId); - return true; - } else { - return false; - } + static boolean addClassLoader(ClassLoader classLoader) { + if (!isClassLoaderVisited(classLoader)) { + newClassLoaders.put(classLoader, ++currentClassLoaderId); + return true; + } else { + return false; } + } - Map getClassLoaders() { - return classLoaders; - } + static boolean isClassLoaderVisited(ClassLoader classLoader){ + return oldClassLoaders.containsKey(classLoader) || newClassLoaders.containsKey(classLoader); + } - long getClassLoaderId(ClassLoader classLoader) { - if (classLoader != null) { - return classLoaders.get(classLoader); - } else { - return 0; + static long getClassLoaderId(ClassLoader classLoader) { + if (classLoader != null) { + if (oldClassLoaders.containsKey(classLoader)) { + return oldClassLoaders.get(classLoader); } + return newClassLoaders.get(classLoader); + } else { + return 0; } } + + private static void clearFlush() { + newModules.clear(); + newPackages.clear(); + newClassLoaders.clear(); + newClasses.clear(); + } + private static void clearEpochChange() { + clearFlush(); + oldClasses.clear(); + oldPackages.clear(); + oldModules.clear(); + oldClassLoaders.clear(); + currentPackageId = 0; + currentModuleId = 0; + currentClassLoaderId = 0; + } } From eae9640cd655d3f246a2897b4ead29a24c9cdd91 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 10 Feb 2023 13:56:46 -0500 Subject: [PATCH 35/72] only serialize new symbol table entries --- .../svm/core/jfr/JfrSymbolRepository.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 121dc85e775d..2332b57304bb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -94,6 +94,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r int hashcode = (int) (rawPointerValue ^ (rawPointerValue >>> 32)); symbol.setHash(hashcode); symbol.setBytes(null); + symbol.setSerialized(false); mutex.lockNoTransition(); try { @@ -145,42 +146,50 @@ private void maybeUnlock(boolean flush) { @Override public int write(JfrChunkWriter writer, boolean flush) { JfrSymbolHashtable table = getTable(!flush); + int count = 0; // compute byte arrays JfrSymbol[] entries = table.getTable(); for (int i = 0; i < entries.length; i++) { JfrSymbol entry = entries[i]; if (entry.isNonNull()) { while (entry.isNonNull()) { - entry.setBytes(entry.getValue().getBytes(StandardCharsets.UTF_8)); + if (!entry.getSerialized()) { + entry.setBytes(entry.getValue().getBytes(StandardCharsets.UTF_8)); + count++; + } entry = entry.getNext(); } } } - return doWrite(writer, flush, table); + return doWrite(writer, flush, table, count); } @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") - private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable table) { - maybeLock(flush); // *** locking is needed so that other thread's concurrent additions don't - // get cleared + private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable table, int count) { + maybeLock(flush); try { - if (table.getSize() == 0) { + if (count == 0) { return EMPTY; } writer.writeCompressedLong(JfrType.Symbol.getId()); - writer.writeCompressedLong(table.getSize()); + writer.writeCompressedLong(count); JfrSymbol[] entries = table.getTable(); for (int i = 0; i < entries.length; i++) { JfrSymbol entry = entries[i]; if (entry.isNonNull()) { while (entry.isNonNull()) { - writeSymbol(writer, entry); + if (!entry.getSerialized() && entry.getBytes() != null) { + writeSymbol(writer, entry); + } entry = entry.getNext(); } } } - table.clear(); // *** should be cleared only after epoch change + if (!flush) { + // Should be cleared only after epoch change + table.clear(); + } return NON_EMPTY; } finally { maybeUnlock(flush); @@ -188,14 +197,8 @@ private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable tab } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { // *** only write if - // not serialized - // before. Set - // serialized + private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { byte[] value = symbol.getBytes(); - if (value == null) { - return; - } writer.writeCompressedLong(symbol.getId()); writer.writeByte(JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); @@ -204,6 +207,7 @@ private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { // ** } writer.writeCompressedInt(value.length); writer.writeBytes(value); + symbol.setSerialized(true); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -216,7 +220,7 @@ private static void replaceDotWithSlash(byte[] utf8String) { } @RawStructure - private interface JfrSymbol extends UninterruptibleEntry { // *** add field for isSerialized? + private interface JfrSymbol extends UninterruptibleEntry { @RawField long getId(); @@ -244,6 +248,10 @@ private interface JfrSymbol extends UninterruptibleEntry { // *** add field for @RawField void setReplaceDotWithSlash(boolean value); + @RawField + boolean getSerialized(); + @RawField + void setSerialized(boolean serialized); } private static class JfrSymbolHashtable extends AbstractUninterruptibleHashtable { From f233aefe926da965b9e1aecd6ffdb916f35da994 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 10 Feb 2023 15:45:12 -0500 Subject: [PATCH 36/72] only flush new constant pool entries --- .../svm/core/jfr/JfrMethodRepository.java | 13 +++++--- .../svm/core/jfr/JfrStackTraceRepository.java | 8 ++--- .../svm/core/jfr/JfrSymbolRepository.java | 2 ++ .../svm/core/jfr/JfrTypeRepository.java | 31 ++++++++++--------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index bb61a2c00b9e..4c19e641878e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -104,6 +104,7 @@ private long getMethodId0(Class clazz, String methodName, int methodId) { /* The buffer may have been replaced with a new one. */ epochData.methodBuffer = data.getJfrBuffer(); + epochData.unflushedMethodCount++; return methodId; } @@ -127,7 +128,7 @@ public int write(JfrChunkWriter writer, boolean flush) { maybeLock(flush); try { JfrMethodEpochData epochData = getEpochData(!flush); - int count = writeMethods(writer, epochData, flush); + int count = writeMethods(writer, epochData); if (!flush) { epochData.clear(); } @@ -138,16 +139,17 @@ public int write(JfrChunkWriter writer, boolean flush) { } @Uninterruptible(reason = "May write current epoch data.") - private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData, boolean flush) { - int numberOfMethods = epochData.visitedMethods.getSize(); + private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData) { + int numberOfMethods = epochData.unflushedMethodCount; if (numberOfMethods == 0) { return EMPTY; } writer.writeCompressedLong(JfrType.Method.getId()); writer.writeCompressedInt(numberOfMethods); - writer.write(epochData.methodBuffer, !flush); + writer.write(epochData.methodBuffer); + epochData.unflushedMethodCount = 0; return NON_EMPTY; } @@ -160,10 +162,12 @@ private JfrMethodEpochData getEpochData(boolean previousEpoch) { private static class JfrMethodEpochData { private JfrBuffer methodBuffer; private final JfrVisitedTable visitedMethods; + private int unflushedMethodCount; @Platforms(Platform.HOSTED_ONLY.class) JfrMethodEpochData() { this.visitedMethods = new JfrVisitedTable(); + this.unflushedMethodCount = 0; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -181,6 +185,7 @@ void clear() { if (methodBuffer.isNonNull()) { JfrBufferAccess.reinitialize(methodBuffer); } + unflushedMethodCount = 0; } } } 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 d409452baef0..07c441e042fc 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 @@ -250,7 +250,7 @@ public int write(JfrChunkWriter writer, boolean flush) { maybeLock(flush); try { JfrStackTraceEpochData epochData = getEpochData(!flush); - int count = writeStackTraces(writer, epochData, flush); + int count = writeStackTraces(writer, epochData); if (!flush) { epochData.clear(); } @@ -261,15 +261,15 @@ public int write(JfrChunkWriter writer, boolean flush) { } @Uninterruptible(reason = "May write current epoch data.") - private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData, boolean flush) { + private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData) { if (epochData.numberOfSerializedStackTraces == 0) { return EMPTY; } writer.writeCompressedLong(JfrType.StackTrace.getId()); writer.writeCompressedInt(epochData.numberOfSerializedStackTraces); - writer.write(epochData.stackTraceBuffer, !flush); - + writer.write(epochData.stackTraceBuffer); + epochData.numberOfSerializedStackTraces = 0; return NON_EMPTY; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 2332b57304bb..7f68b1b4facd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -248,8 +248,10 @@ private interface JfrSymbol extends UninterruptibleEntry { @RawField void setReplaceDotWithSlash(boolean value); + @RawField boolean getSerialized(); + @RawField void setSerialized(boolean serialized); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 77a611304d11..1680e899867d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -41,13 +41,13 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceId; /** - * Repository that collects and writes used classes, packages, modules, and classloaders. - * Only one thread should ever access this repository at a time. It is only used during flushes and chunk rotations. - * This means that the maps in this repository will be entirely used and cleared with respect to the current epoch before they - * are used for the subsequent epoch. + * Repository that collects and writes used classes, packages, modules, and classloaders. Only one + * thread should ever access this repository at a time. It is only used during flushes and chunk + * rotations. This means that the maps in this repository will be entirely used and cleared with + * respect to the current epoch before they are used for the subsequent epoch. * - * The "old" maps hold records with respect to and entire epoch, - * while the "new" maps are with respect to the current flush / chunk rotation. + * The "old" maps hold records with respect to and entire epoch, while the "new" maps are with + * respect to the current flush / chunk rotation. */ public class JfrTypeRepository implements JfrConstantPool { private static final Set> oldClasses = new HashSet<>(); @@ -61,6 +61,7 @@ public class JfrTypeRepository implements JfrConstantPool { private static long currentPackageId = 0; private static long currentModuleId = 0; private static long currentClassLoaderId = 0; + @Platforms(Platform.HOSTED_ONLY.class) public JfrTypeRepository() { } @@ -71,7 +72,7 @@ public long getClassId(Class clazz) { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public int write(JfrChunkWriter writer, boolean flush) { // Visit all used classes, and collect their packages, modules, classloaders and possibly // referenced classes. collectTypeInfo(flush); @@ -168,7 +169,7 @@ private static int writePackages(JfrChunkWriter writer, boolean flush) { for (Map.Entry pkgInfo : newPackages.entrySet()) { writePackage(writer, pkgInfo.getKey(), pkgInfo.getValue(), flush); - oldPackages.put(pkgInfo.getKey(),pkgInfo.getValue()); + oldPackages.put(pkgInfo.getKey(), pkgInfo.getValue()); } return NON_EMPTY; } @@ -290,17 +291,16 @@ private static class PackageInfo { } static boolean addClass(Class clazz) { - if (isClassVisited(clazz)){ + if (isClassVisited(clazz)) { return false; } return newClasses.add(clazz); } - static boolean isClassVisited(Class clazz){ + static boolean isClassVisited(Class clazz) { return newClasses.contains(clazz) || oldClasses.contains(clazz); } - static boolean addPackage(Package pkg, Module module) { if (!isPackageVisited(pkg)) { // The empty package represented by "" is always traced with id 0 @@ -313,11 +313,10 @@ static boolean addPackage(Package pkg, Module module) { } } - static boolean isPackageVisited(Package pkg){ + static boolean isPackageVisited(Package pkg) { return oldPackages.containsKey(pkg.getName()) || newPackages.containsKey(pkg.getName()); } - static long getPackageId(Package pkg) { if (pkg != null) { if (oldPackages.containsKey(pkg.getName())) { @@ -337,7 +336,8 @@ static boolean addModule(Module module) { return false; } } - static boolean isModuleVisited(Module module){ + + static boolean isModuleVisited(Module module) { return newModules.containsKey(module) || oldModules.containsKey(module); } @@ -361,7 +361,7 @@ static boolean addClassLoader(ClassLoader classLoader) { } } - static boolean isClassLoaderVisited(ClassLoader classLoader){ + static boolean isClassLoaderVisited(ClassLoader classLoader) { return oldClassLoaders.containsKey(classLoader) || newClassLoaders.containsKey(classLoader); } @@ -382,6 +382,7 @@ private static void clearFlush() { newClassLoaders.clear(); newClasses.clear(); } + private static void clearEpochChange() { clearFlush(); oldClasses.clear(); From 3201ebdc03b33bce0bca041db91d414674e0c287 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 10 Feb 2023 16:52:00 -0500 Subject: [PATCH 37/72] enums and uninterruptible reasons --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 14 ++++--- .../svm/core/jfr/JfrCheckpointType.java | 38 +++++++++++++++++++ .../oracle/svm/core/jfr/JfrChunkWriter.java | 15 +++----- .../svm/test/jfr/utils/JfrFileParser.java | 5 ++- 4 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 629fded500b8..868d094136e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -112,13 +112,13 @@ public void teardown() { // TODO: maybe iterate list freeing nodes, just in case. } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Locking with no transition.", callerMustBe = true) public JfrBufferNode getAndLockHead() { acquireList(); return head; } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Should not be interrupted while flushing.") public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { JfrBufferNode next = node.getNext(); // next can never be null @@ -136,7 +136,11 @@ public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { return true; } - @Uninterruptible(reason = "Called from uninterruptible code.") + /** + * Must be uninterruptible because if this list is acquired and we safepoint for an epoch change + * in this method, the thread doing the epoch change will be blocked accessing the list. + */ + @Uninterruptible(reason = "Locking with no transition list must not be acquired entering epoch change.") public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { JfrBufferNode newNode = createNode(buffer, thread); acquireList(); @@ -151,12 +155,12 @@ public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { } } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) private void acquireList() { SpinLockUtils.lockNoTransition(this, LOCK_OFFSET); } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) public void releaseList() { SpinLockUtils.unlock(this, LOCK_OFFSET); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java new file mode 100644 index 000000000000..2c0e91228f16 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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.core.jfr; + +public enum JfrCheckpointType { + Flush(1), + Threads(8); + + private final byte id; + + public byte getId(){return id;} + JfrCheckpointType(int id) { + this.id = (byte) id; + } +} 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 ba9e73d10684..193ed9c151bf 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 @@ -68,9 +68,6 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { public static final short JFR_VERSION_MINOR = 0; private static final int CHUNK_SIZE_OFFSET = 8; private static final int FILE_STATE_OFFSET = 64; - - public static final long METADATA_TYPE_ID = 0; - public static final long CONSTANT_POOL_TYPE_ID = 1; private static final byte COMPLETE = 0; private static final short FLAG_COMPRESSED_INTS = 0b01; private static final short FLAG_CHUNK_FINAL = 0b10; @@ -287,11 +284,11 @@ private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolea if (lastCheckpointOffset.lessThan(0)) { lastCheckpointOffset = start; } - writeCompressedLong(CONSTANT_POOL_TYPE_ID); + writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); // deltaToNext - writeCompressedLong(8); // Checkpoint type is "THREADS" + writeByte(JfrCheckpointType.Threads.getId()); SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. @@ -315,11 +312,11 @@ private SignedWord writeCheckpointEvent(boolean flush) { if (lastCheckpointOffset.lessThan(0)) { lastCheckpointOffset = start; } - writeCompressedLong(CONSTANT_POOL_TYPE_ID); + writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); // deltaToNext - writeBoolean(true); // flush + writeByte(JfrCheckpointType.Flush.getId()); SignedWord poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. @@ -370,7 +367,7 @@ private void writeMetadataEvent() { return; } SignedWord start = beginEvent(); - writeCompressedLong(METADATA_TYPE_ID); + writeCompressedLong(JfrReservedEvent.EVENT_METADATA.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(metadata.getCurrentMetadataId()); // metadata id @@ -593,7 +590,7 @@ private void flushStorage(boolean safepoint) { } } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer. Locks linked list with no transition. ") private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { boolean firstIteration = true; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index fbcac8ec2fa8..3f352f8ab404 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -33,6 +33,7 @@ import java.util.HashMap; import com.oracle.svm.core.jfr.JfrChunkWriter; +import com.oracle.svm.core.jfr.JfrReservedEvent; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.JfrType; @@ -112,7 +113,7 @@ private static Positions parserFileHeader(RecordingInput input) throws IOExcepti private static void parseMetadataHeader(RecordingInput input, long metadataPosition) throws IOException { input.position(metadataPosition); // Seek to starting position of metadata region. assertTrue("Metadata size is invalid!", input.readInt() > 0); // Size of metadata. - assertEquals(JfrChunkWriter.METADATA_TYPE_ID, input.readLong()); // Metadata region ID. + assertEquals(JfrReservedEvent.EVENT_METADATA, input.readLong()); // Metadata region ID. assertTrue("Metadata timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. input.readLong(); // Metadata ID. @@ -128,7 +129,7 @@ private static long parseConstantPoolHeader(RecordingInput input, long constantP // Size of constant pools. assertTrue("Constant pool size is invalid!", input.readInt() > 0); // Constant pools region ID. - assertEquals(JfrChunkWriter.CONSTANT_POOL_TYPE_ID, input.readLong()); + assertEquals(JfrReservedEvent.EVENT_CHECKPOINT, input.readLong()); assertTrue("Constant pool timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. long deltaNext = input.readLong(); // Offset to a next constant pools region. From 03a8ce6e781f8e4d0673550e8fa2c82420d845eb Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 13 Feb 2023 17:01:37 -0500 Subject: [PATCH 38/72] tests, inject exclude field into thread --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 5 + .../oracle/svm/core/jfr/JfrChunkWriter.java | 2 +- .../oracle/svm/core/jfr/JfrReservedEvent.java | 38 +++++ .../oracle/svm/core/jfr/JfrThreadLocal.java | 16 +- .../core/thread/Target_java_lang_Thread.java | 4 + .../src/com/oracle/svm/test/jfr/JfrTest.java | 11 ++ .../oracle/svm/test/jfr/StreamingTest.java | 38 +++++ .../svm/test/jfr/TestStreamingCount.java | 146 ++++++++++++++++++ .../svm/test/jfr/events/ClassEvent.java | 6 +- .../svm/test/jfr/events/StringEvent.java | 6 +- .../svm/test/jfr/utils/JfrFileParser.java | 5 +- 11 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 868d094136e8..ecb9e2437d57 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -164,4 +164,9 @@ private void acquireList() { public void releaseList() { SpinLockUtils.unlock(this, LOCK_OFFSET); } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean isLocked() { + return lock == 1; + } } 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 193ed9c151bf..e4c3f112c79a 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 @@ -65,7 +65,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { public static final byte[] FILE_MAGIC = {'F', 'L', 'R', '\0'}; public static final short JFR_VERSION_MAJOR = 2; - public static final short JFR_VERSION_MINOR = 0; + public static final short JFR_VERSION_MINOR = 1; private static final int CHUNK_SIZE_OFFSET = 8; private static final int FILE_STATE_OFFSET = 64; private static final byte COMPLETE = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java new file mode 100644 index 000000000000..a17e7ccc4cc1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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.core.jfr; + +public enum JfrReservedEvent { + EVENT_METADATA(0), + EVENT_CHECKPOINT(1); + + private final long id; + + public long getId(){return id;} + JfrReservedEvent(long id) { + this.id = id; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 9ed33a4ace2e..1826e18a39ce 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -46,8 +46,9 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.threadlocal.FastThreadLocalObject; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; -import com.oracle.svm.core.threadlocal.FastThreadLocalInt; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.thread.Target_java_lang_Thread; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; @@ -80,7 +81,6 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalWord javaBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBufferNode"); private static final FastThreadLocalWord nativeBufferNode = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBufferNode"); private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); - private static final FastThreadLocalInt excluded = FastThreadLocalFactory.createInt("JfrThreadLocal.excluded"); /* Stacktrace-related thread-locals. */ private static final FastThreadLocalWord samplerBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.samplerBuffer"); @@ -300,7 +300,6 @@ public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { VMError.guarantee(threadLocalBuffer.isNonNull(), "TLB cannot be null if promoting."); - VMError.guarantee(!VMOperation.isInProgressAtSafepoint(), "Should not be promoting if at safepoint. "); // Needed for race between streaming flush and promotion acquireBufferWithRetry(threadLocalBuffer); @@ -372,7 +371,8 @@ public void exclude(Thread thread) { return; } IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); - excluded.set(currentIsolateThread, 1); + com.oracle.svm.core.thread.Target_java_lang_Thread tjlt = com.oracle.svm.core.SubstrateUtil.cast(thread, Target_java_lang_Thread.class); + tjlt.jfrExcluded = true; if (javaEventWriter.get(currentIsolateThread) != null && JavaVersionUtil.JAVA_SPEC >= 19) { javaEventWriter.get(currentIsolateThread).excluded = true; @@ -383,9 +383,10 @@ public void include(Thread thread) { if (!thread.equals(Thread.currentThread())) { return; } - IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); - excluded.set(currentIsolateThread, 0); + IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); + Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); + tjlt.jfrExcluded = false; if (javaEventWriter.get(currentIsolateThread) != null && JavaVersionUtil.JAVA_SPEC >= 19) { javaEventWriter.get(currentIsolateThread).excluded = false; @@ -394,7 +395,8 @@ public void include(Thread thread) { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isCurrentThreadExcluded() { - return excluded.get() == 1; + Target_java_lang_Thread tjlt = SubstrateUtil.cast(Thread.currentThread(), Target_java_lang_Thread.class); + return tjlt.jfrExcluded; } @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java index 6a7e7746098f..f4efba2b7464 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java @@ -105,6 +105,10 @@ public final class Target_java_lang_Thread { @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // long parentThreadId; + @Inject // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + public boolean jfrExcluded; + @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = ThreadData.class)// UnacquiredThreadData threadData; 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 200267c17a61..748661d27ca8 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 @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; @@ -146,6 +147,16 @@ private Path makeCopy(String testName) throws IOException { // from jdk 19 protected List getEvents() throws IOException { Path p = makeCopy(ClassUtil.getUnqualifiedName(getClass())); + return getEvents0(p); + } + + protected List getEvents(Path p) throws IOException { + List events = getEvents0(p); + Files.deleteIfExists(p); + return events; + } + + private List getEvents0(Path p) throws IOException { List events = RecordingFile.readAllEvents(p); Collections.sort(events, new ChronologicalComparator()); // remove events that are not in the list of tested events diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java new file mode 100644 index 000000000000..04ffd2a1bf09 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java @@ -0,0 +1,38 @@ +package com.oracle.svm.test.jfr; + +import jdk.jfr.consumer.RecordingStream; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicLong; +import com.oracle.svm.test.jfr.events.EndStreamEvent; + +import static org.junit.Assert.assertTrue; + +abstract class StreamingTest extends JfrTest { + protected static final int TIMEOUT_MILLIS = 3 * 1000; + protected Path dumpLocation; + protected AtomicLong emittedEvents = new AtomicLong(0); + private RecordingStream rs; + private volatile boolean streamEndedSuccessfully = false; + + protected RecordingStream createStream() { + rs = new RecordingStream(); + rs.enable("com.jfr.EndStream"); + // close stream once we get the signal + rs.onEvent("com.jfr.EndStream", e -> { + rs.close(); + streamEndedSuccessfully = true; + }); + return rs; + } + + protected void closeStream() throws InterruptedException { + // We require a signal to close the stream, because if we close the stream immediately after + // dumping, the dump may not have had time to finish. + EndStreamEvent endStreamEvent = new EndStreamEvent(); + endStreamEvent.commit(); + rs.awaitTermination(Duration.ofMillis(TIMEOUT_MILLIS)); + assertTrue("unable to find stream end event signal in stream", streamEndedSuccessfully); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java new file mode 100644 index 000000000000..3f8103ab8968 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java @@ -0,0 +1,146 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.Test; + +import com.oracle.svm.test.jfr.events.StringEvent; +import com.oracle.svm.test.jfr.events.IntegerEvent; +import com.oracle.svm.test.jfr.events.ClassEvent; +import com.oracle.svm.test.jfr.events.EndStreamEvent; + +import jdk.jfr.consumer.*; + +/** + * Check to make sure 1. All events are accounted for when using streaming (even when there are very + * many events generated). This test also forces a chunk rotation after the first flush as a sanity + * check for potential flush/rotatin clashes. + */ + +public class TestStreamingCount extends StreamingTest { + private static final int THREADS = 8; + private static final int COUNT = 1024; + private static final int EXPECTED_EVENTS = THREADS * COUNT; + private AtomicLong remainingClassEvents = new AtomicLong(EXPECTED_EVENTS); + private AtomicLong remainingIntegerEvents = new AtomicLong(EXPECTED_EVENTS); + private AtomicLong remainingStringEvents = new AtomicLong(EXPECTED_EVENTS); + volatile int flushes = 0; + + @Override + public String[] getTestedEvents() { + return new String[]{"com.jfr.String"}; + } + + @Override + public void validateEvents() throws Throwable { + // Tally up a selection of the events in the dump as a quick check they match the expected + // number. + List events = getEvents(dumpLocation); + int count = 0; + for (RecordedEvent event : events) { + count++; + } + if (count != EXPECTED_EVENTS) { + throw new Exception("Not all expected events were found in the JFR file"); + } + } + + @Test + public void test() throws Exception { + Runnable r = () -> { + for (int i = 0; i < COUNT; i++) { + StringEvent stringEvent = new StringEvent(); + stringEvent.message = "StringEvent has been generated as part of TestConcurrentEvents."; + stringEvent.commit(); + + IntegerEvent integerEvent = new IntegerEvent(); + integerEvent.number = Integer.MAX_VALUE; + integerEvent.commit(); + + ClassEvent classEvent = new ClassEvent(); + classEvent.clazz = Math.class; + classEvent.commit(); + emittedEvents.incrementAndGet(); + } + }; + + var rs = createStream(); + rs.enable("com.jfr.String"); + rs.enable("com.jfr.Integer"); + rs.enable("com.jfr.Class"); + rs.onEvent("com.jfr.Class", event -> { + remainingClassEvents.decrementAndGet(); + }); + rs.onEvent("com.jfr.Integer", event -> { + remainingIntegerEvents.decrementAndGet(); + }); + rs.onEvent("com.jfr.String", event -> { + remainingStringEvents.decrementAndGet(); + }); + + File directory = new File("."); + dumpLocation = new File(directory.getAbsolutePath(), "TestStreamingCount.jfr").toPath(); + + Runnable rotateChunk = () -> { + try { + if (flushes == 0) { + rs.dump(dumpLocation); // force chunk rotation + } + } catch (IOException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + flushes++; + }; + + rs.onFlush(rotateChunk); + rs.startAsync(); + Stressor.execute(THREADS, r); + + while (emittedEvents.get() < EXPECTED_EVENTS) { + Thread.sleep(10); + } + + int flushCount = flushes; + while (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || remainingStringEvents.get() > 0) { + assertFalse("Not all expected events were found in the stream. Class:" + remainingClassEvents.get() + " Integer:" + remainingIntegerEvents.get() + " String:" + remainingStringEvents.get(), + flushes > (flushCount + 1) && (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || remainingStringEvents.get() > 0)); + } + closeStream(); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java index ac7bd602409e..49876603e737 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java @@ -26,11 +26,9 @@ package com.oracle.svm.test.jfr.events; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.StackTrace; +import jdk.jfr.*; +@Name("com.jfr.Class") @Label("Class Event") @Description("An event with a class payload") @StackTrace(false) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java index 9e477ab0e90c..f5b43f9617ef 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java @@ -26,11 +26,9 @@ package com.oracle.svm.test.jfr.events; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.StackTrace; +import jdk.jfr.*; +@Name("com.jfr.String") @Label("String Event") @Description("An event with a string payload") @StackTrace(false) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 3f352f8ab404..cc450186d5ad 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -113,7 +113,8 @@ private static Positions parserFileHeader(RecordingInput input) throws IOExcepti private static void parseMetadataHeader(RecordingInput input, long metadataPosition) throws IOException { input.position(metadataPosition); // Seek to starting position of metadata region. assertTrue("Metadata size is invalid!", input.readInt() > 0); // Size of metadata. - assertEquals(JfrReservedEvent.EVENT_METADATA, input.readLong()); // Metadata region ID. + assertEquals(JfrReservedEvent.EVENT_METADATA.getId(), input.readLong()); // Metadata region + // ID. assertTrue("Metadata timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. input.readLong(); // Metadata ID. @@ -129,7 +130,7 @@ private static long parseConstantPoolHeader(RecordingInput input, long constantP // Size of constant pools. assertTrue("Constant pool size is invalid!", input.readInt() > 0); // Constant pools region ID. - assertEquals(JfrReservedEvent.EVENT_CHECKPOINT, input.readLong()); + assertEquals(JfrReservedEvent.EVENT_CHECKPOINT.getId(), input.readLong()); assertTrue("Constant pool timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. long deltaNext = input.readLong(); // Offset to a next constant pools region. From ed1be34e98b6d4c908ba32363aad636e32ab288b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 14 Feb 2023 12:01:28 -0500 Subject: [PATCH 39/72] fix test class name --- .../src/com/oracle/svm/test/jfr/TestClassEvent.java | 2 +- .../src/com/oracle/svm/test/jfr/TestStringEvent.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 ca34d772c0ac..8d0aca82a3a5 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 @@ -34,7 +34,7 @@ public class TestClassEvent extends JfrTest { @Override public String[] getTestedEvents() { - return new String[]{ClassEvent.class.getName()}; + return new String[]{"com.jfr.Class"}; } @Test 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 4fdbfb4ddcec..895d6a5d2b6e 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 @@ -33,7 +33,7 @@ public class TestStringEvent extends JfrTest { @Override public String[] getTestedEvents() { - return new String[]{StringEvent.class.getName()}; + return new String[]{"com.jfr.String"}; } @Test From 44aed0d63b54c8d3cddd22a4ed9872fd448175a1 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 14 Feb 2023 12:58:01 -0500 Subject: [PATCH 40/72] tests and helper classes --- .../svm/test/jfr/TestStreamingBasic.java | 148 ++++++++++++++++ .../svm/test/jfr/TestStreamingStress.java | 163 ++++++++++++++++++ .../svm/test/jfr/events/EndStreamEvent.java | 38 ++++ .../svm/test/jfr/events/IntegerEvent.java | 39 +++++ 4 files changed, 388 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java new file mode 100644 index 000000000000..9354fb70fb2f --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java @@ -0,0 +1,148 @@ +/* + * 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 jdk.jfr.consumer.*; +import org.junit.Test; + +import com.oracle.svm.core.jfr.JfrEvent; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Check to make sure + * 1. The events that are emitted are found in the stream + * 2. The resulting JFR dump is readable and events can be read that match the events that were streamed. + */ +public class TestStreamingBasic extends StreamingTest { + private static final int MILLIS = 20; + private static final int THREADS = 3; + private static final int EXPECTED_EVENTS=THREADS*2; + final Helper helper = new Helper(); + private AtomicLong remainingStringEventsInStream = new AtomicLong(EXPECTED_EVENTS); + volatile int flushes = 0; + HashSet streamEvents = new HashSet<>(); + + @Override + public String[] getTestedEvents() { + return new String[]{JfrEvent.JavaMonitorWait.getName()}; + } + + @Override + public void validateEvents() throws Throwable { + List events = getEvents(dumpLocation); + for (RecordedEvent event : events) { + String eventThread = event.getValue("eventThread").getJavaName(); + if (event.getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS-1) { + if(!streamEvents.contains(eventThread)){ + continue; + } + streamEvents.remove(eventThread); + } + } + assertTrue("Not all expected monitor wait events were found in the JFR file", streamEvents.isEmpty()); + } + + @Test + public void test() throws Exception { + Runnable r = () -> { + try { + helper.doEvent(); + emittedEvents.incrementAndGet(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + var rs = createStream(); + rs.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(MILLIS-1)).withStackTrace(); + rs.onEvent("jdk.JavaMonitorWait", event -> { + String thread = event.getThread("eventThread").getJavaName(); + if(!event.getClass("monitorClass").getName().equals(Helper.class.getName())){ + return; + } + if(streamEvents.contains(thread)) { + return; + } + streamEvents.add(thread); + remainingStringEventsInStream.decrementAndGet(); + }); + + rs.onFlush(() -> { + try { + if (flushes==0) { + Stressor.execute(THREADS, r); + // at this point all expected events should be generated + } + } catch (IOException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + flushes++; + }); + + rs.startAsync(); + Stressor.execute(THREADS, r); + + + // Wait until all events have been emitted. + while (emittedEvents.get() < EXPECTED_EVENTS) { + Thread.sleep(10); + } + int flushCount = flushes; + /* At this point we can expect to have found all the events after the 2 flushes + Scenario: Next flush is occurring while emittedEvents.get() is incremented up to EXPECTED_EVENTS + and therefore doesn't contain all the events. But the flush after the next one must contain + all remaining events. + */ + while (remainingStringEventsInStream.get() > 0) { + assertFalse ("Not all expected monitor wait events were found in the JFR stream. Remaining:" + remainingStringEventsInStream.get(), + flushes > (flushCount + 1) && remainingStringEventsInStream.get() > 0); + } + + File directory = new File("."); + dumpLocation = new File(directory.getAbsolutePath(),"TestStreaming.jfr").toPath(); + rs.dump(dumpLocation); + + closeStream(); + } + + static class Helper { + public synchronized void doEvent() throws InterruptedException { + wait(MILLIS); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java new file mode 100644 index 000000000000..7e5c02353616 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java @@ -0,0 +1,163 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.Test; + +import com.oracle.svm.test.jfr.events.ClassEvent; +import com.oracle.svm.test.jfr.events.EndStreamEvent; +import com.oracle.svm.test.jfr.events.IntegerEvent; +import com.oracle.svm.test.jfr.events.StringEvent; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; + +/** + * This test induces repeated chunk rotations and spawns several threads that create java and native + * events. The goal of this test is to repeatedly create and remove nodes from the + * JfrBufferNodeLinkedList. Unlike TestStreamingCount, each thread in this test will only do a small + * amount of work before dying. 80 Threads in total should spawn. + * + */ + +public class TestStreamingStress extends StreamingTest { + private static final int THREADS = 8; + private static final int COUNT = 10; + private static final int EXPECTED_EVENTS = THREADS * COUNT * 10; + final Helper helper = new Helper(); + private int iterations = 10; + private AtomicLong remainingClassEvents = new AtomicLong(EXPECTED_EVENTS); + private AtomicLong remainingIntegerEvents = new AtomicLong(EXPECTED_EVENTS); + private AtomicLong remainingStringEvents = new AtomicLong(EXPECTED_EVENTS); + private AtomicLong remainingWaitEvents = new AtomicLong(EXPECTED_EVENTS); + volatile int flushes = 0; + volatile boolean doneCollection = false; + + @Override + public String[] getTestedEvents() { + return new String[]{"com.jfr.String"}; + } + + @Test + public void test() throws Exception { + Runnable r = () -> { + for (int i = 0; i < COUNT; i++) { + StringEvent stringEvent = new StringEvent(); + stringEvent.message = "StringEvent has been generated as part of TestConcurrentEvents."; + stringEvent.commit(); + + IntegerEvent integerEvent = new IntegerEvent(); + integerEvent.number = Integer.MAX_VALUE; + integerEvent.commit(); + + ClassEvent classEvent = new ClassEvent(); + classEvent.clazz = Math.class; + classEvent.commit(); + try { + helper.doEvent(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + emittedEvents.incrementAndGet(); + } + }; + var rs = createStream(); + rs.enable("com.jfr.String"); + rs.enable("com.jfr.Integer"); + rs.enable("com.jfr.Class"); + rs.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofNanos(0)); + rs.onEvent("com.jfr.Class", event -> { + remainingClassEvents.decrementAndGet(); + }); + rs.onEvent("com.jfr.Integer", event -> { + remainingIntegerEvents.decrementAndGet(); + }); + rs.onEvent("com.jfr.String", event -> { + remainingStringEvents.decrementAndGet(); + }); + rs.onEvent("jdk.JavaMonitorWait", event -> { + if (!event.getClass("monitorClass").getName().equals(Helper.class.getName())) { + return; + } + remainingWaitEvents.decrementAndGet(); + }); + + File directory = new File("."); + dumpLocation = new File(directory.getAbsolutePath(), "TestStreamingStress.jfr").toPath(); + + Runnable rotateChunk = () -> { + try { + if (flushes % 3 == 0 && !doneCollection) { + rs.dump(dumpLocation); // force chunk rotation + } + } catch (IOException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + flushes++; + }; + + rs.onFlush(rotateChunk); + rs.startAsync(); + while (iterations > 0) { + Stressor.execute(THREADS, r); + iterations--; + } + while (emittedEvents.get() < EXPECTED_EVENTS) { + Thread.sleep(10); + } + + int flushCount = flushes; + while (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || remainingStringEvents.get() > 0 || remainingWaitEvents.get() > 0) { + assertFalse("Not all expected events were found in the stream. Class:" + remainingClassEvents.get() + " Integer:" + remainingIntegerEvents.get() + " String:" + + remainingStringEvents.get() + " Wait:" + remainingWaitEvents.get(), + flushes > (flushCount + 1) && (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || + remainingStringEvents.get() > 0 || remainingWaitEvents.get() > 0)); + } + doneCollection = true; + + rs.dump(dumpLocation); + closeStream(); + } + + static class Helper { + public synchronized void doEvent() throws InterruptedException { + wait(0, 1); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java new file mode 100644 index 000000000000..f3eb0ae7b0ce --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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.events; + +import jdk.jfr.*; + +@Name("com.jfr.EndStream") +@Label("End Stream Event") +@Description("Signals to end stream") +@StackTrace(false) +public class EndStreamEvent extends Event { + @Label("Message") + public String message; +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java new file mode 100644 index 000000000000..712a785ccba0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java @@ -0,0 +1,39 @@ +/* + * 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.events; + +import jdk.jfr.*; + +@Name("com.jfr.Integer") +@Label("Integer Event") +@Description("An event with an integer payload") +@StackTrace(true) +public class IntegerEvent extends Event { + + @Label("Number") + public int number; +} From d3b78d812aec1c7ae66374722e7a11c1bdf05006 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 14 Feb 2023 13:39:49 -0500 Subject: [PATCH 41/72] fix minor things from merge --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 8 ++--- .../oracle/svm/core/jfr/JfrChunkWriter.java | 2 +- .../svm/core/jfr/JfrGCCauseSerializer.java | 2 +- .../svm/core/jfr/JfrGCNameSerializer.java | 2 +- .../JfrMonitorInflationCauseSerializer.java | 2 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 34 +++++++++---------- .../jfr/JfrVMOperationNameSerializer.java | 2 +- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index ecb9e2437d57..0d51f5a07d64 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -37,8 +37,7 @@ import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; import org.graalvm.word.PointerBase; -import com.oracle.svm.core.thread.SpinLockUtils; - +import com.oracle.svm.core.thread.JavaSpinLockUtils; public class JfrBufferNodeLinkedList { @RawStructure public interface JfrBufferNode extends PointerBase { @@ -65,7 +64,6 @@ public interface JfrBufferNode extends PointerBase { @RawField void setAlive(boolean alive); - } private static final long LOCK_OFFSET; @@ -157,12 +155,12 @@ public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) private void acquireList() { - SpinLockUtils.lockNoTransition(this, LOCK_OFFSET); + JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); } @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) public void releaseList() { - SpinLockUtils.unlock(this, LOCK_OFFSET); + JavaSpinLockUtils.unlock(this, LOCK_OFFSET); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) 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 8fccc4ddf333..35db625c1929 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 @@ -587,7 +587,7 @@ private void flushStorage(boolean safepoint) { } write(buffer); JfrBufferAccess.reinitialize(buffer); - JfrBufferAccess.release(buffer); + JfrBufferAccess.unlock(buffer); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java index 8163fc5def80..9f7102afdd09 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java @@ -35,7 +35,7 @@ public JfrGCCauseSerializer() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { // GCCauses has null entries GCCause[] causes = GCCause.getGCCauses(); int nonNullItems = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java index f98e51c74655..1b66e31f04cb 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java @@ -33,7 +33,7 @@ public JfrGCNameSerializer() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { JfrGCName[] gcNames = JfrGCNames.singleton().getNames(); assert gcNames != null && gcNames.length > 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java index 73b0af20c249..f3c21535e0fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java @@ -37,7 +37,7 @@ public JfrMonitorInflationCauseSerializer() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedLong(JfrType.MonitorInflationCause.getId()); MonitorInflationCause[] inflationCauses = MonitorInflationCause.values(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index fb39ae332380..a4abb53f9ea0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -134,24 +134,24 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { public static void stopRecording(IsolateThread isolateThread) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ - if (flushBuffers) { - JfrBufferNode jbn = javaBufferNode.get(isolateThread); - JfrBufferNode nbn = nativeBufferNode.get(isolateThread); - if (jbn.isNonNull()) { - JfrBuffer jb = jbn.getValue(); - assert jb.isNonNull() && jbn.getAlive(); - flush(jb, WordFactory.unsigned(0), 0); - jbn.setAlive(false); + JfrBufferNode jbn = javaBufferNode.get(isolateThread); + JfrBufferNode nbn = nativeBufferNode.get(isolateThread); + + if (jbn.isNonNull()) { + JfrBuffer jb = jbn.getValue(); + assert jb.isNonNull() && jbn.getAlive(); + flush(jb, WordFactory.unsigned(0), 0); + jbn.setAlive(false); - } - if (nbn.isNonNull()) { - JfrBuffer nb = nbn.getValue(); - assert nb.isNonNull() && nbn.getAlive(); - flush(nb, WordFactory.unsigned(0), 0); - nbn.setAlive(false); - } } + if (nbn.isNonNull()) { + JfrBuffer nb = nbn.getValue(); + assert nb.isNonNull() && nbn.getAlive(); + flush(nb, WordFactory.unsigned(0), 0); + nbn.setAlive(false); + } + /* Clear event-related thread-locals. */ dataLost.set(isolateThread, WordFactory.unsigned(0)); @@ -288,7 +288,7 @@ public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { } return true; } finally { - JfrBufferAccess.release(threadLocalBuffer); + JfrBufferAccess.unlock(threadLocalBuffer); } } @@ -322,7 +322,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit return WordFactory.nullPointer(); } finally { - JfrBufferAccess.release(threadLocalBuffer); + JfrBufferAccess.unlock(threadLocalBuffer); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java index dc6c144cad3f..59c10317b233 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java @@ -35,7 +35,7 @@ public JfrVMOperationNameSerializer() { } @Override - public int write(JfrChunkWriter writer) { + public int write(JfrChunkWriter writer, boolean flush) { String[] vmOperationNames = VMOperationInfos.getNames(); assert vmOperationNames.length > 0; writer.writeCompressedLong(JfrType.VMOperation.getId()); From 87d6a071ec68347c1ee97d6cdef20e452663fc4a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 14 Feb 2023 15:08:18 -0500 Subject: [PATCH 42/72] unit test for linked list --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 13 +++++++++++-- .../src/com/oracle/svm/core/jfr/JfrThreadLocal.java | 4 ---- .../com/oracle/svm/core/jfr/JfrTypeRepository.java | 2 -- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 0d51f5a07d64..0070a2927d4b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -38,6 +38,15 @@ import com.oracle.svm.core.util.VMError; import org.graalvm.word.PointerBase; import com.oracle.svm.core.thread.JavaSpinLockUtils; + +/** + * JfrBufferNodeLinkedList is a singly linked list used to store thread local JFR buffers. Threads + * shall only add one node to the list. Only the thread performing a flush or epoch change shall + * iterate this list and is allowed to remove nodes. There is a list-level lock that is acquired + * when adding nodes, and when beginning iteration at the head. Threads may access their own nodes + * at any time up until they call JfrBufferNode.setAlive(false). The list lock must not be held at a + * safepoint. + */ public class JfrBufferNodeLinkedList { @RawStructure public interface JfrBufferNode extends PointerBase { @@ -80,7 +89,7 @@ public interface JfrBufferNode extends PointerBase { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isHead(JfrBufferNode node) { - return node == head; + return node == head || head.isNull(); } @Uninterruptible(reason = "Called from uninterruptible code.") @@ -122,7 +131,7 @@ public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { if (isHead(node)) { VMError.guarantee(prev.isNull(), "If head, prev should be null "); - setHead(next); // head could now be tail if there was only one node in the list + setHead(next); // head could now be null if there was only one node in the list } else { VMError.guarantee(prev.isNonNull(), "If not head, prev should be non-null "); prev.setNext(next); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index a4abb53f9ea0..d0efd98ca55f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -134,7 +134,6 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { public static void stopRecording(IsolateThread isolateThread) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ - JfrBufferNode jbn = javaBufferNode.get(isolateThread); JfrBufferNode nbn = nativeBufferNode.get(isolateThread); @@ -152,7 +151,6 @@ public static void stopRecording(IsolateThread isolateThread) { nbn.setAlive(false); } - /* Clear event-related thread-locals. */ dataLost.set(isolateThread, WordFactory.unsigned(0)); javaEventWriter.set(isolateThread, null); @@ -267,8 +265,6 @@ public static void notifyEventWriter(IsolateThread thread) { } } - - /** * This method only copies the JFR buffer's unflushed data to the global buffers. This can be * used outside a safepoint from the flushing thread while other threads continue writing diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index ef72c1b29313..314d6f0daa5e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -81,7 +81,6 @@ public int write(JfrChunkWriter writer, boolean flush) { count += writeModules(writer, flush); count += writeClassLoaders(writer, flush); - if (flush) { clearFlush(); } else { @@ -228,7 +227,6 @@ private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long } } - private static class PackageInfo { private final long id; private final Module module; From 5b22ef9c607e4eaafd3ce05ba849e7868c85d776 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 14 Feb 2023 15:19:17 -0500 Subject: [PATCH 43/72] unit tests --- .../src/com/oracle/svm/test/jfr/Stressor.java | 47 ++++++ .../test/jfr/TestJfrBufferNodeLinkedList.java | 134 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java new file mode 100644 index 000000000000..f030ced075de --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 java.util.ArrayList; +import java.util.List; + +/** + * Class to help run multiple threads executing some task + */ +public class Stressor { + public static void execute(int numberOfThreads, Runnable task) throws Exception { + List threads = new ArrayList<>(); + for (int n = 0; n < numberOfThreads; ++n) { + Thread t = new Thread(task); + threads.add(t); + t.start(); + } + for (Thread t : threads) { + t.join(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java new file mode 100644 index 000000000000..c029572a1c16 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 org.junit.Test; + +import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; +import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; +import org.graalvm.word.WordFactory; +import com.oracle.svm.core.jfr.JfrBufferType; +import com.oracle.svm.core.jfr.JfrBuffer; +import com.oracle.svm.core.jfr.JfrBufferAccess; +import org.graalvm.nativeimage.CurrentIsolate; +import static org.junit.Assert.assertTrue; +import com.oracle.svm.core.Uninterruptible; + +public class TestJfrBufferNodeLinkedList { + + @Test + public void testBasicAdditionAndRemoval() { + final int nodeCount = 10; + final JfrBufferNodeLinkedList list = new JfrBufferNodeLinkedList(); + addNodes(list, nodeCount); + int count = countNodes(list); + assertTrue("Number of nodes in list does not match nodes added.", count == nodeCount); + cleanUpList(list); + } + + @Test + public void testMiddleRemoval() { + final int nodeCount = 10; + JfrBufferNodeLinkedList list = new JfrBufferNodeLinkedList(); + addNodes(list, nodeCount); + removeNthNode(list, nodeCount / 2); + assertTrue("Removal from middle failed", countNodes(list) == nodeCount - 1); + cleanUpList(list); + } + + @Test + public void testConcurrentAddition() throws Exception { + final int nodeCountPerThread = 10; + final int threads = 10; + JfrBufferNodeLinkedList list = new JfrBufferNodeLinkedList(); + Runnable r = () -> { + addNodes(list, nodeCountPerThread); + }; + Stressor.execute(threads, r); + assertTrue("Incorrect number of nodes added", countNodes(list) == nodeCountPerThread * threads); + cleanUpList(list); + } + + private void cleanUpList(JfrBufferNodeLinkedList list) { + JfrBufferNode node = removeAllNodes(list); + assertTrue("Could not remove all nodes", node.isNull()); + list.teardown(); + } + private void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { + for (int i = 0; i < nodeCount; i++) { + JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(32), JfrBufferType.THREAD_LOCAL_NATIVE); + list.addNode(buffer, CurrentIsolate.getCurrentThread()); + } + } + + @Uninterruptible(reason = "Locking with no transition.") + private int countNodes(JfrBufferNodeLinkedList list) { + int count = 0; + JfrBufferNode node = list.getAndLockHead(); + while (node.isNonNull()) { + count++; + node = node.getNext(); + } + + list.releaseList(); + return count; + } + + @Uninterruptible(reason = "Locking with no transition.") + private JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { + // Try removing the nodes + JfrBufferNode node; + JfrBufferNode prev = WordFactory.nullPointer(); + node = list.getAndLockHead(); + while (node.isNonNull()) { + JfrBufferNode next = node.getNext(); + list.removeNode(node, prev); + node = next; + } + list.releaseList(); + return node; + } + + @Uninterruptible(reason = "Locking with no transition.") + private void removeNthNode(JfrBufferNodeLinkedList list, int target) { + JfrBufferNode node; + JfrBufferNode prev = WordFactory.nullPointer(); + node = list.getAndLockHead(); + int count = 0; + while (node.isNonNull()) { + JfrBufferNode next = node.getNext(); + if (count == target) { + list.removeNode(node, prev); + break; + } + prev = node; + node = next; + count++; + } + list.releaseList(); + } +} From 9cafc2795e09c8bf7b67b163e601a382c668b5cb Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 15 Feb 2023 11:58:25 -0500 Subject: [PATCH 44/72] update constant pool parsing test --- .../svm/core/jfr/JfrCheckpointType.java | 5 ++- .../oracle/svm/core/jfr/JfrReservedEvent.java | 7 ++- .../oracle/svm/test/jfr/StreamingTest.java | 25 +++++++++++ .../src/com/oracle/svm/test/jfr/Stressor.java | 2 +- .../test/jfr/TestJfrBufferNodeLinkedList.java | 1 + .../svm/test/jfr/TestStreamingBasic.java | 45 ++++++++++--------- .../svm/test/jfr/TestStreamingCount.java | 7 +-- .../svm/test/jfr/TestStreamingStress.java | 7 --- .../svm/test/jfr/events/ClassEvent.java | 6 ++- .../svm/test/jfr/events/EndStreamEvent.java | 6 ++- .../svm/test/jfr/events/IntegerEvent.java | 6 ++- .../svm/test/jfr/events/StringEvent.java | 6 ++- .../svm/test/jfr/utils/JfrFileParser.java | 17 ++++++- 13 files changed, 96 insertions(+), 44 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java index 2c0e91228f16..8f5372502d3c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java @@ -31,7 +31,10 @@ public enum JfrCheckpointType { private final byte id; - public byte getId(){return id;} + public byte getId() { + return id; + } + JfrCheckpointType(int id) { this.id = (byte) id; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java index a17e7ccc4cc1..e8fc93c390a3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java @@ -31,8 +31,11 @@ public enum JfrReservedEvent { private final long id; - public long getId(){return id;} + public long getId() { + return id; + } + JfrReservedEvent(long id) { - this.id = id; + this.id = id; } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java index 04ffd2a1bf09..ebdc79d34e8f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 jdk.jfr.consumer.RecordingStream; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java index f030ced075de..0646901bfeb1 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java @@ -30,7 +30,7 @@ import java.util.List; /** - * Class to help run multiple threads executing some task + * Class to help run multiple threads executing some task. */ public class Stressor { public static void execute(int numberOfThreads, Runnable task) throws Exception { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index c029572a1c16..97797ec5098d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -78,6 +78,7 @@ private void cleanUpList(JfrBufferNodeLinkedList list) { assertTrue("Could not remove all nodes", node.isNull()); list.teardown(); } + private void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { for (int i = 0; i < nodeCount; i++) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(32), JfrBufferType.THREAD_LOCAL_NATIVE); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java index 9354fb70fb2f..43613859a2db 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 @@ -29,7 +29,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import jdk.jfr.consumer.*; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; +import jdk.jfr.consumer.RecordedClass; import org.junit.Test; import com.oracle.svm.core.jfr.JfrEvent; @@ -42,14 +44,13 @@ import java.util.concurrent.atomic.AtomicLong; /** - * Check to make sure - * 1. The events that are emitted are found in the stream - * 2. The resulting JFR dump is readable and events can be read that match the events that were streamed. + * Check to make sure 1. The events that are emitted are found in the stream 2. The resulting JFR + * dump is readable and events can be read that match the events that were streamed. */ public class TestStreamingBasic extends StreamingTest { private static final int MILLIS = 20; private static final int THREADS = 3; - private static final int EXPECTED_EVENTS=THREADS*2; + private static final int EXPECTED_EVENTS = THREADS * 2; final Helper helper = new Helper(); private AtomicLong remainingStringEventsInStream = new AtomicLong(EXPECTED_EVENTS); volatile int flushes = 0; @@ -64,9 +65,9 @@ public String[] getTestedEvents() { public void validateEvents() throws Throwable { List events = getEvents(dumpLocation); for (RecordedEvent event : events) { - String eventThread = event.getValue("eventThread").getJavaName(); - if (event.getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS-1) { - if(!streamEvents.contains(eventThread)){ + String eventThread = event. getValue("eventThread").getJavaName(); + if (event. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS - 1) { + if (!streamEvents.contains(eventThread)) { continue; } streamEvents.remove(eventThread); @@ -87,13 +88,13 @@ public void test() throws Exception { }; var rs = createStream(); - rs.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(MILLIS-1)).withStackTrace(); + rs.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(MILLIS - 1)).withStackTrace(); rs.onEvent("jdk.JavaMonitorWait", event -> { String thread = event.getThread("eventThread").getJavaName(); - if(!event.getClass("monitorClass").getName().equals(Helper.class.getName())){ + if (!event.getClass("monitorClass").getName().equals(Helper.class.getName())) { return; } - if(streamEvents.contains(thread)) { + if (streamEvents.contains(thread)) { return; } streamEvents.add(thread); @@ -102,7 +103,7 @@ public void test() throws Exception { rs.onFlush(() -> { try { - if (flushes==0) { + if (flushes == 0) { Stressor.execute(THREADS, r); // at this point all expected events should be generated } @@ -117,24 +118,24 @@ public void test() throws Exception { rs.startAsync(); Stressor.execute(THREADS, r); - // Wait until all events have been emitted. while (emittedEvents.get() < EXPECTED_EVENTS) { Thread.sleep(10); } int flushCount = flushes; - /* At this point we can expect to have found all the events after the 2 flushes - Scenario: Next flush is occurring while emittedEvents.get() is incremented up to EXPECTED_EVENTS - and therefore doesn't contain all the events. But the flush after the next one must contain - all remaining events. + /* + * At this point we can expect to have found all the events after the 2 flushes Scenario: + * Next flush is occurring while emittedEvents.get() is incremented up to EXPECTED_EVENTS + * and therefore doesn't contain all the events. But the flush after the next one must + * contain all remaining events. */ while (remainingStringEventsInStream.get() > 0) { - assertFalse ("Not all expected monitor wait events were found in the JFR stream. Remaining:" + remainingStringEventsInStream.get(), - flushes > (flushCount + 1) && remainingStringEventsInStream.get() > 0); + assertFalse("Not all expected monitor wait events were found in the JFR stream. Remaining:" + remainingStringEventsInStream.get(), + flushes > (flushCount + 1) && remainingStringEventsInStream.get() > 0); } File directory = new File("."); - dumpLocation = new File(directory.getAbsolutePath(),"TestStreaming.jfr").toPath(); + dumpLocation = new File(directory.getAbsolutePath(), "TestStreaming.jfr").toPath(); rs.dump(dumpLocation); closeStream(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java index 3f8103ab8968..d411f542409d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java @@ -27,12 +27,10 @@ package com.oracle.svm.test.jfr; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; + import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -41,9 +39,8 @@ import com.oracle.svm.test.jfr.events.StringEvent; import com.oracle.svm.test.jfr.events.IntegerEvent; import com.oracle.svm.test.jfr.events.ClassEvent; -import com.oracle.svm.test.jfr.events.EndStreamEvent; -import jdk.jfr.consumer.*; +import jdk.jfr.consumer.RecordedEvent; /** * Check to make sure 1. All events are accounted for when using streaming (even when there are very diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java index 7e5c02353616..c7cb94196c75 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java @@ -27,25 +27,18 @@ package com.oracle.svm.test.jfr; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; -import java.nio.file.Path; import java.time.Duration; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; import com.oracle.svm.test.jfr.events.ClassEvent; -import com.oracle.svm.test.jfr.events.EndStreamEvent; import com.oracle.svm.test.jfr.events.IntegerEvent; import com.oracle.svm.test.jfr.events.StringEvent; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordingStream; - /** * This test induces repeated chunk rotations and spawns several threads that create java and native * events. The goal of this test is to repeatedly create and remove nodes from the diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java index 49876603e737..032a0aa3f9c7 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/ClassEvent.java @@ -26,7 +26,11 @@ package com.oracle.svm.test.jfr.events; -import jdk.jfr.*; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; +import jdk.jfr.Description; +import jdk.jfr.Event; @Name("com.jfr.Class") @Label("Class Event") diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java index f3eb0ae7b0ce..930b4f50b8d9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java @@ -26,7 +26,11 @@ package com.oracle.svm.test.jfr.events; -import jdk.jfr.*; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; +import jdk.jfr.Description; +import jdk.jfr.Event; @Name("com.jfr.EndStream") @Label("End Stream Event") diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java index 712a785ccba0..d085b256f46b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java @@ -26,7 +26,11 @@ package com.oracle.svm.test.jfr.events; -import jdk.jfr.*; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; +import jdk.jfr.Description; +import jdk.jfr.Event; @Name("com.jfr.Integer") @Label("Integer Event") diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java index f5b43f9617ef..749974b1c64f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StringEvent.java @@ -26,7 +26,11 @@ package com.oracle.svm.test.jfr.events; -import jdk.jfr.*; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; +import jdk.jfr.Description; +import jdk.jfr.Event; @Name("com.jfr.String") @Label("String Event") diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 052ca80a570a..4c0db56243ab 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -30,7 +30,9 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import org.junit.Assert; @@ -147,11 +149,23 @@ private static void compareFoundAndExpectedIds() { } } + /** + * Must verify constant pools in order that they were written because event streaming can write + * pools before the chunk is finished. This means that a given pool may reference constants from + * another pool written previously (within the same chunk). + */ private static void verifyConstantPools(RecordingInput input, long constantPoolPosition) throws IOException { + List poolPositions = new ArrayList<>(); long deltaNext; long currentConstantPoolPosition = constantPoolPosition; do { + poolPositions.add(currentConstantPoolPosition); deltaNext = parseConstantPoolHeader(input, currentConstantPoolPosition); + currentConstantPoolPosition += deltaNext; + } while (deltaNext != 0); + + for (int j = poolPositions.size() - 1; j > 0; j--) { + parseConstantPoolHeader(input, poolPositions.get(j)); long numberOfCPs = input.readInt(); for (int i = 0; i < numberOfCPs; i++) { ConstantPoolParser constantPoolParser = supportedConstantPools.get(input.readLong()); @@ -159,8 +173,7 @@ private static void verifyConstantPools(RecordingInput input, long constantPoolP constantPoolParser.parse(input); } compareFoundAndExpectedIds(); - currentConstantPoolPosition += deltaNext; - } while (deltaNext != 0); + } } public static void parse(Recording recording) throws IOException { From f72616b53d13f398a4f5d503d9103058c3ffc7f7 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 15 Feb 2023 16:28:24 -0500 Subject: [PATCH 45/72] rename JFR buffer pointers. Add assertions to ensure buffers are locked when they should be --- .../com/oracle/svm/core/jfr/JfrBuffer.java | 16 ++++-- .../oracle/svm/core/jfr/JfrBufferAccess.java | 54 +++++++++++++------ .../oracle/svm/core/jfr/JfrChunkWriter.java | 8 +-- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 8 +-- .../svm/core/jfr/JfrNativeEventWriter.java | 16 +++--- .../jfr/JfrNativeEventWriterDataAccess.java | 6 +-- .../oracle/svm/core/jfr/JfrThreadLocal.java | 6 +-- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 10 ++-- .../svm/test/jfr/TestStreamingBasic.java | 9 ++-- .../svm/test/jfr/TestStreamingCount.java | 2 +- 10 files changed, 80 insertions(+), 55 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index 19792fe03a8d..4b7f247c1d1b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -33,6 +33,7 @@ import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.util.VMError; +import org.graalvm.nativeimage.IsolateThread; /** * A {@link JfrBuffer} is a block of native memory (either thread-local or global) into which JFR @@ -57,16 +58,16 @@ public interface JfrBuffer extends PointerBase { * Returns the committed position. Any data before this position is valid event data. */ @RawField - Pointer getPos(); + Pointer getCommittedPos(); /** * Sets the committed position. */ @RawField - void setPos(Pointer value); + void setCommittedPos(Pointer value); @RawFieldOffset - static int offsetOfPos() { + static int offsetOfCommittedPos() { throw VMError.unimplemented(); // replaced } @@ -75,13 +76,13 @@ static int offsetOfPos() { * some other buffer or to the disk. */ @RawField - Pointer getTop(); + Pointer getFlushedPos(); /** * Sets the position of unflushed data. */ @RawField - void setTop(Pointer value); + void setFlushedPos(Pointer value); @RawField int getLocked(); @@ -104,4 +105,9 @@ static int offsetOfLocked() { @RawField @PinnedObjectField void setBufferType(JfrBufferType bufferType); + @RawField + void setLockOwner(IsolateThread thread); + + @RawField + IsolateThread getLockOwner(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index f528931bdaee..1b966d05cc69 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -37,6 +37,7 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.thread.NativeSpinLockUtils; import com.oracle.svm.core.util.UnsignedUtils; +import org.graalvm.nativeimage.CurrentIsolate; /** * Used to access the raw memory of a {@link JfrBuffer}. @@ -64,7 +65,9 @@ public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType result.setSize(dataSize); result.setBufferType(bufferType); NativeSpinLockUtils.initialize(ptrToLock(result)); + tryLock(result, Integer.MAX_VALUE); reinitialize(result); + unlock(result); } return result; } @@ -77,9 +80,12 @@ public static void free(JfrBuffer buffer) { @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") public static void reinitialize(JfrBuffer buffer) { assert buffer.isNonNull(); + assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || + (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + org.graalvm.nativeimage.CurrentIsolate.getCurrentThread(); Pointer pos = getDataStart(buffer); - buffer.setPos(pos); - buffer.setTop(pos); + buffer.setCommittedPos(pos); + buffer.setFlushedPos(pos); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -91,25 +97,36 @@ public static boolean isLocked(JfrBuffer buffer) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static boolean tryLock(JfrBuffer buffer) { assert buffer.isNonNull(); - return NativeSpinLockUtils.tryLock(ptrToLock(buffer)); + boolean result = NativeSpinLockUtils.tryLock(ptrToLock(buffer)); + if (result) { + buffer.setLockOwner(org.graalvm.nativeimage.CurrentIsolate.getCurrentThread()); + } + return result; } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static boolean tryLock(JfrBuffer buffer, int retries) { assert buffer.isNonNull(); - return NativeSpinLockUtils.tryLock(ptrToLock(buffer), retries); + boolean result = NativeSpinLockUtils.tryLock(ptrToLock(buffer), retries); + if (result) { + buffer.setLockOwner(org.graalvm.nativeimage.CurrentIsolate.getCurrentThread()); + } + return result; } @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void unlock(JfrBuffer buffer) { - assert buffer.isNonNull() && isLocked(buffer); + assert buffer.isNonNull(); + assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || + (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + NativeSpinLockUtils.unlock(ptrToLock(buffer)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static Pointer getAddressOfPos(JfrBuffer buffer) { + public static Pointer getAddressOfCommittedPos(JfrBuffer buffer) { assert buffer.isNonNull(); - return ((Pointer) buffer).add(JfrBuffer.offsetOfPos()); + return ((Pointer) buffer).add(JfrBuffer.offsetOfCommittedPos()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -127,31 +144,34 @@ public static Pointer getDataEnd(JfrBuffer buffer) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord getAvailableSize(JfrBuffer buffer) { assert buffer.isNonNull(); - return getDataEnd(buffer).subtract(buffer.getPos()); + return getDataEnd(buffer).subtract(buffer.getCommittedPos()); } @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.", callerMustBe = true) public static UnsignedWord getUnflushedSize(JfrBuffer buffer) { assert buffer.isNonNull(); - return buffer.getPos().subtract(buffer.getTop()); + return buffer.getCommittedPos().subtract(buffer.getFlushedPos()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void increasePos(JfrBuffer buffer, UnsignedWord delta) { + public static void increaseCommittedPos(JfrBuffer buffer, UnsignedWord delta) { assert buffer.isNonNull(); - buffer.setPos(buffer.getPos().add(delta)); + buffer.setCommittedPos(buffer.getCommittedPos().add(delta)); } @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") - public static void increaseTop(JfrBuffer buffer, UnsignedWord delta) { + public static void increaseFlushedPos(JfrBuffer buffer, UnsignedWord delta) { assert buffer.isNonNull(); - buffer.setTop(buffer.getTop().add(delta)); + assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || + (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + + buffer.setFlushedPos(buffer.getFlushedPos().add(delta)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isEmpty(JfrBuffer buffer) { assert buffer.isNonNull(); - return getDataStart(buffer).equal(buffer.getPos()); + return getDataStart(buffer).equal(buffer.getCommittedPos()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -162,9 +182,9 @@ public static boolean verify(JfrBuffer buffer) { Pointer start = getDataStart(buffer); Pointer end = getDataEnd(buffer); - return buffer.getPos().aboveOrEqual(start) && buffer.getPos().belowOrEqual(end) && - buffer.getTop().aboveOrEqual(start) && buffer.getTop().belowOrEqual(end) && - buffer.getTop().belowOrEqual(buffer.getPos()); + return buffer.getCommittedPos().aboveOrEqual(start) && buffer.getCommittedPos().belowOrEqual(end) && + buffer.getFlushedPos().aboveOrEqual(start) && buffer.getFlushedPos().belowOrEqual(end) && + buffer.getFlushedPos().belowOrEqual(buffer.getCommittedPos()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) 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 35db625c1929..113ce6d6cd25 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 @@ -149,16 +149,16 @@ public boolean write(JfrBuffer buffer) { } @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") - public boolean write(JfrBuffer buffer, boolean increaseTop) { + public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { assert JfrBufferAccess.isLocked(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { return false; } - boolean success = getFileSupport().write(fd, buffer.getTop(), unflushedSize); - if (increaseTop) { - JfrBufferAccess.increaseTop(buffer, unflushedSize); + boolean success = getFileSupport().write(fd, buffer.getFlushedPos(), unflushedSize); + if (increaseFlushedPos) { + JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); } if (!success) { // We lost some data because the write failed. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index febbf5c8d7a5..382f0758e7ba 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -65,7 +65,7 @@ public void initialize(long globalBufferSize, long globalBufferCount) { buffers.addressOf(i).write(buffer); } } - + @Uninterruptible(reason = "Locks without transition.") public void clear() { assert VMOperation.isInProgressAtSafepoint(); @@ -108,13 +108,13 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo try { // Copy all committed but not yet flushed memory to the promotion buffer. assert JfrBufferAccess.getAvailableSize(promotionBuffer).aboveOrEqual(unflushedSize); - UnmanagedMemoryUtil.copy(threadLocalBuffer.getTop(), promotionBuffer.getPos(), unflushedSize); - JfrBufferAccess.increasePos(promotionBuffer, unflushedSize); + UnmanagedMemoryUtil.copy(threadLocalBuffer.getFlushedPos(), promotionBuffer.getCommittedPos(), unflushedSize); + JfrBufferAccess.increaseCommittedPos(promotionBuffer, unflushedSize); shouldSignal = recorderThread.shouldSignal(promotionBuffer); } finally { releasePromotionBuffer(promotionBuffer); } - JfrBufferAccess.increaseTop(threadLocalBuffer, unflushedSize); + JfrBufferAccess.increaseFlushedPos(threadLocalBuffer, unflushedSize); // Notify the thread that writes the global memory to disk. // If we're flushing, the global buffers are about to get persisted anyway if (shouldSignal && !doingFlush) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index 21bcc88adedc..a305677ec3c3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -262,8 +262,8 @@ private static void reserve(JfrNativeEventWriterData data, int size) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) private static void hardReset(JfrNativeEventWriterData data) { JfrBuffer buffer = data.getJfrBuffer(); - data.setStartPos(buffer.getPos()); - data.setCurrentPos(buffer.getPos()); + data.setStartPos(buffer.getCommittedPos()); + data.setCurrentPos(buffer.getCommittedPos()); data.setEndPos(JfrBufferAccess.getDataEnd(buffer)); } @@ -324,8 +324,8 @@ private static JfrBuffer reuseOrReallocateBuffer(JfrBuffer oldBuffer, UnsignedWo // Copy all unflushed data (no matter if committed or uncommitted) from the old buffer // to the new buffer. - UnmanagedMemoryUtil.copy(oldBuffer.getTop(), result.getPos(), totalUsedBytes); - JfrBufferAccess.increasePos(result, unflushedSize); + UnmanagedMemoryUtil.copy(oldBuffer.getFlushedPos(), result.getCommittedPos(), totalUsedBytes); + JfrBufferAccess.increaseCommittedPos(result, unflushedSize); JfrBufferAccess.free(oldBuffer); @@ -335,9 +335,9 @@ private static JfrBuffer reuseOrReallocateBuffer(JfrBuffer oldBuffer, UnsignedWo // Reuse the existing buffer because enough data was already flushed in the meanwhile. // For that, copy all unflushed data (no matter if committed or uncommitted) to the // beginning of the buffer. - UnmanagedMemoryUtil.copy(oldBuffer.getTop(), JfrBufferAccess.getDataStart(oldBuffer), totalUsedBytes); + UnmanagedMemoryUtil.copy(oldBuffer.getFlushedPos(), JfrBufferAccess.getDataStart(oldBuffer), totalUsedBytes); JfrBufferAccess.reinitialize(oldBuffer); - JfrBufferAccess.increasePos(oldBuffer, unflushedSize); + JfrBufferAccess.increaseCommittedPos(oldBuffer, unflushedSize); return oldBuffer; } } @@ -361,11 +361,11 @@ private static void commitEvent(JfrNativeEventWriterData data) { assert isValid(data); JfrBuffer buffer = data.getJfrBuffer(); - assert buffer.getPos().equal(data.getStartPos()); + assert buffer.getCommittedPos().equal(data.getStartPos()); assert JfrBufferAccess.getDataEnd(data.getJfrBuffer()).equal(data.getEndPos()); Pointer newPosition = data.getCurrentPos(); - buffer.setPos(newPosition); + buffer.setCommittedPos(newPosition); data.setStartPos(newPosition); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java index a9600515faa3..2a354dc46162 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java @@ -44,8 +44,8 @@ public static void initialize(JfrNativeEventWriterData data, JfrBuffer buffer) { if (buffer.isNonNull()) { assert JfrBufferAccess.verify(buffer); data.setJfrBuffer(buffer); - data.setStartPos(buffer.getPos()); - data.setCurrentPos(buffer.getPos()); + data.setStartPos(buffer.getCommittedPos()); + data.setCurrentPos(buffer.getCommittedPos()); data.setEndPos(JfrBufferAccess.getDataEnd(buffer)); } else { data.setJfrBuffer(WordFactory.nullPointer()); @@ -76,7 +76,7 @@ public static boolean verify(JfrNativeEventWriterData data) { Pointer dataStart = JfrBufferAccess.getDataStart(buffer); Pointer dataEnd = JfrBufferAccess.getDataEnd(buffer); - return data.getStartPos() == buffer.getPos() && + return data.getStartPos() == buffer.getCommittedPos() && (data.getEndPos() == dataEnd || data.getEndPos().isNull()) && data.getCurrentPos().aboveOrEqual(dataStart) && data.getCurrentPos().belowOrEqual(dataEnd) && data.getCurrentPos().aboveOrEqual(data.getStartPos()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index d0efd98ca55f..1c575af7ee69 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -193,9 +193,9 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { } assert JfrBufferAccess.isEmpty(buffer) : "a fresh JFR buffer must be empty"; - long startPos = buffer.getPos().rawValue(); + long startPos = buffer.getCommittedPos().rawValue(); long maxPos = JfrBufferAccess.getDataEnd(buffer).rawValue(); - long addressOfPos = JfrBufferAccess.getAddressOfPos(buffer).rawValue(); + long addressOfPos = JfrBufferAccess.getAddressOfCommittedPos(buffer).rawValue(); long jfrThreadId = SubstrateJVM.getCurrentThreadId(); Target_jdk_jfr_internal_EventWriter result; if (JavaVersionUtil.JAVA_SPEC >= 19) { @@ -308,7 +308,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit if (uncommitted.aboveThan(0)) { /* Copy all uncommitted memory to the start of the thread local buffer. */ assert JfrBufferAccess.getDataStart(threadLocalBuffer).add(uncommitted).belowOrEqual(JfrBufferAccess.getDataEnd(threadLocalBuffer)); - UnmanagedMemoryUtil.copy(threadLocalBuffer.getPos(), JfrBufferAccess.getDataStart(threadLocalBuffer), uncommitted); + UnmanagedMemoryUtil.copy(threadLocalBuffer.getCommittedPos(), JfrBufferAccess.getDataStart(threadLocalBuffer), uncommitted); } JfrBufferAccess.reinitialize(threadLocalBuffer); assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); 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 2fb205a72ed2..21c7c13e6e7e 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 @@ -482,16 +482,16 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted if (newBuffer.isNull()) { // The flush failed for some reason, so mark the EventWriter as invalid for this write // attempt. - JfrEventWriterAccess.setStartPosition(writer, oldBuffer.getPos().rawValue()); - JfrEventWriterAccess.setCurrentPosition(writer, oldBuffer.getPos().rawValue()); + JfrEventWriterAccess.setStartPosition(writer, oldBuffer.getCommittedPos().rawValue()); + JfrEventWriterAccess.setCurrentPosition(writer, oldBuffer.getCommittedPos().rawValue()); JfrEventWriterAccess.setValid(writer, false); } else { // Update the EventWriter so that it uses the correct buffer and positions. - Pointer newCurrentPos = newBuffer.getPos().add(uncommittedSize); - JfrEventWriterAccess.setStartPosition(writer, newBuffer.getPos().rawValue()); + Pointer newCurrentPos = newBuffer.getCommittedPos().add(uncommittedSize); + JfrEventWriterAccess.setStartPosition(writer, newBuffer.getCommittedPos().rawValue()); JfrEventWriterAccess.setCurrentPosition(writer, newCurrentPos.rawValue()); if (newBuffer.notEqual(oldBuffer)) { - JfrEventWriterAccess.setStartPositionAddress(writer, JfrBufferAccess.getAddressOfPos(newBuffer).rawValue()); + JfrEventWriterAccess.setStartPositionAddress(writer, JfrBufferAccess.getAddressOfCommittedPos(newBuffer).rawValue()); JfrEventWriterAccess.setMaxPosition(writer, JfrBufferAccess.getDataEnd(newBuffer).rawValue()); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java index 43613859a2db..ba8a87248d90 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java @@ -105,7 +105,6 @@ public void test() throws Exception { try { if (flushes == 0) { Stressor.execute(THREADS, r); - // at this point all expected events should be generated } } catch (IOException e) { throw new RuntimeException(e); @@ -124,10 +123,10 @@ public void test() throws Exception { } int flushCount = flushes; /* - * At this point we can expect to have found all the events after the 2 flushes Scenario: - * Next flush is occurring while emittedEvents.get() is incremented up to EXPECTED_EVENTS - * and therefore doesn't contain all the events. But the flush after the next one must - * contain all remaining events. + * At this point we can expect to have found all the events after the next 2 flushes. + * Scenario: A flush is occurring while emittedEvents.get() is incremented up to be + * EXPECTED_EVENTS and therefore doesn't contain all the events. But the flush after the + * next one must contain all remaining events. */ while (remainingStringEventsInStream.get() > 0) { assertFalse("Not all expected monitor wait events were found in the JFR stream. Remaining:" + remainingStringEventsInStream.get(), diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java index d411f542409d..55eda5fde282 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java @@ -45,7 +45,7 @@ /** * Check to make sure 1. All events are accounted for when using streaming (even when there are very * many events generated). This test also forces a chunk rotation after the first flush as a sanity - * check for potential flush/rotatin clashes. + * check for potential flush/rotation clashes. */ public class TestStreamingCount extends StreamingTest { From 9f6bfcccd88e34df85e2468bce7d8a31789b9a0b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 16 Feb 2023 14:41:41 -0500 Subject: [PATCH 46/72] reduce possibility of race on thread repo writing. Minor fixes# This is a combination of 5 commits. reduce possibility of race on thread repo writing use assertions minor changes more minor cleanups gate fixes --- .../oracle/svm/core/jfr/JfrBufferAccess.java | 50 +++++++++++++------ .../svm/core/jfr/JfrBufferNodeLinkedList.java | 25 ++++++---- .../svm/core/jfr/JfrCheckpointType.java | 3 ++ .../oracle/svm/core/jfr/JfrChunkWriter.java | 47 ++++++++++------- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 2 +- .../svm/core/jfr/JfrMethodRepository.java | 4 +- .../svm/core/jfr/JfrNativeEventWriter.java | 4 +- .../oracle/svm/core/jfr/JfrReservedEvent.java | 3 ++ .../svm/core/jfr/JfrStackTraceRepository.java | 4 +- .../svm/core/jfr/JfrSymbolRepository.java | 4 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 14 ++---- .../svm/core/jfr/JfrThreadRepository.java | 43 ++++++++-------- .../svm/core/jfr/JfrTypeRepository.java | 4 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 4 +- .../test/jfr/TestJfrBufferNodeLinkedList.java | 16 +++--- .../svm/test/jfr/TestStreamingBasic.java | 2 +- .../svm/test/jfr/TestStreamingCount.java | 6 +-- 17 files changed, 137 insertions(+), 98 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 1b966d05cc69..a1a7b8028dd4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -77,17 +77,40 @@ public static void free(JfrBuffer buffer) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(buffer); } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") + @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public static void reinitialize(JfrBuffer buffer) { assert buffer.isNonNull(); - assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || - (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + org.graalvm.nativeimage.CurrentIsolate.getCurrentThread(); Pointer pos = getDataStart(buffer); buffer.setCommittedPos(pos); + setFlushedPos(buffer, pos); + } + + /** + * This is a helper method that checks that the thread modifying the flushed pos actually owns + * the buffer lock. This is important because there can races between flushing threads and the + * thread that owns/created JFR local buffers. + */ + @Uninterruptible(reason = "Changes flushed position.") + public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { + assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || + (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); buffer.setFlushedPos(pos); } + /** + * This is a helper method that checks that the thread modifying the flushed pos actually owns + * the buffer lock. This is important because there can races between flushing threads and the + * thread that owns/created JFR local buffers. + */ + @Uninterruptible(reason = "Accesses flushed position. Possible race between flushing and working threads.") + public static Pointer getFlushedPos(JfrBuffer buffer) { + assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || + (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + return buffer.getFlushedPos(); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isLocked(JfrBuffer buffer) { assert buffer.isNonNull(); @@ -99,7 +122,7 @@ public static boolean tryLock(JfrBuffer buffer) { assert buffer.isNonNull(); boolean result = NativeSpinLockUtils.tryLock(ptrToLock(buffer)); if (result) { - buffer.setLockOwner(org.graalvm.nativeimage.CurrentIsolate.getCurrentThread()); + buffer.setLockOwner(CurrentIsolate.getCurrentThread()); } return result; } @@ -109,7 +132,7 @@ public static boolean tryLock(JfrBuffer buffer, int retries) { assert buffer.isNonNull(); boolean result = NativeSpinLockUtils.tryLock(ptrToLock(buffer), retries); if (result) { - buffer.setLockOwner(org.graalvm.nativeimage.CurrentIsolate.getCurrentThread()); + buffer.setLockOwner(CurrentIsolate.getCurrentThread()); } return result; } @@ -117,8 +140,7 @@ public static boolean tryLock(JfrBuffer buffer, int retries) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void unlock(JfrBuffer buffer) { assert buffer.isNonNull(); - assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || - (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()); NativeSpinLockUtils.unlock(ptrToLock(buffer)); } @@ -147,10 +169,10 @@ public static UnsignedWord getAvailableSize(JfrBuffer buffer) { return getDataEnd(buffer).subtract(buffer.getCommittedPos()); } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.", callerMustBe = true) + @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.", callerMustBe = true) public static UnsignedWord getUnflushedSize(JfrBuffer buffer) { assert buffer.isNonNull(); - return buffer.getCommittedPos().subtract(buffer.getFlushedPos()); + return buffer.getCommittedPos().subtract(getFlushedPos(buffer)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -159,13 +181,10 @@ public static void increaseCommittedPos(JfrBuffer buffer, UnsignedWord delta) { buffer.setCommittedPos(buffer.getCommittedPos().add(delta)); } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") + @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public static void increaseFlushedPos(JfrBuffer buffer, UnsignedWord delta) { assert buffer.isNonNull(); - assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || - (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); - - buffer.setFlushedPos(buffer.getFlushedPos().add(delta)); + setFlushedPos(buffer, getFlushedPos(buffer).add(delta)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -182,6 +201,9 @@ public static boolean verify(JfrBuffer buffer) { Pointer start = getDataStart(buffer); Pointer end = getDataEnd(buffer); + + // Don't need to use setFlushedPos and getFlushedPos helpers here because we're just + // checking invariants. return buffer.getCommittedPos().aboveOrEqual(start) && buffer.getCommittedPos().belowOrEqual(end) && buffer.getFlushedPos().aboveOrEqual(start) && buffer.getFlushedPos().belowOrEqual(end) && buffer.getFlushedPos().belowOrEqual(buffer.getCommittedPos()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 0070a2927d4b..1cb08f78fd27 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -87,17 +87,17 @@ public interface JfrBufferNode extends PointerBase { private volatile int lock; private volatile JfrBufferNode head; - @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean isHead(JfrBufferNode node) { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean isHead(JfrBufferNode node) { return node == head || head.isNull(); } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void setHead(JfrBufferNode node) { head = node; } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { if (buffer.isNull()) { return WordFactory.nullPointer(); @@ -115,8 +115,15 @@ private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) public JfrBufferNodeLinkedList() { } + @Uninterruptible(reason = "Locking with no transition.") public void teardown() { - // TODO: maybe iterate list freeing nodes, just in case. + JfrBufferNode node = getAndLockHead(); + while (node.isNonNull()) { + JfrBufferNode next = node.getNext(); + removeNode(node, WordFactory.nullPointer()); + node = next; + } + releaseList(); } @Uninterruptible(reason = "Locking with no transition.", callerMustBe = true) @@ -130,14 +137,14 @@ public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { JfrBufferNode next = node.getNext(); // next can never be null if (isHead(node)) { - VMError.guarantee(prev.isNull(), "If head, prev should be null "); + assert prev.isNull(); setHead(next); // head could now be null if there was only one node in the list } else { - VMError.guarantee(prev.isNonNull(), "If not head, prev should be non-null "); + assert prev.isNonNull(); prev.setNext(next); } - VMError.guarantee(node.getValue().isNonNull(), "JFR buffer should always exist until removal of respective JfrBufferNodeLinkedList node."); + assert node.getValue().isNonNull(); JfrBufferAccess.free(node.getValue()); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); return true; @@ -147,7 +154,7 @@ public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { * Must be uninterruptible because if this list is acquired and we safepoint for an epoch change * in this method, the thread doing the epoch change will be blocked accessing the list. */ - @Uninterruptible(reason = "Locking with no transition list must not be acquired entering epoch change.") + @Uninterruptible(reason = "Locking with no transition. List must not be acquired entering epoch change.") public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { JfrBufferNode newNode = createNode(buffer, thread); acquireList(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java index 8f5372502d3c..7d004e4a9dc6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java @@ -25,12 +25,15 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; + public enum JfrCheckpointType { Flush(1), Threads(8); private final byte id; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public byte getId() { return id; } 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 113ce6d6cd25..8f45241c1877 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 @@ -27,7 +27,6 @@ import java.nio.charset.StandardCharsets; import org.graalvm.compiler.api.replacements.Fold; -import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.SignedWord; @@ -47,8 +46,6 @@ import com.oracle.svm.core.thread.VMOperationControl; import com.oracle.svm.core.locks.VMMutex; -import com.oracle.svm.core.util.VMError; - import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; @@ -143,12 +140,12 @@ public boolean openFile(String outputFile) { return true; } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") + @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public boolean write(JfrBuffer buffer) { return write(buffer, true); } - @Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.") + @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { assert JfrBufferAccess.isLocked(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); @@ -156,7 +153,7 @@ public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { return false; } - boolean success = getFileSupport().write(fd, buffer.getFlushedPos(), unflushedSize); + boolean success = getFileSupport().write(fd, JfrBufferAccess.getFlushedPos(buffer), unflushedSize); if (increaseFlushedPos) { JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); } @@ -170,7 +167,7 @@ public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { /** * Write all the in-memory data to the file. */ - public void closeFile(JfrThreadRepository threadRepo) { + public void closeFile() { assert lock.isOwner(); /* * Switch to a new epoch. This is done at a safepoint to ensure that we end up with @@ -184,9 +181,9 @@ public void closeFile(JfrThreadRepository threadRepo) { * data structures of the new epoch. This guarantees that the data in the old epoch can be * persisted to a file without a safepoint. */ - if (threadRepo.isDirty(false)) { - writeThreadCheckpointEvent(threadRepo, false); - } + + SubstrateJVM.getThreadRepo().maybeWrite(this, false); + SignedWord constantPoolPosition = writeCheckpointEvent(false); writeMetadataEvent(); patchFileHeader(constantPoolPosition, false); @@ -197,13 +194,11 @@ public void closeFile(JfrThreadRepository threadRepo) { newChunk = false; } - public void flush(JfrThreadRepository threadRepo) { + public void flush() { assert lock.isOwner(); flushStorage(false); - if (threadRepo.isDirty(true)) { - writeThreadCheckpointEvent(threadRepo, true); - } + SubstrateJVM.getThreadRepo().maybeWrite(this, true); SignedWord constantPoolPosition = writeCheckpointEvent(true); writeMetadataEvent(); @@ -277,7 +272,9 @@ private byte nextGeneration() { return generation++; } - private SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolean flush) { + /** This is called from JfrThreadRepository only if there is new data to write. */ + @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this thread constant pool.") + public SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolean flush) { SignedWord start = beginEvent(); @@ -381,6 +378,7 @@ public boolean shouldRotateDisk() { return getFileSupport().isValid(fd) && getFileSupport().size(fd).greaterThan(WordFactory.signed(notificationThreshold)); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public SignedWord beginEvent() { SignedWord start = getFileSupport().position(fd); // Write a placeholder for the size. Will be patched by endEvent, @@ -388,6 +386,7 @@ public SignedWord beginEvent() { return start; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void endEvent(SignedWord start) { SignedWord end = getFileSupport().position(fd); SignedWord writtenBytes = end.subtract(start); @@ -491,8 +490,20 @@ public void writeString(String str) { } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static int makePaddedInt(long sizeWritten) { - return JfrNativeEventWriter.makePaddedInt(NumUtil.safeToInt(sizeWritten)); + return JfrNativeEventWriter.makePaddedInt(safeToInt(sizeWritten)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int safeToInt(long v) { + assert isInt(v); + return (int) v; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isInt(long l) { + return (int) l == l; } public enum StringEncoding { @@ -592,7 +603,7 @@ private void flushStorage(boolean safepoint) { } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer. Locks linked list with no transition. ") - private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { + private static void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { boolean firstIteration = true; // hold onto lock until done with the head node. @@ -603,7 +614,7 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, bool try { JfrBufferNode next = node.getNext(); JfrBuffer buffer = node.getValue(); - VMError.guarantee(buffer.isNonNull(), "JFR buffer should exist if we have not already removed its respective node."); + assert buffer.isNonNull(); /* * I/O operation may be slow, so this flushes to the global buffers instead of diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 382f0758e7ba..a98217a095aa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -108,7 +108,7 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo try { // Copy all committed but not yet flushed memory to the promotion buffer. assert JfrBufferAccess.getAvailableSize(promotionBuffer).aboveOrEqual(unflushedSize); - UnmanagedMemoryUtil.copy(threadLocalBuffer.getFlushedPos(), promotionBuffer.getCommittedPos(), unflushedSize); + UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(threadLocalBuffer), promotionBuffer.getCommittedPos(), unflushedSize); JfrBufferAccess.increaseCommittedPos(promotionBuffer, unflushedSize); shouldSignal = recorderThread.shouldSignal(promotionBuffer); } finally { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 4c19e641878e..734ab01fcac5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -108,14 +108,14 @@ private long getMethodId0(Class clazz, String methodName, int methodId) { return methodId; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeLock(boolean flush) { if (flush) { mutex.lockNoTransition(); } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index a305677ec3c3..b2878005f5fd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -324,7 +324,7 @@ private static JfrBuffer reuseOrReallocateBuffer(JfrBuffer oldBuffer, UnsignedWo // Copy all unflushed data (no matter if committed or uncommitted) from the old buffer // to the new buffer. - UnmanagedMemoryUtil.copy(oldBuffer.getFlushedPos(), result.getCommittedPos(), totalUsedBytes); + UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(oldBuffer), result.getCommittedPos(), totalUsedBytes); JfrBufferAccess.increaseCommittedPos(result, unflushedSize); JfrBufferAccess.free(oldBuffer); @@ -335,7 +335,7 @@ private static JfrBuffer reuseOrReallocateBuffer(JfrBuffer oldBuffer, UnsignedWo // Reuse the existing buffer because enough data was already flushed in the meanwhile. // For that, copy all unflushed data (no matter if committed or uncommitted) to the // beginning of the buffer. - UnmanagedMemoryUtil.copy(oldBuffer.getFlushedPos(), JfrBufferAccess.getDataStart(oldBuffer), totalUsedBytes); + UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(oldBuffer), JfrBufferAccess.getDataStart(oldBuffer), totalUsedBytes); JfrBufferAccess.reinitialize(oldBuffer); JfrBufferAccess.increaseCommittedPos(oldBuffer, unflushedSize); return oldBuffer; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java index e8fc93c390a3..e3167829dcfb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java @@ -25,12 +25,15 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; + public enum JfrReservedEvent { EVENT_METADATA(0), EVENT_CHECKPOINT(1); private final long id; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long getId() { return id; } 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 07c441e042fc..ad0e2d09f575 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 @@ -230,14 +230,14 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeLock(boolean flush) { if (flush) { mutex.lockNoTransition(); } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 7f68b1b4facd..ecf8289a26c8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -114,14 +114,14 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r return 0; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeLock(boolean flush) { if (flush) { mutex.lockNoTransition(); } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 1c575af7ee69..c27f12de022b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -46,10 +46,9 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.threadlocal.FastThreadLocalObject; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.thread.Target_java_lang_Thread; import com.oracle.svm.core.SubstrateUtil; - +import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; /** @@ -92,13 +91,11 @@ public class JfrThreadLocal implements ThreadListener { private long threadLocalBufferSize; @Fold - @Uninterruptible(reason = "Called from uninterruptible code.") public static JfrBufferNodeLinkedList getNativeBufferList() { return nativeBufferList; } @Fold - @Uninterruptible(reason = "Called from uninterruptible code.") public static JfrBufferNodeLinkedList getJavaBufferList() { return javaBufferList; } @@ -290,7 +287,7 @@ public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { @Uninterruptible(reason = "Accesses a JFR buffer.") public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { - VMError.guarantee(threadLocalBuffer.isNonNull(), "TLB cannot be null if promoting."); + assert threadLocalBuffer.isNonNull(); // Needed for race between streaming flush and promotion JfrBufferAccess.tryLock(threadLocalBuffer, Integer.MAX_VALUE); @@ -362,10 +359,10 @@ public void exclude(Thread thread) { return; } IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); - com.oracle.svm.core.thread.Target_java_lang_Thread tjlt = com.oracle.svm.core.SubstrateUtil.cast(thread, Target_java_lang_Thread.class); + Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); tjlt.jfrExcluded = true; - if (javaEventWriter.get(currentIsolateThread) != null && JavaVersionUtil.JAVA_SPEC >= 19) { + if (javaEventWriter.get(currentIsolateThread) != null && !JavaThreads.isVirtual(thread)) { javaEventWriter.get(currentIsolateThread).excluded = true; } } @@ -379,7 +376,7 @@ public void include(Thread thread) { Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); tjlt.jfrExcluded = false; - if (javaEventWriter.get(currentIsolateThread) != null && JavaVersionUtil.JAVA_SPEC >= 19) { + if (javaEventWriter.get(currentIsolateThread) != null && !JavaThreads.isVirtual(thread)) { javaEventWriter.get(currentIsolateThread).excluded = false; } } @@ -435,6 +432,5 @@ public static void setSamplerWriterData(SamplerSampleWriterData data) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static SamplerSampleWriterData getSamplerWriterData() { return samplerWriterData.get(); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index cdd5154e2f8a..b4cffa00d34a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -30,6 +30,7 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.WordFactory; +import org.graalvm.word.SignedWord; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; @@ -127,7 +128,6 @@ private void registerThread0(Thread thread) { // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we // need to update the repository pointer as well. epochData.threadBuffer = data.getJfrBuffer(); - epochData.isDirty = true; } @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true) @@ -188,14 +188,14 @@ private JfrThreadEpochData getEpochData(boolean previousEpoch) { return epoch ? epochData0 : epochData1; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeLock(boolean flush) { if (flush) { mutex.lockNoTransition(); } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition.") private void maybeUnlock(boolean flush) { if (flush) { mutex.unlock(); @@ -205,30 +205,35 @@ private void maybeUnlock(boolean flush) { @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { + JfrThreadEpochData epochData = getEpochData(!flush); + int count = writeThreads(writer, epochData); + count += writeThreadGroups(writer, epochData); + + /* + * Thread repository epoch data may be cleared after a flush because threads are only + * registered once with fixed IDs. There is no need to do a lookup in this repo for a + * thread's ID. + */ + epochData.clear(); + return count; + } + + @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") + public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush) { + JfrThreadEpochData epochData = getEpochData(!flush); maybeLock(flush); try { - int count = writeThreads(writer, epochData); - count += writeThreadGroups(writer, epochData); - - /* - * Thread repository epoch data may be cleared after a flush because threads are only - * registered once with fixed IDs. There is no need to do a lookup in this repo for a - * thread's ID. - */ - epochData.clear(); + if (epochData.visitedThreads.getSize() == 0) { + return WordFactory.nullPointer(); + } + return writer.writeThreadCheckpointEvent(this, flush); - return count; } finally { maybeUnlock(flush); } } - public boolean isDirty(boolean flush) { - JfrThreadEpochData epochData = getEpochData(!flush); - return epochData.isDirty; - } - @Uninterruptible(reason = "May write current epoch data.") private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); @@ -271,7 +276,6 @@ private static class JfrThreadEpochData { private JfrBuffer threadBuffer; private JfrBuffer threadGroupBuffer; - private boolean isDirty = false; @Platforms(Platform.HOSTED_ONLY.class) JfrThreadEpochData() { @@ -286,7 +290,6 @@ public void clear() { JfrBufferAccess.reinitialize(threadBuffer); JfrBufferAccess.reinitialize(threadGroupBuffer); - isDirty = false; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 314d6f0daa5e..f3cc09b190c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -44,7 +44,7 @@ * rotations. This means that the maps in this repository will be entirely used and cleared with * respect to the current epoch before they are used for the subsequent epoch. * - * The "old" maps hold records with respect to and entire epoch, while the "new" maps are with + * The "old" maps hold records with respect to an entire epoch, while the "new" maps are with * respect to the current flush / chunk rotation. */ public class JfrTypeRepository implements JfrConstantPool { @@ -90,7 +90,7 @@ public int write(JfrChunkWriter writer, boolean flush) { return count; } - private void collectTypeInfo(boolean flush) { + private static void collectTypeInfo(boolean flush) { for (Class clazz : Heap.getHeap().getLoadedClasses()) { if (flush) { 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 21c7c13e6e7e..425dbf96b972 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 @@ -346,7 +346,7 @@ public void setOutput(String file) { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.closeFile(threadRepo); + chunkWriter.closeFile(); } if (file != null) { chunkWriter.openFile(file); @@ -507,7 +507,7 @@ public void flush() { if (recording) { boolean existingFile = chunkWriter.hasOpenFile(); if (existingFile) { - chunkWriter.flush(threadRepo); + chunkWriter.flush(); } } } finally { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index 97797ec5098d..5aa3dc97978d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -73,13 +73,13 @@ public void testConcurrentAddition() throws Exception { cleanUpList(list); } - private void cleanUpList(JfrBufferNodeLinkedList list) { + private static void cleanUpList(JfrBufferNodeLinkedList list) { JfrBufferNode node = removeAllNodes(list); assertTrue("Could not remove all nodes", node.isNull()); list.teardown(); } - private void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { + private static void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { for (int i = 0; i < nodeCount; i++) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(32), JfrBufferType.THREAD_LOCAL_NATIVE); list.addNode(buffer, CurrentIsolate.getCurrentThread()); @@ -87,7 +87,7 @@ private void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { } @Uninterruptible(reason = "Locking with no transition.") - private int countNodes(JfrBufferNodeLinkedList list) { + private static int countNodes(JfrBufferNodeLinkedList list) { int count = 0; JfrBufferNode node = list.getAndLockHead(); while (node.isNonNull()) { @@ -100,14 +100,12 @@ private int countNodes(JfrBufferNodeLinkedList list) { } @Uninterruptible(reason = "Locking with no transition.") - private JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { + private static JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { // Try removing the nodes - JfrBufferNode node; - JfrBufferNode prev = WordFactory.nullPointer(); - node = list.getAndLockHead(); + JfrBufferNode node = list.getAndLockHead(); while (node.isNonNull()) { JfrBufferNode next = node.getNext(); - list.removeNode(node, prev); + list.removeNode(node, WordFactory.nullPointer()); node = next; } list.releaseList(); @@ -115,7 +113,7 @@ private JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { } @Uninterruptible(reason = "Locking with no transition.") - private void removeNthNode(JfrBufferNodeLinkedList list, int target) { + private static void removeNthNode(JfrBufferNodeLinkedList list, int target) { JfrBufferNode node; JfrBufferNode prev = WordFactory.nullPointer(); node = list.getAndLockHead(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java index ba8a87248d90..7e27fba33ed5 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java @@ -66,7 +66,7 @@ public void validateEvents() throws Throwable { List events = getEvents(dumpLocation); for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); - if (event. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS - 1) { + if (event. getValue("monitorClass").getName().equals(Helper.class.getName())) { if (!streamEvents.contains(eventThread)) { continue; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java index 55eda5fde282..2318ce257f23 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java @@ -67,11 +67,7 @@ public void validateEvents() throws Throwable { // Tally up a selection of the events in the dump as a quick check they match the expected // number. List events = getEvents(dumpLocation); - int count = 0; - for (RecordedEvent event : events) { - count++; - } - if (count != EXPECTED_EVENTS) { + if (events.size() != EXPECTED_EVENTS) { throw new Exception("Not all expected events were found in the JFR file"); } } From 38cc7a309e3fab3de8c7f39619611140a3c76cfa Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 17 Feb 2023 12:38:11 -0500 Subject: [PATCH 47/72] style --- .../src/com/oracle/svm/core/jfr/JfrBuffer.java | 1 + .../src/com/oracle/svm/core/jfr/JfrGlobalMemory.java | 1 + .../src/com/oracle/svm/core/jfr/JfrMetadata.java | 4 ++++ .../src/com/oracle/svm/core/jfr/JfrSerializerSupport.java | 4 ++-- .../src/com/oracle/svm/core/jfr/JfrType.java | 1 + .../src/com/oracle/svm/core/locks/VMMutex.java | 1 + .../src/com/oracle/svm/test/jfr/events/EndStreamEvent.java | 3 +-- .../src/com/oracle/svm/test/jfr/events/IntegerEvent.java | 3 +-- 8 files changed, 12 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index 4b7f247c1d1b..c992d99f1463 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -105,6 +105,7 @@ static int offsetOfLocked() { @RawField @PinnedObjectField void setBufferType(JfrBufferType bufferType); + @RawField void setLockOwner(IsolateThread thread); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index a98217a095aa..b6148e34a8b4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -65,6 +65,7 @@ public void initialize(long globalBufferSize, long globalBufferCount) { buffers.addressOf(i).write(buffer); } } + @Uninterruptible(reason = "Locks without transition.") public void clear() { assert VMOperation.isInProgressAtSafepoint(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index 1710779a5fa8..c6090a189a5e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -27,11 +27,13 @@ import org.graalvm.word.SignedWord; import org.graalvm.word.WordFactory; + public class JfrMetadata { private volatile long currentMetadataId; private volatile byte[] metadataDescriptor; private volatile boolean isDirty; private SignedWord metadataPosition; + public JfrMetadata(byte[] bytes) { metadataDescriptor = bytes; currentMetadataId = 0; @@ -49,9 +51,11 @@ public byte[] getDescriptorAndClearDirtyFlag() { isDirty = false; return metadataDescriptor; } + public boolean isDirty() { return isDirty; } + public void setDirty() { isDirty = true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java index 80d18d0ad6d0..3e150130d40d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java @@ -32,8 +32,8 @@ import org.graalvm.nativeimage.Platforms; /** - * Support for registering and querying {@link JfrConstantPool}s that serialize data. - * Serializers are only written upon a new chunk. + * Support for registering and querying {@link JfrConstantPool}s that serialize data. Serializers + * are only written upon a new chunk. */ public class JfrSerializerSupport { private JfrConstantPool[] serializers; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java index 50719e22f299..e6bd8df17d8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java @@ -52,6 +52,7 @@ public enum JfrType { JfrType(String name) { this.id = JfrMetadataTypeLibrary.lookupType(name); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long getId() { return id; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java index 512f5d1f73f1..7594e6ab81f6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java @@ -195,6 +195,7 @@ public void clearUnspecifiedOwner() { assert owner == (IsolateThread) UNSPECIFIED_OWNER; owner = WordFactory.nullPointer(); } + @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isOwned() { return owner.isNonNull(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java index 930b4f50b8d9..8f46e6f61841 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java @@ -37,6 +37,5 @@ @Description("Signals to end stream") @StackTrace(false) public class EndStreamEvent extends Event { - @Label("Message") - public String message; + @Label("Message") public String message; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java index d085b256f46b..da80a5117821 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/IntegerEvent.java @@ -38,6 +38,5 @@ @StackTrace(true) public class IntegerEvent extends Event { - @Label("Number") - public int number; + @Label("Number") public int number; } From d16e30ea62ddb80e0f8ca1ec90723c65ba19199f Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 17 Feb 2023 13:31:30 -0500 Subject: [PATCH 48/72] use atomic long for metadata id --- .../src/com/oracle/svm/core/jfr/JfrMetadata.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index c6090a189a5e..7f11ad337d8a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -25,25 +25,26 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import org.graalvm.word.SignedWord; import org.graalvm.word.WordFactory; public class JfrMetadata { - private volatile long currentMetadataId; + private UninterruptibleUtils.AtomicLong currentMetadataId; private volatile byte[] metadataDescriptor; private volatile boolean isDirty; private SignedWord metadataPosition; public JfrMetadata(byte[] bytes) { metadataDescriptor = bytes; - currentMetadataId = 0; + currentMetadataId = new UninterruptibleUtils.AtomicLong(0); isDirty = false; metadataPosition = WordFactory.signed(-1); } public void setDescriptor(byte[] bytes) { metadataDescriptor = bytes; - currentMetadataId++; + currentMetadataId.incrementAndGet(); isDirty = true; } @@ -69,6 +70,6 @@ public SignedWord getMetadataPosition() { } public long getCurrentMetadataId() { - return currentMetadataId; + return currentMetadataId.get(); } } From eb35d3602b309a1dd323a598a39ba9a531890f75 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 23 Feb 2023 15:34:49 -0500 Subject: [PATCH 49/72] first half of review comments --- .../com/oracle/svm/core/jfr/JfrBuffer.java | 10 +- .../oracle/svm/core/jfr/JfrBufferAccess.java | 36 +++-- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 64 ++++----- .../svm/core/jfr/JfrCheckpointType.java | 3 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 127 +++++++----------- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 2 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 20 --- .../svm/core/jfr/JfrThreadRepository.java | 28 +++- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 1 + .../svm/core/thread/NativeSpinLockUtils.java | 31 +++++ .../test/jfr/TestJfrBufferNodeLinkedList.java | 15 +-- 11 files changed, 170 insertions(+), 167 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index c992d99f1463..e037a2ddd573 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -37,7 +37,10 @@ /** * A {@link JfrBuffer} is a block of native memory (either thread-local or global) into which JFR - * events are written. + * events are written. The flushedPos is the point up to which data has been flushed. The committedPos is the point up to + * which data has been committed. This means that data between the flushedPos and committedPos is unflushed data that + * is ready to be flushed. This also means that flushedPos should never exceed committedPos. New emitted events + * are written after the commit position. The new events are committed by advancing the committedPos. */ @RawStructure public interface JfrBuffer extends PointerBase { @@ -111,4 +114,9 @@ static int offsetOfLocked() { @RawField IsolateThread getLockOwner(); + + @RawFieldOffset + static int offsetOfLockOwner() { + throw VMError.unimplemented(); // replaced + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index a1a7b8028dd4..4e0217b9ab78 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -27,7 +27,7 @@ import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.struct.SizeOf; -import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -65,9 +65,13 @@ public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType result.setSize(dataSize); result.setBufferType(bufferType); NativeSpinLockUtils.initialize(ptrToLock(result)); - tryLock(result, Integer.MAX_VALUE); - reinitialize(result); - unlock(result); + boolean locked = tryLock(result); + try { + assert locked; + reinitialize(result); + } finally { + unlock(result); + } } return result; } @@ -80,8 +84,6 @@ public static void free(JfrBuffer buffer) { @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public static void reinitialize(JfrBuffer buffer) { assert buffer.isNonNull(); - - org.graalvm.nativeimage.CurrentIsolate.getCurrentThread(); Pointer pos = getDataStart(buffer); buffer.setCommittedPos(pos); setFlushedPos(buffer, pos); @@ -94,8 +96,7 @@ public static void reinitialize(JfrBuffer buffer) { */ @Uninterruptible(reason = "Changes flushed position.") public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { - assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || - (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + assert isLockedByCurrentThread(buffer) || !isThreadLocal(buffer); buffer.setFlushedPos(pos); } @@ -106,8 +107,7 @@ public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { */ @Uninterruptible(reason = "Accesses flushed position. Possible race between flushing and working threads.") public static Pointer getFlushedPos(JfrBuffer buffer) { - assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()) || - (buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_JAVA && buffer.getBufferType() != JfrBufferType.THREAD_LOCAL_NATIVE); + assert isLockedByCurrentThread(buffer) || !isThreadLocal(buffer); return buffer.getFlushedPos(); } @@ -140,7 +140,7 @@ public static boolean tryLock(JfrBuffer buffer, int retries) { @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void unlock(JfrBuffer buffer) { assert buffer.isNonNull(); - assert (isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread()); + assert isLockedByCurrentThread(buffer); NativeSpinLockUtils.unlock(ptrToLock(buffer)); } @@ -210,7 +210,17 @@ public static boolean verify(JfrBuffer buffer) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static CIntPointer ptrToLock(JfrBuffer buffer) { - return (CIntPointer) ((Pointer) buffer).add(JfrBuffer.offsetOfLocked()); + private static WordPointer ptrToLock(JfrBuffer buffer) { + return (WordPointer) ((Pointer) buffer).add(JfrBuffer.offsetOfLockOwner()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean isThreadLocal(JfrBuffer buffer) { + return buffer.getBufferType() == JfrBufferType.THREAD_LOCAL_JAVA || buffer.getBufferType() == JfrBufferType.THREAD_LOCAL_NATIVE; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isLockedByCurrentThread(JfrBuffer buffer) { + return isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 1cb08f78fd27..dbe52b83b11a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -40,12 +40,12 @@ import com.oracle.svm.core.thread.JavaSpinLockUtils; /** - * JfrBufferNodeLinkedList is a singly linked list used to store thread local JFR buffers. Threads + * {@link JfrBufferNodeLinkedList} is a singly linked list used to store thread local JFR buffers. Threads * shall only add one node to the list. Only the thread performing a flush or epoch change shall * iterate this list and is allowed to remove nodes. There is a list-level lock that is acquired * when adding nodes, and when beginning iteration at the head. Threads may access their own nodes - * at any time up until they call JfrBufferNode.setAlive(false). The list lock must not be held at a - * safepoint. + * at any time up until they set the alive flag to false {@link JfrBufferNode#setAlive}. + * When entering a safepoint, the list lock must not be held by one of the blocked Java threads. */ public class JfrBufferNodeLinkedList { @RawStructure @@ -75,28 +75,11 @@ public interface JfrBufferNode extends PointerBase { void setAlive(boolean alive); } - private static final long LOCK_OFFSET; + private static final long LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(JfrBufferNodeLinkedList.class, "lock"); - static { - try { - LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(JfrBufferNodeLinkedList.class.getDeclaredField("lock")); - } catch (Throwable ex) { - throw VMError.shouldNotReachHere(ex); - } - } private volatile int lock; private volatile JfrBufferNode head; - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private boolean isHead(JfrBufferNode node) { - return node == head || head.isNull(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void setHead(JfrBufferNode node) { - head = node; - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { if (buffer.isNull()) { @@ -115,39 +98,47 @@ private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) public JfrBufferNodeLinkedList() { } - @Uninterruptible(reason = "Locking with no transition.") public void teardown() { - JfrBufferNode node = getAndLockHead(); + JfrBufferNode node = head; while (node.isNonNull()) { JfrBufferNode next = node.getNext(); + JfrBufferAccess.free(node.getValue()); + node.setAlive(false); removeNode(node, WordFactory.nullPointer()); node = next; } - releaseList(); } @Uninterruptible(reason = "Locking with no transition.", callerMustBe = true) - public JfrBufferNode getAndLockHead() { + public JfrBufferNode getHead() { acquireList(); - return head; + try { + return head; + } finally { + releaseList(); + } } + /** Removes a node from the linked list. The buffer contained in the nodes must have already been + * freed by the caller.*/ @Uninterruptible(reason = "Should not be interrupted while flushing.") - public boolean removeNode(JfrBufferNode node, JfrBufferNode prev) { + public void removeNode(JfrBufferNode node, JfrBufferNode prev) { + assert head.isNonNull(); + assert !node.getAlive(); + + JfrBufferNode next = node.getNext(); // next can never be null - if (isHead(node)) { + if (node == head) { assert prev.isNull(); - setHead(next); // head could now be null if there was only one node in the list + head = next; // head could now be null if there was only one node in the list } else { assert prev.isNonNull(); + assert prev.getNext() == node; prev.setNext(next); } - assert node.getValue().isNonNull(); - JfrBufferAccess.free(node.getValue()); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); - return true; } /** @@ -162,7 +153,7 @@ public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { // Old head could be null JfrBufferNode oldHead = head; newNode.setNext(oldHead); - setHead(newNode); + head = newNode; return newNode; } finally { releaseList(); @@ -175,12 +166,7 @@ private void acquireList() { } @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) - public void releaseList() { + private void releaseList() { JavaSpinLockUtils.unlock(this, LOCK_OFFSET); } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private boolean isLocked() { - return lock == 1; - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java index 7d004e4a9dc6..f7eb14fc818b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java @@ -26,6 +26,7 @@ package com.oracle.svm.core.jfr; import com.oracle.svm.core.Uninterruptible; +import org.graalvm.compiler.core.common.NumUtil; public enum JfrCheckpointType { Flush(1), @@ -39,6 +40,6 @@ public byte getId() { } JfrCheckpointType(int id) { - this.id = (byte) id; + this.id = NumUtil.safeToByte(id); } } 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 8f45241c1877..13db361960f1 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 @@ -79,9 +79,9 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private long chunkStartTicks; private long chunkStartNanos; private byte generation; - public SignedWord lastCheckpointOffset; - private boolean newChunk = true; - private boolean isFinal = false; + private SignedWord lastCheckpointOffset; + private boolean newChunk; + private boolean isFinal; @Platforms(Platform.HOSTED_ONLY.class) public JfrChunkWriter(JfrGlobalMemory globalMemory, JfrMetadata metadata) { @@ -147,7 +147,7 @@ public boolean write(JfrBuffer buffer) { @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { - assert JfrBufferAccess.isLocked(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; + assert JfrBufferAccess.isLockedByCurrentThread(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { return false; @@ -182,7 +182,7 @@ public void closeFile() { * persisted to a file without a safepoint. */ - SubstrateJVM.getThreadRepo().maybeWrite(this, false); + lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, false, lastCheckpointOffset); SignedWord constantPoolPosition = writeCheckpointEvent(false); writeMetadataEvent(); @@ -191,14 +191,13 @@ public void closeFile() { filename = null; fd = WordFactory.nullPointer(); - newChunk = false; } public void flush() { assert lock.isOwner(); - flushStorage(false); + flushStorage(true); - SubstrateJVM.getThreadRepo().maybeWrite(this, true); + lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, true, lastCheckpointOffset); SignedWord constantPoolPosition = writeCheckpointEvent(true); writeMetadataEvent(); @@ -226,7 +225,7 @@ private void writeFileHeader() { getFileSupport().writeShort(fd, computeHeaderFlags()); } - private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint) { + private void patchFileHeader(SignedWord constantPoolPosition, boolean flush) { assert lock.isOwner(); SignedWord currentPos = getFileSupport().position(fd); long chunkSize = getFileSupport().position(fd).rawValue(); @@ -239,7 +238,7 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flushpoint getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); - if (flushpoint) { + if (flush) { // chunk is not finished getFileSupport().writeByte(fd, nextGeneration()); } else { @@ -272,36 +271,6 @@ private byte nextGeneration() { return generation++; } - /** This is called from JfrThreadRepository only if there is new data to write. */ - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this thread constant pool.") - public SignedWord writeThreadCheckpointEvent(JfrConstantPool threadRepo, boolean flush) { - - SignedWord start = beginEvent(); - - if (lastCheckpointOffset.lessThan(0)) { - lastCheckpointOffset = start; - } - writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); - writeCompressedLong(JfrTicks.elapsedTicks()); - writeCompressedLong(0); // duration - writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); // deltaToNext - writeByte(JfrCheckpointType.Threads.getId()); - - SignedWord poolCountPos = getFileSupport().position(fd); - getFileSupport().writeInt(fd, 0); // We'll patch this later. - - int poolCount = threadRepo.write(this, flush); - - SignedWord currentPos = getFileSupport().position(fd); - getFileSupport().seek(fd, poolCountPos); - getFileSupport().writeInt(fd, makePaddedInt(poolCount)); - getFileSupport().seek(fd, currentPos); - endEvent(start); - lastCheckpointOffset = start; - - return start; - } - private SignedWord writeCheckpointEvent(boolean flush) { assert lock.isOwner(); SignedWord start = beginEvent(); @@ -418,6 +387,11 @@ public void writeCompressedInt(int value) { assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); writeCompressedLong(value & 0xFFFFFFFFL); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void writeInt(int value) { + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + getFileSupport().writeInt(fd, makePaddedInt(value)); + } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeCompressedLong(long value) { @@ -548,7 +522,7 @@ private void changeEpoch() { * space reclamation on their own time. */ - flushStorage(true); + flushStorage(false); JfrTraceIdEpoch.getInstance().changeEpoch(); @@ -586,9 +560,9 @@ private void processSamplerBuffers0() { } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") - private void flushStorage(boolean safepoint) { - traverseList(getJavaBufferList(), true, safepoint); - traverseList(getNativeBufferList(), false, safepoint); + private void flushStorage(boolean flush) { + traverseList(getJavaBufferList(), true, flush); + traverseList(getNativeBufferList(), false, flush); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { @@ -603,53 +577,44 @@ private void flushStorage(boolean safepoint) { } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer. Locks linked list with no transition. ") - private static void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean safepoint) { + private static void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean flush) { boolean firstIteration = true; // hold onto lock until done with the head node. - JfrBufferNode node = linkedList.getAndLockHead(); + JfrBufferNode node = linkedList.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); while (node.isNonNull()) { - try { - JfrBufferNode next = node.getNext(); - JfrBuffer buffer = node.getValue(); - assert buffer.isNonNull(); - - /* - * I/O operation may be slow, so this flushes to the global buffers instead of - * writing to disk directly. This mitigates the risk of acquiring the TLBs for too - * long. - */ - if (!JfrThreadLocal.flushNoReset(buffer)) { - prev = node; - node = next; - continue; - } + JfrBufferNode next = node.getNext(); + JfrBuffer buffer = node.getValue(); + assert buffer.isNonNull(); - if (!node.getAlive()) { - linkedList.removeNode(node, prev); - // if removed current node, should not update prev. - } else { - // Only notify java event writer if thread is still alive and we are at an epoch - // change. - if (safepoint && java) { - JfrThreadLocal.notifyEventWriter(node.getThread()); - } - prev = node; - } + /* + * I/O operation may be slow, so this flushes to the global buffers instead of + * writing to disk directly. This mitigates the risk of acquiring the TLBs for too + * long. + */ + if (!JfrThreadLocal.flushNoReset(buffer)) { + prev = node; node = next; - } finally { - if (firstIteration) { - linkedList.releaseList(); - firstIteration = false; - } + continue; } - } - // we may never have entered the while loop if the list is empty. - if (firstIteration) { - linkedList.releaseList(); + if (!node.getAlive()) { + // It is safe to free without acquiring because the owning thread is gone. + assert !JfrBufferAccess.isLockedByCurrentThread(buffer); + JfrBufferAccess.free(buffer); + linkedList.removeNode(node, prev); + // if removed current node, should not update prev. + } else { + // Only notify java event writer if thread is still alive and we are at an epoch + // change. + if (!flush && java) { + JfrThreadLocal.notifyEventWriter(node.getThread()); + } + prev = node; + } + node = next; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index b6148e34a8b4..af102845ba82 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -144,7 +144,7 @@ private JfrBuffer tryAcquirePromotionBuffer(UnsignedWord size) { @Uninterruptible(reason = "Epoch must not change while in this method.") private static void releasePromotionBuffer(JfrBuffer buffer) { - assert JfrBufferAccess.isLocked(buffer); + assert JfrBufferAccess.isLockedByCurrentThread(buffer); JfrBufferAccess.unlock(buffer); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index c27f12de022b..085eea9a3aed 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -235,26 +235,6 @@ public JfrBuffer getNativeBuffer() { return WordFactory.nullPointer(); } - @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) - public static JfrBuffer getJavaBuffer(IsolateThread thread) { - assert VMOperation.isInProgressAtSafepoint(); - JfrBufferNode result = javaBufferNode.get(thread); - if (result.isNonNull()) { - return result.getValue(); - } - return WordFactory.nullPointer(); - } - - @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) - public static JfrBuffer getNativeBuffer(IsolateThread thread) { - assert VMOperation.isInProgressAtSafepoint(); - JfrBufferNode result = nativeBufferNode.get(thread); - if (result.isNonNull()) { - return result.getValue(); - } - return WordFactory.nullPointer(); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void notifyEventWriter(IsolateThread thread) { if (javaEventWriter.get(thread) != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index b4cffa00d34a..f8b93a6f0572 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.os.RawFileOperationSupport; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -219,15 +220,36 @@ public int write(JfrChunkWriter writer, boolean flush) { } @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") - public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush) { + public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord lastCheckpointOffset) { JfrThreadEpochData epochData = getEpochData(!flush); maybeLock(flush); try { if (epochData.visitedThreads.getSize() == 0) { - return WordFactory.nullPointer(); + return lastCheckpointOffset; } - return writer.writeThreadCheckpointEvent(this, flush); + SignedWord start = writer.beginEvent(); + if (lastCheckpointOffset.lessThan(0)) { + lastCheckpointOffset = start; + } + writer.writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); + writer.writeCompressedLong(JfrTicks.elapsedTicks()); + writer.writeCompressedLong(0); // duration + writer.writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); + writer.writeByte(JfrCheckpointType.Threads.getId()); + + // If only writing threads pool, count is 1 + int poolCount = 1; + if (epochData.visitedThreadGroups.getSize() > 0) { + poolCount = 2; + } + writer.writeInt(poolCount); + + int actualPoolCount = write(writer, flush); + assert poolCount == actualPoolCount; + + writer.endEvent(start); + return start; } finally { maybeUnlock(flush); 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 425dbf96b972..dc9304dd5639 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 @@ -733,6 +733,7 @@ protected void operate() { * If JFR recording is restarted later on, then it needs to start with a clean state. * Therefore, we clear all data that is still pending. */ + ((JfrThreadLocal)SubstrateJVM.getThreadLocal()).teardown(); SubstrateJVM.getSamplerBufferPool().teardown(); SubstrateJVM.getGlobalMemory().clear(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java index a00468184d01..5a0eb7d75bf1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.thread; import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.c.type.WordPointer; import com.oracle.svm.core.Uninterruptible; @@ -37,28 +38,58 @@ public static void initialize(CIntPointer spinLock) { JavaSpinLockUtils.initialize(null, spinLock.rawValue()); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void initialize(WordPointer spinLock) { + JavaSpinLockUtils.initialize(null, spinLock.rawValue()); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isLocked(CIntPointer spinLock) { return JavaSpinLockUtils.isLocked(null, spinLock.rawValue()); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isLocked(WordPointer spinLock) { + return JavaSpinLockUtils.isLocked(null, spinLock.rawValue()); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean tryLock(CIntPointer spinLock) { return JavaSpinLockUtils.tryLock(null, spinLock.rawValue()); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean tryLock(WordPointer spinLock) { + return JavaSpinLockUtils.tryLock(null, spinLock.rawValue()); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean tryLock(CIntPointer spinLock, int retries) { return JavaSpinLockUtils.tryLock(null, spinLock.rawValue(), retries); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean tryLock(WordPointer spinLock, int retries) { + return JavaSpinLockUtils.tryLock(null, spinLock.rawValue(), retries); + } + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) public static void lockNoTransition(CIntPointer spinLock) { JavaSpinLockUtils.lockNoTransition(null, spinLock.rawValue()); } + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public static void lockNoTransition(WordPointer spinLock) { + JavaSpinLockUtils.lockNoTransition(null, spinLock.rawValue()); + } + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static void unlock(CIntPointer spinLock) { JavaSpinLockUtils.unlock(null, spinLock.rawValue()); } + + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) + public static void unlock(WordPointer spinLock) { + JavaSpinLockUtils.unlock(null, spinLock.rawValue()); + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index 5aa3dc97978d..ae29b8de9b68 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -89,38 +89,38 @@ private static void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { @Uninterruptible(reason = "Locking with no transition.") private static int countNodes(JfrBufferNodeLinkedList list) { int count = 0; - JfrBufferNode node = list.getAndLockHead(); + JfrBufferNode node = list.getHead(); while (node.isNonNull()) { count++; node = node.getNext(); } - - list.releaseList(); return count; } @Uninterruptible(reason = "Locking with no transition.") private static JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { // Try removing the nodes - JfrBufferNode node = list.getAndLockHead(); + JfrBufferNode node = list.getHead(); while (node.isNonNull()) { JfrBufferNode next = node.getNext(); + JfrBufferAccess.free(node.getValue()); + node.setAlive(false); list.removeNode(node, WordFactory.nullPointer()); node = next; } - list.releaseList(); return node; } @Uninterruptible(reason = "Locking with no transition.") private static void removeNthNode(JfrBufferNodeLinkedList list, int target) { - JfrBufferNode node; JfrBufferNode prev = WordFactory.nullPointer(); - node = list.getAndLockHead(); + JfrBufferNode node = list.getHead(); int count = 0; while (node.isNonNull()) { JfrBufferNode next = node.getNext(); if (count == target) { + JfrBufferAccess.free(node.getValue()); + node.setAlive(false); list.removeNode(node, prev); break; } @@ -128,6 +128,5 @@ private static void removeNthNode(JfrBufferNodeLinkedList list, int target) { node = next; count++; } - list.releaseList(); } } From 131f4274337a5c605f1d97759197dcaf166c31e6 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 23 Feb 2023 16:52:11 -0500 Subject: [PATCH 50/72] more changes. Metadata fixes --- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 4 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 98 +++++++++++-------- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 1 - .../com/oracle/svm/core/jfr/JfrMetadata.java | 31 +----- .../oracle/svm/core/jfr/JfrThreadLocal.java | 2 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 11 ++- 6 files changed, 71 insertions(+), 76 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index dbe52b83b11a..4a5920f2450b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -59,6 +59,9 @@ public interface JfrBufferNode extends PointerBase { @RawField JfrBuffer getValue(); + /** + * This field is effectively final and should always be non-null. + * Changing its value after the node is added to the {@link JfrBufferNodeLinkedList} can result in races.*/ @RawField void setValue(JfrBuffer value); @@ -126,7 +129,6 @@ public void removeNode(JfrBufferNode node, JfrBufferNode prev) { assert head.isNonNull(); assert !node.getAlive(); - JfrBufferNode next = node.getNext(); // next can never be null if (node == head) { 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 13db361960f1..e2068303c5c3 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 @@ -32,6 +32,7 @@ import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.VMOperationInfos; @@ -43,6 +44,7 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.ThreadingSupportImpl; import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.thread.VMOperationControl; import com.oracle.svm.core.locks.VMMutex; @@ -82,13 +84,14 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private SignedWord lastCheckpointOffset; private boolean newChunk; private boolean isFinal; - + private long lastMetadataId; + private SignedWord metadataPosition; @Platforms(Platform.HOSTED_ONLY.class) - public JfrChunkWriter(JfrGlobalMemory globalMemory, JfrMetadata metadata) { + public JfrChunkWriter(JfrGlobalMemory globalMemory) { this.lock = new VMMutex("JfrChunkWriter"); this.compressedInts = true; this.globalMemory = globalMemory; - this.metadata = metadata; + this.metadata = new JfrMetadata(null); } @Override @@ -130,7 +133,8 @@ public boolean openFile(String outputFile) { isFinal = false; generation = 1; newChunk = true; - metadata.setDirty(); + lastMetadataId = -1; + metadataPosition = WordFactory.signed(-1); chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; @@ -140,13 +144,9 @@ public boolean openFile(String outputFile) { return true; } - @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") - public boolean write(JfrBuffer buffer) { - return write(buffer, true); - } @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") - public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { + public boolean write(JfrBuffer buffer) { assert JfrBufferAccess.isLockedByCurrentThread(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { @@ -154,9 +154,9 @@ public boolean write(JfrBuffer buffer, boolean increaseFlushedPos) { } boolean success = getFileSupport().write(fd, JfrBufferAccess.getFlushedPos(buffer), unflushedSize); - if (increaseFlushedPos) { - JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); - } + + JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); + if (!success) { // We lost some data because the write failed. return false; @@ -233,8 +233,8 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flush) { getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); getFileSupport().writeLong(fd, chunkSize); getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); - assert metadata.getMetadataPosition().greaterThan(0); - getFileSupport().writeLong(fd, metadata.getMetadataPosition().rawValue()); + assert metadataPosition.greaterThan(0); + getFileSupport().writeLong(fd, metadataPosition.rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); @@ -325,11 +325,14 @@ private int writeSerializers(boolean flush) { } return count; } + public void setMetadata(byte[] bytes) { + metadata.setDescriptor(bytes); + } private void writeMetadataEvent() { assert lock.isOwner(); // always write metadata on a new chunk! - if (!metadata.isDirty()) { + if (lastMetadataId == metadata.getCurrentMetadataId()) { return; } SignedWord start = beginEvent(); @@ -337,9 +340,10 @@ private void writeMetadataEvent() { writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(metadata.getCurrentMetadataId()); // metadata id - writeBytes(metadata.getDescriptorAndClearDirtyFlag()); // payload + writeBytes(metadata.getDescriptor()); // payload endEvent(start); - metadata.setMetadataPosition(start); + metadataPosition = start; + lastMetadataId = metadata.getCurrentMetadataId(); } public boolean shouldRotateDisk() { @@ -516,13 +520,10 @@ protected void operate() { private void changeEpoch() { processSamplerBuffers(); - /* - * Write unflushed data from the thread-local event buffers to the output file. We do - * *not* reinitialize the thread-local buffers as the individual threads will handle - * space reclamation on their own time. - */ - flushStorage(false); + for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + JfrThreadLocal.notifyEventWriter(thread); + } JfrTraceIdEpoch.getInstance().changeEpoch(); @@ -561,13 +562,19 @@ private void processSamplerBuffers0() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage(boolean flush) { - traverseList(getJavaBufferList(), true, flush); - traverseList(getNativeBufferList(), false, flush); + /* + * Write unflushed data from the thread-local event buffers to the output file. We do + * *not* reinitialize the thread-local buffers as the individual threads will handle + * space reclamation on their own time. + */ + traverseList(getJavaBufferList(), flush); + traverseList(getNativeBufferList(), flush); JfrBuffers buffers = globalMemory.getBuffers(); for (int i = 0; i < globalMemory.getBufferCount(); i++) { JfrBuffer buffer = buffers.addressOf(i).read(); if (!JfrBufferAccess.tryLock(buffer)) { // one attempt + assert flush; continue; } write(buffer); @@ -577,9 +584,8 @@ private void flushStorage(boolean flush) { } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer. Locks linked list with no transition. ") - private static void traverseList(JfrBufferNodeLinkedList linkedList, boolean java, boolean flush) { + private void traverseList(JfrBufferNodeLinkedList linkedList, boolean flush) { - boolean firstIteration = true; // hold onto lock until done with the head node. JfrBufferNode node = linkedList.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); @@ -589,31 +595,37 @@ private static void traverseList(JfrBufferNodeLinkedList linkedList, boolean jav JfrBuffer buffer = node.getValue(); assert buffer.isNonNull(); - /* - * I/O operation may be slow, so this flushes to the global buffers instead of - * writing to disk directly. This mitigates the risk of acquiring the TLBs for too - * long. - */ - if (!JfrThreadLocal.flushNoReset(buffer)) { - prev = node; - node = next; - continue; - } - if (!node.getAlive()) { // It is safe to free without acquiring because the owning thread is gone. assert !JfrBufferAccess.isLockedByCurrentThread(buffer); JfrBufferAccess.free(buffer); linkedList.removeNode(node, prev); // if removed current node, should not update prev. + node = next; + continue; + } + + if (flush) { + /* + * I/O operations may be slow, so this flushes to the global buffers instead of + * writing to disk directly. This mitigates the risk of acquiring the TLBs for too + * long. + */ + JfrThreadLocal.flushNoReset(buffer); } else { - // Only notify java event writer if thread is still alive and we are at an epoch - // change. - if (!flush && java) { - JfrThreadLocal.notifyEventWriter(node.getThread()); + /* Buffer should not be locked when entering a safepoint. + * Lock buffer here to satisfy assertion checks. + */ + if(!JfrBufferAccess.tryLock(buffer)) { + assert false; + } + try { + write(buffer); + }finally { + JfrBufferAccess.unlock(buffer); } - prev = node; } + prev = node; node = next; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index af102845ba82..913c238beef0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -66,7 +66,6 @@ public void initialize(long globalBufferSize, long globalBufferCount) { } } - @Uninterruptible(reason = "Locks without transition.") public void clear() { assert VMOperation.isInProgressAtSafepoint(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index 7f11ad337d8a..73e46b72a6f0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -30,46 +30,25 @@ import org.graalvm.word.WordFactory; public class JfrMetadata { - private UninterruptibleUtils.AtomicLong currentMetadataId; + private long currentMetadataId; private volatile byte[] metadataDescriptor; - private volatile boolean isDirty; - private SignedWord metadataPosition; public JfrMetadata(byte[] bytes) { metadataDescriptor = bytes; - currentMetadataId = new UninterruptibleUtils.AtomicLong(0); - isDirty = false; - metadataPosition = WordFactory.signed(-1); + currentMetadataId = 0; } public void setDescriptor(byte[] bytes) { metadataDescriptor = bytes; - currentMetadataId.incrementAndGet(); - isDirty = true; + currentMetadataId++; } - public byte[] getDescriptorAndClearDirtyFlag() { - isDirty = false; + public byte[] getDescriptor() { return metadataDescriptor; } - public boolean isDirty() { - return isDirty; - } - - public void setDirty() { - isDirty = true; - } - - public void setMetadataPosition(SignedWord metadataPosition) { - this.metadataPosition = metadataPosition; - } - - public SignedWord getMetadataPosition() { - return metadataPosition; - } public long getCurrentMetadataId() { - return currentMetadataId.get(); + return currentMetadataId; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 085eea9a3aed..b16b350350bf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -254,7 +254,7 @@ public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); if (unflushedSize.aboveThan(0)) { JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); - // Top is increased in JfrGlobalMemory.write + // flushedPos is increased in JfrGlobalMemory.write if (!globalMemory.write(threadLocalBuffer, unflushedSize, true)) { return false; } 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 dc9304dd5639..5296501f8b07 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 @@ -90,7 +90,6 @@ public class SubstrateJVM { * in). */ private volatile boolean recording; - private JfrMetadata metadata; private String dumpPath; @Platforms(Platform.HOSTED_ONLY.class) @@ -114,8 +113,7 @@ public SubstrateJVM(List configurations) { threadLocal = new JfrThreadLocal(); globalMemory = new JfrGlobalMemory(); samplerBufferPool = new SamplerBufferPool(); - metadata = new JfrMetadata(null); - unlockedChunkWriter = new JfrChunkWriter(globalMemory, metadata); + unlockedChunkWriter = new JfrChunkWriter(globalMemory); recorderThread = new JfrRecorderThread(globalMemory, unlockedChunkWriter); jfrLogging = new JfrLogging(); @@ -292,7 +290,12 @@ public static long getCurrentThreadId() { * See {@link JVM#storeMetadataDescriptor}. */ public void storeMetadataDescriptor(byte[] bytes) { - metadata.setDescriptor(bytes); + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); + try { + chunkWriter.setMetadata(bytes); + } finally { + chunkWriter.unlock(); + } } /** From c4c7c8e87bbf48ab5bc22decd87704bf6ff33000 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 27 Feb 2023 09:10:13 -0500 Subject: [PATCH 51/72] more changes --- .../com/oracle/svm/core/jfr/JfrBuffer.java | 9 +++--- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 29 ++++++++++--------- .../oracle/svm/core/jfr/JfrChunkWriter.java | 19 +++++++----- .../com/oracle/svm/core/jfr/JfrMetadata.java | 4 --- .../svm/core/jfr/JfrMethodRepository.java | 2 +- .../svm/core/jfr/JfrStackTraceRepository.java | 1 + .../oracle/svm/core/jfr/JfrThreadLocal.java | 26 ++++++++++------- .../svm/core/jfr/JfrThreadRepository.java | 1 - .../com/oracle/svm/core/jfr/SubstrateJVM.java | 2 +- 9 files changed, 51 insertions(+), 42 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index e037a2ddd573..f0fcb6a673be 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -37,10 +37,11 @@ /** * A {@link JfrBuffer} is a block of native memory (either thread-local or global) into which JFR - * events are written. The flushedPos is the point up to which data has been flushed. The committedPos is the point up to - * which data has been committed. This means that data between the flushedPos and committedPos is unflushed data that - * is ready to be flushed. This also means that flushedPos should never exceed committedPos. New emitted events - * are written after the commit position. The new events are committed by advancing the committedPos. + * events are written. The flushedPos is the point up to which data has been flushed. The + * committedPos is the point up to which data has been committed. This means that data between the + * flushedPos and committedPos is unflushed data that is ready to be flushed. This also means that + * flushedPos should never exceed committedPos. New emitted events are written after the commit + * position. The new events are committed by advancing the committedPos. */ @RawStructure public interface JfrBuffer extends PointerBase { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 4a5920f2450b..ac6762967cd3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -35,16 +35,15 @@ import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.IsolateThread; -import com.oracle.svm.core.util.VMError; import org.graalvm.word.PointerBase; import com.oracle.svm.core.thread.JavaSpinLockUtils; /** - * {@link JfrBufferNodeLinkedList} is a singly linked list used to store thread local JFR buffers. Threads - * shall only add one node to the list. Only the thread performing a flush or epoch change shall - * iterate this list and is allowed to remove nodes. There is a list-level lock that is acquired - * when adding nodes, and when beginning iteration at the head. Threads may access their own nodes - * at any time up until they set the alive flag to false {@link JfrBufferNode#setAlive}. + * {@link JfrBufferNodeLinkedList} is a singly linked list used to store thread local JFR buffers. + * Threads shall only add one node to the list. Only the thread performing a flush or epoch change + * shall iterate this list and is allowed to remove nodes. There is a list-level lock that is + * acquired when adding nodes, and when beginning iteration at the head. Threads may access their + * own nodes at any time up until they set the alive flag to false {@link JfrBufferNode#setAlive}. * When entering a safepoint, the list lock must not be held by one of the blocked Java threads. */ public class JfrBufferNodeLinkedList { @@ -60,8 +59,9 @@ public interface JfrBufferNode extends PointerBase { JfrBuffer getValue(); /** - * This field is effectively final and should always be non-null. - * Changing its value after the node is added to the {@link JfrBufferNodeLinkedList} can result in races.*/ + * This field is effectively final and should always be non-null. Changing its value after + * the node is added to the {@link JfrBufferNodeLinkedList} can result in races. + */ @RawField void setValue(JfrBuffer value); @@ -85,9 +85,6 @@ public interface JfrBufferNode extends PointerBase { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { - if (buffer.isNull()) { - return WordFactory.nullPointer(); - } JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(JfrBufferNode.class)); if (node.isNonNull()) { node.setAlive(true); @@ -122,8 +119,10 @@ public JfrBufferNode getHead() { } } - /** Removes a node from the linked list. The buffer contained in the nodes must have already been - * freed by the caller.*/ + /** + * Removes a node from the linked list. The buffer contained in the nodes must have already been + * freed by the caller. + */ @Uninterruptible(reason = "Should not be interrupted while flushing.") public void removeNode(JfrBufferNode node, JfrBufferNode prev) { assert head.isNonNull(); @@ -149,7 +148,11 @@ public void removeNode(JfrBufferNode node, JfrBufferNode prev) { */ @Uninterruptible(reason = "Locking with no transition. List must not be acquired entering epoch change.") public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { + assert buffer.isNonNull(); JfrBufferNode newNode = createNode(buffer, thread); + if (newNode.isNull()) { + return WordFactory.nullPointer(); + } acquireList(); try { // Old head could be null 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 e2068303c5c3..1794dbc0a187 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 @@ -86,6 +86,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private boolean isFinal; private long lastMetadataId; private SignedWord metadataPosition; + @Platforms(Platform.HOSTED_ONLY.class) public JfrChunkWriter(JfrGlobalMemory globalMemory) { this.lock = new VMMutex("JfrChunkWriter"); @@ -144,7 +145,6 @@ public boolean openFile(String outputFile) { return true; } - @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public boolean write(JfrBuffer buffer) { assert JfrBufferAccess.isLockedByCurrentThread(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; @@ -325,6 +325,7 @@ private int writeSerializers(boolean flush) { } return count; } + public void setMetadata(byte[] bytes) { metadata.setDescriptor(bytes); } @@ -391,6 +392,7 @@ public void writeCompressedInt(int value) { assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); writeCompressedLong(value & 0xFFFFFFFFL); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeInt(int value) { assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); @@ -563,9 +565,9 @@ private void processSamplerBuffers0() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage(boolean flush) { /* - * Write unflushed data from the thread-local event buffers to the output file. We do - * *not* reinitialize the thread-local buffers as the individual threads will handle - * space reclamation on their own time. + * Write unflushed data from the thread-local event buffers to the output file. We do *not* + * reinitialize the thread-local buffers as the individual threads will handle space + * reclamation on their own time. */ traverseList(getJavaBufferList(), flush); traverseList(getNativeBufferList(), flush); @@ -613,15 +615,16 @@ private void traverseList(JfrBufferNodeLinkedList linkedList, boolean flush) { */ JfrThreadLocal.flushNoReset(buffer); } else { - /* Buffer should not be locked when entering a safepoint. - * Lock buffer here to satisfy assertion checks. + /* + * Buffer should not be locked when entering a safepoint. Lock buffer here to + * satisfy assertion checks. */ - if(!JfrBufferAccess.tryLock(buffer)) { + if (!JfrBufferAccess.tryLock(buffer)) { assert false; } try { write(buffer); - }finally { + } finally { JfrBufferAccess.unlock(buffer); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index 73e46b72a6f0..4386226127af 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -25,9 +25,6 @@ */ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.jdk.UninterruptibleUtils; -import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; public class JfrMetadata { private long currentMetadataId; @@ -47,7 +44,6 @@ public byte[] getDescriptor() { return metadataDescriptor; } - public long getCurrentMetadataId() { return currentMetadataId; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 734ab01fcac5..46171c0bc8c9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -148,7 +148,7 @@ private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochD writer.writeCompressedLong(JfrType.Method.getId()); writer.writeCompressedInt(numberOfMethods); writer.write(epochData.methodBuffer); - + JfrBufferAccess.reinitialize(epochData.methodBuffer); epochData.unflushedMethodCount = 0; return NON_EMPTY; } 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 ad0e2d09f575..4d3224fb05d8 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 @@ -269,6 +269,7 @@ private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochDat writer.writeCompressedLong(JfrType.StackTrace.getId()); writer.writeCompressedInt(epochData.numberOfSerializedStackTraces); writer.write(epochData.stackTraceBuffer); + JfrBufferAccess.reinitialize(epochData.stackTraceBuffer); epochData.numberOfSerializedStackTraces = 0; return NON_EMPTY; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index b16b350350bf..917a228041ea 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -210,14 +210,17 @@ public JfrBuffer getJavaBuffer() { JfrBufferNode result = javaBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); + if (buffer.isNull()) { + return WordFactory.nullPointer(); + } result = javaBufferList.addNode(buffer, CurrentIsolate.getCurrentThread()); + if (result.isNull()) { + JfrBufferAccess.free(buffer); + return WordFactory.nullPointer(); + } javaBufferNode.set(result); } - // result can still be null if allocation of a node or JFR buffer fails. - if (result.isNonNull()) { - return result.getValue(); - } - return WordFactory.nullPointer(); + return result.getValue(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) @@ -225,14 +228,17 @@ public JfrBuffer getNativeBuffer() { JfrBufferNode result = nativeBufferNode.get(); if (result.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); + if (buffer.isNull()) { + return WordFactory.nullPointer(); + } result = nativeBufferList.addNode(buffer, CurrentIsolate.getCurrentThread()); + if (result.isNull()) { + JfrBufferAccess.free(buffer); + return WordFactory.nullPointer(); + } nativeBufferNode.set(result); } - // result can still be null if allocation of a node or JFR buffer fails. - if (result.isNonNull()) { - return result.getValue(); - } - return WordFactory.nullPointer(); + return result.getValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index f8b93a6f0572..fb9f36886089 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.os.RawFileOperationSupport; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; 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 5296501f8b07..6d37aa8f4bb9 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 @@ -736,7 +736,7 @@ protected void operate() { * If JFR recording is restarted later on, then it needs to start with a clean state. * Therefore, we clear all data that is still pending. */ - ((JfrThreadLocal)SubstrateJVM.getThreadLocal()).teardown(); + ((JfrThreadLocal) SubstrateJVM.getThreadLocal()).teardown(); SubstrateJVM.getSamplerBufferPool().teardown(); SubstrateJVM.getGlobalMemory().clear(); } From f273004ab34a44ed0a3c59d98b8de16a0cdbd644 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 27 Feb 2023 14:40:55 -0500 Subject: [PATCH 52/72] Thread repo, type repo review comments --- .../oracle/svm/core/jfr/JfrBufferNode.java | 67 +++++ .../svm/core/jfr/JfrBufferNodeLinkedList.java | 51 +--- .../oracle/svm/core/jfr/JfrChunkWriter.java | 13 +- .../com/oracle/svm/core/jfr/JfrMetadata.java | 1 - .../oracle/svm/core/jfr/JfrThreadLocal.java | 49 ++-- .../svm/core/jfr/JfrThreadRepository.java | 41 ++-- .../svm/core/jfr/JfrTypeRepository.java | 228 +++++++++--------- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 9 +- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 4 +- .../com/oracle/svm/core/locks/VMMutex.java | 2 +- .../test/jfr/TestJfrBufferNodeLinkedList.java | 15 +- 11 files changed, 259 insertions(+), 221 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java new file mode 100644 index 000000000000..5fc1a400aa0f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java @@ -0,0 +1,67 @@ +/* + * 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.core.jfr; + +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.RawFieldOffset; +import org.graalvm.word.PointerBase; +import com.oracle.svm.core.util.VMError; +import org.graalvm.nativeimage.IsolateThread; + +@RawStructure +public interface JfrBufferNode extends PointerBase { + @RawField + JfrBufferNode getNext(); + + @RawField + void setNext(JfrBufferNode value); + + @RawField + JfrBuffer getValue(); + + /** + * This field is effectively final and should always be non-null. Changing its value after + * the node is added to the {@link JfrBufferNodeLinkedList} can result in races. + */ + @RawField + void setValue(JfrBuffer value); + + @RawField + IsolateThread getThread(); + + @RawField + void setThread(IsolateThread thread); + + @RawField + boolean getAlive(); + + @RawFieldOffset + static int offsetOfAlive() { + throw VMError.unimplemented(); // replaced + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index ac6762967cd3..f09fa9979e18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -31,11 +31,10 @@ import org.graalvm.word.WordFactory; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.struct.RawStructure; + import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.word.PointerBase; + import com.oracle.svm.core.thread.JavaSpinLockUtils; /** @@ -43,40 +42,11 @@ * Threads shall only add one node to the list. Only the thread performing a flush or epoch change * shall iterate this list and is allowed to remove nodes. There is a list-level lock that is * acquired when adding nodes, and when beginning iteration at the head. Threads may access their - * own nodes at any time up until they set the alive flag to false {@link JfrBufferNode#setAlive}. - * When entering a safepoint, the list lock must not be held by one of the blocked Java threads. + * own nodes at any time up until they set the alive flag to false + * {@link com.oracle.svm.core.jfr.JfrBufferNodeAccess#setRetired(JfrBufferNode)}. When entering a + * safepoint, the list lock must not be held by one of the blocked Java threads. */ public class JfrBufferNodeLinkedList { - @RawStructure - public interface JfrBufferNode extends PointerBase { - @RawField - JfrBufferNode getNext(); - - @RawField - void setNext(JfrBufferNode value); - - @RawField - JfrBuffer getValue(); - - /** - * This field is effectively final and should always be non-null. Changing its value after - * the node is added to the {@link JfrBufferNodeLinkedList} can result in races. - */ - @RawField - void setValue(JfrBuffer value); - - @RawField - IsolateThread getThread(); - - @RawField - void setThread(IsolateThread thread); - - @RawField - boolean getAlive(); - - @RawField - void setAlive(boolean alive); - } private static final long LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(JfrBufferNodeLinkedList.class, "lock"); @@ -87,7 +57,7 @@ public interface JfrBufferNode extends PointerBase { private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(JfrBufferNode.class)); if (node.isNonNull()) { - node.setAlive(true); + JfrBufferNodeAccess.setAlive(node); node.setValue(buffer); node.setThread(thread); node.setNext(WordFactory.nullPointer()); @@ -103,13 +73,18 @@ public void teardown() { while (node.isNonNull()) { JfrBufferNode next = node.getNext(); JfrBufferAccess.free(node.getValue()); - node.setAlive(false); + /* + * Once JfrBufferNode. JfrBufferNodeAccess.setRetired(node) is called, another thread + * may free the node at any time. In this case it shouldn't matter because the recording + * has ended and this is called at a safepoint. + */ + JfrBufferNodeAccess.setRetired(node); removeNode(node, WordFactory.nullPointer()); node = next; } } - @Uninterruptible(reason = "Locking with no transition.", callerMustBe = true) + @Uninterruptible(reason = "Locking with no transition.") public JfrBufferNode getHead() { acquireList(); try { 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 1794dbc0a187..f25d5ab0d46f 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 @@ -48,7 +48,6 @@ import com.oracle.svm.core.thread.VMOperationControl; import com.oracle.svm.core.locks.VMMutex; -import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; @@ -371,37 +370,37 @@ public void endEvent(SignedWord start) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeBoolean(boolean value) { - assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); writeByte((byte) (value ? 1 : 0)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeByte(byte value) { - assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); getFileSupport().writeByte(fd, value); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeBytes(byte[] values) { - assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); getFileSupport().write(fd, values); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeCompressedInt(int value) { - assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); writeCompressedLong(value & 0xFFFFFFFFL); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeInt(int value) { - assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); getFileSupport().writeInt(fd, makePaddedInt(value)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeCompressedLong(long value) { - assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.isOwned(); + assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); long v = value; if ((v & ~0x7FL) == 0L) { getFileSupport().writeByte(fd, (byte) v); // 0-6 diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index 4386226127af..9af6dc752fb9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -25,7 +25,6 @@ */ package com.oracle.svm.core.jfr; - public class JfrMetadata { private long currentMetadataId; private volatile byte[] metadataDescriptor; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 917a228041ea..44262aea2cfd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -49,7 +49,6 @@ import com.oracle.svm.core.thread.Target_java_lang_Thread; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.thread.JavaThreads; -import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; /** * This class holds various JFR-specific thread local values. @@ -134,25 +133,28 @@ public static void stopRecording(IsolateThread isolateThread) { JfrBufferNode jbn = javaBufferNode.get(isolateThread); JfrBufferNode nbn = nativeBufferNode.get(isolateThread); + /* + * Once JfrBufferNode. JfrBufferNodeAccess.setRetired(JfrBufferNode) is called, another + * thread may free the node at any time. + */ if (jbn.isNonNull()) { JfrBuffer jb = jbn.getValue(); assert jb.isNonNull() && jbn.getAlive(); flush(jb, WordFactory.unsigned(0), 0); - jbn.setAlive(false); - + javaBufferNode.set(isolateThread, WordFactory.nullPointer()); + JfrBufferNodeAccess.setRetired(jbn); } if (nbn.isNonNull()) { JfrBuffer nb = nbn.getValue(); assert nb.isNonNull() && nbn.getAlive(); flush(nb, WordFactory.unsigned(0), 0); - nbn.setAlive(false); + nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); + JfrBufferNodeAccess.setRetired(nbn); } /* Clear event-related thread-locals. */ dataLost.set(isolateThread, WordFactory.unsigned(0)); javaEventWriter.set(isolateThread, null); - javaBufferNode.set(isolateThread, WordFactory.nullPointer()); - nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); /* Clear stacktrace-related thread-locals. */ missedSamples.set(isolateThread, 0); @@ -330,40 +332,25 @@ private static UnsignedWord increaseDataLost(UnsignedWord delta) { } public void teardown() { - JfrBufferNodeLinkedList nativeBuffers = getNativeBufferList(); - if (nativeBuffers != null) { - nativeBuffers.teardown(); - } - JfrBufferNodeLinkedList javaBuffers = getJavaBufferList(); - if (javaBuffers != null) { - javaBuffers.teardown(); - } - } - - public void exclude(Thread thread) { - if (!thread.equals(Thread.currentThread())) { - return; - } - IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); - Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); - tjlt.jfrExcluded = true; - - if (javaEventWriter.get(currentIsolateThread) != null && !JavaThreads.isVirtual(thread)) { - javaEventWriter.get(currentIsolateThread).excluded = true; - } + getNativeBufferList().teardown(); + getJavaBufferList().teardown(); } - public void include(Thread thread) { + /** + * This method excludes/includes a thread from JFR (emitting events and sampling). Unlike in + * hotspot, only the current thread may be excluded/included. TODO: possibly modify this method + * to match hotspot behaviour. + */ + public void setExcluded(Thread thread, boolean excluded) { if (!thread.equals(Thread.currentThread())) { return; } - IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); - tjlt.jfrExcluded = false; + tjlt.jfrExcluded = excluded; if (javaEventWriter.get(currentIsolateThread) != null && !JavaThreads.isVirtual(thread)) { - javaEventWriter.get(currentIsolateThread).excluded = false; + javaEventWriter.get(currentIsolateThread).excluded = excluded; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index fb9f36886089..e445e36236a3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -102,6 +102,8 @@ private void registerThread0(Thread thread) { return; } + epochData.unflushedThreadCount++; + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); @@ -158,6 +160,7 @@ private void registerThreadGroup(long threadGroupId, ThreadGroup threadGroup) { if (!epochData.visitedThreadGroups.putIfAbsent(jfrVisited)) { return; } + epochData.unflushedThreadGroupCount++; JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initialize(data, epochData.threadGroupBuffer); @@ -209,12 +212,6 @@ public int write(JfrChunkWriter writer, boolean flush) { int count = writeThreads(writer, epochData); count += writeThreadGroups(writer, epochData); - /* - * Thread repository epoch data may be cleared after a flush because threads are only - * registered once with fixed IDs. There is no need to do a lookup in this repo for a - * thread's ID. - */ - epochData.clear(); return count; } @@ -224,7 +221,7 @@ public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord la JfrThreadEpochData epochData = getEpochData(!flush); maybeLock(flush); try { - if (epochData.visitedThreads.getSize() == 0) { + if (epochData.unflushedThreadCount == 0) { return lastCheckpointOffset; } SignedWord start = writer.beginEvent(); @@ -239,7 +236,7 @@ public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord la // If only writing threads pool, count is 1 int poolCount = 1; - if (epochData.visitedThreadGroups.getSize() > 0) { + if (epochData.unflushedThreadGroupCount > 0) { poolCount = 2; } writer.writeInt(poolCount); @@ -252,23 +249,33 @@ public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord la } finally { maybeUnlock(flush); + /* + * Thread repository epoch data may be cleared after a flush because threads are only + * registered once with fixed IDs. There is no need to do a lookup in this repo for a + * thread's ID. However, we preserve visitedThreads and visitedThreadGroups until the + * epoch change to avoid unnecessary re-registrations. + */ + if (!flush) { + epochData.clear(); + } } } @Uninterruptible(reason = "May write current epoch data.") private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { - VMError.guarantee(epochData.visitedThreads.getSize() > 0, "Thread repository must not be empty."); + VMError.guarantee(epochData.unflushedThreadCount > 0, "Thread repository must not be empty."); writer.writeCompressedLong(JfrType.Thread.getId()); - writer.writeCompressedInt(epochData.visitedThreads.getSize()); + writer.writeCompressedInt(epochData.unflushedThreadCount); writer.write(epochData.threadBuffer); - + JfrBufferAccess.reinitialize(epochData.threadBuffer); + epochData.unflushedThreadCount = 0; return NON_EMPTY; } @Uninterruptible(reason = "May write current epoch data.") private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { - int threadGroupCount = epochData.visitedThreadGroups.getSize(); + int threadGroupCount = epochData.unflushedThreadGroupCount; if (threadGroupCount == 0) { return EMPTY; } @@ -276,7 +283,8 @@ private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData e writer.writeCompressedLong(JfrType.ThreadGroup.getId()); writer.writeCompressedInt(threadGroupCount); writer.write(epochData.threadGroupBuffer); - + JfrBufferAccess.reinitialize(epochData.threadGroupBuffer); + epochData.unflushedThreadGroupCount = 0; return NON_EMPTY; } @@ -294,6 +302,8 @@ private static class JfrThreadEpochData { */ private final JfrVisitedTable visitedThreads; private final JfrVisitedTable visitedThreadGroups; + private int unflushedThreadCount; + private int unflushedThreadGroupCount; private JfrBuffer threadBuffer; private JfrBuffer threadGroupBuffer; @@ -302,13 +312,16 @@ private static class JfrThreadEpochData { JfrThreadEpochData() { this.visitedThreads = new JfrVisitedTable(); this.visitedThreadGroups = new JfrVisitedTable(); + this.unflushedThreadCount = 0; + this.unflushedThreadGroupCount = 0; } @Uninterruptible(reason = "May write current epoch data.") public void clear() { visitedThreads.clear(); visitedThreadGroups.clear(); - + unflushedThreadCount = 0; + unflushedThreadGroupCount = 0; JfrBufferAccess.reinitialize(threadBuffer); JfrBufferAccess.reinitialize(threadGroupBuffer); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index f3cc09b190c4..ae000143a115 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -39,26 +39,21 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceId; /** - * Repository that collects and writes used classes, packages, modules, and classloaders. Only one - * thread should ever access this repository at a time. It is only used during flushes and chunk - * rotations. This means that the maps in this repository will be entirely used and cleared with + * Repository that collects and writes used classes, packages, modules, and classloaders. Fields + * that store state are only used during flushes and chunk rotations (only one thread will use them + * at a time). This means that the maps in this repository will be entirely used and cleared with * respect to the current epoch before they are used for the subsequent epoch. * - * The "old" maps hold records with respect to an entire epoch, while the "new" maps are with - * respect to the current flush / chunk rotation. + * The "epoch" maps hold records with respect to a specific epoch and are reset at an epoch change. */ public class JfrTypeRepository implements JfrConstantPool { - private static final Set> oldClasses = new HashSet<>(); - private static final Map oldPackages = new HashMap<>(); - private static final Map oldModules = new HashMap<>(); - private static final Map oldClassLoaders = new HashMap<>(); - private static final Set> newClasses = new HashSet<>(); - private static final Map newPackages = new HashMap<>(); - private static final Map newModules = new HashMap<>(); - private static final Map newClassLoaders = new HashMap<>(); - private static long currentPackageId = 0; - private static long currentModuleId = 0; - private static long currentClassLoaderId = 0; + private final Set> epochClasses = new HashSet<>(); + private final Map epochPackages = new HashMap<>(); + private final Map epochModules = new HashMap<>(); + private final Map epochClassLoaders = new HashMap<>(); + private long currentPackageId = 0; + private long currentModuleId = 0; + private long currentClassLoaderId = 0; @Platforms(Platform.HOSTED_ONLY.class) public JfrTypeRepository() { @@ -73,16 +68,19 @@ public long getClassId(Class clazz) { public int write(JfrChunkWriter writer, boolean flush) { // Visit all used classes, and collect their packages, modules, classloaders and possibly // referenced classes. - collectTypeInfo(flush); + TypeInfo typeInfo = collectTypeInfo(flush); // The order of writing matters as following types can be tagged during the write process - int count = writeClasses(writer, flush); - count += writePackages(writer, flush); - count += writeModules(writer, flush); - count += writeClassLoaders(writer, flush); + int count = writeClasses(typeInfo, writer, flush); + count += writePackages(typeInfo, writer, flush); + count += writeModules(typeInfo, writer, flush); + count += writeClassLoaders(typeInfo, writer, flush); if (flush) { - clearFlush(); + epochClasses.addAll(typeInfo.classes); + epochPackages.putAll(typeInfo.packages); + epochModules.putAll(typeInfo.modules); + epochClassLoaders.putAll(typeInfo.classLoaders); } else { clearEpochChange(); } @@ -90,132 +88,129 @@ public int write(JfrChunkWriter writer, boolean flush) { return count; } - private static void collectTypeInfo(boolean flush) { - + private TypeInfo collectTypeInfo(boolean flush) { + TypeInfo typeInfo = new TypeInfo(); for (Class clazz : Heap.getHeap().getLoadedClasses()) { if (flush) { if (JfrTraceId.isUsedCurrentEpoch(clazz)) { - visitClass(clazz); + visitClass(typeInfo, clazz); } } else if (JfrTraceId.isUsedPreviousEpoch(clazz)) { JfrTraceId.clearUsedPreviousEpoch(clazz); - visitClass(clazz); + visitClass(typeInfo, clazz); } } + return typeInfo; } - private static void visitClass(Class clazz) { - if (clazz != null && addClass(clazz)) { - visitPackage(clazz.getPackage(), clazz.getModule()); - visitClass(clazz.getSuperclass()); + private void visitClass(TypeInfo typeInfo, Class clazz) { + if (clazz != null && addClass(typeInfo, clazz)) { + visitPackage(typeInfo, clazz.getPackage(), clazz.getModule()); + visitClass(typeInfo, clazz.getSuperclass()); } } - private static void visitPackage(Package pkg, Module module) { - if (pkg != null && addPackage(pkg, module)) { - visitModule(module); + private void visitPackage(TypeInfo typeInfo, Package pkg, Module module) { + if (pkg != null && addPackage(typeInfo, pkg, module)) { + visitModule(typeInfo, module); } } - private static void visitModule(Module module) { - if (module != null && addModule(module)) { - visitClassLoader(module.getClassLoader()); + private void visitModule(TypeInfo typeInfo, Module module) { + if (module != null && addModule(typeInfo, module)) { + visitClassLoader(typeInfo, module.getClassLoader()); } } - private static void visitClassLoader(ClassLoader classLoader) { + private void visitClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { // The null class-loader is serialized as the "bootstrap" class-loader. - if (classLoader != null && addClassLoader(classLoader)) { - visitClass(classLoader.getClass()); + if (classLoader != null && addClassLoader(typeInfo, classLoader)) { + visitClass(typeInfo, classLoader.getClass()); } } - public static int writeClasses(JfrChunkWriter writer, boolean flush) { - if (newClasses.isEmpty()) { + private int writeClasses(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + if (typeInfo.classes.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.Class.getId()); - writer.writeCompressedInt(newClasses.size()); + writer.writeCompressedInt(typeInfo.classes.size()); - for (Class clazz : newClasses) { - writeClass(writer, clazz, flush); - oldClasses.add(clazz); + for (Class clazz : typeInfo.classes) { + writeClass(typeInfo, writer, clazz, flush); } return NON_EMPTY; } - private static void writeClass(JfrChunkWriter writer, Class clazz, boolean flush) { + private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); // key - writer.writeCompressedLong(getClassLoaderId(clazz.getClassLoader())); + writer.writeCompressedLong(getClassLoaderId(typeInfo, clazz.getClassLoader())); writer.writeCompressedLong(symbolRepo.getSymbolId(clazz.getName(), !flush, true)); - writer.writeCompressedLong(getPackageId(clazz.getPackage())); + writer.writeCompressedLong(getPackageId(typeInfo, clazz.getPackage())); writer.writeCompressedLong(clazz.getModifiers()); if (JavaVersionUtil.JAVA_SPEC >= 17) { writer.writeBoolean(SubstrateUtil.isHiddenClass(clazz)); } } - private static int writePackages(JfrChunkWriter writer, boolean flush) { - if (newPackages.isEmpty()) { + private int writePackages(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + if (typeInfo.packages.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.Package.getId()); - writer.writeCompressedInt(newPackages.size()); + writer.writeCompressedInt(typeInfo.packages.size()); - for (Map.Entry pkgInfo : newPackages.entrySet()) { - writePackage(writer, pkgInfo.getKey(), pkgInfo.getValue(), flush); - oldPackages.put(pkgInfo.getKey(), pkgInfo.getValue()); + for (Map.Entry pkgInfo : typeInfo.packages.entrySet()) { + writePackage(typeInfo, writer, pkgInfo.getKey(), pkgInfo.getValue(), flush); } return NON_EMPTY; } - private static void writePackage(JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flush) { + private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(pkgInfo.id); // id writer.writeCompressedLong(symbolRepo.getSymbolId(pkgName, !flush, true)); - writer.writeCompressedLong(getModuleId(pkgInfo.module)); + writer.writeCompressedLong(getModuleId(typeInfo, pkgInfo.module)); writer.writeBoolean(false); // exported } - private static int writeModules(JfrChunkWriter writer, boolean flush) { - if (newModules.isEmpty()) { + private int writeModules(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + if (typeInfo.modules.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.Module.getId()); - writer.writeCompressedInt(newModules.size()); + writer.writeCompressedInt(typeInfo.modules.size()); - for (Map.Entry modInfo : newModules.entrySet()) { - writeModule(writer, modInfo.getKey(), modInfo.getValue(), flush); - oldModules.put(modInfo.getKey(), modInfo.getValue()); + for (Map.Entry modInfo : typeInfo.modules.entrySet()) { + writeModule(typeInfo, writer, modInfo.getKey(), modInfo.getValue(), flush); } return NON_EMPTY; } - private static void writeModule(JfrChunkWriter writer, Module module, long id, boolean flush) { + private void writeModule(TypeInfo typeInfo, JfrChunkWriter writer, Module module, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); writer.writeCompressedLong(symbolRepo.getSymbolId(module.getName(), !flush)); writer.writeCompressedLong(0); // Version, e.g. "11.0.10-internal" writer.writeCompressedLong(0); // Location, e.g. "jrt:/java.base" - writer.writeCompressedLong(getClassLoaderId(module.getClassLoader())); + writer.writeCompressedLong(getClassLoaderId(typeInfo, module.getClassLoader())); } - private static int writeClassLoaders(JfrChunkWriter writer, boolean flush) { - if (newClassLoaders.isEmpty()) { + private int writeClassLoaders(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + if (typeInfo.classLoaders.isEmpty()) { return EMPTY; } writer.writeCompressedLong(JfrType.ClassLoader.getId()); - writer.writeCompressedInt(newClassLoaders.size()); + writer.writeCompressedInt(typeInfo.classLoaders.size()); - for (Map.Entry clInfo : newClassLoaders.entrySet()) { - writeClassLoader(writer, clInfo.getKey(), clInfo.getValue(), flush); - oldClassLoaders.put(clInfo.getKey(), clInfo.getValue()); + for (Map.Entry clInfo : typeInfo.classLoaders.entrySet()) { + writeClassLoader(typeInfo, writer, clInfo.getKey(), clInfo.getValue(), flush); } return NON_EMPTY; } - private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { + private void writeClassLoader(TypeInfo typeInfo, JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); if (cl == null) { @@ -237,107 +232,106 @@ private static class PackageInfo { } } - static boolean addClass(Class clazz) { - if (isClassVisited(clazz)) { + private boolean addClass(TypeInfo typeInfo, Class clazz) { + if (isClassVisited(typeInfo, clazz)) { return false; } - return newClasses.add(clazz); + return typeInfo.classes.add(clazz); } - static boolean isClassVisited(Class clazz) { - return newClasses.contains(clazz) || oldClasses.contains(clazz); + private boolean isClassVisited(TypeInfo typeInfo, Class clazz) { + return typeInfo.classes.contains(clazz) || epochClasses.contains(clazz); } - static boolean addPackage(Package pkg, Module module) { - if (!isPackageVisited(pkg)) { + private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module) { + if (!isPackageVisited(typeInfo, pkg)) { // The empty package represented by "" is always traced with id 0 long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; - newPackages.put(pkg.getName(), new PackageInfo(id, module)); + typeInfo.packages.put(pkg.getName(), new PackageInfo(id, module)); return true; } else { - assert oldPackages.containsKey(pkg.getName()) ? module == oldPackages.get(pkg.getName()).module : module == newPackages.get(pkg.getName()).module; + assert epochPackages.containsKey(pkg.getName()) ? module == epochPackages.get(pkg.getName()).module : module == typeInfo.packages.get(pkg.getName()).module; return false; } } - static boolean isPackageVisited(Package pkg) { - return oldPackages.containsKey(pkg.getName()) || newPackages.containsKey(pkg.getName()); + private boolean isPackageVisited(TypeInfo typeInfo, Package pkg) { + return epochPackages.containsKey(pkg.getName()) || typeInfo.packages.containsKey(pkg.getName()); } - static long getPackageId(Package pkg) { + private long getPackageId(TypeInfo typeInfo, Package pkg) { if (pkg != null) { - if (oldPackages.containsKey(pkg.getName())) { - return oldPackages.get(pkg.getName()).id; + if (epochPackages.containsKey(pkg.getName())) { + return epochPackages.get(pkg.getName()).id; } - return newPackages.get(pkg.getName()).id; + return typeInfo.packages.get(pkg.getName()).id; } else { return 0; } } - static boolean addModule(Module module) { - if (!isModuleVisited(module)) { - newModules.put(module, ++currentModuleId); + private boolean addModule(TypeInfo typeInfo, Module module) { + if (!isModuleVisited(typeInfo, module)) { + typeInfo.modules.put(module, ++currentModuleId); return true; } else { return false; } } - static boolean isModuleVisited(Module module) { - return newModules.containsKey(module) || oldModules.containsKey(module); + private boolean isModuleVisited(TypeInfo typeInfo, Module module) { + return typeInfo.modules.containsKey(module) || epochModules.containsKey(module); } - static long getModuleId(Module module) { + private long getModuleId(TypeInfo typeInfo, Module module) { if (module != null) { - if (oldModules.containsKey(module)) { - return oldModules.get(module); + if (epochModules.containsKey(module)) { + return epochModules.get(module); } - return newModules.get(module); + return typeInfo.modules.get(module); } else { return 0; } } - static boolean addClassLoader(ClassLoader classLoader) { - if (!isClassLoaderVisited(classLoader)) { - newClassLoaders.put(classLoader, ++currentClassLoaderId); + private boolean addClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { + if (!isClassLoaderVisited(typeInfo, classLoader)) { + typeInfo.classLoaders.put(classLoader, ++currentClassLoaderId); return true; } else { return false; } } - static boolean isClassLoaderVisited(ClassLoader classLoader) { - return oldClassLoaders.containsKey(classLoader) || newClassLoaders.containsKey(classLoader); + private boolean isClassLoaderVisited(TypeInfo typeInfo, ClassLoader classLoader) { + return epochClassLoaders.containsKey(classLoader) || typeInfo.classLoaders.containsKey(classLoader); } - static long getClassLoaderId(ClassLoader classLoader) { + private long getClassLoaderId(TypeInfo typeInfo, ClassLoader classLoader) { if (classLoader != null) { - if (oldClassLoaders.containsKey(classLoader)) { - return oldClassLoaders.get(classLoader); + if (epochClassLoaders.containsKey(classLoader)) { + return epochClassLoaders.get(classLoader); } - return newClassLoaders.get(classLoader); + return typeInfo.classLoaders.get(classLoader); } else { return 0; } } - private static void clearFlush() { - newModules.clear(); - newPackages.clear(); - newClassLoaders.clear(); - newClasses.clear(); - } - - private static void clearEpochChange() { - clearFlush(); - oldClasses.clear(); - oldPackages.clear(); - oldModules.clear(); - oldClassLoaders.clear(); + private void clearEpochChange() { + epochClasses.clear(); + epochPackages.clear(); + epochModules.clear(); + epochClassLoaders.clear(); currentPackageId = 0; currentModuleId = 0; currentClassLoaderId = 0; } + + private static class TypeInfo { + public final Set> classes = new HashSet<>(); + public final Map packages = new HashMap<>(); + public final Map modules = new HashMap<>(); + public final Map classLoaders = new HashMap<>(); + } } 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 6d37aa8f4bb9..852f23e0f1c9 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 @@ -673,14 +673,9 @@ public Object getConfiguration(Class eventClass) { return DynamicHub.fromClass(eventClass).getJfrEventConfiguration(); } - public void exclude(Thread thread) { + public void setExcluded(Thread thread, boolean excluded) { JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); - jfrThreadLocal.exclude(thread); - } - - public void include(Thread thread) { - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); - jfrThreadLocal.include(thread); + jfrThreadLocal.setExcluded(thread, excluded); } public boolean isExcluded(Thread thread) { 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 848bccc800f7..961f6f4da841 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 @@ -440,13 +440,13 @@ public void flush() { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void include(Thread thread) { - SubstrateJVM.get().include(thread); + SubstrateJVM.get().setExcluded(thread, false); } @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void exclude(Thread thread) { - SubstrateJVM.get().exclude(thread); + SubstrateJVM.get().setExcluded(thread, true); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java index 7594e6ab81f6..6b1af217322d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java @@ -197,7 +197,7 @@ public void clearUnspecifiedOwner() { } @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean isOwned() { + public boolean hasOwner() { return owner.isNonNull(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index ae29b8de9b68..fb2dee916fd7 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -29,11 +29,12 @@ import org.junit.Test; import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; -import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList.JfrBufferNode; +import com.oracle.svm.core.jfr.JfrBufferNode; import org.graalvm.word.WordFactory; import com.oracle.svm.core.jfr.JfrBufferType; import com.oracle.svm.core.jfr.JfrBuffer; import com.oracle.svm.core.jfr.JfrBufferAccess; +import com.oracle.svm.core.jfr.JfrBufferNodeAccess; import org.graalvm.nativeimage.CurrentIsolate; import static org.junit.Assert.assertTrue; import com.oracle.svm.core.Uninterruptible; @@ -104,7 +105,11 @@ private static JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { while (node.isNonNull()) { JfrBufferNode next = node.getNext(); JfrBufferAccess.free(node.getValue()); - node.setAlive(false); + /* + * Once JfrBufferNodeAccess.setRetired(node) is called, another thread may free the node + * at any time. + */ + JfrBufferNodeAccess.setRetired(node); list.removeNode(node, WordFactory.nullPointer()); node = next; } @@ -120,7 +125,11 @@ private static void removeNthNode(JfrBufferNodeLinkedList list, int target) { JfrBufferNode next = node.getNext(); if (count == target) { JfrBufferAccess.free(node.getValue()); - node.setAlive(false); + /* + * Once JfrBufferNodeAccess.setRetired(node) is called, another thread may free the + * node at any time. + */ + JfrBufferNodeAccess.setRetired(node); list.removeNode(node, prev); break; } From 083f1c49611f70f6c90f705c323f62a207514ad5 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 27 Feb 2023 16:23:50 -0500 Subject: [PATCH 53/72] JavaOwnedSpinLockUtils --- .../com/oracle/svm/core/jfr/JfrBuffer.java | 8 -- .../svm/core/jfr/JfrBufferNodeAccess.java | 57 +++++++++ .../core/thread/JavaOwnedSpinLockUtils.java | 113 ++++++++++++++++++ 3 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index f0fcb6a673be..753650016c60 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -88,14 +88,6 @@ static int offsetOfCommittedPos() { @RawField void setFlushedPos(Pointer value); - @RawField - int getLocked(); - - @RawFieldOffset - static int offsetOfLocked() { - throw VMError.unimplemented(); // replaced - } - /** * Returns the type of the buffer. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java new file mode 100644 index 000000000000..a5fd8c2c7b51 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -0,0 +1,57 @@ +/* + * 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.core.jfr; + +import jdk.internal.misc.Unsafe; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.word.Pointer; +import com.oracle.svm.core.Uninterruptible; + + +/** + * Used to access the raw memory of a {@link com.oracle.svm.core.jfr.JfrBufferNode}. + */ +public final class JfrBufferNodeAccess { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private JfrBufferNodeAccess() { + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void setRetired(JfrBufferNode node) { + UNSAFE.putBooleanVolatile(null, ptrToLock(node).rawValue(), false); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void setAlive(JfrBufferNode node) { + UNSAFE.putBooleanVolatile(null, ptrToLock(node).rawValue(), true); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static CIntPointer ptrToLock(JfrBufferNode node) { + return (CIntPointer) ((Pointer) node).add(JfrBufferNode.offsetOfAlive()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java new file mode 100644 index 000000000000..a7bf91609bd9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java @@ -0,0 +1,113 @@ +/* + * 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.core.thread; + +import org.graalvm.compiler.nodes.PauseNode; + +import com.oracle.svm.core.Uninterruptible; +import org.graalvm.word.WordFactory; +import jdk.internal.misc.Unsafe; +import org.graalvm.nativeimage.CurrentIsolate; + +/** + * Spin locks may only be used in places where the critical section contains only a few instructions + * of uninterruptible code. We don't do a transition to native in case of a lock contention, so it + * is crucial that really all code within the critical section is uninterruptible. + */ +public class JavaOwnedSpinLockUtils { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final long UNLOCKED = WordFactory.nullPointer().rawValue(); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void initialize(Object obj, long wordFieldOffset) { + UNSAFE.putLongVolatile(obj, wordFieldOffset, UNLOCKED); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isLocked(Object obj, long wordFieldOffset) { + return UNSAFE.getLongOpaque(obj, wordFieldOffset) != UNLOCKED; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean tryLock(Object obj, long wordFieldOffset) { + return UNSAFE.compareAndSetLong(obj, wordFieldOffset, UNLOCKED, CurrentIsolate.getCurrentThread().rawValue()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean tryLock(Object obj, long wordFieldOffset, int retries) { + if (tryLock(obj, wordFieldOffset)) { + return true; // fast-path + } + + int yields = 0; + for (int i = 0; i < retries; i++) { + if (isLocked(obj, wordFieldOffset)) { + /* + * It would be better to take into account if we are on a single-processor machine + * where spinning is futile. However, determining that is expensive in itself. We do + * use fewer successive spins than the equivalent HotSpot code does (0xFFF). + */ + if ((i & 0xff) == 0 && VMThreads.singleton().supportsNativeYieldAndSleep()) { + if (yields > 5) { + VMThreads.singleton().nativeSleep(1); + } else { + VMThreads.singleton().yield(); + yields++; + } + } else { + PauseNode.pause(); + } + } else if (tryLock(obj, wordFieldOffset)) { + return true; + } + } + + return false; + } + + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public static void lockNoTransition(Object obj, long wordFieldOffset) { + while (!tryLock(obj, wordFieldOffset, Integer.MAX_VALUE)) { + // Nothing to do. + } + } + + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) + public static void unlock(Object obj, long wordFieldOffset) { + /* + * Roach-motel semantics. It's safe if subsequent LDs and STs float "up" into the critical + * section, but prior LDs and STs within the critical section can't be allowed to reorder or + * float past the ST that releases the lock. Loads and stores in the critical section - + * which appear in program order before the store that releases the lock - must also appear + * before the store that releases the lock in memory visibility order. Conceptually we need + * a #loadstore|#storestore "release" MEMBAR before the ST of 0 into the lock-word which + * releases the lock. + */ + UNSAFE.putLongVolatile(obj, wordFieldOffset, UNLOCKED); + } +} From 0bf120469b86fe9a0176ed8cb27136927a33d082 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 28 Feb 2023 10:54:24 -0500 Subject: [PATCH 54/72] rework symbol repo. Touch-ups. --- .../svm/core/jdk/UninterruptibleUtils.java | 13 +- .../com/oracle/svm/core/jfr/JfrBuffer.java | 12 +- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 6 +- .../com/oracle/svm/core/jfr/JfrMetadata.java | 2 +- .../svm/core/jfr/JfrSymbolRepository.java | 200 ++++++++---------- .../svm/core/jfr/JfrThreadRepository.java | 10 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 1 - .../test/jfr/TestJfrBufferNodeLinkedList.java | 1 - 8 files changed, 112 insertions(+), 133 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index f9caff2a2821..bc33ae88f59d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -525,8 +525,11 @@ private static int modifiedUTF8Length(char c) { * Write a char in modified UTF8 format into the buffer. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static Pointer writeModifiedUTF8(Pointer buffer, char c) { + private static Pointer writeModifiedUTF8(Pointer buffer, char c, boolean replaceDotWithSlash) { Pointer pos = buffer; + if (replaceDotWithSlash && c == '.') { + c = '/'; + } if (c >= 0x0001 && c <= 0x007F) { pos.writeByte(0, (byte) c); pos = pos.add(1); @@ -568,9 +571,15 @@ public static int modifiedUTF8Length(java.lang.String string, boolean addNullTer */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator) { + + return toModifiedUTF8(string, buffer, bufferEnd, addNullTerminator, false); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator, boolean replaceDotWithSlash) { Pointer pos = buffer; for (int index = 0; index < string.length(); index++) { - pos = writeModifiedUTF8(pos, StringUtil.charAt(string, index)); + pos = writeModifiedUTF8(pos, StringUtil.charAt(string, index), replaceDotWithSlash); } if (addNullTerminator) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index 753650016c60..62b822b9be29 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -37,11 +37,13 @@ /** * A {@link JfrBuffer} is a block of native memory (either thread-local or global) into which JFR - * events are written. The flushedPos is the point up to which data has been flushed. The - * committedPos is the point up to which data has been committed. This means that data between the - * flushedPos and committedPos is unflushed data that is ready to be flushed. This also means that - * flushedPos should never exceed committedPos. New emitted events are written after the commit - * position. The new events are committed by advancing the committedPos. + * events are written. {@link JfrBuffer#getFlushedPos()} returns the point up to which data has been + * flushed. {@link JfrBuffer#getCommittedPos()} returns the point up to which data has been + * committed. This means that data between the these two positions is unflushed data that is ready + * to be flushed. This also means that {@link JfrBuffer#getFlushedPos()} should never exceed + * {@link JfrBuffer#getCommittedPos()}. New emitted events are written after the + * {@link JfrBuffer#getCommittedPos()}. The new events are committed by advancing the committed + * position. */ @RawStructure public interface JfrBuffer extends PointerBase { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index f09fa9979e18..36aed3362f45 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -36,6 +36,7 @@ import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.thread.JavaSpinLockUtils; +import com.oracle.svm.core.thread.VMOperation; /** * {@link JfrBufferNodeLinkedList} is a singly linked list used to store thread local JFR buffers. @@ -43,8 +44,8 @@ * shall iterate this list and is allowed to remove nodes. There is a list-level lock that is * acquired when adding nodes, and when beginning iteration at the head. Threads may access their * own nodes at any time up until they set the alive flag to false - * {@link com.oracle.svm.core.jfr.JfrBufferNodeAccess#setRetired(JfrBufferNode)}. When entering a - * safepoint, the list lock must not be held by one of the blocked Java threads. + * {@link JfrBufferNodeAccess#setRetired(JfrBufferNode)}. When entering a safepoint, the list lock + * must not be held by one of the blocked Java threads. */ public class JfrBufferNodeLinkedList { @@ -69,6 +70,7 @@ public JfrBufferNodeLinkedList() { } public void teardown() { + assert VMOperation.isInProgressAtSafepoint(); JfrBufferNode node = head; while (node.isNonNull()) { JfrBufferNode next = node.getNext(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java index 9af6dc752fb9..1bc19739214b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMetadata.java @@ -27,7 +27,7 @@ public class JfrMetadata { private long currentMetadataId; - private volatile byte[] metadataDescriptor; + private byte[] metadataDescriptor; public JfrMetadata(byte[] bytes) { metadataDescriptor = bytes; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index ecf8289a26c8..368c751cff21 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -24,10 +24,11 @@ */ package com.oracle.svm.core.jfr; -import java.nio.charset.StandardCharsets; - +import com.oracle.svm.core.jdk.UninterruptibleUtils; import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.compiler.word.Word; +import org.graalvm.word.WordFactory; +import org.graalvm.word.Pointer; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; @@ -48,29 +49,25 @@ */ public class JfrSymbolRepository implements JfrConstantPool { private final VMMutex mutex; - private final JfrSymbolHashtable table0; - private final JfrSymbolHashtable table1; + private final JfrSymbolEpochData epochData0; + private final JfrSymbolEpochData epochData1; @Platforms(Platform.HOSTED_ONLY.class) public JfrSymbolRepository() { + this.epochData0 = new JfrSymbolEpochData(); + this.epochData1 = new JfrSymbolEpochData(); mutex = new VMMutex("jfrSymbolRepository"); - table0 = new JfrSymbolHashtable(); - table1 = new JfrSymbolHashtable(); } public void teardown() { - table0.teardown(); - table1.teardown(); + epochData0.teardown(); + epochData1.teardown(); } @Uninterruptible(reason = "Called by uninterruptible code.") - private JfrSymbolHashtable getTable(boolean previousEpoch) { + private JfrSymbolEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); - if (epoch) { - return table0; - } else { - return table1; - } + return epoch ? epochData0 : epochData1; } @Uninterruptible(reason = "Epoch must not change while in this method.") @@ -93,8 +90,6 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r long rawPointerValue = Word.objectToUntrackedPointer(imageHeapString).rawValue(); int hashcode = (int) (rawPointerValue ^ (rawPointerValue >>> 32)); symbol.setHash(hashcode); - symbol.setBytes(null); - symbol.setSerialized(false); mutex.lockNoTransition(); try { @@ -104,14 +99,43 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r * concurrently. For every inserted entry, a unique id is generated that is then used as * the JFR trace id. */ - JfrSymbol entry = getTable(previousEpoch).getOrPut(symbol); - if (entry.isNonNull()) { - return entry.getId(); + JfrSymbolEpochData epochData = getEpochData(previousEpoch); + boolean visited = false; + if (!epochData.table.putIfAbsent(symbol)) { + visited = true; + } + JfrSymbol entry = (JfrSymbol) epochData.table.get(symbol); + if (entry.isNull()) { + return 0; + } + if (!visited) { + + epochData.unflushedSymbolCount++; + + if (epochData.symbolBuffer.isNull()) { + // This will happen only on the first call. + epochData.symbolBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + } + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.symbolBuffer); + + JfrNativeEventWriter.putLong(data, entry.getId()); + JfrNativeEventWriter.putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); + JfrNativeEventWriter.putInt(data, UninterruptibleUtils.String.modifiedUTF8Length(entry.getValue(), false)); + + Pointer newPosition = UninterruptibleUtils.String.toModifiedUTF8(entry.getValue(), + data.getCurrentPos(), data.getEndPos(), false, entry.getReplaceDotWithSlash()); + data.setCurrentPos(newPosition); + + JfrNativeEventWriter.commit(data); + + /* The buffer may have been replaced with a new one. */ + epochData.symbolBuffer = data.getJfrBuffer(); } + return entry.getId(); } finally { mutex.unlock(); } - return 0; } @Uninterruptible(reason = "Locking without transition.") @@ -128,67 +152,25 @@ private void maybeUnlock(boolean flush) { } } - /** - * The purpose of this method is so that String.getBytes() can be used outside uninterruptible - * code. This is not an ideal solution because it results in having to scan the table twice. - * - * This method does not need to be locked during flushes. It can race with other threads adding - * entries because in the worst case, the new entries are missed until the next flush. - * - * This method can be interrupted because there is no locking (no risk of deadlocks), and if it - * is interrupted for an operation that results in new pool entries, in the worst case, those - * entries will be missed until the next flush. - * - * Any missed new entries should not be needed in the chunk because their corresponding event - * data will not be flushed during this flush operation. This is due to the flushing order: - * event data then constant pools. - */ @Override - public int write(JfrChunkWriter writer, boolean flush) { - JfrSymbolHashtable table = getTable(!flush); - int count = 0; - // compute byte arrays - JfrSymbol[] entries = table.getTable(); - for (int i = 0; i < entries.length; i++) { - JfrSymbol entry = entries[i]; - if (entry.isNonNull()) { - while (entry.isNonNull()) { - if (!entry.getSerialized()) { - entry.setBytes(entry.getValue().getBytes(StandardCharsets.UTF_8)); - count++; - } - entry = entry.getNext(); - } - } - } - return doWrite(writer, flush, table, count); - } - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") - private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable table, int count) { + public int write(JfrChunkWriter writer, boolean flush) { maybeLock(flush); try { - if (count == 0) { + JfrSymbolEpochData epochData = getEpochData(!flush); + int numberOfSymbols = epochData.unflushedSymbolCount; + if (numberOfSymbols == 0) { return EMPTY; } writer.writeCompressedLong(JfrType.Symbol.getId()); - writer.writeCompressedLong(count); - - JfrSymbol[] entries = table.getTable(); - for (int i = 0; i < entries.length; i++) { - JfrSymbol entry = entries[i]; - if (entry.isNonNull()) { - while (entry.isNonNull()) { - if (!entry.getSerialized() && entry.getBytes() != null) { - writeSymbol(writer, entry); - } - entry = entry.getNext(); - } - } - } + writer.writeCompressedLong(numberOfSymbols); + writer.write(epochData.symbolBuffer); + JfrBufferAccess.reinitialize(epochData.symbolBuffer); + epochData.unflushedSymbolCount = 0; + if (!flush) { // Should be cleared only after epoch change - table.clear(); + epochData.clear(); } return NON_EMPTY; } finally { @@ -196,29 +178,6 @@ private int doWrite(JfrChunkWriter writer, boolean flush, JfrSymbolHashtable tab } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { - byte[] value = symbol.getBytes(); - writer.writeCompressedLong(symbol.getId()); - writer.writeByte(JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); - - if (symbol.getReplaceDotWithSlash()) { - replaceDotWithSlash(value); - } - writer.writeCompressedInt(value.length); - writer.writeBytes(value); - symbol.setSerialized(true); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void replaceDotWithSlash(byte[] utf8String) { - for (int i = 0; i < utf8String.length; i++) { - if (utf8String[i] == '.') { - utf8String[i] = '/'; - } - } - } - @RawStructure private interface JfrSymbol extends UninterruptibleEntry { @RawField @@ -235,25 +194,11 @@ private interface JfrSymbol extends UninterruptibleEntry { @RawField void setValue(String value); - @PinnedObjectField - @RawField - byte[] getBytes(); - - @PinnedObjectField - @RawField - void setBytes(byte[] bytes); - @RawField boolean getReplaceDotWithSlash(); @RawField void setReplaceDotWithSlash(boolean value); - - @RawField - boolean getSerialized(); - - @RawField - void setSerialized(boolean serialized); } private static class JfrSymbolHashtable extends AbstractUninterruptibleHashtable { @@ -271,12 +216,6 @@ public JfrSymbol[] getTable() { return (JfrSymbol[]) super.getTable(); } - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public JfrSymbol getOrPut(UninterruptibleEntry valueOnStack) { - return (JfrSymbol) super.getOrPut(valueOnStack); - } - @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "image heap pointer comparison") @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -294,4 +233,35 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry symbolOnStack) { return result; } } + + private static class JfrSymbolEpochData { + private JfrBuffer symbolBuffer; + private final JfrSymbolHashtable table; + private int unflushedSymbolCount; + + @Platforms(Platform.HOSTED_ONLY.class) + JfrSymbolEpochData() { + table = new JfrSymbolHashtable(); + this.unflushedSymbolCount = 0; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void teardown() { + if (symbolBuffer.isNonNull()) { + JfrBufferAccess.free(symbolBuffer); + } + symbolBuffer = WordFactory.nullPointer(); + table.teardown(); + } + + @Uninterruptible(reason = "May write current epoch data.") + void clear() { + if (symbolBuffer.isNonNull()) { + JfrBufferAccess.reinitialize(symbolBuffer); + } + table.clear(); + unflushedSymbolCount = 0; + } + + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index e445e36236a3..3002d30860c7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -47,7 +47,7 @@ /** * Repository that collects all metadata about threads and thread groups. */ -public final class JfrThreadRepository implements JfrConstantPool { +public final class JfrThreadRepository { private final VMMutex mutex; private final JfrThreadEpochData epochData0; private final JfrThreadEpochData epochData1; @@ -205,7 +205,6 @@ private void maybeUnlock(boolean flush) { } } - @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { JfrThreadEpochData epochData = getEpochData(!flush); @@ -246,7 +245,6 @@ public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord la writer.endEvent(start); return start; - } finally { maybeUnlock(flush); /* @@ -270,14 +268,14 @@ private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochD writer.write(epochData.threadBuffer); JfrBufferAccess.reinitialize(epochData.threadBuffer); epochData.unflushedThreadCount = 0; - return NON_EMPTY; + return JfrConstantPool.NON_EMPTY; } @Uninterruptible(reason = "May write current epoch data.") private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { int threadGroupCount = epochData.unflushedThreadGroupCount; if (threadGroupCount == 0) { - return EMPTY; + return JfrConstantPool.EMPTY; } writer.writeCompressedLong(JfrType.ThreadGroup.getId()); @@ -285,7 +283,7 @@ private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData e writer.write(epochData.threadGroupBuffer); JfrBufferAccess.reinitialize(epochData.threadGroupBuffer); epochData.unflushedThreadGroupCount = 0; - return NON_EMPTY; + return JfrConstantPool.NON_EMPTY; } @Uninterruptible(reason = "Releasing repository buffers.") 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 852f23e0f1c9..2059ccaaa4f8 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 @@ -239,7 +239,6 @@ public boolean destroyJFR() { threadRepo.teardown(); stackTraceRepo.teardown(); methodRepo.teardown(); - threadLocal.teardown(); initialized = false; return true; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index fb2dee916fd7..2d71c6a845d9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -77,7 +77,6 @@ public void testConcurrentAddition() throws Exception { private static void cleanUpList(JfrBufferNodeLinkedList list) { JfrBufferNode node = removeAllNodes(list); assertTrue("Could not remove all nodes", node.isNull()); - list.teardown(); } private static void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { From 0ce8666473eeb88e51ae357e9c00a71b8234821e Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 28 Feb 2023 11:04:32 -0500 Subject: [PATCH 55/72] use JavaOwnedSpinlockUtils --- .../src/com/oracle/svm/core/jfr/JfrBufferNode.java | 4 ++-- .../com/oracle/svm/core/jfr/JfrBufferNodeAccess.java | 2 +- .../oracle/svm/core/thread/NativeSpinLockUtils.java | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java index 5fc1a400aa0f..0158a9145fd2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java @@ -45,8 +45,8 @@ public interface JfrBufferNode extends PointerBase { JfrBuffer getValue(); /** - * This field is effectively final and should always be non-null. Changing its value after - * the node is added to the {@link JfrBufferNodeLinkedList} can result in races. + * This field is effectively final and should always be non-null. Changing its value after the + * node is added to the {@link JfrBufferNodeLinkedList} can result in races. */ @RawField void setValue(JfrBuffer value); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index a5fd8c2c7b51..5e08b88bc574 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -31,12 +31,12 @@ import org.graalvm.word.Pointer; import com.oracle.svm.core.Uninterruptible; - /** * Used to access the raw memory of a {@link com.oracle.svm.core.jfr.JfrBufferNode}. */ public final class JfrBufferNodeAccess { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private JfrBufferNodeAccess() { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java index 5a0eb7d75bf1..26077e409c03 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java @@ -40,7 +40,7 @@ public static void initialize(CIntPointer spinLock) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void initialize(WordPointer spinLock) { - JavaSpinLockUtils.initialize(null, spinLock.rawValue()); + JavaOwnedSpinLockUtils.initialize(null, spinLock.rawValue()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -50,7 +50,7 @@ public static boolean isLocked(CIntPointer spinLock) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isLocked(WordPointer spinLock) { - return JavaSpinLockUtils.isLocked(null, spinLock.rawValue()); + return JavaOwnedSpinLockUtils.isLocked(null, spinLock.rawValue()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -60,7 +60,7 @@ public static boolean tryLock(CIntPointer spinLock) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean tryLock(WordPointer spinLock) { - return JavaSpinLockUtils.tryLock(null, spinLock.rawValue()); + return JavaOwnedSpinLockUtils.tryLock(null, spinLock.rawValue()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -70,7 +70,7 @@ public static boolean tryLock(CIntPointer spinLock, int retries) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean tryLock(WordPointer spinLock, int retries) { - return JavaSpinLockUtils.tryLock(null, spinLock.rawValue(), retries); + return JavaOwnedSpinLockUtils.tryLock(null, spinLock.rawValue(), retries); } @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) @@ -80,7 +80,7 @@ public static void lockNoTransition(CIntPointer spinLock) { @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) public static void lockNoTransition(WordPointer spinLock) { - JavaSpinLockUtils.lockNoTransition(null, spinLock.rawValue()); + JavaOwnedSpinLockUtils.lockNoTransition(null, spinLock.rawValue()); } @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) @@ -90,6 +90,6 @@ public static void unlock(CIntPointer spinLock) { @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static void unlock(WordPointer spinLock) { - JavaSpinLockUtils.unlock(null, spinLock.rawValue()); + JavaOwnedSpinLockUtils.unlock(null, spinLock.rawValue()); } } From cfbc3f47030aa97097cc72549c42200d065b1d06 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 28 Feb 2023 12:24:55 -0500 Subject: [PATCH 56/72] gate fixes --- .../svm/core/jdk/UninterruptibleUtils.java | 21 ++++++++++--------- .../svm/core/jfr/JfrBufferNodeAccess.java | 9 ++++---- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 1 + .../svm/core/jfr/JfrThreadRepository.java | 7 ++++--- .../svm/core/jfr/JfrTypeRepository.java | 4 ++-- .../oracle/svm/core/thread/ThreadData.java | 1 - 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index bc33ae88f59d..8f7919b8b587 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -527,20 +527,21 @@ private static int modifiedUTF8Length(char c) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static Pointer writeModifiedUTF8(Pointer buffer, char c, boolean replaceDotWithSlash) { Pointer pos = buffer; - if (replaceDotWithSlash && c == '.') { - c = '/'; + char replacedChar = c; + if (replaceDotWithSlash && replacedChar == '.') { + replacedChar = '/'; } - if (c >= 0x0001 && c <= 0x007F) { - pos.writeByte(0, (byte) c); + if (replacedChar >= 0x0001 && replacedChar <= 0x007F) { + pos.writeByte(0, (byte) replacedChar); pos = pos.add(1); - } else if (c <= 0x07FF) { - pos.writeByte(0, (byte) (0xC0 | (c >> 6))); - pos.writeByte(1, (byte) (0x80 | (c & 0x3F))); + } else if (replacedChar <= 0x07FF) { + pos.writeByte(0, (byte) (0xC0 | (replacedChar >> 6))); + pos.writeByte(1, (byte) (0x80 | (replacedChar & 0x3F))); pos = pos.add(2); } else { - pos.writeByte(0, (byte) (0xE0 | (c >> 12))); - pos.writeByte(1, (byte) (0x80 | ((c >> 6) & 0x3F))); - pos.writeByte(2, (byte) (0x80 | (c & 0x3F))); + pos.writeByte(0, (byte) (0xE0 | (replacedChar >> 12))); + pos.writeByte(1, (byte) (0x80 | ((replacedChar >> 6) & 0x3F))); + pos.writeByte(2, (byte) (0x80 | (replacedChar & 0x3F))); pos = pos.add(3); } return pos; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 5e08b88bc574..3431821dd62c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -27,7 +27,6 @@ package com.oracle.svm.core.jfr; import jdk.internal.misc.Unsafe; -import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.word.Pointer; import com.oracle.svm.core.Uninterruptible; @@ -42,16 +41,16 @@ private JfrBufferNodeAccess() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setRetired(JfrBufferNode node) { - UNSAFE.putBooleanVolatile(null, ptrToLock(node).rawValue(), false); + UNSAFE.putBooleanVolatile(null, ptrToAlive(node).rawValue(), false); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setAlive(JfrBufferNode node) { - UNSAFE.putBooleanVolatile(null, ptrToLock(node).rawValue(), true); + UNSAFE.putBooleanVolatile(null, ptrToAlive(node).rawValue(), true); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static CIntPointer ptrToLock(JfrBufferNode node) { - return (CIntPointer) ((Pointer) node).add(JfrBufferNode.offsetOfAlive()); + private static Pointer ptrToAlive(JfrBufferNode node) { + return ((Pointer) node).add(JfrBufferNode.offsetOfAlive()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java index 36aed3362f45..1e5d9cf469f1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java @@ -51,6 +51,7 @@ public class JfrBufferNodeLinkedList { private static final long LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(JfrBufferNodeLinkedList.class, "lock"); + @SuppressWarnings("unused") private volatile int lock; private volatile JfrBufferNode head; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 3002d30860c7..5b29da054c32 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -224,13 +224,14 @@ public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord la return lastCheckpointOffset; } SignedWord start = writer.beginEvent(); - if (lastCheckpointOffset.lessThan(0)) { - lastCheckpointOffset = start; + long delta = 0; + if (lastCheckpointOffset.greaterOrEqual(0)) { + delta = lastCheckpointOffset.subtract(start).rawValue(); } writer.writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); writer.writeCompressedLong(JfrTicks.elapsedTicks()); writer.writeCompressedLong(0); // duration - writer.writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); + writer.writeCompressedLong(delta); writer.writeByte(JfrCheckpointType.Threads.getId()); // If only writing threads pool, count is 1 diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index ae000143a115..20fa1df339b9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -205,12 +205,12 @@ private int writeClassLoaders(TypeInfo typeInfo, JfrChunkWriter writer, boolean writer.writeCompressedInt(typeInfo.classLoaders.size()); for (Map.Entry clInfo : typeInfo.classLoaders.entrySet()) { - writeClassLoader(typeInfo, writer, clInfo.getKey(), clInfo.getValue(), flush); + writeClassLoader(writer, clInfo.getKey(), clInfo.getValue(), flush); } return NON_EMPTY; } - private void writeClassLoader(TypeInfo typeInfo, JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { + private void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); if (cl == null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java index f1922dc18f25..855c656b9e6a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java @@ -53,7 +53,6 @@ public final class ThreadData extends UnacquiredThreadData { throw VMError.shouldNotReachHere(ex); } } - private volatile int lock; private boolean detached; private long refCount; From fa64197c915ee2baa24347a298121aab8b892e7d Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 2 Mar 2023 11:46:43 +0100 Subject: [PATCH 57/72] Small refactorings and fixed a buffer overflow. --- .../jdk/AbstractUninterruptibleHashtable.java | 7 + .../core/jdk/UninterruptibleHashtable.java | 8 + .../svm/core/jdk/UninterruptibleUtils.java | 46 ++++-- .../svm/core/jfr/JfrNativeEventWriter.java | 10 +- .../svm/core/jfr/JfrSymbolRepository.java | 149 ++++++++++-------- 5 files changed, 134 insertions(+), 86 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java index e33a6336daf0..5cb458dbd8fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java @@ -147,6 +147,13 @@ public boolean putIfAbsent(UninterruptibleEntry valueOnStack) { } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public UninterruptibleEntry putNew(UninterruptibleEntry valueOnStack) { + assert valueOnStack.isNonNull(); + assert get(valueOnStack).isNull(); + return insertEntry(valueOnStack); + } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void clear() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java index c20cc8ec0b22..f6b543ffbb22 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java @@ -59,6 +59,14 @@ public interface UninterruptibleHashtable { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) boolean putIfAbsent(UninterruptibleEntry valueOnStack); + /** + * Inserts {@code valueOnStack} into the hashtable. May only be called if it is guaranteed that + * there is no matching entry in the table. Returns the inserted entry. If an error occurred + * while inserting the entry, a null pointer is returned instead. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + UninterruptibleEntry putNew(UninterruptibleEntry valueOnStack); + /** * If the hashtable contains an existing entry that matches {@code valueOnStack}, then this * existing entry will be returned and no value will be inserted. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index 8f7919b8b587..96d17767bf5c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -525,23 +525,19 @@ private static int modifiedUTF8Length(char c) { * Write a char in modified UTF8 format into the buffer. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static Pointer writeModifiedUTF8(Pointer buffer, char c, boolean replaceDotWithSlash) { + private static Pointer writeModifiedUTF8(Pointer buffer, char c) { Pointer pos = buffer; - char replacedChar = c; - if (replaceDotWithSlash && replacedChar == '.') { - replacedChar = '/'; - } - if (replacedChar >= 0x0001 && replacedChar <= 0x007F) { - pos.writeByte(0, (byte) replacedChar); + if (c >= 0x0001 && c <= 0x007F) { + pos.writeByte(0, (byte) c); pos = pos.add(1); - } else if (replacedChar <= 0x07FF) { - pos.writeByte(0, (byte) (0xC0 | (replacedChar >> 6))); - pos.writeByte(1, (byte) (0x80 | (replacedChar & 0x3F))); + } else if (c <= 0x07FF) { + pos.writeByte(0, (byte) (0xC0 | (c >> 6))); + pos.writeByte(1, (byte) (0x80 | (c & 0x3F))); pos = pos.add(2); } else { - pos.writeByte(0, (byte) (0xE0 | (replacedChar >> 12))); - pos.writeByte(1, (byte) (0x80 | ((replacedChar >> 6) & 0x3F))); - pos.writeByte(2, (byte) (0x80 | (replacedChar & 0x3F))); + pos.writeByte(0, (byte) (0xE0 | (c >> 12))); + pos.writeByte(1, (byte) (0x80 | ((c >> 6) & 0x3F))); + pos.writeByte(2, (byte) (0x80 | (c & 0x3F))); pos = pos.add(3); } return pos; @@ -554,9 +550,17 @@ private static Pointer writeModifiedUTF8(Pointer buffer, char c, boolean replace */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int modifiedUTF8Length(java.lang.String string, boolean addNullTerminator) { + return modifiedUTF8Length(string, addNullTerminator, null); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int modifiedUTF8Length(java.lang.String string, boolean addNullTerminator, CharReplacer replacer) { int result = 0; for (int index = 0; index < string.length(); index++) { char ch = StringUtil.charAt(string, index); + if (replacer != null) { + ch = replacer.replace(ch); + } result += modifiedUTF8Length(ch); } @@ -572,15 +576,18 @@ public static int modifiedUTF8Length(java.lang.String string, boolean addNullTer */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator) { - - return toModifiedUTF8(string, buffer, bufferEnd, addNullTerminator, false); + return toModifiedUTF8(string, buffer, bufferEnd, addNullTerminator, null); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator, boolean replaceDotWithSlash) { + public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator, CharReplacer replacer) { Pointer pos = buffer; for (int index = 0; index < string.length(); index++) { - pos = writeModifiedUTF8(pos, StringUtil.charAt(string, index), replaceDotWithSlash); + char ch = StringUtil.charAt(string, index); + if (replacer != null) { + ch = replacer.replace(ch); + } + pos = writeModifiedUTF8(pos, ch); } if (addNullTerminator) { @@ -591,4 +598,9 @@ public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Po return pos; } } + + @FunctionalInterface + public interface CharReplacer { + char replace(char val); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index b2878005f5fd..bbe659768908 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -31,6 +31,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer; import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.util.VMError; @@ -191,16 +192,21 @@ public static void putLong(JfrNativeEventWriterData data, long v) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) public static void putString(JfrNativeEventWriterData data, String string) { + putString(data, string, null); + } + + @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) + public static void putString(JfrNativeEventWriterData data, String string, CharReplacer replacer) { if (string == null) { putByte(data, JfrChunkWriter.StringEncoding.NULL.byteValue); } else if (string.isEmpty()) { putByte(data, JfrChunkWriter.StringEncoding.EMPTY_STRING.byteValue); } else { - int mUTF8Length = UninterruptibleUtils.String.modifiedUTF8Length(string, false); + int mUTF8Length = UninterruptibleUtils.String.modifiedUTF8Length(string, false, replacer); putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); putInt(data, mUTF8Length); if (ensureSize(data, mUTF8Length)) { - Pointer newPosition = UninterruptibleUtils.String.toModifiedUTF8(string, data.getCurrentPos(), data.getEndPos(), false); + Pointer newPosition = UninterruptibleUtils.String.toModifiedUTF8(string, data.getCurrentPos(), data.getEndPos(), false, replacer); data.setCurrentPos(newPosition); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 368c751cff21..0f27b90b31fa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -24,23 +24,22 @@ */ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.jdk.UninterruptibleUtils; import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.compiler.word.Word; -import org.graalvm.word.WordFactory; -import org.graalvm.word.Pointer; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.locks.VMMutex; @@ -49,14 +48,16 @@ */ public class JfrSymbolRepository implements JfrConstantPool { private final VMMutex mutex; + private final CharReplacer dotWithSlash; private final JfrSymbolEpochData epochData0; private final JfrSymbolEpochData epochData1; @Platforms(Platform.HOSTED_ONLY.class) public JfrSymbolRepository() { + this.mutex = new VMMutex("jfrSymbolRepository"); + this.dotWithSlash = new ReplaceDotWithSlash(); this.epochData0 = new JfrSymbolEpochData(); this.epochData1 = new JfrSymbolEpochData(); - mutex = new VMMutex("jfrSymbolRepository"); } public void teardown() { @@ -91,67 +92,49 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r int hashcode = (int) (rawPointerValue ^ (rawPointerValue >>> 32)); symbol.setHash(hashcode); + /* + * Get an existing entry from the hashtable or insert a new entry. This needs to be atomic + * to avoid races as this method can be executed by multiple threads concurrently. For every + * inserted entry, a unique id is generated that is then used as the JFR trace id. + */ mutex.lockNoTransition(); try { - /* - * Get an existing entry from the hashtable or insert a new entry. This needs to be - * atomic to avoid races as this method can be executed by multiple threads - * concurrently. For every inserted entry, a unique id is generated that is then used as - * the JFR trace id. - */ JfrSymbolEpochData epochData = getEpochData(previousEpoch); - boolean visited = false; - if (!epochData.table.putIfAbsent(symbol)) { - visited = true; + JfrSymbol existingEntry = epochData.table.get(symbol); + if (existingEntry.isNonNull()) { + return existingEntry.getId(); } - JfrSymbol entry = (JfrSymbol) epochData.table.get(symbol); - if (entry.isNull()) { - return 0; + + JfrSymbol newEntry = epochData.table.putNew(symbol); + if (newEntry.isNull()) { + return 0L; } - if (!visited) { - epochData.unflushedSymbolCount++; + /* We have a new symbol, so serialize it to the buffer. */ + epochData.unflushedSymbolCount++; - if (epochData.symbolBuffer.isNull()) { - // This will happen only on the first call. - epochData.symbolBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); - } - JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initialize(data, epochData.symbolBuffer); + if (epochData.symbolBuffer.isNull()) { + epochData.symbolBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + } - JfrNativeEventWriter.putLong(data, entry.getId()); - JfrNativeEventWriter.putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); - JfrNativeEventWriter.putInt(data, UninterruptibleUtils.String.modifiedUTF8Length(entry.getValue(), false)); + CharReplacer charReplacer = replaceDotWithSlash ? dotWithSlash : null; + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.symbolBuffer); - Pointer newPosition = UninterruptibleUtils.String.toModifiedUTF8(entry.getValue(), - data.getCurrentPos(), data.getEndPos(), false, entry.getReplaceDotWithSlash()); - data.setCurrentPos(newPosition); + JfrNativeEventWriter.putLong(data, newEntry.getId()); + JfrNativeEventWriter.putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); + JfrNativeEventWriter.putString(data, imageHeapString, charReplacer); + JfrNativeEventWriter.commit(data); - JfrNativeEventWriter.commit(data); + /* The buffer may have been replaced with a new one. */ + epochData.symbolBuffer = data.getJfrBuffer(); - /* The buffer may have been replaced with a new one. */ - epochData.symbolBuffer = data.getJfrBuffer(); - } - return entry.getId(); + return newEntry.getId(); } finally { mutex.unlock(); } } - @Uninterruptible(reason = "Locking without transition.") - private void maybeLock(boolean flush) { - if (flush) { - mutex.lockNoTransition(); - } - } - - @Uninterruptible(reason = "Locking without transition.") - private void maybeUnlock(boolean flush) { - if (flush) { - mutex.unlock(); - } - } - @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { @@ -165,19 +148,28 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedLong(JfrType.Symbol.getId()); writer.writeCompressedLong(numberOfSymbols); writer.write(epochData.symbolBuffer); - JfrBufferAccess.reinitialize(epochData.symbolBuffer); - epochData.unflushedSymbolCount = 0; - if (!flush) { - // Should be cleared only after epoch change - epochData.clear(); - } + epochData.clear(flush); return NON_EMPTY; } finally { maybeUnlock(flush); } } + @Uninterruptible(reason = "Locking without transition.") + private void maybeLock(boolean flush) { + if (flush) { + mutex.lockNoTransition(); + } + } + + @Uninterruptible(reason = "Locking without transition.") + private void maybeUnlock(boolean flush) { + if (flush) { + mutex.unlock(); + } + } + @RawStructure private interface JfrSymbol extends UninterruptibleEntry { @RawField @@ -216,6 +208,18 @@ public JfrSymbol[] getTable() { return (JfrSymbol[]) super.getTable(); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public JfrSymbol get(UninterruptibleEntry valueOnStack) { + return (JfrSymbol) super.get(valueOnStack); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public JfrSymbol putNew(UninterruptibleEntry valueOnStack) { + return (JfrSymbol) super.putNew(valueOnStack); + } + @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "image heap pointer comparison") @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -235,33 +239,44 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry symbolOnStack) { } private static class JfrSymbolEpochData { - private JfrBuffer symbolBuffer; private final JfrSymbolHashtable table; + private JfrBuffer symbolBuffer; private int unflushedSymbolCount; @Platforms(Platform.HOSTED_ONLY.class) JfrSymbolEpochData() { - table = new JfrSymbolHashtable(); + this.table = new JfrSymbolHashtable(); this.unflushedSymbolCount = 0; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - void teardown() { + @Uninterruptible(reason = "May write current epoch data.") + void clear(boolean flush) { if (symbolBuffer.isNonNull()) { - JfrBufferAccess.free(symbolBuffer); + JfrBufferAccess.reinitialize(symbolBuffer); + } + if (!flush) { + /* The IDs must be stable for the whole epoch, so only clear after epoch change. */ + table.clear(); } + unflushedSymbolCount = 0; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void teardown() { + JfrBufferAccess.free(symbolBuffer); symbolBuffer = WordFactory.nullPointer(); table.teardown(); } + } - @Uninterruptible(reason = "May write current epoch data.") - void clear() { - if (symbolBuffer.isNonNull()) { - JfrBufferAccess.reinitialize(symbolBuffer); + private static class ReplaceDotWithSlash implements CharReplacer { + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public char replace(char ch) { + if (ch == '.') { + return '/'; } - table.clear(); - unflushedSymbolCount = 0; + return ch; } - } } From 39d99070ee91ff851506e186f74a5c60e875bada Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 2 Mar 2023 20:44:02 +0100 Subject: [PATCH 58/72] Added more documentation and changed the locking concept. Fixed memory management issues. Fixed races. --- .../com/oracle/svm/core/jfr/JfrBuffer.java | 49 +++-- .../oracle/svm/core/jfr/JfrBufferAccess.java | 78 ++------ .../oracle/svm/core/jfr/JfrBufferList.java | 161 ++++++++++++++++ .../oracle/svm/core/jfr/JfrBufferNode.java | 29 ++- .../svm/core/jfr/JfrBufferNodeAccess.java | 61 +++++- .../svm/core/jfr/JfrBufferNodeLinkedList.java | 155 ---------------- .../com/oracle/svm/core/jfr/JfrBuffers.java | 41 ---- .../oracle/svm/core/jfr/JfrChunkWriter.java | 108 +++++------ .../oracle/svm/core/jfr/JfrGlobalMemory.java | 101 +++++----- .../core/jfr/JfrNativeEventWriterData.java | 1 + .../jfr/JfrNativeEventWriterDataAccess.java | 6 +- .../svm/core/jfr/JfrRecorderThread.java | 42 ++--- .../oracle/svm/core/jfr/JfrThreadLocal.java | 175 +++++++++--------- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 46 ++--- .../svm/core/sampler/SamplerBufferPool.java | 4 +- .../core/thread/JavaOwnedSpinLockUtils.java | 113 ----------- .../oracle/svm/core/thread/ThreadData.java | 4 +- .../test/jfr/TestJfrBufferNodeLinkedList.java | 43 ++--- .../svm/test/jfr/utils/JfrFileParser.java | 9 +- 19 files changed, 531 insertions(+), 695 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffers.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index 62b822b9be29..303832bacf9f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -33,17 +33,25 @@ import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.util.VMError; -import org.graalvm.nativeimage.IsolateThread; /** * A {@link JfrBuffer} is a block of native memory (either thread-local or global) into which JFR - * events are written. {@link JfrBuffer#getFlushedPos()} returns the point up to which data has been - * flushed. {@link JfrBuffer#getCommittedPos()} returns the point up to which data has been - * committed. This means that data between the these two positions is unflushed data that is ready - * to be flushed. This also means that {@link JfrBuffer#getFlushedPos()} should never exceed - * {@link JfrBuffer#getCommittedPos()}. New emitted events are written after the - * {@link JfrBuffer#getCommittedPos()}. The new events are committed by advancing the committed - * position. + * events are written. It has the following layout: + * + *
+ * Buffer: ---------------------------------------------------------------------
+ *         | header | flushed data | committed data | unflushed data | unused  |
+ *         ---------------------------------------------------------------------
+ *                  |              |                |                          |
+ *              data start    flushed pos     committed pos                 data end
+ * 
+ * + *
    + *
  • Flushed data has already been flushed to the {@link JfrGlobalMemory global memory} or to the + * disk.
  • + *
  • Committed data refers to valid and fully written event data that could be flushed at any + * time.
  • + *
  • Unflushed data refers to the data of a JFR event that is currently being written.
  • */ @RawStructure public interface JfrBuffer extends PointerBase { @@ -61,7 +69,7 @@ public interface JfrBuffer extends PointerBase { void setSize(UnsignedWord value); /** - * Returns the committed position. Any data before this position is valid event data. + * Any data before this position was committed and is therefore valid event data. */ @RawField Pointer getCommittedPos(); @@ -78,14 +86,13 @@ static int offsetOfCommittedPos() { } /** - * Returns the position of unflushed data. Any data before this position was already flushed to - * some other buffer or to the disk. + * Any data before this position was already flushed to some other buffer or to the disk. */ @RawField Pointer getFlushedPos(); /** - * Sets the position of unflushed data. + * Sets the flushed position. */ @RawField void setFlushedPos(Pointer value); @@ -102,16 +109,18 @@ static int offsetOfCommittedPos() { */ @RawField @PinnedObjectField - void setBufferType(JfrBufferType bufferType); + void setBufferType(JfrBufferType value); + /** + * Returns the {@link JfrBufferNode} that references this {@link JfrBuffer}. This value may be + * null for {@link JfrBufferType#C_HEAP} buffers. + */ @RawField - void setLockOwner(IsolateThread thread); + JfrBufferNode getNode(); + /** + * Sets the {@link JfrBufferNode}. + */ @RawField - IsolateThread getLockOwner(); - - @RawFieldOffset - static int offsetOfLockOwner() { - throw VMError.unimplemented(); // replaced - } + void setNode(JfrBufferNode value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 4e0217b9ab78..2625a35aa16c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -27,7 +27,6 @@ import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.struct.SizeOf; -import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -35,9 +34,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.thread.NativeSpinLockUtils; import com.oracle.svm.core.util.UnsignedUtils; -import org.graalvm.nativeimage.CurrentIsolate; /** * Used to access the raw memory of a {@link JfrBuffer}. @@ -53,8 +50,8 @@ public static UnsignedWord getHeaderSize() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static JfrBuffer allocate(JfrBufferType bufferType) { - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); - return allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize()), bufferType); + long dataSize = SubstrateJVM.getThreadLocal().getThreadLocalBufferSize(); + return allocate(WordFactory.unsigned(dataSize), bufferType); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -64,14 +61,8 @@ public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType if (result.isNonNull()) { result.setSize(dataSize); result.setBufferType(bufferType); - NativeSpinLockUtils.initialize(ptrToLock(result)); - boolean locked = tryLock(result); - try { - assert locked; - reinitialize(result); - } finally { - unlock(result); - } + result.setNode(WordFactory.nullPointer()); + reinitialize(result); } return result; } @@ -90,61 +81,26 @@ public static void reinitialize(JfrBuffer buffer) { } /** - * This is a helper method that checks that the thread modifying the flushed pos actually owns - * the buffer lock. This is important because there can races between flushing threads and the - * thread that owns/created JFR local buffers. + * Sets the flushed position. Also verifies that the thread that modifies the flushed position + * owns the lock if the buffer is published in a {@link JfrBufferList}. This is important to + * avoid races between the thread that owns/created JFR local buffers and threads that iterate + * {@link JfrBufferList}s (e.g., threads that flush for event streaming). */ @Uninterruptible(reason = "Changes flushed position.") public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { - assert isLockedByCurrentThread(buffer) || !isThreadLocal(buffer); + assert buffer.getNode().isNull() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); buffer.setFlushedPos(pos); } /** - * This is a helper method that checks that the thread modifying the flushed pos actually owns - * the buffer lock. This is important because there can races between flushing threads and the - * thread that owns/created JFR local buffers. + * Gets the flushed position. Does the same verification as {@link #setFlushedPos}. */ @Uninterruptible(reason = "Accesses flushed position. Possible race between flushing and working threads.") public static Pointer getFlushedPos(JfrBuffer buffer) { - assert isLockedByCurrentThread(buffer) || !isThreadLocal(buffer); + assert buffer.getNode().isNull() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); return buffer.getFlushedPos(); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isLocked(JfrBuffer buffer) { - assert buffer.isNonNull(); - return NativeSpinLockUtils.isLocked(ptrToLock(buffer)); - } - - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static boolean tryLock(JfrBuffer buffer) { - assert buffer.isNonNull(); - boolean result = NativeSpinLockUtils.tryLock(ptrToLock(buffer)); - if (result) { - buffer.setLockOwner(CurrentIsolate.getCurrentThread()); - } - return result; - } - - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static boolean tryLock(JfrBuffer buffer, int retries) { - assert buffer.isNonNull(); - boolean result = NativeSpinLockUtils.tryLock(ptrToLock(buffer), retries); - if (result) { - buffer.setLockOwner(CurrentIsolate.getCurrentThread()); - } - return result; - } - - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static void unlock(JfrBuffer buffer) { - assert buffer.isNonNull(); - assert isLockedByCurrentThread(buffer); - - NativeSpinLockUtils.unlock(ptrToLock(buffer)); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer getAddressOfCommittedPos(JfrBuffer buffer) { assert buffer.isNonNull(); @@ -210,17 +166,7 @@ public static boolean verify(JfrBuffer buffer) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static WordPointer ptrToLock(JfrBuffer buffer) { - return (WordPointer) ((Pointer) buffer).add(JfrBuffer.offsetOfLockOwner()); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static boolean isThreadLocal(JfrBuffer buffer) { + public static boolean isThreadLocal(JfrBuffer buffer) { return buffer.getBufferType() == JfrBufferType.THREAD_LOCAL_JAVA || buffer.getBufferType() == JfrBufferType.THREAD_LOCAL_NATIVE; } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isLockedByCurrentThread(JfrBuffer buffer) { - return isLocked(buffer) && buffer.getLockOwner() == CurrentIsolate.getCurrentThread(); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java new file mode 100644 index 000000000000..c62c0faa4efa --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -0,0 +1,161 @@ +/* + * 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.core.jfr; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.thread.JavaSpinLockUtils; +import com.oracle.svm.core.thread.VMOperation; + +import jdk.internal.misc.Unsafe; + +/** + * Singly linked list that stores JFR buffers. Multiple instances of this data structure are used to + * keep track of the global and the various thread-local buffers. When entering a safepoint, it is + * guaranteed that none of the blocked Java threads holds the list's lock. + * + * The following invariants are crucial if the list is used for thread-local buffers: + *
      + *
    • Each thread shall only add one node to the list.
    • + *
    • Only the thread performing a flush or epoch change shall iterate this list.
    • + *
    • Only the thread performing a flush or epoch change is allowed to remove nodes.
    • + *
    + */ +public class JfrBufferList { + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long LOCK_OFFSET = U.objectFieldOffset(JfrBufferList.class, "lock"); + + @SuppressWarnings("unused") private volatile int lock; + private JfrBufferNode head; + + @Platforms(Platform.HOSTED_ONLY.class) + public JfrBufferList() { + } + + /** + * This method is called after all the threads already stopped recording. So, the + * {@link JfrBuffer}s were already flushed and freed, but there may still be nodes in the list. + * This node data needs to be freed. + */ + public void teardown() { + // TEMP (chaeubl): fix the case that flushing is disabled - then, the individual threads + // should free their data immediately, otherwise, we would leak memory... can this option be + // changed at runtime? then this tricky... + assert VMOperation.isInProgressAtSafepoint(); + + lock(); + try { + JfrBufferNode node = head; + while (node.isNonNull()) { + assert node.getBuffer().isNull(); + + JfrBufferNode next = node.getNext(); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); + node = next; + } + } finally { + unlock(); + } + } + + @Uninterruptible(reason = "Locking with no transition.") + public JfrBufferNode getHead() { + lock(); + try { + return head; + } finally { + unlock(); + } + } + + /** + * Must be uninterruptible because if this list is acquired and we safepoint for an epoch change + * in this method, the thread doing the epoch change will be blocked accessing the list. + */ + @Uninterruptible(reason = "Locking with no transition. List must not be acquired entering epoch change.") + public JfrBufferNode addNode(JfrBuffer buffer) { + assert buffer.isNonNull(); + assert buffer.getBufferType() != null && buffer.getBufferType() != JfrBufferType.C_HEAP; + + JfrBufferNode node = JfrBufferNodeAccess.allocate(buffer); + if (node.isNull()) { + return WordFactory.nullPointer(); + } + + assert buffer.getNode().isNull(); + buffer.setNode(node); + + lock(); + try { + node.setNext(head); + head = node; + return node; + } finally { + unlock(); + } + } + + /** + * Removes a node from the list. The buffer contained in the nodes must have already been freed + * by the caller. + */ + @Uninterruptible(reason = "Should not be interrupted while flushing.") + public void removeNode(JfrBufferNode node, JfrBufferNode prev) { + assert head.isNonNull(); + assert node.getBuffer().isNull(); + + lock(); + try { + JfrBufferNode next = node.getNext(); + if (node == head) { + assert prev.isNull(); + head = next; + } else { + assert prev.isNonNull(); + assert prev.getNext() == node; + prev.setNext(next); + } + } finally { + unlock(); + } + } + + @Uninterruptible(reason = "Whole critical section must be uninterruptible because we are locking without transition.", callerMustBe = true) + private void lock() { + JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); + } + + @Uninterruptible(reason = "Whole critical section must be uninterruptible because we are locking without transition.", callerMustBe = true) + private void unlock() { + JavaSpinLockUtils.unlock(this, LOCK_OFFSET); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java index 0158a9145fd2..b6e48403ce17 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java @@ -26,13 +26,20 @@ package com.oracle.svm.core.jfr; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.RawFieldOffset; +import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.PointerBase; + import com.oracle.svm.core.util.VMError; -import org.graalvm.nativeimage.IsolateThread; +/** + * {@link JfrBufferNode}s are added to {@link JfrBufferList}s and have a longer lifetime than the + * {@link JfrBuffer} that they reference. With this concept and the providing locking mechanism, + * threads can iterate over the thread-local JFR buffers of other threads. This enables use cases, + * such as JFR event streaming. + */ @RawStructure public interface JfrBufferNode extends PointerBase { @RawField @@ -42,26 +49,16 @@ public interface JfrBufferNode extends PointerBase { void setNext(JfrBufferNode value); @RawField - JfrBuffer getValue(); - - /** - * This field is effectively final and should always be non-null. Changing its value after the - * node is added to the {@link JfrBufferNodeLinkedList} can result in races. - */ - @RawField - void setValue(JfrBuffer value); - - @RawField - IsolateThread getThread(); + JfrBuffer getBuffer(); @RawField - void setThread(IsolateThread thread); + void setBuffer(JfrBuffer value); @RawField - boolean getAlive(); + IsolateThread getLockOwner(); @RawFieldOffset - static int offsetOfAlive() { + static int offsetOfLockOwner() { throw VMError.unimplemented(); // replaced } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 3431821dd62c..0f414e0b1a2a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -26,31 +26,74 @@ package com.oracle.svm.core.jfr; -import jdk.internal.misc.Unsafe; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.thread.NativeSpinLockUtils; /** * Used to access the raw memory of a {@link com.oracle.svm.core.jfr.JfrBufferNode}. */ public final class JfrBufferNodeAccess { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - private JfrBufferNodeAccess() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setRetired(JfrBufferNode node) { - UNSAFE.putBooleanVolatile(null, ptrToAlive(node).rawValue(), false); + public static JfrBufferNode allocate(JfrBuffer buffer) { + JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(JfrBufferNode.class)); + if (node.isNonNull()) { + node.setBuffer(buffer); + node.setNext(WordFactory.nullPointer()); + NativeSpinLockUtils.initialize(ptrToLock(node)); + } + return node; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void free(JfrBufferNode node) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); + } + + @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + public static boolean tryLock(JfrBufferNode node) { + assert node.isNonNull(); + return NativeSpinLockUtils.tryLock(ptrToLock(node)); + } + + @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + public static boolean tryLock(JfrBufferNode node, int retries) { + assert node.isNonNull(); + return NativeSpinLockUtils.tryLock(ptrToLock(node), retries); + } + + @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + public static void lock(JfrBufferNode node) { + assert node.isNonNull(); + NativeSpinLockUtils.lockNoTransition(ptrToLock(node)); + } + + @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + public static void unlock(JfrBufferNode node) { + assert node.isNonNull(); + assert isLockedByCurrentThread(node); + NativeSpinLockUtils.unlock(ptrToLock(node)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setAlive(JfrBufferNode node) { - UNSAFE.putBooleanVolatile(null, ptrToAlive(node).rawValue(), true); + public static boolean isLockedByCurrentThread(JfrBufferNode node) { + assert node.isNonNull(); + assert CurrentIsolate.getCurrentThread().isNonNull(); + return node.getLockOwner() == CurrentIsolate.getCurrentThread(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static Pointer ptrToAlive(JfrBufferNode node) { - return ((Pointer) node).add(JfrBufferNode.offsetOfAlive()); + private static WordPointer ptrToLock(JfrBufferNode node) { + return (WordPointer) ((Pointer) node).add(JfrBufferNode.offsetOfLockOwner()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java deleted file mode 100644 index 1e5d9cf469f1..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeLinkedList.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.core.jfr; - -import com.oracle.svm.core.Uninterruptible; -import jdk.internal.misc.Unsafe; -import org.graalvm.word.WordFactory; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; - -import org.graalvm.nativeimage.c.struct.SizeOf; -import org.graalvm.nativeimage.IsolateThread; - -import com.oracle.svm.core.thread.JavaSpinLockUtils; -import com.oracle.svm.core.thread.VMOperation; - -/** - * {@link JfrBufferNodeLinkedList} is a singly linked list used to store thread local JFR buffers. - * Threads shall only add one node to the list. Only the thread performing a flush or epoch change - * shall iterate this list and is allowed to remove nodes. There is a list-level lock that is - * acquired when adding nodes, and when beginning iteration at the head. Threads may access their - * own nodes at any time up until they set the alive flag to false - * {@link JfrBufferNodeAccess#setRetired(JfrBufferNode)}. When entering a safepoint, the list lock - * must not be held by one of the blocked Java threads. - */ -public class JfrBufferNodeLinkedList { - - private static final long LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(JfrBufferNodeLinkedList.class, "lock"); - - @SuppressWarnings("unused") - private volatile int lock; - private volatile JfrBufferNode head; - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static JfrBufferNode createNode(JfrBuffer buffer, IsolateThread thread) { - JfrBufferNode node = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(JfrBufferNode.class)); - if (node.isNonNull()) { - JfrBufferNodeAccess.setAlive(node); - node.setValue(buffer); - node.setThread(thread); - node.setNext(WordFactory.nullPointer()); - } - return node; - } - - public JfrBufferNodeLinkedList() { - } - - public void teardown() { - assert VMOperation.isInProgressAtSafepoint(); - JfrBufferNode node = head; - while (node.isNonNull()) { - JfrBufferNode next = node.getNext(); - JfrBufferAccess.free(node.getValue()); - /* - * Once JfrBufferNode. JfrBufferNodeAccess.setRetired(node) is called, another thread - * may free the node at any time. In this case it shouldn't matter because the recording - * has ended and this is called at a safepoint. - */ - JfrBufferNodeAccess.setRetired(node); - removeNode(node, WordFactory.nullPointer()); - node = next; - } - } - - @Uninterruptible(reason = "Locking with no transition.") - public JfrBufferNode getHead() { - acquireList(); - try { - return head; - } finally { - releaseList(); - } - } - - /** - * Removes a node from the linked list. The buffer contained in the nodes must have already been - * freed by the caller. - */ - @Uninterruptible(reason = "Should not be interrupted while flushing.") - public void removeNode(JfrBufferNode node, JfrBufferNode prev) { - assert head.isNonNull(); - assert !node.getAlive(); - - JfrBufferNode next = node.getNext(); // next can never be null - - if (node == head) { - assert prev.isNull(); - head = next; // head could now be null if there was only one node in the list - } else { - assert prev.isNonNull(); - assert prev.getNext() == node; - prev.setNext(next); - } - - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); - } - - /** - * Must be uninterruptible because if this list is acquired and we safepoint for an epoch change - * in this method, the thread doing the epoch change will be blocked accessing the list. - */ - @Uninterruptible(reason = "Locking with no transition. List must not be acquired entering epoch change.") - public JfrBufferNode addNode(JfrBuffer buffer, IsolateThread thread) { - assert buffer.isNonNull(); - JfrBufferNode newNode = createNode(buffer, thread); - if (newNode.isNull()) { - return WordFactory.nullPointer(); - } - acquireList(); - try { - // Old head could be null - JfrBufferNode oldHead = head; - newNode.setNext(oldHead); - head = newNode; - return newNode; - } finally { - releaseList(); - } - } - - @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) - private void acquireList() { - JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); - } - - @Uninterruptible(reason = "Locking with no transition and list must not be acquired entering epoch change.", callerMustBe = true) - private void releaseList() { - JavaSpinLockUtils.unlock(this, LOCK_OFFSET); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffers.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffers.java deleted file mode 100644 index da960e5dbe10..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffers.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. 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.core.jfr; - -import org.graalvm.nativeimage.c.struct.RawPointerTo; -import org.graalvm.word.PointerBase; - -/** - * Pointer to an array of {@link JfrBuffer}s. - */ -@RawPointerTo(JfrBuffer.class) -public interface JfrBuffers extends PointerBase { - - JfrBuffers addressOf(long index); - - void write(JfrBuffer value); - - JfrBuffer read(); -} 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 f25d5ab0d46f..10d5ea73bbe1 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 @@ -24,32 +24,32 @@ */ package com.oracle.svm.core.jfr; +import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; +import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; + import java.nio.charset.StandardCharsets; import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; -import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.sampler.JfrRecurringCallbackExecutionSampler; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; +import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.os.RawFileOperationSupport; import com.oracle.svm.core.sampler.SamplerBuffersAccess; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.ThreadingSupportImpl; import com.oracle.svm.core.thread.VMOperation; -import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.thread.VMOperationControl; -import com.oracle.svm.core.locks.VMMutex; - -import static com.oracle.svm.core.jfr.JfrThreadLocal.getJavaBufferList; -import static com.oracle.svm.core.jfr.JfrThreadLocal.getNativeBufferList; +import com.oracle.svm.core.thread.VMThreads; /** * This class is used when writing the in-memory JFR data to a file. For all operations, except @@ -128,7 +128,7 @@ public void maybeOpenFile() { } } - public boolean openFile(String outputFile) { + public void openFile(String outputFile) { assert lock.isOwner(); isFinal = false; generation = 1; @@ -141,23 +141,22 @@ public boolean openFile(String outputFile) { fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); writeFileHeader(); lastCheckpointOffset = WordFactory.signed(-1); // must reset this on new chunk - return true; } @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public boolean write(JfrBuffer buffer) { - assert JfrBufferAccess.isLockedByCurrentThread(buffer) || VMOperation.isInProgressAtSafepoint() || buffer.getBufferType() == JfrBufferType.C_HEAP; + assert buffer.isNonNull(); + assert buffer.getBufferType() == JfrBufferType.C_HEAP || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { return false; } boolean success = getFileSupport().write(fd, JfrBufferAccess.getFlushedPos(buffer), unflushedSize); - JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); if (!success) { - // We lost some data because the write failed. + /* We lost some data because the write failed. */ return false; } return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); @@ -571,64 +570,59 @@ private void flushStorage(boolean flush) { traverseList(getJavaBufferList(), flush); traverseList(getNativeBufferList(), flush); - JfrBuffers buffers = globalMemory.getBuffers(); - for (int i = 0; i < globalMemory.getBufferCount(); i++) { - JfrBuffer buffer = buffers.addressOf(i).read(); - if (!JfrBufferAccess.tryLock(buffer)) { // one attempt - assert flush; - continue; + JfrBufferList buffers = globalMemory.getBuffers(); + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + boolean locked = JfrBufferNodeAccess.tryLock(node); + if (locked) { + try { + JfrBuffer buffer = node.getBuffer(); + write(buffer); + JfrBufferAccess.reinitialize(buffer); + } finally { + JfrBufferNodeAccess.unlock(node); + } } - write(buffer); - JfrBufferAccess.reinitialize(buffer); - JfrBufferAccess.unlock(buffer); + assert locked || flush; + node = node.getNext(); } } - @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer. Locks linked list with no transition. ") - private void traverseList(JfrBufferNodeLinkedList linkedList, boolean flush) { - - // hold onto lock until done with the head node. + @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") + private void traverseList(JfrBufferList linkedList, boolean flush) { JfrBufferNode node = linkedList.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); + // TEMP (chaeubl): node.getBuffer() should be accessed if the node is locked -> add an + // accessor method. while (node.isNonNull()) { - JfrBufferNode next = node.getNext(); - JfrBuffer buffer = node.getValue(); - assert buffer.isNonNull(); - - if (!node.getAlive()) { - // It is safe to free without acquiring because the owning thread is gone. - assert !JfrBufferAccess.isLockedByCurrentThread(buffer); - JfrBufferAccess.free(buffer); - linkedList.removeNode(node, prev); - // if removed current node, should not update prev. - node = next; - continue; - } - - if (flush) { - /* - * I/O operations may be slow, so this flushes to the global buffers instead of - * writing to disk directly. This mitigates the risk of acquiring the TLBs for too - * long. - */ - JfrThreadLocal.flushNoReset(buffer); - } else { - /* - * Buffer should not be locked when entering a safepoint. Lock buffer here to - * satisfy assertion checks. - */ - if (!JfrBufferAccess.tryLock(buffer)) { - assert false; - } + boolean locked = JfrBufferNodeAccess.tryLock(node); + if (locked) { try { - write(buffer); + JfrBuffer buffer = node.getBuffer(); + if (buffer.isNull()) { + linkedList.removeNode(node, prev); + /* Don't update prev if we removed the node. */ + } else { + if (flush) { + /* + * I/O operations may be slow, so this flushes to the global buffers + * instead of writing to disk directly. This mitigates the risk of + * acquiring the thread-local buffers for too long. + */ + SubstrateJVM.getGlobalMemory().write(buffer, true); + } else { + write(buffer); + } + prev = node; + } } finally { - JfrBufferAccess.unlock(buffer); + JfrBufferNodeAccess.unlock(node); } } - prev = node; - node = next; + + assert locked || flush; + node = node.getNext(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 913c238beef0..14e43fab6d1a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -26,8 +26,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.UnmanagedMemory; -import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -36,114 +34,123 @@ import com.oracle.svm.core.thread.VMOperation; /** - * Manages the global JFR memory. A lot of the methods must be uninterruptible to ensure that we can - * iterate and process the global JFR memory at a safepoint without having to worry about partial - * modifications that were interrupted by the safepoint. + * Manages the global JFR buffers (see {@link JfrBufferType#GLOBAL_MEMORY}). The memory has a very + * long lifetime, as it is allocated during JFR startup and released during JFR teardown. + * + * A lot of the methods must be uninterruptible to ensure that we can iterate and process the global + * JFR memory at a safepoint without having to worry about partial modifications that were + * interrupted by the safepoint. */ public class JfrGlobalMemory { private static final int PROMOTION_RETRY_COUNT = 100; - private long bufferCount; + private final JfrBufferList buffers; private long bufferSize; - private JfrBuffers buffers; @Platforms(Platform.HOSTED_ONLY.class) public JfrGlobalMemory() { + this.buffers = new JfrBufferList(); } public void initialize(long globalBufferSize, long globalBufferCount) { - this.bufferCount = globalBufferCount; this.bufferSize = globalBufferSize; - // Allocate all buffers eagerly. - buffers = UnmanagedMemory.calloc(SizeOf.unsigned(JfrBuffers.class).multiply(WordFactory.unsigned(bufferCount))); - for (int i = 0; i < bufferCount; i++) { + /* Allocate all buffers eagerly. */ + for (int i = 0; i < globalBufferCount; i++) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(bufferSize), JfrBufferType.GLOBAL_MEMORY); if (buffer.isNull()) { throw new OutOfMemoryError("Could not allocate JFR buffer."); } - buffers.addressOf(i).write(buffer); + + JfrBufferNode node = buffers.addNode(buffer); + if (node.isNull()) { + throw new OutOfMemoryError("Could not allocate JFR buffer node."); + } } } public void clear() { assert VMOperation.isInProgressAtSafepoint(); - for (int i = 0; i < bufferCount; i++) { - JfrBuffer buffer = buffers.addressOf(i).read(); - JfrBufferAccess.reinitialize(buffer); + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + JfrBufferAccess.reinitialize(node.getBuffer()); + node = node.getNext(); } } public void teardown() { - if (buffers.isNonNull()) { - for (int i = 0; i < bufferCount; i++) { - JfrBuffer buffer = buffers.addressOf(i).read(); - JfrBufferAccess.free(buffer); - } - UnmanagedMemory.free(buffers); - buffers = WordFactory.nullPointer(); + /* Free the buffers. */ + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + JfrBufferAccess.free(node.getBuffer()); + node = node.getNext(); } + + /* Free the nodes. */ + buffers.teardown(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public JfrBuffers getBuffers() { - assert buffers.isNonNull(); + public JfrBufferList getBuffers() { return buffers; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public long getBufferCount() { - return bufferCount; + @Uninterruptible(reason = "Epoch must not change while in this method.") + public boolean write(JfrBuffer threadLocalBuffer, boolean streamingFlush) { + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); + return write(threadLocalBuffer, unflushedSize, streamingFlush); } @Uninterruptible(reason = "Epoch must not change while in this method.") - public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, boolean doingFlush) { - JfrBuffer promotionBuffer = tryAcquirePromotionBuffer(unflushedSize); - if (promotionBuffer.isNull()) { + public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, boolean streamingFlush) { + if (unflushedSize.equal(0)) { + return true; + } + + JfrBufferNode promotionNode = tryAcquirePromotionBuffer(unflushedSize); + if (promotionNode.isNull()) { return false; } + boolean shouldSignal; - JfrRecorderThread recorderThread = SubstrateJVM.getRecorderThread(); try { // Copy all committed but not yet flushed memory to the promotion buffer. + JfrBuffer promotionBuffer = promotionNode.getBuffer(); assert JfrBufferAccess.getAvailableSize(promotionBuffer).aboveOrEqual(unflushedSize); UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(threadLocalBuffer), promotionBuffer.getCommittedPos(), unflushedSize); JfrBufferAccess.increaseCommittedPos(promotionBuffer, unflushedSize); - shouldSignal = recorderThread.shouldSignal(promotionBuffer); + shouldSignal = SubstrateJVM.getRecorderThread().shouldSignal(promotionBuffer); } finally { - releasePromotionBuffer(promotionBuffer); + JfrBufferNodeAccess.unlock(promotionNode); } + JfrBufferAccess.increaseFlushedPos(threadLocalBuffer, unflushedSize); // Notify the thread that writes the global memory to disk. // If we're flushing, the global buffers are about to get persisted anyway - if (shouldSignal && !doingFlush) { - recorderThread.signal(); + if (shouldSignal && !streamingFlush) { + SubstrateJVM.getRecorderThread().signal(); } return true; } @Uninterruptible(reason = "Epoch must not change while in this method.") - private JfrBuffer tryAcquirePromotionBuffer(UnsignedWord size) { + private JfrBufferNode tryAcquirePromotionBuffer(UnsignedWord size) { assert size.belowOrEqual(WordFactory.unsigned(bufferSize)); for (int retry = 0; retry < PROMOTION_RETRY_COUNT; retry++) { - for (int i = 0; i < bufferCount; i++) { - JfrBuffer buffer = buffers.addressOf(i).read(); - if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size) && JfrBufferAccess.tryLock(buffer)) { + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + JfrBuffer buffer = node.getBuffer(); + if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size) && JfrBufferNodeAccess.tryLock(node)) { /* Recheck the available size after acquiring the buffer. */ if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size)) { - return buffer; + return node; } - JfrBufferAccess.unlock(buffer); + JfrBufferNodeAccess.unlock(node); } + node = node.getNext(); } } return WordFactory.nullPointer(); } - - @Uninterruptible(reason = "Epoch must not change while in this method.") - private static void releasePromotionBuffer(JfrBuffer buffer) { - assert JfrBufferAccess.isLockedByCurrentThread(buffer); - JfrBufferAccess.unlock(buffer); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java index a67410ee653f..1bc9bc32239a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java @@ -35,6 +35,7 @@ */ @RawStructure public interface JfrNativeEventWriterData extends PointerBase { + /** * Gets the JfrBuffer that data will be written to. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java index 2a354dc46162..d5ccbd4f2bae 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java @@ -24,10 +24,11 @@ */ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.Uninterruptible; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.Uninterruptible; + /** * Helper class that holds methods related to {@link JfrNativeEventWriterData}. */ @@ -61,8 +62,7 @@ public static void initialize(JfrNativeEventWriterData data, JfrBuffer buffer) { */ @Uninterruptible(reason = "Accesses a JFR buffer", callerMustBe = true) public static void initializeThreadLocalNativeBuffer(JfrNativeEventWriterData data) { - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); - JfrBuffer nativeBuffer = jfrThreadLocal.getNativeBuffer(); + JfrBuffer nativeBuffer = SubstrateJVM.getThreadLocal().getNativeBuffer(); initialize(data, nativeBuffer); } 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 48fc90808fd1..a3dc6dbd123e 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 @@ -122,18 +122,28 @@ private void run0() { @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification = "state change is in native buffer") private void persistBuffers(JfrChunkWriter chunkWriter) { - JfrBuffers buffers = globalMemory.getBuffers(); - for (int i = 0; i < globalMemory.getBufferCount(); i++) { - JfrBuffer buffer = buffers.addressOf(i).read(); - if (isFullEnough(buffer)) { - boolean shouldNotify = persistBuffer(chunkWriter, buffer); - if (shouldNotify) { - Object chunkRotationMonitor = getChunkRotationMonitor(); - synchronized (chunkRotationMonitor) { - chunkRotationMonitor.notifyAll(); + JfrBufferList buffers = globalMemory.getBuffers(); + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + if (JfrBufferNodeAccess.tryLock(node)) { + try { + JfrBuffer buffer = node.getBuffer(); + if (isFullEnough(buffer)) { + boolean shouldNotify = chunkWriter.write(buffer); + JfrBufferAccess.reinitialize(buffer); + + if (shouldNotify) { + Object chunkRotationMonitor = getChunkRotationMonitor(); + synchronized (chunkRotationMonitor) { + chunkRotationMonitor.notifyAll(); + } + } } + } finally { + JfrBufferNodeAccess.unlock(node); } } + node = node.getNext(); } } @@ -145,20 +155,6 @@ private static Object getChunkRotationMonitor() { } } - @Uninterruptible(reason = "Epoch must not change while in this method.") - private static boolean persistBuffer(JfrChunkWriter chunkWriter, JfrBuffer buffer) { - if (JfrBufferAccess.tryLock(buffer)) { - try { - boolean shouldNotify = chunkWriter.write(buffer); - JfrBufferAccess.reinitialize(buffer); - return shouldNotify; - } finally { - JfrBufferAccess.unlock(buffer); - } - } - return false; - } - /** * We need to be a bit careful with this method as the recorder thread can't do anything if the * chunk writer doesn't have an output file. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 44262aea2cfd..2d10e62482f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jfr; +import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; @@ -33,22 +34,21 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; -import org.graalvm.compiler.api.replacements.Fold; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.jfr.events.ThreadEndEvent; import com.oracle.svm.core.jfr.events.ThreadStartEvent; import com.oracle.svm.core.sampler.SamplerBuffer; import com.oracle.svm.core.sampler.SamplerSampleWriterData; +import com.oracle.svm.core.thread.JavaThreads; +import com.oracle.svm.core.thread.Target_java_lang_Thread; import com.oracle.svm.core.thread.ThreadListener; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.threadlocal.FastThreadLocalObject; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; -import com.oracle.svm.core.thread.Target_java_lang_Thread; -import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.thread.JavaThreads; /** * This class holds various JFR-specific thread local values. @@ -85,17 +85,19 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("JfrThreadLocal.missedSamples"); private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("JfrThreadLocal.unparseableStacks"); private static final FastThreadLocalWord samplerWriterData = FastThreadLocalFactory.createWord("JfrThreadLocal.samplerWriterData"); - private static final JfrBufferNodeLinkedList javaBufferList = new JfrBufferNodeLinkedList(); - private static final JfrBufferNodeLinkedList nativeBufferList = new JfrBufferNodeLinkedList(); + + /* Non-thread-local fields. */ + private static final JfrBufferList javaBufferList = new JfrBufferList(); + private static final JfrBufferList nativeBufferList = new JfrBufferList(); private long threadLocalBufferSize; @Fold - public static JfrBufferNodeLinkedList getNativeBufferList() { + public static JfrBufferList getNativeBufferList() { return nativeBufferList; } @Fold - public static JfrBufferNodeLinkedList getJavaBufferList() { + public static JfrBufferList getJavaBufferList() { return javaBufferList; } @@ -129,30 +131,16 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { @Uninterruptible(reason = "Accesses various JFR buffers.") public static void stopRecording(IsolateThread isolateThread) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ + JfrBufferNode javaNode = javaBufferNode.get(isolateThread); + javaBufferNode.set(isolateThread, WordFactory.nullPointer()); - JfrBufferNode jbn = javaBufferNode.get(isolateThread); - JfrBufferNode nbn = nativeBufferNode.get(isolateThread); - - /* - * Once JfrBufferNode. JfrBufferNodeAccess.setRetired(JfrBufferNode) is called, another - * thread may free the node at any time. - */ - if (jbn.isNonNull()) { - JfrBuffer jb = jbn.getValue(); - assert jb.isNonNull() && jbn.getAlive(); - flush(jb, WordFactory.unsigned(0), 0); - javaBufferNode.set(isolateThread, WordFactory.nullPointer()); - JfrBufferNodeAccess.setRetired(jbn); - } - if (nbn.isNonNull()) { - JfrBuffer nb = nbn.getValue(); - assert nb.isNonNull() && nbn.getAlive(); - flush(nb, WordFactory.unsigned(0), 0); - nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); - JfrBufferNodeAccess.setRetired(nbn); - } + JfrBufferNode nativeNode = nativeBufferNode.get(isolateThread); + nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); + + flushAndFreeBuffer(javaNode); + flushAndFreeBuffer(nativeNode); - /* Clear event-related thread-locals. */ + /* Clear the other event-related thread-locals. */ dataLost.set(isolateThread, WordFactory.unsigned(0)); javaEventWriter.set(isolateThread, null); @@ -168,6 +156,30 @@ public static void stopRecording(IsolateThread isolateThread) { } } + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void flushAndFreeBuffer(JfrBufferNode node) { + if (node.isNull()) { + return; + } + + /* Free the JFRBuffer but leave the node alive as it still needed. */ + JfrBufferNodeAccess.lock(node); + try { + JfrBuffer buffer = node.getBuffer(); + node.setBuffer(WordFactory.nullPointer()); + + flush0(buffer, WordFactory.unsigned(0), 0); + JfrBufferAccess.free(buffer); + } finally { + JfrBufferNodeAccess.unlock(node); + } + } + + public void teardown() { + getNativeBufferList().teardown(); + getJavaBufferList().teardown(); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long getThreadLocalBufferSize() { return threadLocalBufferSize; @@ -209,38 +221,40 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { - JfrBufferNode result = javaBufferNode.get(); - if (result.isNull()) { + JfrBufferNode node = javaBufferNode.get(); + if (node.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); if (buffer.isNull()) { return WordFactory.nullPointer(); } - result = javaBufferList.addNode(buffer, CurrentIsolate.getCurrentThread()); - if (result.isNull()) { + + node = javaBufferList.addNode(buffer); + if (node.isNull()) { JfrBufferAccess.free(buffer); return WordFactory.nullPointer(); } - javaBufferNode.set(result); + javaBufferNode.set(node); } - return result.getValue(); + return node.getBuffer(); } @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public JfrBuffer getNativeBuffer() { - JfrBufferNode result = nativeBufferNode.get(); - if (result.isNull()) { + JfrBufferNode node = nativeBufferNode.get(); + if (node.isNull()) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); if (buffer.isNull()) { return WordFactory.nullPointer(); } - result = nativeBufferList.addNode(buffer, CurrentIsolate.getCurrentThread()); - if (result.isNull()) { + + node = nativeBufferList.addNode(buffer); + if (node.isNull()) { JfrBufferAccess.free(buffer); return WordFactory.nullPointer(); } - nativeBufferNode.set(result); + nativeBufferNode.set(node); } - return result.getValue(); + return node.getBuffer(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -250,61 +264,43 @@ public static void notifyEventWriter(IsolateThread thread) { } } - /** - * This method only copies the JFR buffer's unflushed data to the global buffers. This can be - * used outside a safepoint from the flushing thread while other threads continue writing - * events. - */ @Uninterruptible(reason = "Accesses a JFR buffer.") - public static boolean flushNoReset(JfrBuffer threadLocalBuffer) { - JfrBufferAccess.tryLock(threadLocalBuffer, Integer.MAX_VALUE); + public static JfrBuffer flush(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { + assert buffer.isNonNull(); + assert JfrBufferAccess.isThreadLocal(buffer); + + /* Acquire the buffer because a streaming flush could be in progress. */ + JfrBufferNode node = buffer.getNode(); + JfrBufferNodeAccess.lock(node); try { - UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); - if (unflushedSize.aboveThan(0)) { - JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); - // flushedPos is increased in JfrGlobalMemory.write - if (!globalMemory.write(threadLocalBuffer, unflushedSize, true)) { - return false; - } - } - return true; + return flush0(buffer, uncommitted, requested); } finally { - JfrBufferAccess.unlock(threadLocalBuffer); + JfrBufferNodeAccess.unlock(node); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommitted, int requested) { - assert threadLocalBuffer.isNonNull(); - - // Needed for race between streaming flush and promotion - JfrBufferAccess.tryLock(threadLocalBuffer, Integer.MAX_VALUE); - try { - UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); - if (unflushedSize.aboveThan(0)) { - JfrGlobalMemory globalMemory = SubstrateJVM.getGlobalMemory(); - if (!globalMemory.write(threadLocalBuffer, unflushedSize, false)) { - JfrBufferAccess.reinitialize(threadLocalBuffer); - writeDataLoss(threadLocalBuffer, unflushedSize); - return WordFactory.nullPointer(); - } - } - - if (uncommitted.aboveThan(0)) { - /* Copy all uncommitted memory to the start of the thread local buffer. */ - assert JfrBufferAccess.getDataStart(threadLocalBuffer).add(uncommitted).belowOrEqual(JfrBufferAccess.getDataEnd(threadLocalBuffer)); - UnmanagedMemoryUtil.copy(threadLocalBuffer.getCommittedPos(), JfrBufferAccess.getDataStart(threadLocalBuffer), uncommitted); - } - JfrBufferAccess.reinitialize(threadLocalBuffer); - assert JfrBufferAccess.getUnflushedSize(threadLocalBuffer).equal(0); - if (threadLocalBuffer.getSize().aboveOrEqual(uncommitted.add(requested))) { - return threadLocalBuffer; + private static JfrBuffer flush0(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); + if (unflushedSize.aboveThan(0)) { + if (!SubstrateJVM.getGlobalMemory().write(buffer, unflushedSize, false)) { + JfrBufferAccess.reinitialize(buffer); + writeDataLoss(buffer, unflushedSize); + return WordFactory.nullPointer(); } + } - return WordFactory.nullPointer(); - } finally { - JfrBufferAccess.unlock(threadLocalBuffer); + if (uncommitted.aboveThan(0)) { + /* Copy all uncommitted memory to the start of the thread local buffer. */ + assert JfrBufferAccess.getDataStart(buffer).add(uncommitted).belowOrEqual(JfrBufferAccess.getDataEnd(buffer)); + UnmanagedMemoryUtil.copy(buffer.getCommittedPos(), JfrBufferAccess.getDataStart(buffer), uncommitted); } + JfrBufferAccess.reinitialize(buffer); + assert JfrBufferAccess.getUnflushedSize(buffer).equal(0); + if (buffer.getSize().aboveOrEqual(uncommitted.add(requested))) { + return buffer; + } + return WordFactory.nullPointer(); } @Uninterruptible(reason = "Accesses a JFR buffer.") @@ -331,11 +327,6 @@ private static UnsignedWord increaseDataLost(UnsignedWord delta) { return result; } - public void teardown() { - getNativeBufferList().teardown(); - getJavaBufferList().teardown(); - } - /** * This method excludes/includes a thread from JFR (emitting events and sampling). Unlike in * hotspot, only the current thread may be excluded/included. TODO: possibly modify this method 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 2059ccaaa4f8..57fe133c5a2c 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 @@ -44,7 +44,6 @@ import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.JavaVMOperation; -import com.oracle.svm.core.thread.ThreadListener; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; @@ -143,7 +142,7 @@ public static JfrRecorderThread getRecorderThread() { } @Fold - public static ThreadListener getThreadLocal() { + public static JfrThreadLocal getThreadLocal() { return get().threadLocal; } @@ -479,27 +478,29 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted assert uncommittedSize >= 0; JfrBuffer oldBuffer = threadLocal.getJavaBuffer(); - assert oldBuffer.isNonNull(); - JfrBuffer newBuffer = JfrThreadLocal.flush(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); - if (newBuffer.isNull()) { - // The flush failed for some reason, so mark the EventWriter as invalid for this write - // attempt. - JfrEventWriterAccess.setStartPosition(writer, oldBuffer.getCommittedPos().rawValue()); - JfrEventWriterAccess.setCurrentPosition(writer, oldBuffer.getCommittedPos().rawValue()); - JfrEventWriterAccess.setValid(writer, false); - } else { - // Update the EventWriter so that it uses the correct buffer and positions. - Pointer newCurrentPos = newBuffer.getCommittedPos().add(uncommittedSize); - JfrEventWriterAccess.setStartPosition(writer, newBuffer.getCommittedPos().rawValue()); - JfrEventWriterAccess.setCurrentPosition(writer, newCurrentPos.rawValue()); - if (newBuffer.notEqual(oldBuffer)) { - JfrEventWriterAccess.setStartPositionAddress(writer, JfrBufferAccess.getAddressOfCommittedPos(newBuffer).rawValue()); - JfrEventWriterAccess.setMaxPosition(writer, JfrBufferAccess.getDataEnd(newBuffer).rawValue()); + if (oldBuffer.isNonNull()) { + JfrBuffer newBuffer = JfrThreadLocal.flush(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); + if (newBuffer.isNull()) { + /* The flush failed, so mark the EventWriter as invalid for this write attempt. */ + JfrEventWriterAccess.setStartPosition(writer, oldBuffer.getCommittedPos().rawValue()); + JfrEventWriterAccess.setCurrentPosition(writer, oldBuffer.getCommittedPos().rawValue()); + JfrEventWriterAccess.setValid(writer, false); + } else { + /* Update the EventWriter so that it uses the correct buffer and positions. */ + Pointer newCurrentPos = newBuffer.getCommittedPos().add(uncommittedSize); + JfrEventWriterAccess.setStartPosition(writer, newBuffer.getCommittedPos().rawValue()); + JfrEventWriterAccess.setCurrentPosition(writer, newCurrentPos.rawValue()); + if (newBuffer.notEqual(oldBuffer)) { + JfrEventWriterAccess.setStartPositionAddress(writer, JfrBufferAccess.getAddressOfCommittedPos(newBuffer).rawValue()); + JfrEventWriterAccess.setMaxPosition(writer, JfrBufferAccess.getDataEnd(newBuffer).rawValue()); + } } } - // Return false to signal that there is no need to do another flush at the end of the - // current event. + /* + * Return false to signal that there is no need to do another flush at the end of the + * current event. + */ return false; } @@ -673,8 +674,7 @@ public Object getConfiguration(Class eventClass) { } public void setExcluded(Thread thread, boolean excluded) { - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); - jfrThreadLocal.setExcluded(thread, excluded); + getThreadLocal().setExcluded(thread, excluded); } public boolean isExcluded(Thread thread) { @@ -730,7 +730,7 @@ protected void operate() { * If JFR recording is restarted later on, then it needs to start with a clean state. * Therefore, we clear all data that is still pending. */ - ((JfrThreadLocal) SubstrateJVM.getThreadLocal()).teardown(); + SubstrateJVM.getThreadLocal().teardown(); SubstrateJVM.getSamplerBufferPool().teardown(); SubstrateJVM.getGlobalMemory().clear(); } 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 831b8e6f2d28..1ce08914254f 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 @@ -34,7 +34,6 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean; -import com.oracle.svm.core.jfr.JfrThreadLocal; import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.locks.VMMutex; @@ -150,8 +149,7 @@ private boolean allocateAndPush() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private SamplerBuffer tryAllocateBuffer0() { UnsignedWord headerSize = SamplerBufferAccess.getHeaderSize(); - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); - UnsignedWord dataSize = WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize()); + UnsignedWord dataSize = WordFactory.unsigned(SubstrateJVM.getThreadLocal().getThreadLocalBufferSize()); SamplerBuffer result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(headerSize.add(dataSize)); if (result.isNonNull()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java deleted file mode 100644 index a7bf91609bd9..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaOwnedSpinLockUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.core.thread; - -import org.graalvm.compiler.nodes.PauseNode; - -import com.oracle.svm.core.Uninterruptible; -import org.graalvm.word.WordFactory; -import jdk.internal.misc.Unsafe; -import org.graalvm.nativeimage.CurrentIsolate; - -/** - * Spin locks may only be used in places where the critical section contains only a few instructions - * of uninterruptible code. We don't do a transition to native in case of a lock contention, so it - * is crucial that really all code within the critical section is uninterruptible. - */ -public class JavaOwnedSpinLockUtils { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - - private static final long UNLOCKED = WordFactory.nullPointer().rawValue(); - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void initialize(Object obj, long wordFieldOffset) { - UNSAFE.putLongVolatile(obj, wordFieldOffset, UNLOCKED); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isLocked(Object obj, long wordFieldOffset) { - return UNSAFE.getLongOpaque(obj, wordFieldOffset) != UNLOCKED; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean tryLock(Object obj, long wordFieldOffset) { - return UNSAFE.compareAndSetLong(obj, wordFieldOffset, UNLOCKED, CurrentIsolate.getCurrentThread().rawValue()); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean tryLock(Object obj, long wordFieldOffset, int retries) { - if (tryLock(obj, wordFieldOffset)) { - return true; // fast-path - } - - int yields = 0; - for (int i = 0; i < retries; i++) { - if (isLocked(obj, wordFieldOffset)) { - /* - * It would be better to take into account if we are on a single-processor machine - * where spinning is futile. However, determining that is expensive in itself. We do - * use fewer successive spins than the equivalent HotSpot code does (0xFFF). - */ - if ((i & 0xff) == 0 && VMThreads.singleton().supportsNativeYieldAndSleep()) { - if (yields > 5) { - VMThreads.singleton().nativeSleep(1); - } else { - VMThreads.singleton().yield(); - yields++; - } - } else { - PauseNode.pause(); - } - } else if (tryLock(obj, wordFieldOffset)) { - return true; - } - } - - return false; - } - - @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public static void lockNoTransition(Object obj, long wordFieldOffset) { - while (!tryLock(obj, wordFieldOffset, Integer.MAX_VALUE)) { - // Nothing to do. - } - } - - @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) - public static void unlock(Object obj, long wordFieldOffset) { - /* - * Roach-motel semantics. It's safe if subsequent LDs and STs float "up" into the critical - * section, but prior LDs and STs within the critical section can't be allowed to reorder or - * float past the ST that releases the lock. Loads and stores in the critical section - - * which appear in program order before the store that releases the lock - must also appear - * before the store that releases the lock in memory visibility order. Conceptually we need - * a #loadstore|#storestore "release" MEMBAR before the ST of 0 into the lock-word which - * releases the lock. - */ - UNSAFE.putLongVolatile(obj, wordFieldOffset, UNLOCKED); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java index 855c656b9e6a..22379473ac18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.thread; +import org.graalvm.nativeimage.IsolateThread; + import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.util.VMError; @@ -53,7 +55,7 @@ public final class ThreadData extends UnacquiredThreadData { throw VMError.shouldNotReachHere(ex); } } - private volatile int lock; + private volatile IsolateThread lock; private boolean detached; private long refCount; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index 2d71c6a845d9..b12eda968ecb 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -26,38 +26,39 @@ package com.oracle.svm.test.jfr; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.jfr.JfrBufferNodeLinkedList; -import com.oracle.svm.core.jfr.JfrBufferNode; import org.graalvm.word.WordFactory; -import com.oracle.svm.core.jfr.JfrBufferType; +import org.junit.Test; + +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.JfrBuffer; import com.oracle.svm.core.jfr.JfrBufferAccess; +import com.oracle.svm.core.jfr.JfrBufferList; +import com.oracle.svm.core.jfr.JfrBufferNode; import com.oracle.svm.core.jfr.JfrBufferNodeAccess; -import org.graalvm.nativeimage.CurrentIsolate; -import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrBufferType; public class TestJfrBufferNodeLinkedList { @Test public void testBasicAdditionAndRemoval() { final int nodeCount = 10; - final JfrBufferNodeLinkedList list = new JfrBufferNodeLinkedList(); + final JfrBufferList list = new JfrBufferList(); addNodes(list, nodeCount); int count = countNodes(list); - assertTrue("Number of nodes in list does not match nodes added.", count == nodeCount); + assertEquals("Number of nodes in list does not match nodes added.", count, nodeCount); cleanUpList(list); } @Test public void testMiddleRemoval() { final int nodeCount = 10; - JfrBufferNodeLinkedList list = new JfrBufferNodeLinkedList(); + JfrBufferList list = new JfrBufferList(); addNodes(list, nodeCount); removeNthNode(list, nodeCount / 2); - assertTrue("Removal from middle failed", countNodes(list) == nodeCount - 1); + assertEquals("Removal from middle failed", countNodes(list), nodeCount - 1); cleanUpList(list); } @@ -65,29 +66,29 @@ public void testMiddleRemoval() { public void testConcurrentAddition() throws Exception { final int nodeCountPerThread = 10; final int threads = 10; - JfrBufferNodeLinkedList list = new JfrBufferNodeLinkedList(); + JfrBufferList list = new JfrBufferList(); Runnable r = () -> { addNodes(list, nodeCountPerThread); }; Stressor.execute(threads, r); - assertTrue("Incorrect number of nodes added", countNodes(list) == nodeCountPerThread * threads); + assertEquals("Incorrect number of nodes added", countNodes(list), nodeCountPerThread * threads); cleanUpList(list); } - private static void cleanUpList(JfrBufferNodeLinkedList list) { + private static void cleanUpList(JfrBufferList list) { JfrBufferNode node = removeAllNodes(list); assertTrue("Could not remove all nodes", node.isNull()); } - private static void addNodes(JfrBufferNodeLinkedList list, int nodeCount) { + private static void addNodes(JfrBufferList list, int nodeCount) { for (int i = 0; i < nodeCount; i++) { JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(32), JfrBufferType.THREAD_LOCAL_NATIVE); - list.addNode(buffer, CurrentIsolate.getCurrentThread()); + list.addNode(buffer); } } @Uninterruptible(reason = "Locking with no transition.") - private static int countNodes(JfrBufferNodeLinkedList list) { + private static int countNodes(JfrBufferList list) { int count = 0; JfrBufferNode node = list.getHead(); while (node.isNonNull()) { @@ -98,12 +99,12 @@ private static int countNodes(JfrBufferNodeLinkedList list) { } @Uninterruptible(reason = "Locking with no transition.") - private static JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { + private static JfrBufferNode removeAllNodes(JfrBufferList list) { // Try removing the nodes JfrBufferNode node = list.getHead(); while (node.isNonNull()) { JfrBufferNode next = node.getNext(); - JfrBufferAccess.free(node.getValue()); + JfrBufferAccess.free(node.getBuffer()); /* * Once JfrBufferNodeAccess.setRetired(node) is called, another thread may free the node * at any time. @@ -116,14 +117,14 @@ private static JfrBufferNode removeAllNodes(JfrBufferNodeLinkedList list) { } @Uninterruptible(reason = "Locking with no transition.") - private static void removeNthNode(JfrBufferNodeLinkedList list, int target) { + private static void removeNthNode(JfrBufferList list, int target) { JfrBufferNode prev = WordFactory.nullPointer(); JfrBufferNode node = list.getHead(); int count = 0; while (node.isNonNull()) { JfrBufferNode next = node.getNext(); if (count == target) { - JfrBufferAccess.free(node.getValue()); + JfrBufferAccess.free(node.getBuffer()); /* * Once JfrBufferNodeAccess.setRetired(node) is called, another thread may free the * node at any time. diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 4c0db56243ab..2a9d1ac1d23b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -118,8 +118,7 @@ private static Positions parserFileHeader(RecordingInput input) throws IOExcepti private static void parseMetadataHeader(RecordingInput input, long metadataPosition) throws IOException { input.position(metadataPosition); // Seek to starting position of metadata region. assertTrue("Metadata size is invalid!", input.readInt() > 0); // Size of metadata. - assertEquals(JfrReservedEvent.EVENT_METADATA.getId(), input.readLong()); // Metadata region - // ID. + assertEquals(JfrReservedEvent.EVENT_METADATA.getId(), input.readLong()); assertTrue("Metadata timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. input.readLong(); // Metadata ID. @@ -150,9 +149,9 @@ private static void compareFoundAndExpectedIds() { } /** - * Must verify constant pools in order that they were written because event streaming can write - * pools before the chunk is finished. This means that a given pool may reference constants from - * another pool written previously (within the same chunk). + * Must verify constant pools in the order that they were written because event streaming can + * write pools before the chunk is finished. This means that a given pool may reference + * constants from another pool written previously (within the same chunk). */ private static void verifyConstantPools(RecordingInput input, long constantPoolPosition) throws IOException { List poolPositions = new ArrayList<>(); From 7396d877e3d91fc52aa9a81bcf6c6a8d78d11a77 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Fri, 3 Mar 2023 13:51:44 +0100 Subject: [PATCH 59/72] Various cleanups and simplifications. --- .../oracle/svm/core/jfr/JfrBufferAccess.java | 9 +- .../oracle/svm/core/jfr/JfrBufferList.java | 10 +- .../svm/core/jfr/JfrBufferNodeAccess.java | 6 - .../svm/core/jfr/JfrCheckpointType.java | 3 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 308 +++++++++--------- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 12 +- .../svm/core/jfr/JfrMethodRepository.java | 148 ++++----- .../oracle/svm/core/jfr/JfrReservedEvent.java | 4 +- .../svm/core/jfr/JfrStackTraceRepository.java | 103 +++--- .../svm/core/jfr/JfrSymbolRepository.java | 74 ++--- .../oracle/svm/core/jfr/JfrThreadLocal.java | 1 - .../svm/core/jfr/JfrThreadRepository.java | 9 +- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 24 +- .../oracle/svm/core/thread/ThreadData.java | 26 +- .../svm/test/jfr/utils/JfrFileParser.java | 4 +- 15 files changed, 329 insertions(+), 412 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 2625a35aa16c..6e0084314d0f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -74,10 +74,11 @@ public static void free(JfrBuffer buffer) { @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public static void reinitialize(JfrBuffer buffer) { - assert buffer.isNonNull(); - Pointer pos = getDataStart(buffer); - buffer.setCommittedPos(pos); - setFlushedPos(buffer, pos); + if (buffer.isNonNull()) { + Pointer pos = getDataStart(buffer); + buffer.setCommittedPos(pos); + setFlushedPos(buffer, pos); + } } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index c62c0faa4efa..d5636aa5321d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -39,15 +39,15 @@ import jdk.internal.misc.Unsafe; /** - * Singly linked list that stores JFR buffers. Multiple instances of this data structure are used to - * keep track of the global and the various thread-local buffers. When entering a safepoint, it is - * guaranteed that none of the blocked Java threads holds the list's lock. + * Singly linked list that stores {@link JfrBuffer}s. Multiple instances of this data structure are + * used to keep track of the global and the various thread-local buffers. When entering a safepoint, + * it is guaranteed that none of the blocked Java threads holds the list's lock. * * The following invariants are crucial if the list is used for thread-local buffers: *
      *
    • Each thread shall only add one node to the list.
    • - *
    • Only the thread performing a flush or epoch change shall iterate this list.
    • - *
    • Only the thread performing a flush or epoch change is allowed to remove nodes.
    • + *
    • Only threads that hold the {@link JfrChunkWriter#lock()} may iterate or remove nodes from the + * list.
    • *
    */ public class JfrBufferList { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 0f414e0b1a2a..f26c3122e6bd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -66,12 +66,6 @@ public static boolean tryLock(JfrBufferNode node) { return NativeSpinLockUtils.tryLock(ptrToLock(node)); } - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) - public static boolean tryLock(JfrBufferNode node, int retries) { - assert node.isNonNull(); - return NativeSpinLockUtils.tryLock(ptrToLock(node), retries); - } - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) public static void lock(JfrBufferNode node) { assert node.isNonNull(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java index f7eb14fc818b..4d1f50e2cf45 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrCheckpointType.java @@ -25,9 +25,10 @@ */ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.Uninterruptible; import org.graalvm.compiler.core.common.NumUtil; +import com.oracle.svm.core.Uninterruptible; + public enum JfrCheckpointType { Flush(1), Threads(8); 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 10d5ea73bbe1..8e2823034cba 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 @@ -44,6 +44,7 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; import com.oracle.svm.core.sampler.SamplerBuffersAccess; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.ThreadingSupportImpl; @@ -69,29 +70,31 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private static final byte COMPLETE = 0; private static final short FLAG_COMPRESSED_INTS = 0b01; private static final short FLAG_CHUNK_FINAL = 0b10; - private final JfrGlobalMemory globalMemory; + private final VMMutex lock; - private final boolean compressedInts; + private final JfrGlobalMemory globalMemory; private final JfrMetadata metadata; + private final boolean compressedInts; + private long notificationThreshold; private String filename; - private RawFileOperationSupport.RawFileDescriptor fd; + private RawFileDescriptor fd; private long chunkStartTicks; private long chunkStartNanos; private byte generation; - private SignedWord lastCheckpointOffset; private boolean newChunk; private boolean isFinal; private long lastMetadataId; private SignedWord metadataPosition; + private SignedWord lastCheckpointOffset; @Platforms(Platform.HOSTED_ONLY.class) public JfrChunkWriter(JfrGlobalMemory globalMemory) { - this.lock = new VMMutex("JfrChunkWriter"); - this.compressedInts = true; + this.lock = new VMMutex("jfrChunkWriter"); this.globalMemory = globalMemory; this.metadata = new JfrMetadata(null); + this.compressedInts = true; } @Override @@ -116,6 +119,10 @@ public boolean hasOpenFile() { return getFileSupport().isValid(fd); } + public long getChunkStartNanos() { + return chunkStartNanos; + } + public void setFilename(String filename) { assert lock.isOwner(); this.filename = filename; @@ -130,23 +137,27 @@ public void maybeOpenFile() { public void openFile(String outputFile) { assert lock.isOwner(); - isFinal = false; + filename = outputFile; + fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); + + chunkStartTicks = JfrTicks.elapsedTicks(); + chunkStartNanos = JfrTicks.currentTimeNanos(); generation = 1; newChunk = true; + isFinal = false; lastMetadataId = -1; metadataPosition = WordFactory.signed(-1); - chunkStartNanos = JfrTicks.currentTimeNanos(); - chunkStartTicks = JfrTicks.elapsedTicks(); - filename = outputFile; - fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); + lastCheckpointOffset = WordFactory.signed(-1); + writeFileHeader(); - lastCheckpointOffset = WordFactory.signed(-1); // must reset this on new chunk } @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") public boolean write(JfrBuffer buffer) { + assert lock.isOwner(); assert buffer.isNonNull(); assert buffer.getBufferType() == JfrBufferType.C_HEAP || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { return false; @@ -162,6 +173,23 @@ public boolean write(JfrBuffer buffer) { return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); } + public void flush() { + assert lock.isOwner(); + flushStorage(true); + + lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, true, lastCheckpointOffset); + SignedWord constantPoolPosition = writeCheckpointEvent(true); + writeMetadataEvent(); + patchFileHeader(constantPoolPosition, true); + + newChunk = false; + } + + public void markChunkFinal() { + assert lock.isOwner(); + isFinal = true; + } + /** * Write all the in-memory data to the file. */ @@ -181,71 +209,56 @@ public void closeFile() { */ lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, false, lastCheckpointOffset); - SignedWord constantPoolPosition = writeCheckpointEvent(false); writeMetadataEvent(); patchFileHeader(constantPoolPosition, false); - getFileSupport().close(fd); + getFileSupport().close(fd); filename = null; fd = WordFactory.nullPointer(); } - public void flush() { - assert lock.isOwner(); - flushStorage(true); - - lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, true, lastCheckpointOffset); - SignedWord constantPoolPosition = writeCheckpointEvent(true); - writeMetadataEvent(); - - patchFileHeader(constantPoolPosition, true); - newChunk = false; - // unlike rotate chunk, don't close file. - } - private void writeFileHeader() { - // Write the header - some data gets patched later on. - getFileSupport().write(fd, FILE_MAGIC); // magic - getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); // version + /* Write the header - some data gets patched later on. */ + getFileSupport().write(fd, FILE_MAGIC); + getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); getFileSupport().writeShort(fd, JFR_VERSION_MINOR); assert getFileSupport().position(fd).equal(CHUNK_SIZE_OFFSET); getFileSupport().writeLong(fd, 0L); // chunk size getFileSupport().writeLong(fd, 0L); // last checkpoint offset getFileSupport().writeLong(fd, 0L); // metadata position - getFileSupport().writeLong(fd, chunkStartNanos); // startNanos + getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, 0L); // durationNanos getFileSupport().writeLong(fd, chunkStartTicks); getFileSupport().writeLong(fd, JfrTicks.getTicksFrequency()); assert getFileSupport().position(fd).equal(FILE_STATE_OFFSET); - getFileSupport().writeByte(fd, nextGeneration()); // A 1 byte generation is written - getFileSupport().writeByte(fd, (byte) 0); // A 1 byte padding + getFileSupport().writeByte(fd, getAndIncrementGeneration()); + getFileSupport().writeByte(fd, (byte) 0); // padding getFileSupport().writeShort(fd, computeHeaderFlags()); } private void patchFileHeader(SignedWord constantPoolPosition, boolean flush) { assert lock.isOwner(); + assert metadataPosition.greaterThan(0); + + byte generation = flush ? getAndIncrementGeneration() : COMPLETE; SignedWord currentPos = getFileSupport().position(fd); - long chunkSize = getFileSupport().position(fd).rawValue(); + long chunkSize = currentPos.rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; + getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); getFileSupport().writeLong(fd, chunkSize); getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); - assert metadataPosition.greaterThan(0); getFileSupport().writeLong(fd, metadataPosition.rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); + getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); - if (flush) { - // chunk is not finished - getFileSupport().writeByte(fd, nextGeneration()); - } else { - getFileSupport().writeByte(fd, COMPLETE); - } + getFileSupport().writeByte(fd, generation); getFileSupport().writeByte(fd, (byte) 0); getFileSupport().writeShort(fd, computeHeaderFlags()); - // need to move pointer back to correct position for next write + /* Move pointer back to correct position for next write. */ getFileSupport().seek(fd, currentPos); } @@ -260,7 +273,7 @@ private short computeHeaderFlags() { return flags; } - private byte nextGeneration() { + private byte getAndIncrementGeneration() { if (generation == Byte.MAX_VALUE) { // Restart counter if required. generation = 1; @@ -272,35 +285,42 @@ private byte nextGeneration() { private SignedWord writeCheckpointEvent(boolean flush) { assert lock.isOwner(); SignedWord start = beginEvent(); + long deltaToNextCheckpoint = getDeltaToNextCheckpoint(start); - if (lastCheckpointOffset.lessThan(0)) { - lastCheckpointOffset = start; - } - writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); + writeCompressedLong(JfrReservedEvent.CHECKPOINT.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(lastCheckpointOffset.subtract(start).rawValue()); // deltaToNext + writeCompressedLong(deltaToNextCheckpoint); writeByte(JfrCheckpointType.Flush.getId()); SignedWord poolCountPos = getFileSupport().position(fd); - getFileSupport().writeInt(fd, 0); // We'll patch this later. + getFileSupport().writeInt(fd, 0); // pool count (patched later) int poolCount = 0; if (newChunk) { - poolCount = writeSerializers(flush); + poolCount += writeSerializers(flush); } poolCount += writeRepositories(flush); SignedWord currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); - getFileSupport().writeInt(fd, makePaddedInt(poolCount)); + writePaddedInt(poolCount); + getFileSupport().seek(fd, currentPos); endEvent(start); - lastCheckpointOffset = start; + lastCheckpointOffset = start; return start; } + private long getDeltaToNextCheckpoint(SignedWord startOfNewCheckpoint) { + if (lastCheckpointOffset.lessThan(0)) { + return 0L; + } else { + return lastCheckpointOffset.subtract(startOfNewCheckpoint).rawValue(); + } + } + /** * Repositories earlier in the write order may reference entries of repositories later in the * write order. This ordering is required to prevent races during flushing without changing @@ -316,12 +336,11 @@ private int writeRepositories(boolean flush) { } private int writeSerializers(boolean flush) { - int count = 0; + int poolCount = 0; for (JfrConstantPool constantPool : JfrSerializerSupport.get().getSerializers()) { - int poolCount = constantPool.write(this, flush); - count += poolCount; + poolCount += constantPool.write(this, flush); } - return count; + return poolCount; } public void setMetadata(byte[] bytes) { @@ -330,19 +349,23 @@ public void setMetadata(byte[] bytes) { private void writeMetadataEvent() { assert lock.isOwner(); - // always write metadata on a new chunk! - if (lastMetadataId == metadata.getCurrentMetadataId()) { + + /* Only the write the metadata if this is a new chunk or if it changed in the meanwhile. */ + long currentMetadataId = metadata.getCurrentMetadataId(); + if (lastMetadataId == currentMetadataId) { return; } + SignedWord start = beginEvent(); - writeCompressedLong(JfrReservedEvent.EVENT_METADATA.getId()); + writeCompressedLong(JfrReservedEvent.METADATA.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(metadata.getCurrentMetadataId()); // metadata id + writeCompressedLong(currentMetadataId); writeBytes(metadata.getDescriptor()); // payload endEvent(start); + metadataPosition = start; - lastMetadataId = metadata.getCurrentMetadataId(); + lastMetadataId = currentMetadataId; } public boolean shouldRotateDisk() { @@ -362,8 +385,10 @@ public SignedWord beginEvent() { public void endEvent(SignedWord start) { SignedWord end = getFileSupport().position(fd); SignedWord writtenBytes = end.subtract(start); + assert (int) writtenBytes.rawValue() == writtenBytes.rawValue(); + getFileSupport().seek(fd, start); - getFileSupport().writeInt(fd, makePaddedInt(writtenBytes.rawValue())); + writePaddedInt(writtenBytes.rawValue()); getFileSupport().seek(fd, end); } @@ -392,9 +417,10 @@ public void writeCompressedInt(int value) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void writeInt(int value) { + public void writePaddedInt(long value) { assert lock.isOwner() || VMOperationControl.isDedicatedVMOperationThread() && lock.hasOwner(); - getFileSupport().writeInt(fd, makePaddedInt(value)); + assert (int) value == value; + getFileSupport().writeInt(fd, JfrNativeEventWriter.makePaddedInt((int) value)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -468,20 +494,71 @@ public void writeString(String str) { } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static int makePaddedInt(long sizeWritten) { - return JfrNativeEventWriter.makePaddedInt(safeToInt(sizeWritten)); - } + @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") + private void flushStorage(boolean flush) { + /* + * Write unflushed data from the thread-local event buffers to the output file. We do *not* + * reinitialize the thread-local buffers as the individual threads will handle space + * reclamation on their own time. + */ + traverseList(getJavaBufferList(), flush); + traverseList(getNativeBufferList(), flush); - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static int safeToInt(long v) { - assert isInt(v); - return (int) v; + /* Flush all global JFRBuffers. */ + JfrBufferList buffers = globalMemory.getBuffers(); + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + boolean locked = JfrBufferNodeAccess.tryLock(node); + if (locked) { + try { + JfrBuffer buffer = node.getBuffer(); + write(buffer); + JfrBufferAccess.reinitialize(buffer); + } finally { + JfrBufferNodeAccess.unlock(node); + } + } + assert locked || flush; + node = node.getNext(); + } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isInt(long l) { - return (int) l == l; + @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") + private void traverseList(JfrBufferList linkedList, boolean flush) { + JfrBufferNode node = linkedList.getHead(); + JfrBufferNode prev = WordFactory.nullPointer(); + + // TEMP (chaeubl): node.getBuffer() should be accessed if the node is locked -> add an + // accessor method. + while (node.isNonNull()) { + boolean locked = JfrBufferNodeAccess.tryLock(node); + if (locked) { + try { + JfrBuffer buffer = node.getBuffer(); + if (buffer.isNull()) { + linkedList.removeNode(node, prev); + /* Don't update prev if we removed the node. */ + } else { + if (flush) { + /* + * I/O operations may be slow, so this flushes to the global buffers + * instead of writing to disk directly. This mitigates the risk of + * acquiring the thread-local buffers for too long. + */ + SubstrateJVM.getGlobalMemory().write(buffer, true); + } else { + write(buffer); + } + prev = node; + } + } finally { + JfrBufferNodeAccess.unlock(node); + } + } + + assert locked || flush; + node = node.getNext(); + } } public enum StringEncoding { @@ -492,7 +569,7 @@ public enum StringEncoding { CHAR_ARRAY(4), LATIN1_BYTE_ARRAY(5); - public byte byteValue; + public final byte byteValue; StringEncoding(int byteValue) { this.byteValue = (byte) byteValue; @@ -500,7 +577,6 @@ public enum StringEncoding { } private class JfrChangeEpochOperation extends JavaVMOperation { - protected JfrChangeEpochOperation() { super(VMOperationInfos.get(JfrChangeEpochOperation.class, "JFR change epoch", SystemEffect.SAFEPOINT)); } @@ -519,8 +595,9 @@ protected void operate() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void changeEpoch() { processSamplerBuffers(); - flushStorage(false); + + /* Notify all event writers that the epoch changed. */ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { JfrThreadLocal.notifyEventWriter(thread); } @@ -559,79 +636,4 @@ private void processSamplerBuffers0() { SamplerBuffersAccess.processFullBuffers(false); } } - - @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") - private void flushStorage(boolean flush) { - /* - * Write unflushed data from the thread-local event buffers to the output file. We do *not* - * reinitialize the thread-local buffers as the individual threads will handle space - * reclamation on their own time. - */ - traverseList(getJavaBufferList(), flush); - traverseList(getNativeBufferList(), flush); - - JfrBufferList buffers = globalMemory.getBuffers(); - JfrBufferNode node = buffers.getHead(); - while (node.isNonNull()) { - boolean locked = JfrBufferNodeAccess.tryLock(node); - if (locked) { - try { - JfrBuffer buffer = node.getBuffer(); - write(buffer); - JfrBufferAccess.reinitialize(buffer); - } finally { - JfrBufferNodeAccess.unlock(node); - } - } - assert locked || flush; - node = node.getNext(); - } - } - - @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") - private void traverseList(JfrBufferList linkedList, boolean flush) { - JfrBufferNode node = linkedList.getHead(); - JfrBufferNode prev = WordFactory.nullPointer(); - - // TEMP (chaeubl): node.getBuffer() should be accessed if the node is locked -> add an - // accessor method. - while (node.isNonNull()) { - boolean locked = JfrBufferNodeAccess.tryLock(node); - if (locked) { - try { - JfrBuffer buffer = node.getBuffer(); - if (buffer.isNull()) { - linkedList.removeNode(node, prev); - /* Don't update prev if we removed the node. */ - } else { - if (flush) { - /* - * I/O operations may be slow, so this flushes to the global buffers - * instead of writing to disk directly. This mitigates the risk of - * acquiring the thread-local buffers for too long. - */ - SubstrateJVM.getGlobalMemory().write(buffer, true); - } else { - write(buffer); - } - prev = node; - } - } finally { - JfrBufferNodeAccess.unlock(node); - } - } - - assert locked || flush; - node = node.getNext(); - } - } - - public long getChunkStartNanos() { - return chunkStartNanos; - } - - public void markChunkFinal() { - assert lock.isOwner(); - isFinal = true; - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 14e43fab6d1a..080d3dcfdd41 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jfr; +import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; @@ -91,7 +92,7 @@ public void teardown() { buffers.teardown(); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Fold public JfrBufferList getBuffers() { return buffers; } @@ -115,7 +116,7 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo boolean shouldSignal; try { - // Copy all committed but not yet flushed memory to the promotion buffer. + /* Copy all committed but not yet flushed memory to the promotion buffer. */ JfrBuffer promotionBuffer = promotionNode.getBuffer(); assert JfrBufferAccess.getAvailableSize(promotionBuffer).aboveOrEqual(unflushedSize); UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(threadLocalBuffer), promotionBuffer.getCommittedPos(), unflushedSize); @@ -126,8 +127,11 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo } JfrBufferAccess.increaseFlushedPos(threadLocalBuffer, unflushedSize); - // Notify the thread that writes the global memory to disk. - // If we're flushing, the global buffers are about to get persisted anyway + + /* + * Notify the thread that writes the global memory to disk. If we're flushing, the global + * buffers are about to get persisted anyway. + */ if (shouldSignal && !streamingFlush) { SubstrateJVM.getRecorderThread().signal(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 46171c0bc8c9..5a2c2311c280 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -62,62 +62,44 @@ public long getMethodId(Class clazz, String methodName, int methodId) { assert methodName != null; assert methodId > 0; - mutex.lockNoTransition(); - try { - return getMethodId0(clazz, methodName, methodId); - } finally { - mutex.unlock(); - } - } - - @Uninterruptible(reason = "Epoch must not change while in this method.") - private long getMethodId0(Class clazz, String methodName, int methodId) { JfrVisited jfrVisited = StackValue.get(JfrVisited.class); jfrVisited.setId(methodId); jfrVisited.setHash(methodId); - JfrMethodEpochData epochData = getEpochData(false); - if (!epochData.visitedMethods.putIfAbsent(jfrVisited)) { - return methodId; - } + mutex.lockNoTransition(); + try { + JfrMethodEpochData epochData = getEpochData(false); + if (!epochData.table.putIfAbsent(jfrVisited)) { + return methodId; + } - if (epochData.methodBuffer.isNull()) { - // This will happen only on the first call. - epochData.methodBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); - } + /* We have a new entry, so serialize it to the buffer. */ + epochData.unflushedEntries++; - JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); - JfrTypeRepository typeRepo = SubstrateJVM.getTypeRepository(); - - JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initialize(data, epochData.methodBuffer); - JfrNativeEventWriter.putLong(data, methodId); - JfrNativeEventWriter.putLong(data, typeRepo.getClassId(clazz)); - JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId(methodName, false)); - /* Dummy value for signature. */ - JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId("()V", false)); - /* Dummy value for modifiers. */ - JfrNativeEventWriter.putShort(data, (short) 0); - /* Dummy value for isHidden. */ - JfrNativeEventWriter.putBoolean(data, false); - JfrNativeEventWriter.commit(data); - - /* The buffer may have been replaced with a new one. */ - epochData.methodBuffer = data.getJfrBuffer(); - epochData.unflushedMethodCount++; - return methodId; - } - - @Uninterruptible(reason = "Locking without transition.") - private void maybeLock(boolean flush) { - if (flush) { - mutex.lockNoTransition(); - } - } + if (epochData.buffer.isNull()) { + epochData.buffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + } - @Uninterruptible(reason = "Locking without transition.") - private void maybeUnlock(boolean flush) { - if (flush) { + JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); + JfrTypeRepository typeRepo = SubstrateJVM.getTypeRepository(); + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.buffer); + JfrNativeEventWriter.putLong(data, methodId); + JfrNativeEventWriter.putLong(data, typeRepo.getClassId(clazz)); + JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId(methodName, false)); + /* Dummy value for signature. */ + JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId("()V", false)); + /* Dummy value for modifiers. */ + JfrNativeEventWriter.putShort(data, (short) 0); + /* Dummy value for isHidden. */ + JfrNativeEventWriter.putBoolean(data, false); + JfrNativeEventWriter.commit(data); + + /* The buffer may have been replaced with a new one. */ + epochData.buffer = data.getJfrBuffer(); + return methodId; + } finally { mutex.unlock(); } } @@ -125,32 +107,23 @@ private void maybeUnlock(boolean flush) { @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { - maybeLock(flush); + mutex.lockNoTransition(); try { JfrMethodEpochData epochData = getEpochData(!flush); - int count = writeMethods(writer, epochData); - if (!flush) { - epochData.clear(); + int count = epochData.unflushedEntries; + if (count == 0) { + return EMPTY; } - return count; - } finally { - maybeUnlock(flush); - } - } - @Uninterruptible(reason = "May write current epoch data.") - private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData) { - int numberOfMethods = epochData.unflushedMethodCount; - if (numberOfMethods == 0) { - return EMPTY; - } + writer.writeCompressedLong(JfrType.Method.getId()); + writer.writeCompressedInt(count); + writer.write(epochData.buffer); - writer.writeCompressedLong(JfrType.Method.getId()); - writer.writeCompressedInt(numberOfMethods); - writer.write(epochData.methodBuffer); - JfrBufferAccess.reinitialize(epochData.methodBuffer); - epochData.unflushedMethodCount = 0; - return NON_EMPTY; + epochData.clear(flush); + return NON_EMPTY; + } finally { + mutex.unlock(); + } } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -160,32 +133,31 @@ private JfrMethodEpochData getEpochData(boolean previousEpoch) { } private static class JfrMethodEpochData { - private JfrBuffer methodBuffer; - private final JfrVisitedTable visitedMethods; - private int unflushedMethodCount; + private final JfrVisitedTable table; + private int unflushedEntries; + private JfrBuffer buffer; @Platforms(Platform.HOSTED_ONLY.class) JfrMethodEpochData() { - this.visitedMethods = new JfrVisitedTable(); - this.unflushedMethodCount = 0; + this.table = new JfrVisitedTable(); + this.unflushedEntries = 0; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - void teardown() { - visitedMethods.teardown(); - if (methodBuffer.isNonNull()) { - JfrBufferAccess.free(methodBuffer); + @Uninterruptible(reason = "May write current epoch data.") + void clear(boolean flush) { + if (!flush) { + table.clear(); } - methodBuffer = WordFactory.nullPointer(); + unflushedEntries = 0; + JfrBufferAccess.reinitialize(buffer); } - @Uninterruptible(reason = "May write current epoch data.") - void clear() { - visitedMethods.clear(); - if (methodBuffer.isNonNull()) { - JfrBufferAccess.reinitialize(methodBuffer); - } - unflushedMethodCount = 0; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void teardown() { + table.teardown(); + unflushedEntries = 0; + JfrBufferAccess.free(buffer); + buffer = WordFactory.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java index e3167829dcfb..9e230ac7124d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrReservedEvent.java @@ -28,8 +28,8 @@ import com.oracle.svm.core.Uninterruptible; public enum JfrReservedEvent { - EVENT_METADATA(0), - EVENT_CHECKPOINT(1); + METADATA(0), + CHECKPOINT(1); private final long id; 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 4d3224fb05d8..3893851b43b6 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 @@ -46,6 +46,7 @@ import com.oracle.svm.core.headers.LibC; import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.jfr.utils.JfrVisited; @@ -65,7 +66,7 @@ public class JfrStackTraceRepository implements JfrConstantPool { private static final int MIN_STACK_DEPTH = 1; private static final int MAX_STACK_DEPTH = 2048; - private int stackTraceDepth = DEFAULT_STACK_DEPTH; + private int stackTraceDepth; private final VMMutex mutex; private final JfrStackTraceEpochData epochData0; @@ -73,19 +74,14 @@ public class JfrStackTraceRepository implements JfrConstantPool { @Platforms(Platform.HOSTED_ONLY.class) JfrStackTraceRepository() { + this.mutex = new VMMutex("jfrStackTraceRepository"); this.epochData0 = new JfrStackTraceEpochData(); this.epochData1 = new JfrStackTraceEpochData(); - this.mutex = new VMMutex("jfrStackTraceRepository"); + this.stackTraceDepth = DEFAULT_STACK_DEPTH; } public void setStackTraceDepth(int value) { - if (value < MIN_STACK_DEPTH) { - stackTraceDepth = MIN_STACK_DEPTH; - } else if (value > MAX_STACK_DEPTH) { - stackTraceDepth = MAX_STACK_DEPTH; - } else { - stackTraceDepth = value; - } + stackTraceDepth = UninterruptibleUtils.Math.clamp(value, MIN_STACK_DEPTH, MAX_STACK_DEPTH); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -93,7 +89,6 @@ public int getStackTraceDepth() { return stackTraceDepth; } - @Uninterruptible(reason = "Releasing repository buffers.") public void teardown() { epochData0.teardown(); epochData1.teardown(); @@ -186,7 +181,7 @@ private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord entry.setSerialized(false); JfrStackTraceEpochData epochData = getEpochData(false); - JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) epochData.visitedStackTraces.get(entry); + JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) epochData.table.get(entry); if (result.isNonNull()) { /* There is an existing stack trace. */ int status = result.getSerialized() ? JfrStackTraceTableEntryStatus.EXISTING_SERIALIZED : JfrStackTraceTableEntryStatus.EXISTING_RAW; @@ -203,7 +198,7 @@ private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord UnmanagedMemoryUtil.copy(start, to, size); entry.setRawStackTrace(to); - JfrStackTraceTableEntry newEntry = (JfrStackTraceTableEntry) epochData.visitedStackTraces.getOrPut(entry); + JfrStackTraceTableEntry newEntry = (JfrStackTraceTableEntry) epochData.table.getOrPut(entry); if (newEntry.isNonNull()) { statusPtr.write(JfrStackTraceTableEntryStatus.INSERTED); return newEntry; @@ -224,54 +219,32 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { mutex.lockNoTransition(); try { entry.setSerialized(true); - getEpochData(false).numberOfSerializedStackTraces++; + getEpochData(false).unflushedEntries++; } finally { mutex.unlock(); } } - @Uninterruptible(reason = "Locking without transition.") - private void maybeLock(boolean flush) { - if (flush) { - mutex.lockNoTransition(); - } - } - - @Uninterruptible(reason = "Locking without transition.") - private void maybeUnlock(boolean flush) { - if (flush) { - mutex.unlock(); - } - } - @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { - maybeLock(flush); + mutex.lockNoTransition(); try { JfrStackTraceEpochData epochData = getEpochData(!flush); - int count = writeStackTraces(writer, epochData); - if (!flush) { - epochData.clear(); + int count = epochData.unflushedEntries; + if (count == 0) { + return EMPTY; } - return count; - } finally { - maybeUnlock(flush); - } - } - @Uninterruptible(reason = "May write current epoch data.") - private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData) { - if (epochData.numberOfSerializedStackTraces == 0) { - return EMPTY; - } + writer.writeCompressedLong(JfrType.StackTrace.getId()); + writer.writeCompressedInt(count); + writer.write(epochData.buffer); - writer.writeCompressedLong(JfrType.StackTrace.getId()); - writer.writeCompressedInt(epochData.numberOfSerializedStackTraces); - writer.write(epochData.stackTraceBuffer); - JfrBufferAccess.reinitialize(epochData.stackTraceBuffer); - epochData.numberOfSerializedStackTraces = 0; - return NON_EMPTY; + epochData.clear(flush); + return NON_EMPTY; + } finally { + mutex.unlock(); + } } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -287,15 +260,15 @@ private JfrStackTraceEpochData getEpochData(boolean previousEpoch) { @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) public JfrBuffer getCurrentBuffer() { JfrStackTraceEpochData epochData = getEpochData(false); - if (epochData.stackTraceBuffer.isNull()) { - epochData.stackTraceBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + if (epochData.buffer.isNull()) { + epochData.buffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } - return epochData.stackTraceBuffer; + return epochData.buffer; } @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) public void setCurrentBuffer(JfrBuffer value) { - getEpochData(false).stackTraceBuffer = value; + getEpochData(false).buffer = value; } /** @@ -373,31 +346,31 @@ public static class JfrStackTraceTableEntryStatus { } private static class JfrStackTraceEpochData { - private JfrBuffer stackTraceBuffer; - private int numberOfSerializedStackTraces; - private final JfrStackTraceTable visitedStackTraces; + private final JfrStackTraceTable table; + private int unflushedEntries; + private JfrBuffer buffer; @Platforms(Platform.HOSTED_ONLY.class) JfrStackTraceEpochData() { - this.visitedStackTraces = new JfrStackTraceTable(); + this.table = new JfrStackTraceTable(); + this.unflushedEntries = 0; } @Uninterruptible(reason = "May write current epoch data.") - void clear() { - visitedStackTraces.clear(); - numberOfSerializedStackTraces = 0; - if (stackTraceBuffer.isNonNull()) { - JfrBufferAccess.reinitialize(stackTraceBuffer); + void clear(boolean flush) { + if (!flush) { + table.clear(); } + unflushedEntries = 0; + JfrBufferAccess.reinitialize(buffer); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) void teardown() { - visitedStackTraces.teardown(); - numberOfSerializedStackTraces = 0; - - JfrBufferAccess.free(stackTraceBuffer); - stackTraceBuffer = WordFactory.nullPointer(); + table.teardown(); + unflushedEntries = 0; + JfrBufferAccess.free(buffer); + buffer = WordFactory.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 0f27b90b31fa..7d332990176f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -48,16 +48,16 @@ */ public class JfrSymbolRepository implements JfrConstantPool { private final VMMutex mutex; - private final CharReplacer dotWithSlash; private final JfrSymbolEpochData epochData0; private final JfrSymbolEpochData epochData1; + private final CharReplacer dotWithSlash; @Platforms(Platform.HOSTED_ONLY.class) public JfrSymbolRepository() { this.mutex = new VMMutex("jfrSymbolRepository"); - this.dotWithSlash = new ReplaceDotWithSlash(); this.epochData0 = new JfrSymbolEpochData(); this.epochData1 = new JfrSymbolEpochData(); + this.dotWithSlash = new ReplaceDotWithSlash(); } public void teardown() { @@ -65,12 +65,6 @@ public void teardown() { epochData1.teardown(); } - @Uninterruptible(reason = "Called by uninterruptible code.") - private JfrSymbolEpochData getEpochData(boolean previousEpoch) { - boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); - return epoch ? epochData0 : epochData1; - } - @Uninterruptible(reason = "Epoch must not change while in this method.") public long getSymbolId(String imageHeapString, boolean previousEpoch) { return getSymbolId(imageHeapString, previousEpoch, false); @@ -110,25 +104,24 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r return 0L; } - /* We have a new symbol, so serialize it to the buffer. */ - epochData.unflushedSymbolCount++; + /* We have a new entry, so serialize it to the buffer. */ + epochData.unflushedEntries++; - if (epochData.symbolBuffer.isNull()) { - epochData.symbolBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + if (epochData.buffer.isNull()) { + epochData.buffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } - CharReplacer charReplacer = replaceDotWithSlash ? dotWithSlash : null; + CharReplacer charReplacer = newEntry.getReplaceDotWithSlash() ? dotWithSlash : null; JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initialize(data, epochData.symbolBuffer); + JfrNativeEventWriterDataAccess.initialize(data, epochData.buffer); JfrNativeEventWriter.putLong(data, newEntry.getId()); JfrNativeEventWriter.putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); - JfrNativeEventWriter.putString(data, imageHeapString, charReplacer); + JfrNativeEventWriter.putString(data, newEntry.getValue(), charReplacer); JfrNativeEventWriter.commit(data); /* The buffer may have been replaced with a new one. */ - epochData.symbolBuffer = data.getJfrBuffer(); - + epochData.buffer = data.getJfrBuffer(); return newEntry.getId(); } finally { mutex.unlock(); @@ -138,36 +131,29 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { - maybeLock(flush); + mutex.lockNoTransition(); try { JfrSymbolEpochData epochData = getEpochData(!flush); - int numberOfSymbols = epochData.unflushedSymbolCount; - if (numberOfSymbols == 0) { + int count = epochData.unflushedEntries; + if (count == 0) { return EMPTY; } + writer.writeCompressedLong(JfrType.Symbol.getId()); - writer.writeCompressedLong(numberOfSymbols); - writer.write(epochData.symbolBuffer); + writer.writeCompressedLong(count); + writer.write(epochData.buffer); epochData.clear(flush); return NON_EMPTY; } finally { - maybeUnlock(flush); - } - } - - @Uninterruptible(reason = "Locking without transition.") - private void maybeLock(boolean flush) { - if (flush) { - mutex.lockNoTransition(); + mutex.unlock(); } } - @Uninterruptible(reason = "Locking without transition.") - private void maybeUnlock(boolean flush) { - if (flush) { - mutex.unlock(); - } + @Uninterruptible(reason = "Called by uninterruptible code.") + private JfrSymbolEpochData getEpochData(boolean previousEpoch) { + boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); + return epoch ? epochData0 : epochData1; } @RawStructure @@ -240,32 +226,30 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry symbolOnStack) { private static class JfrSymbolEpochData { private final JfrSymbolHashtable table; - private JfrBuffer symbolBuffer; - private int unflushedSymbolCount; + private int unflushedEntries; + private JfrBuffer buffer; @Platforms(Platform.HOSTED_ONLY.class) JfrSymbolEpochData() { this.table = new JfrSymbolHashtable(); - this.unflushedSymbolCount = 0; + this.unflushedEntries = 0; } @Uninterruptible(reason = "May write current epoch data.") void clear(boolean flush) { - if (symbolBuffer.isNonNull()) { - JfrBufferAccess.reinitialize(symbolBuffer); - } if (!flush) { - /* The IDs must be stable for the whole epoch, so only clear after epoch change. */ table.clear(); } - unflushedSymbolCount = 0; + unflushedEntries = 0; + JfrBufferAccess.reinitialize(buffer); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) void teardown() { - JfrBufferAccess.free(symbolBuffer); - symbolBuffer = WordFactory.nullPointer(); table.teardown(); + unflushedEntries = 0; + JfrBufferAccess.free(buffer); + buffer = WordFactory.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 2d10e62482f7..6068c4107be7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -111,7 +111,6 @@ public void initialize(long bufferSize) { @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.") @Override - public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) { if (SubstrateJVM.get().isRecording()) { SubstrateJVM.getThreadRepo().registerThread(javaThread); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 5b29da054c32..2720cae781d1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -29,8 +29,8 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.WordFactory; import org.graalvm.word.SignedWord; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; @@ -210,25 +210,24 @@ public int write(JfrChunkWriter writer, boolean flush) { JfrThreadEpochData epochData = getEpochData(!flush); int count = writeThreads(writer, epochData); count += writeThreadGroups(writer, epochData); - return count; } @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord lastCheckpointOffset) { - JfrThreadEpochData epochData = getEpochData(!flush); maybeLock(flush); try { if (epochData.unflushedThreadCount == 0) { return lastCheckpointOffset; } + SignedWord start = writer.beginEvent(); long delta = 0; if (lastCheckpointOffset.greaterOrEqual(0)) { delta = lastCheckpointOffset.subtract(start).rawValue(); } - writer.writeCompressedLong(JfrReservedEvent.EVENT_CHECKPOINT.getId()); + writer.writeCompressedLong(JfrReservedEvent.CHECKPOINT.getId()); writer.writeCompressedLong(JfrTicks.elapsedTicks()); writer.writeCompressedLong(0); // duration writer.writeCompressedLong(delta); @@ -239,7 +238,7 @@ public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord la if (epochData.unflushedThreadGroupCount > 0) { poolCount = 2; } - writer.writeInt(poolCount); + writer.writePaddedInt(poolCount); int actualPoolCount = write(writer, flush); assert poolCount == actualPoolCount; 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 961f6f4da841..3dff84fe5b5a 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 @@ -79,6 +79,12 @@ public final class Target_jdk_jfr_internal_JVM { private static void registerNatives() { } + @Substitute + @TargetElement(onlyWith = JDK17OrLater.class) // + public void markChunkFinal() { + SubstrateJVM.get().markChunkFinal(); + } + /** See {@link JVM#beginRecording}. */ @Substitute public void beginRecording() { @@ -359,6 +365,12 @@ public static boolean flush(Target_jdk_jfr_internal_EventWriter writer, int unco return SubstrateJVM.get().flush(writer, uncommittedSize, requestedSize); } + @Substitute + @TargetElement(onlyWith = JDK17OrLater.class) // + public void flush() { + SubstrateJVM.get().flush(); + } + /** See {@link JVM#setRepositoryLocation}. */ @Substitute public void setRepositoryLocation(String dirText) { @@ -431,12 +443,6 @@ public boolean shouldRotateDisk() { return SubstrateJVM.get().shouldRotateDisk(); } - @Substitute - @TargetElement(onlyWith = JDK17OrLater.class) // - public void flush() { - SubstrateJVM.get().flush(); - } - @Substitute @TargetElement(onlyWith = JDK17OrLater.class) // public void include(Thread thread) { @@ -501,12 +507,6 @@ public boolean isContainerized() { return Containers.isContainerized(); } - @Substitute - @TargetElement(onlyWith = JDK17OrLater.class) // - public void markChunkFinal() { - SubstrateJVM.get().markChunkFinal(); - } - @Substitute @TargetElement(onlyWith = JDK20OrLater.class) // public long hostTotalMemory() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java index 22379473ac18..c662cf034802 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java @@ -24,10 +24,7 @@ */ package com.oracle.svm.core.thread; -import org.graalvm.nativeimage.IsolateThread; - import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.util.VMError; import jdk.internal.misc.Unsafe; @@ -41,21 +38,12 @@ * unexpectedly. */ public final class ThreadData extends UnacquiredThreadData { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - private static final long LOCK_OFFSET; - private static final long UNSAFE_PARK_EVENT_OFFSET; - private static final long SLEEP_PARK_EVENT_OFFSET; + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long LOCK_OFFSET = U.objectFieldOffset(ThreadData.class, "lock"); + private static final long UNSAFE_PARK_EVENT_OFFSET = U.objectFieldOffset(ThreadData.class, "unsafeParkEvent"); + private static final long SLEEP_PARK_EVENT_OFFSET = U.objectFieldOffset(ThreadData.class, "sleepParkEvent"); - static { - try { - LOCK_OFFSET = UNSAFE.objectFieldOffset(ThreadData.class.getDeclaredField("lock")); - UNSAFE_PARK_EVENT_OFFSET = UNSAFE.objectFieldOffset(ThreadData.class.getDeclaredField("unsafeParkEvent")); - SLEEP_PARK_EVENT_OFFSET = UNSAFE.objectFieldOffset(ThreadData.class.getDeclaredField("sleepParkEvent")); - } catch (Throwable ex) { - throw VMError.shouldNotReachHere(ex); - } - } - private volatile IsolateThread lock; + private volatile int lock; private boolean detached; private long refCount; @@ -190,10 +178,10 @@ private void initializeParkEvent(long offset, boolean isSleepEvent) { private boolean tryToStoreParkEvent(long offset, ParkEvent newEvent) { JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); try { - if (UNSAFE.getObject(this, offset) != null) { + if (U.getObject(this, offset) != null) { return false; } - UNSAFE.putObjectVolatile(this, offset, newEvent); + U.putObjectVolatile(this, offset, newEvent); return true; } finally { JavaSpinLockUtils.unlock(this, LOCK_OFFSET); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 2a9d1ac1d23b..43ffb649025a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -118,7 +118,7 @@ private static Positions parserFileHeader(RecordingInput input) throws IOExcepti private static void parseMetadataHeader(RecordingInput input, long metadataPosition) throws IOException { input.position(metadataPosition); // Seek to starting position of metadata region. assertTrue("Metadata size is invalid!", input.readInt() > 0); // Size of metadata. - assertEquals(JfrReservedEvent.EVENT_METADATA.getId(), input.readLong()); + assertEquals(JfrReservedEvent.METADATA.getId(), input.readLong()); assertTrue("Metadata timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. input.readLong(); // Metadata ID. @@ -134,7 +134,7 @@ private static long parseConstantPoolHeader(RecordingInput input, long constantP // Size of constant pools. assertTrue("Constant pool size is invalid!", input.readInt() > 0); // Constant pools region ID. - assertEquals(JfrReservedEvent.EVENT_CHECKPOINT.getId(), input.readLong()); + assertEquals(JfrReservedEvent.CHECKPOINT.getId(), input.readLong()); assertTrue("Constant pool timestamp is invalid!", input.readLong() > 0); // Timestamp. input.readLong(); // Duration. long deltaNext = input.readLong(); // Offset to a next constant pools region. From 274e71e68c6b11c67e66e6c25ad778dc2c033d58 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Fri, 3 Mar 2023 17:46:51 +0100 Subject: [PATCH 60/72] More fixes and refactorings. --- .../svm/core/jdk/UninterruptibleUtils.java | 4 + .../com/oracle/svm/core/jfr/JfrBuffer.java | 43 ++- .../svm/core/jfr/JfrBufferNodeAccess.java | 3 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 90 ++--- .../svm/core/jfr/JfrFrameTypeSerializer.java | 6 +- .../svm/core/jfr/JfrGCCauseSerializer.java | 5 +- .../svm/core/jfr/JfrGCNameSerializer.java | 7 +- .../svm/core/jfr/JfrMethodRepository.java | 11 +- .../JfrMonitorInflationCauseSerializer.java | 6 +- .../svm/core/jfr/JfrNativeEventWriter.java | 2 +- ...frConstantPool.java => JfrRepository.java} | 8 +- .../oracle/svm/core/jfr/JfrSerializer.java | 30 ++ .../svm/core/jfr/JfrSerializerSupport.java | 12 +- .../svm/core/jfr/JfrStackTraceRepository.java | 4 +- .../svm/core/jfr/JfrSymbolRepository.java | 16 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 80 ++--- .../svm/core/jfr/JfrThreadRepository.java | 307 ++++++++---------- .../core/jfr/JfrThreadStateSerializer.java | 6 +- .../svm/core/jfr/JfrTypeRepository.java | 2 +- .../jfr/JfrVMOperationNameSerializer.java | 7 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 4 +- .../test/jfr/TestJfrBufferNodeLinkedList.java | 11 - 22 files changed, 331 insertions(+), 333 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/{JfrConstantPool.java => JfrRepository.java} (88%) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializer.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index 96d17767bf5c..06deed14487a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -451,6 +451,10 @@ public static int numberOfLeadingZeros(long i) { // @formatter:on } // Checkstyle: resume + + public static int hashCode(long value) { + return (int) (value ^ (value >>> 32)); + } } public static class Integer { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index 303832bacf9f..d141c21ddb9a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -36,28 +36,43 @@ /** * A {@link JfrBuffer} is a block of native memory (either thread-local or global) into which JFR - * events are written. It has the following layout: + * data (e.g., events) are written. It has the following layout: * *
    - * Buffer: ---------------------------------------------------------------------
    - *         | header | flushed data | committed data | unflushed data | unused  |
    - *         ---------------------------------------------------------------------
    - *                  |              |                |                          |
    - *              data start    flushed pos     committed pos                 data end
    + * Buffer: --------------------------------------------------------------------
    + *         | header | flushed data | committed data | unflushed data | unused |
    + *         --------------------------------------------------------------------
    + *                  |              |                |                         |
    + *              data start    flushed pos     committed pos                data end
      * 
    * + * The header contains the fields that are defined in the {@link RawStructure} below. The data part + * consists of several sections: *
      *
    • Flushed data has already been flushed to the {@link JfrGlobalMemory global memory} or to the * disk.
    • - *
    • Committed data refers to valid and fully written event data that could be flushed at any + *
    • Committed data refers to fully written, valid event data that can be flushed at any * time.
    • *
    • Unflushed data refers to the data of a JFR event that is currently being written.
    • + * + * Multiple threads may access the same {@link JfrBuffer} concurrently: + *
    • If a thread owns/created a thread-local buffer, then it may access and modify most of that + * buffer's data at any time, without the need for any locking. The flushed position is the only + * exception as it may only be accessed/modified after locking the corresponding + * {@link JfrBufferNode}.
    • + *
    • Accessing a thread-local buffer of another thread is only allowed after locking the + * corresponding {@link JfrBufferNode} (see {@link #getNode()}). This prevents other threads from + * freeing the buffer in meanwhile. The thread that holds the lock may read any field in the buffer + * header and it may also access flushed or committed data (i.e., everything below + * {@link #getCommittedPos()}). It must not modify any header fields, except for the flushed + * position.
    • */ @RawStructure public interface JfrBuffer extends PointerBase { /** - * Returns the size of the buffer. This excludes the header of the buffer. + * Returns the size of the buffer. This excludes the header of the buffer. This field is + * effectively final. */ @RawField UnsignedWord getSize(); @@ -86,19 +101,20 @@ static int offsetOfCommittedPos() { } /** - * Any data before this position was already flushed to some other buffer or to the disk. + * Any data before this position was already flushed to some other buffer or to the disk. Needs + * locking, see JavaDoc at the class level. */ @RawField Pointer getFlushedPos(); /** - * Sets the flushed position. + * Sets the flushed position. Needs locking, see JavaDoc at the class level. */ @RawField void setFlushedPos(Pointer value); /** - * Returns the type of the buffer. + * Returns the type of the buffer. This field is effectively final. */ @RawField @PinnedObjectField @@ -112,8 +128,9 @@ static int offsetOfCommittedPos() { void setBufferType(JfrBufferType value); /** - * Returns the {@link JfrBufferNode} that references this {@link JfrBuffer}. This value may be - * null for {@link JfrBufferType#C_HEAP} buffers. + * Returns the {@link JfrBufferNode} that references this {@link JfrBuffer}. This field is only + * set when a {@link JfrBuffer} was added to a {@link JfrBufferList} (i.e., for + * {@link JfrBufferType#C_HEAP} buffers, this field is usually null). */ @RawField JfrBufferNode getNode(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index f26c3122e6bd..db7b56afbe0e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -81,9 +81,8 @@ public static void unlock(JfrBufferNode node) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isLockedByCurrentThread(JfrBufferNode node) { - assert node.isNonNull(); assert CurrentIsolate.getCurrentThread().isNonNull(); - return node.getLockOwner() == CurrentIsolate.getCurrentThread(); + return node != null && node.getLockOwner() == CurrentIsolate.getCurrentThread(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) 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 8e2823034cba..77361d5ff7ec 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 @@ -74,6 +74,8 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private final VMMutex lock; private final JfrGlobalMemory globalMemory; private final JfrMetadata metadata; + private final JfrRepository[] flushCheckpointRepos; + private final JfrRepository[] threadCheckpointRepos; private final boolean compressedInts; private long notificationThreshold; @@ -90,11 +92,20 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private SignedWord lastCheckpointOffset; @Platforms(Platform.HOSTED_ONLY.class) - public JfrChunkWriter(JfrGlobalMemory globalMemory) { + public JfrChunkWriter(JfrGlobalMemory globalMemory, JfrStackTraceRepository stackTraceRepo, JfrMethodRepository methodRepo, JfrTypeRepository typeRepo, JfrSymbolRepository symbolRepo, + JfrThreadRepository threadRepo) { this.lock = new VMMutex("jfrChunkWriter"); this.globalMemory = globalMemory; this.metadata = new JfrMetadata(null); this.compressedInts = true; + + /* + * Repositories earlier in the write order may reference entries of repositories later in + * the write order. This ordering is required to prevent races during flushing without + * changing epoch. + */ + this.flushCheckpointRepos = new JfrRepository[]{stackTraceRepo, methodRepo, typeRepo, symbolRepo}; + this.threadCheckpointRepos = new JfrRepository[]{threadRepo}; } @Override @@ -177,10 +188,10 @@ public void flush() { assert lock.isOwner(); flushStorage(true); - lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, true, lastCheckpointOffset); - SignedWord constantPoolPosition = writeCheckpointEvent(true); + writeThreadCheckpoint(true); + writeFlushCheckpoint(true); writeMetadataEvent(); - patchFileHeader(constantPoolPosition, true); + patchFileHeader(true); newChunk = false; } @@ -208,10 +219,10 @@ public void closeFile() { * persisted to a file without a safepoint. */ - lastCheckpointOffset = SubstrateJVM.getThreadRepo().maybeWrite(this, false, lastCheckpointOffset); - SignedWord constantPoolPosition = writeCheckpointEvent(false); + writeThreadCheckpoint(false); + writeFlushCheckpoint(false); writeMetadataEvent(); - patchFileHeader(constantPoolPosition, false); + patchFileHeader(false); getFileSupport().close(fd); filename = null; @@ -237,9 +248,10 @@ private void writeFileHeader() { getFileSupport().writeShort(fd, computeHeaderFlags()); } - private void patchFileHeader(SignedWord constantPoolPosition, boolean flush) { + private void patchFileHeader(boolean flush) { assert lock.isOwner(); assert metadataPosition.greaterThan(0); + assert lastCheckpointOffset.greaterThan(0); byte generation = flush ? getAndIncrementGeneration() : COMPLETE; SignedWord currentPos = getFileSupport().position(fd); @@ -248,7 +260,7 @@ private void patchFileHeader(SignedWord constantPoolPosition, boolean flush) { getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); getFileSupport().writeLong(fd, chunkSize); - getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); + getFileSupport().writeLong(fd, lastCheckpointOffset.rawValue()); getFileSupport().writeLong(fd, metadataPosition.rawValue()); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); @@ -282,25 +294,33 @@ private byte getAndIncrementGeneration() { return generation++; } - private SignedWord writeCheckpointEvent(boolean flush) { + private void writeFlushCheckpoint(boolean flush) { + // TEMP (chaeubl): this should also check if there is any data. + writeCheckpointEvent(JfrCheckpointType.Flush, flushCheckpointRepos, newChunk, flush); + } + + private void writeThreadCheckpoint(boolean flush) { + assert threadCheckpointRepos.length == 1 && threadCheckpointRepos[0] == SubstrateJVM.getThreadRepo(); + if (SubstrateJVM.getThreadRepo().hasUnflushedData()) { + writeCheckpointEvent(JfrCheckpointType.Threads, threadCheckpointRepos, false, flush); + } + } + + private void writeCheckpointEvent(JfrCheckpointType type, JfrRepository[] repositories, boolean writeSerializers, boolean flush) { assert lock.isOwner(); - SignedWord start = beginEvent(); - long deltaToNextCheckpoint = getDeltaToNextCheckpoint(start); + SignedWord start = beginEvent(); writeCompressedLong(JfrReservedEvent.CHECKPOINT.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration - writeCompressedLong(deltaToNextCheckpoint); - writeByte(JfrCheckpointType.Flush.getId()); + writeCompressedLong(getDeltaToLastCheckpoint(start)); + writeByte(type.getId()); SignedWord poolCountPos = getFileSupport().position(fd); - getFileSupport().writeInt(fd, 0); // pool count (patched later) + getFileSupport().writeInt(fd, 0); // pool count (patched below) - int poolCount = 0; - if (newChunk) { - poolCount += writeSerializers(flush); - } - poolCount += writeRepositories(flush); + int poolCount = writeSerializers ? writeSerializers() : 0; + poolCount += writeConstantPools(repositories, flush); SignedWord currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); @@ -310,35 +330,27 @@ private SignedWord writeCheckpointEvent(boolean flush) { endEvent(start); lastCheckpointOffset = start; - return start; } - private long getDeltaToNextCheckpoint(SignedWord startOfNewCheckpoint) { + private long getDeltaToLastCheckpoint(SignedWord startOfNewCheckpoint) { if (lastCheckpointOffset.lessThan(0)) { return 0L; - } else { - return lastCheckpointOffset.subtract(startOfNewCheckpoint).rawValue(); } + return lastCheckpointOffset.subtract(startOfNewCheckpoint).rawValue(); } - /** - * Repositories earlier in the write order may reference entries of repositories later in the - * write order. This ordering is required to prevent races during flushing without changing - * epoch. - */ - private int writeRepositories(boolean flush) { - int count = 0; - count += SubstrateJVM.getStackTraceRepo().write(this, flush); - count += SubstrateJVM.getMethodRepo().write(this, flush); - count += SubstrateJVM.getTypeRepository().write(this, flush); - count += SubstrateJVM.getSymbolRepository().write(this, flush); - return count; + private int writeSerializers() { + JfrSerializer[] serializers = JfrSerializerSupport.get().getSerializers(); + for (JfrSerializer serializer : serializers) { + serializer.write(this); + } + return serializers.length; } - private int writeSerializers(boolean flush) { + private int writeConstantPools(JfrRepository[] repositories, boolean flush) { int poolCount = 0; - for (JfrConstantPool constantPool : JfrSerializerSupport.get().getSerializers()) { - poolCount += constantPool.write(this, flush); + for (JfrRepository repo : repositories) { + poolCount += repo.write(this, flush); } return poolCount; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java index b857ecb7e880..9bb9d56a516c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFrameTypeSerializer.java @@ -30,21 +30,19 @@ /** * Used to serialize all predefined frame types into the chunk. */ -public class JfrFrameTypeSerializer implements JfrConstantPool { +public class JfrFrameTypeSerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrFrameTypeSerializer() { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public void write(JfrChunkWriter writer) { writer.writeCompressedLong(JfrType.FrameType.getId()); - JfrFrameType[] values = JfrFrameType.values(); writer.writeCompressedLong(values.length); for (int i = 0; i < values.length; i++) { writer.writeCompressedInt(i); writer.writeString(values[i].getText()); } - return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java index 9f7102afdd09..d01a5e4f4f17 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCCauseSerializer.java @@ -29,13 +29,13 @@ import com.oracle.svm.core.heap.GCCause; -public class JfrGCCauseSerializer implements JfrConstantPool { +public class JfrGCCauseSerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrGCCauseSerializer() { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public void write(JfrChunkWriter writer) { // GCCauses has null entries GCCause[] causes = GCCause.getGCCauses(); int nonNullItems = 0; @@ -55,6 +55,5 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeString(cause.getName()); } } - return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java index 1b66e31f04cb..2aee2f3e1dd0 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGCNameSerializer.java @@ -27,22 +27,19 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -public class JfrGCNameSerializer implements JfrConstantPool { +public class JfrGCNameSerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrGCNameSerializer() { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public void write(JfrChunkWriter writer) { JfrGCName[] gcNames = JfrGCNames.singleton().getNames(); - assert gcNames != null && gcNames.length > 0; - writer.writeCompressedLong(JfrType.GCName.getId()); writer.writeCompressedLong(gcNames.length); for (JfrGCName name : gcNames) { writer.writeCompressedLong(name.getId()); writer.writeString(name.getName()); } - return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 5a2c2311c280..cf055f24d175 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -38,7 +38,7 @@ /** * Repository that collects and writes used methods. */ -public class JfrMethodRepository implements JfrConstantPool { +public class JfrMethodRepository implements JfrRepository { private final VMMutex mutex; private final JfrMethodEpochData epochData0; private final JfrMethodEpochData epochData1; @@ -73,9 +73,7 @@ public long getMethodId(Class clazz, String methodName, int methodId) { return methodId; } - /* We have a new entry, so serialize it to the buffer. */ - epochData.unflushedEntries++; - + /* New entry, so serialize it to the buffer. */ if (epochData.buffer.isNull()) { epochData.buffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } @@ -94,8 +92,11 @@ public long getMethodId(Class clazz, String methodName, int methodId) { JfrNativeEventWriter.putShort(data, (short) 0); /* Dummy value for isHidden. */ JfrNativeEventWriter.putBoolean(data, false); - JfrNativeEventWriter.commit(data); + if (!JfrNativeEventWriter.commit(data)) { + return methodId; + } + epochData.unflushedEntries++; /* The buffer may have been replaced with a new one. */ epochData.buffer = data.getJfrBuffer(); return methodId; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java index f3c21535e0fb..4d71137a41ff 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java @@ -31,13 +31,13 @@ import com.oracle.svm.core.monitor.MonitorInflationCause; -public class JfrMonitorInflationCauseSerializer implements JfrConstantPool { +public class JfrMonitorInflationCauseSerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrMonitorInflationCauseSerializer() { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public void write(JfrChunkWriter writer) { writer.writeCompressedLong(JfrType.MonitorInflationCause.getId()); MonitorInflationCause[] inflationCauses = MonitorInflationCause.values(); @@ -46,7 +46,5 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedInt(i); writer.writeString(inflationCauses[i].getText()); } - - return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index bbe659768908..0660cb798630 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -302,7 +302,7 @@ private static JfrBuffer accommodate0(JfrNativeEventWriterData data, UnsignedWor JfrBuffer oldBuffer = data.getJfrBuffer(); switch (oldBuffer.getBufferType()) { case THREAD_LOCAL_NATIVE: - return JfrThreadLocal.flush(oldBuffer, uncommitted, requested); + return JfrThreadLocal.flushToGlobalMemory(oldBuffer, uncommitted, requested); case C_HEAP: return reuseOrReallocateBuffer(oldBuffer, uncommitted, requested); default: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java similarity index 88% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java index 2560ef3cf057..d5e217bae41e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java @@ -32,16 +32,16 @@ * {@link Uninterruptible} to guarantee that a safepoint always sees a consistent state. Otherwise, * other JFR code could see partially added data when it tries to iterate the data at a safepoint. */ -public interface JfrConstantPool { +public interface JfrRepository { /** - * If constant pool is empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} - * function returns this value. + * If constant pool is empty, the {@link JfrRepository#write(JfrChunkWriter, boolean)} function + * returns this value. */ int EMPTY = 0; /** - * If constant pool is not empty, the {@link JfrConstantPool#write(JfrChunkWriter, boolean)} + * If constant pool is not empty, the {@link JfrRepository#write(JfrChunkWriter, boolean)} * function returns this value. */ int NON_EMPTY = 1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializer.java new file mode 100644 index 000000000000..0e0a58640fad --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializer.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. 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.core.jfr; + +/** Serializers are only written upon a new chunk. */ +public interface JfrSerializer { + void write(JfrChunkWriter writer); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java index 3e150130d40d..b3a7b983307f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java @@ -32,19 +32,19 @@ import org.graalvm.nativeimage.Platforms; /** - * Support for registering and querying {@link JfrConstantPool}s that serialize data. Serializers - * are only written upon a new chunk. + * Support for registering and querying {@link JfrRepository}s that serialize data. Serializers are + * only written upon a new chunk. */ public class JfrSerializerSupport { - private JfrConstantPool[] serializers; + private JfrSerializer[] serializers; @Platforms(Platform.HOSTED_ONLY.class) public JfrSerializerSupport() { - serializers = new JfrConstantPool[0]; + serializers = new JfrSerializer[0]; } @Platforms(Platform.HOSTED_ONLY.class) - public synchronized void register(JfrConstantPool serializer) { + public synchronized void register(JfrSerializer serializer) { assert serializer != null; int oldLength = serializers.length; // We expect a very small number of serializers, so only increase the size by 1. @@ -57,7 +57,7 @@ public static JfrSerializerSupport get() { return ImageSingletons.lookup(JfrSerializerSupport.class); } - public JfrConstantPool[] getSerializers() { + public JfrSerializer[] getSerializers() { return serializers; } } 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 3893851b43b6..4831dd8dda80 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 @@ -61,7 +61,7 @@ /** * Repository that collects all metadata about stacktraces. */ -public class JfrStackTraceRepository implements JfrConstantPool { +public class JfrStackTraceRepository implements JfrRepository { private static final int DEFAULT_STACK_DEPTH = 64; private static final int MIN_STACK_DEPTH = 1; private static final int MAX_STACK_DEPTH = 2048; @@ -219,6 +219,8 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { mutex.lockNoTransition(); try { entry.setSerialized(true); + // TEMP (chaeubl): doesn't this cause issues? the buffer is written elsewhere -> + // reinitialize would destroy data getEpochData(false).unflushedEntries++; } finally { mutex.unlock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 7d332990176f..77d480e16e38 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -39,6 +39,7 @@ import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.locks.VMMutex; @@ -46,7 +47,7 @@ /** * In Native Image, we use {@link java.lang.String} objects that live in the image heap as symbols. */ -public class JfrSymbolRepository implements JfrConstantPool { +public class JfrSymbolRepository implements JfrRepository { private final VMMutex mutex; private final JfrSymbolEpochData epochData0; private final JfrSymbolEpochData epochData1; @@ -83,8 +84,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r symbol.setReplaceDotWithSlash(replaceDotWithSlash); long rawPointerValue = Word.objectToUntrackedPointer(imageHeapString).rawValue(); - int hashcode = (int) (rawPointerValue ^ (rawPointerValue >>> 32)); - symbol.setHash(hashcode); + symbol.setHash(UninterruptibleUtils.Long.hashCode(rawPointerValue)); /* * Get an existing entry from the hashtable or insert a new entry. This needs to be atomic @@ -104,9 +104,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r return 0L; } - /* We have a new entry, so serialize it to the buffer. */ - epochData.unflushedEntries++; - + /* New entry, so serialize it to the buffer. */ if (epochData.buffer.isNull()) { epochData.buffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } @@ -118,8 +116,11 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r JfrNativeEventWriter.putLong(data, newEntry.getId()); JfrNativeEventWriter.putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); JfrNativeEventWriter.putString(data, newEntry.getValue(), charReplacer); - JfrNativeEventWriter.commit(data); + if (!JfrNativeEventWriter.commit(data)) { + return 0L; + } + epochData.unflushedEntries++; /* The buffer may have been replaced with a new one. */ epochData.buffer = data.getJfrBuffer(); return newEntry.getId(); @@ -244,7 +245,6 @@ void clear(boolean flush) { JfrBufferAccess.reinitialize(buffer); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) void teardown() { table.teardown(); unflushedEntries = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 6068c4107be7..e7108d1d1e4e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -109,6 +109,16 @@ public void initialize(long bufferSize) { this.threadLocalBufferSize = bufferSize; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getThreadLocalBufferSize() { + return threadLocalBufferSize; + } + + public void teardown() { + getNativeBufferList().teardown(); + getJavaBufferList().teardown(); + } + @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.") @Override public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) { @@ -136,8 +146,8 @@ public static void stopRecording(IsolateThread isolateThread) { JfrBufferNode nativeNode = nativeBufferNode.get(isolateThread); nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); - flushAndFreeBuffer(javaNode); - flushAndFreeBuffer(nativeNode); + flushToGlobalMemoryAndFreeBuffer(javaNode); + flushToGlobalMemoryAndFreeBuffer(nativeNode); /* Clear the other event-related thread-locals. */ dataLost.set(isolateThread, WordFactory.unsigned(0)); @@ -156,32 +166,46 @@ public static void stopRecording(IsolateThread isolateThread) { } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void flushAndFreeBuffer(JfrBufferNode node) { + private static void flushToGlobalMemoryAndFreeBuffer(JfrBufferNode node) { if (node.isNull()) { return; } - /* Free the JFRBuffer but leave the node alive as it still needed. */ + /* Free the buffer but leave the node alive as it still needed. */ JfrBufferNodeAccess.lock(node); try { JfrBuffer buffer = node.getBuffer(); node.setBuffer(WordFactory.nullPointer()); - flush0(buffer, WordFactory.unsigned(0), 0); + flushToGlobalMemory0(buffer, WordFactory.unsigned(0), 0); JfrBufferAccess.free(buffer); } finally { JfrBufferNodeAccess.unlock(node); } } - public void teardown() { - getNativeBufferList().teardown(); - getJavaBufferList().teardown(); + /** + * This method excludes/includes a thread from JFR (emitting events and sampling). At the + * moment, only the current thread may be excluded/included. This is a difference to HotSpot + * that we will need to address at some point. + */ + public void setExcluded(Thread thread, boolean excluded) { + if (!thread.equals(Thread.currentThread())) { + return; + } + IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); + Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); + tjlt.jfrExcluded = excluded; + + if (javaEventWriter.get(currentIsolateThread) != null && !JavaThreads.isVirtual(thread)) { + javaEventWriter.get(currentIsolateThread).excluded = excluded; + } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public long getThreadLocalBufferSize() { - return threadLocalBufferSize; + @Uninterruptible(reason = "Called from uninterruptible code.") + public boolean isCurrentThreadExcluded() { + Target_java_lang_Thread tjlt = SubstrateUtil.cast(Thread.currentThread(), Target_java_lang_Thread.class); + return tjlt.jfrExcluded; } public Target_jdk_jfr_internal_EventWriter getEventWriter() { @@ -264,22 +288,26 @@ public static void notifyEventWriter(IsolateThread thread) { } @Uninterruptible(reason = "Accesses a JFR buffer.") - public static JfrBuffer flush(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { + public static JfrBuffer flushToGlobalMemory(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { assert buffer.isNonNull(); assert JfrBufferAccess.isThreadLocal(buffer); + assert buffer.getNode().isNonNull(); /* Acquire the buffer because a streaming flush could be in progress. */ JfrBufferNode node = buffer.getNode(); JfrBufferNodeAccess.lock(node); try { - return flush0(buffer, uncommitted, requested); + return flushToGlobalMemory0(buffer, uncommitted, requested); } finally { JfrBufferNodeAccess.unlock(node); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static JfrBuffer flush0(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { + private static JfrBuffer flushToGlobalMemory0(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { + assert buffer.isNonNull(); + assert JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.aboveThan(0)) { if (!SubstrateJVM.getGlobalMemory().write(buffer, unflushedSize, false)) { @@ -326,30 +354,6 @@ private static UnsignedWord increaseDataLost(UnsignedWord delta) { return result; } - /** - * This method excludes/includes a thread from JFR (emitting events and sampling). Unlike in - * hotspot, only the current thread may be excluded/included. TODO: possibly modify this method - * to match hotspot behaviour. - */ - public void setExcluded(Thread thread, boolean excluded) { - if (!thread.equals(Thread.currentThread())) { - return; - } - IsolateThread currentIsolateThread = CurrentIsolate.getCurrentThread(); - Target_java_lang_Thread tjlt = SubstrateUtil.cast(thread, Target_java_lang_Thread.class); - tjlt.jfrExcluded = excluded; - - if (javaEventWriter.get(currentIsolateThread) != null && !JavaThreads.isVirtual(thread)) { - javaEventWriter.get(currentIsolateThread).excluded = excluded; - } - } - - @Uninterruptible(reason = "Called from uninterruptible code.") - public boolean isCurrentThreadExcluded() { - Target_java_lang_Thread tjlt = SubstrateUtil.cast(Thread.currentThread(), Target_java_lang_Thread.class); - return tjlt.jfrExcluded; - } - @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true) public static SamplerBuffer getSamplerBuffer() { return getSamplerBuffer(CurrentIsolate.getCurrentThread()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 2720cae781d1..6127ec092a55 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -29,10 +29,10 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.SignedWord; import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.jfr.utils.JfrVisited; import com.oracle.svm.core.jfr.utils.JfrVisitedTable; @@ -42,240 +42,188 @@ import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.util.VMError; /** * Repository that collects all metadata about threads and thread groups. */ -public final class JfrThreadRepository { +public final class JfrThreadRepository implements JfrRepository { + private static final int VIRTUAL_THREAD_GROUP_ID = 1; + private final VMMutex mutex; private final JfrThreadEpochData epochData0; private final JfrThreadEpochData epochData1; @Platforms(Platform.HOSTED_ONLY.class) JfrThreadRepository() { + this.mutex = new VMMutex("jfrThreadRepository"); this.epochData0 = new JfrThreadEpochData(); this.epochData1 = new JfrThreadEpochData(); - this.mutex = new VMMutex("jfrThreadRepository"); + } + + public void teardown() { + epochData0.teardown(); + epochData1.teardown(); } @Uninterruptible(reason = "Prevent any JFR events from triggering.") public void registerRunningThreads() { assert VMOperation.isInProgressAtSafepoint(); - mutex.lockNoTransition(); - try { - for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { - // IsolateThreads without a Java thread just started executing and will register - // themselves later on. - Thread thread = PlatformThreads.fromVMThread(isolateThread); - if (thread != null) { - registerThread0(thread); - } + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + /* + * IsolateThreads without a Java thread just started executing and will register + * themselves later on. + */ + Thread thread = PlatformThreads.fromVMThread(isolateThread); + if (thread != null) { + registerThread(thread); } - } finally { - mutex.unlock(); } } @Uninterruptible(reason = "Epoch must not change while in this method.") public void registerThread(Thread thread) { + long threadId = JavaThreads.getThreadId(thread); + + JfrVisited visitedThread = StackValue.get(JfrVisited.class); + visitedThread.setId(threadId); + visitedThread.setHash(UninterruptibleUtils.Long.hashCode(threadId)); + mutex.lockNoTransition(); try { - registerThread0(thread); + JfrThreadEpochData epochData = getEpochData(false); + if (!epochData.threadTable.putIfAbsent(visitedThread)) { + return; + } + + /* New thread, so serialize it to the buffer. */ + if (epochData.threadBuffer.isNull()) { + epochData.threadBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + } + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); + + /* Similar to JfrThreadConstant::serialize in HotSpot. */ + boolean isVirtual = JavaThreads.isVirtual(thread); + long osThreadId = isVirtual ? 0 : threadId; + long threadGroupId = registerThreadGroup(thread, isVirtual); + + JfrNativeEventWriter.putLong(data, threadId); + JfrNativeEventWriter.putString(data, thread.getName()); // OS thread name + JfrNativeEventWriter.putLong(data, osThreadId); // OS thread id + JfrNativeEventWriter.putString(data, thread.getName()); // Java thread name + JfrNativeEventWriter.putLong(data, threadId); // Java thread id + JfrNativeEventWriter.putLong(data, threadGroupId); // Java thread group + if (JavaVersionUtil.JAVA_SPEC >= 19) { + JfrNativeEventWriter.putBoolean(data, isVirtual); + } + if (!JfrNativeEventWriter.commit(data)) { + return; + } + + epochData.unflushedThreadCount++; + /* The buffer may have been replaced with a new one. */ + epochData.threadBuffer = data.getJfrBuffer(); } finally { mutex.unlock(); } } @Uninterruptible(reason = "Epoch must not change while in this method.") - private void registerThread0(Thread thread) { - JfrThreadEpochData epochData = getEpochData(false); - if (epochData.threadBuffer.isNull()) { - // This will happen only on the first call. - epochData.threadBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); - } - - JfrVisited visitedThread = StackValue.get(JfrVisited.class); - visitedThread.setId(JavaThreads.getThreadId(thread)); - visitedThread.setHash((int) JavaThreads.getThreadId(thread)); - if (!epochData.visitedThreads.putIfAbsent(visitedThread)) { - return; - } - - epochData.unflushedThreadCount++; - - JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); - - // needs to be in sync with JfrThreadConstant::serialize - boolean isVirtual = JavaThreads.isVirtual(thread); - long osThreadId = isVirtual ? 0 : JavaThreads.getThreadId(thread); - ThreadGroup threadGroup = thread.getThreadGroup(); - long threadGroupId = getThreadGroupId(isVirtual, threadGroup); - - JfrNativeEventWriter.putLong(data, JavaThreads.getThreadId(thread)); // JFR trace id - JfrNativeEventWriter.putString(data, thread.getName()); // Java or native thread name - JfrNativeEventWriter.putLong(data, osThreadId); // OS thread id - JfrNativeEventWriter.putString(data, thread.getName()); // Java thread name - JfrNativeEventWriter.putLong(data, JavaThreads.getThreadId(thread)); // Java thread id - JfrNativeEventWriter.putLong(data, threadGroupId); // Java thread group - if (JavaVersionUtil.JAVA_SPEC >= 19) { - JfrNativeEventWriter.putBoolean(data, isVirtual); // isVirtual - } - if (!isVirtual && threadGroup != null) { - registerThreadGroup(threadGroupId, threadGroup); + private long registerThreadGroup(Thread thread, boolean isVirtual) { + if (isVirtual) { + /* For virtual threads, a fixed thread group id is reserved. */ + return VIRTUAL_THREAD_GROUP_ID; } - JfrNativeEventWriter.commit(data); - - // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we - // need to update the repository pointer as well. - epochData.threadBuffer = data.getJfrBuffer(); + return registerThreadGroup0(thread.getThreadGroup()); } - @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true) - private static long getThreadGroupId(boolean isVirtual, ThreadGroup threadGroup) { - if (isVirtual) { - // java thread group - VirtualThread threadgroup reserved id 1 - return 1; - } else if (threadGroup == null) { + @Uninterruptible(reason = "Epoch must not change while in this method.") + private long registerThreadGroup0(ThreadGroup threadGroup) { + if (threadGroup == null) { return 0; - } else { - return JavaLangThreadGroupSubstitutions.getThreadGroupId(threadGroup); } - } - @Uninterruptible(reason = "Epoch must not change while in this method.") - private void registerThreadGroup(long threadGroupId, ThreadGroup threadGroup) { - VMError.guarantee(mutex.isOwner(), "The current thread is not the owner of the mutex!"); + long threadGroupId = JavaLangThreadGroupSubstitutions.getThreadGroupId(threadGroup); + JfrVisited jfrVisited = StackValue.get(JfrVisited.class); + jfrVisited.setId(threadGroupId); + jfrVisited.setHash(UninterruptibleUtils.Long.hashCode(threadGroupId)); JfrThreadEpochData epochData = getEpochData(false); + if (!epochData.threadGroupTable.putIfAbsent(jfrVisited)) { + return threadGroupId; + } + + /* New thread group, so serialize it to the buffer. */ if (epochData.threadGroupBuffer.isNull()) { - // This will happen only on the first call. epochData.threadGroupBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } - JfrVisited jfrVisited = StackValue.get(JfrVisited.class); - jfrVisited.setId(threadGroupId); - jfrVisited.setHash((int) threadGroupId); - if (!epochData.visitedThreadGroups.putIfAbsent(jfrVisited)) { - return; - } - epochData.unflushedThreadGroupCount++; + ThreadGroup parentThreadGroup = JavaLangThreadGroupSubstitutions.getParentThreadGroupUnsafe(threadGroup); + long parentThreadGroupId = registerThreadGroup0(parentThreadGroup); JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initialize(data, epochData.threadGroupBuffer); - JfrNativeEventWriter.putLong(data, threadGroupId); - ThreadGroup parentThreadGroup = JavaLangThreadGroupSubstitutions.getParentThreadGroupUnsafe(threadGroup); - long parentThreadGroupId = 0; - if (parentThreadGroup != null) { - parentThreadGroupId = JavaLangThreadGroupSubstitutions.getThreadGroupId(parentThreadGroup); - } + JfrNativeEventWriter.putLong(data, threadGroupId); JfrNativeEventWriter.putLong(data, parentThreadGroupId); JfrNativeEventWriter.putString(data, threadGroup.getName()); - JfrNativeEventWriter.commit(data); - - // Maybe during writing, the thread group buffer was replaced with a new (larger) one, so we - // need to update the repository pointer as well. - epochData.threadGroupBuffer = data.getJfrBuffer(); - - if (parentThreadGroupId > 0) { - // Parent is not null, need to visit him as well. - registerThreadGroup(parentThreadGroupId, parentThreadGroup); + if (!JfrNativeEventWriter.commit(data)) { + return threadGroupId; } - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private JfrThreadEpochData getEpochData(boolean previousEpoch) { - boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); - return epoch ? epochData0 : epochData1; - } - - @Uninterruptible(reason = "Locking without transition.") - private void maybeLock(boolean flush) { - if (flush) { - mutex.lockNoTransition(); - } + epochData.unflushedThreadGroupCount++; + /* The buffer may have been replaced with a new one. */ + epochData.threadGroupBuffer = data.getJfrBuffer(); + return threadGroupId; } - @Uninterruptible(reason = "Locking without transition.") - private void maybeUnlock(boolean flush) { - if (flush) { + @Uninterruptible(reason = "Lock without transition.") + public boolean hasUnflushedData() { + mutex.lockNoTransition(); + try { + JfrThreadEpochData epochData = getEpochData(false); + return epochData.unflushedThreadCount == 0 && epochData.unflushedThreadGroupCount == 0; + } finally { mutex.unlock(); } } - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") + @Override + @Uninterruptible(reason = "May write current epoch data.") public int write(JfrChunkWriter writer, boolean flush) { - JfrThreadEpochData epochData = getEpochData(!flush); - int count = writeThreads(writer, epochData); - count += writeThreadGroups(writer, epochData); - return count; - } - - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") - public SignedWord maybeWrite(JfrChunkWriter writer, boolean flush, SignedWord lastCheckpointOffset) { - JfrThreadEpochData epochData = getEpochData(!flush); - maybeLock(flush); + mutex.lockNoTransition(); try { - if (epochData.unflushedThreadCount == 0) { - return lastCheckpointOffset; - } - - SignedWord start = writer.beginEvent(); - long delta = 0; - if (lastCheckpointOffset.greaterOrEqual(0)) { - delta = lastCheckpointOffset.subtract(start).rawValue(); - } - writer.writeCompressedLong(JfrReservedEvent.CHECKPOINT.getId()); - writer.writeCompressedLong(JfrTicks.elapsedTicks()); - writer.writeCompressedLong(0); // duration - writer.writeCompressedLong(delta); - writer.writeByte(JfrCheckpointType.Threads.getId()); - - // If only writing threads pool, count is 1 - int poolCount = 1; - if (epochData.unflushedThreadGroupCount > 0) { - poolCount = 2; - } - writer.writePaddedInt(poolCount); - - int actualPoolCount = write(writer, flush); - assert poolCount == actualPoolCount; - - writer.endEvent(start); - return start; + JfrThreadEpochData epochData = getEpochData(!flush); + int count = writeThreads(writer, epochData); + count += writeThreadGroups(writer, epochData); + epochData.clear(flush); + return count; } finally { - maybeUnlock(flush); - /* - * Thread repository epoch data may be cleared after a flush because threads are only - * registered once with fixed IDs. There is no need to do a lookup in this repo for a - * thread's ID. However, we preserve visitedThreads and visitedThreadGroups until the - * epoch change to avoid unnecessary re-registrations. - */ - if (!flush) { - epochData.clear(); - } + mutex.unlock(); } } @Uninterruptible(reason = "May write current epoch data.") private static int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { - VMError.guarantee(epochData.unflushedThreadCount > 0, "Thread repository must not be empty."); + int threadCount = epochData.unflushedThreadCount; + if (threadCount == 0) { + return JfrRepository.EMPTY; + } writer.writeCompressedLong(JfrType.Thread.getId()); writer.writeCompressedInt(epochData.unflushedThreadCount); writer.write(epochData.threadBuffer); JfrBufferAccess.reinitialize(epochData.threadBuffer); epochData.unflushedThreadCount = 0; - return JfrConstantPool.NON_EMPTY; + return JfrRepository.NON_EMPTY; } @Uninterruptible(reason = "May write current epoch data.") private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { int threadGroupCount = epochData.unflushedThreadGroupCount; if (threadGroupCount == 0) { - return JfrConstantPool.EMPTY; + return JfrRepository.EMPTY; } writer.writeCompressedLong(JfrType.ThreadGroup.getId()); @@ -283,51 +231,56 @@ private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData e writer.write(epochData.threadGroupBuffer); JfrBufferAccess.reinitialize(epochData.threadGroupBuffer); epochData.unflushedThreadGroupCount = 0; - return JfrConstantPool.NON_EMPTY; + return JfrRepository.NON_EMPTY; } - @Uninterruptible(reason = "Releasing repository buffers.") - public void teardown() { - epochData0.teardown(); - epochData1.teardown(); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private JfrThreadEpochData getEpochData(boolean previousEpoch) { + boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); + return epoch ? epochData0 : epochData1; } private static class JfrThreadEpochData { /* * We need to keep track of the threads because it is not guaranteed that registerThread is - * only invoked once per thread (there can be races when re-registering already running - * threads). + * only invoked once per thread (there can be races where we might re-register already + * running threads). */ - private final JfrVisitedTable visitedThreads; - private final JfrVisitedTable visitedThreadGroups; + private final JfrVisitedTable threadTable; + private final JfrVisitedTable threadGroupTable; private int unflushedThreadCount; private int unflushedThreadGroupCount; - private JfrBuffer threadBuffer; private JfrBuffer threadGroupBuffer; @Platforms(Platform.HOSTED_ONLY.class) JfrThreadEpochData() { - this.visitedThreads = new JfrVisitedTable(); - this.visitedThreadGroups = new JfrVisitedTable(); + this.threadTable = new JfrVisitedTable(); + this.threadGroupTable = new JfrVisitedTable(); this.unflushedThreadCount = 0; this.unflushedThreadGroupCount = 0; } @Uninterruptible(reason = "May write current epoch data.") - public void clear() { - visitedThreads.clear(); - visitedThreadGroups.clear(); + void clear(boolean flush) { + if (!flush) { + threadTable.clear(); + threadGroupTable.clear(); + } + unflushedThreadCount = 0; unflushedThreadGroupCount = 0; + JfrBufferAccess.reinitialize(threadBuffer); JfrBufferAccess.reinitialize(threadGroupBuffer); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void teardown() { - visitedThreads.teardown(); - visitedThreadGroups.teardown(); + void teardown() { + threadTable.teardown(); + threadGroupTable.teardown(); + + unflushedThreadCount = 0; + unflushedThreadGroupCount = 0; JfrBufferAccess.free(threadBuffer); threadBuffer = WordFactory.nullPointer(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java index 93d252b7892e..ba05a2f7cc7c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadStateSerializer.java @@ -30,14 +30,14 @@ /** * Used to serialize all possible thread states into the chunk. */ -public class JfrThreadStateSerializer implements JfrConstantPool { +public class JfrThreadStateSerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrThreadStateSerializer() { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public void write(JfrChunkWriter writer) { writer.writeCompressedLong(JfrType.ThreadState.getId()); JfrThreadState[] threadStates = JfrThreadState.values(); @@ -46,7 +46,5 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedInt(i); writer.writeString(threadStates[i].getText()); } - - return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 20fa1df339b9..ab45d822c95b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -46,7 +46,7 @@ * * The "epoch" maps hold records with respect to a specific epoch and are reset at an epoch change. */ -public class JfrTypeRepository implements JfrConstantPool { +public class JfrTypeRepository implements JfrRepository { private final Set> epochClasses = new HashSet<>(); private final Map epochPackages = new HashMap<>(); private final Map epochModules = new HashMap<>(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java index 59c10317b233..569b77f25c11 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrVMOperationNameSerializer.java @@ -29,22 +29,19 @@ import com.oracle.svm.core.heap.VMOperationInfos; -public class JfrVMOperationNameSerializer implements JfrConstantPool { +public class JfrVMOperationNameSerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrVMOperationNameSerializer() { } @Override - public int write(JfrChunkWriter writer, boolean flush) { + public void write(JfrChunkWriter writer) { String[] vmOperationNames = VMOperationInfos.getNames(); - assert vmOperationNames.length > 0; writer.writeCompressedLong(JfrType.VMOperation.getId()); writer.writeCompressedLong(vmOperationNames.length); for (int id = 0; id < vmOperationNames.length; id++) { writer.writeCompressedLong(id + 1); // id starts with 1 writer.writeString(vmOperationNames[id]); } - - return NON_EMPTY; } } 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 57fe133c5a2c..96b902ad0f8e 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 @@ -112,7 +112,7 @@ public SubstrateJVM(List configurations) { threadLocal = new JfrThreadLocal(); globalMemory = new JfrGlobalMemory(); samplerBufferPool = new SamplerBufferPool(); - unlockedChunkWriter = new JfrChunkWriter(globalMemory); + unlockedChunkWriter = new JfrChunkWriter(globalMemory, stackTraceRepo, methodRepo, typeRepo, symbolRepo, threadRepo); recorderThread = new JfrRecorderThread(globalMemory, unlockedChunkWriter); jfrLogging = new JfrLogging(); @@ -479,7 +479,7 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted JfrBuffer oldBuffer = threadLocal.getJavaBuffer(); if (oldBuffer.isNonNull()) { - JfrBuffer newBuffer = JfrThreadLocal.flush(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); + JfrBuffer newBuffer = JfrThreadLocal.flushToGlobalMemory(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); if (newBuffer.isNull()) { /* The flush failed, so mark the EventWriter as invalid for this write attempt. */ JfrEventWriterAccess.setStartPosition(writer, oldBuffer.getCommittedPos().rawValue()); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java index b12eda968ecb..d25d3680467d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java @@ -37,7 +37,6 @@ import com.oracle.svm.core.jfr.JfrBufferAccess; import com.oracle.svm.core.jfr.JfrBufferList; import com.oracle.svm.core.jfr.JfrBufferNode; -import com.oracle.svm.core.jfr.JfrBufferNodeAccess; import com.oracle.svm.core.jfr.JfrBufferType; public class TestJfrBufferNodeLinkedList { @@ -105,11 +104,6 @@ private static JfrBufferNode removeAllNodes(JfrBufferList list) { while (node.isNonNull()) { JfrBufferNode next = node.getNext(); JfrBufferAccess.free(node.getBuffer()); - /* - * Once JfrBufferNodeAccess.setRetired(node) is called, another thread may free the node - * at any time. - */ - JfrBufferNodeAccess.setRetired(node); list.removeNode(node, WordFactory.nullPointer()); node = next; } @@ -125,11 +119,6 @@ private static void removeNthNode(JfrBufferList list, int target) { JfrBufferNode next = node.getNext(); if (count == target) { JfrBufferAccess.free(node.getBuffer()); - /* - * Once JfrBufferNodeAccess.setRetired(node) is called, another thread may free the - * node at any time. - */ - JfrBufferNodeAccess.setRetired(node); list.removeNode(node, prev); break; } From 6d20de028727994e42bff693a8fcbff058da3605 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Fri, 3 Mar 2023 20:28:31 +0100 Subject: [PATCH 61/72] More cleanups. Disabled flushing of stack traces. --- .../oracle/svm/core/jfr/JfrBufferAccess.java | 3 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 5 +- .../svm/core/jfr/JfrStackTraceRepository.java | 12 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 3 +- .../svm/core/jfr/JfrTypeRepository.java | 122 +++++++++--------- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 8 +- .../svm/core/sampler/SamplerSpinLock.java | 1 + 7 files changed, 78 insertions(+), 76 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 6e0084314d0f..c5fde47b1554 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -159,8 +159,7 @@ public static boolean verify(JfrBuffer buffer) { Pointer start = getDataStart(buffer); Pointer end = getDataEnd(buffer); - // Don't need to use setFlushedPos and getFlushedPos helpers here because we're just - // checking invariants. + /* Just checking invariants, so no need to use setFlushedPos() and getFlushedPos(). */ return buffer.getCommittedPos().aboveOrEqual(start) && buffer.getCommittedPos().belowOrEqual(end) && buffer.getFlushedPos().aboveOrEqual(start) && buffer.getFlushedPos().belowOrEqual(end) && buffer.getFlushedPos().belowOrEqual(buffer.getCommittedPos()); 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 77361d5ff7ec..5351fc0dc0f7 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 @@ -295,7 +295,8 @@ private byte getAndIncrementGeneration() { } private void writeFlushCheckpoint(boolean flush) { - // TEMP (chaeubl): this should also check if there is any data. + // TEMP (chaeubl): this should also check if there is any data - otherwise it is useless to + // emit the event. writeCheckpointEvent(JfrCheckpointType.Flush, flushCheckpointRepos, newChunk, flush); } @@ -540,8 +541,6 @@ private void traverseList(JfrBufferList linkedList, boolean flush) { JfrBufferNode node = linkedList.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); - // TEMP (chaeubl): node.getBuffer() should be accessed if the node is locked -> add an - // accessor method. while (node.isNonNull()) { boolean locked = JfrBufferNodeAccess.tryLock(node); if (locked) { 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 4831dd8dda80..ae40cc6aaca7 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 @@ -219,8 +219,6 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { mutex.lockNoTransition(); try { entry.setSerialized(true); - // TEMP (chaeubl): doesn't this cause issues? the buffer is written elsewhere -> - // reinitialize would destroy data getEpochData(false).unflushedEntries++; } finally { mutex.unlock(); @@ -230,6 +228,16 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { @Override @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") public int write(JfrChunkWriter writer, boolean flush) { + if (flush) { + /* + * Flushing is currently not support for the stack traces. When a stack trace is + * serialized, the methods getOrPutStackTrace() and commitSerializedStackTrace() are + * used, which are not atomic enough (i.e., a flush could destroy the JfrBuffer of the + * epoch, while it is being written). + */ + return EMPTY; + } + mutex.lockNoTransition(); try { JfrStackTraceEpochData epochData = getEpochData(!flush); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index e7108d1d1e4e..e8cc1e24aaf6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -186,8 +186,7 @@ private static void flushToGlobalMemoryAndFreeBuffer(JfrBufferNode node) { /** * This method excludes/includes a thread from JFR (emitting events and sampling). At the - * moment, only the current thread may be excluded/included. This is a difference to HotSpot - * that we will need to address at some point. + * moment, only the current thread may be excluded/included. See GR-44616. */ public void setExcluded(Thread thread, boolean excluded) { if (!thread.equals(Thread.currentThread())) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index ab45d822c95b..370760710e51 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -39,18 +39,13 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceId; /** - * Repository that collects and writes used classes, packages, modules, and classloaders. Fields - * that store state are only used during flushes and chunk rotations (only one thread will use them - * at a time). This means that the maps in this repository will be entirely used and cleared with - * respect to the current epoch before they are used for the subsequent epoch. - * - * The "epoch" maps hold records with respect to a specific epoch and are reset at an epoch change. + * Repository that collects and writes used classes, packages, modules, and classloaders. */ public class JfrTypeRepository implements JfrRepository { - private final Set> epochClasses = new HashSet<>(); - private final Map epochPackages = new HashMap<>(); - private final Map epochModules = new HashMap<>(); - private final Map epochClassLoaders = new HashMap<>(); + private final Set> flushedClasses = new HashSet<>(); + private final Map flushedPackages = new HashMap<>(); + private final Map flushedModules = new HashMap<>(); + private final Map flushedClassLoaders = new HashMap<>(); private long currentPackageId = 0; private long currentModuleId = 0; private long currentClassLoaderId = 0; @@ -66,28 +61,28 @@ public long getClassId(Class clazz) { @Override public int write(JfrChunkWriter writer, boolean flush) { - // Visit all used classes, and collect their packages, modules, classloaders and possibly - // referenced classes. TypeInfo typeInfo = collectTypeInfo(flush); - - // The order of writing matters as following types can be tagged during the write process - int count = writeClasses(typeInfo, writer, flush); - count += writePackages(typeInfo, writer, flush); - count += writeModules(typeInfo, writer, flush); - count += writeClassLoaders(typeInfo, writer, flush); + int count = writeClasses(writer, typeInfo, flush); + count += writePackages(writer, typeInfo, flush); + count += writeModules(writer, typeInfo, flush); + count += writeClassLoaders(writer, typeInfo, flush); if (flush) { - epochClasses.addAll(typeInfo.classes); - epochPackages.putAll(typeInfo.packages); - epochModules.putAll(typeInfo.modules); - epochClassLoaders.putAll(typeInfo.classLoaders); + flushedClasses.addAll(typeInfo.classes); + flushedPackages.putAll(typeInfo.packages); + flushedModules.putAll(typeInfo.modules); + flushedClassLoaders.putAll(typeInfo.classLoaders); } else { - clearEpochChange(); + clearEpochData(); } return count; } + /** + * Visit all used classes, and collect their packages, modules, classloaders and possibly + * referenced classes. + */ private TypeInfo collectTypeInfo(boolean flush) { TypeInfo typeInfo = new TypeInfo(); for (Class clazz : Heap.getHeap().getLoadedClasses()) { @@ -95,9 +90,11 @@ private TypeInfo collectTypeInfo(boolean flush) { if (JfrTraceId.isUsedCurrentEpoch(clazz)) { visitClass(typeInfo, clazz); } - } else if (JfrTraceId.isUsedPreviousEpoch(clazz)) { - JfrTraceId.clearUsedPreviousEpoch(clazz); - visitClass(typeInfo, clazz); + } else { + if (JfrTraceId.isUsedPreviousEpoch(clazz)) { + JfrTraceId.clearUsedPreviousEpoch(clazz); + visitClass(typeInfo, clazz); + } } } return typeInfo; @@ -129,7 +126,7 @@ private void visitClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { } } - private int writeClasses(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + private int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.classes.isEmpty()) { return EMPTY; } @@ -154,7 +151,7 @@ private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz } } - private int writePackages(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.packages.isEmpty()) { return EMPTY; } @@ -175,7 +172,7 @@ private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, String pkgNa writer.writeBoolean(false); // exported } - private int writeModules(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + private int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.modules.isEmpty()) { return EMPTY; } @@ -197,7 +194,7 @@ private void writeModule(TypeInfo typeInfo, JfrChunkWriter writer, Module module writer.writeCompressedLong(getClassLoaderId(typeInfo, module.getClassLoader())); } - private int writeClassLoaders(TypeInfo typeInfo, JfrChunkWriter writer, boolean flush) { + private int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.classLoaders.isEmpty()) { return EMPTY; } @@ -240,29 +237,28 @@ private boolean addClass(TypeInfo typeInfo, Class clazz) { } private boolean isClassVisited(TypeInfo typeInfo, Class clazz) { - return typeInfo.classes.contains(clazz) || epochClasses.contains(clazz); + return typeInfo.classes.contains(clazz) || flushedClasses.contains(clazz); } private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module) { - if (!isPackageVisited(typeInfo, pkg)) { - // The empty package represented by "" is always traced with id 0 - long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; - typeInfo.packages.put(pkg.getName(), new PackageInfo(id, module)); - return true; - } else { - assert epochPackages.containsKey(pkg.getName()) ? module == epochPackages.get(pkg.getName()).module : module == typeInfo.packages.get(pkg.getName()).module; + if (isPackageVisited(typeInfo, pkg)) { + assert module == (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); return false; } + // The empty package represented by "" is always traced with id 0 + long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; + typeInfo.packages.put(pkg.getName(), new PackageInfo(id, module)); + return true; } private boolean isPackageVisited(TypeInfo typeInfo, Package pkg) { - return epochPackages.containsKey(pkg.getName()) || typeInfo.packages.containsKey(pkg.getName()); + return flushedPackages.containsKey(pkg.getName()) || typeInfo.packages.containsKey(pkg.getName()); } private long getPackageId(TypeInfo typeInfo, Package pkg) { if (pkg != null) { - if (epochPackages.containsKey(pkg.getName())) { - return epochPackages.get(pkg.getName()).id; + if (flushedPackages.containsKey(pkg.getName())) { + return flushedPackages.get(pkg.getName()).id; } return typeInfo.packages.get(pkg.getName()).id; } else { @@ -271,22 +267,21 @@ private long getPackageId(TypeInfo typeInfo, Package pkg) { } private boolean addModule(TypeInfo typeInfo, Module module) { - if (!isModuleVisited(typeInfo, module)) { - typeInfo.modules.put(module, ++currentModuleId); - return true; - } else { + if (isModuleVisited(typeInfo, module)) { return false; } + typeInfo.modules.put(module, ++currentModuleId); + return true; } private boolean isModuleVisited(TypeInfo typeInfo, Module module) { - return typeInfo.modules.containsKey(module) || epochModules.containsKey(module); + return typeInfo.modules.containsKey(module) || flushedModules.containsKey(module); } private long getModuleId(TypeInfo typeInfo, Module module) { if (module != null) { - if (epochModules.containsKey(module)) { - return epochModules.get(module); + if (flushedModules.containsKey(module)) { + return flushedModules.get(module); } return typeInfo.modules.get(module); } else { @@ -295,22 +290,21 @@ private long getModuleId(TypeInfo typeInfo, Module module) { } private boolean addClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { - if (!isClassLoaderVisited(typeInfo, classLoader)) { - typeInfo.classLoaders.put(classLoader, ++currentClassLoaderId); - return true; - } else { + if (isClassLoaderVisited(typeInfo, classLoader)) { return false; } + typeInfo.classLoaders.put(classLoader, ++currentClassLoaderId); + return true; } private boolean isClassLoaderVisited(TypeInfo typeInfo, ClassLoader classLoader) { - return epochClassLoaders.containsKey(classLoader) || typeInfo.classLoaders.containsKey(classLoader); + return flushedClassLoaders.containsKey(classLoader) || typeInfo.classLoaders.containsKey(classLoader); } private long getClassLoaderId(TypeInfo typeInfo, ClassLoader classLoader) { if (classLoader != null) { - if (epochClassLoaders.containsKey(classLoader)) { - return epochClassLoaders.get(classLoader); + if (flushedClassLoaders.containsKey(classLoader)) { + return flushedClassLoaders.get(classLoader); } return typeInfo.classLoaders.get(classLoader); } else { @@ -318,20 +312,20 @@ private long getClassLoaderId(TypeInfo typeInfo, ClassLoader classLoader) { } } - private void clearEpochChange() { - epochClasses.clear(); - epochPackages.clear(); - epochModules.clear(); - epochClassLoaders.clear(); + private void clearEpochData() { + flushedClasses.clear(); + flushedPackages.clear(); + flushedModules.clear(); + flushedClassLoaders.clear(); currentPackageId = 0; currentModuleId = 0; currentClassLoaderId = 0; } private static class TypeInfo { - public final Set> classes = new HashSet<>(); - public final Map packages = new HashMap<>(); - public final Map modules = new HashMap<>(); - public final Map classLoaders = new HashMap<>(); + final Set> classes = new HashSet<>(); + final Map packages = new HashMap<>(); + final Map modules = new HashMap<>(); + final Map classLoaders = new HashMap<>(); } } 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 96b902ad0f8e..0b5ca560049a 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 @@ -678,7 +678,10 @@ public void setExcluded(Thread thread, boolean excluded) { } public boolean isExcluded(Thread thread) { - // in jdk 17 and jdk19 the argument is only ever the current thread. + /* + * Only the current thread is passed to this method in JDK 17, 19, and 20. Eventually, we + * will need to implement that in a more general way though, see GR-44616. + */ if (!thread.equals(Thread.currentThread())) { return false; } @@ -687,8 +690,7 @@ public boolean isExcluded(Thread thread) { @Uninterruptible(reason = "Called from uninterruptible code.") public boolean isCurrentThreadExcluded() { - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) getThreadLocal(); - return jfrThreadLocal.isCurrentThreadExcluded(); + return getThreadLocal().isCurrentThreadExcluded(); } private static class JfrBeginRecordingOperation extends JavaVMOperation { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java index bcde98e44d7f..0f85a3c7c71c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java @@ -44,6 +44,7 @@ * implementations can deadlock in this case. So it is essential to check if the current thread is * the owner of the lock, before acquiring it. */ +// TEMP (chaeubl): should be removed, once we have the other SpinLock. class SamplerSpinLock { private final UninterruptibleUtils.AtomicPointer owner; From 1940cf938aa7ad67b39317b6e943430731087770 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sat, 4 Mar 2023 09:51:19 +0100 Subject: [PATCH 62/72] More cleanups. --- .../src/com/oracle/svm/core/jfr/JfrBufferList.java | 4 ++++ .../com/oracle/svm/core/jfr/JfrMethodRepository.java | 2 +- .../svm/core/jfr/JfrNativeEventWriterData.java | 1 - .../oracle/svm/core/jfr/JfrStackTraceRepository.java | 12 ++++++------ .../com/oracle/svm/core/jfr/JfrSymbolRepository.java | 2 +- .../com/oracle/svm/core/jfr/JfrThreadRepository.java | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index d5636aa5321d..13146c863df0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -54,6 +54,10 @@ public class JfrBufferList { private static final Unsafe U = Unsafe.getUnsafe(); private static final long LOCK_OFFSET = U.objectFieldOffset(JfrBufferList.class, "lock"); + // TEMP (chaeubl): we use a second lock for iteration & removal - if an iteration is in + // progress, then there is no need to remove the nodes because they will be removed + // periodically. Otherwise, remove the node right away. Would avoid the garbage issues, see + // comment below. @SuppressWarnings("unused") private volatile int lock; private JfrBufferNode head; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index cf055f24d175..852bf15ca570 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -127,7 +127,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) private JfrMethodEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); return epoch ? epochData0 : epochData1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java index 1bc9bc32239a..a67410ee653f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java @@ -35,7 +35,6 @@ */ @RawStructure public interface JfrNativeEventWriterData extends PointerBase { - /** * Gets the JfrBuffer that data will be written to. */ 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 ae40cc6aaca7..845b7a9bb29f 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 @@ -66,12 +66,12 @@ public class JfrStackTraceRepository implements JfrRepository { private static final int MIN_STACK_DEPTH = 1; private static final int MAX_STACK_DEPTH = 2048; - private int stackTraceDepth; - private final VMMutex mutex; private final JfrStackTraceEpochData epochData0; private final JfrStackTraceEpochData epochData1; + private int stackTraceDepth; + @Platforms(Platform.HOSTED_ONLY.class) JfrStackTraceRepository() { this.mutex = new VMMutex("jfrStackTraceRepository"); @@ -160,7 +160,7 @@ private long storeDeduplicatedStackTrace(SamplerSampleWriterData data) { * NOTE: the returned value is only valid until the JFR epoch changes. So, this method may only * be used from uninterruptible code. */ - @Uninterruptible(reason = "Prevent epoch change. Code that holds the mutex must be fully uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Prevent epoch change. Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) public JfrStackTraceTableEntry getOrPutStackTrace(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) { mutex.lockNoTransition(); try { @@ -170,7 +170,7 @@ public JfrStackTraceTableEntry getOrPutStackTrace(Pointer start, UnsignedWord si } } - @Uninterruptible(reason = "Code that holds the mutex must be fully uninterruptible.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) { assert size.rawValue() == (int) size.rawValue(); @@ -214,7 +214,7 @@ private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord } } - @Uninterruptible(reason = "Code that holds the mutex must be fully uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { mutex.lockNoTransition(); try { @@ -257,7 +257,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) private JfrStackTraceEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); return epoch ? epochData0 : epochData1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 77d480e16e38..688c842613c9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -151,7 +151,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } - @Uninterruptible(reason = "Called by uninterruptible code.") + @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) private JfrSymbolEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); return epoch ? epochData0 : epochData1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 6127ec092a55..fd3ea2e36592 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -234,7 +234,7 @@ private static int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData e return JfrRepository.NON_EMPTY; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) private JfrThreadEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); return epoch ? epochData0 : epochData1; From fd0baa9c137d94d8568cd6a1a5209b16a7571d07 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sat, 4 Mar 2023 16:40:59 +0100 Subject: [PATCH 63/72] Fixed concurrency issues. --- .../svm/core/jdk/UninterruptibleUtils.java | 1 + .../oracle/svm/core/jfr/JfrBufferList.java | 24 +++--------- .../oracle/svm/core/jfr/JfrBufferNode.java | 10 ++++- .../svm/core/jfr/JfrBufferNodeAccess.java | 31 +++++++++++---- .../oracle/svm/core/jfr/JfrChunkWriter.java | 2 +- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 3 +- .../svm/core/jfr/JfrRecorderThread.java | 38 +++++++++++-------- .../svm/core/sampler/SamplerSpinLock.java | 1 - .../svm/core/thread/NativeSpinLockUtils.java | 31 --------------- .../oracle/svm/core/thread/ThreadData.java | 7 +--- 10 files changed, 64 insertions(+), 84 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index 9fd300dd977c..cedba1a8fa2d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -457,6 +457,7 @@ public static int numberOfLeadingZeros(long i) { } // Checkstyle: resume + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int hashCode(long value) { return (int) (value ^ (value >>> 32)); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index 13146c863df0..0f92512deb1c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -54,10 +54,6 @@ public class JfrBufferList { private static final Unsafe U = Unsafe.getUnsafe(); private static final long LOCK_OFFSET = U.objectFieldOffset(JfrBufferList.class, "lock"); - // TEMP (chaeubl): we use a second lock for iteration & removal - if an iteration is in - // progress, then there is no need to remove the nodes because they will be removed - // periodically. Otherwise, remove the node right away. Would avoid the garbage issues, see - // comment below. @SuppressWarnings("unused") private volatile int lock; private JfrBufferNode head; @@ -71,23 +67,15 @@ public JfrBufferList() { * This node data needs to be freed. */ public void teardown() { - // TEMP (chaeubl): fix the case that flushing is disabled - then, the individual threads - // should free their data immediately, otherwise, we would leak memory... can this option be - // changed at runtime? then this tricky... assert VMOperation.isInProgressAtSafepoint(); - lock(); - try { - JfrBufferNode node = head; - while (node.isNonNull()) { - assert node.getBuffer().isNull(); + JfrBufferNode node = head; + while (node.isNonNull()) { + assert node.getBuffer().isNull(); - JfrBufferNode next = node.getNext(); - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); - node = next; - } - } finally { - unlock(); + JfrBufferNode next = node.getNext(); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); + node = next; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java index b6e48403ce17..6bc5b09b2440 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java @@ -55,10 +55,16 @@ public interface JfrBufferNode extends PointerBase { void setBuffer(JfrBuffer value); @RawField - IsolateThread getLockOwner(); + int getLock(); @RawFieldOffset - static int offsetOfLockOwner() { + static int offsetOfLock() { throw VMError.unimplemented(); // replaced } + + @RawField + IsolateThread getLockOwner(); + + @RawField + void setLockOwner(IsolateThread value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index db7b56afbe0e..2f187825ffe1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -29,7 +29,7 @@ import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.struct.SizeOf; -import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; @@ -39,6 +39,10 @@ /** * Used to access the raw memory of a {@link com.oracle.svm.core.jfr.JfrBufferNode}. + * + * This class also provides the infrastructure that threads can access thread-local buffers of other + * threads (see {@link JfrBuffer} for more information regarding concurrency). When the VM enters a + * safepoint, we must guarantee that all {@link JfrBufferNode}s are unlocked. */ public final class JfrBufferNodeAccess { private JfrBufferNodeAccess() { @@ -50,6 +54,7 @@ public static JfrBufferNode allocate(JfrBuffer buffer) { if (node.isNonNull()) { node.setBuffer(buffer); node.setNext(WordFactory.nullPointer()); + node.setLockOwner(WordFactory.nullPointer()); NativeSpinLockUtils.initialize(ptrToLock(node)); } return node; @@ -60,22 +65,28 @@ public static void free(JfrBufferNode node) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); } - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static boolean tryLock(JfrBufferNode node) { assert node.isNonNull(); - return NativeSpinLockUtils.tryLock(ptrToLock(node)); + if (NativeSpinLockUtils.tryLock(ptrToLock(node))) { + setLockOwner(node); + return true; + } + return false; } - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static void lock(JfrBufferNode node) { assert node.isNonNull(); NativeSpinLockUtils.lockNoTransition(ptrToLock(node)); + setLockOwner(node); } - @Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true) + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static void unlock(JfrBufferNode node) { assert node.isNonNull(); assert isLockedByCurrentThread(node); + node.setLockOwner(WordFactory.nullPointer()); NativeSpinLockUtils.unlock(ptrToLock(node)); } @@ -86,7 +97,13 @@ public static boolean isLockedByCurrentThread(JfrBufferNode node) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static WordPointer ptrToLock(JfrBufferNode node) { - return (WordPointer) ((Pointer) node).add(JfrBufferNode.offsetOfLockOwner()); + private static void setLockOwner(JfrBufferNode node) { + assert node.getLockOwner().isNull(); + node.setLockOwner(CurrentIsolate.getCurrentThread()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static CIntPointer ptrToLock(JfrBufferNode node) { + return (CIntPointer) ((Pointer) node).add(JfrBufferNode.offsetOfLock()); } } 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 5351fc0dc0f7..b5f0af6fec2e 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 @@ -517,7 +517,7 @@ private void flushStorage(boolean flush) { traverseList(getJavaBufferList(), flush); traverseList(getNativeBufferList(), flush); - /* Flush all global JFRBuffers. */ + /* Flush all global buffers. */ JfrBufferList buffers = globalMemory.getBuffers(); JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 080d3dcfdd41..01375eff3ca2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.jfr; -import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; @@ -92,7 +91,7 @@ public void teardown() { buffers.teardown(); } - @Fold + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public JfrBufferList getBuffers() { return buffers; } 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 a3dc6dbd123e..8f55beeff8d8 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 @@ -125,22 +125,11 @@ private void persistBuffers(JfrChunkWriter chunkWriter) { JfrBufferList buffers = globalMemory.getBuffers(); JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - if (JfrBufferNodeAccess.tryLock(node)) { - try { - JfrBuffer buffer = node.getBuffer(); - if (isFullEnough(buffer)) { - boolean shouldNotify = chunkWriter.write(buffer); - JfrBufferAccess.reinitialize(buffer); - - if (shouldNotify) { - Object chunkRotationMonitor = getChunkRotationMonitor(); - synchronized (chunkRotationMonitor) { - chunkRotationMonitor.notifyAll(); - } - } - } - } finally { - JfrBufferNodeAccess.unlock(node); + boolean shouldNotify = tryPersistBuffer(chunkWriter, node); + if (shouldNotify) { + Object chunkRotationMonitor = getChunkRotationMonitor(); + synchronized (chunkRotationMonitor) { + chunkRotationMonitor.notifyAll(); } } node = node.getNext(); @@ -155,6 +144,23 @@ private static Object getChunkRotationMonitor() { } } + @Uninterruptible(reason = "Locks a BufferNode.") + private static boolean tryPersistBuffer(JfrChunkWriter chunkWriter, JfrBufferNode node) { + if (JfrBufferNodeAccess.tryLock(node)) { + try { + JfrBuffer buffer = node.getBuffer(); + if (isFullEnough(buffer)) { + boolean shouldNotify = chunkWriter.write(buffer); + JfrBufferAccess.reinitialize(buffer); + return shouldNotify; + } + } finally { + JfrBufferNodeAccess.unlock(node); + } + } + return false; + } + /** * We need to be a bit careful with this method as the recorder thread can't do anything if the * chunk writer doesn't have an output file. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java index 0f85a3c7c71c..bcde98e44d7f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java @@ -44,7 +44,6 @@ * implementations can deadlock in this case. So it is essential to check if the current thread is * the owner of the lock, before acquiring it. */ -// TEMP (chaeubl): should be removed, once we have the other SpinLock. class SamplerSpinLock { private final UninterruptibleUtils.AtomicPointer owner; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java index 26077e409c03..a00468184d01 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeSpinLockUtils.java @@ -25,7 +25,6 @@ package com.oracle.svm.core.thread; import org.graalvm.nativeimage.c.type.CIntPointer; -import org.graalvm.nativeimage.c.type.WordPointer; import com.oracle.svm.core.Uninterruptible; @@ -38,58 +37,28 @@ public static void initialize(CIntPointer spinLock) { JavaSpinLockUtils.initialize(null, spinLock.rawValue()); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void initialize(WordPointer spinLock) { - JavaOwnedSpinLockUtils.initialize(null, spinLock.rawValue()); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isLocked(CIntPointer spinLock) { return JavaSpinLockUtils.isLocked(null, spinLock.rawValue()); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isLocked(WordPointer spinLock) { - return JavaOwnedSpinLockUtils.isLocked(null, spinLock.rawValue()); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean tryLock(CIntPointer spinLock) { return JavaSpinLockUtils.tryLock(null, spinLock.rawValue()); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean tryLock(WordPointer spinLock) { - return JavaOwnedSpinLockUtils.tryLock(null, spinLock.rawValue()); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean tryLock(CIntPointer spinLock, int retries) { return JavaSpinLockUtils.tryLock(null, spinLock.rawValue(), retries); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean tryLock(WordPointer spinLock, int retries) { - return JavaOwnedSpinLockUtils.tryLock(null, spinLock.rawValue(), retries); - } - @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) public static void lockNoTransition(CIntPointer spinLock) { JavaSpinLockUtils.lockNoTransition(null, spinLock.rawValue()); } - @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public static void lockNoTransition(WordPointer spinLock) { - JavaOwnedSpinLockUtils.lockNoTransition(null, spinLock.rawValue()); - } - @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static void unlock(CIntPointer spinLock) { JavaSpinLockUtils.unlock(null, spinLock.rawValue()); } - - @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) - public static void unlock(WordPointer spinLock) { - JavaOwnedSpinLockUtils.unlock(null, spinLock.rawValue()); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java index 54bc3bd4f5d6..e2d562f9d365 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java @@ -156,7 +156,7 @@ public void detach() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void free() { - assert isLocked(); + assert JavaSpinLockUtils.isLocked(this, LOCK_OFFSET); if (unsafeParker != null) { unsafeParker.release(); unsafeParker = null; @@ -192,11 +192,6 @@ private boolean tryToStoreParker(long offset, Parker newEvent) { private boolean isForCurrentThread() { return this == PlatformThreads.getCurrentThreadData(); } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private boolean isLocked() { - return lock == 1; - } } abstract class UnacquiredThreadData { From f01fee14d7de50a666e9957d11a5a87645be7761 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sat, 4 Mar 2023 16:47:16 +0100 Subject: [PATCH 64/72] Moved UninterruptibleHashtable to 'com.oracle.svm.core.collections'. --- .../AbstractUninterruptibleHashtable.java | 3 ++- .../core/{jdk => collections}/UninterruptibleHashtable.java | 3 ++- .../src/com/oracle/svm/core/jdk/UninterruptibleEntry.java | 2 ++ .../src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java | 2 +- .../src/com/oracle/svm/core/jfr/JfrSymbolRepository.java | 2 +- .../src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java | 4 ++-- 6 files changed, 10 insertions(+), 6 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{jdk => collections}/AbstractUninterruptibleHashtable.java (98%) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{jdk => collections}/UninterruptibleHashtable.java (97%) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java similarity index 98% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java index 5cb458dbd8fb..432d41ee2ca0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jdk; +package com.oracle.svm.core.collections; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -34,6 +34,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.jdk.UninterruptibleEntry; /** * An uninterruptible hashtable with a fixed size that uses chaining in case of a collision. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java similarity index 97% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java index f6b543ffbb22..4b9b9a85d504 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java @@ -22,9 +22,10 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jdk; +package com.oracle.svm.core.collections; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleEntry; /** * Common interface for all uninterruptible hashtable implementations. Please note that we don't use diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java index 343a09ac6783..a044d3e57878 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java @@ -28,6 +28,8 @@ import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.PointerBase; +import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; + /** * The common interface for the LinkedList entries that can be used in an * {@link AbstractUninterruptibleHashtable}. 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 845b7a9bb29f..8ce7fd1a2f4f 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 @@ -42,9 +42,9 @@ import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.headers.LibC; -import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; import com.oracle.svm.core.jdk.UninterruptibleEntry; import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 688c842613c9..6c6271a962ed 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -36,8 +36,8 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.struct.PinnedObjectField; +import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; import com.oracle.svm.core.jdk.UninterruptibleEntry; import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java index 6e25fbe9206c..26a9ee56e6eb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java @@ -25,10 +25,10 @@ package com.oracle.svm.core.jfr.utils; -import com.oracle.svm.core.Uninterruptible; import org.graalvm.nativeimage.c.struct.SizeOf; -import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; import com.oracle.svm.core.jdk.UninterruptibleEntry; public final class JfrVisitedTable extends AbstractUninterruptibleHashtable { From 72364c69d629c0d713a2cd85ca3e5f58402a0c32 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sat, 4 Mar 2023 20:11:11 +0100 Subject: [PATCH 65/72] Fix further issues. --- .../oracle/svm/core/jfr/JfrBufferAccess.java | 5 +- .../oracle/svm/core/jfr/JfrBufferList.java | 15 +- .../svm/core/jfr/JfrBufferNodeAccess.java | 9 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 73 +++++----- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 16 ++- .../svm/core/jfr/JfrNativeEventWriter.java | 6 +- .../svm/core/jfr/JfrRecorderThread.java | 2 +- .../svm/core/jfr/JfrSymbolRepository.java | 17 +-- .../oracle/svm/core/jfr/JfrThreadLocal.java | 2 +- .../svm/core/jfr/JfrThreadRepository.java | 2 +- ...reamingTest.java => JfrStreamingTest.java} | 9 +- .../test/jfr/TestJfrBufferNodeLinkedList.java | 130 ------------------ ...gBasic.java => TestJfrStreamingBasic.java} | 114 +++++++-------- ...gCount.java => TestJfrStreamingCount.java} | 7 +- ...tress.java => TestJfrStreamingStress.java} | 2 +- 15 files changed, 140 insertions(+), 269 deletions(-) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{StreamingTest.java => JfrStreamingTest.java} (95%) delete mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestStreamingBasic.java => TestJfrStreamingBasic.java} (54%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestStreamingCount.java => TestJfrStreamingCount.java} (98%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestStreamingStress.java => TestJfrStreamingStress.java} (99%) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index c5fde47b1554..8bf47d6ae342 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -34,6 +34,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.UnsignedUtils; /** @@ -89,7 +90,7 @@ public static void reinitialize(JfrBuffer buffer) { */ @Uninterruptible(reason = "Changes flushed position.") public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { - assert buffer.getNode().isNull() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); + assert buffer.getNode().isNull() || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); buffer.setFlushedPos(pos); } @@ -98,7 +99,7 @@ public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { */ @Uninterruptible(reason = "Accesses flushed position. Possible race between flushing and working threads.") public static Pointer getFlushedPos(JfrBuffer buffer) { - assert buffer.getNode().isNull() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); + assert buffer.getNode().isNull() || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); return buffer.getFlushedPos(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index 0f92512deb1c..b1d2455b3f75 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -57,6 +57,15 @@ public class JfrBufferList { @SuppressWarnings("unused") private volatile int lock; private JfrBufferNode head; + // TEMP (chaeubl): fix the case that flushing is disabled - then, the individual threads + // should free their data immediately, otherwise, we would leak memory... can this option be + // changed at runtime? then this tricky... + + // TEMP (chaeubl): we use a second lock for iteration & removal - if an iteration is in + // progress, then there is no need to remove the nodes because they will be removed + // periodically. Otherwise, remove the node right away. Would avoid the garbage issues, see + // comment below. + @Platforms(Platform.HOSTED_ONLY.class) public JfrBufferList() { } @@ -71,12 +80,13 @@ public void teardown() { JfrBufferNode node = head; while (node.isNonNull()) { - assert node.getBuffer().isNull(); + assert JfrBufferNodeAccess.getBuffer(node).isNull(); JfrBufferNode next = node.getNext(); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); node = next; } + head = WordFactory.nullPointer(); } @Uninterruptible(reason = "Locking with no transition.") @@ -123,10 +133,11 @@ public JfrBufferNode addNode(JfrBuffer buffer) { @Uninterruptible(reason = "Should not be interrupted while flushing.") public void removeNode(JfrBufferNode node, JfrBufferNode prev) { assert head.isNonNull(); - assert node.getBuffer().isNull(); lock(); try { + assert JfrBufferNodeAccess.getBuffer(node).isNull(); + JfrBufferNode next = node.getNext(); if (node == head) { assert prev.isNull(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 2f187825ffe1..956bda9ea7d3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.thread.NativeSpinLockUtils; +import com.oracle.svm.core.thread.VMOperation; /** * Used to access the raw memory of a {@link com.oracle.svm.core.jfr.JfrBufferNode}. @@ -65,6 +66,12 @@ public static void free(JfrBufferNode node) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static JfrBuffer getBuffer(JfrBufferNode node) { + assert isLockedByCurrentThread(node) || VMOperation.isInProgressAtSafepoint(); + return node.getBuffer(); + } + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public static boolean tryLock(JfrBufferNode node) { assert node.isNonNull(); @@ -93,7 +100,7 @@ public static void unlock(JfrBufferNode node) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isLockedByCurrentThread(JfrBufferNode node) { assert CurrentIsolate.getCurrentThread().isNonNull(); - return node != null && node.getLockOwner() == CurrentIsolate.getCurrentThread(); + return node.isNonNull() && node.getLockOwner() == CurrentIsolate.getCurrentThread(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) 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 b5f0af6fec2e..89ec6657ea46 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 @@ -30,6 +30,7 @@ import java.nio.charset.StandardCharsets; import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -498,10 +499,10 @@ static RawFileOperationSupport getFileSupport() { public void writeString(String str) { if (str.isEmpty()) { - getFileSupport().writeByte(fd, StringEncoding.EMPTY_STRING.byteValue); + getFileSupport().writeByte(fd, StringEncoding.EMPTY_STRING.getValue()); } else { byte[] bytes = str.getBytes(StandardCharsets.UTF_8); - getFileSupport().writeByte(fd, StringEncoding.UTF8_BYTE_ARRAY.byteValue); + getFileSupport().writeByte(fd, StringEncoding.UTF8_BYTE_ARRAY.getValue()); writeCompressedInt(bytes.length); getFileSupport().write(fd, bytes); } @@ -514,61 +515,64 @@ private void flushStorage(boolean flush) { * reinitialize the thread-local buffers as the individual threads will handle space * reclamation on their own time. */ - traverseList(getJavaBufferList(), flush); - traverseList(getNativeBufferList(), flush); + traverseThreadLocalBuffers(getJavaBufferList(), flush); + traverseThreadLocalBuffers(getNativeBufferList(), flush); /* Flush all global buffers. */ JfrBufferList buffers = globalMemory.getBuffers(); JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - boolean locked = JfrBufferNodeAccess.tryLock(node); - if (locked) { + boolean success = JfrBufferNodeAccess.tryLock(node); + if (success) { try { - JfrBuffer buffer = node.getBuffer(); + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); write(buffer); JfrBufferAccess.reinitialize(buffer); } finally { JfrBufferNodeAccess.unlock(node); } } - assert locked || flush; + assert success || flush; node = node.getNext(); } } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") - private void traverseList(JfrBufferList linkedList, boolean flush) { - JfrBufferNode node = linkedList.getHead(); + private void traverseThreadLocalBuffers(JfrBufferList list, boolean flush) { + JfrBufferNode node = list.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); while (node.isNonNull()) { - boolean locked = JfrBufferNodeAccess.tryLock(node); - if (locked) { + JfrBufferNode next = node.getNext(); + boolean success = JfrBufferNodeAccess.tryLock(node); + if (success) { + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); + if (buffer.isNull()) { + list.removeNode(node, prev); + JfrBufferNodeAccess.free(node); + node = next; + continue; + } + try { - JfrBuffer buffer = node.getBuffer(); - if (buffer.isNull()) { - linkedList.removeNode(node, prev); - /* Don't update prev if we removed the node. */ + if (flush) { + /* + * I/O operations may be slow, so this flushes to the global buffers instead + * of writing to disk directly. This mitigates the risk of acquiring the + * thread-local buffers for too long. + */ + SubstrateJVM.getGlobalMemory().write(buffer, true); } else { - if (flush) { - /* - * I/O operations may be slow, so this flushes to the global buffers - * instead of writing to disk directly. This mitigates the risk of - * acquiring the thread-local buffers for too long. - */ - SubstrateJVM.getGlobalMemory().write(buffer, true); - } else { - write(buffer); - } - prev = node; + write(buffer); } + prev = node; } finally { JfrBufferNodeAccess.unlock(node); } } - assert locked || flush; - node = node.getNext(); + assert success || flush; + node = next; } } @@ -580,10 +584,15 @@ public enum StringEncoding { CHAR_ARRAY(4), LATIN1_BYTE_ARRAY(5); - public final byte byteValue; + private final byte value; + + StringEncoding(int value) { + this.value = NumUtil.safeToByte(value); + } - StringEncoding(int byteValue) { - this.byteValue = (byte) byteValue; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public byte getValue() { + return value; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 01375eff3ca2..8b76e2463062 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -74,7 +74,8 @@ public void clear() { JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - JfrBufferAccess.reinitialize(node.getBuffer()); + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); + JfrBufferAccess.reinitialize(buffer); node = node.getNext(); } } @@ -83,7 +84,8 @@ public void teardown() { /* Free the buffers. */ JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - JfrBufferAccess.free(node.getBuffer()); + JfrBuffer buffer = node.getBuffer(); + JfrBufferAccess.free(buffer); node = node.getNext(); } @@ -143,11 +145,13 @@ private JfrBufferNode tryAcquirePromotionBuffer(UnsignedWord size) { for (int retry = 0; retry < PROMOTION_RETRY_COUNT; retry++) { JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - JfrBuffer buffer = node.getBuffer(); - if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size) && JfrBufferNodeAccess.tryLock(node)) { - /* Recheck the available size after acquiring the buffer. */ + if (JfrBufferNodeAccess.tryLock(node)) { + JfrBuffer buffer = node.getBuffer(); if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size)) { - return node; + /* Recheck the available size after acquiring the buffer. */ + if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size)) { + return node; + } } JfrBufferNodeAccess.unlock(node); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index 0660cb798630..9ba620030d5d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -198,12 +198,12 @@ public static void putString(JfrNativeEventWriterData data, String string) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) public static void putString(JfrNativeEventWriterData data, String string, CharReplacer replacer) { if (string == null) { - putByte(data, JfrChunkWriter.StringEncoding.NULL.byteValue); + putByte(data, JfrChunkWriter.StringEncoding.NULL.getValue()); } else if (string.isEmpty()) { - putByte(data, JfrChunkWriter.StringEncoding.EMPTY_STRING.byteValue); + putByte(data, JfrChunkWriter.StringEncoding.EMPTY_STRING.getValue()); } else { int mUTF8Length = UninterruptibleUtils.String.modifiedUTF8Length(string, false, replacer); - putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); + putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.getValue()); putInt(data, mUTF8Length); if (ensureSize(data, mUTF8Length)) { Pointer newPosition = UninterruptibleUtils.String.toModifiedUTF8(string, data.getCurrentPos(), data.getEndPos(), false, replacer); 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 8f55beeff8d8..9197f1f42c88 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 @@ -148,7 +148,7 @@ private static Object getChunkRotationMonitor() { private static boolean tryPersistBuffer(JfrChunkWriter chunkWriter, JfrBufferNode node) { if (JfrBufferNodeAccess.tryLock(node)) { try { - JfrBuffer buffer = node.getBuffer(); + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); if (isFullEnough(buffer)) { boolean shouldNotify = chunkWriter.write(buffer); JfrBufferAccess.reinitialize(buffer); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 6c6271a962ed..4b84a19aca13 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -94,12 +94,12 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r mutex.lockNoTransition(); try { JfrSymbolEpochData epochData = getEpochData(previousEpoch); - JfrSymbol existingEntry = epochData.table.get(symbol); + JfrSymbol existingEntry = (JfrSymbol) epochData.table.get(symbol); if (existingEntry.isNonNull()) { return existingEntry.getId(); } - JfrSymbol newEntry = epochData.table.putNew(symbol); + JfrSymbol newEntry = (JfrSymbol) epochData.table.putNew(symbol); if (newEntry.isNull()) { return 0L; } @@ -114,7 +114,6 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r JfrNativeEventWriterDataAccess.initialize(data, epochData.buffer); JfrNativeEventWriter.putLong(data, newEntry.getId()); - JfrNativeEventWriter.putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); JfrNativeEventWriter.putString(data, newEntry.getValue(), charReplacer); if (!JfrNativeEventWriter.commit(data)) { return 0L; @@ -195,18 +194,6 @@ public JfrSymbol[] getTable() { return (JfrSymbol[]) super.getTable(); } - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public JfrSymbol get(UninterruptibleEntry valueOnStack) { - return (JfrSymbol) super.get(valueOnStack); - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public JfrSymbol putNew(UninterruptibleEntry valueOnStack) { - return (JfrSymbol) super.putNew(valueOnStack); - } - @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "image heap pointer comparison") @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index e8cc1e24aaf6..8fadb60c7019 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -174,7 +174,7 @@ private static void flushToGlobalMemoryAndFreeBuffer(JfrBufferNode node) { /* Free the buffer but leave the node alive as it still needed. */ JfrBufferNodeAccess.lock(node); try { - JfrBuffer buffer = node.getBuffer(); + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); node.setBuffer(WordFactory.nullPointer()); flushToGlobalMemory0(buffer, WordFactory.unsigned(0), 0); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index fd3ea2e36592..420a7f3a9def 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -183,7 +183,7 @@ public boolean hasUnflushedData() { mutex.lockNoTransition(); try { JfrThreadEpochData epochData = getEpochData(false); - return epochData.unflushedThreadCount == 0 && epochData.unflushedThreadGroupCount == 0; + return epochData.unflushedThreadCount > 0 || epochData.unflushedThreadGroupCount > 0; } finally { mutex.unlock(); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java similarity index 95% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java index ebdc79d34e8f..5d157c60c192 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/StreamingTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java @@ -25,19 +25,20 @@ */ package com.oracle.svm.test.jfr; -import jdk.jfr.consumer.RecordingStream; +import static org.junit.Assert.assertTrue; import java.nio.file.Path; import java.time.Duration; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicInteger; + import com.oracle.svm.test.jfr.events.EndStreamEvent; -import static org.junit.Assert.assertTrue; +import jdk.jfr.consumer.RecordingStream; abstract class StreamingTest extends JfrTest { protected static final int TIMEOUT_MILLIS = 3 * 1000; protected Path dumpLocation; - protected AtomicLong emittedEvents = new AtomicLong(0); + protected AtomicInteger emittedEvents = new AtomicInteger(0); private RecordingStream rs; private volatile boolean streamEndedSuccessfully = false; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java deleted file mode 100644 index d25d3680467d..000000000000 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrBufferNodeLinkedList.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, 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.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.graalvm.word.WordFactory; -import org.junit.Test; - -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jfr.JfrBuffer; -import com.oracle.svm.core.jfr.JfrBufferAccess; -import com.oracle.svm.core.jfr.JfrBufferList; -import com.oracle.svm.core.jfr.JfrBufferNode; -import com.oracle.svm.core.jfr.JfrBufferType; - -public class TestJfrBufferNodeLinkedList { - - @Test - public void testBasicAdditionAndRemoval() { - final int nodeCount = 10; - final JfrBufferList list = new JfrBufferList(); - addNodes(list, nodeCount); - int count = countNodes(list); - assertEquals("Number of nodes in list does not match nodes added.", count, nodeCount); - cleanUpList(list); - } - - @Test - public void testMiddleRemoval() { - final int nodeCount = 10; - JfrBufferList list = new JfrBufferList(); - addNodes(list, nodeCount); - removeNthNode(list, nodeCount / 2); - assertEquals("Removal from middle failed", countNodes(list), nodeCount - 1); - cleanUpList(list); - } - - @Test - public void testConcurrentAddition() throws Exception { - final int nodeCountPerThread = 10; - final int threads = 10; - JfrBufferList list = new JfrBufferList(); - Runnable r = () -> { - addNodes(list, nodeCountPerThread); - }; - Stressor.execute(threads, r); - assertEquals("Incorrect number of nodes added", countNodes(list), nodeCountPerThread * threads); - cleanUpList(list); - } - - private static void cleanUpList(JfrBufferList list) { - JfrBufferNode node = removeAllNodes(list); - assertTrue("Could not remove all nodes", node.isNull()); - } - - private static void addNodes(JfrBufferList list, int nodeCount) { - for (int i = 0; i < nodeCount; i++) { - JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(32), JfrBufferType.THREAD_LOCAL_NATIVE); - list.addNode(buffer); - } - } - - @Uninterruptible(reason = "Locking with no transition.") - private static int countNodes(JfrBufferList list) { - int count = 0; - JfrBufferNode node = list.getHead(); - while (node.isNonNull()) { - count++; - node = node.getNext(); - } - return count; - } - - @Uninterruptible(reason = "Locking with no transition.") - private static JfrBufferNode removeAllNodes(JfrBufferList list) { - // Try removing the nodes - JfrBufferNode node = list.getHead(); - while (node.isNonNull()) { - JfrBufferNode next = node.getNext(); - JfrBufferAccess.free(node.getBuffer()); - list.removeNode(node, WordFactory.nullPointer()); - node = next; - } - return node; - } - - @Uninterruptible(reason = "Locking with no transition.") - private static void removeNthNode(JfrBufferList list, int target) { - JfrBufferNode prev = WordFactory.nullPointer(); - JfrBufferNode node = list.getHead(); - int count = 0; - while (node.isNonNull()) { - JfrBufferNode next = node.getNext(); - if (count == target) { - JfrBufferAccess.free(node.getBuffer()); - list.removeNode(node, prev); - break; - } - prev = node; - node = next; - count++; - } - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java similarity index 54% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java index 7e27fba33ed5..8cd8cffa05ec 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingBasic.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java @@ -26,35 +26,36 @@ package com.oracle.svm.test.jfr; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedThread; -import jdk.jfr.consumer.RecordedClass; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; import com.oracle.svm.core.jfr.JfrEvent; -import java.io.File; -import java.io.IOException; -import java.time.Duration; -import java.util.HashSet; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; /** * Check to make sure 1. The events that are emitted are found in the stream 2. The resulting JFR * dump is readable and events can be read that match the events that were streamed. */ public class TestStreamingBasic extends StreamingTest { - private static final int MILLIS = 20; + private static final int WAIT_TIMEOUT = 20; private static final int THREADS = 3; private static final int EXPECTED_EVENTS = THREADS * 2; - final Helper helper = new Helper(); - private AtomicLong remainingStringEventsInStream = new AtomicLong(EXPECTED_EVENTS); - volatile int flushes = 0; - HashSet streamEvents = new HashSet<>(); + + private final Helper helper = new Helper(); + private final AtomicInteger seenEvents = new AtomicInteger(0); + private final Set seenThreads = new HashSet<>(); + private final AtomicInteger flushes = new AtomicInteger(0); @Override public String[] getTestedEvents() { @@ -63,86 +64,67 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { - List events = getEvents(dumpLocation); + List events = getEvents(); for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); if (event. getValue("monitorClass").getName().equals(Helper.class.getName())) { - if (!streamEvents.contains(eventThread)) { - continue; - } - streamEvents.remove(eventThread); + seenThreads.remove(eventThread); } } - assertTrue("Not all expected monitor wait events were found in the JFR file", streamEvents.isEmpty()); + assertTrue("Not all expected monitor wait events were found in the JFR file", seenThreads.isEmpty()); } @Test public void test() throws Exception { - Runnable r = () -> { - try { - helper.doEvent(); - emittedEvents.incrementAndGet(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - }; - - var rs = createStream(); - rs.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(MILLIS - 1)).withStackTrace(); - rs.onEvent("jdk.JavaMonitorWait", event -> { + final var stream = createStream(); + stream.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(WAIT_TIMEOUT - 1)).withStackTrace(); + stream.onEvent("jdk.JavaMonitorWait", event -> { String thread = event.getThread("eventThread").getJavaName(); if (!event.getClass("monitorClass").getName().equals(Helper.class.getName())) { return; } - if (streamEvents.contains(thread)) { - return; - } - streamEvents.add(thread); - remainingStringEventsInStream.decrementAndGet(); + seenThreads.add(thread); + seenEvents.incrementAndGet(); }); - rs.onFlush(() -> { - try { - if (flushes == 0) { - Stressor.execute(THREADS, r); - } - } catch (IOException e) { - throw new RuntimeException(e); - } catch (Exception e) { - throw new RuntimeException(e); + Runnable task = () -> { + helper.doEvent(); + emittedEvents.incrementAndGet(); + }; + + stream.onFlush(() -> { + if (flushes.getAndIncrement() == 0) { + Stressor.execute(THREADS, task); } - flushes++; }); - rs.startAsync(); - Stressor.execute(THREADS, r); + stream.start(); - // Wait until all events have been emitted. + Stressor.execute(THREADS, task); + + /* Wait until all events have been emitted. */ while (emittedEvents.get() < EXPECTED_EVENTS) { Thread.sleep(10); } - int flushCount = flushes; - /* - * At this point we can expect to have found all the events after the next 2 flushes. - * Scenario: A flush is occurring while emittedEvents.get() is incremented up to be - * EXPECTED_EVENTS and therefore doesn't contain all the events. But the flush after the - * next one must contain all remaining events. - */ - while (remainingStringEventsInStream.get() > 0) { - assertFalse("Not all expected monitor wait events were found in the JFR stream. Remaining:" + remainingStringEventsInStream.get(), - flushes > (flushCount + 1) && remainingStringEventsInStream.get() > 0); + /* At this point we can expect to have found all the events after the next 2 flushes. */ + int flushLimit = flushes.get() + 2; + while (seenEvents.get() < EXPECTED_EVENTS && flushes.get() < flushLimit) { + Thread.sleep(10); } - File directory = new File("."); - dumpLocation = new File(directory.getAbsolutePath(), "TestStreaming.jfr").toPath(); - rs.dump(dumpLocation); + assertEquals("Not all expected monitor wait events were found in the JFR stream.", EXPECTED_EVENTS, seenEvents.get()); closeStream(); } static class Helper { - public synchronized void doEvent() throws InterruptedException { - wait(MILLIS); + public synchronized void doEvent() { + try { + /* Emits a JFR wait event. */ + this.wait(WAIT_TIMEOUT); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java similarity index 98% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java index 2318ce257f23..a3d3e2bc1e7a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java @@ -30,15 +30,14 @@ import java.io.File; import java.io.IOException; - import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; -import com.oracle.svm.test.jfr.events.StringEvent; -import com.oracle.svm.test.jfr.events.IntegerEvent; import com.oracle.svm.test.jfr.events.ClassEvent; +import com.oracle.svm.test.jfr.events.IntegerEvent; +import com.oracle.svm.test.jfr.events.StringEvent; import jdk.jfr.consumer.RecordedEvent; @@ -48,7 +47,7 @@ * check for potential flush/rotation clashes. */ -public class TestStreamingCount extends StreamingTest { +public class TestStreamingCount extends JfrStreamingTest { private static final int THREADS = 8; private static final int COUNT = 1024; private static final int EXPECTED_EVENTS = THREADS * COUNT; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java similarity index 99% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java index c7cb94196c75..3a4b953141e2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStreamingStress.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java @@ -47,7 +47,7 @@ * */ -public class TestStreamingStress extends StreamingTest { +public class TestStreamingStress extends JfrStreamingTest { private static final int THREADS = 8; private static final int COUNT = 10; private static final int EXPECTED_EVENTS = THREADS * COUNT * 10; From 92ee77b5189a1b5592900d17516f72e31b1720ef Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sun, 5 Mar 2023 10:32:28 +0100 Subject: [PATCH 66/72] Style fixes. --- substratevm/CHANGELOG.md | 1 + .../collections/AbstractUninterruptibleHashtable.java | 1 + .../src/com/oracle/svm/core/jfr/JfrChunkWriter.java | 10 +++++----- .../src/com/oracle/svm/core/jfr/JfrTypeRepository.java | 4 ++-- .../src/com/oracle/svm/core/thread/ThreadData.java | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 9adf1883eafd..938403319cc9 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -25,6 +25,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-38414) BellSoft implemented the `MemoryPoolMXBean` for the serial and epsilon GCs. * (GR-40641) Dynamic linking of AWT libraries on Linux. * (GR-40463) Red Hat added experimental support for JMX, which can be enabled with the `--enable-monitoring` option (e.g. `--enable-monitoring=jmxclient,jmxserver`). +* (GR-42740) Red Hat added experimental support for JFR event streaming. * (GR-44110) Native Image now targets `x86-64-v3` by default on AMD64 and supports a new `-march` option. Use `-march=compatibility` for best compatibility (previous default) or `-march=native` for best performance if the native executable is deployed on the same machine or on a machine with the same CPU features. To list all available machine types, use `-march=list`. ## Version 22.3.0 diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java index 432d41ee2ca0..fc72b9fcb7f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java @@ -148,6 +148,7 @@ public boolean putIfAbsent(UninterruptibleEntry valueOnStack) { } } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public UninterruptibleEntry putNew(UninterruptibleEntry valueOnStack) { assert valueOnStack.isNonNull(); 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 89ec6657ea46..1d384eada07b 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 @@ -85,7 +85,7 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private RawFileDescriptor fd; private long chunkStartTicks; private long chunkStartNanos; - private byte generation; + private byte nextGeneration; private boolean newChunk; private boolean isFinal; private long lastMetadataId; @@ -154,7 +154,7 @@ public void openFile(String outputFile) { chunkStartTicks = JfrTicks.elapsedTicks(); chunkStartNanos = JfrTicks.currentTimeNanos(); - generation = 1; + nextGeneration = 1; newChunk = true; isFinal = false; lastMetadataId = -1; @@ -287,12 +287,12 @@ private short computeHeaderFlags() { } private byte getAndIncrementGeneration() { - if (generation == Byte.MAX_VALUE) { + if (nextGeneration == Byte.MAX_VALUE) { // Restart counter if required. - generation = 1; + nextGeneration = 1; return Byte.MAX_VALUE; } - return generation++; + return nextGeneration++; } private void writeFlushCheckpoint(boolean flush) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 370760710e51..bbb79eb207fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -194,7 +194,7 @@ private void writeModule(TypeInfo typeInfo, JfrChunkWriter writer, Module module writer.writeCompressedLong(getClassLoaderId(typeInfo, module.getClassLoader())); } - private int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { + private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.classLoaders.isEmpty()) { return EMPTY; } @@ -207,7 +207,7 @@ private int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean return NON_EMPTY; } - private void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { + private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); if (cl == null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java index e2d562f9d365..24dec1244287 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadData.java @@ -43,7 +43,7 @@ public final class ThreadData extends UnacquiredThreadData { private static final long UNSAFE_PARK_EVENT_OFFSET = U.objectFieldOffset(ThreadData.class, "unsafeParker"); private static final long SLEEP_PARK_EVENT_OFFSET = U.objectFieldOffset(ThreadData.class, "sleepParker"); - private volatile int lock; + @SuppressWarnings("unused") private volatile int lock; private boolean detached; private long refCount; From 667323da7038d28ca4928795ae8a9e289a4820ff Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sun, 5 Mar 2023 18:39:27 +0100 Subject: [PATCH 67/72] Fixed test cases. --- .../oracle/svm/test/jfr/JfrStreamingTest.java | 60 +++++-- .../src/com/oracle/svm/test/jfr/JfrTest.java | 16 +- .../src/com/oracle/svm/test/jfr/Stressor.java | 26 +-- .../test/jfr/TestJavaMonitorEnterEvent.java | 3 +- .../svm/test/jfr/TestJfrStreamingBasic.java | 58 ++----- .../svm/test/jfr/TestJfrStreamingCount.java | 94 ++++------ .../svm/test/jfr/TestJfrStreamingStress.java | 164 ++++++++---------- .../svm/test/jfr/events/EndStreamEvent.java | 5 +- .../svm/test/jfr/events/StartStreamEvent.java | 39 +++++ 9 files changed, 237 insertions(+), 228 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StartStreamEvent.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java index 5d157c60c192..2d9bbd13e6b6 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java @@ -27,38 +27,66 @@ import static org.junit.Assert.assertTrue; -import java.nio.file.Path; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import com.oracle.svm.test.jfr.events.EndStreamEvent; +import com.oracle.svm.test.jfr.events.StartStreamEvent; import jdk.jfr.consumer.RecordingStream; -abstract class StreamingTest extends JfrTest { - protected static final int TIMEOUT_MILLIS = 3 * 1000; - protected Path dumpLocation; - protected AtomicInteger emittedEvents = new AtomicInteger(0); - private RecordingStream rs; +abstract class JfrStreamingTest extends JfrTest { + protected static final int TIMEOUT_MILLIS = 10 * 1000; + + protected final AtomicInteger emittedEventsPerType = new AtomicInteger(0); + protected RecordingStream stream; + private volatile boolean streamStarted = false; private volatile boolean streamEndedSuccessfully = false; - protected RecordingStream createStream() { - rs = new RecordingStream(); - rs.enable("com.jfr.EndStream"); - // close stream once we get the signal - rs.onEvent("com.jfr.EndStream", e -> { - rs.close(); + protected void createStream() { + stream = new RecordingStream(); + + stream.enable("com.jfr.StartStream"); + stream.onEvent("com.jfr.StartStream", e -> { + streamStarted = true; + }); + + stream.enable("com.jfr.EndStream"); + stream.onEvent("com.jfr.EndStream", e -> { + stream.close(); streamEndedSuccessfully = true; }); - return rs; + } + + protected void startStream() throws InterruptedException { + stream.startAsync(); + /* Wait until the started thread can handle events. */ + waitUntilTrue(() -> { + if (!streamStarted) { + StartStreamEvent event = new StartStreamEvent(); + event.commit(); + return false; + } + return true; + }); } protected void closeStream() throws InterruptedException { // We require a signal to close the stream, because if we close the stream immediately after // dumping, the dump may not have had time to finish. - EndStreamEvent endStreamEvent = new EndStreamEvent(); - endStreamEvent.commit(); - rs.awaitTermination(Duration.ofMillis(TIMEOUT_MILLIS)); + EndStreamEvent event = new EndStreamEvent(); + event.commit(); + stream.awaitTermination(Duration.ofMillis(TIMEOUT_MILLIS)); assertTrue("unable to find stream end event signal in stream", streamEndedSuccessfully); } + + static class MonitorWaitHelper { + public synchronized void doEvent() { + try { + this.wait(0, 1); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } } 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 748661d27ca8..4d92e69464e8 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 @@ -37,6 +37,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.function.BooleanSupplier; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.hosted.Feature; @@ -46,6 +47,7 @@ import org.junit.BeforeClass; import com.oracle.svm.core.jfr.HasJfrSupport; +import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.test.jfr.utils.Jfr; import com.oracle.svm.test.jfr.utils.JfrFileParser; import com.oracle.svm.test.jfr.utils.LocalJfr; @@ -159,10 +161,22 @@ protected List getEvents(Path p) throws IOException { private List getEvents0(Path p) throws IOException { List events = RecordingFile.readAllEvents(p); Collections.sort(events, new ChronologicalComparator()); - // remove events that are not in the list of tested events + /* 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; } + + protected static void waitUntilTrue(BooleanSupplier supplier) throws InterruptedException { + long timeout = TimeUtils.secondsToNanos(30); + long startTime = System.nanoTime(); + while (!supplier.getAsBoolean()) { + long elapsedNanos = System.nanoTime() - startTime; + if (elapsedNanos > timeout) { + Assert.fail("Timed out after: " + TimeUtils.divideNanosToMillis(timeout) + "ms."); + } + Thread.sleep(10); + } + } } class JfrTestFeature implements Feature { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java index 0646901bfeb1..9b0563b9537d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java @@ -26,22 +26,26 @@ package com.oracle.svm.test.jfr; -import java.util.ArrayList; -import java.util.List; - /** * Class to help run multiple threads executing some task. */ public class Stressor { - public static void execute(int numberOfThreads, Runnable task) throws Exception { - List threads = new ArrayList<>(); - for (int n = 0; n < numberOfThreads; ++n) { - Thread t = new Thread(task); - threads.add(t); - t.start(); + public static void execute(int numberOfThreads, Runnable task) { + Thread[] threads = new Thread[numberOfThreads]; + for (int i = 0; i < numberOfThreads; i++) { + threads[i] = new Thread(task); + } + + for (int i = 0; i < numberOfThreads; i++) { + threads[i].start(); } - for (Thread t : threads) { - t.join(); + + for (int i = 0; i < numberOfThreads; i++) { + try { + threads[i].join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } } } 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 d8db8c2f0db6..22407a768ed0 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 @@ -28,9 +28,10 @@ import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.jfr.JfrEvent; import org.junit.Test; +import com.oracle.svm.core.jfr.JfrEvent; + import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java index 8cd8cffa05ec..c07eda88d71a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java @@ -33,7 +33,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; @@ -47,15 +46,14 @@ * Check to make sure 1. The events that are emitted are found in the stream 2. The resulting JFR * dump is readable and events can be read that match the events that were streamed. */ -public class TestStreamingBasic extends StreamingTest { +public class TestJfrStreamingBasic extends JfrStreamingTest { private static final int WAIT_TIMEOUT = 20; private static final int THREADS = 3; private static final int EXPECTED_EVENTS = THREADS * 2; - private final Helper helper = new Helper(); - private final AtomicInteger seenEvents = new AtomicInteger(0); + private final MonitorWaitHelper helper = new MonitorWaitHelper(); private final Set seenThreads = new HashSet<>(); - private final AtomicInteger flushes = new AtomicInteger(0); + private boolean firstFlush = true; @Override public String[] getTestedEvents() { @@ -64,67 +62,49 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { + int count = 0; List events = getEvents(); for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); - if (event. getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (event. getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { + count++; seenThreads.remove(eventThread); } } - assertTrue("Not all expected monitor wait events were found in the JFR file", seenThreads.isEmpty()); + assertEquals(EXPECTED_EVENTS, count); + assertTrue(seenThreads.isEmpty()); } @Test public void test() throws Exception { - final var stream = createStream(); + createStream(); stream.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(WAIT_TIMEOUT - 1)).withStackTrace(); stream.onEvent("jdk.JavaMonitorWait", event -> { String thread = event.getThread("eventThread").getJavaName(); - if (!event.getClass("monitorClass").getName().equals(Helper.class.getName())) { + if (!event.getClass("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { return; } seenThreads.add(thread); - seenEvents.incrementAndGet(); }); - Runnable task = () -> { + Runnable eventEmitter = () -> { helper.doEvent(); - emittedEvents.incrementAndGet(); + emittedEventsPerType.incrementAndGet(); }; stream.onFlush(() -> { - if (flushes.getAndIncrement() == 0) { - Stressor.execute(THREADS, task); + if (firstFlush) { + firstFlush = false; + Stressor.execute(THREADS, eventEmitter); } }); - stream.start(); + startStream(); + Stressor.execute(THREADS, eventEmitter); - Stressor.execute(THREADS, task); - - /* Wait until all events have been emitted. */ - while (emittedEvents.get() < EXPECTED_EVENTS) { - Thread.sleep(10); - } - /* At this point we can expect to have found all the events after the next 2 flushes. */ - int flushLimit = flushes.get() + 2; - while (seenEvents.get() < EXPECTED_EVENTS && flushes.get() < flushLimit) { - Thread.sleep(10); - } - - assertEquals("Not all expected monitor wait events were found in the JFR stream.", EXPECTED_EVENTS, seenEvents.get()); + waitUntilTrue(() -> emittedEventsPerType.get() == EXPECTED_EVENTS); + waitUntilTrue(() -> seenThreads.size() == EXPECTED_EVENTS); closeStream(); } - - static class Helper { - public synchronized void doEvent() { - try { - /* Emits a JFR wait event. */ - this.wait(WAIT_TIMEOUT); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java index a3d3e2bc1e7a..a2701a872bf5 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java @@ -26,10 +26,6 @@ package com.oracle.svm.test.jfr; -import static org.junit.Assert.assertFalse; - -import java.io.File; -import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -43,37 +39,49 @@ /** * Check to make sure 1. All events are accounted for when using streaming (even when there are very - * many events generated). This test also forces a chunk rotation after the first flush as a sanity - * check for potential flush/rotation clashes. + * many events generated). This test also reduces the maximum JFR file size to force chunk + * rotations. */ - -public class TestStreamingCount extends JfrStreamingTest { +public class TestJfrStreamingCount extends JfrStreamingTest { private static final int THREADS = 8; - private static final int COUNT = 1024; - private static final int EXPECTED_EVENTS = THREADS * COUNT; - private AtomicLong remainingClassEvents = new AtomicLong(EXPECTED_EVENTS); - private AtomicLong remainingIntegerEvents = new AtomicLong(EXPECTED_EVENTS); - private AtomicLong remainingStringEvents = new AtomicLong(EXPECTED_EVENTS); - volatile int flushes = 0; + private static final int COUNT = 4 * 1024; + private static final int EXPECTED_EVENTS_PER_TYPE = THREADS * COUNT; + private static final int EXPECTED_TOTAL_EVENTS = EXPECTED_EVENTS_PER_TYPE * 3; + + private final AtomicLong classEvents = new AtomicLong(0); + private final AtomicLong integerEvents = new AtomicLong(0); + private final AtomicLong stringEvents = new AtomicLong(0); @Override public String[] getTestedEvents() { - return new String[]{"com.jfr.String"}; + return new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class"}; } @Override public void validateEvents() throws Throwable { - // Tally up a selection of the events in the dump as a quick check they match the expected - // number. - List events = getEvents(dumpLocation); - if (events.size() != EXPECTED_EVENTS) { + List events = getEvents(); + if (events.size() != EXPECTED_TOTAL_EVENTS) { throw new Exception("Not all expected events were found in the JFR file"); } } @Test public void test() throws Exception { - Runnable r = () -> { + createStream(); + + stream.onEvent("com.jfr.Class", event -> { + classEvents.incrementAndGet(); + }); + stream.onEvent("com.jfr.Integer", event -> { + integerEvents.incrementAndGet(); + }); + stream.onEvent("com.jfr.String", event -> { + stringEvents.incrementAndGet(); + }); + + startStream(); + + Runnable eventEmitter = () -> { for (int i = 0; i < COUNT; i++) { StringEvent stringEvent = new StringEvent(); stringEvent.message = "StringEvent has been generated as part of TestConcurrentEvents."; @@ -86,53 +94,15 @@ public void test() throws Exception { ClassEvent classEvent = new ClassEvent(); classEvent.clazz = Math.class; classEvent.commit(); - emittedEvents.incrementAndGet(); - } - }; - var rs = createStream(); - rs.enable("com.jfr.String"); - rs.enable("com.jfr.Integer"); - rs.enable("com.jfr.Class"); - rs.onEvent("com.jfr.Class", event -> { - remainingClassEvents.decrementAndGet(); - }); - rs.onEvent("com.jfr.Integer", event -> { - remainingIntegerEvents.decrementAndGet(); - }); - rs.onEvent("com.jfr.String", event -> { - remainingStringEvents.decrementAndGet(); - }); - - File directory = new File("."); - dumpLocation = new File(directory.getAbsolutePath(), "TestStreamingCount.jfr").toPath(); - - Runnable rotateChunk = () -> { - try { - if (flushes == 0) { - rs.dump(dumpLocation); // force chunk rotation - } - } catch (IOException e) { - throw new RuntimeException(e); - } catch (Exception e) { - throw new RuntimeException(e); + emittedEventsPerType.incrementAndGet(); } - flushes++; }; + Stressor.execute(THREADS, eventEmitter); - rs.onFlush(rotateChunk); - rs.startAsync(); - Stressor.execute(THREADS, r); - - while (emittedEvents.get() < EXPECTED_EVENTS) { - Thread.sleep(10); - } + waitUntilTrue(() -> emittedEventsPerType.get() == EXPECTED_EVENTS_PER_TYPE); + waitUntilTrue(() -> classEvents.get() == EXPECTED_EVENTS_PER_TYPE && integerEvents.get() == EXPECTED_EVENTS_PER_TYPE && stringEvents.get() == EXPECTED_EVENTS_PER_TYPE); - int flushCount = flushes; - while (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || remainingStringEvents.get() > 0) { - assertFalse("Not all expected events were found in the stream. Class:" + remainingClassEvents.get() + " Integer:" + remainingIntegerEvents.get() + " String:" + remainingStringEvents.get(), - flushes > (flushCount + 1) && (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || remainingStringEvents.get() > 0)); - } closeStream(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java index 3a4b953141e2..026e79bf8705 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java @@ -26,11 +26,7 @@ package com.oracle.svm.test.jfr; -import static org.junit.Assert.assertFalse; - -import java.io.File; -import java.io.IOException; -import java.time.Duration; +import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; @@ -39,118 +35,96 @@ import com.oracle.svm.test.jfr.events.IntegerEvent; import com.oracle.svm.test.jfr.events.StringEvent; +import jdk.jfr.consumer.RecordedEvent; + /** - * This test induces repeated chunk rotations and spawns several threads that create java and native - * events. The goal of this test is to repeatedly create and remove nodes from the - * JfrBufferNodeLinkedList. Unlike TestStreamingCount, each thread in this test will only do a small - * amount of work before dying. 80 Threads in total should spawn. - * + * This test spawns several threads that create Java and native events. The goal of this test is to + * repeatedly create and remove nodes from the {@link com.oracle.svm.core.jfr.JfrBufferList}. Unlike + * {@link TestJfrStreamingCount}, each thread in this test will only do a small amount of work + * before dying. */ - -public class TestStreamingStress extends JfrStreamingTest { - private static final int THREADS = 8; - private static final int COUNT = 10; - private static final int EXPECTED_EVENTS = THREADS * COUNT * 10; - final Helper helper = new Helper(); - private int iterations = 10; - private AtomicLong remainingClassEvents = new AtomicLong(EXPECTED_EVENTS); - private AtomicLong remainingIntegerEvents = new AtomicLong(EXPECTED_EVENTS); - private AtomicLong remainingStringEvents = new AtomicLong(EXPECTED_EVENTS); - private AtomicLong remainingWaitEvents = new AtomicLong(EXPECTED_EVENTS); - volatile int flushes = 0; - volatile boolean doneCollection = false; +public class TestJfrStreamingStress extends JfrStreamingTest { + private static final int THREADS = 32; + private static final int ITERATIONS = 100; + private static final int EXPECTED_EVENTS_PER_TYPE = THREADS * ITERATIONS; + private static final int EXPECTED_TOTAL_EVENTS = EXPECTED_EVENTS_PER_TYPE * 4; + + private final MonitorWaitHelper helper = new MonitorWaitHelper(); + private final AtomicLong classEvents = new AtomicLong(0); + private final AtomicLong integerEvents = new AtomicLong(0); + private final AtomicLong stringEvents = new AtomicLong(0); + private final AtomicLong waitEvents = new AtomicLong(0); @Override public String[] getTestedEvents() { - return new String[]{"com.jfr.String"}; + return new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class", "jdk.JavaMonitorWait"}; } - @Test - public void test() throws Exception { - Runnable r = () -> { - for (int i = 0; i < COUNT; i++) { - StringEvent stringEvent = new StringEvent(); - stringEvent.message = "StringEvent has been generated as part of TestConcurrentEvents."; - stringEvent.commit(); - - IntegerEvent integerEvent = new IntegerEvent(); - integerEvent.number = Integer.MAX_VALUE; - integerEvent.commit(); - - ClassEvent classEvent = new ClassEvent(); - classEvent.clazz = Math.class; - classEvent.commit(); - try { - helper.doEvent(); - } catch (InterruptedException e) { - throw new RuntimeException(e); + @Override + public void validateEvents() throws Throwable { + int otherMonitorWaitEvents = 0; + List events = getEvents(); + for (RecordedEvent event : events) { + if (event.getEventType().getName().equals("jdk.JavaMonitorWait")) { + if (!event.getClass("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { + otherMonitorWaitEvents++; } - emittedEvents.incrementAndGet(); } - }; - var rs = createStream(); - rs.enable("com.jfr.String"); - rs.enable("com.jfr.Integer"); - rs.enable("com.jfr.Class"); - rs.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofNanos(0)); - rs.onEvent("com.jfr.Class", event -> { - remainingClassEvents.decrementAndGet(); + } + + if (events.size() - otherMonitorWaitEvents != EXPECTED_TOTAL_EVENTS) { + throw new Exception("Not all expected events were found in the JFR file"); + } + } + + @Test + public void test() throws Exception { + createStream(); + + stream.onEvent("com.jfr.Class", event -> { + classEvents.incrementAndGet(); }); - rs.onEvent("com.jfr.Integer", event -> { - remainingIntegerEvents.decrementAndGet(); + stream.onEvent("com.jfr.Integer", event -> { + integerEvents.incrementAndGet(); }); - rs.onEvent("com.jfr.String", event -> { - remainingStringEvents.decrementAndGet(); + stream.onEvent("com.jfr.String", event -> { + stringEvents.incrementAndGet(); }); - rs.onEvent("jdk.JavaMonitorWait", event -> { - if (!event.getClass("monitorClass").getName().equals(Helper.class.getName())) { + stream.onEvent("jdk.JavaMonitorWait", event -> { + if (!event.getClass("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { return; } - remainingWaitEvents.decrementAndGet(); + waitEvents.incrementAndGet(); }); - File directory = new File("."); - dumpLocation = new File(directory.getAbsolutePath(), "TestStreamingStress.jfr").toPath(); + startStream(); - Runnable rotateChunk = () -> { - try { - if (flushes % 3 == 0 && !doneCollection) { - rs.dump(dumpLocation); // force chunk rotation - } - } catch (IOException e) { - throw new RuntimeException(e); - } catch (Exception e) { - throw new RuntimeException(e); - } - flushes++; + Runnable eventEmitter = () -> { + StringEvent stringEvent = new StringEvent(); + stringEvent.message = "StringEvent has been generated as part of TestConcurrentEvents."; + stringEvent.commit(); + + IntegerEvent integerEvent = new IntegerEvent(); + integerEvent.number = Integer.MAX_VALUE; + integerEvent.commit(); + + ClassEvent classEvent = new ClassEvent(); + classEvent.clazz = Math.class; + classEvent.commit(); + + helper.doEvent(); + + emittedEventsPerType.incrementAndGet(); }; - rs.onFlush(rotateChunk); - rs.startAsync(); - while (iterations > 0) { - Stressor.execute(THREADS, r); - iterations--; - } - while (emittedEvents.get() < EXPECTED_EVENTS) { - Thread.sleep(10); + for (int i = 0; i < ITERATIONS; i++) { + Stressor.execute(THREADS, eventEmitter); } - int flushCount = flushes; - while (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || remainingStringEvents.get() > 0 || remainingWaitEvents.get() > 0) { - assertFalse("Not all expected events were found in the stream. Class:" + remainingClassEvents.get() + " Integer:" + remainingIntegerEvents.get() + " String:" + - remainingStringEvents.get() + " Wait:" + remainingWaitEvents.get(), - flushes > (flushCount + 1) && (remainingClassEvents.get() > 0 || remainingIntegerEvents.get() > 0 || - remainingStringEvents.get() > 0 || remainingWaitEvents.get() > 0)); - } - doneCollection = true; + waitUntilTrue(() -> emittedEventsPerType.get() == EXPECTED_EVENTS_PER_TYPE); + waitUntilTrue(() -> classEvents.get() == EXPECTED_EVENTS_PER_TYPE && integerEvents.get() == EXPECTED_EVENTS_PER_TYPE && stringEvents.get() == EXPECTED_EVENTS_PER_TYPE && + waitEvents.get() == EXPECTED_EVENTS_PER_TYPE); - rs.dump(dumpLocation); closeStream(); } - - static class Helper { - public synchronized void doEvent() throws InterruptedException { - wait(0, 1); - } - } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java index 8f46e6f61841..1159fb89c362 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/EndStreamEvent.java @@ -26,16 +26,15 @@ package com.oracle.svm.test.jfr.events; +import jdk.jfr.Description; +import jdk.jfr.Event; import jdk.jfr.Label; import jdk.jfr.Name; import jdk.jfr.StackTrace; -import jdk.jfr.Description; -import jdk.jfr.Event; @Name("com.jfr.EndStream") @Label("End Stream Event") @Description("Signals to end stream") @StackTrace(false) public class EndStreamEvent extends Event { - @Label("Message") public String message; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StartStreamEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StartStreamEvent.java new file mode 100644 index 000000000000..a6fc386b7ade --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/events/StartStreamEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. 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.events; + +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("com.jfr.StartStream") +@Label("Start Stream Event") +@Description("Signals thread the stream was started") +@StackTrace(false) +public class StartStreamEvent extends Event { +} From 51d19cb51cbd28f66724d00e41ac659758e72a25 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 6 Mar 2023 14:06:12 +0100 Subject: [PATCH 68/72] More fixes and refactorings. --- .../AbstractUninterruptibleHashtable.java | 1 - .../UninterruptibleEntry.java | 4 +- .../collections/UninterruptibleHashtable.java | 4 +- .../oracle/svm/core/jfr/JfrBufferList.java | 9 -- .../oracle/svm/core/jfr/JfrChunkWriter.java | 21 +-- .../svm/core/jfr/JfrMethodRepository.java | 4 +- .../svm/core/jfr/JfrRecorderThread.java | 21 ++- .../oracle/svm/core/jfr/JfrRepository.java | 13 +- .../svm/core/jfr/JfrStackTraceRepository.java | 29 ++-- .../svm/core/jfr/JfrSymbolRepository.java | 14 +- .../svm/core/jfr/JfrThreadRepository.java | 4 + .../svm/core/jfr/JfrTypeRepository.java | 32 +++-- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 13 +- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 10 ++ .../svm/core/jfr/traceid/JfrTraceId.java | 3 +- .../svm/core/jfr/traceid/JfrTraceIdMap.java | 8 ++ .../oracle/svm/core/jfr/utils/JfrVisited.java | 3 +- .../svm/core/jfr/utils/JfrVisitedTable.java | 2 +- .../JavaLangThreadGroupSubstitutions.java | 8 +- .../{JfrTest.java => AbstractJfrTest.java} | 136 +++++++++--------- .../{utils/Jfr.java => JfrRecordingTest.java} | 47 +++--- .../oracle/svm/test/jfr/JfrStreamingTest.java | 39 +++-- .../oracle/svm/test/jfr/TestClassEvent.java | 13 +- .../com/oracle/svm/test/jfr/TestGCEvents.java | 13 +- .../test/jfr/TestJavaMonitorEnterEvent.java | 8 +- .../test/jfr/TestJavaMonitorInflateEvent.java | 8 +- .../test/jfr/TestJavaMonitorWaitEvent.java | 11 +- .../TestJavaMonitorWaitInterruptEvent.java | 11 +- .../TestJavaMonitorWaitNotifyAllEvent.java | 11 +- .../jfr/TestJavaMonitorWaitTimeoutEvent.java | 11 +- .../test/jfr/TestJfrExecutionSampleEvent.java | 11 +- .../svm/test/jfr/TestJfrStreamingBasic.java | 23 +-- .../svm/test/jfr/TestJfrStreamingCount.java | 9 +- .../svm/test/jfr/TestJfrStreamingStress.java | 24 ++-- .../TestObjectAllocationInNewTLABEvent.java | 8 +- .../svm/test/jfr/TestStackTraceEvent.java | 13 +- .../oracle/svm/test/jfr/TestStringEvent.java | 13 +- .../oracle/svm/test/jfr/TestThreadEvent.java | 13 +- .../svm/test/jfr/TestThreadSleepEvent.java | 7 +- .../svm/test/jfr/utils/JfrFileParser.java | 45 +++--- .../oracle/svm/test/jfr/utils/LocalJfr.java | 75 ---------- .../poolparsers/ClassConstantPoolParser.java | 1 + .../ClassLoaderConstantPoolParser.java | 10 +- .../utils/poolparsers/ConstantPoolParser.java | 14 +- .../PackageConstantPoolParser.java | 10 +- .../StacktraceConstantPoolParser.java | 10 +- .../poolparsers/SymbolConstantPoolParser.java | 6 + .../ThreadGroupConstantPoolParser.java | 11 +- 48 files changed, 438 insertions(+), 376 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{jdk => collections}/UninterruptibleEntry.java (94%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{JfrTest.java => AbstractJfrTest.java} (66%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{utils/Jfr.java => JfrRecordingTest.java} (54%) delete mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java index fc72b9fcb7f7..cc193fac091e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java @@ -34,7 +34,6 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; -import com.oracle.svm.core.jdk.UninterruptibleEntry; /** * An uninterruptible hashtable with a fixed size that uses chaining in case of a collision. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleEntry.java similarity index 94% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleEntry.java index a044d3e57878..8d3ac409562c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleEntry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleEntry.java @@ -22,14 +22,12 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jdk; +package com.oracle.svm.core.collections; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.PointerBase; -import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; - /** * The common interface for the LinkedList entries that can be used in an * {@link AbstractUninterruptibleHashtable}. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java index 4b9b9a85d504..e13d29a26c6b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/UninterruptibleHashtable.java @@ -25,12 +25,10 @@ package com.oracle.svm.core.collections; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jdk.UninterruptibleEntry; /** * Common interface for all uninterruptible hashtable implementations. Please note that we don't use - * generics as this sometimes breaks the {@link Uninterruptible} annotation when ECJ is used for - * compiling the Java sources. + * generics as this may break the {@link Uninterruptible} annotations. */ public interface UninterruptibleHashtable { /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index b1d2455b3f75..2d1fedd2a176 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -57,15 +57,6 @@ public class JfrBufferList { @SuppressWarnings("unused") private volatile int lock; private JfrBufferNode head; - // TEMP (chaeubl): fix the case that flushing is disabled - then, the individual threads - // should free their data immediately, otherwise, we would leak memory... can this option be - // changed at runtime? then this tricky... - - // TEMP (chaeubl): we use a second lock for iteration & removal - if an iteration is in - // progress, then there is no need to remove the nodes because they will be removed - // periodically. Otherwise, remove the node right away. Would avoid the garbage issues, see - // comment below. - @Platforms(Platform.HOSTED_ONLY.class) public JfrBufferList() { } 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 1d384eada07b..ef72b0dba9f3 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 @@ -165,28 +165,25 @@ public void openFile(String outputFile) { } @Uninterruptible(reason = "Prevent safepoints as those could change the flushed position.") - public boolean write(JfrBuffer buffer) { + public void write(JfrBuffer buffer) { assert lock.isOwner(); assert buffer.isNonNull(); assert buffer.getBufferType() == JfrBufferType.C_HEAP || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { - return false; + return; } boolean success = getFileSupport().write(fd, JfrBufferAccess.getFlushedPos(buffer), unflushedSize); - JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); - - if (!success) { - /* We lost some data because the write failed. */ - return false; + if (success) { + JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); } - return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); } public void flush() { assert lock.isOwner(); + flushStorage(true); writeThreadCheckpoint(true); @@ -296,13 +293,12 @@ private byte getAndIncrementGeneration() { } private void writeFlushCheckpoint(boolean flush) { - // TEMP (chaeubl): this should also check if there is any data - otherwise it is useless to - // emit the event. writeCheckpointEvent(JfrCheckpointType.Flush, flushCheckpointRepos, newChunk, flush); } private void writeThreadCheckpoint(boolean flush) { assert threadCheckpointRepos.length == 1 && threadCheckpointRepos[0] == SubstrateJVM.getThreadRepo(); + /* The code below is only atomic enough because the epoch can't change while flushing. */ if (SubstrateJVM.getThreadRepo().hasUnflushedData()) { writeCheckpointEvent(JfrCheckpointType.Threads, threadCheckpointRepos, false, flush); } @@ -576,6 +572,11 @@ private void traverseThreadLocalBuffers(JfrBufferList list, boolean flush) { } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isLockedByCurrentThread() { + return lock.isOwner(); + } + public enum StringEncoding { NULL(0), EMPTY_STRING(1), diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 852bf15ca570..7ca98a83cb7c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -56,7 +56,7 @@ public void teardown() { epochData1.teardown(); } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getMethodId(Class clazz, String methodName, int methodId) { assert clazz != null; assert methodName != null; @@ -106,7 +106,7 @@ public long getMethodId(Class clazz, String methodName, int methodId) { } @Override - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public int write(JfrChunkWriter writer, boolean flush) { mutex.lockNoTransition(); try { 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 9197f1f42c88..bb44aaa9fc98 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 @@ -125,15 +125,16 @@ private void persistBuffers(JfrChunkWriter chunkWriter) { JfrBufferList buffers = globalMemory.getBuffers(); JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - boolean shouldNotify = tryPersistBuffer(chunkWriter, node); - if (shouldNotify) { - Object chunkRotationMonitor = getChunkRotationMonitor(); - synchronized (chunkRotationMonitor) { - chunkRotationMonitor.notifyAll(); - } - } + tryPersistBuffer(chunkWriter, node); node = node.getNext(); } + + if (chunkWriter.shouldRotateDisk()) { + Object chunkRotationMonitor = getChunkRotationMonitor(); + synchronized (chunkRotationMonitor) { + chunkRotationMonitor.notifyAll(); + } + } } private static Object getChunkRotationMonitor() { @@ -145,20 +146,18 @@ private static Object getChunkRotationMonitor() { } @Uninterruptible(reason = "Locks a BufferNode.") - private static boolean tryPersistBuffer(JfrChunkWriter chunkWriter, JfrBufferNode node) { + private static void tryPersistBuffer(JfrChunkWriter chunkWriter, JfrBufferNode node) { if (JfrBufferNodeAccess.tryLock(node)) { try { JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); if (isFullEnough(buffer)) { - boolean shouldNotify = chunkWriter.write(buffer); + chunkWriter.write(buffer); JfrBufferAccess.reinitialize(buffer); - return shouldNotify; } } finally { JfrBufferNodeAccess.unlock(node); } } - return false; } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java index d5e217bae41e..1de54332ca0f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java @@ -27,10 +27,15 @@ import com.oracle.svm.core.Uninterruptible; /** - * Epoch-based storage for metadata. Switching the epoch and iterating the collected data may only - * be done at a safepoint. All methods that manipulate data in the constant pool must be - * {@link Uninterruptible} to guarantee that a safepoint always sees a consistent state. Otherwise, - * other JFR code could see partially added data when it tries to iterate the data at a safepoint. + * Epoch-based storage for metadata. Switching the epoch may only be done at a safepoint. All + * methods that manipulate data in the constant pool must be {@link Uninterruptible} to guarantee + * that a safepoint always sees a consistent state. Otherwise, other JFR code could see partially + * added data when it tries to iterate the data at a safepoint. + * + * Some repositories (e.g., {@link JfrTypeRepository}) return stable JFR trace IDs (i.e., the trace + * id does not change if the epoch changes). However, the corresponding data (e.g., the type) is + * only marked as used in a certain epoch, so callers must always be aware that the returned trace + * ID is only valid for a specific epoch, no matter if the trace ID is stable or not. */ public interface JfrRepository { 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 8ce7fd1a2f4f..69d1732fd1e7 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 @@ -43,9 +43,9 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; +import com.oracle.svm.core.collections.UninterruptibleEntry; import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.headers.LibC; -import com.oracle.svm.core.jdk.UninterruptibleEntry; import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; @@ -95,7 +95,7 @@ public void teardown() { } @NeverInline("Starting a stack walk in the caller frame.") - @Uninterruptible(reason = "Accesses a sampler buffer.") + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getStackTraceId(int skipCount) { if (DeoptimizationSupport.enabled()) { /* Stack traces are not supported if JIT compilation is used (GR-43686). */ @@ -130,7 +130,7 @@ public long getStackTraceId(int skipCount) { } } - @Uninterruptible(reason = "Accesses a sampler buffer.") + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) private long storeDeduplicatedStackTrace(SamplerSampleWriterData data) { if (SamplerSampleWriter.isValid(data)) { /* There is a valid stack trace in the buffer, so deduplicate and store it. */ @@ -160,7 +160,7 @@ private long storeDeduplicatedStackTrace(SamplerSampleWriterData data) { * NOTE: the returned value is only valid until the JFR epoch changes. So, this method may only * be used from uninterruptible code. */ - @Uninterruptible(reason = "Prevent epoch change. Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.", callerMustBe = true) public JfrStackTraceTableEntry getOrPutStackTrace(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) { mutex.lockNoTransition(); try { @@ -170,7 +170,7 @@ public JfrStackTraceTableEntry getOrPutStackTrace(Pointer start, UnsignedWord si } } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.") private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) { assert size.rawValue() == (int) size.rawValue(); @@ -214,7 +214,7 @@ private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord } } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.", callerMustBe = true) public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { mutex.lockNoTransition(); try { @@ -226,14 +226,14 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { } @Override - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public int write(JfrChunkWriter writer, boolean flush) { if (flush) { /* - * Flushing is currently not support for the stack traces. When a stack trace is + * Flushing is not support for stack traces at the moment. When a stack trace is * serialized, the methods getOrPutStackTrace() and commitSerializedStackTrace() are - * used, which are not atomic enough (i.e., a flush could destroy the JfrBuffer of the - * epoch, while it is being written). + * used. The lock is not held all the time, so a flush could destroy the JfrBuffer of + * the epoch, while it is being written. */ return EMPTY; } @@ -257,17 +257,14 @@ public int write(JfrChunkWriter writer, boolean flush) { } } - @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) private JfrStackTraceEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); return epoch ? epochData0 : epochData1; } - /** - * NOTE: the returned value is only valid until the JFR epoch changes. So, this method may only - * be called from uninterruptible code. Returns null if the buffer allocation failed. - */ - @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) + /** Returns null if the buffer allocation failed. */ + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public JfrBuffer getCurrentBuffer() { JfrStackTraceEpochData epochData = getEpochData(false); if (epochData.buffer.isNull()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 4b84a19aca13..a75dac8c7fcd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -37,8 +37,8 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; +import com.oracle.svm.core.collections.UninterruptibleEntry; import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.jdk.UninterruptibleEntry; import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; @@ -66,12 +66,12 @@ public void teardown() { epochData1.teardown(); } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getSymbolId(String imageHeapString, boolean previousEpoch) { return getSymbolId(imageHeapString, previousEpoch, false); } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean replaceDotWithSlash) { if (imageHeapString == null) { return 0; @@ -129,7 +129,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r } @Override - @Uninterruptible(reason = "Must not be interrupted for operations that emit events, potentially writing to this pool.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public int write(JfrChunkWriter writer, boolean flush) { mutex.lockNoTransition(); try { @@ -150,7 +150,7 @@ public int write(JfrChunkWriter writer, boolean flush) { } } - @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true) + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) private JfrSymbolEpochData getEpochData(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); return epoch ? epochData0 : epochData1; @@ -207,7 +207,9 @@ protected boolean isEqual(UninterruptibleEntry v0, UninterruptibleEntry v1) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected UninterruptibleEntry copyToHeap(UninterruptibleEntry symbolOnStack) { JfrSymbol result = (JfrSymbol) copyToHeap(symbolOnStack, SizeOf.unsigned(JfrSymbol.class)); - result.setId(++nextId); + if (result.isNonNull()) { + result.setId(++nextId); + } return result; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 420a7f3a9def..a6f830baf680 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -45,6 +45,10 @@ /** * Repository that collects all metadata about threads and thread groups. + * + * Note that the JFR trace ID for threads is the only trace ID that is not epoch-specific: the trace + * ID is stable over epochs and all alive threads are re-registered right away when the epoch + * changes. */ public final class JfrThreadRepository implements JfrRepository { private static final int VIRTUAL_THREAD_GROUP_ID = 1; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index bbb79eb207fb..703ba1bed86b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -54,7 +54,11 @@ public class JfrTypeRepository implements JfrRepository { public JfrTypeRepository() { } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void teardown() { + clearEpochData(); + } + + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getClassId(Class clazz) { return JfrTraceId.load(clazz); } @@ -140,10 +144,9 @@ private int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush } private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz, boolean flush) { - JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); - writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); // key + writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); writer.writeCompressedLong(getClassLoaderId(typeInfo, clazz.getClassLoader())); - writer.writeCompressedLong(symbolRepo.getSymbolId(clazz.getName(), !flush, true)); + writer.writeCompressedLong(getSymbolId(writer, clazz.getName(), flush, true)); writer.writeCompressedLong(getPackageId(typeInfo, clazz.getPackage())); writer.writeCompressedLong(clazz.getModifiers()); if (JavaVersionUtil.JAVA_SPEC >= 17) { @@ -151,6 +154,16 @@ private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz } } + @Uninterruptible(reason = "Needed for JfrSymbolRepository.getSymbolId().") + private static long getSymbolId(JfrChunkWriter writer, String symbol, boolean flush, boolean replaceDotWithSlash) { + /* + * The result is only valid for the current epoch, but the epoch can't change while the + * current thread holds the JfrChunkWriter lock. + */ + assert writer.isLockedByCurrentThread(); + return SubstrateJVM.getSymbolRepository().getSymbolId(symbol, !flush, replaceDotWithSlash); + } + private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { if (typeInfo.packages.isEmpty()) { return EMPTY; @@ -165,9 +178,8 @@ private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flus } private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flush) { - JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(pkgInfo.id); // id - writer.writeCompressedLong(symbolRepo.getSymbolId(pkgName, !flush, true)); + writer.writeCompressedLong(getSymbolId(writer, pkgName, flush, true)); writer.writeCompressedLong(getModuleId(typeInfo, pkgInfo.module)); writer.writeBoolean(false); // exported } @@ -186,9 +198,8 @@ private int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush } private void writeModule(TypeInfo typeInfo, JfrChunkWriter writer, Module module, long id, boolean flush) { - JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); - writer.writeCompressedLong(symbolRepo.getSymbolId(module.getName(), !flush)); + writer.writeCompressedLong(getSymbolId(writer, module.getName(), flush, false)); writer.writeCompressedLong(0); // Version, e.g. "11.0.10-internal" writer.writeCompressedLong(0); // Location, e.g. "jrt:/java.base" writer.writeCompressedLong(getClassLoaderId(typeInfo, module.getClassLoader())); @@ -208,14 +219,13 @@ private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, b } private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { - JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); writer.writeCompressedLong(id); if (cl == null) { writer.writeCompressedLong(0); - writer.writeCompressedLong(symbolRepo.getSymbolId("bootstrap", !flush)); + writer.writeCompressedLong(getSymbolId(writer, "bootstrap", flush, false)); } else { writer.writeCompressedLong(JfrTraceId.getTraceId(cl.getClass())); - writer.writeCompressedLong(symbolRepo.getSymbolId(cl.getName(), !flush)); + writer.writeCompressedLong(getSymbolId(writer, cl.getName(), flush, false)); } } 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 0b5ca560049a..d480a1221076 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 @@ -238,12 +238,13 @@ public boolean destroyJFR() { threadRepo.teardown(); stackTraceRepo.teardown(); methodRepo.teardown(); + typeRepo.teardown(); initialized = false; return true; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getStackTraceId(long eventTypeId, int skipCount) { if (isStackTraceEnabled(eventTypeId)) { return getStackTraceId(skipCount); @@ -255,12 +256,12 @@ public long getStackTraceId(long eventTypeId, int skipCount) { /** * See {@link JVM#getStackTraceId}. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getStackTraceId(int skipCount) { return stackTraceRepo.getStackTraceId(skipCount); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getStackTraceId(JfrEvent eventType, int skipCount) { return getStackTraceId(eventType.getId(), skipCount); } @@ -332,7 +333,7 @@ public void endRecording() { /** * See {@link JVM#getClassId}. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) public long getClassId(Class clazz) { return typeRepo.getClassId(clazz); } @@ -406,8 +407,8 @@ public void setMethodSamplingInterval(long type, long intervalMillis) { JfrExecutionSampler.singleton().setIntervalMillis(intervalMillis); if (intervalMillis > 0) { - SubstrateJVM.get().setStackTraceEnabled(type, true); - SubstrateJVM.get().setEnabled(type, true); + setStackTraceEnabled(type, true); + setEnabled(type, true); } updateSampler(); 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 3dff84fe5b5a..c788d5cf93b8 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 @@ -139,7 +139,12 @@ public long getUnloadedEventClassCount() { /** See {@link JVM#getClassId}. Intrinsified on HotSpot. */ @Substitute + @Uninterruptible(reason = "Needed for SubstrateJVM.getClassId().") public static long getClassId(Class clazz) { + /* + * The result is only valid until the epoch changes but this is fine because EventWriter + * instances are invalidated when the epoch changes. + */ return SubstrateJVM.get().getClassId(clazz); } @@ -159,7 +164,12 @@ public String getPid() { /** See {@link JVM#getStackTraceId}. */ @Substitute + @Uninterruptible(reason = "Needed for SubstrateJVM.getStackTraceId().") public long getStackTraceId(int skipCount) { + /* + * The result is only valid until the epoch changes but this is fine because EventWriter + * instances are invalidated when the epoch changes. + */ return SubstrateJVM.get().getStackTraceId(skipCount); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java index 5ee94008c7c7..79f0eac72bbd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceId.java @@ -107,8 +107,7 @@ private static boolean predicate(Class clazz, long bits) { @Platforms(Platform.HOSTED_ONLY.class) private static void tag(int index, long value) { JfrTraceIdMap map = JfrTraceIdMap.singleton(); - long id = map.getId(index); - map.setId(index, id | value); + map.tag(index, value); } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdMap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdMap.java index 495c69be5623..d919abd93fc4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdMap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdMap.java @@ -65,9 +65,17 @@ long getId(int index) { @Platforms(Platform.HOSTED_ONLY.class) void setId(int index, long id) { + assert traceIDs[index] == -1; traceIDs[index] = id; } + @Platforms(Platform.HOSTED_ONLY.class) + void tag(int index, long value) { + long id = traceIDs[index]; + assert id != -1; + traceIDs[index] = id | value; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) long getId(Class clazz) { long id = traceIDs[getIndex(clazz)]; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java index 12e37cf17caf..377342de7a56 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java @@ -25,10 +25,11 @@ package com.oracle.svm.core.jfr.utils; -import com.oracle.svm.core.jdk.UninterruptibleEntry; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; +import com.oracle.svm.core.collections.UninterruptibleEntry; + @RawStructure public interface JfrVisited extends UninterruptibleEntry { @RawField diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java index 26a9ee56e6eb..0aa3e41ff71c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java @@ -29,7 +29,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; -import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.collections.UninterruptibleEntry; public final class JfrVisitedTable extends AbstractUninterruptibleHashtable { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java index 26d7e1de73aa..27ce6da6ace7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.AnnotateOriginal; import com.oracle.svm.core.annotate.Inject; @@ -40,7 +41,6 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jdk.JDK17OrEarlier; import com.oracle.svm.core.jdk.JDK19OrLater; @@ -112,13 +112,13 @@ final class Target_java_lang_ThreadGroup { * This class assigns a unique id to each thread group, and this unique id is used by JFR. */ class ThreadGroupIdAccessor { - - private static final UninterruptibleUtils.AtomicLong nextID = new UninterruptibleUtils.AtomicLong(0L); + private static final int VIRTUAL_THREAD_GROUP = 1; + private static final UninterruptibleUtils.AtomicLong nextID = new UninterruptibleUtils.AtomicLong(VIRTUAL_THREAD_GROUP + 1); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static long getId(Target_java_lang_ThreadGroup that) { if (that.injectedId == 0) { - that.injectedId = nextID.incrementAndGet(); + that.injectedId = nextID.getAndIncrement(); } return that.injectedId; } 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/AbstractJfrTest.java similarity index 66% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java index 4d92e69464e8..6eac4ee25f5b 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/AbstractJfrTest.java @@ -32,11 +32,11 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.function.BooleanSupplier; import org.graalvm.nativeimage.ImageInfo; @@ -48,20 +48,16 @@ import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.core.util.TimeUtils; -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; +import jdk.jfr.Configuration; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingFile; /** Base class for JFR unit tests. */ -public abstract class JfrTest { - private Jfr jfr; - private Recording recording; +public abstract class AbstractJfrTest { + protected Path jfrFile; @BeforeClass public static void checkForJFR() { @@ -69,40 +65,54 @@ public static void checkForJFR() { } @Before - public void startRecording() throws Throwable { - jfr = new LocalJfr(); - recording = jfr.createRecording(getClass().getName()); - enableEvents(); - jfr.startRecording(recording); + public void beforeTest() throws Throwable { + JfrFileParser.resetConstantPoolParsers(); + + long id = new Random().nextLong(0, Long.MAX_VALUE); + jfrFile = File.createTempFile(getClass().getName() + "-" + id, ".jfr").toPath(); + if (isDebuggingEnabled()) { + System.out.println("Recording: " + jfrFile); + } + + Configuration defaultConfig = Configuration.getConfiguration("default"); + startRecording(defaultConfig); } @After - public void endRecording() throws Throwable { + public void afterTest() throws Throwable { try { - jfr.endRecording(recording); - checkRecording(); - validateEvents(); + stopRecording(); + checkRecording(jfrFile); + validateEvents(getEvents()); } finally { - jfr.cleanupRecording(recording); + if (!isDebuggingEnabled()) { + Files.deleteIfExists(jfrFile); + } } } + protected abstract void startRecording(Configuration config) throws Throwable; + + protected abstract void stopRecording() throws Throwable; + protected abstract String[] getTestedEvents(); - private void enableEvents() { - /* Additionally, enable all events that the test case wants to test explicitly. */ - String[] events = getTestedEvents(); - for (String event : events) { - recording.enable(event); - } - } + protected abstract void validateEvents(List events) throws Throwable; - public void validateEvents() throws Throwable { + protected void checkRecording(Path path) throws AssertionError { + try { + /* Check if file header and constant pools are adequate. */ + JfrFileParser.parse(path); + /* Check if all event are there. */ + checkEvents(path); + } catch (Exception e) { + Assert.fail("Failed to parse recording: " + e.getMessage()); + } } - private void checkEvents() { + protected void checkEvents(Path path) { HashSet seenEvents = new HashSet<>(); - try (RecordingFile recordingFile = new RecordingFile(recording.getDestination())) { + try (RecordingFile recordingFile = new RecordingFile(path)) { while (recordingFile.hasMoreEvents()) { RecordedEvent event = recordingFile.readEvent(); String eventName = event.getEventType().getName(); @@ -119,51 +129,25 @@ private void checkEvents() { } } - private void checkRecording() throws AssertionError { - try { - /* Check if file header and constant pools are adequate. */ - JfrFileParser.parse(recording); - /* Check if all event are there. */ - checkEvents(); - } catch (Exception e) { - 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()); + protected List getEvents() throws IOException { + /* Only return events that are in the list of tested events. */ + ArrayList result = new ArrayList<>(); + for (RecordedEvent event : RecordingFile.readAllEvents(jfrFile)) { + if (isTestedEvent(event)) { + result.add(event); + } } + result.sort(new ChronologicalComparator()); + return result; } - 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); + private boolean isTestedEvent(RecordedEvent event) { + for (String tested : getTestedEvents()) { + if (tested.equals(event.getEventType().getName())) { + return true; + } } - return p; - } - - protected List getEvents() throws IOException { - Path p = makeCopy(ClassUtil.getUnqualifiedName(getClass())); - return getEvents0(p); - } - - protected List getEvents(Path p) throws IOException { - List events = getEvents0(p); - Files.deleteIfExists(p); - return events; - } - - private List getEvents0(Path p) throws IOException { - 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; + return false; } protected static void waitUntilTrue(BooleanSupplier supplier) throws InterruptedException { @@ -177,6 +161,18 @@ protected static void waitUntilTrue(BooleanSupplier supplier) throws Interrupted Thread.sleep(10); } } + + private static boolean isDebuggingEnabled() { + String debugRecording = System.getenv("DEBUG_RECORDING"); + return debugRecording != null && !"false".equals(debugRecording); + } + + private static class ChronologicalComparator implements Comparator { + @Override + public int compare(RecordedEvent e1, RecordedEvent e2) { + return e1.getEndTime().compareTo(e2.getEndTime()); + } + } } class JfrTestFeature implements Feature { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java similarity index 54% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java index a36b79e97c57..0c0b695e9f51 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrRecordingTest.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2020, 2022, Red Hat Inc. All rights reserved. + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -24,22 +24,35 @@ * questions. */ -package com.oracle.svm.test.jfr.utils; - -import java.io.IOException; +package com.oracle.svm.test.jfr; +import jdk.jfr.Configuration; import jdk.jfr.Recording; -/** - * Utility class to handle recording. - */ -public interface Jfr { - - Recording createRecording(String recordingName) throws Exception; - - void startRecording(Recording recording); - - void endRecording(Recording recording) throws Exception; - - void cleanupRecording(Recording recording) throws IOException; +/** Base class for JFR unit tests. */ +public abstract class JfrRecordingTest extends AbstractJfrTest { + private Recording recording; + + @Override + public void startRecording(Configuration config) throws Throwable { + /* Enable a lot of events by default to increase the test coverage. */ + recording = new Recording(config); + recording.setDestination(jfrFile); + enableEvents(); + recording.start(); + } + + @Override + public void stopRecording() { + recording.stop(); + recording.close(); + } + + private void enableEvents() { + /* Additionally, enable all events that the test case wants to test explicitly. */ + String[] events = getTestedEvents(); + for (String event : events) { + recording.enable(event); + } + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java index 2d9bbd13e6b6..a521aa2c57dc 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java @@ -33,18 +33,21 @@ import com.oracle.svm.test.jfr.events.EndStreamEvent; import com.oracle.svm.test.jfr.events.StartStreamEvent; +import jdk.jfr.Configuration; import jdk.jfr.consumer.RecordingStream; -abstract class JfrStreamingTest extends JfrTest { - protected static final int TIMEOUT_MILLIS = 10 * 1000; +abstract class JfrStreamingTest extends AbstractJfrTest { + public static final int JFR_MAX_SIZE = 100 * 1024 * 1024; protected final AtomicInteger emittedEventsPerType = new AtomicInteger(0); protected RecordingStream stream; private volatile boolean streamStarted = false; private volatile boolean streamEndedSuccessfully = false; - protected void createStream() { - stream = new RecordingStream(); + @Override + public void startRecording(Configuration config) throws Throwable { + stream = new RecordingStream(config); + stream.setMaxSize(JFR_MAX_SIZE); stream.enable("com.jfr.StartStream"); stream.onEvent("com.jfr.StartStream", e -> { @@ -56,9 +59,17 @@ protected void createStream() { stream.close(); streamEndedSuccessfully = true; }); + enableEvents(); + startStream(); } - protected void startStream() throws InterruptedException { + @Override + public void stopRecording() throws Throwable { + stream.dump(jfrFile); + closeStream(); + } + + private void startStream() throws InterruptedException { stream.startAsync(); /* Wait until the started thread can handle events. */ waitUntilTrue(() -> { @@ -71,15 +82,25 @@ protected void startStream() throws InterruptedException { }); } - protected void closeStream() throws InterruptedException { - // We require a signal to close the stream, because if we close the stream immediately after - // dumping, the dump may not have had time to finish. + private void closeStream() throws InterruptedException { + /* + * We require a signal to close the stream, because if we close the stream immediately after + * dumping, the dump may not have had time to finish. + */ EndStreamEvent event = new EndStreamEvent(); event.commit(); - stream.awaitTermination(Duration.ofMillis(TIMEOUT_MILLIS)); + stream.awaitTermination(Duration.ofSeconds(10)); assertTrue("unable to find stream end event signal in stream", streamEndedSuccessfully); } + private void enableEvents() { + /* Additionally, enable all events that the test case wants to test explicitly. */ + String[] events = getTestedEvents(); + for (String event : events) { + stream.enable(event); + } + } + static class MonitorWaitHelper { public synchronized void doEvent() { try { 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 8d0aca82a3a5..da65a95cbc80 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,17 +26,28 @@ package com.oracle.svm.test.jfr; +import static org.junit.Assert.assertEquals; + +import java.util.List; + import org.junit.Test; import com.oracle.svm.test.jfr.events.ClassEvent; -public class TestClassEvent extends JfrTest { +import jdk.jfr.consumer.RecordedEvent; + +public class TestClassEvent extends JfrRecordingTest { @Override public String[] getTestedEvents() { return new String[]{"com.jfr.Class"}; } + @Override + protected void validateEvents(List events) throws Throwable { + assertEquals(1, events.size()); + } + @Test public void test() throws Exception { ClassEvent event = new ClassEvent(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestGCEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestGCEvents.java index f26cb0d113c2..1f226d5b95e0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestGCEvents.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestGCEvents.java @@ -26,11 +26,17 @@ 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.jfr.JfrEvent; -public class TestGCEvents extends JfrTest { +import jdk.jfr.consumer.RecordedEvent; + +public class TestGCEvents extends JfrRecordingTest { @Override public String[] getTestedEvents() { return new String[]{ @@ -42,6 +48,11 @@ public String[] getTestedEvents() { }; } + @Override + protected void validateEvents(List events) throws Throwable { + assertTrue(events.size() > 0); + } + @Test public void test() throws Exception { System.gc(); 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 22407a768ed0..9fa6ec32515d 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 @@ -28,6 +28,8 @@ import static org.junit.Assert.assertTrue; +import java.util.List; + import org.junit.Test; import com.oracle.svm.core.jfr.JfrEvent; @@ -36,7 +38,7 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorEnterEvent extends JfrTest { +public class TestJavaMonitorEnterEvent extends JfrRecordingTest { private static final int MILLIS = 60; private final Helper helper = new Helper(); @@ -50,9 +52,9 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { + protected void validateEvents(List events) throws Throwable { boolean found = false; - for (RecordedEvent event : getEvents()) { + for (RecordedEvent event : events) { 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 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 9edcbfde57d6..4580bfb0cbc5 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 @@ -28,6 +28,8 @@ import static org.junit.Assert.assertTrue; +import java.util.List; + import org.junit.Test; import com.oracle.svm.core.jfr.JfrEvent; @@ -37,7 +39,7 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorInflateEvent extends JfrTest { +public class TestJavaMonitorInflateEvent extends JfrRecordingTest { private static final EnterHelper ENTER_HELPER = new EnterHelper(); private static Thread firstThread; private static Thread secondThread; @@ -48,9 +50,9 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { + protected void validateEvents(List events) throws Throwable { boolean foundCauseEnter = false; - for (RecordedEvent event : getEvents()) { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); String monitorClass = event. getValue("monitorClass").getName(); String cause = event.getValue("cause"); 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 db37581af10b..0674179501d6 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 @@ -30,14 +30,17 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.jfr.JfrEvent; +import java.util.List; + import org.junit.Test; +import com.oracle.svm.core.jfr.JfrEvent; + import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitEvent extends JfrTest { +public class TestJavaMonitorWaitEvent extends JfrRecordingTest { private static final int MILLIS = 50; private static final int COUNT = 10; @@ -51,11 +54,11 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { + public void validateEvents(List events) throws Throwable { int prodCount = 0; int consCount = 0; String lastEventThreadName = null; // should alternate if buffer is 1 - for (RecordedEvent event : getEvents()) { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; assertTrue("No event thread", eventThread != null); 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 97252b49e425..9abf11d5ec77 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 @@ -29,15 +29,18 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.jfr.JfrEvent; +import java.util.List; + import org.junit.Assert; import org.junit.Test; +import com.oracle.svm.core.jfr.JfrEvent; + import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitInterruptEvent extends JfrTest { +public class TestJavaMonitorWaitInterruptEvent extends JfrRecordingTest { private static final int MILLIS = 50; private final Helper helper = new Helper(); @@ -54,8 +57,8 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { - for (RecordedEvent event : getEvents()) { + public void validateEvents(List events) throws Throwable { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; if (!eventThread.equals(interrupterThread.getName()) && 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 0aaa1ae23b12..72d204d344b4 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 @@ -29,14 +29,17 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.jfr.JfrEvent; +import java.util.List; + import org.junit.Test; +import com.oracle.svm.core.jfr.JfrEvent; + import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitNotifyAllEvent extends JfrTest { +public class TestJavaMonitorWaitNotifyAllEvent extends JfrRecordingTest { private static final int MILLIS = 50; private final Helper helper = new Helper(); @@ -52,8 +55,8 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { - for (RecordedEvent event : getEvents()) { + protected void validateEvents(List events) throws Throwable { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; if (!eventThread.equals(producerThread1.getName()) && 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 98e0e187e3eb..1114e7b98cde 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 @@ -28,15 +28,18 @@ import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.jfr.JfrEvent; +import java.util.List; + import org.junit.Assert; import org.junit.Test; +import com.oracle.svm.core.jfr.JfrEvent; + import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitTimeoutEvent extends JfrTest { +public class TestJavaMonitorWaitTimeoutEvent extends JfrRecordingTest { private static final int MILLIS = 50; private final Helper helper = new Helper(); @@ -53,8 +56,8 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { - for (RecordedEvent event : getEvents()) { + protected void validateEvents(List events) throws Throwable { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; if (!eventThread.equals(unheardNotifierThread.getName()) && diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java index a751db0b0031..7c51a1910eee 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java @@ -32,26 +32,25 @@ import java.util.List; import java.util.Set; -import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.util.VMError; -import jdk.jfr.consumer.RecordedFrame; import org.junit.Test; +import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.util.VMError; import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; import jdk.jfr.consumer.RecordedStackTrace; import jdk.jfr.consumer.RecordedThread; -public class TestJfrExecutionSampleEvent extends JfrTest { +public class TestJfrExecutionSampleEvent extends JfrRecordingTest { @Override public String[] getTestedEvents() { return new String[]{JfrEvent.ExecutionSample.getName()}; } @Override - public void validateEvents() throws Throwable { - List events = getEvents(); + protected void validateEvents(List events) throws Throwable { assertTrue(events.size() > 0); Set seenThreadIds = new HashSet<>(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java index c07eda88d71a..e9c784ad9e91 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingBasic.java @@ -29,7 +29,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -47,7 +46,6 @@ * dump is readable and events can be read that match the events that were streamed. */ public class TestJfrStreamingBasic extends JfrStreamingTest { - private static final int WAIT_TIMEOUT = 20; private static final int THREADS = 3; private static final int EXPECTED_EVENTS = THREADS * 2; @@ -61,14 +59,13 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { + protected void validateEvents(List events) throws Throwable { int count = 0; - List events = getEvents(); for (RecordedEvent event : events) { - String eventThread = event. getValue("eventThread").getJavaName(); if (event. getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { - count++; + String eventThread = event. getValue("eventThread").getJavaName(); seenThreads.remove(eventThread); + count++; } } assertEquals(EXPECTED_EVENTS, count); @@ -77,14 +74,11 @@ public void validateEvents() throws Throwable { @Test public void test() throws Exception { - createStream(); - stream.enable("jdk.JavaMonitorWait").withThreshold(Duration.ofMillis(WAIT_TIMEOUT - 1)).withStackTrace(); - stream.onEvent("jdk.JavaMonitorWait", event -> { - String thread = event.getThread("eventThread").getJavaName(); - if (!event.getClass("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { - return; + stream.onEvent(JfrEvent.JavaMonitorWait.getName(), event -> { + if (event. getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { + String thread = event.getThread("eventThread").getJavaName(); + seenThreads.add(thread); } - seenThreads.add(thread); }); Runnable eventEmitter = () -> { @@ -99,12 +93,9 @@ public void test() throws Exception { } }); - startStream(); Stressor.execute(THREADS, eventEmitter); waitUntilTrue(() -> emittedEventsPerType.get() == EXPECTED_EVENTS); waitUntilTrue(() -> seenThreads.size() == EXPECTED_EVENTS); - - closeStream(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java index a2701a872bf5..be80c0368c5e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java @@ -58,8 +58,7 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { - List events = getEvents(); + protected void validateEvents(List events) throws Throwable { if (events.size() != EXPECTED_TOTAL_EVENTS) { throw new Exception("Not all expected events were found in the JFR file"); } @@ -67,8 +66,6 @@ public void validateEvents() throws Throwable { @Test public void test() throws Exception { - createStream(); - stream.onEvent("com.jfr.Class", event -> { classEvents.incrementAndGet(); }); @@ -79,8 +76,6 @@ public void test() throws Exception { stringEvents.incrementAndGet(); }); - startStream(); - Runnable eventEmitter = () -> { for (int i = 0; i < COUNT; i++) { StringEvent stringEvent = new StringEvent(); @@ -102,7 +97,5 @@ public void test() throws Exception { waitUntilTrue(() -> emittedEventsPerType.get() == EXPECTED_EVENTS_PER_TYPE); waitUntilTrue(() -> classEvents.get() == EXPECTED_EVENTS_PER_TYPE && integerEvents.get() == EXPECTED_EVENTS_PER_TYPE && stringEvents.get() == EXPECTED_EVENTS_PER_TYPE); - - closeStream(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java index 026e79bf8705..bdaf4f2055fe 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java @@ -31,10 +31,12 @@ import org.junit.Test; +import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.test.jfr.events.ClassEvent; import com.oracle.svm.test.jfr.events.IntegerEvent; import com.oracle.svm.test.jfr.events.StringEvent; +import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; /** @@ -57,16 +59,15 @@ public class TestJfrStreamingStress extends JfrStreamingTest { @Override public String[] getTestedEvents() { - return new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class", "jdk.JavaMonitorWait"}; + return new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class", JfrEvent.JavaMonitorWait.getName()}; } @Override - public void validateEvents() throws Throwable { + protected void validateEvents(List events) throws Throwable { int otherMonitorWaitEvents = 0; - List events = getEvents(); for (RecordedEvent event : events) { - if (event.getEventType().getName().equals("jdk.JavaMonitorWait")) { - if (!event.getClass("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { + if (event.getEventType().getName().equals(JfrEvent.JavaMonitorWait.getName())) { + if (!event. getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { otherMonitorWaitEvents++; } } @@ -79,8 +80,6 @@ public void validateEvents() throws Throwable { @Test public void test() throws Exception { - createStream(); - stream.onEvent("com.jfr.Class", event -> { classEvents.incrementAndGet(); }); @@ -90,15 +89,12 @@ public void test() throws Exception { stream.onEvent("com.jfr.String", event -> { stringEvents.incrementAndGet(); }); - stream.onEvent("jdk.JavaMonitorWait", event -> { - if (!event.getClass("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { - return; + stream.onEvent(JfrEvent.JavaMonitorWait.getName(), event -> { + if (event. getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { + waitEvents.incrementAndGet(); } - waitEvents.incrementAndGet(); }); - startStream(); - Runnable eventEmitter = () -> { StringEvent stringEvent = new StringEvent(); stringEvent.message = "StringEvent has been generated as part of TestConcurrentEvents."; @@ -124,7 +120,5 @@ public void test() throws Exception { waitUntilTrue(() -> emittedEventsPerType.get() == EXPECTED_EVENTS_PER_TYPE); waitUntilTrue(() -> classEvents.get() == EXPECTED_EVENTS_PER_TYPE && integerEvents.get() == EXPECTED_EVENTS_PER_TYPE && stringEvents.get() == EXPECTED_EVENTS_PER_TYPE && waitEvents.get() == EXPECTED_EVENTS_PER_TYPE); - - closeStream(); } } 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 0eb5419046cb..7d5d69ab8705 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 @@ -28,6 +28,8 @@ import static org.junit.Assert.assertTrue; +import java.util.List; + import org.junit.Test; import com.oracle.svm.core.NeverInline; @@ -37,7 +39,7 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestObjectAllocationInNewTLABEvent extends JfrTest { +public class TestObjectAllocationInNewTLABEvent extends JfrRecordingTest { private static final int K = 1024; private static final int DEFAULT_ALIGNED_HEAP_CHUNK_SIZE = 1024 * K; @@ -47,13 +49,13 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws Throwable { + protected void validateEvents(List events) throws Throwable { boolean foundBigByteArray = false; boolean foundSmallByteArray = false; boolean foundBigCharArray = false; boolean foundInstance = false; - for (RecordedEvent event : getEvents()) { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); if (!eventThread.equals("main")) { continue; 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 c0c76dfee509..c4e8f824a71b 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,19 +25,30 @@ package com.oracle.svm.test.jfr; +import static org.junit.Assert.assertEquals; + +import java.util.List; + import org.junit.Test; import com.oracle.svm.test.jfr.events.StackTraceEvent; +import jdk.jfr.consumer.RecordedEvent; + /** * Test if event ({@link StackTraceEvent}) with stacktrace payload is working. */ -public class TestStackTraceEvent extends JfrTest { +public class TestStackTraceEvent extends JfrRecordingTest { @Override public String[] getTestedEvents() { return new String[]{StackTraceEvent.class.getName()}; } + @Override + protected void validateEvents(List events) throws Throwable { + assertEquals(1, events.size()); + } + @Test public void test() throws Exception { /* 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 895d6a5d2b6e..3d529a2f8d23 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,16 +26,27 @@ package com.oracle.svm.test.jfr; +import static org.junit.Assert.assertEquals; + +import java.util.List; + import org.junit.Test; import com.oracle.svm.test.jfr.events.StringEvent; -public class TestStringEvent extends JfrTest { +import jdk.jfr.consumer.RecordedEvent; + +public class TestStringEvent extends JfrRecordingTest { @Override public String[] getTestedEvents() { return new String[]{"com.jfr.String"}; } + @Override + protected void validateEvents(List events) throws Throwable { + assertEquals(1, events.size()); + } + @Test public void test() throws Exception { StringEvent event = new StringEvent(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java index aa16dc655477..3dc35073fc99 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java @@ -26,19 +26,30 @@ package com.oracle.svm.test.jfr; +import static org.junit.Assert.assertEquals; + +import java.util.List; + import org.junit.Test; import com.oracle.svm.test.jfr.events.ThreadEvent; +import jdk.jfr.consumer.RecordedEvent; + /** * Test if event ({@link ThreadEvent}) with {@link Thread} payload is working. */ -public class TestThreadEvent extends JfrTest { +public class TestThreadEvent extends JfrRecordingTest { @Override public String[] getTestedEvents() { return new String[]{ThreadEvent.class.getName()}; } + @Override + protected void validateEvents(List events) throws Throwable { + assertEquals(1, events.size()); + } + @Test public void test() throws Exception { ThreadEvent event = new ThreadEvent(); 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 index 650886322d89..d4297200ef25 100644 --- 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 @@ -29,13 +29,14 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.List; import org.junit.Test; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedThread; -public class TestThreadSleepEvent extends JfrTest { +public class TestThreadSleepEvent extends JfrRecordingTest { private static final int MILLIS = 50; private Thread sleepingThread; @@ -46,9 +47,9 @@ public String[] getTestedEvents() { } @Override - public void validateEvents() throws IOException { + protected void validateEvents(List events) throws IOException { boolean foundSleepEvent = false; - for (RecordedEvent event : getEvents()) { + for (RecordedEvent event : events) { String eventThread = event. getValue("eventThread").getJavaName(); if (!eventThread.equals(sleepingThread.getName())) { continue; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 43ffb649025a..1fb3ec06bd7a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -30,9 +30,8 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.util.ArrayList; +import java.nio.file.Path; import java.util.HashMap; -import java.util.List; import org.junit.Assert; @@ -57,8 +56,6 @@ import com.oracle.svm.test.jfr.utils.poolparsers.ThreadStateConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.VMOperationConstantPoolParser; -import jdk.jfr.Recording; - public class JfrFileParser { private static final HashMap supportedConstantPools; @@ -142,41 +139,39 @@ private static long parseConstantPoolHeader(RecordingInput input, long constantP return deltaNext; } - private static void compareFoundAndExpectedIds() { - for (ConstantPoolParser parser : supportedConstantPools.values()) { - parser.compareFoundAndExpectedIds(); - } - } - - /** - * Must verify constant pools in the order that they were written because event streaming can - * write pools before the chunk is finished. This means that a given pool may reference - * constants from another pool written previously (within the same chunk). - */ private static void verifyConstantPools(RecordingInput input, long constantPoolPosition) throws IOException { - List poolPositions = new ArrayList<>(); long deltaNext; long currentConstantPoolPosition = constantPoolPosition; do { - poolPositions.add(currentConstantPoolPosition); deltaNext = parseConstantPoolHeader(input, currentConstantPoolPosition); - currentConstantPoolPosition += deltaNext; - } while (deltaNext != 0); - - for (int j = poolPositions.size() - 1; j > 0; j--) { - parseConstantPoolHeader(input, poolPositions.get(j)); long numberOfCPs = input.readInt(); for (int i = 0; i < numberOfCPs; i++) { ConstantPoolParser constantPoolParser = supportedConstantPools.get(input.readLong()); Assert.assertNotNull("Unknown constant pool!", constantPoolParser); constantPoolParser.parse(input); } - compareFoundAndExpectedIds(); + currentConstantPoolPosition += deltaNext; + } while (deltaNext != 0); + + /* Now that we collected all data, verify and clear it. */ + compareFoundAndExpectedIds(); + resetConstantPoolParsers(); + } + + private static void compareFoundAndExpectedIds() { + for (ConstantPoolParser parser : supportedConstantPools.values()) { + parser.compareFoundAndExpectedIds(); + } + } + + public static void resetConstantPoolParsers() { + for (ConstantPoolParser parser : supportedConstantPools.values()) { + parser.reset(); } } - public static void parse(Recording recording) throws IOException { - RecordingInput input = new RecordingInput(recording.getDestination().toFile()); + public static void parse(Path path) throws IOException { + RecordingInput input = new RecordingInput(path.toFile()); Positions positions = parserFileHeader(input); verifyConstantPools(input, positions.getConstantPoolPosition()); parseMetadata(input, positions.getMetadataPosition()); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java deleted file mode 100644 index 9fd0399949b7..000000000000 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.utils; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import jdk.jfr.Configuration; -import jdk.jfr.Recording; - -public class LocalJfr implements Jfr { - - @Override - public Recording createRecording(String recordingName) throws Exception { - /* Enable a lot of events by default to increase the test coverage. */ - Configuration defaultConfig = Configuration.getConfiguration("default"); - return createRecording(new Recording(defaultConfig), recordingName); - } - - @Override - public void startRecording(Recording recording) { - recording.start(); - } - - private static Recording createRecording(Recording recording, String name) throws Exception { - long id = recording.getId(); - - Path destination = File.createTempFile(name + "-" + id, ".jfr").toPath(); - recording.setDestination(destination); - - return recording; - } - - @Override - public void endRecording(Recording recording) { - recording.stop(); - recording.close(); - } - - @Override - public void cleanupRecording(Recording recording) throws IOException { - String debugRecording = System.getenv("DEBUG_RECORDING"); - if (debugRecording != null && !"false".equals(debugRecording)) { - System.out.println("Recording: " + recording.getDestination()); - } else { - Files.deleteIfExists(recording.getDestination()); - } - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java index 384cdd78673a..61dd2fd4b8a8 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java @@ -39,6 +39,7 @@ public class ClassConstantPoolParser extends ConstantPoolParser { @Override public void parse(RecordingInput input) throws IOException { int numberOfClasses = input.readInt(); + for (int i = 0; i < numberOfClasses; i++) { addFoundId(input.readLong()); // ClassId. addExpectedId(JfrType.ClassLoader, input.readLong()); // ClassLoaderId. diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java index b98da5f8ecdc..806f55c9e40b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java @@ -26,13 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; +import java.io.IOException; + import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -import java.io.IOException; - public class ClassLoaderConstantPoolParser extends ConstantPoolParser { + @Override + public void reset() { + /* 0 is the null class loader. */ + foundIds.add(0L); + } + @Override public void parse(RecordingInput input) throws IOException { int numberOfClassLoaders = input.readInt(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java index 1b970a1236b7..40ad96aaefa9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java @@ -30,22 +30,23 @@ import java.util.HashSet; import java.util.Set; +import org.junit.Assert; + import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.JfrFileParser; import com.oracle.svm.test.jfr.utils.RecordingInput; -import org.junit.Assert; public abstract class ConstantPoolParser { /** * Set of ids found during parsing of current constant pool. */ - private final Set foundIds = new HashSet<>(); + protected final Set foundIds = new HashSet<>(); /** * List of ids found during parsing of other constant pools that are referencing this one. */ - private final Set expectedIds = new HashSet<>(); + protected final Set expectedIds = new HashSet<>(); protected ConstantPoolParser() { foundIds.add(0L); @@ -63,7 +64,12 @@ protected static void addExpectedId(JfrType typeId, long id) { public void compareFoundAndExpectedIds() { Assert.assertTrue("Error during parsing " + this + " constant pool!" + " Expected IDs: " + expectedIds + - ". Found IDs: " + foundIds, foundIds.size() == 0 || foundIds.containsAll(expectedIds)); + ". Found IDs: " + foundIds, foundIds.containsAll(expectedIds)); + } + + public void reset() { + foundIds.clear(); + expectedIds.clear(); } public abstract void parse(RecordingInput input) throws IOException; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java index f42d1e863bb1..57478c469003 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java @@ -26,13 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; +import java.io.IOException; + import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -import java.io.IOException; - public class PackageConstantPoolParser extends ConstantPoolParser { + @Override + public void reset() { + /* 0 is the empty package. */ + foundIds.add(0L); + } + @Override public void parse(RecordingInput input) throws IOException { int numberOfPackages = input.readInt(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java index 814bed3119d9..4091d3f4a833 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java @@ -26,13 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; +import java.io.IOException; + import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -import java.io.IOException; - public class StacktraceConstantPoolParser extends ConstantPoolParser { + @Override + public void reset() { + /* 0 is a null stack trace. */ + foundIds.add(0L); + } + @Override public void parse(RecordingInput input) throws IOException { int numberOfStackTraces = input.readInt(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java index e9413ef361b2..ee6cfb3f3ac6 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java @@ -32,6 +32,12 @@ public class SymbolConstantPoolParser extends ConstantPoolParser { + @Override + public void reset() { + /* 0 is the null symbol. */ + foundIds.add(0L); + } + @Override public void parse(RecordingInput input) throws IOException { int numberOfSymbols = input.readInt(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java index d06180eeb1c3..ba9ca9cb356b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java @@ -26,13 +26,20 @@ package com.oracle.svm.test.jfr.utils.poolparsers; +import java.io.IOException; + import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -import java.io.IOException; - public class ThreadGroupConstantPoolParser extends ConstantPoolParser { + @Override + public void reset() { + /* 0 is the null thread group, 1 is the virtual thread group. */ + foundIds.add(0L); + foundIds.add(1L); + } + @Override public void parse(RecordingInput input) throws IOException { int numberOfThreadGroups = input.readInt(); From 79bd4201bd03b0f7e23d14cbea540eb6e4c05b89 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 6 Mar 2023 16:08:01 +0100 Subject: [PATCH 69/72] Small fixes and cleanups. --- .../com/oracle/svm/core/jfr/JfrBuffer.java | 10 ++- .../oracle/svm/core/jfr/JfrBufferAccess.java | 4 +- .../oracle/svm/core/jfr/JfrBufferList.java | 65 +++++++++++-------- .../oracle/svm/core/jfr/JfrBufferNode.java | 4 +- .../svm/core/jfr/JfrBufferNodeAccess.java | 15 ++--- .../oracle/svm/core/jfr/JfrChunkWriter.java | 49 +++++++------- .../oracle/svm/core/jfr/JfrGlobalMemory.java | 29 ++++++--- .../svm/core/jfr/JfrMethodRepository.java | 2 +- .../svm/core/jfr/JfrRecorderThread.java | 2 +- .../svm/core/jfr/JfrStackTraceRepository.java | 2 +- .../svm/core/jfr/JfrSymbolRepository.java | 2 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 10 +-- .../svm/core/jfr/JfrThreadRepository.java | 6 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 23 ++++--- .../svm/core/jfr/logging/JfrLogging.java | 5 ++ .../com/oracle/svm/core/locks/VMMutex.java | 2 +- .../test/jfr/TestJavaMonitorInflateEvent.java | 16 ++--- 17 files changed, 139 insertions(+), 107 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java index d141c21ddb9a..9d71afaaf9b8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBuffer.java @@ -54,12 +54,16 @@ *
    • Committed data refers to fully written, valid event data that can be flushed at any * time.
    • *
    • Unflushed data refers to the data of a JFR event that is currently being written.
    • + *
    * * Multiple threads may access the same {@link JfrBuffer} concurrently: *
  • If a thread owns/created a thread-local buffer, then it may access and modify most of that - * buffer's data at any time, without the need for any locking. The flushed position is the only - * exception as it may only be accessed/modified after locking the corresponding - * {@link JfrBufferNode}.
  • + * buffer's data at any time, without the need for any locking. Only the following operations + * require that the {@link JfrBufferNode} is locked: + *
      + *
    • accessing or modifying the flushed position (see {@link JfrBufferAccess#setFlushedPos}
    • + *
    • freeing the buffer
    • + *
    *
  • Accessing a thread-local buffer of another thread is only allowed after locking the * corresponding {@link JfrBufferNode} (see {@link #getNode()}). This prevents other threads from * freeing the buffer in meanwhile. The thread that holds the lock may read any field in the buffer diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 8bf47d6ae342..737db0e20474 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -85,8 +85,8 @@ public static void reinitialize(JfrBuffer buffer) { /** * Sets the flushed position. Also verifies that the thread that modifies the flushed position * owns the lock if the buffer is published in a {@link JfrBufferList}. This is important to - * avoid races between the thread that owns/created JFR local buffers and threads that iterate - * {@link JfrBufferList}s (e.g., threads that flush for event streaming). + * avoid races between the thread that owns/created a thread local buffer and threads that + * iterate {@link JfrBufferList}s (e.g., threads that flush for event streaming). */ @Uninterruptible(reason = "Changes flushed position.") public static void setFlushedPos(JfrBuffer buffer, Pointer pos) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index 2d1fedd2a176..da77c3168ece 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -34,7 +34,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.thread.JavaSpinLockUtils; -import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.util.VMError; import jdk.internal.misc.Unsafe; @@ -62,16 +62,13 @@ public JfrBufferList() { } /** - * This method is called after all the threads already stopped recording. So, the - * {@link JfrBuffer}s were already flushed and freed, but there may still be nodes in the list. - * This node data needs to be freed. + * This method is called after the {@link JfrBuffer}s were already flushed and freed, but there + * may still be nodes in the list. */ public void teardown() { - assert VMOperation.isInProgressAtSafepoint(); - JfrBufferNode node = head; while (node.isNonNull()) { - assert JfrBufferNodeAccess.getBuffer(node).isNull(); + assert node.getBuffer().isNull(); JfrBufferNode next = node.getNext(); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); @@ -80,9 +77,9 @@ public void teardown() { head = WordFactory.nullPointer(); } - @Uninterruptible(reason = "Locking with no transition.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public JfrBufferNode getHead() { - lock(); + lockNoTransition(); try { return head; } finally { @@ -90,11 +87,7 @@ public JfrBufferNode getHead() { } } - /** - * Must be uninterruptible because if this list is acquired and we safepoint for an epoch change - * in this method, the thread doing the epoch change will be blocked accessing the list. - */ - @Uninterruptible(reason = "Locking with no transition. List must not be acquired entering epoch change.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public JfrBufferNode addNode(JfrBuffer buffer) { assert buffer.isNonNull(); assert buffer.getBufferType() != null && buffer.getBufferType() != JfrBufferType.C_HEAP; @@ -104,11 +97,11 @@ public JfrBufferNode addNode(JfrBuffer buffer) { return WordFactory.nullPointer(); } - assert buffer.getNode().isNull(); - buffer.setNode(node); - - lock(); + lockNoTransition(); try { + assert buffer.getNode().isNull(); + buffer.setNode(node); + node.setNext(head); head = node; return node; @@ -118,14 +111,14 @@ public JfrBufferNode addNode(JfrBuffer buffer) { } /** - * Removes a node from the list. The buffer contained in the nodes must have already been freed - * by the caller. + * Removes a node from the list. The buffer that is referenced by the node must have already + * been freed by the caller. */ - @Uninterruptible(reason = "Should not be interrupted while flushing.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public void removeNode(JfrBufferNode node, JfrBufferNode prev) { assert head.isNonNull(); - lock(); + lockNoTransition(); try { assert JfrBufferNodeAccess.getBuffer(node).isNull(); @@ -133,22 +126,40 @@ public void removeNode(JfrBufferNode node, JfrBufferNode prev) { if (node == head) { assert prev.isNull(); head = next; - } else { - assert prev.isNonNull(); + } else if (prev.isNonNull()) { assert prev.getNext() == node; prev.setNext(next); + } else { + /* We are removing an old head (other threads added nodes in the meanwhile). */ + JfrBufferNode p = findPrev(node); + assert p.isNonNull() && p.getNext() == node; + p.setNext(next); } } finally { unlock(); } } - @Uninterruptible(reason = "Whole critical section must be uninterruptible because we are locking without transition.", callerMustBe = true) - private void lock() { + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + private JfrBufferNode findPrev(JfrBufferNode node) { + JfrBufferNode cur = head; + JfrBufferNode prev = WordFactory.nullPointer(); + while (cur.isNonNull()) { + if (cur == node) { + return prev; + } + prev = cur; + cur = cur.getNext(); + } + throw VMError.shouldNotReachHere("JfrBufferNode not found in JfrBufferList."); + } + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) + private void lockNoTransition() { JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); } - @Uninterruptible(reason = "Whole critical section must be uninterruptible because we are locking without transition.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) private void unlock() { JavaSpinLockUtils.unlock(this, LOCK_OFFSET); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java index 6bc5b09b2440..d5ca6bd0677c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java @@ -36,8 +36,8 @@ /** * {@link JfrBufferNode}s are added to {@link JfrBufferList}s and have a longer lifetime than the - * {@link JfrBuffer} that they reference. With this concept and the providing locking mechanism, - * threads can iterate over the thread-local JFR buffers of other threads. This enables use cases, + * {@link JfrBuffer} that they reference. With this concept and the provided locking mechanism, + * threads can iterate over the thread-local JFR buffers of other threads, which enables use cases * such as JFR event streaming. */ @RawStructure diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 956bda9ea7d3..759db4b69ce4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -39,11 +39,7 @@ import com.oracle.svm.core.thread.VMOperation; /** - * Used to access the raw memory of a {@link com.oracle.svm.core.jfr.JfrBufferNode}. - * - * This class also provides the infrastructure that threads can access thread-local buffers of other - * threads (see {@link JfrBuffer} for more information regarding concurrency). When the VM enters a - * safepoint, we must guarantee that all {@link JfrBufferNode}s are unlocked. + * Used to access the raw memory of a {@link JfrBufferNode}. */ public final class JfrBufferNodeAccess { private JfrBufferNodeAccess() { @@ -66,13 +62,14 @@ public static void free(JfrBufferNode node) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(node); } + /** Should be used instead of {@link JfrBufferNode#getBuffer}. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static JfrBuffer getBuffer(JfrBufferNode node) { assert isLockedByCurrentThread(node) || VMOperation.isInProgressAtSafepoint(); return node.getBuffer(); } - @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) public static boolean tryLock(JfrBufferNode node) { assert node.isNonNull(); if (NativeSpinLockUtils.tryLock(ptrToLock(node))) { @@ -82,14 +79,14 @@ public static boolean tryLock(JfrBufferNode node) { return false; } - @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) - public static void lock(JfrBufferNode node) { + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) + public static void lockNoTransition(JfrBufferNode node) { assert node.isNonNull(); NativeSpinLockUtils.lockNoTransition(ptrToLock(node)); setLockOwner(node); } - @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", callerMustBe = true) public static void unlock(JfrBufferNode node) { assert node.isNonNull(); assert isLockedByCurrentThread(node); 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 ef72b0dba9f3..2a44b39ebc83 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 @@ -360,7 +360,7 @@ public void setMetadata(byte[] bytes) { private void writeMetadataEvent() { assert lock.isOwner(); - /* Only the write the metadata if this is a new chunk or if it changed in the meanwhile. */ + /* Only write the metadata if this is a new chunk or if it changed in the meanwhile. */ long currentMetadataId = metadata.getCurrentMetadataId(); if (lastMetadataId == currentMetadataId) { return; @@ -506,34 +506,13 @@ public void writeString(String str) { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void flushStorage(boolean flush) { - /* - * Write unflushed data from the thread-local event buffers to the output file. We do *not* - * reinitialize the thread-local buffers as the individual threads will handle space - * reclamation on their own time. - */ traverseThreadLocalBuffers(getJavaBufferList(), flush); traverseThreadLocalBuffers(getNativeBufferList(), flush); - /* Flush all global buffers. */ - JfrBufferList buffers = globalMemory.getBuffers(); - JfrBufferNode node = buffers.getHead(); - while (node.isNonNull()) { - boolean success = JfrBufferNodeAccess.tryLock(node); - if (success) { - try { - JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); - write(buffer); - JfrBufferAccess.reinitialize(buffer); - } finally { - JfrBufferNodeAccess.unlock(node); - } - } - assert success || flush; - node = node.getNext(); - } + flushGlobalMemory(flush); } - @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private void traverseThreadLocalBuffers(JfrBufferList list, boolean flush) { JfrBufferNode node = list.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); @@ -561,17 +540,37 @@ private void traverseThreadLocalBuffers(JfrBufferList list, boolean flush) { } else { write(buffer); } - prev = node; } finally { JfrBufferNodeAccess.unlock(node); } } assert success || flush; + prev = node; node = next; } } + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + private void flushGlobalMemory(boolean flush) { + JfrBufferList buffers = globalMemory.getBuffers(); + JfrBufferNode node = buffers.getHead(); + while (node.isNonNull()) { + boolean success = JfrBufferNodeAccess.tryLock(node); + if (success) { + try { + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); + write(buffer); + JfrBufferAccess.reinitialize(buffer); + } finally { + JfrBufferNodeAccess.unlock(node); + } + } + assert success || flush; + node = node.getNext(); + } + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isLockedByCurrentThread() { return lock.isOwner(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 8b76e2463062..27e1336a6cc9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -81,16 +81,27 @@ public void clear() { } public void teardown() { + freeBuffers(); + + /* Free the nodes. */ + buffers.teardown(); + } + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + private void freeBuffers() { /* Free the buffers. */ JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - JfrBuffer buffer = node.getBuffer(); - JfrBufferAccess.free(buffer); + JfrBufferNodeAccess.lockNoTransition(node); + try { + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); + JfrBufferAccess.free(buffer); + node.setBuffer(WordFactory.nullPointer()); + } finally { + JfrBufferNodeAccess.unlock(node); + } node = node.getNext(); } - - /* Free the nodes. */ - buffers.teardown(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -104,7 +115,7 @@ public boolean write(JfrBuffer threadLocalBuffer, boolean streamingFlush) { return write(threadLocalBuffer, unflushedSize, streamingFlush); } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, boolean streamingFlush) { if (unflushedSize.equal(0)) { return true; @@ -118,7 +129,7 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo boolean shouldSignal; try { /* Copy all committed but not yet flushed memory to the promotion buffer. */ - JfrBuffer promotionBuffer = promotionNode.getBuffer(); + JfrBuffer promotionBuffer = JfrBufferNodeAccess.getBuffer(promotionNode); assert JfrBufferAccess.getAvailableSize(promotionBuffer).aboveOrEqual(unflushedSize); UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(threadLocalBuffer), promotionBuffer.getCommittedPos(), unflushedSize); JfrBufferAccess.increaseCommittedPos(promotionBuffer, unflushedSize); @@ -139,14 +150,14 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo return true; } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private JfrBufferNode tryAcquirePromotionBuffer(UnsignedWord size) { assert size.belowOrEqual(WordFactory.unsigned(bufferSize)); for (int retry = 0; retry < PROMOTION_RETRY_COUNT; retry++) { JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { if (JfrBufferNodeAccess.tryLock(node)) { - JfrBuffer buffer = node.getBuffer(); + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size)) { /* Recheck the available size after acquiring the buffer. */ if (JfrBufferAccess.getAvailableSize(buffer).aboveOrEqual(size)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index 7ca98a83cb7c..f45920c64fb7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -56,7 +56,7 @@ public void teardown() { epochData1.teardown(); } - @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.", callerMustBe = true) public long getMethodId(Class clazz, String methodName, int methodId) { assert clazz != null; assert methodName != null; 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 bb44aaa9fc98..a62737442227 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 @@ -145,7 +145,7 @@ private static Object getChunkRotationMonitor() { } } - @Uninterruptible(reason = "Locks a BufferNode.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private static void tryPersistBuffer(JfrChunkWriter chunkWriter, JfrBufferNode node) { if (JfrBufferNodeAccess.tryLock(node)) { try { 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 69d1732fd1e7..e3ca7c61b4bd 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 @@ -170,7 +170,7 @@ public JfrStackTraceTableEntry getOrPutStackTrace(Pointer start, UnsignedWord si } } - @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.") + @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.", callerMustBe = true) private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) { assert size.rawValue() == (int) size.rawValue(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index a75dac8c7fcd..7b37eb161977 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -71,7 +71,7 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch) { return getSymbolId(imageHeapString, previousEpoch, false); } - @Uninterruptible(reason = "Result is only valid until epoch changes.", callerMustBe = true) + @Uninterruptible(reason = "Locking without transition and result is only valid until epoch changes.", callerMustBe = true) public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean replaceDotWithSlash) { if (imageHeapString == null) { return 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 8fadb60c7019..43fc0583218c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -165,14 +165,14 @@ public static void stopRecording(IsolateThread isolateThread) { } } - @Uninterruptible(reason = "Accesses a JFR buffer.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private static void flushToGlobalMemoryAndFreeBuffer(JfrBufferNode node) { if (node.isNull()) { return; } /* Free the buffer but leave the node alive as it still needed. */ - JfrBufferNodeAccess.lock(node); + JfrBufferNodeAccess.lockNoTransition(node); try { JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); node.setBuffer(WordFactory.nullPointer()); @@ -201,7 +201,7 @@ public void setExcluded(Thread thread, boolean excluded) { } } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isCurrentThreadExcluded() { Target_java_lang_Thread tjlt = SubstrateUtil.cast(Thread.currentThread(), Target_java_lang_Thread.class); return tjlt.jfrExcluded; @@ -286,7 +286,7 @@ public static void notifyEventWriter(IsolateThread thread) { } } - @Uninterruptible(reason = "Accesses a JFR buffer.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public static JfrBuffer flushToGlobalMemory(JfrBuffer buffer, UnsignedWord uncommitted, int requested) { assert buffer.isNonNull(); assert JfrBufferAccess.isThreadLocal(buffer); @@ -294,7 +294,7 @@ public static JfrBuffer flushToGlobalMemory(JfrBuffer buffer, UnsignedWord uncom /* Acquire the buffer because a streaming flush could be in progress. */ JfrBufferNode node = buffer.getNode(); - JfrBufferNodeAccess.lock(node); + JfrBufferNodeAccess.lockNoTransition(node); try { return flushToGlobalMemory0(buffer, uncommitted, requested); } finally { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index a6f830baf680..9ec0c6b13b77 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -84,7 +84,7 @@ public void registerRunningThreads() { } } - @Uninterruptible(reason = "Epoch must not change while in this method.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public void registerThread(Thread thread) { long threadId = JavaThreads.getThreadId(thread); @@ -182,7 +182,7 @@ private long registerThreadGroup0(ThreadGroup threadGroup) { return threadGroupId; } - @Uninterruptible(reason = "Lock without transition.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public boolean hasUnflushedData() { mutex.lockNoTransition(); try { @@ -194,7 +194,7 @@ public boolean hasUnflushedData() { } @Override - @Uninterruptible(reason = "May write current epoch data.") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public int write(JfrChunkWriter writer, boolean flush) { mutex.lockNoTransition(); try { 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 d480a1221076..1cb7c45ec392 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 @@ -231,14 +231,19 @@ public boolean destroyJFR() { return false; } - recorderThread.shutdown(); - - globalMemory.teardown(); - symbolRepo.teardown(); - threadRepo.teardown(); - stackTraceRepo.teardown(); - methodRepo.teardown(); - typeRepo.teardown(); + try { + recorderThread.shutdown(); + + globalMemory.teardown(); + symbolRepo.teardown(); + threadRepo.teardown(); + stackTraceRepo.teardown(); + methodRepo.teardown(); + typeRepo.teardown(); + } catch (Throwable e) { + /* Log errors because the shutdown hook swallows exceptions. */ + jfrLogging.warnInternal(e.getClass().getName() + " in destroyJFR."); + } initialized = false; return true; @@ -689,7 +694,7 @@ public boolean isExcluded(Thread thread) { return isCurrentThreadExcluded(); } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isCurrentThreadExcluded() { return getThreadLocal().isCurrentThreadExcluded(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java index 670bdb77ba65..b5fab28f4b7d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java @@ -55,6 +55,11 @@ public void parseConfiguration(String config) { configuration.parse(config); } + public void warnInternal(String message) { + int tagSetId = SubstrateUtil.cast(LogTag.JFR_SYSTEM, Target_jdk_jfr_internal_LogTag.class).id; + log(tagSetId, JfrLogConfiguration.JfrLogLevel.WARNING.level, message); + } + public void log(int tagSetId, int level, String message) { if (message == null) { return; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java index 9610cfab9de5..6a4504de5446 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java @@ -199,7 +199,7 @@ public void clearUnspecifiedOwner() { owner = WordFactory.nullPointer(); } - @Uninterruptible(reason = "Called from uninterruptible code.") + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean hasOwner() { return owner.isNonNull(); } 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 4580bfb0cbc5..493af1f40349 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 @@ -40,9 +40,9 @@ import jdk.jfr.consumer.RecordedThread; public class TestJavaMonitorInflateEvent extends JfrRecordingTest { - private static final EnterHelper ENTER_HELPER = new EnterHelper(); - private static Thread firstThread; - private static Thread secondThread; + private final EnterHelper enterHelper = new EnterHelper(); + private Thread firstThread; + private Thread secondThread; @Override public String[] getTestedEvents() { @@ -69,7 +69,7 @@ protected void validateEvents(List events) throws Throwable { public void test() throws Exception { Runnable first = () -> { try { - ENTER_HELPER.doWork(); + enterHelper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -77,8 +77,8 @@ public void test() throws Exception { Runnable second = () -> { try { - EnterHelper.passedCheckpoint = true; - ENTER_HELPER.doWork(); + enterHelper.passedCheckpoint = true; + enterHelper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -94,8 +94,8 @@ public void test() throws Exception { secondThread.join(); } - private static class EnterHelper { - static volatile boolean passedCheckpoint = false; + private class EnterHelper { + volatile boolean passedCheckpoint = false; synchronized void doWork() throws InterruptedException { if (Thread.currentThread().equals(secondThread)) { From b5b3e784254cb074dc6c704b04e981a00b113630 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Tue, 7 Mar 2023 14:26:07 +0100 Subject: [PATCH 70/72] Further fixes. --- .../core/jdk/JDKContainerSubstitutions.java | 9 +- .../oracle/svm/core/jfr/JfrBufferAccess.java | 2 +- .../oracle/svm/core/jfr/JfrBufferNode.java | 6 ++ .../svm/core/jfr/JfrBufferNodeAccess.java | 22 ++++ .../oracle/svm/core/jfr/JfrBufferType.java | 18 +++- .../oracle/svm/core/jfr/JfrChunkWriter.java | 65 ++++++----- .../svm/core/jfr/JfrEventWriterAccess.java | 77 +++++++------ .../oracle/svm/core/jfr/JfrGlobalMemory.java | 16 +-- .../svm/core/jfr/JfrMethodRepository.java | 10 +- .../oracle/svm/core/jfr/JfrRepository.java | 4 +- .../svm/core/jfr/JfrStackTraceRepository.java | 16 +-- .../svm/core/jfr/JfrSymbolRepository.java | 10 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 102 ++++++++++++------ .../svm/core/jfr/JfrThreadRepository.java | 10 +- .../svm/core/jfr/JfrTypeRepository.java | 58 +++++----- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 30 ++---- .../Target_jdk_jfr_internal_EventWriter.java | 4 +- 17 files changed, 274 insertions(+), 185 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java index 28fdb623cf1a..2c7c2bad3e80 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java @@ -25,17 +25,16 @@ package com.oracle.svm.core.jdk; -import org.graalvm.nativeimage.Platform.LINUX; - import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; +import org.graalvm.nativeimage.Platform.LINUX; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; - import com.oracle.svm.core.annotate.TargetClass; @TargetClass(className = "jdk.internal.platform.cgroupv1.CgroupV1Subsystem", onlyWith = JDK17OrLater.class) @@ -69,6 +68,10 @@ final class Target_jdk_jfr_internal_instrument_JDKEvents { @TargetClass(className = "jdk.jfr.internal.RequestEngine", onlyWith = JDK17OrLater.class) @Platforms(LINUX.class) final class Target_jdk_jfr_internal_RequestEngine { + @Alias // + @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ReentrantLock.class) // + private static ReentrantLock lock; + @Alias // @RecomputeFieldValue(kind = Kind.NewInstance, declClass = CopyOnWriteArrayList.class) // private static List entries; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 737db0e20474..322602ad4747 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -168,6 +168,6 @@ public static boolean verify(JfrBuffer buffer) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isThreadLocal(JfrBuffer buffer) { - return buffer.getBufferType() == JfrBufferType.THREAD_LOCAL_JAVA || buffer.getBufferType() == JfrBufferType.THREAD_LOCAL_NATIVE; + return buffer.getBufferType().isThreadLocal(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java index d5ca6bd0677c..1c0b75b2c8b3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNode.java @@ -67,4 +67,10 @@ static int offsetOfLock() { @RawField void setLockOwner(IsolateThread value); + + @RawField + byte getFlags(); + + @RawField + void setFlags(byte value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 759db4b69ce4..42cb92c0e59a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -42,6 +42,8 @@ * Used to access the raw memory of a {@link JfrBufferNode}. */ public final class JfrBufferNodeAccess { + private static final byte RETIRED = 0b01; + private JfrBufferNodeAccess() { } @@ -110,4 +112,24 @@ private static void setLockOwner(JfrBufferNode node) { private static CIntPointer ptrToLock(JfrBufferNode node) { return (CIntPointer) ((Pointer) node).add(JfrBufferNode.offsetOfLock()); } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void setRetired(JfrBufferNode node) { + assert isLockedByCurrentThread(node); + assert !isRetired(node); + node.setFlags((byte) (node.getFlags() | RETIRED)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void clearRetired(JfrBufferNode node) { + assert isLockedByCurrentThread(node); + assert isRetired(node); + node.setFlags((byte) (node.getFlags() & ~RETIRED)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isRetired(JfrBufferNode node) { + assert isLockedByCurrentThread(node); + return (node.getFlags() & RETIRED) != 0; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java index ff7b8cfd7239..7521bd5705c0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java @@ -31,19 +31,29 @@ public enum JfrBufferType { /** * A thread-local native buffer, see {@link JfrThreadLocal}. */ - THREAD_LOCAL_NATIVE, + THREAD_LOCAL_NATIVE(true), /** * A thread-local java buffer, see {@link JfrThreadLocal}. */ - THREAD_LOCAL_JAVA, + THREAD_LOCAL_JAVA(true), /** * A global JFR buffer, see {@link JfrGlobalMemory}. */ - GLOBAL_MEMORY, + GLOBAL_MEMORY(false), /** * Other buffers that live in the C heap and that can be resized (i.e., reallocated) if * necessary. This type is for example used for the epoch-based global buffers in * {@link JfrThreadRepository}. */ - C_HEAP + C_HEAP(false); + + private final boolean threadLocal; + + JfrBufferType(boolean threadLocal) { + this.threadLocal = threadLocal; + } + + public boolean isThreadLocal() { + return threadLocal; + } } 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 2a44b39ebc83..87df5f190477 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 @@ -169,6 +169,7 @@ public void write(JfrBuffer buffer) { assert lock.isOwner(); assert buffer.isNonNull(); assert buffer.getBufferType() == JfrBufferType.C_HEAP || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); + assert buffer.getNode() == null || !JfrBufferNodeAccess.isRetired(buffer.getNode()); UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { @@ -246,12 +247,12 @@ private void writeFileHeader() { getFileSupport().writeShort(fd, computeHeaderFlags()); } - private void patchFileHeader(boolean flush) { + private void patchFileHeader(boolean flushpoint) { assert lock.isOwner(); assert metadataPosition.greaterThan(0); assert lastCheckpointOffset.greaterThan(0); - byte generation = flush ? getAndIncrementGeneration() : COMPLETE; + byte generation = flushpoint ? getAndIncrementGeneration() : COMPLETE; SignedWord currentPos = getFileSupport().position(fd); long chunkSize = currentPos.rawValue(); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; @@ -292,19 +293,19 @@ private byte getAndIncrementGeneration() { return nextGeneration++; } - private void writeFlushCheckpoint(boolean flush) { - writeCheckpointEvent(JfrCheckpointType.Flush, flushCheckpointRepos, newChunk, flush); + private void writeFlushCheckpoint(boolean flushpoint) { + writeCheckpointEvent(JfrCheckpointType.Flush, flushCheckpointRepos, newChunk, flushpoint); } - private void writeThreadCheckpoint(boolean flush) { + private void writeThreadCheckpoint(boolean flushpoint) { assert threadCheckpointRepos.length == 1 && threadCheckpointRepos[0] == SubstrateJVM.getThreadRepo(); /* The code below is only atomic enough because the epoch can't change while flushing. */ if (SubstrateJVM.getThreadRepo().hasUnflushedData()) { - writeCheckpointEvent(JfrCheckpointType.Threads, threadCheckpointRepos, false, flush); + writeCheckpointEvent(JfrCheckpointType.Threads, threadCheckpointRepos, false, flushpoint); } } - private void writeCheckpointEvent(JfrCheckpointType type, JfrRepository[] repositories, boolean writeSerializers, boolean flush) { + private void writeCheckpointEvent(JfrCheckpointType type, JfrRepository[] repositories, boolean writeSerializers, boolean flushpoint) { assert lock.isOwner(); SignedWord start = beginEvent(); @@ -318,7 +319,7 @@ private void writeCheckpointEvent(JfrCheckpointType type, JfrRepository[] reposi getFileSupport().writeInt(fd, 0); // pool count (patched below) int poolCount = writeSerializers ? writeSerializers() : 0; - poolCount += writeConstantPools(repositories, flush); + poolCount += writeConstantPools(repositories, flushpoint); SignedWord currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); @@ -345,10 +346,10 @@ private int writeSerializers() { return serializers.length; } - private int writeConstantPools(JfrRepository[] repositories, boolean flush) { + private int writeConstantPools(JfrRepository[] repositories, boolean flushpoint) { int poolCount = 0; for (JfrRepository repo : repositories) { - poolCount += repo.write(this, flush); + poolCount += repo.write(this, flushpoint); } return poolCount; } @@ -505,15 +506,15 @@ public void writeString(String str) { } @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") - private void flushStorage(boolean flush) { - traverseThreadLocalBuffers(getJavaBufferList(), flush); - traverseThreadLocalBuffers(getNativeBufferList(), flush); + private void flushStorage(boolean flushpoint) { + traverseThreadLocalBuffers(getJavaBufferList(), flushpoint); + traverseThreadLocalBuffers(getNativeBufferList(), flushpoint); - flushGlobalMemory(flush); + flushGlobalMemory(flushpoint); } @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - private void traverseThreadLocalBuffers(JfrBufferList list, boolean flush) { + private void traverseThreadLocalBuffers(JfrBufferList list, boolean flushpoint) { JfrBufferNode node = list.getHead(); JfrBufferNode prev = WordFactory.nullPointer(); @@ -529,30 +530,38 @@ private void traverseThreadLocalBuffers(JfrBufferList list, boolean flush) { continue; } - try { - if (flush) { + /* Skip retired nodes as they may contain invalid data. */ + if (!JfrBufferNodeAccess.isRetired(node)) { + try { + if (flushpoint) { + /* + * I/O operations may be slow, so this flushes to the global buffers + * instead of writing to disk directly. This mitigates the risk of + * acquiring the thread-local buffers for too long. + */ + SubstrateJVM.getGlobalMemory().write(buffer, true); + } else { + write(buffer); + } /* - * I/O operations may be slow, so this flushes to the global buffers instead - * of writing to disk directly. This mitigates the risk of acquiring the - * thread-local buffers for too long. + * The flushed position is modified in the calls above. We do *not* + * reinitialize the thread-local buffers as the individual threads will + * handle space reclamation on their own time. */ - SubstrateJVM.getGlobalMemory().write(buffer, true); - } else { - write(buffer); + } finally { + JfrBufferNodeAccess.unlock(node); } - } finally { - JfrBufferNodeAccess.unlock(node); } } - assert success || flush; + assert success || flushpoint; prev = node; node = next; } } @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - private void flushGlobalMemory(boolean flush) { + private void flushGlobalMemory(boolean flushpoint) { JfrBufferList buffers = globalMemory.getBuffers(); JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { @@ -566,7 +575,7 @@ private void flushGlobalMemory(boolean flush) { JfrBufferNodeAccess.unlock(node); } } - assert success || flush; + assert success || flushpoint; node = node.getNext(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java index ea2cef42ccbb..1071a9715260 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java @@ -24,11 +24,10 @@ */ package com.oracle.svm.core.jfr; -import java.lang.reflect.Field; - import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.Pointer; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.util.ReflectionUtil; @@ -39,20 +38,19 @@ * Used to access the Java event writer class, see {@code jdk.jfr.internal.EventWriter}. */ public final class JfrEventWriterAccess { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final Unsafe U = Unsafe.getUnsafe(); + /* + * The fields "startPosition" and "startPositionAddress" in the JDK class EventWriter refer to + * the committed position and not to the start of the buffer. + */ + private static final long COMMITTED_POSITION_OFFSET = U.objectFieldOffset(getEventWriterClass(), "startPosition"); + private static final long COMMITTED_POSITION_ADDRESS_OFFSET = U.objectFieldOffset(getEventWriterClass(), "startPositionAddress"); + private static final long CURRENT_POSITION_OFFSET = U.objectFieldOffset(getEventWriterClass(), "currentPosition"); + private static final long MAX_POSITION_OFFSET = U.objectFieldOffset(getEventWriterClass(), "maxPosition"); + private static final long VALID_OFFSET = U.objectFieldOffset(getEventWriterClass(), "valid"); - private static final Field startPosition; - private static final Field startPositionAddress; - private static final Field currentPosition; - private static final Field maxPosition; - private static final Field valid; - static { - Class declaringClass = getEventWriterClass(); - startPosition = ReflectionUtil.lookupField(declaringClass, "startPosition"); - startPositionAddress = ReflectionUtil.lookupField(declaringClass, "startPositionAddress"); - currentPosition = ReflectionUtil.lookupField(declaringClass, "currentPosition"); - maxPosition = ReflectionUtil.lookupField(declaringClass, "maxPosition"); - valid = ReflectionUtil.lookupField(declaringClass, "valid"); + @Platforms(Platform.HOSTED_ONLY.class) + private JfrEventWriterAccess() { } @Platforms(Platform.HOSTED_ONLY.class) @@ -66,32 +64,43 @@ public static Class getEventWriterClass() { return ReflectionUtil.lookupClass(false, className); } - @Platforms(Platform.HOSTED_ONLY.class) - private JfrEventWriterAccess() { - } + public static Target_jdk_jfr_internal_EventWriter newEventWriter(JfrBuffer buffer, boolean isCurrentThreadExcluded) { + assert JfrBufferAccess.isEmpty(buffer) : "a fresh JFR buffer must be empty"; - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setStartPosition(Target_jdk_jfr_internal_EventWriter writer, long value) { - UNSAFE.putLong(writer, UNSAFE.objectFieldOffset(startPosition), value); + long committedPos = buffer.getCommittedPos().rawValue(); + long maxPos = JfrBufferAccess.getDataEnd(buffer).rawValue(); + long addressOfCommittedPos = JfrBufferAccess.getAddressOfCommittedPos(buffer).rawValue(); + long jfrThreadId = SubstrateJVM.getCurrentThreadId(); + if (JavaVersionUtil.JAVA_SPEC >= 19) { + return new Target_jdk_jfr_internal_EventWriter(committedPos, maxPos, addressOfCommittedPos, jfrThreadId, true, isCurrentThreadExcluded); + } else { + return new Target_jdk_jfr_internal_EventWriter(committedPos, maxPos, addressOfCommittedPos, jfrThreadId, true); + } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setStartPositionAddress(Target_jdk_jfr_internal_EventWriter writer, long value) { - UNSAFE.putLong(writer, UNSAFE.objectFieldOffset(startPositionAddress), value); - } + /** Update the EventWriter so that it uses the correct buffer and positions. */ + @Uninterruptible(reason = "Accesses a JFR buffer.") + public static void update(Target_jdk_jfr_internal_EventWriter writer, JfrBuffer buffer, int uncommittedSize, boolean valid) { + assert SubstrateJVM.getThreadLocal().getJavaBuffer() == buffer; + assert JfrBufferAccess.verify(buffer); - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setCurrentPosition(Target_jdk_jfr_internal_EventWriter writer, long value) { - UNSAFE.putLong(writer, UNSAFE.objectFieldOffset(currentPosition), value); - } + Pointer committedPos = buffer.getCommittedPos(); + Pointer addressOfCommittedPos = JfrBufferAccess.getAddressOfCommittedPos(buffer); + Pointer currentPos = committedPos.add(uncommittedSize); + Pointer maxPos = JfrBufferAccess.getDataEnd(buffer); - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setMaxPosition(Target_jdk_jfr_internal_EventWriter writer, long value) { - UNSAFE.putLong(writer, UNSAFE.objectFieldOffset(maxPosition), value); + U.putLong(writer, COMMITTED_POSITION_OFFSET, committedPos.rawValue()); + U.putLong(writer, COMMITTED_POSITION_ADDRESS_OFFSET, addressOfCommittedPos.rawValue()); + U.putLong(writer, CURRENT_POSITION_OFFSET, currentPos.rawValue()); + U.putLong(writer, MAX_POSITION_OFFSET, maxPos.rawValue()); + if (!valid) { + markAsInvalid(writer); + } } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setValid(Target_jdk_jfr_internal_EventWriter writer, boolean value) { - UNSAFE.putBooleanVolatile(writer, UNSAFE.objectFieldOffset(valid), value); + public static void markAsInvalid(Target_jdk_jfr_internal_EventWriter writer) { + /* The VM should never write true (only the JDK code may do that). */ + U.putBooleanVolatile(writer, VALID_OFFSET, false); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 27e1336a6cc9..e1f54e61a3f6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -110,13 +110,15 @@ public JfrBufferList getBuffers() { } @Uninterruptible(reason = "Epoch must not change while in this method.") - public boolean write(JfrBuffer threadLocalBuffer, boolean streamingFlush) { - UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(threadLocalBuffer); - return write(threadLocalBuffer, unflushedSize, streamingFlush); + public boolean write(JfrBuffer buffer, boolean flushpoint) { + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); + return write(buffer, unflushedSize, flushpoint); } @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, boolean streamingFlush) { + public boolean write(JfrBuffer buffer, UnsignedWord unflushedSize, boolean flushpoint) { + assert !JfrBufferNodeAccess.isRetired(buffer.getNode()); + if (unflushedSize.equal(0)) { return true; } @@ -131,20 +133,20 @@ public boolean write(JfrBuffer threadLocalBuffer, UnsignedWord unflushedSize, bo /* Copy all committed but not yet flushed memory to the promotion buffer. */ JfrBuffer promotionBuffer = JfrBufferNodeAccess.getBuffer(promotionNode); assert JfrBufferAccess.getAvailableSize(promotionBuffer).aboveOrEqual(unflushedSize); - UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(threadLocalBuffer), promotionBuffer.getCommittedPos(), unflushedSize); + UnmanagedMemoryUtil.copy(JfrBufferAccess.getFlushedPos(buffer), promotionBuffer.getCommittedPos(), unflushedSize); JfrBufferAccess.increaseCommittedPos(promotionBuffer, unflushedSize); shouldSignal = SubstrateJVM.getRecorderThread().shouldSignal(promotionBuffer); } finally { JfrBufferNodeAccess.unlock(promotionNode); } - JfrBufferAccess.increaseFlushedPos(threadLocalBuffer, unflushedSize); + JfrBufferAccess.increaseFlushedPos(buffer, unflushedSize); /* * Notify the thread that writes the global memory to disk. If we're flushing, the global * buffers are about to get persisted anyway. */ - if (shouldSignal && !streamingFlush) { + if (shouldSignal && !flushpoint) { SubstrateJVM.getRecorderThread().signal(); } return true; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index f45920c64fb7..5c525acb2003 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -107,10 +107,10 @@ public long getMethodId(Class clazz, String methodName, int methodId) { @Override @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public int write(JfrChunkWriter writer, boolean flush) { + public int write(JfrChunkWriter writer, boolean flushpoint) { mutex.lockNoTransition(); try { - JfrMethodEpochData epochData = getEpochData(!flush); + JfrMethodEpochData epochData = getEpochData(!flushpoint); int count = epochData.unflushedEntries; if (count == 0) { return EMPTY; @@ -120,7 +120,7 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedInt(count); writer.write(epochData.buffer); - epochData.clear(flush); + epochData.clear(flushpoint); return NON_EMPTY; } finally { mutex.unlock(); @@ -145,8 +145,8 @@ private static class JfrMethodEpochData { } @Uninterruptible(reason = "May write current epoch data.") - void clear(boolean flush) { - if (!flush) { + void clear(boolean flushpoint) { + if (!flushpoint) { table.clear(); } unflushedEntries = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java index 1de54332ca0f..543b7993e26a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java @@ -54,7 +54,7 @@ public interface JfrRepository { /** * Persists the data of the previous/current epoch. * - * @param flush Determines whether the current or previous epoch is used. + * @param flushpoint Determines whether the current or previous epoch is used. */ - int write(JfrChunkWriter writer, boolean flush); + int write(JfrChunkWriter writer, boolean flushpoint); } 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 e3ca7c61b4bd..222f436a8f1f 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 @@ -227,20 +227,20 @@ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) { @Override @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public int write(JfrChunkWriter writer, boolean flush) { - if (flush) { + public int write(JfrChunkWriter writer, boolean flushpoint) { + if (flushpoint) { /* * Flushing is not support for stack traces at the moment. When a stack trace is * serialized, the methods getOrPutStackTrace() and commitSerializedStackTrace() are - * used. The lock is not held all the time, so a flush could destroy the JfrBuffer of - * the epoch, while it is being written. + * used. The lock is not held all the time, so a flushpoint could destroy the JfrBuffer + * of the epoch, while it is being written. */ return EMPTY; } mutex.lockNoTransition(); try { - JfrStackTraceEpochData epochData = getEpochData(!flush); + JfrStackTraceEpochData epochData = getEpochData(!flushpoint); int count = epochData.unflushedEntries; if (count == 0) { return EMPTY; @@ -250,7 +250,7 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedInt(count); writer.write(epochData.buffer); - epochData.clear(flush); + epochData.clear(flushpoint); return NON_EMPTY; } finally { mutex.unlock(); @@ -364,8 +364,8 @@ private static class JfrStackTraceEpochData { } @Uninterruptible(reason = "May write current epoch data.") - void clear(boolean flush) { - if (!flush) { + void clear(boolean flushpoint) { + if (!flushpoint) { table.clear(); } unflushedEntries = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 7b37eb161977..695af27639be 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -130,10 +130,10 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r @Override @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public int write(JfrChunkWriter writer, boolean flush) { + public int write(JfrChunkWriter writer, boolean flushpoint) { mutex.lockNoTransition(); try { - JfrSymbolEpochData epochData = getEpochData(!flush); + JfrSymbolEpochData epochData = getEpochData(!flushpoint); int count = epochData.unflushedEntries; if (count == 0) { return EMPTY; @@ -143,7 +143,7 @@ public int write(JfrChunkWriter writer, boolean flush) { writer.writeCompressedLong(count); writer.write(epochData.buffer); - epochData.clear(flush); + epochData.clear(flushpoint); return NON_EMPTY; } finally { mutex.unlock(); @@ -226,8 +226,8 @@ private static class JfrSymbolEpochData { } @Uninterruptible(reason = "May write current epoch data.") - void clear(boolean flush) { - if (!flush) { + void clear(boolean flushpoint) { + if (!flushpoint) { table.clear(); } unflushedEntries = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 43fc0583218c..4bc0b2557ef8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -25,7 +25,6 @@ package com.oracle.svm.core.jfr; import org.graalvm.compiler.api.replacements.Fold; -import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -114,9 +113,16 @@ public long getThreadLocalBufferSize() { return threadLocalBufferSize; } - public void teardown() { + public void teardown(boolean destroyJfr) { getNativeBufferList().teardown(); - getJavaBufferList().teardown(); + /* + * Java buffers and their nodes can only be freed in destroyJfr(). Otherwise, there is no + * guarantee that the buffer isn't still needed (JDK class EventWriter is not + * uninterruptible). + */ + if (destroyJfr) { + getJavaBufferList().teardown(); + } } @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.") @@ -133,25 +139,28 @@ public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) { public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { if (SubstrateJVM.get().isRecording()) { ThreadEndEvent.emit(javaThread); - stopRecording(isolateThread); + stopRecording(isolateThread, true); } } @Uninterruptible(reason = "Accesses various JFR buffers.") - public static void stopRecording(IsolateThread isolateThread) { + public static void stopRecording(IsolateThread isolateThread, boolean threadExits) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ - JfrBufferNode javaNode = javaBufferNode.get(isolateThread); - javaBufferNode.set(isolateThread, WordFactory.nullPointer()); - JfrBufferNode nativeNode = nativeBufferNode.get(isolateThread); nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); - - flushToGlobalMemoryAndFreeBuffer(javaNode); flushToGlobalMemoryAndFreeBuffer(nativeNode); + JfrBufferNode javaNode = javaBufferNode.get(isolateThread); + javaBufferNode.set(isolateThread, WordFactory.nullPointer()); + if (threadExits) { + flushToGlobalMemoryAndFreeBuffer(javaNode); + } else { + flushToGlobalMemoryAndRetireBuffer(javaNode); + } + /* Clear the other event-related thread-locals. */ - dataLost.set(isolateThread, WordFactory.unsigned(0)); javaEventWriter.set(isolateThread, null); + dataLost.set(isolateThread, WordFactory.unsigned(0)); /* Clear stacktrace-related thread-locals. */ missedSamples.set(isolateThread, 0); @@ -184,6 +193,24 @@ private static void flushToGlobalMemoryAndFreeBuffer(JfrBufferNode node) { } } + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + private static void flushToGlobalMemoryAndRetireBuffer(JfrBufferNode node) { + assert VMOperation.isInProgressAtSafepoint(); + if (node.isNull()) { + return; + } + + JfrBufferNodeAccess.lockNoTransition(node); + try { + JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); + flushToGlobalMemory0(buffer, WordFactory.unsigned(0), 0); + + JfrBufferNodeAccess.setRetired(node); + } finally { + JfrBufferNodeAccess.unlock(node); + } + } + /** * This method excludes/includes a thread from JFR (emitting events and sampling). At the * moment, only the current thread may be excluded/included. See GR-44616. @@ -218,29 +245,40 @@ public Target_jdk_jfr_internal_EventWriter getEventWriter() { */ public Target_jdk_jfr_internal_EventWriter newEventWriter() { assert javaEventWriter.get() == null; - assert javaBufferNode.get().isNull(); - JfrBuffer buffer = getJavaBuffer(); + JfrBuffer buffer = reinstateBuffer(getJavaBuffer()); if (buffer.isNull()) { throw new OutOfMemoryError("OOME for thread local buffer"); } - assert JfrBufferAccess.isEmpty(buffer) : "a fresh JFR buffer must be empty"; - long startPos = buffer.getCommittedPos().rawValue(); - long maxPos = JfrBufferAccess.getDataEnd(buffer).rawValue(); - long addressOfPos = JfrBufferAccess.getAddressOfCommittedPos(buffer).rawValue(); - long jfrThreadId = SubstrateJVM.getCurrentThreadId(); - Target_jdk_jfr_internal_EventWriter result; - if (JavaVersionUtil.JAVA_SPEC >= 19) { - result = new Target_jdk_jfr_internal_EventWriter(startPos, maxPos, addressOfPos, jfrThreadId, true, false); - } else { - result = new Target_jdk_jfr_internal_EventWriter(startPos, maxPos, addressOfPos, jfrThreadId, true); - } + Target_jdk_jfr_internal_EventWriter result = JfrEventWriterAccess.newEventWriter(buffer, isCurrentThreadExcluded()); javaEventWriter.set(result); - return result; } + /** + * If recording is started and stopped multiple times, then we may get a retired buffer instead + * of allocating a new one. Retired buffers need to be reset to a clean state. Once such a + * buffer is reinstated, the flushing can iterate over them at any time. + */ + private static JfrBuffer reinstateBuffer(JfrBuffer buffer) { + if (buffer.isNull()) { + return WordFactory.nullPointer(); + } + + JfrBufferNode node = buffer.getNode(); + JfrBufferNodeAccess.lockNoTransition(node); + try { + if (JfrBufferNodeAccess.isRetired(node)) { + JfrBufferAccess.reinitialize(buffer); + JfrBufferNodeAccess.clearRetired(node); + } + } finally { + JfrBufferNodeAccess.unlock(node); + } + return buffer; + } + @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { JfrBufferNode node = javaBufferNode.get(); @@ -292,10 +330,12 @@ public static JfrBuffer flushToGlobalMemory(JfrBuffer buffer, UnsignedWord uncom assert JfrBufferAccess.isThreadLocal(buffer); assert buffer.getNode().isNonNull(); - /* Acquire the buffer because a streaming flush could be in progress. */ JfrBufferNode node = buffer.getNode(); JfrBufferNodeAccess.lockNoTransition(node); try { + if (JfrBufferNodeAccess.isRetired(node)) { + return WordFactory.nullPointer(); + } return flushToGlobalMemory0(buffer, uncommitted, requested); } finally { JfrBufferNodeAccess.unlock(node); @@ -308,12 +348,10 @@ private static JfrBuffer flushToGlobalMemory0(JfrBuffer buffer, UnsignedWord unc assert JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); - if (unflushedSize.aboveThan(0)) { - if (!SubstrateJVM.getGlobalMemory().write(buffer, unflushedSize, false)) { - JfrBufferAccess.reinitialize(buffer); - writeDataLoss(buffer, unflushedSize); - return WordFactory.nullPointer(); - } + if (!SubstrateJVM.getGlobalMemory().write(buffer, unflushedSize, false)) { + JfrBufferAccess.reinitialize(buffer); + writeDataLoss(buffer, unflushedSize); + return WordFactory.nullPointer(); } if (uncommitted.aboveThan(0)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 9ec0c6b13b77..f603b9409806 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -195,13 +195,13 @@ public boolean hasUnflushedData() { @Override @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public int write(JfrChunkWriter writer, boolean flush) { + public int write(JfrChunkWriter writer, boolean flushpoint) { mutex.lockNoTransition(); try { - JfrThreadEpochData epochData = getEpochData(!flush); + JfrThreadEpochData epochData = getEpochData(!flushpoint); int count = writeThreads(writer, epochData); count += writeThreadGroups(writer, epochData); - epochData.clear(flush); + epochData.clear(flushpoint); return count; } finally { mutex.unlock(); @@ -266,8 +266,8 @@ private static class JfrThreadEpochData { } @Uninterruptible(reason = "May write current epoch data.") - void clear(boolean flush) { - if (!flush) { + void clear(boolean flushpoint) { + if (!flushpoint) { threadTable.clear(); threadGroupTable.clear(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 703ba1bed86b..a4fef326bb93 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -64,14 +64,14 @@ public long getClassId(Class clazz) { } @Override - public int write(JfrChunkWriter writer, boolean flush) { - TypeInfo typeInfo = collectTypeInfo(flush); - int count = writeClasses(writer, typeInfo, flush); - count += writePackages(writer, typeInfo, flush); - count += writeModules(writer, typeInfo, flush); - count += writeClassLoaders(writer, typeInfo, flush); - - if (flush) { + public int write(JfrChunkWriter writer, boolean flushpoint) { + TypeInfo typeInfo = collectTypeInfo(flushpoint); + int count = writeClasses(writer, typeInfo, flushpoint); + count += writePackages(writer, typeInfo, flushpoint); + count += writeModules(writer, typeInfo, flushpoint); + count += writeClassLoaders(writer, typeInfo, flushpoint); + + if (flushpoint) { flushedClasses.addAll(typeInfo.classes); flushedPackages.putAll(typeInfo.packages); flushedModules.putAll(typeInfo.modules); @@ -87,10 +87,10 @@ public int write(JfrChunkWriter writer, boolean flush) { * Visit all used classes, and collect their packages, modules, classloaders and possibly * referenced classes. */ - private TypeInfo collectTypeInfo(boolean flush) { + private TypeInfo collectTypeInfo(boolean flushpoint) { TypeInfo typeInfo = new TypeInfo(); for (Class clazz : Heap.getHeap().getLoadedClasses()) { - if (flush) { + if (flushpoint) { if (JfrTraceId.isUsedCurrentEpoch(clazz)) { visitClass(typeInfo, clazz); } @@ -130,7 +130,7 @@ private void visitClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { } } - private int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { + private int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flushpoint) { if (typeInfo.classes.isEmpty()) { return EMPTY; } @@ -138,15 +138,15 @@ private int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush writer.writeCompressedInt(typeInfo.classes.size()); for (Class clazz : typeInfo.classes) { - writeClass(typeInfo, writer, clazz, flush); + writeClass(typeInfo, writer, clazz, flushpoint); } return NON_EMPTY; } - private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz, boolean flush) { + private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz, boolean flushpoint) { writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); writer.writeCompressedLong(getClassLoaderId(typeInfo, clazz.getClassLoader())); - writer.writeCompressedLong(getSymbolId(writer, clazz.getName(), flush, true)); + writer.writeCompressedLong(getSymbolId(writer, clazz.getName(), flushpoint, true)); writer.writeCompressedLong(getPackageId(typeInfo, clazz.getPackage())); writer.writeCompressedLong(clazz.getModifiers()); if (JavaVersionUtil.JAVA_SPEC >= 17) { @@ -155,16 +155,16 @@ private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz } @Uninterruptible(reason = "Needed for JfrSymbolRepository.getSymbolId().") - private static long getSymbolId(JfrChunkWriter writer, String symbol, boolean flush, boolean replaceDotWithSlash) { + private static long getSymbolId(JfrChunkWriter writer, String symbol, boolean flushpoint, boolean replaceDotWithSlash) { /* * The result is only valid for the current epoch, but the epoch can't change while the * current thread holds the JfrChunkWriter lock. */ assert writer.isLockedByCurrentThread(); - return SubstrateJVM.getSymbolRepository().getSymbolId(symbol, !flush, replaceDotWithSlash); + return SubstrateJVM.getSymbolRepository().getSymbolId(symbol, !flushpoint, replaceDotWithSlash); } - private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { + private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flushpoint) { if (typeInfo.packages.isEmpty()) { return EMPTY; } @@ -172,19 +172,19 @@ private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flus writer.writeCompressedInt(typeInfo.packages.size()); for (Map.Entry pkgInfo : typeInfo.packages.entrySet()) { - writePackage(typeInfo, writer, pkgInfo.getKey(), pkgInfo.getValue(), flush); + writePackage(typeInfo, writer, pkgInfo.getKey(), pkgInfo.getValue(), flushpoint); } return NON_EMPTY; } - private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flush) { + private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flushpoint) { writer.writeCompressedLong(pkgInfo.id); // id - writer.writeCompressedLong(getSymbolId(writer, pkgName, flush, true)); + writer.writeCompressedLong(getSymbolId(writer, pkgName, flushpoint, true)); writer.writeCompressedLong(getModuleId(typeInfo, pkgInfo.module)); writer.writeBoolean(false); // exported } - private int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { + private int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flushpoint) { if (typeInfo.modules.isEmpty()) { return EMPTY; } @@ -192,20 +192,20 @@ private int writeModules(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush writer.writeCompressedInt(typeInfo.modules.size()); for (Map.Entry modInfo : typeInfo.modules.entrySet()) { - writeModule(typeInfo, writer, modInfo.getKey(), modInfo.getValue(), flush); + writeModule(typeInfo, writer, modInfo.getKey(), modInfo.getValue(), flushpoint); } return NON_EMPTY; } - private void writeModule(TypeInfo typeInfo, JfrChunkWriter writer, Module module, long id, boolean flush) { + private void writeModule(TypeInfo typeInfo, JfrChunkWriter writer, Module module, long id, boolean flushpoint) { writer.writeCompressedLong(id); - writer.writeCompressedLong(getSymbolId(writer, module.getName(), flush, false)); + writer.writeCompressedLong(getSymbolId(writer, module.getName(), flushpoint, false)); writer.writeCompressedLong(0); // Version, e.g. "11.0.10-internal" writer.writeCompressedLong(0); // Location, e.g. "jrt:/java.base" writer.writeCompressedLong(getClassLoaderId(typeInfo, module.getClassLoader())); } - private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flush) { + private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, boolean flushpoint) { if (typeInfo.classLoaders.isEmpty()) { return EMPTY; } @@ -213,19 +213,19 @@ private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo, b writer.writeCompressedInt(typeInfo.classLoaders.size()); for (Map.Entry clInfo : typeInfo.classLoaders.entrySet()) { - writeClassLoader(writer, clInfo.getKey(), clInfo.getValue(), flush); + writeClassLoader(writer, clInfo.getKey(), clInfo.getValue(), flushpoint); } return NON_EMPTY; } - private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flush) { + private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id, boolean flushpoint) { writer.writeCompressedLong(id); if (cl == null) { writer.writeCompressedLong(0); - writer.writeCompressedLong(getSymbolId(writer, "bootstrap", flush, false)); + writer.writeCompressedLong(getSymbolId(writer, "bootstrap", flushpoint, false)); } else { writer.writeCompressedLong(JfrTraceId.getTraceId(cl.getClass())); - writer.writeCompressedLong(getSymbolId(writer, cl.getName(), flush, false)); + writer.writeCompressedLong(getSymbolId(writer, cl.getName(), flushpoint, false)); } } 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 1cb7c45ec392..0a07f8c5fe0c 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 @@ -33,7 +33,6 @@ import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -234,6 +233,7 @@ public boolean destroyJFR() { try { recorderThread.shutdown(); + threadLocal.teardown(true); globalMemory.teardown(); symbolRepo.teardown(); threadRepo.teardown(); @@ -484,23 +484,13 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted assert uncommittedSize >= 0; JfrBuffer oldBuffer = threadLocal.getJavaBuffer(); - if (oldBuffer.isNonNull()) { - JfrBuffer newBuffer = JfrThreadLocal.flushToGlobalMemory(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); - if (newBuffer.isNull()) { - /* The flush failed, so mark the EventWriter as invalid for this write attempt. */ - JfrEventWriterAccess.setStartPosition(writer, oldBuffer.getCommittedPos().rawValue()); - JfrEventWriterAccess.setCurrentPosition(writer, oldBuffer.getCommittedPos().rawValue()); - JfrEventWriterAccess.setValid(writer, false); - } else { - /* Update the EventWriter so that it uses the correct buffer and positions. */ - Pointer newCurrentPos = newBuffer.getCommittedPos().add(uncommittedSize); - JfrEventWriterAccess.setStartPosition(writer, newBuffer.getCommittedPos().rawValue()); - JfrEventWriterAccess.setCurrentPosition(writer, newCurrentPos.rawValue()); - if (newBuffer.notEqual(oldBuffer)) { - JfrEventWriterAccess.setStartPositionAddress(writer, JfrBufferAccess.getAddressOfCommittedPos(newBuffer).rawValue()); - JfrEventWriterAccess.setMaxPosition(writer, JfrBufferAccess.getDataEnd(newBuffer).rawValue()); - } - } + assert oldBuffer.isNonNull() : "Java EventWriter should not be used otherwise"; + JfrBuffer newBuffer = JfrThreadLocal.flushToGlobalMemory(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); + if (newBuffer.isNull()) { + /* The flush failed, so mark the EventWriter as invalid for this write attempt. */ + JfrEventWriterAccess.update(writer, oldBuffer, 0, false); + } else { + JfrEventWriterAccess.update(writer, newBuffer, uncommittedSize, true); } /* @@ -731,14 +721,14 @@ protected void operate() { /* No further JFR events are emitted, so free all JFR-related buffers. */ for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { - JfrThreadLocal.stopRecording(isolateThread); + JfrThreadLocal.stopRecording(isolateThread, false); } /* * If JFR recording is restarted later on, then it needs to start with a clean state. * Therefore, we clear all data that is still pending. */ - SubstrateJVM.getThreadLocal().teardown(); + SubstrateJVM.getThreadLocal().teardown(false); SubstrateJVM.getSamplerBufferPool().teardown(); SubstrateJVM.getGlobalMemory().clear(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java index d33e8be8619d..d8b17c421e44 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_EventWriter.java @@ -41,12 +41,12 @@ public final class Target_jdk_jfr_internal_EventWriter { @Alias @SuppressWarnings("unused") @TargetElement(onlyWith = JDK17OrEarlier.class) - Target_jdk_jfr_internal_EventWriter(long startPos, long maxPos, long startPosAddress, long threadID, boolean valid) { + Target_jdk_jfr_internal_EventWriter(long committedPos, long maxPos, long committedPosAddress, long threadID, boolean valid) { } @Alias @SuppressWarnings("unused") @TargetElement(onlyWith = JDK19OrLater.class) - Target_jdk_jfr_internal_EventWriter(long startPos, long maxPos, long startPosAddress, long threadID, boolean valid, boolean excluded) { + Target_jdk_jfr_internal_EventWriter(long committedPos, long maxPos, long committedPosAddress, long threadID, boolean valid, boolean excluded) { } } From e2da2db66fe70ebfe75838a2cf4e40a1359eae0c Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Tue, 7 Mar 2023 15:00:34 +0100 Subject: [PATCH 71/72] A few more fixes. --- substratevm/CHANGELOG.md | 2 +- .../core/jdk/JDKContainerSubstitutions.java | 2 + .../svm/core/jdk/UninterruptibleUtils.java | 1 + .../svm/core/jfr/JfrBufferNodeAccess.java | 9 ++ .../oracle/svm/core/jfr/JfrBufferType.java | 3 + .../oracle/svm/core/jfr/JfrChunkWriter.java | 90 +++++++++---------- .../svm/core/jfr/JfrEventWriterAccess.java | 22 ++--- .../oracle/svm/core/jfr/JfrRepository.java | 2 +- .../svm/core/jfr/JfrSerializerSupport.java | 6 +- .../oracle/svm/core/jfr/JfrThreadLocal.java | 9 +- .../svm/core/jfr/JfrThreadRepository.java | 2 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 23 ++--- .../JavaLangThreadGroupSubstitutions.java | 4 +- .../poolparsers/ClassConstantPoolParser.java | 1 - 14 files changed, 94 insertions(+), 82 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 0fe02cbbdbd5..f8d4bea2b538 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -25,7 +25,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-38414) BellSoft implemented the `MemoryPoolMXBean` for the serial and epsilon GCs. * (GR-40641) Dynamic linking of AWT libraries on Linux. * (GR-40463) Red Hat added experimental support for JMX, which can be enabled with the `--enable-monitoring` option (e.g. `--enable-monitoring=jmxclient,jmxserver`). -* (GR-42740) Red Hat added experimental support for JFR event streaming. +* (GR-42740) Together with Red Hat, we added experimental support for JFR event streaming. * (GR-44110) Native Image now targets `x86-64-v3` by default on AMD64 and supports a new `-march` option. Use `-march=compatibility` for best compatibility (previous default) or `-march=native` for best performance if the native executable is deployed on the same machine or on a machine with the same CPU features. To list all available machine types, use `-march=list`. * (GR-43971) Add native-image option `-E[=]` and support environment variable capturing in bundles. Previously almost all environment variables were available in the builder. To temporarily revert back to the old behaviour, env setting `NATIVE_IMAGE_SLOPPY_BUILDER_SANITATION=true` can be used. The old behaviour will be removed in a future release. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java index 2c7c2bad3e80..0f202de5d8de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKContainerSubstitutions.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; @TargetClass(className = "jdk.internal.platform.cgroupv1.CgroupV1Subsystem", onlyWith = JDK17OrLater.class) @Platforms(LINUX.class) @@ -69,6 +70,7 @@ final class Target_jdk_jfr_internal_instrument_JDKEvents { @Platforms(LINUX.class) final class Target_jdk_jfr_internal_RequestEngine { @Alias // + @TargetElement(onlyWith = JDK20OrLater.class) // @RecomputeFieldValue(kind = Kind.NewInstance, declClass = ReentrantLock.class) // private static ReentrantLock lock; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index cedba1a8fa2d..edb8c09bb17b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -611,6 +611,7 @@ public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Po @FunctionalInterface public interface CharReplacer { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) char replace(char val); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index 42cb92c0e59a..f27b7c137f35 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -42,6 +42,7 @@ * Used to access the raw memory of a {@link JfrBufferNode}. */ public final class JfrBufferNodeAccess { + private static final byte NONE = 0b00; private static final byte RETIRED = 0b01; private JfrBufferNodeAccess() { @@ -54,6 +55,7 @@ public static JfrBufferNode allocate(JfrBuffer buffer) { node.setBuffer(buffer); node.setNext(WordFactory.nullPointer()); node.setLockOwner(WordFactory.nullPointer()); + node.setFlags(NONE); NativeSpinLockUtils.initialize(ptrToLock(node)); } return node; @@ -113,6 +115,13 @@ private static CIntPointer ptrToLock(JfrBufferNode node) { return (CIntPointer) ((Pointer) node).add(JfrBufferNode.offsetOfLock()); } + /** + * The thread-local {@link JfrBuffer}s that are used for Java-level JFR events can't be freed + * when JFR recording is stopped because the JDK class {@code EventWriter} is not + * uninterruptible (i.e., threads could continue using such {@link JfrBuffer}s even after the + * 'StopRecording' safepoint ends). Therefore, we mark those buffers as retired and either free + * or reinstantiate them at a later point in time. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setRetired(JfrBufferNode node) { assert isLockedByCurrentThread(node); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java index 7521bd5705c0..01cfe09e2c1b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferType.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; + /** * List of all possible {@link JfrBuffer} types. */ @@ -53,6 +55,7 @@ public enum JfrBufferType { this.threadLocal = threadLocal; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isThreadLocal() { return threadLocal; } 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 acd60b977262..627ad9c3b63f 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 @@ -34,7 +34,6 @@ import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -45,6 +44,8 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.os.RawFileOperationSupport.FileAccessMode; +import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; import com.oracle.svm.core.sampler.SamplerBuffersAccess; import com.oracle.svm.core.thread.JavaVMOperation; @@ -89,8 +90,8 @@ public final class JfrChunkWriter implements JfrUnlockedChunkWriter { private boolean newChunk; private boolean isFinal; private long lastMetadataId; - private SignedWord metadataPosition; - private SignedWord lastCheckpointOffset; + private long metadataPosition; + private long lastCheckpointOffset; @Platforms(Platform.HOSTED_ONLY.class) public JfrChunkWriter(JfrGlobalMemory globalMemory, JfrStackTraceRepository stackTraceRepo, JfrMethodRepository methodRepo, JfrTypeRepository typeRepo, JfrSymbolRepository symbolRepo, @@ -158,8 +159,8 @@ public void openFile(String outputFile) { newChunk = true; isFinal = false; lastMetadataId = -1; - metadataPosition = WordFactory.signed(-1); - lastCheckpointOffset = WordFactory.signed(-1); + metadataPosition = -1; + lastCheckpointOffset = -1; writeFileHeader(); } @@ -169,7 +170,7 @@ public void write(JfrBuffer buffer) { assert lock.isOwner(); assert buffer.isNonNull(); assert buffer.getBufferType() == JfrBufferType.C_HEAP || VMOperation.isInProgressAtSafepoint() || JfrBufferNodeAccess.isLockedByCurrentThread(buffer.getNode()); - assert buffer.getNode() == null || !JfrBufferNodeAccess.isRetired(buffer.getNode()); + assert buffer.getNode().isNull() || !JfrBufferNodeAccess.isRetired(buffer.getNode()); UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(buffer); if (unflushedSize.equal(0)) { @@ -233,7 +234,7 @@ private void writeFileHeader() { getFileSupport().write(fd, FILE_MAGIC); getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); getFileSupport().writeShort(fd, JFR_VERSION_MINOR); - assert getFileSupport().position(fd).equal(CHUNK_SIZE_OFFSET); + assert getFileSupport().position(fd) == CHUNK_SIZE_OFFSET; getFileSupport().writeLong(fd, 0L); // chunk size getFileSupport().writeLong(fd, 0L); // last checkpoint offset getFileSupport().writeLong(fd, 0L); // metadata position @@ -241,7 +242,7 @@ private void writeFileHeader() { getFileSupport().writeLong(fd, 0L); // durationNanos getFileSupport().writeLong(fd, chunkStartTicks); getFileSupport().writeLong(fd, JfrTicks.getTicksFrequency()); - assert getFileSupport().position(fd).equal(FILE_STATE_OFFSET); + assert getFileSupport().position(fd) == FILE_STATE_OFFSET; getFileSupport().writeByte(fd, getAndIncrementGeneration()); getFileSupport().writeByte(fd, (byte) 0); // padding getFileSupport().writeShort(fd, computeHeaderFlags()); @@ -249,22 +250,21 @@ private void writeFileHeader() { private void patchFileHeader(boolean flushpoint) { assert lock.isOwner(); - assert metadataPosition.greaterThan(0); - assert lastCheckpointOffset.greaterThan(0); + assert metadataPosition > 0; + assert lastCheckpointOffset > 0; byte generation = flushpoint ? getAndIncrementGeneration() : COMPLETE; - SignedWord currentPos = getFileSupport().position(fd); - long chunkSize = currentPos.rawValue(); + long currentPos = getFileSupport().position(fd); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; - getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); - getFileSupport().writeLong(fd, chunkSize); - getFileSupport().writeLong(fd, lastCheckpointOffset.rawValue()); - getFileSupport().writeLong(fd, metadataPosition.rawValue()); + getFileSupport().seek(fd, CHUNK_SIZE_OFFSET); + getFileSupport().writeLong(fd, currentPos); + getFileSupport().writeLong(fd, lastCheckpointOffset); + getFileSupport().writeLong(fd, metadataPosition); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); - getFileSupport().seek(fd, WordFactory.signed(FILE_STATE_OFFSET)); + getFileSupport().seek(fd, FILE_STATE_OFFSET); getFileSupport().writeByte(fd, generation); getFileSupport().writeByte(fd, (byte) 0); getFileSupport().writeShort(fd, computeHeaderFlags()); @@ -308,20 +308,20 @@ private void writeThreadCheckpoint(boolean flushpoint) { private void writeCheckpointEvent(JfrCheckpointType type, JfrRepository[] repositories, boolean writeSerializers, boolean flushpoint) { assert lock.isOwner(); - SignedWord start = beginEvent(); + long start = beginEvent(); writeCompressedLong(JfrReservedEvent.CHECKPOINT.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(getDeltaToLastCheckpoint(start)); writeByte(type.getId()); - SignedWord poolCountPos = getFileSupport().position(fd); + long poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // pool count (patched below) int poolCount = writeSerializers ? writeSerializers() : 0; poolCount += writeConstantPools(repositories, flushpoint); - SignedWord currentPos = getFileSupport().position(fd); + long currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); writePaddedInt(poolCount); @@ -331,11 +331,11 @@ private void writeCheckpointEvent(JfrCheckpointType type, JfrRepository[] reposi lastCheckpointOffset = start; } - private long getDeltaToLastCheckpoint(SignedWord startOfNewCheckpoint) { - if (lastCheckpointOffset.lessThan(0)) { + private long getDeltaToLastCheckpoint(long startOfNewCheckpoint) { + if (lastCheckpointOffset < 0) { return 0L; } - return lastCheckpointOffset.subtract(startOfNewCheckpoint).rawValue(); + return lastCheckpointOffset - startOfNewCheckpoint; } private int writeSerializers() { @@ -367,7 +367,7 @@ private void writeMetadataEvent() { return; } - SignedWord start = beginEvent(); + long start = beginEvent(); writeCompressedLong(JfrReservedEvent.METADATA.getId()); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration @@ -381,25 +381,25 @@ private void writeMetadataEvent() { public boolean shouldRotateDisk() { assert lock.isOwner(); - return getFileSupport().isValid(fd) && getFileSupport().size(fd).greaterThan(WordFactory.signed(notificationThreshold)); + return getFileSupport().isValid(fd) && getFileSupport().size(fd) > notificationThreshold; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public SignedWord beginEvent() { - SignedWord start = getFileSupport().position(fd); + public long beginEvent() { + long start = getFileSupport().position(fd); // Write a placeholder for the size. Will be patched by endEvent, getFileSupport().writeInt(fd, 0); return start; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void endEvent(SignedWord start) { - SignedWord end = getFileSupport().position(fd); - SignedWord writtenBytes = end.subtract(start); - assert (int) writtenBytes.rawValue() == writtenBytes.rawValue(); + public void endEvent(long start) { + long end = getFileSupport().position(fd); + long writtenBytes = end - start; + assert (int) writtenBytes == writtenBytes; getFileSupport().seek(fd, start); - writePaddedInt(writtenBytes.rawValue()); + writePaddedInt(writtenBytes); getFileSupport().seek(fd, end); } @@ -520,8 +520,8 @@ private void traverseThreadLocalBuffers(JfrBufferList list, boolean flushpoint) while (node.isNonNull()) { JfrBufferNode next = node.getNext(); - boolean success = JfrBufferNodeAccess.tryLock(node); - if (success) { + boolean lockAcquired = JfrBufferNodeAccess.tryLock(node); + if (lockAcquired) { JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); if (buffer.isNull()) { list.removeNode(node, prev); @@ -530,9 +530,9 @@ private void traverseThreadLocalBuffers(JfrBufferList list, boolean flushpoint) continue; } - /* Skip retired nodes as they may contain invalid data. */ - if (!JfrBufferNodeAccess.isRetired(node)) { - try { + try { + /* Skip retired nodes as they may contain invalid data. */ + if (!JfrBufferNodeAccess.isRetired(node)) { if (flushpoint) { /* * I/O operations may be slow, so this flushes to the global buffers @@ -548,13 +548,13 @@ private void traverseThreadLocalBuffers(JfrBufferList list, boolean flushpoint) * reinitialize the thread-local buffers as the individual threads will * handle space reclamation on their own time. */ - } finally { - JfrBufferNodeAccess.unlock(node); } + } finally { + JfrBufferNodeAccess.unlock(node); } } - assert success || flushpoint; + assert lockAcquired || flushpoint; prev = node; node = next; } @@ -565,8 +565,8 @@ private void flushGlobalMemory(boolean flushpoint) { JfrBufferList buffers = globalMemory.getBuffers(); JfrBufferNode node = buffers.getHead(); while (node.isNonNull()) { - boolean success = JfrBufferNodeAccess.tryLock(node); - if (success) { + boolean lockAcquired = JfrBufferNodeAccess.tryLock(node); + if (lockAcquired) { try { JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); write(buffer); @@ -575,7 +575,7 @@ private void flushGlobalMemory(boolean flushpoint) { JfrBufferNodeAccess.unlock(node); } } - assert success || flushpoint; + assert lockAcquired || flushpoint; node = node.getNext(); } } @@ -647,7 +647,7 @@ private void changeEpoch() { * accessing the currently active buffers of other threads. */ @Uninterruptible(reason = "Prevent JFR recording.") - private void processSamplerBuffers() { + private static void processSamplerBuffers() { assert VMOperation.isInProgressAtSafepoint(); assert ThreadingSupportImpl.isRecurringCallbackPaused(); @@ -660,7 +660,7 @@ private void processSamplerBuffers() { } @Uninterruptible(reason = "Prevent JFR recording.") - private void processSamplerBuffers0() { + private static void processSamplerBuffers0() { SamplerBuffersAccess.processActiveBuffers(); SamplerBuffersAccess.processFullBuffers(false); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java index 1071a9715260..d95c52b74555 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.jfr; +import java.lang.reflect.Field; + import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -43,11 +45,11 @@ public final class JfrEventWriterAccess { * The fields "startPosition" and "startPositionAddress" in the JDK class EventWriter refer to * the committed position and not to the start of the buffer. */ - private static final long COMMITTED_POSITION_OFFSET = U.objectFieldOffset(getEventWriterClass(), "startPosition"); - private static final long COMMITTED_POSITION_ADDRESS_OFFSET = U.objectFieldOffset(getEventWriterClass(), "startPositionAddress"); - private static final long CURRENT_POSITION_OFFSET = U.objectFieldOffset(getEventWriterClass(), "currentPosition"); - private static final long MAX_POSITION_OFFSET = U.objectFieldOffset(getEventWriterClass(), "maxPosition"); - private static final long VALID_OFFSET = U.objectFieldOffset(getEventWriterClass(), "valid"); + private static final Field COMMITTED_POSITION_FIELD = ReflectionUtil.lookupField(getEventWriterClass(), "startPosition"); + private static final Field COMMITTED_POSITION_ADDRESS_FIELD = ReflectionUtil.lookupField(getEventWriterClass(), "startPositionAddress"); + private static final Field CURRENT_POSITION_FIELD = ReflectionUtil.lookupField(getEventWriterClass(), "currentPosition"); + private static final Field MAX_POSITION_FIELD = ReflectionUtil.lookupField(getEventWriterClass(), "maxPosition"); + private static final Field VALID_FIELD = ReflectionUtil.lookupField(getEventWriterClass(), "valid"); @Platforms(Platform.HOSTED_ONLY.class) private JfrEventWriterAccess() { @@ -89,10 +91,10 @@ public static void update(Target_jdk_jfr_internal_EventWriter writer, JfrBuffer Pointer currentPos = committedPos.add(uncommittedSize); Pointer maxPos = JfrBufferAccess.getDataEnd(buffer); - U.putLong(writer, COMMITTED_POSITION_OFFSET, committedPos.rawValue()); - U.putLong(writer, COMMITTED_POSITION_ADDRESS_OFFSET, addressOfCommittedPos.rawValue()); - U.putLong(writer, CURRENT_POSITION_OFFSET, currentPos.rawValue()); - U.putLong(writer, MAX_POSITION_OFFSET, maxPos.rawValue()); + U.putLong(writer, U.objectFieldOffset(COMMITTED_POSITION_FIELD), committedPos.rawValue()); + U.putLong(writer, U.objectFieldOffset(COMMITTED_POSITION_ADDRESS_FIELD), addressOfCommittedPos.rawValue()); + U.putLong(writer, U.objectFieldOffset(CURRENT_POSITION_FIELD), currentPos.rawValue()); + U.putLong(writer, U.objectFieldOffset(MAX_POSITION_FIELD), maxPos.rawValue()); if (!valid) { markAsInvalid(writer); } @@ -101,6 +103,6 @@ public static void update(Target_jdk_jfr_internal_EventWriter writer, JfrBuffer @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void markAsInvalid(Target_jdk_jfr_internal_EventWriter writer) { /* The VM should never write true (only the JDK code may do that). */ - U.putBooleanVolatile(writer, VALID_OFFSET, false); + U.putBooleanVolatile(writer, U.objectFieldOffset(VALID_FIELD), false); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java index 543b7993e26a..4e3fe279f52b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRepository.java @@ -33,7 +33,7 @@ * added data when it tries to iterate the data at a safepoint. * * Some repositories (e.g., {@link JfrTypeRepository}) return stable JFR trace IDs (i.e., the trace - * id does not change if the epoch changes). However, the corresponding data (e.g., the type) is + * ID does not change if the epoch changes). However, the corresponding data (e.g., the type) is * only marked as used in a certain epoch, so callers must always be aware that the returned trace * ID is only valid for a specific epoch, no matter if the trace ID is stable or not. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java index b3a7b983307f..2dd8471fc34b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSerializerSupport.java @@ -32,8 +32,8 @@ import org.graalvm.nativeimage.Platforms; /** - * Support for registering and querying {@link JfrRepository}s that serialize data. Serializers are - * only written upon a new chunk. + * Support for registering and querying {@link JfrSerializer}s. Serializers are only written upon a + * new chunk. */ public class JfrSerializerSupport { private JfrSerializer[] serializers; @@ -47,7 +47,7 @@ public JfrSerializerSupport() { public synchronized void register(JfrSerializer serializer) { assert serializer != null; int oldLength = serializers.length; - // We expect a very small number of serializers, so only increase the size by 1. + /* We expect a very small number of serializers, so only increase the size by 1. */ serializers = Arrays.copyOf(serializers, oldLength + 1); serializers[oldLength] = serializer; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 4bc0b2557ef8..dcd379e65ace 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -144,7 +144,7 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { } @Uninterruptible(reason = "Accesses various JFR buffers.") - public static void stopRecording(IsolateThread isolateThread, boolean threadExits) { + public static void stopRecording(IsolateThread isolateThread, boolean freeJavaBuffer) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ JfrBufferNode nativeNode = nativeBufferNode.get(isolateThread); nativeBufferNode.set(isolateThread, WordFactory.nullPointer()); @@ -152,7 +152,7 @@ public static void stopRecording(IsolateThread isolateThread, boolean threadExit JfrBufferNode javaNode = javaBufferNode.get(isolateThread); javaBufferNode.set(isolateThread, WordFactory.nullPointer()); - if (threadExits) { + if (freeJavaBuffer) { flushToGlobalMemoryAndFreeBuffer(javaNode); } else { flushToGlobalMemoryAndRetireBuffer(javaNode); @@ -258,9 +258,10 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() { /** * If recording is started and stopped multiple times, then we may get a retired buffer instead - * of allocating a new one. Retired buffers need to be reset to a clean state. Once such a - * buffer is reinstated, the flushing can iterate over them at any time. + * of a new one. Retired buffers can have an invalid state, so we need to reset them before + * clearing the retired flag. */ + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private static JfrBuffer reinstateBuffer(JfrBuffer buffer) { if (buffer.isNull()) { return WordFactory.nullPointer(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index f603b9409806..579e6f1c3f31 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -51,7 +51,7 @@ * changes. */ public final class JfrThreadRepository implements JfrRepository { - private static final int VIRTUAL_THREAD_GROUP_ID = 1; + public static final int VIRTUAL_THREAD_GROUP_ID = 1; private final VMMutex mutex; private final JfrThreadEpochData epochData0; 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 0a07f8c5fe0c..623fb51d92a5 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 @@ -230,20 +230,15 @@ public boolean destroyJFR() { return false; } - try { - recorderThread.shutdown(); - - threadLocal.teardown(true); - globalMemory.teardown(); - symbolRepo.teardown(); - threadRepo.teardown(); - stackTraceRepo.teardown(); - methodRepo.teardown(); - typeRepo.teardown(); - } catch (Throwable e) { - /* Log errors because the shutdown hook swallows exceptions. */ - jfrLogging.warnInternal(e.getClass().getName() + " in destroyJFR."); - } + recorderThread.shutdown(); + + threadLocal.teardown(true); + globalMemory.teardown(); + symbolRepo.teardown(); + threadRepo.teardown(); + stackTraceRepo.teardown(); + methodRepo.teardown(); + typeRepo.teardown(); initialized = false; return true; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java index 27ce6da6ace7..6c2ced32de59 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java @@ -45,6 +45,7 @@ import com.oracle.svm.core.jdk.JDK17OrEarlier; import com.oracle.svm.core.jdk.JDK19OrLater; import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.JfrThreadRepository; import com.oracle.svm.util.ReflectionUtil; @TargetClass(ThreadGroup.class) @@ -112,8 +113,7 @@ final class Target_java_lang_ThreadGroup { * This class assigns a unique id to each thread group, and this unique id is used by JFR. */ class ThreadGroupIdAccessor { - private static final int VIRTUAL_THREAD_GROUP = 1; - private static final UninterruptibleUtils.AtomicLong nextID = new UninterruptibleUtils.AtomicLong(VIRTUAL_THREAD_GROUP + 1); + private static final UninterruptibleUtils.AtomicLong nextID = new UninterruptibleUtils.AtomicLong(JfrThreadRepository.VIRTUAL_THREAD_GROUP_ID + 1); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static long getId(Target_java_lang_ThreadGroup that) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java index 61dd2fd4b8a8..384cdd78673a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java @@ -39,7 +39,6 @@ public class ClassConstantPoolParser extends ConstantPoolParser { @Override public void parse(RecordingInput input) throws IOException { int numberOfClasses = input.readInt(); - for (int i = 0; i < numberOfClasses; i++) { addFoundId(input.readLong()); // ClassId. addExpectedId(JfrType.ClassLoader, input.readLong()); // ClassLoaderId. From 322c36dde058ca539c8e87233f55536dc52aad37 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 9 Mar 2023 13:08:30 +0100 Subject: [PATCH 72/72] Review feedback and minor test case improvements. --- .../AutomaticallyRegisteredFeature.java | 4 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 2 +- .../svm/test/jfr/utils/JfrFileParser.java | 191 ++++++++++-------- .../poolparsers/AbstractRepositoryParser.java | 29 +++ .../poolparsers/AbstractSerializerParser.java | 29 +++ .../poolparsers/ClassConstantPoolParser.java | 2 +- .../ClassLoaderConstantPoolParser.java | 2 +- .../utils/poolparsers/ConstantPoolParser.java | 4 + .../FrameTypeConstantPoolParser.java | 12 +- .../GCCauseConstantPoolParser.java | 12 +- .../poolparsers/GCNameConstantPoolParser.java | 12 +- .../poolparsers/MethodConstantPoolParser.java | 5 +- .../poolparsers/ModuleConstantPoolParser.java | 2 +- ...nitorInflationCauseConstantPoolParser.java | 3 +- .../PackageConstantPoolParser.java | 2 +- .../StacktraceConstantPoolParser.java | 2 +- .../poolparsers/SymbolConstantPoolParser.java | 2 +- .../poolparsers/ThreadConstantPoolParser.java | 2 +- .../ThreadGroupConstantPoolParser.java | 2 +- .../ThreadStateConstantPoolParser.java | 12 +- .../VMOperationConstantPoolParser.java | 12 +- 21 files changed, 216 insertions(+), 127 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractRepositoryParser.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractSerializerParser.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/feature/AutomaticallyRegisteredFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/feature/AutomaticallyRegisteredFeature.java index 5a862d1b0ab0..bf5bb370471e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/feature/AutomaticallyRegisteredFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/feature/AutomaticallyRegisteredFeature.java @@ -34,7 +34,9 @@ /** * {@link InternalFeature} classes with this annotation are automatically registered using an - * annotation processor. + * annotation processor. If multiple implementations for a class are annotated with + * {@link AutomaticallyRegisteredFeature}, only the most specific one will be registered as a + * feature. * * Note that this requires the `SVM_PROCESSOR` to be defined as an annotation processor in the * suite.py file. 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 623fb51d92a5..e788e67be516 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 @@ -714,7 +714,7 @@ protected void operate() { SubstrateJVM.get().recording = false; JfrExecutionSampler.singleton().update(); - /* No further JFR events are emitted, so free all JFR-related buffers. */ + /* No further JFR events are emitted, so free some JFR-related buffers. */ for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { JfrThreadLocal.stopRecording(isolateThread, false); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 1fb3ec06bd7a..27ef2f4d7c54 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -27,18 +27,23 @@ package com.oracle.svm.test.jfr.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import org.junit.Assert; - +import com.oracle.svm.core.jfr.JfrCheckpointType; import com.oracle.svm.core.jfr.JfrChunkWriter; import com.oracle.svm.core.jfr.JfrReservedEvent; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.JfrType; +import com.oracle.svm.test.jfr.utils.poolparsers.AbstractSerializerParser; import com.oracle.svm.test.jfr.utils.poolparsers.ClassConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ClassLoaderConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ConstantPoolParser; @@ -59,105 +64,136 @@ public class JfrFileParser { private static final HashMap supportedConstantPools; + private static final ArrayList serializerParsers; static { supportedConstantPools = new HashMap<>(); + serializerParsers = new ArrayList<>(); + + addParser(JfrType.Class, new ClassConstantPoolParser()); + addParser(JfrType.ClassLoader, new ClassLoaderConstantPoolParser()); + addParser(JfrType.Package, new PackageConstantPoolParser()); + addParser(JfrType.Module, new ModuleConstantPoolParser()); + + addParser(JfrType.Symbol, new SymbolConstantPoolParser()); + addParser(JfrType.Method, new MethodConstantPoolParser()); + addParser(JfrType.StackTrace, new StacktraceConstantPoolParser()); + + addParser(JfrType.Thread, new ThreadConstantPoolParser()); + addParser(JfrType.ThreadGroup, new ThreadGroupConstantPoolParser()); + + addParser(JfrType.FrameType, new FrameTypeConstantPoolParser()); + addParser(JfrType.ThreadState, new ThreadStateConstantPoolParser()); + addParser(JfrType.GCName, new GCNameConstantPoolParser()); + addParser(JfrType.GCCause, new GCCauseConstantPoolParser()); + addParser(JfrType.VMOperation, new VMOperationConstantPoolParser()); + addParser(JfrType.MonitorInflationCause, new MonitorInflationCauseConstantPoolParser()); + } - supportedConstantPools.put(JfrType.Class.getId(), new ClassConstantPoolParser()); - supportedConstantPools.put(JfrType.ClassLoader.getId(), new ClassLoaderConstantPoolParser()); - supportedConstantPools.put(JfrType.Package.getId(), new PackageConstantPoolParser()); - supportedConstantPools.put(JfrType.Module.getId(), new ModuleConstantPoolParser()); + private static void addParser(JfrType type, ConstantPoolParser parser) { + supportedConstantPools.put(type.getId(), parser); + if (parser instanceof AbstractSerializerParser p) { + serializerParsers.add(p); + } + } - supportedConstantPools.put(JfrType.Symbol.getId(), new SymbolConstantPoolParser()); - supportedConstantPools.put(JfrType.Method.getId(), new MethodConstantPoolParser()); - supportedConstantPools.put(JfrType.StackTrace.getId(), new StacktraceConstantPoolParser()); - supportedConstantPools.put(JfrType.FrameType.getId(), new FrameTypeConstantPoolParser()); + public static HashMap getSupportedConstantPools() { + return supportedConstantPools; + } - supportedConstantPools.put(JfrType.Thread.getId(), new ThreadConstantPoolParser()); - supportedConstantPools.put(JfrType.ThreadGroup.getId(), new ThreadGroupConstantPoolParser()); - supportedConstantPools.put(JfrType.ThreadState.getId(), new ThreadStateConstantPoolParser()); + public static void parse(Path path) throws IOException { + RecordingInput input = new RecordingInput(path.toFile()); + FileHeaderInfo header = parseFileHeader(input); + parseMetadata(input, header.metadataPosition); - supportedConstantPools.put(JfrType.GCName.getId(), new GCNameConstantPoolParser()); - supportedConstantPools.put(JfrType.GCCause.getId(), new GCCauseConstantPoolParser()); - supportedConstantPools.put(JfrType.VMOperation.getId(), new VMOperationConstantPoolParser()); - supportedConstantPools.put(JfrType.MonitorInflationCause.getId(), new MonitorInflationCauseConstantPoolParser()); + Collection constantPoolOffsets = getConstantPoolOffsets(input, header.checkpointPosition); + verifyConstantPools(input, constantPoolOffsets); } - public static HashMap getSupportedConstantPools() { - return supportedConstantPools; + private static void parseMetadata(RecordingInput input, long metadataPosition) throws IOException { + parseMetadataHeader(input, metadataPosition); + MetadataDescriptor.read(input); + } + + private static void parseMetadataHeader(RecordingInput input, long metadataPosition) throws IOException { + input.position(metadataPosition); + assertTrue("Metadata size is invalid!", input.readInt() > 0); + assertEquals(JfrReservedEvent.METADATA.getId(), input.readLong()); + assertTrue("Metadata timestamp is invalid!", input.readLong() > 0); + input.readLong(); // Duration. + assertTrue("Metadata ID is invalid!", input.readLong() > 0); } - private static Positions parserFileHeader(RecordingInput input) throws IOException { + private static FileHeaderInfo parseFileHeader(RecordingInput input) throws IOException { byte[] fileMagic = new byte[JfrChunkWriter.FILE_MAGIC.length]; input.readFully(fileMagic); // File magic. assertEquals("File magic is not correct!", new String(JfrChunkWriter.FILE_MAGIC), new String(fileMagic)); assertEquals("JFR version major is not correct!", JfrChunkWriter.JFR_VERSION_MAJOR, input.readRawShort()); assertEquals("JFR version minor is not correct!", JfrChunkWriter.JFR_VERSION_MINOR, input.readRawShort()); - assertTrue("Chunk size is invalid!", input.readRawLong() > 0); // Chunk size. + assertTrue("Chunk size is invalid!", input.readRawLong() > 0); - long constantPoolPosition = input.readRawLong(); - assertTrue("Constant pool positions is invalid!", constantPoolPosition > 0); + long checkpointPosition = input.readRawLong(); + assertTrue("Checkpoint positions is invalid!", checkpointPosition > 0); long metadataPosition = input.readRawLong(); - assertTrue("Metadata positions is null!", metadataPosition != 0); + assertTrue("Metadata position is invalid", metadataPosition > 0); long startingTime = input.readRawLong(); - assertTrue("Starting time is invalid!", startingTime > 0); // Starting time. - Assert.assertTrue("Starting time is bigger than current time!", startingTime < JfrTicks.currentTimeNanos()); + assertTrue("Starting time is invalid!", startingTime > 0); + assertTrue("Starting time is bigger than current time!", startingTime < JfrTicks.currentTimeNanos()); input.readRawLong(); // Duration. - assertTrue("Chunk start tick is invalid!", input.readRawLong() > 0); // ChunkStartTick. - assertTrue("Tick frequency is invalid!", input.readRawLong() > 0); // Tick frequency. + assertTrue("Chunk start tick is invalid!", input.readRawLong() > 0); + assertTrue("Tick frequency is invalid!", input.readRawLong() > 0); int shouldUseCompressedInt = input.readRawInt(); - assertTrue("Compressed int must be either 0 or 1!", shouldUseCompressedInt == 0 || shouldUseCompressedInt == 1); // ChunkWriteTick. + assertTrue("Compressed int must be either 0 or 1!", shouldUseCompressedInt == 0 || shouldUseCompressedInt == 1); - return new Positions(constantPoolPosition, metadataPosition); + return new FileHeaderInfo(checkpointPosition, metadataPosition); } - private static void parseMetadataHeader(RecordingInput input, long metadataPosition) throws IOException { - input.position(metadataPosition); // Seek to starting position of metadata region. - assertTrue("Metadata size is invalid!", input.readInt() > 0); // Size of metadata. - assertEquals(JfrReservedEvent.METADATA.getId(), input.readLong()); - assertTrue("Metadata timestamp is invalid!", input.readLong() > 0); // Timestamp. - input.readLong(); // Duration. - input.readLong(); // Metadata ID. - } - - private static void parseMetadata(RecordingInput input, long metadataPosition) throws IOException { - parseMetadataHeader(input, metadataPosition); - MetadataDescriptor.read(input); - } + private static ArrayDeque getConstantPoolOffsets(RecordingInput input, long initialCheckpointPosition) throws IOException { + ArrayDeque constantPoolOffsets = new ArrayDeque<>(); + long deltaNext; + long currentCheckpointPosition = initialCheckpointPosition; + do { + input.position(currentCheckpointPosition); + assertTrue("Constant pool size is invalid!", input.readInt() > 0); + assertEquals(JfrReservedEvent.CHECKPOINT.getId(), input.readLong()); + assertTrue("Constant pool timestamp is invalid!", input.readLong() > 0); + input.readLong(); // Duration. + deltaNext = input.readLong(); + assertTrue("Delta to next checkpoint is invalid!", deltaNext <= 0); + byte checkpointType = input.readByte(); + assertTrue("Checkpoint type is invalid!", checkpointType == JfrCheckpointType.Flush.getId() || checkpointType == JfrCheckpointType.Threads.getId()); + + constantPoolOffsets.addFirst(input.position()); + + currentCheckpointPosition += deltaNext; + } while (deltaNext != 0); - private static long parseConstantPoolHeader(RecordingInput input, long constantPoolPosition) throws IOException { - input.position(constantPoolPosition); // Seek to starting position of constant pools. - // Size of constant pools. - assertTrue("Constant pool size is invalid!", input.readInt() > 0); - // Constant pools region ID. - assertEquals(JfrReservedEvent.CHECKPOINT.getId(), input.readLong()); - assertTrue("Constant pool timestamp is invalid!", input.readLong() > 0); // Timestamp. - input.readLong(); // Duration. - long deltaNext = input.readLong(); // Offset to a next constant pools region. - assertTrue(input.readBoolean()); // Flush. - return deltaNext; + return constantPoolOffsets; } - private static void verifyConstantPools(RecordingInput input, long constantPoolPosition) throws IOException { - long deltaNext; - long currentConstantPoolPosition = constantPoolPosition; - do { - deltaNext = parseConstantPoolHeader(input, currentConstantPoolPosition); - long numberOfCPs = input.readInt(); + private static void verifyConstantPools(RecordingInput input, Collection constantPoolOffsets) throws IOException { + for (Long offset : constantPoolOffsets) { + input.position(offset); + int numberOfCPs = input.readInt(); for (int i = 0; i < numberOfCPs; i++) { ConstantPoolParser constantPoolParser = supportedConstantPools.get(input.readLong()); - Assert.assertNotNull("Unknown constant pool!", constantPoolParser); + assertNotNull("Unknown constant pool!", constantPoolParser); constantPoolParser.parse(input); } - currentConstantPoolPosition += deltaNext; - } while (deltaNext != 0); + compareFoundAndExpectedIds(); + } - /* Now that we collected all data, verify and clear it. */ - compareFoundAndExpectedIds(); + verifySerializers(); resetConstantPoolParsers(); } + private static void verifySerializers() { + for (ConstantPoolParser parser : serializerParsers) { + assertFalse("Serializer data must always be present in the chunk.", parser.isEmpty()); + } + } + private static void compareFoundAndExpectedIds() { for (ConstantPoolParser parser : supportedConstantPools.values()) { parser.compareFoundAndExpectedIds(); @@ -170,29 +206,6 @@ public static void resetConstantPoolParsers() { } } - public static void parse(Path path) throws IOException { - RecordingInput input = new RecordingInput(path.toFile()); - Positions positions = parserFileHeader(input); - verifyConstantPools(input, positions.getConstantPoolPosition()); - parseMetadata(input, positions.getMetadataPosition()); - } - - private static class Positions { - - private final long constantPoolPosition; - private final long metadataPosition; - - Positions(long constantPoolPosition, long metadataPositions) { - this.constantPoolPosition = constantPoolPosition; - this.metadataPosition = metadataPositions; - } - - public long getConstantPoolPosition() { - return constantPoolPosition; - } - - public long getMetadataPosition() { - return metadataPosition; - } + private record FileHeaderInfo(long checkpointPosition, long metadataPosition) { } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractRepositoryParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractRepositoryParser.java new file mode 100644 index 000000000000..46ddfcae625d --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractRepositoryParser.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. 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.utils.poolparsers; + +public abstract class AbstractRepositoryParser extends ConstantPoolParser { +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractSerializerParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractSerializerParser.java new file mode 100644 index 000000000000..2c410fea638d --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/AbstractSerializerParser.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. 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.utils.poolparsers; + +public abstract class AbstractSerializerParser extends ConstantPoolParser { +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java index 384cdd78673a..84c3dab18ca0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassConstantPoolParser.java @@ -34,7 +34,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class ClassConstantPoolParser extends ConstantPoolParser { +public class ClassConstantPoolParser extends AbstractRepositoryParser { @Override public void parse(RecordingInput input) throws IOException { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java index 806f55c9e40b..7943ba01f596 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ClassLoaderConstantPoolParser.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class ClassLoaderConstantPoolParser extends ConstantPoolParser { +public class ClassLoaderConstantPoolParser extends AbstractRepositoryParser { @Override public void reset() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java index 40ad96aaefa9..bd5d5b9f0a36 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ConstantPoolParser.java @@ -52,6 +52,10 @@ protected ConstantPoolParser() { foundIds.add(0L); } + public boolean isEmpty() { + return foundIds.isEmpty(); + } + protected final void addFoundId(long id) { foundIds.add(id); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/FrameTypeConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/FrameTypeConstantPoolParser.java index a172ddd8c9c1..29303f1a4168 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/FrameTypeConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/FrameTypeConstantPoolParser.java @@ -26,17 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; -import com.oracle.svm.test.jfr.utils.RecordingInput; +import java.io.IOException; + import org.junit.Assert; -import java.io.IOException; +import com.oracle.svm.test.jfr.utils.RecordingInput; -public class FrameTypeConstantPoolParser extends ConstantPoolParser { +public class FrameTypeConstantPoolParser extends AbstractSerializerParser { @Override public void parse(RecordingInput input) throws IOException { - int numberOfFrameTypes = input.readInt(); - for (int i = 0; i < numberOfFrameTypes; i++) { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { addFoundId(input.readInt()); // FrameTypeId. Assert.assertFalse("FrameTypeName is empty!", input.readUTF().isEmpty()); // FrameTypeName. } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCCauseConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCCauseConstantPoolParser.java index 54143d0879f9..ab0bfafaf489 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCCauseConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCCauseConstantPoolParser.java @@ -26,17 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; -import com.oracle.svm.test.jfr.utils.RecordingInput; +import java.io.IOException; + import org.junit.Assert; -import java.io.IOException; +import com.oracle.svm.test.jfr.utils.RecordingInput; -public class GCCauseConstantPoolParser extends ConstantPoolParser { +public class GCCauseConstantPoolParser extends AbstractSerializerParser { @Override public void parse(RecordingInput input) throws IOException { - int numberOfGCCauses = input.readInt(); - for (int i = 0; i < numberOfGCCauses; i++) { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { addFoundId(input.readInt()); // Id. Assert.assertFalse("GCCause name is empty!", input.readUTF().isEmpty()); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCNameConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCNameConstantPoolParser.java index b309ecde93e7..900fe72323b2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCNameConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/GCNameConstantPoolParser.java @@ -26,17 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; -import com.oracle.svm.test.jfr.utils.RecordingInput; +import java.io.IOException; + import org.junit.Assert; -import java.io.IOException; +import com.oracle.svm.test.jfr.utils.RecordingInput; -public class GCNameConstantPoolParser extends ConstantPoolParser { +public class GCNameConstantPoolParser extends AbstractSerializerParser { @Override public void parse(RecordingInput input) throws IOException { - int numberOfGCNames = input.readInt(); - for (int i = 0; i < numberOfGCNames; i++) { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { addFoundId(input.readInt()); // Id. Assert.assertFalse("GC name is empty!", input.readUTF().isEmpty()); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MethodConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MethodConstantPoolParser.java index c368f0ec0a2c..6eb9d217f298 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MethodConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MethodConstantPoolParser.java @@ -28,11 +28,12 @@ import java.io.IOException; +import org.junit.Assert; + import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -import org.junit.Assert; -public class MethodConstantPoolParser extends ConstantPoolParser { +public class MethodConstantPoolParser extends AbstractRepositoryParser { @Override public void parse(RecordingInput input) throws IOException { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ModuleConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ModuleConstantPoolParser.java index d39f3f918ad5..2a5b895cb8f0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ModuleConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ModuleConstantPoolParser.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class ModuleConstantPoolParser extends ConstantPoolParser { +public class ModuleConstantPoolParser extends AbstractRepositoryParser { @Override public void parse(RecordingInput input) throws IOException { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java index 75777994b4be..b0d3b40e25fc 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java @@ -32,11 +32,12 @@ import com.oracle.svm.test.jfr.utils.RecordingInput; -public class MonitorInflationCauseConstantPoolParser extends ConstantPoolParser { +public class MonitorInflationCauseConstantPoolParser extends AbstractSerializerParser { @Override public void parse(RecordingInput input) throws IOException { int count = input.readInt(); + Assert.assertTrue(count > 0); for (int i = 0; i < count; i++) { addFoundId(input.readInt()); Assert.assertFalse("Inflate cause is empty!", input.readUTF().isEmpty()); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java index 57478c469003..b838e23bc212 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/PackageConstantPoolParser.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class PackageConstantPoolParser extends ConstantPoolParser { +public class PackageConstantPoolParser extends AbstractRepositoryParser { @Override public void reset() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java index 4091d3f4a833..d16d952edb55 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/StacktraceConstantPoolParser.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class StacktraceConstantPoolParser extends ConstantPoolParser { +public class StacktraceConstantPoolParser extends AbstractRepositoryParser { @Override public void reset() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java index ee6cfb3f3ac6..4ecc28daecfd 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/SymbolConstantPoolParser.java @@ -30,7 +30,7 @@ import com.oracle.svm.test.jfr.utils.RecordingInput; -public class SymbolConstantPoolParser extends ConstantPoolParser { +public class SymbolConstantPoolParser extends AbstractRepositoryParser { @Override public void reset() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadConstantPoolParser.java index b6115d8c2aef..71767ac41081 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadConstantPoolParser.java @@ -34,7 +34,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class ThreadConstantPoolParser extends ConstantPoolParser { +public class ThreadConstantPoolParser extends AbstractRepositoryParser { @Override public void parse(RecordingInput input) throws IOException { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java index ba9ca9cb356b..282a294d3234 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadGroupConstantPoolParser.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.jfr.JfrType; import com.oracle.svm.test.jfr.utils.RecordingInput; -public class ThreadGroupConstantPoolParser extends ConstantPoolParser { +public class ThreadGroupConstantPoolParser extends AbstractRepositoryParser { @Override public void reset() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadStateConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadStateConstantPoolParser.java index 55228ba90c69..de9a9ad333b1 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadStateConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/ThreadStateConstantPoolParser.java @@ -26,17 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; -import com.oracle.svm.test.jfr.utils.RecordingInput; +import java.io.IOException; + import org.junit.Assert; -import java.io.IOException; +import com.oracle.svm.test.jfr.utils.RecordingInput; -public class ThreadStateConstantPoolParser extends ConstantPoolParser { +public class ThreadStateConstantPoolParser extends AbstractSerializerParser { @Override public void parse(RecordingInput input) throws IOException { - int numberOfThreadStates = input.readInt(); - for (int i = 0; i < numberOfThreadStates; i++) { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { addFoundId(input.readInt()); // ThreadStateId. Assert.assertFalse("ThreadStateName is empty!", input.readUTF().isEmpty()); // ThreadStateName. } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/VMOperationConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/VMOperationConstantPoolParser.java index 490a3b17ba8e..a3e0336a9ed2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/VMOperationConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/VMOperationConstantPoolParser.java @@ -26,17 +26,19 @@ package com.oracle.svm.test.jfr.utils.poolparsers; -import com.oracle.svm.test.jfr.utils.RecordingInput; +import java.io.IOException; + import org.junit.Assert; -import java.io.IOException; +import com.oracle.svm.test.jfr.utils.RecordingInput; -public class VMOperationConstantPoolParser extends ConstantPoolParser { +public class VMOperationConstantPoolParser extends AbstractSerializerParser { @Override public void parse(RecordingInput input) throws IOException { - int numberOfVMOperationTypes = input.readInt(); - for (int i = 0; i < numberOfVMOperationTypes; i++) { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { addFoundId(input.readInt()); // Id. Assert.assertFalse("VMOperation type is empty!", input.readUTF().isEmpty()); }