From 6c1eed145527d2dcee4961d74a8da2ecd8168fb4 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 18 Oct 2023 11:48:56 +0000 Subject: [PATCH] Trivial prototype of JFR context --- .../build/tools/jfr/GenerateJfrFiles.java | 7 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 15 ++ src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 4 + .../jfr/jni/jfrJniMethodRegistration.cpp | 5 +- src/hotspot/share/jfr/metadata/metadata.xml | 12 +- src/hotspot/share/jfr/metadata/metadata.xsd | 1 + .../share/jfr/recorder/service/jfrEvent.hpp | 8 + .../jfr/recorder/service/jfrOptionSet.hpp | 7 + src/hotspot/share/jfr/support/jfrContext.cpp | 67 +++++++ src/hotspot/share/jfr/support/jfrContext.hpp | 20 +++ .../share/jfr/support/jfrThreadLocal.hpp | 24 +++ .../classes/java/lang/VirtualThread.java | 41 ++++- .../jdk/internal/access/JFRContextAccess.java | 6 + .../jdk/internal/access/SharedSecrets.java | 10 ++ .../share/classes/jdk/jfr/ContextAware.java | 13 ++ .../share/classes/jdk/jfr/ContextType.java | 58 ++++++ .../share/classes/jdk/jfr/FlightRecorder.java | 32 +++- .../jfr/internal/EventInstrumentation.java | 52 +++++- .../jdk/jfr/internal/EventWriterMethod.java | 4 +- .../share/classes/jdk/jfr/internal/JVM.java | 45 ++++- .../jdk/jfr/internal/MetadataLoader.java | 8 +- .../classes/jdk/jfr/internal/Options.java | 8 +- .../jdk/jfr/internal/PlatformRecorder.java | 8 +- .../classes/jdk/jfr/internal/StringPool.java | 10 +- .../classes/jdk/jfr/internal/TypeLibrary.java | 34 +++- .../jfr/internal/context/BaseContextType.java | 27 +++ .../internal/context/ContextDescriptor.java | 6 + .../internal/context/ContextEventWriter.java | 16 ++ .../internal/context/ContextRepository.java | 116 ++++++++++++ .../jfr/internal/context/ContextWriter.java | 62 +++++++ .../jdk/jfr/internal/event/EventWriter.java | 7 +- src/jdk.jfr/share/classes/module-info.java | 3 + .../jdk/jfr/api/context/TestJfrContext.java | 147 ++++++++++++++++ .../api/context/TestJfrContextVirtual.java | 165 ++++++++++++++++++ .../jdk/test/lib/jfr/ContextAwareEvent.java | 37 ++++ 35 files changed, 1044 insertions(+), 41 deletions(-) create mode 100644 src/hotspot/share/jfr/support/jfrContext.cpp create mode 100644 src/hotspot/share/jfr/support/jfrContext.hpp create mode 100644 src/java.base/share/classes/jdk/internal/access/JFRContextAccess.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/ContextAware.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/ContextType.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/context/BaseContextType.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextDescriptor.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextEventWriter.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextRepository.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextWriter.java create mode 100644 test/jdk/jdk/jfr/api/context/TestJfrContext.java create mode 100644 test/jdk/jdk/jfr/api/context/TestJfrContextVirtual.java create mode 100644 test/lib/jdk/test/lib/jfr/ContextAwareEvent.java 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..c3bab297dedd7 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 @@ - + @@ -727,7 +727,7 @@ - + @@ -915,7 +915,7 @@ + period="everyChunk" withContext="true"> 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 03f267abae96e..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,9 @@ */ 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; @@ -216,7 +219,7 @@ private void runContinuation() { } // notify JVMTI before mount - notifyJvmtiMount(/*hide*/true); + notifyMount(/*hide*/true); try { cont.run(); @@ -305,7 +308,7 @@ private void run(Runnable task) { event.commit(); } - Object bindings = scopedValueBindings(); + Object bindings = Thread.scopedValueBindings(); try { runWith(bindings, task); } catch (Throwable exc) { @@ -426,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); } } @@ -450,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)) { @@ -466,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) { @@ -496,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 @@ -1069,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/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..55f77d802fc77 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/ContextType.java @@ -0,0 +1,58 @@ +package jdk.jfr; + +import java.io.Closeable; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import jdk.jfr.internal.context.BaseContextType; +import jdk.jfr.internal.context.ContextRepository; + +public abstract class ContextType extends BaseContextType { + public static interface Registration { + Stream> types(); + } + + public static final class Captured> implements Closeable, AutoCloseable { + private final T parent, current; + + Captured(T parent, T current) { + assert parent != null; + assert current != null; + this.parent = parent; + this.current = current; + } + + @Override + public void close() { + parent.set(); + } + + public T get() { + return current; + } + + public Captured capture() { + return current.capture(); + } + } + + public static abstract class Capturable> extends ContextType { + public Capturable() {} + + @SuppressWarnings("unchecked") + public final Captured capture() { + return new Captured((T) this, snapshot()); + } + + abstract protected T snapshot(); + } + + 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 4999df75653f2..582dc505a02e3 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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.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/internal/EventInstrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java index 23fc956347746..4402534cccf8f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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 215878111eb90..a7e9a12eb4822 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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; @@ -50,7 +55,7 @@ private static class ChunkRotationMonitor {} */ public static final Object CHUNK_ROTATION_MONITOR = new ChunkRotationMonitor(); - private volatile boolean nativeOK; + private volatile static boolean nativeOK; private static native void registerNatives(); @@ -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 19eaa84483ce3..75e15eb1f219b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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 379e10c5c2f3e..d5b9522149fda 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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 71a5e28b2c835..ae261e2a4bfb5 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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; @@ -58,6 +58,8 @@ import jdk.jfr.Timespan; 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; @@ -265,7 +267,8 @@ public static synchronized Type createType(Class clazz, List clazz, Type type, List createStandardAnnotations(String name, String description) { @@ -359,6 +371,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..00f01afa62761 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/BaseContextType.java @@ -0,0 +1,27 @@ +package jdk.jfr.internal.context; + +import jdk.jfr.FlightRecorder; +import jdk.jfr.internal.JVM; +import jdk.jfr.internal.PlatformRecorder; + +public abstract class BaseContextType { + public final void set() { + if (shouldCaptureState()) { + ContextRepository.getOrRegister(this.getClass()).write(this); + } + } + + public final void unset() { + if (shouldCaptureState()) { + ContextRepository.getOrRegister(this.getClass()).clear(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..b393282d85b4c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextDescriptor.java @@ -0,0 +1,6 @@ +package jdk.jfr.internal.context; + +import java.lang.invoke.VarHandle; + +public record ContextDescriptor(String holderId, String name, String label, String description, VarHandle access) { +} 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..1f19e86c0b443 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextRepository.java @@ -0,0 +1,116 @@ +package jdk.jfr.internal.context; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.ContextType; +import jdk.jfr.Description; +import jdk.jfr.FlightRecorder; +import jdk.jfr.FlightRecorderListener; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.Recording; +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 (ContextType.class.isAssignableFrom(type)) { + return writerFor((Class) type); + } + return ContextWriter.NULL; + } + }; + + private ContextRepository() { + } + + public static ContextWriter getOrRegister(Class contextTypeClass) { + return contextWriters.get(contextTypeClass); + } + + private static List descriptorsOf(Class contextTypeClass) { + try { + List ctxDescriptors = new ArrayList<>(8); + Name typeNameAnnot = contextTypeClass.getAnnotation(Name.class); + String id = typeNameAnnot.value(); + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + for (Field f : contextTypeClass.getFields()) { + 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) { + 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(id, name, label, desc, lookup.unreflectVarHandle(f))); + } + } + return ctxDescriptors; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + private static ContextWriter writerFor(Class type) { + List 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.NULL; + } + + private static int register(List descriptors) { + if (!shouldCaptureState()) { + return -1; + } + if (FlightRecorder.isInitialized()) { + return -2; + } + if (slotPointer + descriptors.size() > CAPACITY) { + return -3; + } + final int offset = slotPointer; + int idx = 0; + for (var descriptor : descriptors) { + allDescriptors[idx + slotPointer] = descriptor; + idx++; + } + slotPointer += idx; + 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..40d38278d14fd --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/context/ContextWriter.java @@ -0,0 +1,62 @@ +package jdk.jfr.internal.context; + +import java.lang.invoke.MethodHandles.Lookup; +import java.nio.LongBuffer; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.jfr.ContextType; +import jdk.jfr.FlightRecorder; +import jdk.jfr.internal.JVM; +import jdk.jfr.internal.StringPool; + +public final class ContextWriter { + static final ContextWriter NULL = new ContextWriter(-1, null); + private final int offset; + private final List descriptors; + + ContextWriter(int offset, List descriptors) { + this.offset = offset; + this.descriptors = offset > -1 ? Collections.unmodifiableList(descriptors) : null; + } + + public boolean isActive() { + return offset != -1; + } + + void write(BaseContextType target) { + if (offset == -1 || descriptors == null) { + return; + } + LongBuffer context = JVM.getThreadContextBuffer(); + if (context == null) { + return; + } + int cntr = offset; + for (ContextDescriptor cd : descriptors) { + String value = (String) cd.access().get(target); + context.put(cntr++, value != null ? StringPool.addString(value, false) : 0); + if (cntr >= 8) { + break; + } + } + } + + void clear(BaseContextType target) { + if (offset == -1 || descriptors == null) { + return; + } + LongBuffer context = JVM.getThreadContextBuffer(); + if (context == null) { + return; + } + int cntr = 0; + for (ContextDescriptor cd : descriptors) { + context.put(offset + (cntr++), 0L); + cd.access().set(target, null); + } + } +} 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 7c3531ddd0556..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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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..ea62cf30e10da --- /dev/null +++ b/test/jdk/jdk/jfr/api/context/TestJfrContext.java @@ -0,0 +1,147 @@ +/* + * 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 String result; + } + + @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.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.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..ae632fb2e91be --- /dev/null +++ b/test/lib/jdk/test/lib/jfr/ContextAwareEvent.java @@ -0,0 +1,37 @@ +/* + * 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; + +@ContextAware +public class ContextAwareEvent extends Event { + public ContextAwareEvent(int id) { + this.id = id; + } + public int id; +}