> types();
+ }
+
+ protected ContextType() {}
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java
index d88742f7435f6..582dc505a02e3 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java
@@ -25,19 +25,23 @@
package jdk.jfr;
-import static jdk.jfr.internal.LogLevel.DEBUG;
-import static jdk.jfr.internal.LogLevel.INFO;
-import static jdk.jfr.internal.LogTag.JFR;
-
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.ServiceLoader;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.JVMSupport;
+import static jdk.jfr.internal.LogLevel.DEBUG;
+import static jdk.jfr.internal.LogLevel.INFO;
+import static jdk.jfr.internal.LogTag.JFR;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.Options;
@@ -45,8 +49,9 @@
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.Repository;
import jdk.jfr.internal.SecuritySupport;
-import jdk.jfr.internal.util.Utils;
+import jdk.jfr.internal.context.ContextRepository;
import jdk.jfr.internal.periodic.PeriodicEvents;
+import jdk.jfr.internal.util.Utils;
/**
* Class for accessing, controlling, and managing Flight Recorder.
@@ -169,6 +174,11 @@ public static FlightRecorder getFlightRecorder() throws IllegalStateException, S
JVMSupport.ensureWithIllegalStateException();
if (platformRecorder == null) {
try {
+ Logger.log(JFR, DEBUG, "Automatically registering available context types");
+ for (ContextType.Registration reg : ServiceLoader.load( ContextType.Registration.class)) {
+ reg.types().forEach(ContextRepository::getOrRegister);
+ }
+
platformRecorder = new FlightRecorder(new PlatformRecorder());
} catch (IllegalStateException ise) {
throw ise;
@@ -246,6 +256,16 @@ public static boolean removePeriodicEvent(Runnable hook) throws SecurityExceptio
return PeriodicEvents.removeEvent(hook);
}
+ @SuppressWarnings("unchecked")
+ public static boolean registerContext(Class extends ContextType> ctxType) {
+ Name typeNameAnnot = ctxType.getAnnotation(Name.class);
+ if (typeNameAnnot == null) {
+ throw new IllegalArgumentException("Context type must be annotated by @Name");
+ }
+ String id = typeNameAnnot.value();
+ return ContextRepository.getOrRegister(ctxType).isActive();
+ }
+
/**
* Returns an immutable list that contains all currently registered events.
*
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java
index 76daa39b57136..282ea22cd9a71 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java
@@ -26,6 +26,7 @@
package jdk.jfr.events;
import jdk.jfr.Category;
+import jdk.jfr.ContextAware;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
@@ -36,6 +37,7 @@
@Label("File Read")
@Category("Java Application")
@Description("Reading data from a file")
+@ContextAware
public final class FileReadEvent extends AbstractJDKEvent {
// The order of these fields must be the same as the parameters in
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java
index 508c4eb07cc7e..3197760eb66d5 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java
@@ -26,6 +26,7 @@
package jdk.jfr.events;
import jdk.jfr.Category;
+import jdk.jfr.ContextAware;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
@@ -36,6 +37,7 @@
@Label("File Write")
@Category("Java Application")
@Description("Writing data to a file")
+@ContextAware
public final class FileWriteEvent extends AbstractJDKEvent {
// The order of these fields must be the same as the parameters in
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java
index 8daf4726f675c..0c11c4e2a8dfa 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java
@@ -26,6 +26,7 @@
package jdk.jfr.events;
import jdk.jfr.Category;
+import jdk.jfr.ContextAware;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
@@ -37,6 +38,7 @@
@Label("Socket Read")
@Category("Java Application")
@Description("Reading data from a socket")
+@ContextAware
public final class SocketReadEvent extends AbstractJDKEvent {
// The order of these fields must be the same as the parameters in
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java
index 1ba40d29146d3..c6003571c7410 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java
@@ -26,6 +26,7 @@
package jdk.jfr.events;
import jdk.jfr.Category;
+import jdk.jfr.ContextAware;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
@@ -36,6 +37,7 @@
@Label("Socket Write")
@Category("Java Application")
@Description("Writing data to a socket")
+@ContextAware
public final class SocketWriteEvent extends AbstractJDKEvent {
// The order of these fields must be the same as the parameters in
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/ThreadSleepEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/ThreadSleepEvent.java
index ab606d8d6de4d..a3cc3251b609a 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/ThreadSleepEvent.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ThreadSleepEvent.java
@@ -26,6 +26,7 @@
package jdk.jfr.events;
import jdk.jfr.Category;
+import jdk.jfr.ContextAware;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.Timespan;
@@ -35,6 +36,7 @@
@Label("Java Thread Sleep")
@Name("jdk.ThreadSleep")
@MirrorEvent(className = "jdk.internal.event.ThreadSleepEvent")
+@ContextAware
public final class ThreadSleepEvent extends AbstractJDKEvent {
@Label("Sleep Time")
@Timespan(Timespan.NANOSECONDS)
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java
index 9c3770c8fad76..4402534cccf8f 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java
@@ -33,7 +33,6 @@
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
-
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
@@ -45,6 +44,7 @@
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.FieldNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
+import jdk.jfr.ContextAware;
import jdk.jfr.Enabled;
import jdk.jfr.Event;
import jdk.jfr.Name;
@@ -77,6 +77,7 @@ record FieldInfo(String name, String descriptor) {
private static final String ANNOTATION_NAME_DESCRIPTOR = Type.getDescriptor(Name.class);
private static final String ANNOTATION_REGISTERED_DESCRIPTOR = Type.getDescriptor(Registered.class);
private static final String ANNOTATION_ENABLED_DESCRIPTOR = Type.getDescriptor(Enabled.class);
+ private static final String ANNOTATION_CONTEXT_AWARE_DESCRIPTOR = Type.getDescriptor(ContextAware.class);
private static final Type TYPE_EVENT_CONFIGURATION = Type.getType(EventConfiguration.class);
private static final Type TYPE_EVENT_WRITER = Type.getType(EventWriter.class);
private static final Type TYPE_EVENT_WRITER_FACTORY = Type.getType("Ljdk/jfr/internal/event/EventWriterFactory;");
@@ -190,8 +191,23 @@ boolean isEnabled() {
return true;
}
+ boolean isContextAware() {
+ if (hasAnnotation(classNode, ANNOTATION_CONTEXT_AWARE_DESCRIPTOR)) {
+ return true;
+ }
+ if (superClass != null) {
+ return superClass.getAnnotation(ContextAware.class) != null;
+ }
+ return false;
+ }
+
+
+ private static T annotationValue(ClassNode classNode, String typeDescriptor, Class type) {
+ return annotationValue(classNode, typeDescriptor, type, null);
+ }
+
@SuppressWarnings("unchecked")
- private static T annotationValue(ClassNode classNode, String typeDescriptor, Class> type) {
+ private static T annotationValue(ClassNode classNode, String typeDescriptor, Class type, T dfltValue) {
if (classNode.visibleAnnotations != null) {
for (AnnotationNode a : classNode.visibleAnnotations) {
if (typeDescriptor.equals(a.desc)) {
@@ -206,6 +222,9 @@ private static T annotationValue(ClassNode classNode, String typeDescriptor,
}
}
}
+ } else {
+ // use the default value
+ return dfltValue;
}
}
}
@@ -213,6 +232,17 @@ private static T annotationValue(ClassNode classNode, String typeDescriptor,
return null;
}
+ private static boolean hasAnnotation(ClassNode classNode, String typeDescriptor) {
+ if (classNode.visibleAnnotations != null) {
+ for (AnnotationNode a : classNode.visibleAnnotations) {
+ if (typeDescriptor.equals(a.desc)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private static List buildSettingInfos(Class> superClass, ClassNode classNode) {
Set methodSet = new HashSet<>();
List settingInfos = new ArrayList<>();
@@ -326,6 +356,8 @@ public byte[] buildUninstrumented() {
}
private void makeInstrumented() {
+ boolean contextAware = isContextAware();
+
// MyEvent#isEnabled()
updateEnabledMethod(METHOD_IS_ENABLED);
@@ -408,6 +440,13 @@ private void makeInstrumented() {
// stack: [EW], [EW]
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.asASM());
// stack: [EW]
+ if (contextAware) {
+ // write _ctx_*
+ mv.visitInsn(Opcodes.DUP);
+ // stack: [EW], [EW]
+ visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_CONTEXT_FIELDS.asASM());
+ }
+ // stack: [EW]
// write custom fields
while (fieldIndex < fieldInfos.size()) {
mv.visitInsn(Opcodes.DUP);
@@ -555,6 +594,13 @@ private void makeInstrumented() {
// stack: [EW] [EW]
invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.asASM());
// stack: [EW]
+ if (contextAware) {
+ // write _ctx_*
+ methodVisitor.visitInsn(Opcodes.DUP);
+ // stack: [EW], [EW]
+ visitMethod(methodVisitor, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_CONTEXT_FIELDS.asASM());
+ }
+ // stack: [EW]
while (fieldIndex < fieldInfos.size()) {
FieldInfo field = fieldInfos.get(fieldIndex);
methodVisitor.visitInsn(Opcodes.DUP);
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java
index 5497aa19455ee..773f43109d026 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java
@@ -27,6 +27,7 @@
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.jfr.internal.EventInstrumentation.FieldInfo;
+import jdk.jfr.internal.context.ContextEventWriter;
import jdk.jfr.internal.event.EventConfiguration;
public enum EventWriterMethod {
@@ -45,7 +46,8 @@ public enum EventWriterMethod {
PUT_CLASS("(Ljava/lang/Class;)V", Type.CLASS.getName(), "putClass"),
PUT_STRING("(Ljava/lang/String;)V", Type.STRING.getName(), "putString"),
PUT_EVENT_THREAD("()V", Type.THREAD.getName(), "putEventThread"),
- PUT_STACK_TRACE("()V", Type.TYPES_PREFIX + "StackTrace", "putStackTrace");
+ PUT_STACK_TRACE("()V", Type.TYPES_PREFIX + "StackTrace", "putStackTrace"),
+ PUT_CONTEXT_FIELDS("()V", ContextEventWriter.class.getName(), "putContext");
final Method asmMethod;
final String typeDescriptor;
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
index 986b6f2450bac..a7e9a12eb4822 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
@@ -25,10 +25,15 @@
package jdk.jfr.internal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.LongBuffer;
import java.util.List;
-
+import jdk.internal.access.JFRContextAccess;
+import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.jfr.Event;
+import jdk.jfr.internal.context.ContextRepository;
import jdk.jfr.internal.event.EventConfiguration;
import jdk.jfr.internal.event.EventWriter;
@@ -60,6 +65,17 @@ private static class ChunkRotationMonitor {}
subscribeLogLevel(tag, tag.id);
}
Options.ensureInitialized();
+
+ if (Options.isContextEnabled()) {
+ SharedSecrets.setJFRContextAccess(new JFRContextAccess() {
+ public int getAllContext(long[] data, int size) {
+ return JVM.getAllContext(data, size);
+ }
+ public void setAllContext(long[] data) {
+ JVM.setAllContext(data);
+ }
+ });
+ }
}
/**
@@ -628,4 +644,27 @@ private static class ChunkRotationMonitor {}
* @param bytes number of bytes that were lost
*/
public static native void emitDataLoss(long bytes);
+
+ public static native void setUsedContextSize(int size);
+ public static int getAllContext(long[] context, int length) {
+ LongBuffer buffer = getThreadContextBuffer();
+ int toCopy = Math.min(buffer.limit(), length);
+ buffer.rewind().get(0, context, 0, toCopy);
+ return toCopy;
+ }
+ public static void setAllContext(long[] context) {
+ LongBuffer buffer = getThreadContextBuffer();
+ int toCopy = Math.min(buffer.limit(), context.length);
+ buffer.put(0, context, 0, toCopy).rewind();
+ }
+ public static native boolean isContextEnabled();
+
+ private static final ThreadLocal threadContextBufferRef = ThreadLocal.withInitial(JVM::initializeContextBuffer);
+ public static LongBuffer getThreadContextBuffer() {
+ return threadContextBufferRef.get();
+ }
+ private static final LongBuffer initializeContextBuffer() {
+ return ((ByteBuffer)getThreadContextBuffer0()).order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+ }
+ private static native Object getThreadContextBuffer0();
}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java
index 0d564fb931fb1..75e15eb1f219b 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java
@@ -34,7 +34,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-
import jdk.jfr.AnnotationElement;
import jdk.jfr.Category;
import jdk.jfr.Description;
@@ -85,6 +84,7 @@ private static final class TypeElement {
private final boolean isRelation;
private final boolean experimental;
private final boolean internal;
+ private final boolean withContext;
private final long id;
public TypeElement(DataInputStream dis) throws IOException {
@@ -108,6 +108,7 @@ public TypeElement(DataInputStream dis) throws IOException {
id = dis.readLong();
isEvent = dis.readBoolean();
isRelation = dis.readBoolean();
+ withContext = dis.readBoolean();
}
}
@@ -221,7 +222,7 @@ private void addFields(Map lookup, Map
Type type = lookup.get(te.name);
if (te.isEvent) {
boolean periodic = !te.period.isEmpty();
- TypeLibrary.addImplicitFields(type, periodic, te.startTime && !periodic, te.thread, te.stackTrace && !periodic, te.cutoff);
+ TypeLibrary.addImplicitFields(type, periodic, te.startTime && !periodic, te.thread, te.stackTrace && !periodic, te.cutoff, te.withContext);
}
for (FieldElement f : te.fields) {
Type fieldType = Type.getKnownType(f.typeName);
@@ -264,6 +265,7 @@ private void addFields(Map lookup, Map
}
type.add(PrivateAccess.getInstance().newValueDescriptor(f.name, fieldType, aes, f.array ? 1 : 0, f.constantPool, null));
}
+
}
}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java
index d07f1e6cb5e71..d5b9522149fda 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java
@@ -60,6 +60,7 @@ public final class Options {
private static int stackDepth;
private static long maxChunkSize;
private static boolean preserveRepository;
+ private static boolean contextEnabled;
static {
final long pageSize = Unsafe.getUnsafe().pageSize();
@@ -147,6 +148,10 @@ public static synchronized boolean getPreserveRepository() {
return preserveRepository;
}
+ public static boolean isContextEnabled() {
+ return contextEnabled;
+ }
+
private static synchronized void reset() {
setMaxChunkSize(DEFAULT_MAX_CHUNK_SIZE);
setMemorySize(DEFAULT_MEMORY_SIZE);
@@ -160,6 +165,7 @@ private static synchronized void reset() {
setStackDepth(DEFAULT_STACK_DEPTH);
setThreadBufferSize(DEFAULT_THREAD_BUFFER_SIZE);
setPreserveRepository(DEFAULT_PRESERVE_REPOSITORY);
+ contextEnabled = true;
}
static synchronized long getWaitInterval() {
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java
index 1215cfa29dbac..c78762e942467 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java
@@ -62,7 +62,7 @@
import jdk.jfr.internal.util.Utils;
public final class PlatformRecorder {
-
+ private static volatile boolean hasRecordings = false;
private final ArrayList recordings = new ArrayList<>();
private static final List changeListeners = new ArrayList<>();
@@ -125,6 +125,7 @@ private synchronized PlatformRecording newRecording(Map settings
recording.setSettings(settings);
}
recordings.add(recording);
+ hasRecordings = true;
return recording;
}
@@ -133,12 +134,17 @@ synchronized void finish(PlatformRecording recording) {
recording.stop("Recording closed");
}
recordings.remove(recording);
+ hasRecordings = !recordings.isEmpty();
}
public synchronized List getRecordings() {
return Collections.unmodifiableList(new ArrayList(recordings));
}
+ public static boolean hasRecordings() {
+ return hasRecordings;
+ }
+
public static synchronized void addListener(FlightRecorderListener changeListener) {
@SuppressWarnings("removal")
AccessControlContext context = AccessController.getContext();
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java
index 6513895069e17..f292f542eb092 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java
@@ -110,11 +110,15 @@ private static long ensureCurrentGeneration(String s, Long lsid) {
* of the EventWriter ensures that committed strings are in the correct generation.
*/
public static long addString(String s) {
+ return addString(s, true);
+ }
+
+ public static long addString(String s, boolean usePrecache) {
Long lsid = cache.get(s);
if (lsid != null) {
return ensureCurrentGeneration(s, lsid);
}
- if (!preCache(s)) {
+ if (usePrecache && !preCache(s)) {
/* we should not pool this string */
return DO_NOT_POOL;
}
@@ -125,6 +129,10 @@ public static long addString(String s) {
return storeString(s);
}
+ public static boolean hasString(String s) {
+ return cache.containsKey(s);
+ }
+
private static boolean preCache(String s) {
if (preCache[0].equals(s)) {
return true;
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
index 42c74ee64c9f5..3c07c467aad5c 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
@@ -47,8 +47,8 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
-
import jdk.jfr.AnnotationElement;
+import jdk.jfr.ContextAware;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
@@ -59,6 +59,7 @@
import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.util.Utils;
+import jdk.jfr.internal.context.ContextRepository;
public final class TypeLibrary {
private static boolean implicitFieldTypes;
@@ -266,7 +267,8 @@ public static synchronized Type createType(Class> clazz, List clazz, Type type, List varType = null;
+ if (descriptor.fAccess() != null) {
+ varType = descriptor.fAccess().varType();
+ } else if (descriptor.mAccess() != null) {
+ varType = descriptor.mAccess().type().returnType();
+ } else {
+ throw new IllegalStateException("A context field descriptor must have associated type field or method");
+ }
+ Type fType = Type.LONG;
+ if (varType.isAssignableFrom(String.class)) {
+ fType = Type.STRING;
+ } else if (varType == boolean.class) {
+ fType = Type.BOOLEAN;
+ }
+ type.add(PrivateAccess.getInstance().newValueDescriptor(name, fType, annos, 0, fType == Type.STRING, name));
+ }
+ }
}
private static List createStandardAnnotations(String name, String description) {
@@ -360,6 +383,16 @@ private static List createStandardAnnotations(String name, St
return annotationElements;
}
+ private static List createStandardAnnotations(String name, String label, String description) {
+ List annotationElements = new ArrayList<>(3);
+ annotationElements.add(new jdk.jfr.AnnotationElement(Name.class, name));
+ annotationElements.add(new jdk.jfr.AnnotationElement(Label.class, label));
+ if (description != null && !description.isEmpty()) {
+ annotationElements.add(new jdk.jfr.AnnotationElement(Description.class, description));
+ }
+ return annotationElements;
+ }
+
private static ValueDescriptor createField(Field field) {
int mod = field.getModifiers();
if (Modifier.isTransient(mod)) {
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/context/BaseContextType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/BaseContextType.java
new file mode 100644
index 0000000000000..406aa09205257
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/BaseContextType.java
@@ -0,0 +1,30 @@
+package jdk.jfr.internal.context;
+
+import jdk.jfr.ContextType;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.PlatformRecorder;
+
+public abstract class BaseContextType {
+ @SuppressWarnings("unchecked")
+ public final void set() {
+ if (shouldCaptureState()) {
+ ContextRepository.getOrRegister((Class)this.getClass()).set(this);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public final void unset() {
+ if (shouldCaptureState()) {
+ ContextRepository.getOrRegister((Class)this.getClass()).unset(this);
+ }
+ }
+
+ public final boolean isActive() {
+ return ContextRepository.getOrRegister(this.getClass()).isActive();
+ }
+
+ private static boolean shouldCaptureState() {
+ return FlightRecorder.isInitialized() && PlatformRecorder.hasRecordings() && JVM.isContextEnabled();
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextDescriptor.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextDescriptor.java
new file mode 100644
index 0000000000000..386155ef54954
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextDescriptor.java
@@ -0,0 +1,7 @@
+package jdk.jfr.internal.context;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
+
+public record ContextDescriptor(int order, String holderId, String name, String label, String description, VarHandle fAccess, MethodHandle mAccess) {
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextEventWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextEventWriter.java
new file mode 100644
index 0000000000000..fe9163dc59e2e
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextEventWriter.java
@@ -0,0 +1,16 @@
+package jdk.jfr.internal.context;
+
+import java.nio.LongBuffer;
+
+import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.event.EventWriter;
+
+public final class ContextEventWriter {
+ public static void putContext(EventWriter writer) {
+ LongBuffer context = JVM.getThreadContextBuffer();
+ int limit = context.capacity();
+ for (int i = 0; i < limit; i++) {
+ writer.putLong(context.get(i));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextRepository.java
new file mode 100644
index 0000000000000..6329a4b73e488
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextRepository.java
@@ -0,0 +1,136 @@
+package jdk.jfr.internal.context;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import jdk.jfr.Description;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Label;
+import jdk.jfr.Name;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.Logger;
+import static jdk.jfr.internal.LogLevel.DEBUG;
+import static jdk.jfr.internal.LogLevel.INFO;
+import static jdk.jfr.internal.LogTag.JFR;
+import static jdk.jfr.internal.LogLevel.WARN;
+
+public final class ContextRepository {
+ public static final int CAPACITY = 8; // 8 slots
+ private static final long[] EMPTY = new long[0];
+
+ private static int slotPointer = 0; // pointer to the next slot
+
+ private static final ContextDescriptor[] allDescriptors = new ContextDescriptor[CAPACITY];
+ private static final ClassValue> contextWriters = new ClassValue<>() {
+ @SuppressWarnings("unchecked")
+ @Override
+ protected ContextWriter> computeValue(Class> type) {
+ if (type.isAnnotationPresent(Name.class)) {
+ return writerFor(type);
+ }
+ return ContextWriter.NULL;
+ }
+ };
+
+ private ContextRepository() {
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ContextWriter getOrRegister(Class contextTypeClass) {
+ return (ContextWriter) contextWriters.get(contextTypeClass);
+ }
+
+ private static Set descriptorsOf(Class> contextTypeClass) {
+ try {
+ Set ctxDescriptors = new HashSet<>(8);
+ Name typeNameAnnot = contextTypeClass.getAnnotation(Name.class);
+ String id = typeNameAnnot.value();
+ MethodHandles.Lookup lookup = MethodHandles.publicLookup();
+ int order = 0;
+ for (Field f : contextTypeClass.getFields()) {
+ Class> fType = f.getType();
+ Name nameAnnot = f.getAnnotation(Name.class);
+ Label labelAnnot = f.getAnnotation(Label.class);
+ Description descAnnot = f.getAnnotation(Description.class);
+ if (nameAnnot != null || labelAnnot != null || descAnnot != null) {
+ if (fType.isAssignableFrom(String.class) || (fType.isPrimitive() && fType != float.class && fType != double.class)) {
+ String name = nameAnnot != null ? nameAnnot.value() : f.getName();
+ String label = labelAnnot != null ? labelAnnot.value() : name;
+ String desc = descAnnot != null ? descAnnot.value() : "";
+ ctxDescriptors.add(new ContextDescriptor(order++, id, name, label, desc, lookup.unreflectVarHandle(f), null));
+ } else {
+ throw new IllegalArgumentException("Only context fields of String, boolean, int, long, byte, short and char types are supported");
+ }
+ }
+
+ }
+ for (Method m : contextTypeClass.getMethods()) {
+ Class> rType = m.getReturnType();
+ Name nameAnnot = m.getAnnotation(Name.class);
+ Label labelAnnot = m.getAnnotation(Label.class);
+ Description descAnnot = m.getAnnotation(Description.class);
+ if (nameAnnot != null || labelAnnot != null || descAnnot != null) {
+ if (rType.isAssignableFrom(String.class) || (rType.isPrimitive() && rType != float.class && rType != double.class)) {
+ String name = nameAnnot != null ? nameAnnot.value() : m.getName().replace("get", "");
+ String label = labelAnnot != null ? labelAnnot.value() : name;
+ String desc = descAnnot != null ? descAnnot.value() : "";
+ ctxDescriptors.add(new ContextDescriptor(order++, id, name, label, desc, null, lookup.unreflect(m)));
+ } else {
+ throw new IllegalArgumentException("Only methods returning one of String, boolean, int, long, byte, short and char types are supported");
+ }
+ }
+ }
+ return ctxDescriptors;
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ContextWriter writerFor(Class type) {
+ Set ctxDescriptors = descriptorsOf(type);
+ int offset = register(ctxDescriptors);
+ if (offset == -2) {
+ Logger.log(JFR, INFO,
+ "Context type " + type + " can not be registered after FlightRecorder has been initialized");
+ } else if (offset == -3) {
+ Logger.log(JFR, WARN, "Context capacity exhausted. Context type " + type + " was not registered.");
+ } else {
+ Logger.log(JFR, DEBUG, "Registered type: " + type);
+ }
+ return offset > -1 ? new ContextWriter<>(offset, ctxDescriptors) : (ContextWriter) ContextWriter.NULL;
+ }
+
+ private static int register(Set descriptors) {
+ if (!shouldCaptureState()) {
+ return -1;
+ }
+ if (FlightRecorder.isInitialized()) {
+ return -2;
+ }
+ if (slotPointer + descriptors.size() > CAPACITY) {
+ return -3;
+ }
+ final int offset = slotPointer;
+ int top = 0;
+ for (var descriptor : descriptors) {
+ allDescriptors[descriptor.order() + slotPointer] = descriptor;
+ top = Math.max(top, descriptor.order());
+ }
+ slotPointer += top + 1;
+ JVM.setUsedContextSize(slotPointer);
+ return offset;
+ }
+
+ public static ContextDescriptor[] registrations() {
+ return Arrays.copyOf(allDescriptors, slotPointer);
+ }
+
+ private static boolean shouldCaptureState() {
+ return FlightRecorder.isAvailable() && JVM.isContextEnabled();
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextWriter.java
new file mode 100644
index 0000000000000..3380ae70948f7
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextWriter.java
@@ -0,0 +1,106 @@
+package jdk.jfr.internal.context;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
+import java.nio.LongBuffer;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import jdk.jfr.ContextAccess;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.StringPool;
+
+public final class ContextWriter implements ContextAccess {
+ static final ContextWriter> NULL = new ContextWriter<>(-1, null);
+ private final int offset;
+ private final Set descriptors;
+ private final Map attributeIndexMap;
+
+ ContextWriter(int offset, Set descriptors) {
+ this.offset = offset;
+ this.descriptors = offset > -1 ? Collections.unmodifiableSet(descriptors) : null;
+ this.attributeIndexMap = this.descriptors != null ? this.descriptors.stream().collect(Collectors.toUnmodifiableMap(ContextDescriptor::name, cd -> cd)) : null;
+ }
+
+ public boolean isActive() {
+ return offset != -1;
+ }
+
+ @Override
+ public void set(T target) {
+ if (offset == -1 || descriptors == null) {
+ return;
+ }
+ LongBuffer context = JVM.getThreadContextBuffer();
+ if (context == null) {
+ return;
+ }
+ for (ContextDescriptor cd : descriptors) {
+ if (cd.order() < 8) {
+ VarHandle fAccess = cd.fAccess();
+ if (fAccess != null) {
+ Class> vType = fAccess.varType();
+ if (vType == String.class) {
+ String value = (String) fAccess.get(target);
+ context.put(offset + cd.order(), value != null ? StringPool.addString(value, false) : 0);
+ } else if (vType == boolean.class) {
+ context.put(offset + cd.order(), ((boolean) fAccess.get(target) ? 0 : 1));
+ } else {
+ context.put(offset + cd.order(), (long) fAccess.get(target));
+ }
+ } else {
+ MethodHandle mAccess = cd.mAccess();
+ if (mAccess != null) {
+ Class> vType = mAccess.type().returnType();
+ try {
+ if (vType.isAssignableFrom(String.class)) {
+ String value = (String) mAccess.invoke(target);
+ context.put(offset + cd.order(), value != null ? StringPool.addString(value, false) : 0);
+ } else if (vType == boolean.class) {
+ context.put(offset + cd.order(), ((boolean) mAccess.invoke(target) ? 0 : 1));
+ } else {
+ context.put(offset + cd.order(), (long) mAccess.invoke(target));
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ } else {
+ throw new IllegalStateException("Invalid context field descriptor: neither field nor method");
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unset() {
+ unset(null);
+ }
+
+ public void unset(T target) {
+ if (offset == -1 || descriptors == null) {
+ return;
+ }
+ LongBuffer context = JVM.getThreadContextBuffer();
+ if (context == null) {
+ return;
+ }
+ for (ContextDescriptor cd : descriptors) {
+ if (cd.order() < 8) {
+ context.put(offset + cd.order(), 0L);
+ if (target != null) {
+ VarHandle access = cd.fAccess();
+ if (access != null) {
+ if (access.varType() == String.class) {
+ access.set(target, null);
+ } else {
+ access.set(target, 0);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java
index 43fc613049558..e062fd1e6a854 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java
@@ -27,6 +27,7 @@
import jdk.internal.misc.Unsafe;
import jdk.jfr.internal.Bits;
+import jdk.jfr.internal.context.ContextEventWriter;
import jdk.jfr.internal.EventWriterKey;
import jdk.jfr.internal.StringPool;
import jdk.jfr.internal.JVM;
@@ -194,6 +195,10 @@ public void putStackTrace() {
}
}
+ public void putContext() {
+ ContextEventWriter.putContext(this);
+ }
+
private void reserveEventSizeField() {
this.largeSize = eventType.isLargeSize();
if (largeSize) {
diff --git a/src/jdk.jfr/share/classes/module-info.java b/src/jdk.jfr/share/classes/module-info.java
index aab58c319537c..49a4471c60352 100644
--- a/src/jdk.jfr/share/classes/module-info.java
+++ b/src/jdk.jfr/share/classes/module-info.java
@@ -33,5 +33,8 @@
exports jdk.jfr;
exports jdk.jfr.consumer;
+ exports jdk.jfr.internal.context to jdk.jfr;
exports jdk.jfr.internal.management to jdk.management.jfr;
+
+ uses jdk.jfr.ContextType.Registration;
}
diff --git a/test/jdk/jdk/jfr/api/context/TestJfrContext.java b/test/jdk/jdk/jfr/api/context/TestJfrContext.java
new file mode 100644
index 0000000000000..14c1f069bc517
--- /dev/null
+++ b/test/jdk/jdk/jfr/api/context/TestJfrContext.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jdk.jfr.api.context;
+
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+import jdk.jfr.*;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.ContextType;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.jfr.ContextAwareEvent;
+import jdk.test.lib.jfr.Events;
+
+/**
+ * @test
+ * @summary JFR Context sanity test.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @build jdk.test.lib.jfr.ContextAwareEvent
+ * @run main/othervm jdk.jfr.api.context.TestJfrContext
+ */
+public class TestJfrContext {
+ @Name("test")
+ @Description("Test JFR Context")
+ public static class TestContextType extends ContextType {
+ @Name("state")
+ @Description("Global application state")
+ public String state;
+
+ @Name("result")
+ @Description("TRUE/FALSE")
+ public boolean result;
+
+ @Name("value")
+ public byte value;
+ }
+
+ @Name("too_large")
+ @Description("Not registered because of context capacity")
+ public static class TooLargeContextType extends ContextType {
+ @Name("attr1")
+ public String attr1;
+
+ @Name("attr2")
+ public String attr2;
+
+ @Name("attr3")
+ public String attr3;
+
+ @Name("attr4")
+ public String attr4;
+
+ @Name("attr5")
+ public String attr5;
+
+ @Name("attr6")
+ public String attr6;
+
+ @Name("attr7")
+ public String attr7;
+
+ @Name("attr8")
+ public String attr8;
+ }
+
+ public static void main(String[] args) throws Throwable {
+ boolean testCtxRegistered = FlightRecorder.registerContext(TestContextType.class);
+ boolean largeCtxRegistered = FlightRecorder.registerContext(TooLargeContextType.class);
+
+ Asserts.assertTrue(testCtxRegistered);
+ Asserts.assertFalse(largeCtxRegistered);
+
+ Recording r = new Recording();
+ r.enable("jdk.JavaMonitorWait").with("threshold", "5 ms");
+ r.setDestination(Paths.get("/tmp/dump.jfr"));
+ r.start();
+
+ TestContextType captured = new TestContextType();
+ captured.state = "enabled";
+ captured.result = false;
+ captured.value = -1;
+ captured.set();
+
+ ClassLoader classLoader = TestJfrContext.class.getClassLoader();
+ Class extends Event> eventClass =
+ classLoader.loadClass("jdk.test.lib.jfr.ContextAwareEvent").asSubclass(Event.class);
+
+ r.enable(eventClass).withThreshold(Duration.ofMillis(0)).withoutStackTrace();
+ new ContextAwareEvent(1).commit();
+ r.disable(eventClass);
+ new ContextAwareEvent(2).commit();
+ synchronized (captured) {
+ captured.wait(20);
+ }
+ r.enable(eventClass).withThreshold(Duration.ofMillis(0)).withoutStackTrace();
+ captured.state = "disabled";
+ captured.result = true;
+ captured.value = 1;
+ captured.set();
+ synchronized (captured) {
+ TooLargeContextType tlct = new TooLargeContextType();
+ tlct.attr1 = "value1";
+ captured.wait(20);
+ }
+ new ContextAwareEvent(3).commit();
+ r.stop();
+
+ verifyEvents(r, 1, 3);
+ }
+
+ private static void verifyEvents(Recording r, int ... ids) throws Exception {
+ List eventIds = new ArrayList<>();
+ for (RecordedEvent event : Events.fromRecording(r)) {
+ System.out.println("===> [" + event.getEventType().getId() + "]: " + event);
+ // int id = Events.assertField(event, "id").getValue();
+ // System.out.println("Event id:" + id);
+ // eventIds.add(id);
+
+ // String ctx = Events.assertField(event, "_ctx_1").getValue();
+ // System.out.println("===> context: " + ctx);
+ }
+ // Asserts.assertEquals(eventIds.size(), ids.length, "Wrong number of events");
+ // for (int i = 0; i < ids.length; ++i) {
+ // Asserts.assertEquals(eventIds.get(i).intValue(), ids[i], "Wrong id in event");
+ // }
+ }
+}
diff --git a/test/jdk/jdk/jfr/api/context/TestJfrContextVirtual.java b/test/jdk/jdk/jfr/api/context/TestJfrContextVirtual.java
new file mode 100644
index 0000000000000..698bd600a26df
--- /dev/null
+++ b/test/jdk/jdk/jfr/api/context/TestJfrContextVirtual.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jdk.jfr.api.context;
+
+import java.lang.reflect.Constructor;
+import java.net.*;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.locks.LockSupport;
+
+import jdk.jfr.*;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.context.ContextRegistration;
+import jdk.jfr.ContextType;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.jfr.ContextAwareEvent;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.jfr.SimpleEvent;
+
+/**
+ * @test
+ * @summary JFR Context sanity test.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @build jdk.test.lib.jfr.ContextAwareEvent
+ * @run main/othervm jdk.jfr.api.context.TestJfrContextVirtual
+ */
+public class TestJfrContextVirtual {
+ private static final int VIRTUAL_THREAD_COUNT = 10000; // 10_000;
+ private static final int STARTER_THREADS = 10; // 10;
+
+ @Name("test.Tester")
+ private static class TestEvent extends Event {
+ @Name("id")
+ public int id;
+ }
+
+ @Name("test")
+ public static class TestContextType extends ContextType {
+ @Name("taskid")
+ public String taskid;
+ }
+
+ public static void main(String[] args) throws Throwable {
+ // In order to be able to set/unset context one needs to obtain a ContextAccess instance
+ // The instance is associated with a context name and a number of named attributes.
+ // There is only a limited number of available attribute slots so it may happen that
+ // the registration request can not be fullfilled - in that case an 'empty' context will be
+ // returned where the operations of setting/unsetting context will be a noop.
+ boolean ctxRegistered = FlightRecorder.registerContext(TestContextType.class);
+
+ Asserts.assertTrue(ctxRegistered);
+
+ Recording r = new Recording();
+
+ r.enable(ContextAwareEvent.class).withThreshold(Duration.ofMillis(0)).withoutStackTrace();
+ r.enable(TestEvent.class).withThreshold(Duration.ofMillis(0));
+ r.setDestination(Paths.get("/tmp/dump.jfr"));
+ r.start();
+
+ SimpleEvent e = new SimpleEvent();
+ e.id = 1;
+ e.commit();
+
+ new ContextAwareEvent(1).commit();
+
+ long ts = System.nanoTime();
+ ThreadFactory factory = Thread.ofVirtual().factory();
+ CompletableFuture>[] c = new CompletableFuture[STARTER_THREADS];
+ for (int j = 0; j < STARTER_THREADS; j++) {
+ int id1 = j;
+ c[j] = CompletableFuture.runAsync(() -> {
+ for (int i = 0; i < VIRTUAL_THREAD_COUNT / STARTER_THREADS; i++) {
+ int id = (i * STARTER_THREADS) + id1;
+ try {
+ Thread vt = factory.newThread(() -> {
+ TestContextType captured = new TestContextType();
+ // Any context-aware event emitted after the context was set wil contain the context fields
+ captured.taskid = "Task " + id;
+ captured.set();
+
+ SimpleEvent e1 = new SimpleEvent();
+ e1.id = id;
+ e1.begin();
+ new ContextAwareEvent(10000 + id).commit();
+ // createEvent(eventClass, 10000 + id);
+ LockSupport.parkNanos(10_000_000L); // 10ms
+ new ContextAwareEvent(20000 + id).commit();
+ // createEvent(eventClass, 20000 + id);
+ // context will be reset to the previous state when leaving the try block
+ e1.commit();
+ });
+ vt.start();
+ vt.join();
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ });
+ }
+ for (int j = 0; j < STARTER_THREADS; j++) {
+ c[j].get();
+ }
+
+ r.stop();
+
+ System.out.println("===> Test took: " + (double)((System.nanoTime() - ts) / 1_000_000L) + "ms");
+
+ verifyEvents(r, 1, 3);
+ }
+
+ private static void verifyEvents(Recording r, int ... ids) throws Exception {
+ List eventIds = new ArrayList<>();
+ for (RecordedEvent event : Events.fromRecording(r)) {
+ System.out.println("===> [" + event.getEventType().getId() + "]: " + event);
+ // int id = Events.assertField(event, "id").getValue();
+ // System.out.println("Event id:" + id);
+ // eventIds.add(id);
+
+ // String ctx = Events.assertField(event, "_ctx_1").getValue();
+ // System.out.println("===> context: " + ctx);
+ }
+ // Asserts.assertEquals(eventIds.size(), ids.length, "Wrong number of events");
+ // for (int i = 0; i < ids.length; ++i) {
+ // Asserts.assertEquals(eventIds.get(i).intValue(), ids[i], "Wrong id in event");
+ // }
+ }
+
+ private static void createEvent(Class extends Event> eventClass, int id) {
+ try {
+ Constructor extends Event> constructor = eventClass.getConstructor();
+ Event event = (Event) constructor.newInstance();
+ event.begin();
+ eventClass.getDeclaredField("id").setInt(event, id);
+ event.end();
+ event.commit();
+ } catch (Exception ignored) {}
+ }
+}
diff --git a/test/lib/jdk/test/lib/jfr/ContextAwareEvent.java b/test/lib/jdk/test/lib/jfr/ContextAwareEvent.java
new file mode 100644
index 0000000000000..cb9d465f9ece8
--- /dev/null
+++ b/test/lib/jdk/test/lib/jfr/ContextAwareEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018, 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 jdk.test.lib.jfr;
+
+import jdk.jfr.ContextAware;
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+
+@ContextAware
+@Name("test.ContextAware")
+public class ContextAwareEvent extends Event {
+ public ContextAwareEvent(int id) {
+ this.id = id;
+ }
+ public int id;
+}