diff --git a/.github/workflows/dd-sync.yml b/.github/workflows/dd-sync.yml new file mode 100644 index 0000000000000..e30ce9b2118c1 --- /dev/null +++ b/.github/workflows/dd-sync.yml @@ -0,0 +1,34 @@ +# .github/workflows/sync-upstream.yml +name: Sync Upstream + +on: + schedule: + - cron: '22 14 * * *' # Runs every day at 14:15 UTC + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Pull latest changes from upstream + run: | + git config --global user.email "sync@datadoghq.com" + git config --global user.name "Datadog Syncup Service" + git remote add upstream https://github.com/openjdk/jdk.git + git fetch upstream + git checkout -b upstream-master upstream/master + git checkout master + git merge upstream-master + + - name: Push changes to downstream + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GH_PAT }} + branch: master diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000..5e37e5e05e659 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +image: alpine + +stages: [trigger] + +build: + stage: trigger + variables: + JDK_VERSION: "22" + DEBUG_LEVEL: "fastdebug" + HASH: "${CI_COMMIT_SHORT_SHA}" + trigger: + project: DataDog/openjdk-build + strategy: depend + branch: main + forward: + pipeline_variables: true + +test: + stage: trigger + needs: [build] + variables: + JDK_VERSION: "22" + DEBUG_LEVEL: "fastdebug" + HASH: "${CI_COMMIT_SHORT_SHA}" + trigger: + project: DataDog/apm-reliability/async-profiler-build + strategy: depend + branch: main + forward: + pipeline_variables: true \ No newline at end of file diff --git a/make/src/classes/build/tools/jfr/GenerateJfrFiles.java b/make/src/classes/build/tools/jfr/GenerateJfrFiles.java index 6b89d02ad2f75..31088a6366af0 100644 --- a/make/src/classes/build/tools/jfr/GenerateJfrFiles.java +++ b/make/src/classes/build/tools/jfr/GenerateJfrFiles.java @@ -204,6 +204,7 @@ static class TypeElement { boolean isEvent; boolean isRelation; boolean supportStruct = false; + boolean withContext = false; String commitState; public boolean primitive; @@ -227,6 +228,7 @@ public void persist(DataOutputStream pos) throws IOException { pos.writeLong(id); pos.writeBoolean(isEvent); pos.writeBoolean(isRelation); + pos.writeBoolean(withContext); } } @@ -524,6 +526,7 @@ public void startElement(String uri, String localName, String qName, Attributes currentType.commitState = getString(attributes, "commitState"); currentType.isEvent = "Event".equals(qName); currentType.isRelation = "Relation".equals(qName); + currentType.withContext = getBoolean(attributes, "withContext", false); break; case "Field": currentField = new FieldElement(metadata); @@ -655,7 +658,8 @@ private static void printJfrEventControlHpp(Metadata metadata, File outputFile) out.write(" u1 stacktrace;"); out.write(" u1 enabled;"); out.write(" u1 large;"); - out.write(" u1 pad[5]; // Because GCC on linux ia32 at least tries to pack this."); + out.write(" u1 context;"); + out.write(" u1 pad[4]; // Because GCC on linux ia32 at least tries to pack this."); out.write("};"); out.write(""); out.write("union JfrNativeSettings {"); @@ -860,6 +864,7 @@ private static void printEvent(Printer out, TypeElement event, boolean empty) { out.write(" static const bool isInstant = " + !event.startTime + ";"); out.write(" static const bool hasCutoff = " + event.cutoff + ";"); out.write(" static const bool hasThrottle = " + event.throttle + ";"); + out.write(" static const bool hasContext = " + event.withContext + ";"); out.write(" static const bool isRequestable = " + !event.period.isEmpty() + ";"); out.write(" static const JfrEventId eventId = Jfr" + event.name + "Event;"); out.write(""); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 4951e74dfd48c..4d7b548c702fe 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -43,6 +43,7 @@ #include "jfr/instrumentation/jfrEventClassTransformer.hpp" #include "jfr/instrumentation/jfrJvmtiAgent.hpp" #include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/support/jfrContext.hpp" #include "jfr/support/jfrJdkJfrEvent.hpp" #include "jfr/support/jfrKlassUnloading.hpp" #include "jfr/utilities/jfrJavaLog.hpp" @@ -397,3 +398,17 @@ JVM_END JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes)) EventDataLoss::commit(bytes, min_jlong); JVM_END + +NO_TRANSITION(void, jfr_set_used_context_size(JNIEnv* env, jclass jvm, jint size)) + JfrContext::set_used_context_size(size); +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_is_context_enabled(JNIEnv* env, jclass jvmf)) + return JfrOptionSet::is_context_enabled(); +NO_TRANSITION_END + +NO_TRANSITION(jobject, jfr_get_thread_context_buffer(JNIEnv* env, jclass jvm)) + uint64_t* data; + uint8_t size = JfrContext::get_context(&data); + return env->NewDirectByteBuffer((void*)data, (jlong) size * 8); +NO_TRANSITION_END \ No newline at end of file diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 28bafc4b73f21..038f42a19e747 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -159,6 +159,10 @@ jlong JNICALL jfr_host_total_memory(JNIEnv* env, jclass jvm); void JNICALL jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes); +void JNICALL jfr_set_used_context_size(JNIEnv* env, jclass jvm, jint size); +jboolean JNICALL jfr_is_context_enabled(JNIEnv* env, jclass jvm); +jobject JNICALL jfr_get_thread_context_buffer(JNIEnv* env, jclass jvm); + #ifdef __cplusplus } #endif diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 7c571abbd695f..030bba0652e44 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -97,7 +97,10 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"isInstrumented", (char*)"(Ljava/lang/Class;)Z", (void*) jfr_is_class_instrumented, (char*)"isContainerized", (char*)"()Z", (void*) jfr_is_containerized, (char*)"hostTotalMemory", (char*)"()J", (void*) jfr_host_total_memory, - (char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss + (char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss, + (char*)"setUsedContextSize", (char*)"(I)V", (void*)jfr_set_used_context_size, + (char*)"isContextEnabled", (char*)"()Z", (void*)jfr_is_context_enabled, + (char*)"getThreadContextBuffer0", (char*)"()Ljava/lang/Object;", (void*)jfr_get_thread_context_buffer }; const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod); diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index f8fc4e5bccdfd..9e6a370b3c34e 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -89,20 +89,20 @@ - + - + - + @@ -110,7 +110,7 @@ - + @@ -359,7 +359,7 @@ - + @@ -715,19 +715,19 @@ + thread="true" stackTrace="true" startTime="false" withContext="true"> + thread="true" stackTrace="true" startTime="false" withContext="true"> - + @@ -915,14 +915,14 @@ + period="everyChunk" withContext="true"> + period="everyChunk" withContext="true"> @@ -1118,7 +1118,7 @@ - + diff --git a/src/hotspot/share/jfr/metadata/metadata.xsd b/src/hotspot/share/jfr/metadata/metadata.xsd index ffef3d932e3c2..91167f65a9917 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xsd +++ b/src/hotspot/share/jfr/metadata/metadata.xsd @@ -72,6 +72,7 @@ + diff --git a/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp b/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp index d487820c6906a..2fbbaa9c63f4c 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp +++ b/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp @@ -28,6 +28,7 @@ #include "jfr/recorder/jfrEventSetting.inline.hpp" #include "jfr/recorder/service/jfrEventThrottler.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/support/jfrContext.hpp" #include "jfr/utilities/jfrTime.hpp" #include "jfr/utilities/jfrTypes.hpp" #include "jfr/writers/jfrNativeEventWriter.hpp" @@ -243,6 +244,13 @@ class JfrEvent { if (T::hasStackTrace) { writer.write(sid); } + if (T::hasContext) { + uint64_t* data; + uint8_t ctx_len = JfrContext::get_context(&data); + for (uint8_t i = 0; i < ctx_len; i++) { + writer.write(data[i]); + } + } // Payload. static_cast(this)->writeData(writer); return writer.end_event_write(large_size) > 0; diff --git a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp index 9ad810bc3cd9c..fdb96262893e8 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp +++ b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp @@ -47,6 +47,7 @@ class JfrOptionSet : public AllStatic { static u4 _stack_depth; static jboolean _retransform; static jboolean _sample_protection; + static jboolean _context; static bool initialize(JavaThread* thread); static bool configure(TRAPS); @@ -74,6 +75,12 @@ class JfrOptionSet : public AllStatic { static bool allow_event_retransforms(); static bool sample_protection(); DEBUG_ONLY(static void set_sample_protection(jboolean protection);) + static void enable_context() { + _context = true; + } + static bool is_context_enabled() { + return true; // _context; + } static bool parse_flight_recorder_option(const JavaVMOption** option, char* delimiter); static bool parse_start_flight_recording_option(const JavaVMOption** option, char* delimiter); diff --git a/src/hotspot/share/jfr/support/jfrContext.cpp b/src/hotspot/share/jfr/support/jfrContext.cpp new file mode 100644 index 0000000000000..61cc782c39e0a --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrContext.cpp @@ -0,0 +1,67 @@ +#include "precompiled.hpp" +#include "jfrContext.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "runtime/thread.hpp" + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +uint8_t JfrContext::_size = 0; + +JfrThreadLocal* getThreadLocal() { + Thread* thrd = Thread::current_or_null_safe(); + return thrd != nullptr ? thrd->jfr_thread_local() : nullptr; +} + +void JfrContext::set_used_context_size(uint8_t size) { + assert(size <= 8, "max context size is 8"); + _size = size; +} + +uint64_t JfrContext::get_and_set_context(uint8_t idx, uint64_t value) { + JfrThreadLocal* jfrTLocal = getThreadLocal(); + if (jfrTLocal != nullptr) { + uint64_t old_value = 0; + jfrTLocal->get_context(&old_value, 1, idx); + jfrTLocal->set_context(&value, 1, idx); + return old_value; + } + return 0; +} + +uint8_t JfrContext::get_all_context(uint64_t* data, uint8_t length) { + JfrThreadLocal* jfrTLocal = getThreadLocal(); + if (jfrTLocal != nullptr) { + return jfrTLocal->get_context(data, MIN(_size, length), 0); + } + + return 0; +} + +uint8_t JfrContext::get_context(uint64_t** data) { + void* buffer = get_thread_context_buffer(); + if (buffer) { + *data = (uint64_t*) buffer; + return _size; + } + return 0; +} + +uint8_t JfrContext::set_all_context(uint64_t* data, uint8_t length) { + JfrThreadLocal* jfrTLocal = getThreadLocal(); + if (jfrTLocal != nullptr) { + jfrTLocal->set_context(data, MIN(_size, length), 0); + return length; + } + + return 0; +} + +void* JfrContext::get_thread_context_buffer() { + JfrThreadLocal* jfrTLocal = getThreadLocal(); + if (jfrTLocal != nullptr) { + return (void*)jfrTLocal->get_context_buffer(); + } + return nullptr; +} \ No newline at end of file diff --git a/src/hotspot/share/jfr/support/jfrContext.hpp b/src/hotspot/share/jfr/support/jfrContext.hpp new file mode 100644 index 0000000000000..9d6454f4c794f --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrContext.hpp @@ -0,0 +1,20 @@ +#ifndef SHARE_JFR_SUPPORT_CONTEXT_HPP +#define SHARE_JFR_SUPPORT_CONTEXT_HPP + +#include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" + +class JfrContext : AllStatic { + private: + static uint8_t _size; + public: + static void set_used_context_size(uint8_t size); + static inline uint8_t get_used_context_size() { return _size; } + static uint64_t get_and_set_context(uint8_t idx, uint64_t value); + static uint8_t get_all_context(uint64_t* data, uint8_t length); + static uint8_t set_all_context(uint64_t* data, uint8_t length); + static uint8_t get_context(uint64_t** data); + static void* get_thread_context_buffer(); +}; + +#endif // SHARE_JFR_SUPPORT_CONTEXT_HPP diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 73ff226c826db..f72c8c498b2fc 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -72,6 +72,8 @@ class JfrThreadLocal { bool _vthread; bool _notified; bool _dead; + uint64_t _context[8] {0}; + uint8_t _context_size = 0; JfrBuffer* install_native_buffer() const; JfrBuffer* install_java_buffer() const; @@ -267,6 +269,28 @@ class JfrThreadLocal { return _dead; } + // context operations + void set_context(uint64_t* context, uint8_t len, uint8_t offset) { + assert(len > 0, "setting empty context"); + assert(len + offset < 8, "context capacity"); + memcpy(_context + offset, context, len * sizeof(uint64_t)); + uint8_t new_size = len + offset; + _context_size = new_size > _context_size ? new_size : _context_size; + } + + uint8_t get_context(uint64_t* context, uint8_t len, uint8_t offset) { + int limit = (len + offset < 8) ? len : (len - offset); + len = limit < 0 ? 0 : limit; + assert(len >= 0, "getting empty context"); + assert(len + offset <= 8, "context capacity"); + memcpy(context, _context + offset, len * sizeof(uint64_t)); + return len; + } + + uint64_t* get_context_buffer() { + return _context; + } + bool is_excluded() const; bool is_included() const; static bool is_excluded(const Thread* thread); diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 37d092e20118b..01ec99eaaa6bb 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -24,6 +24,10 @@ */ package java.lang; +import jdk.internal.access.JFRContextAccess; +import jdk.internal.access.SharedSecrets; + +import java.lang.ref.Reference; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Locale; @@ -215,7 +219,7 @@ private void runContinuation() { } // notify JVMTI before mount - notifyJvmtiMount(/*hide*/true); + notifyMount(/*hide*/true); try { cont.run(); @@ -425,14 +429,14 @@ V executeOnCarrierThread(Callable task) throws Exception { @ChangesCurrentThread private boolean yieldContinuation() { // unmount - notifyJvmtiUnmount(/*hide*/true); + notifyUnmount(/*hide*/true); unmount(); try { return Continuation.yield(VTHREAD_SCOPE); } finally { // re-mount mount(); - notifyJvmtiMount(/*hide*/false); + notifyMount(/*hide*/false); } } @@ -449,7 +453,7 @@ private void afterYield() { setState(PARKED); // notify JVMTI that unmount has completed, thread is parked - notifyJvmtiUnmount(/*hide*/false); + notifyUnmount(/*hide*/false); // may have been unparked while parking if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) { @@ -465,7 +469,7 @@ private void afterYield() { setState(RUNNABLE); // notify JVMTI that unmount has completed, thread is runnable - notifyJvmtiUnmount(/*hide*/false); + notifyUnmount(/*hide*/false); // external submit if there are no tasks in the local task queue if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) { @@ -495,7 +499,7 @@ private void afterTerminate(boolean notifyContainer, boolean executed) { assert (state() == TERMINATED) && (carrierThread == null); if (executed) { - notifyJvmtiUnmount(/*hide*/false); + notifyUnmount(/*hide*/false); } // notify anyone waiting for this virtual thread to terminate @@ -1068,6 +1072,30 @@ private void setCarrierThread(Thread carrier) { this.carrierThread = carrier; } + private static final long[] EMPTY_JFR_CONTEXT = new long[0]; + private long[] jfrContext = new long[8]; + + @JvmtiMountTransition + private void notifyMount(boolean hide) { + notifyJvmtiMount(hide); + if (!hide) { + JFRContextAccess access = SharedSecrets.getJFRContextAccess(); + if (access != null) { + access.setAllContext(jfrContext); + } + } + } + + private void notifyUnmount(boolean hide) { + notifyJvmtiUnmount(hide); + if (!hide) { + JFRContextAccess access = SharedSecrets.getJFRContextAccess(); + if (access != null) { + access.getAllContext(jfrContext, 8); + } + } + } + // -- JVM TI support -- @IntrinsicCandidate diff --git a/src/java.base/share/classes/jdk/internal/access/JFRContextAccess.java b/src/java.base/share/classes/jdk/internal/access/JFRContextAccess.java new file mode 100644 index 0000000000000..b20c8a82e6789 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/access/JFRContextAccess.java @@ -0,0 +1,6 @@ +package jdk.internal.access; + +public interface JFRContextAccess { + int getAllContext(long[] data, int size); + void setAllContext(long[] data); +} \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java index 919d758a6e35c..0b2c1aba89094 100644 --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -91,6 +91,8 @@ public class SharedSecrets { private static JavaxCryptoSpecAccess javaxCryptoSpecAccess; private static JavaTemplateAccess javaTemplateAccess; + private static JFRContextAccess jfrContextAccess; + public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) { javaUtilCollectionAccess = juca; } @@ -532,6 +534,14 @@ public static JavaTemplateAccess getJavaTemplateAccess() { return access; } + public static void setJFRContextAccess(JFRContextAccess jca) { + jfrContextAccess = jca; + } + + public static JFRContextAccess getJFRContextAccess() { + return jfrContextAccess; + } + private static void ensureClassInitialized(Class c) { try { MethodHandles.lookup().ensureInitialized(c); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/ContextAccess.java b/src/jdk.jfr/share/classes/jdk/jfr/ContextAccess.java new file mode 100644 index 0000000000000..40b6187c401aa --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/ContextAccess.java @@ -0,0 +1,12 @@ +package jdk.jfr; + +import jdk.jfr.internal.context.ContextRepository; + +public interface ContextAccess { + static ContextAccess forType(Class type) { + return ContextRepository.getOrRegister(type); + } + + void set(T target); + void unset(); +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/ContextAware.java b/src/jdk.jfr/share/classes/jdk/jfr/ContextAware.java new file mode 100644 index 0000000000000..f155d67c32df8 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/ContextAware.java @@ -0,0 +1,13 @@ +package jdk.jfr; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@MetadataDefinition +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContextAware { +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/ContextType.java b/src/jdk.jfr/share/classes/jdk/jfr/ContextType.java new file mode 100644 index 0000000000000..c14137be158a1 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/ContextType.java @@ -0,0 +1,15 @@ +package jdk.jfr; + +import java.io.Closeable; +import java.util.stream.Stream; + +import jdk.jfr.internal.context.BaseContextType; +import jdk.jfr.internal.context.ContextRepository; + +public abstract class ContextType extends BaseContextType { + public interface Registration { + Stream> 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 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 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 eventClass, int id) { + try { + Constructor 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; +}