Skip to content

Commit 6f6188c

Browse files
JFR getStackTraceId implementation. JFR Tests. Code cleanup.
1 parent 6ce58a6 commit 6f6188c

22 files changed

+397
-87
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static boolean hasHeapDumpSupport() {
8787
}
8888

8989
@Fold
90-
public static boolean hasJFRSupport() {
90+
public static boolean hasJfrSupport() {
9191
return hasAllOrKeywordMonitoringSupport(MONITORING_JFR_NAME) && !Platform.includedIn(WINDOWS.class);
9292
}
9393

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
import org.graalvm.word.UnsignedWord;
3333
import org.graalvm.word.WordFactory;
3434

35-
import com.oracle.svm.core.UnmanagedMemoryUtil;
3635
import com.oracle.svm.core.Uninterruptible;
36+
import com.oracle.svm.core.UnmanagedMemoryUtil;
3737

3838
/**
3939
* An uninterruptible hashtable with a fixed size that uses chaining in case of a collision.
@@ -76,6 +76,7 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry pointerOnStack, U
7676

7777
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
7878
protected void free(UninterruptibleEntry entry) {
79+
size--;
7980
ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
8081
}
8182

@@ -158,7 +159,7 @@ public void clear() {
158159
}
159160
table[i] = WordFactory.nullPointer();
160161
}
161-
size = 0;
162+
assert size == 0 : "The table is not empty!";
162163
}
163164

164165
@Override

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.nio.charset.StandardCharsets;
2828
import java.util.concurrent.locks.ReentrantLock;
2929

30-
import com.oracle.svm.core.heap.VMOperationInfos;
3130
import org.graalvm.compiler.api.replacements.Fold;
3231
import org.graalvm.compiler.core.common.NumUtil;
3332
import org.graalvm.nativeimage.IsolateThread;
@@ -38,12 +37,14 @@
3837
import org.graalvm.word.WordFactory;
3938

4039
import com.oracle.svm.core.Uninterruptible;
40+
import com.oracle.svm.core.heap.VMOperationInfos;
41+
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
4142
import com.oracle.svm.core.os.RawFileOperationSupport;
43+
import com.oracle.svm.core.sampler.SamplerBuffersAccess;
4244
import com.oracle.svm.core.thread.JavaVMOperation;
4345
import com.oracle.svm.core.thread.VMOperation;
4446
import com.oracle.svm.core.thread.VMOperationControl;
4547
import com.oracle.svm.core.thread.VMThreads;
46-
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
4748

4849
/**
4950
* This class is used when writing the in-memory JFR data to a file. For all operations, except
@@ -383,6 +384,9 @@ protected void operate() {
383384
*/
384385
@Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.")
385386
private void changeEpoch() {
387+
/* Process all unprocessed sampler buffers before changing the epoch. */
388+
SamplerBuffersAccess.processSamplerBuffers();
389+
386390
// Write unflushed data from the thread local buffers but do *not* reinitialize them
387391
// The thread local code will handle space reclamation on their own time
388392
for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public final class JfrEvent {
6565
public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait");
6666

6767
private final long id;
68+
private final String name;
6869

6970
@Platforms(Platform.HOSTED_ONLY.class)
7071
private static JfrEvent create(String name) {
@@ -84,10 +85,16 @@ private static JfrEvent create(String name, Class<? extends BooleanSupplier> onl
8485
@Platforms(Platform.HOSTED_ONLY.class)
8586
private JfrEvent(String name) {
8687
this.id = JfrMetadataTypeLibrary.lookupPlatformEvent(name);
88+
this.name = name;
8789
}
8890

8991
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
9092
public long getId() {
9193
return id;
9294
}
95+
96+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
97+
public String getName() {
98+
return name;
99+
}
93100
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public static boolean isInConfiguration(boolean allowPrinting) {
112112
throw UserError.abort("FlightRecorder cannot be used to profile the image generator on this platform. " +
113113
"The image generator can only be profiled on platforms where FlightRecoder is also supported at run time.");
114114
}
115-
boolean runtimeEnabled = VMInspectionOptions.hasJFRSupport();
115+
boolean runtimeEnabled = VMInspectionOptions.hasJfrSupport();
116116
if (HOSTED_ENABLED && !runtimeEnabled) {
117117
if (allowPrinting) {
118118
System.err.println("Warning: When FlightRecoder is used to profile the image generator, it is also automatically enabled in the native image at run time. " +

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
*/
2525
package com.oracle.svm.core.jfr;
2626

27-
import com.oracle.svm.core.Uninterruptible;
2827
import org.graalvm.nativeimage.Platform;
2928
import org.graalvm.nativeimage.Platforms;
3029
import org.graalvm.nativeimage.StackValue;
3130
import org.graalvm.word.WordFactory;
3231

3332
import com.oracle.svm.core.SubstrateUtil;
33+
import com.oracle.svm.core.Uninterruptible;
3434
import com.oracle.svm.core.code.FrameInfoQueryResult;
3535
import com.oracle.svm.core.hub.DynamicHub;
3636
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@
4242
/**
4343
* A daemon thread that is created during JFR startup and torn down by
4444
* {@link SubstrateJVM#destroyJFR}.
45-
*
45+
* <p>
4646
* It is used for persisting the {@link JfrGlobalMemory} buffers to a file and for processing the
4747
* pool of {@link SamplerBuffer}s collected in signal handler (see {@link SubstrateSigprofHandler}).
4848
* With that in mind, the thread is using {@link VMSemaphore} for a synchronization between threads
4949
* as it is async signal safe.
50+
* </p>
5051
*/
5152
public class JfrRecorderThread extends Thread {
5253
private static final int BUFFER_FULL_ENOUGH_PERCENTAGE = 50;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.graalvm.nativeimage.Platform;
2828
import org.graalvm.nativeimage.Platforms;
2929
import org.graalvm.nativeimage.StackValue;
30+
import org.graalvm.nativeimage.c.function.CodePointer;
3031
import org.graalvm.nativeimage.c.struct.RawField;
3132
import org.graalvm.nativeimage.c.struct.RawStructure;
3233
import org.graalvm.nativeimage.c.struct.SizeOf;
@@ -35,6 +36,8 @@
3536
import org.graalvm.word.UnsignedWord;
3637
import org.graalvm.word.WordFactory;
3738

39+
import com.oracle.svm.core.FrameAccess;
40+
import com.oracle.svm.core.NeverInline;
3841
import com.oracle.svm.core.SubstrateOptions;
3942
import com.oracle.svm.core.Uninterruptible;
4043
import com.oracle.svm.core.UnmanagedMemoryUtil;
@@ -47,6 +50,13 @@
4750
import com.oracle.svm.core.locks.VMMutex;
4851
import com.oracle.svm.core.sampler.SamplerBuffer;
4952
import com.oracle.svm.core.sampler.SamplerBufferAccess;
53+
import com.oracle.svm.core.sampler.SamplerSampleWriter;
54+
import com.oracle.svm.core.sampler.SamplerSampleWriterData;
55+
import com.oracle.svm.core.sampler.SamplerSampleWriterDataAccess;
56+
import com.oracle.svm.core.sampler.SamplerThreadLocal;
57+
import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
58+
import com.oracle.svm.core.snippets.KnownIntrinsics;
59+
import com.oracle.svm.core.stack.JavaStackWalker;
5060

5161
/**
5262
* Repository that collects all metadata about stacktraces.
@@ -88,14 +98,40 @@ public void teardown() {
8898
epochData1.teardown();
8999
}
90100

91-
@Uninterruptible(reason = "Epoch must not change while in this method.")
92-
public long getStackTraceId(@SuppressWarnings("unused") int skipCount) {
101+
@NeverInline("Starting a stack walk in the caller frame.")
102+
@Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
103+
public long getStackTraceId(int skipCount) {
93104
assert maxDepth >= 0;
94-
return 0;
105+
long stackTraceId = 0;
106+
107+
/* Initialize stack walk. */
108+
SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
109+
SamplerThreadLocal.setSignalHandlerLocallyDisabled(true);
110+
if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth)) {
111+
SamplerSampleWriter.begin(data);
112+
/* Walk the stack. */
113+
Pointer sp = KnownIntrinsics.readCallerStackPointer();
114+
CodePointer ip = FrameAccess.singleton().readReturnAddress(sp);
115+
if (JavaStackWalker.walkCurrentThread(sp, ip, SubstrateSigprofHandler.visitor()) || data.getTruncated()) {
116+
acquireLock();
117+
try {
118+
CIntPointer status = StackValue.get(CIntPointer.class);
119+
Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
120+
stackTraceId = getStackTraceId(start, data.getCurrentPos(), data.getHashCode(), status, false);
121+
if (JfrStackTraceTableEntryStatus.get(status, JfrStackTraceTableEntryStatus.NEW)) {
122+
SamplerSampleWriter.end(data);
123+
}
124+
} finally {
125+
releaseLock();
126+
}
127+
}
128+
}
129+
SamplerThreadLocal.setSignalHandlerLocallyDisabled(false);
130+
return stackTraceId;
95131
}
96132

97133
@Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
98-
public long getStackTraceId(Pointer start, Pointer end, int hashCode, CIntPointer isRecorded) {
134+
public long getStackTraceId(Pointer start, Pointer end, int hashCode, CIntPointer status, boolean isSerializationInProgress) {
99135
JfrStackTraceEpochData epochData = getEpochData(false);
100136
JfrStackTraceTableEntry entry = StackValue.get(JfrStackTraceTableEntry.class);
101137

@@ -107,7 +143,7 @@ public long getStackTraceId(Pointer start, Pointer end, int hashCode, CIntPointe
107143

108144
JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) epochData.visitedStackTraces.get(entry);
109145
if (result.isNonNull()) {
110-
isRecorded.write(1);
146+
JfrStackTraceTableEntryStatus.update(result, status, false, result.getSerialized(), isSerializationInProgress);
111147
return result.getId();
112148
} else {
113149
/* Replace the previous pointer with new one (entry size and hash remains the same). */
@@ -118,7 +154,7 @@ public long getStackTraceId(Pointer start, Pointer end, int hashCode, CIntPointe
118154
UnmanagedMemoryUtil.copy(start, to, size);
119155

120156
JfrStackTraceTableEntry newEntry = (JfrStackTraceTableEntry) epochData.visitedStackTraces.getOrPut(entry);
121-
isRecorded.write(0);
157+
JfrStackTraceTableEntryStatus.update(newEntry, status, true, false, isSerializationInProgress);
122158
return newEntry.getId();
123159
}
124160
}
@@ -141,6 +177,7 @@ public void serializeStackTraceHeader(long stackTraceId, boolean isTruncated, in
141177
/* Stacktrace size. */
142178
JfrNativeEventWriter.putInt(data, stackTraceLength);
143179

180+
epochData.numberOfSerializedStackTraces++;
144181
JfrNativeEventWriter.commit(data);
145182

146183
/*
@@ -195,13 +232,12 @@ public int write(JfrChunkWriter writer) {
195232
}
196233

197234
private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData) {
198-
int stackTraceCount = epochData.visitedStackTraces.getSize();
199-
if (stackTraceCount == 0) {
235+
if (epochData.numberOfSerializedStackTraces == 0) {
200236
return EMPTY;
201237
}
202238

203239
writer.writeCompressedLong(JfrType.StackTrace.getId());
204-
writer.writeCompressedInt(stackTraceCount);
240+
writer.writeCompressedInt(epochData.numberOfSerializedStackTraces);
205241
writer.write(epochData.stackTraceBuffer);
206242

207243
return NON_EMPTY;
@@ -226,6 +262,12 @@ public interface JfrStackTraceTableEntry extends JfrVisited {
226262

227263
@RawField
228264
void setSize(int size);
265+
266+
@RawField
267+
boolean getSerialized();
268+
269+
@RawField
270+
void setSerialized(boolean serialized);
229271
}
230272

231273
public static final class JfrStackTraceTable extends AbstractUninterruptibleHashtable {
@@ -254,8 +296,31 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry valueOnStack) {
254296
}
255297
}
256298

299+
public static class JfrStackTraceTableEntryStatus {
300+
public static final int NEW = 1;
301+
public static final int SHOULD_SERIALIZE = NEW << 1;
302+
public static final int SERIALIZED = SHOULD_SERIALIZE << 1;
303+
304+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
305+
public static void update(JfrStackTraceTableEntry entry, CIntPointer status, boolean setNew, boolean isAlreadySerialized, boolean isSerializationInProgress) {
306+
int isRecorded = setNew ? NEW : 0;
307+
int shouldSerialize = !isAlreadySerialized ? SHOULD_SERIALIZE : 0;
308+
int isSerialized = isAlreadySerialized ? SERIALIZED : 0;
309+
status.write(isRecorded | shouldSerialize | isSerialized);
310+
if (!entry.getSerialized() && isSerializationInProgress) {
311+
entry.setSerialized(true);
312+
}
313+
}
314+
315+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
316+
public static boolean get(CIntPointer status, int check) {
317+
return (status.read() & check) != 0;
318+
}
319+
}
320+
257321
private static class JfrStackTraceEpochData {
258322
private JfrBuffer stackTraceBuffer;
323+
private int numberOfSerializedStackTraces;
259324
private final JfrStackTraceTable visitedStackTraces;
260325

261326
@Platforms(Platform.HOSTED_ONLY.class)
@@ -265,6 +330,7 @@ private static class JfrStackTraceEpochData {
265330

266331
void clear() {
267332
visitedStackTraces.clear();
333+
numberOfSerializedStackTraces = 0;
268334
if (stackTraceBuffer.isNonNull()) {
269335
JfrBufferAccess.reinitialize(stackTraceBuffer);
270336
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EndChunkNativePeriodicEvents.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
import java.util.Map;
2828
import java.util.Properties;
2929

30-
import com.oracle.svm.core.jfr.JfrEventWriteStatus;
3130
import org.graalvm.nativeimage.StackValue;
3231

3332
import com.oracle.svm.core.Uninterruptible;
3433
import com.oracle.svm.core.heap.Heap;
3534
import com.oracle.svm.core.jfr.JfrEvent;
35+
import com.oracle.svm.core.jfr.JfrEventWriteStatus;
3636
import com.oracle.svm.core.jfr.JfrNativeEventWriter;
3737
import com.oracle.svm.core.jfr.JfrNativeEventWriterData;
3838
import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess;
@@ -55,7 +55,7 @@ private static String formatOSInformation() {
5555
}
5656

5757
public static void emit() {
58-
emitClassLoadingStatistics(Heap.getHeap().getClassCount(), 0);
58+
emitClassLoadingStatistics(Heap.getHeap().getClassCount());
5959
emitJVMInformation(JVMInformation.getJVMInfo());
6060
emitOSInformation(formatOSInformation());
6161
emitInitialEnvironmentVariables(getEnvironmentVariables());
@@ -113,15 +113,15 @@ private static JfrEventWriteStatus emitInitialSystemProperty(JfrNativeEventWrite
113113
}
114114

115115
@Uninterruptible(reason = "Accesses a JFR buffer.")
116-
private static void emitClassLoadingStatistics(long loadedClassCount, long unloadedClassCount) {
116+
private static void emitClassLoadingStatistics(long loadedClassCount) {
117117
if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ClassLoadingStatistics)) {
118118
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
119119
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
120120

121121
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ClassLoadingStatistics);
122122
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks());
123123
JfrNativeEventWriter.putLong(data, loadedClassCount);
124-
JfrNativeEventWriter.putLong(data, unloadedClassCount);
124+
JfrNativeEventWriter.putLong(data, 0); /* unloadedClassCount */
125125
JfrNativeEventWriter.endSmallEvent(data);
126126
}
127127
}

0 commit comments

Comments
 (0)