From 6016536ab96e154f4eaff10ac19f590c812e5377 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Wed, 22 Nov 2023 20:48:42 +0000 Subject: [PATCH] 8314745: JFR: @StackFilter Reviewed-by: mgronlun --- src/hotspot/share/jfr/jni/jfrJavaSupport.cpp | 27 ++ src/hotspot/share/jfr/jni/jfrJavaSupport.hpp | 1 + src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 14 +- src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 6 +- .../jfr/jni/jfrJniMethodRegistration.cpp | 6 +- .../recorder/stacktrace/jfrStackFilter.cpp | 61 ++++ .../recorder/stacktrace/jfrStackFilter.hpp | 45 +++ .../stacktrace/jfrStackFilterRegistry.cpp | 95 ++++++ .../stacktrace/jfrStackFilterRegistry.hpp | 43 +++ .../jfr/recorder/stacktrace/jfrStackTrace.cpp | 15 +- .../jfr/recorder/stacktrace/jfrStackTrace.hpp | 4 +- .../stacktrace/jfrStackTraceRepository.cpp | 11 +- .../stacktrace/jfrStackTraceRepository.hpp | 4 +- .../jdk/jfr/events/ProcessStartEvent.java | 1 + .../SecurityPropertyModificationEvent.java | 1 + .../events/SecurityProviderServiceEvent.java | 1 + .../classes/jdk/jfr/events/StackFilter.java | 65 ++++ .../jdk/jfr/events/TLSHandshakeEvent.java | 1 + .../jdk/jfr/events/ThreadSleepEvent.java | 5 +- .../jdk/jfr/internal/EventControl.java | 78 +++++ .../share/classes/jdk/jfr/internal/JVM.java | 35 +- .../jdk/jfr/internal/MetadataRepository.java | 7 + .../jdk/jfr/internal/PlatformEventType.java | 13 + .../classes/jdk/jfr/internal/TypeLibrary.java | 11 +- .../jdk/jfr/internal/event/EventWriter.java | 2 +- .../metadata/annotations/TestStackFilter.java | 319 ++++++++++++++++++ .../metadata/annotations/UnloadableClass.java | 64 ++++ .../jdk/jfr/event/os/TestProcessStart.java | 3 +- .../event/runtime/TestThreadSleepEvent.java | 18 +- ...TestSecurityPropertyModificationEvent.java | 3 +- .../TestSecurityProviderServiceEvent.java | 27 +- .../event/security/TestTLSHandshakeEvent.java | 6 +- test/jdk/jdk/jfr/jvm/TestGetStackTraceId.java | 4 +- test/lib/jdk/test/lib/jfr/Events.java | 38 ++- 34 files changed, 982 insertions(+), 52 deletions(-) create mode 100644 src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.cpp create mode 100644 src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.hpp create mode 100644 src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.cpp create mode 100644 src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/events/StackFilter.java create mode 100644 test/jdk/jdk/jfr/api/metadata/annotations/TestStackFilter.java create mode 100644 test/jdk/jdk/jfr/api/metadata/annotations/UnloadableClass.java diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp index 0f147f137dc..866f8c1df13 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp @@ -525,6 +525,33 @@ const char* JfrJavaSupport::c_str(jstring string, Thread* thread, bool c_heap /* return string != nullptr ? c_str(resolve_non_null(string), thread, c_heap) : nullptr; } +static Symbol** allocate_symbol_array(bool c_heap, int length, Thread* thread) { + return c_heap ? + NEW_C_HEAP_ARRAY(Symbol*, length, mtTracing) : + NEW_RESOURCE_ARRAY_IN_THREAD(thread, Symbol*, length); +} + +Symbol** JfrJavaSupport::symbol_array(jobjectArray string_array, JavaThread* thread, intptr_t* result_array_size, bool c_heap /* false */) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread)); + assert(string_array != nullptr, "invariant"); + assert(result_array_size != nullptr, "invariant"); + objArrayOop arrayOop = objArrayOop(resolve_non_null(string_array)); + const int length = arrayOop->length(); + *result_array_size = length; + Symbol** result_array = allocate_symbol_array(c_heap, length, thread); + assert(result_array != nullptr, "invariant"); + for (int i = 0; i < length; i++) { + oop object = arrayOop->obj_at(i); + Symbol* symbol = nullptr; + if (object != nullptr) { + const char* text = c_str(arrayOop->obj_at(i), thread, c_heap); + symbol = SymbolTable::new_symbol(text); + } + result_array[i] = symbol; + } + return result_array; +} + /* * Exceptions and errors */ diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp index 05559573e4a..8b4ecf18dc1 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp @@ -86,6 +86,7 @@ class JfrJavaSupport : public AllStatic { static Klass* klass(const jobject handle); static const char* c_str(jstring string, Thread* thread, bool c_heap = false); static const char* c_str(oop string, Thread* thread, bool c_heap = false); + static Symbol** symbol_array(jobjectArray string_array, JavaThread* thread, intptr_t* result_size, bool c_heap = false); // exceptions static void throw_illegal_state_exception(const char* message, TRAPS); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 4951e74dfd4..82f26d7bdd0 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -36,6 +36,8 @@ #include "jfr/recorder/repository/jfrEmergencyDump.hpp" #include "jfr/recorder/service/jfrEventThrottler.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/stacktrace/jfrStackFilter.hpp" +#include "jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" #include "jfr/recorder/stringpool/jfrStringPool.hpp" #include "jfr/jni/jfrJavaSupport.hpp" @@ -239,8 +241,8 @@ JVM_ENTRY_NO_ENV(jlong, jfr_class_id(JNIEnv* env, jclass jvm, jclass jc)) return JfrTraceId::load(jc); JVM_END -JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip)) - return JfrStackTraceRepository::record(thread, skip); +JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id)) + return JfrStackTraceRepository::record(thread, skip, stack_filter_id); JVM_END JVM_ENTRY_NO_ENV(void, jfr_log(JNIEnv* env, jclass jvm, jint tag_set, jint level, jstring message)) @@ -397,3 +399,11 @@ JVM_END JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes)) EventDataLoss::commit(bytes, min_jlong); JVM_END + +JVM_ENTRY_NO_ENV(jlong, jfr_register_stack_filter(JNIEnv* env, jclass jvm, jobjectArray classes, jobjectArray methods)) + return JfrStackFilterRegistry::add(classes, methods, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_unregister_stack_filter(JNIEnv* env, jclass jvm, jlong id)) + JfrStackFilterRegistry::remove(id); +JVM_END diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 28bafc4b73f..b37841aeac2 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -57,7 +57,7 @@ jlong JNICALL jfr_class_id(JNIEnv* env, jclass jvm, jclass jc); jstring JNICALL jfr_get_pid(JNIEnv* env, jclass jvm); -jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip); +jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id); jlong JNICALL jfr_elapsed_frequency(JNIEnv* env, jclass jvm); @@ -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); +jlong JNICALL jfr_register_stack_filter(JNIEnv* env, jobject classes, jobject methods); + +jlong JNICALL jfr_unregister_stack_filter(JNIEnv* env, jlong start_filter_id); + #ifdef __cplusplus } #endif diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 7c571abbd69..f8475881ff8 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -46,7 +46,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"getAllEventClasses", (char*)"()Ljava/util/List;", (void*)jfr_get_all_event_classes, (char*)"getClassId", (char*)"(Ljava/lang/Class;)J", (void*)jfr_class_id, (char*)"getPid", (char*)"()Ljava/lang/String;", (void*)jfr_get_pid, - (char*)"getStackTraceId", (char*)"(I)J", (void*)jfr_stacktrace_id, + (char*)"getStackTraceId", (char*)"(IJ)J", (void*)jfr_stacktrace_id, (char*)"getThreadId", (char*)"(Ljava/lang/Thread;)J", (void*)jfr_id_for_thread, (char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency, (char*)"subscribeLogLevel", (char*)"(Ljdk/jfr/internal/LogTag;I)V", (void*)jfr_subscribe_log_level, @@ -97,7 +97,9 @@ 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*)"registerStackFilter", (char*)"([Ljava/lang/String;[Ljava/lang/String;)J", (void*)jfr_register_stack_filter, + (char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter }; const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod); diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.cpp new file mode 100644 index 00000000000..bf10c531c63 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "precompiled.hpp" +#include "jfr/recorder/stacktrace/jfrStackFilter.hpp" +#include "oops/method.hpp" +#include "oops/symbol.hpp" + +JfrStackFilter::JfrStackFilter(Symbol** class_names, Symbol** method_names, size_t count) + : _count(count), + _class_names(class_names), + _method_names(method_names) { + assert(_class_names != nullptr, "invariant"); + assert(_method_names != nullptr, "invariant"); +} + +bool JfrStackFilter::match(const Method* method) const { + assert(method != nullptr, "Invariant"); + const Symbol* const method_name = method->name(); + const Symbol* const klass_name = method->klass_name(); + for (size_t i = 0; i < _count; i++) { + const Symbol* m = _method_names[i]; + if (m == nullptr || m == method_name) { + const Symbol* c = _class_names[i]; + if (c == nullptr || c == klass_name) { + return true; + } + } + } + return false; +} + +JfrStackFilter::~JfrStackFilter() { + for (size_t i = 0; i < _count; i++) { + Symbol::maybe_decrement_refcount(_method_names[i]); + Symbol::maybe_decrement_refcount(_class_names[i]); + } + FREE_C_HEAP_ARRAY(Symbol*, _method_names); + FREE_C_HEAP_ARRAY(Symbol*, _class_names); +} diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.hpp new file mode 100644 index 00000000000..19c1821149c --- /dev/null +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKFILTER_HPP +#define SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKFILTER_HPP + +#include "jfr/utilities/jfrAllocation.hpp" + +class Mathod; +class Symbol; + +class JfrStackFilter : public JfrCHeapObj { + private: + size_t _count; + Symbol** _class_names; + Symbol** _method_names; + + public: + JfrStackFilter(Symbol** class_names, Symbol** method_names, size_t count); + ~JfrStackFilter(); + bool match(const Method* method) const; +}; + +#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKFILTER_HPP diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.cpp new file mode 100644 index 00000000000..481dcbdc840 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + * + */ + + +#include "precompiled.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/stacktrace/jfrStackFilter.hpp" +#include "jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp" +#include "logging/log.hpp" + +static const intptr_t STACK_FILTER_ELEMENTS_SIZE = 4096; +static const intptr_t STACK_FILTER_ERROR_CODE = -1; +static const JfrStackFilter* _elements[STACK_FILTER_ELEMENTS_SIZE]; +static intptr_t _free_list[STACK_FILTER_ELEMENTS_SIZE]; +static intptr_t _index = 0; +static intptr_t _free_list_index = 0; + +int64_t JfrStackFilterRegistry::add(jobjectArray classes, jobjectArray methods, JavaThread* jt) { + intptr_t c_size = 0; + Symbol** class_names = JfrJavaSupport::symbol_array(classes, jt, &c_size, true); + assert(class_names != nullptr, "invariant"); + intptr_t m_size = 0; + Symbol** method_names = JfrJavaSupport::symbol_array(methods, jt, &m_size, true); + assert(method_names != nullptr, "invariant"); + if (c_size != m_size) { + FREE_C_HEAP_ARRAY(Symbol*, class_names); + FREE_C_HEAP_ARRAY(Symbol*, method_names); + JfrJavaSupport::throw_internal_error("Method array size doesn't match class array size", jt); + return STACK_FILTER_ERROR_CODE; + } + assert(c_size >= 0, "invariant"); + const JfrStackFilter* filter = new JfrStackFilter(class_names, method_names, static_cast(c_size)); + return JfrStackFilterRegistry::add(filter); +} + +#ifdef ASSERT +static bool range_check(int64_t idx) { + return idx < STACK_FILTER_ELEMENTS_SIZE && idx >= 0; +} +#endif + +int64_t JfrStackFilterRegistry::add(const JfrStackFilter* filter) { + if (_free_list_index > 0) { + assert(range_check(_free_list_index), "invariant"); + const intptr_t free_index = _free_list[_free_list_index - 1]; + _elements[free_index] = filter; + _free_list_index--; + return free_index; + } + if (_index >= STACK_FILTER_ELEMENTS_SIZE - 1) { + log_warning(jfr)("Maximum number of @StackFrame in use has been reached."); + return STACK_FILTER_ERROR_CODE; + } + assert(range_check(_index), "invariant"); + _elements[_index] = filter; + return _index++; +} + +const JfrStackFilter* JfrStackFilterRegistry::lookup(int64_t id) { + if (id < 0) { + return nullptr; + } + assert(range_check(id), "invariant"); + return _elements[id]; +} + +void JfrStackFilterRegistry::remove(int64_t index) { + assert(range_check(index), "invariant"); + delete _elements[index]; + if (_free_list_index < STACK_FILTER_ELEMENTS_SIZE - 1) { + assert(range_check(_free_list_index), "invariant"); + _free_list[_free_list_index++] = index; + } +} diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp new file mode 100644 index 00000000000..e35fb90938f --- /dev/null +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef SHARE_JFR_RECORDER_STACKTRACE_JFRSTCKFILTERREGISTRY_HPP +#define SHARE_JFR_RECORDER_STACKTRACE_JFRSTCKFILTERREGISTRY_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" + +class JavaThread; +class JfrStackFilter; + +class JfrStackFilterRegistry : AllStatic { + private: + static int64_t add(const JfrStackFilter* frame); + public: + static int64_t add(jobjectArray classes, jobjectArray methods, JavaThread* jt); + static void remove(int64_t id); + static const JfrStackFilter* lookup(int64_t id); +}; + +#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTCKFILTERREGISTRY_HPP diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp index c70f626a2fe..55e1e2ac374 100644 --- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp @@ -30,6 +30,8 @@ #include "jfr/recorder/storage/jfrBuffer.hpp" #include "jfr/support/jfrMethodLookup.hpp" #include "jfr/support/jfrThreadLocal.hpp" +#include "jfrStackFilter.hpp" +#include "jfrStackFilterRegistry.hpp" #include "memory/allocation.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "runtime/continuation.hpp" @@ -284,7 +286,7 @@ bool JfrStackTrace::record_async(JavaThread* jt, const frame& frame) { return count > 0; } -bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) { +bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip, int64_t stack_filter_id) { assert(jt != nullptr, "invariant"); assert(jt == Thread::current(), "invariant"); assert(jt->thread_state() != _thread_in_native, "invariant"); @@ -302,6 +304,7 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) { } vfs.next_vframe(); } + const JfrStackFilter* stack_filter = JfrStackFilterRegistry::lookup(stack_filter_id); _hash = 1; while (!vfs.at_end()) { if (count >= _max_frames) { @@ -309,6 +312,12 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) { break; } const Method* method = vfs.method(); + if (stack_filter != nullptr) { + if (stack_filter->match(method)) { + vfs.next_vframe(); + continue; + } + } const traceid mid = JfrTraceId::load(method); u1 type = vfs.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT; int bci = 0; @@ -335,13 +344,13 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) { return count > 0; } -bool JfrStackTrace::record(JavaThread* current_thread, int skip) { +bool JfrStackTrace::record(JavaThread* current_thread, int skip, int64_t stack_filter_id) { assert(current_thread != nullptr, "invariant"); assert(current_thread == Thread::current(), "invariant"); if (!current_thread->has_last_Java_frame()) { return false; } - return record(current_thread, current_thread->last_frame(), skip); + return record(current_thread, current_thread->last_frame(), skip, stack_filter_id); } void JfrStackFrame::resolve_lineno() const { diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp index acd9d41fbf7..8b2ca022443 100644 --- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp @@ -93,8 +93,8 @@ class JfrStackTrace : public JfrCHeapObj { void set_reached_root(bool reached_root) { _reached_root = reached_root; } void resolve_linenos() const; - bool record(JavaThread* current_thread, int skip); - bool record(JavaThread* current_thread, const frame& frame, int skip); + bool record(JavaThread* current_thread, int skip, int64_t stack_frame_id); + bool record(JavaThread* current_thread, const frame& frame, int skip, int64_t stack_frame_id); bool record_async(JavaThread* other_thread, const frame& frame); bool have_lineno() const { return _lineno; } diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp index 26056141a72..1c207f5bb2b 100644 --- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp @@ -146,7 +146,7 @@ size_t JfrStackTraceRepository::clear(JfrStackTraceRepository& repo) { return processed; } -traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */) { +traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */, int64_t stack_filter_id /* -1 */) { assert(current_thread == Thread::current(), "invariant"); JfrThreadLocal* const tl = current_thread->jfr_thread_local(); assert(tl != nullptr, "invariant"); @@ -163,13 +163,14 @@ traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */ } assert(frames != nullptr, "invariant"); assert(tl->stackframes() == frames, "invariant"); - return instance().record(JavaThread::cast(current_thread), skip, frames, tl->stackdepth()); + return instance().record(JavaThread::cast(current_thread), skip, stack_filter_id, frames, tl->stackdepth()); } -traceid JfrStackTraceRepository::record(JavaThread* current_thread, int skip, JfrStackFrame *frames, u4 max_frames) { +traceid JfrStackTraceRepository::record(JavaThread* current_thread, int skip, int64_t stack_filter_id, JfrStackFrame *frames, u4 max_frames) { JfrStackTrace stacktrace(frames, max_frames); - return stacktrace.record(current_thread, skip) ? add(instance(), stacktrace) : 0; + return stacktrace.record(current_thread, skip, stack_filter_id) ? add(instance(), stacktrace) : 0; } + traceid JfrStackTraceRepository::add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace) { traceid tid = repo.add_trace(stacktrace); if (tid == 0) { @@ -191,7 +192,7 @@ void JfrStackTraceRepository::record_for_leak_profiler(JavaThread* current_threa assert(tl != nullptr, "invariant"); assert(!tl->has_cached_stack_trace(), "invariant"); JfrStackTrace stacktrace(tl->stackframes(), tl->stackdepth()); - stacktrace.record(current_thread, skip); + stacktrace.record(current_thread, skip, -1); const traceid hash = stacktrace.hash(); if (hash != 0) { tl->set_cached_stack_trace_id(add(leak_profiler_instance(), stacktrace), hash); diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp index d59dd5f57e0..abcf88450ff 100644 --- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp @@ -67,10 +67,10 @@ class JfrStackTraceRepository : public JfrCHeapObj { traceid add_trace(const JfrStackTrace& stacktrace); static traceid add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace); static traceid add(const JfrStackTrace& stacktrace); - traceid record(JavaThread* current_thread, int skip, JfrStackFrame* frames, u4 max_frames); + traceid record(JavaThread* current_thread, int skip, int64_t stack_filter_id, JfrStackFrame* frames, u4 max_frames); public: - static traceid record(Thread* current_thread, int skip = 0); + static traceid record(Thread* current_thread, int skip = 0, int64_t stack_filter_id = -1); }; #endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKTRACEREPOSITORY_HPP diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/ProcessStartEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/ProcessStartEvent.java index b936099aa74..5ad2dc82fc1 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/ProcessStartEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ProcessStartEvent.java @@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields; @Description("Operating system process started") @MirrorEvent(className = "jdk.internal.event.ProcessStartEvent") @RemoveFields("duration") +@StackFilter({"java.lang.ProcessBuilder", "java.lang.Runtime::exec"}) public final class ProcessStartEvent extends AbstractJDKEvent { @Label("Process Id") public long pid; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityPropertyModificationEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityPropertyModificationEvent.java index df2e9c241de..7f4e5045c9d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityPropertyModificationEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityPropertyModificationEvent.java @@ -35,6 +35,7 @@ import jdk.jfr.internal.RemoveFields; @Description("Modification of Security property") @MirrorEvent(className = "jdk.internal.event.SecurityPropertyModificationEvent") @RemoveFields("duration") +@StackFilter({"java.security.Security::setProperty"}) public final class SecurityPropertyModificationEvent extends AbstractJDKEvent { @Label("Key") public String key; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityProviderServiceEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityProviderServiceEvent.java index 193bfa54ae2..8bbbf09d87d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityProviderServiceEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SecurityProviderServiceEvent.java @@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields; @Description("Details of Provider.getInstance(String type, String algorithm) calls") @MirrorEvent(className = "jdk.internal.event.SecurityProviderServiceEvent") @RemoveFields("duration") +@StackFilter({"java.security.Provider::getService"}) public final class SecurityProviderServiceEvent extends AbstractJDKEvent { @Label("Type of Service") public String type; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/StackFilter.java b/src/jdk.jfr/share/classes/jdk/jfr/events/StackFilter.java new file mode 100644 index 00000000000..f0c6a45bf83 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/StackFilter.java @@ -0,0 +1,65 @@ +package jdk.jfr.events; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import jdk.jfr.MetadataDefinition; + +/** +* Event annotation, specifies method names or classes to exclude in a stack +* trace. +*

+* The following example illustrates how the {@code StackFilter} annotation can +* be used to remove the {@code Logger::log} method in a stack trace: +* +* {@snippet : +* package com.example; +* +* @Name("example.LogMessage") +* @Label("Log Message") +* @StackFilter("com.example.Logger::log") +* class LogMessage extends Event { +* @Label("Message") +* String message; +* } +* +* public class Logger { +* +* public static void log(String message) { +* System.out.print(Instant.now() + " : " + message); +* LogMessage event = new LogMessage(); +* event.message = message; +* event.commit(); +* } +* } +* } +* +* @since 22 +*/ +@Target({ ElementType.TYPE }) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@MetadataDefinition +public @interface StackFilter { + /** + * The methods or classes that should not be part of an event stack trace. + *

+ * A filter is formed by using the fully qualified class name concatenated with + * the method name using {@code "::"} as separator, for example + * {@code "java.lang.String::toString"} + *

+ * If only the name of a class is specified, for example {@code + * "java.lang.String"}, all methods in that class are filtered out. + *

+ * Methods can't be qualified using method parameters or return types. + *

+ * Instance methods belonging to an interface can't be filtered out. + *

+ * Wilcards are not permitted. + * + * @return the method names, not {@code null} + */ + String[] value(); +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/TLSHandshakeEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/TLSHandshakeEvent.java index ce5e8c85768..df2f4cfc2ed 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/TLSHandshakeEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/TLSHandshakeEvent.java @@ -39,6 +39,7 @@ import jdk.jfr.internal.RemoveFields; @Description("Parameters used in TLS Handshake") @MirrorEvent(className = "jdk.internal.event.TLSHandshakeEvent") @RemoveFields("duration") +@StackFilter("sun.security.ssl.Finished::recordEvent") public final class TLSHandshakeEvent extends AbstractJDKEvent { @Label("Peer Host") public String peerHost; 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 ab606d8d6de..216ad370aca 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/ThreadSleepEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ThreadSleepEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -35,6 +35,9 @@ import jdk.jfr.internal.MirrorEvent; @Label("Java Thread Sleep") @Name("jdk.ThreadSleep") @MirrorEvent(className = "jdk.internal.event.ThreadSleepEvent") +@StackFilter({"java.lang.Thread::afterSleep", + "java.lang.Thread::sleepNanos", + "java.lang.Thread::sleep"}) public final class ThreadSleepEvent extends AbstractJDKEvent { @Label("Sleep Time") @Timespan(Timespan.NANOSECONDS) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index d2327a189b5..a83d244f1d7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import jdk.internal.module.Checks; import jdk.internal.module.Modules; import jdk.jfr.AnnotationElement; import jdk.jfr.Enabled; @@ -44,6 +45,8 @@ import jdk.jfr.SettingDefinition; import jdk.jfr.StackTrace; import jdk.jfr.Threshold; import jdk.jfr.events.ActiveSettingEvent; +import jdk.jfr.events.StackFilter; +import jdk.jfr.internal.JVM; import jdk.jfr.internal.settings.CutoffSetting; import jdk.jfr.internal.settings.EnabledSetting; import jdk.jfr.internal.settings.PeriodSetting; @@ -65,6 +68,7 @@ public final class EventControl { private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class); private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class); private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.class); + private static final long STACK_FILTER_ID = Type.getTypeId(StackFilter.class); private final ArrayList settingControls = new ArrayList<>(); private final ArrayList namedControls = new ArrayList<>(5); @@ -89,6 +93,7 @@ public final class EventControl { } addControl(Enabled.NAME, defineEnabled(eventType)); + addStackFilters(eventType); List aes = new ArrayList<>(eventType.getAnnotationElements()); remove(eventType, aes, Threshold.class); remove(eventType, aes, Period.class); @@ -96,11 +101,84 @@ public final class EventControl { remove(eventType, aes, StackTrace.class); remove(eventType, aes, Cutoff.class); remove(eventType, aes, Throttle.class); + remove(eventType, aes, StackFilter.class); eventType.setAnnotations(aes); this.type = eventType; this.idName = String.valueOf(eventType.getId()); } + private void addStackFilters(PlatformEventType eventType) { + String[] filter = getStackFilter(eventType); + if (filter != null) { + int size = filter.length; + List types = new ArrayList<>(size); + List methods = new ArrayList<>(size); + for (String frame : filter) { + int index = frame.indexOf("::"); + String clazz = null; + String method = null; + boolean valid = false; + if (index != -1) { + clazz = frame.substring(0, index); + method = frame.substring(index + 2); + if (clazz.isEmpty()) { + clazz = null; + valid = isValidMethod(method); + } else { + valid = isValidType(clazz) && isValidMethod(method); + } + } else { + clazz = frame; + valid = isValidType(frame); + } + if (valid) { + if (clazz == null) { + types.add(null); + } else { + types.add(clazz.replace(".", "/")); + } + // If unqualified class name equals method name, it's a constructor + String className = clazz.substring(clazz.lastIndexOf(".") + 1); + if (className.equals(method)) { + method = ""; + } + methods.add(method); + } else { + Logger.log(LogTag.JFR, LogLevel.WARN, "@StackFrameFilter element ignored, not a valid Java identifier."); + } + } + if (!types.isEmpty()) { + String[] typeArray = types.toArray(new String[0]); + String[] methodArray = methods.toArray(new String[0]); + long id = MetadataRepository.getInstance().registerStackFilter(typeArray, methodArray); + eventType.setStackFilterId(id); + } + } + } + + private String[] getStackFilter(PlatformEventType eventType) { + for (var a : eventType.getAnnotationElements()) { + if (a.getTypeId() == STACK_FILTER_ID) { + return (String[])a.getValue("value"); + } + } + return null; + } + + private boolean isValidType(String className) { + if (className.length() < 1 || className.length() > 65535) { + return false; + } + return Checks.isClassName(className); + } + + private boolean isValidMethod(String method) { + if (method.length() < 1 || method.length() > 65535) { + return false; + } + return Checks.isJavaIdentifier(method); + } + private boolean hasControl(String name) { for (NamedControl nc : namedControls) { if (name.equals(nc.name)) { 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 986b6f2450b..3f37924c9a7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -149,10 +149,15 @@ public final class JVM { * * Requires that JFR has been started with {@link #createNativeJFR()} * - * @param skipCount number of frames to skip + * @param skipCount number of frames to skip, or 0 if no frames should be + * skipped + * + * @param ID ID of the filter that should be used, or -1 if no filter should + * be used + * * @return a unique stack trace identifier */ - public static native long getStackTraceId(int skipCount); + public static native long getStackTraceId(int skipCount, long stackFilerId); /** * Return identifier for thread @@ -628,4 +633,30 @@ public final class JVM { * @param bytes number of bytes that were lost */ public static native void emitDataLoss(long bytes); + + /** + * Registers stack filters that should be used with getStackTrace(int, long) + *

+ * Method name at an array index is for class at the same array index. + *

+ * This method should be called holding the MetadataRepository lock and before + * bytecode for the associated event class has been added. + * + * @param classes, name of classes, for example {"java/lang/String"}, not + * {@code null} + * @param methods, name of method, for example {"toString"}, not {@code null} + * + * @return an ID that can be used to unregister the start frames, or -1 if it could not be registered + */ + public static native long registerStackFilter(String[] classes, String[] methods); + + /** + * Unregisters a set of stack filters. + *

+ * This method should be called holding the MetadataRepository lock and after + * the associated event class has been unloaded. + * + * @param stackFilterId the stack filter ID to unregister + */ + public static native void unregisterStackFilter(long stackFilterId); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java index 417238c06f8..c83e9de7893 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -321,6 +321,9 @@ public final class MetadataRepository { if (!knownIds.contains(pe.getId())) { if (!pe.isJVM()) { pe.setRegistered(false); + if (pe.hasStackFilters()) { + JVM.unregisterStackFilter(pe.getStackFilterId()); + } } } } @@ -355,4 +358,8 @@ public final class MetadataRepository { public synchronized List getVisibleTypes() { return TypeLibrary.getVisibleTypes(); } + + public synchronized long registerStackFilter(String[] typeArray, String[] methodArray) { + return JVM.registerStackFilter(typeArray, methodArray); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index e600a0c70fe..e8621c56794 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -46,6 +46,7 @@ public final class PlatformEventType extends Type { private final List settings = new ArrayList<>(5); private final boolean dynamicSettings; private final int stackTraceOffset; + private long startFilterId = -1; // default values private boolean largeSize = false; @@ -339,4 +340,16 @@ public final class PlatformEventType extends Type { public boolean isMethodSampling() { return isMethodSampling; } + + public void setStackFilterId(long id) { + startFilterId = id; + } + + public boolean hasStackFilters() { + return startFilterId >= 0; + } + + public long getStackFilterId() { + return startFilterId; + } } 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 0a1f1c05dee..f461212659d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java @@ -60,6 +60,7 @@ import jdk.jfr.Timestamp; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.util.ImplicitFields; +import jdk.internal.module.Modules; public final class TypeLibrary { private static boolean implicitFieldTypes; @@ -165,7 +166,6 @@ public final class TypeLibrary { for (ValueDescriptor v : type.getFields()) { values.add(invokeAnnotation(annotation, v.getName())); } - return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null); } return null; @@ -178,6 +178,15 @@ public final class TypeLibrary { } catch (NoSuchMethodException e1) { throw (Error) new InternalError("Could not locate method " + methodName + " in annotation " + annotation.getClass().getName()); } + // Add export from JDK proxy module + if (annotation.getClass().getClassLoader() == null) { + if (annotation.getClass().getName().contains("Proxy")) { + Module proxyModule = annotation.getClass().getModule(); + String proxyPackage = annotation.getClass().getPackageName(); + Module jfrModule = TypeLibrary.class.getModule(); + Modules.addExports(proxyModule, proxyPackage, jfrModule); + } + } SecuritySupport.setAccessible(m); try { return m.invoke(annotation, new Object[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 43fc6130495..833fb087e24 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 @@ -188,7 +188,7 @@ public final class EventWriter { public void putStackTrace() { if (eventType.getStackTraceEnabled()) { - putLong(JVM.getStackTraceId(eventType.getStackTraceOffset())); + putLong(JVM.getStackTraceId(eventType.getStackTraceOffset(), eventType.getStackFilterId())); } else { putLong(0L); } diff --git a/test/jdk/jdk/jfr/api/metadata/annotations/TestStackFilter.java b/test/jdk/jdk/jfr/api/metadata/annotations/TestStackFilter.java new file mode 100644 index 00000000000..2acd369b6cd --- /dev/null +++ b/test/jdk/jdk/jfr/api/metadata/annotations/TestStackFilter.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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.metadata.annotations; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import jdk.jfr.api.metadata.annotations.UnloadableClass; +import jdk.jfr.Event; +import jdk.jfr.AnnotationElement; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.events.StackFilter; +import jdk.jfr.Recording; +import jdk.jfr.Name; +import jdk.jfr.EventType; +import jdk.jfr.EventFactory; +import jdk.jfr.FlightRecorder; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.TestClassLoader; + +/** + * @test + * @key jfr + * @requires vm.hasJFR + * @modules jdk.jfr/jdk.jfr.events + * @library /test/lib /test/jdk + * @run main/othervm -Xlog:jfr=warning jdk.jfr.api.metadata.annotations.TestStackFilter + */ +public class TestStackFilter { + private static class Quux { + private static void one() throws Exception { + two(); + } + private static void two() throws Exception { + three(); + } + + private static void three() throws Exception { + TestStackFilter.qux(); + } + } + private final static String PACKAGE = "jdk.jfr.api.metadata.annotations.TestStackFilter"; + private final static String M1 = PACKAGE + "::foo"; + private final static String M2 = PACKAGE + "::baz"; + private final static String C1 = PACKAGE + "$Quux"; + + @StackFilter({ M1, M2 }) + @Name("MethodFilter") + public static class MethodFilterEvent extends Event { + } + + @StackFilter(C1) + @Name("ClassFilter") + public static class ClassFilterEvent extends Event { + } + + @StackFilter({}) + @Name("Empty") + public static class EmptyEvent extends Event { + } + + @StackFilter(PACKAGE + "::testUnload") + @Name("Unload") + public static class UnloadEvent extends Event { + } + + @StackFilter(PACKAGE + "::emitCommitter") + @Name("Reuse") + public static class ReuseEvent extends Event { + } + + @StackFilter(PACKAGE + "::emitCommitter") + @Name("Max") + public static class ExceedMaxEvent extends Event { + } + + public static void main(String[] args) throws Exception { + testMethodFilter(); + testClassFilter(); + testUnload(); + testReuse(); + testExceedMax(); + } + + // Use more stack filters than there is capacity for + private static void testExceedMax() throws Exception { + try (Recording r = new Recording()) { + r.start(); + // Maximum number of simultaneous event classes that can + // use a filter is 4096. Additional filters will be ignored. + var classes = new ArrayList<>(); + for (int i = 0; i < 4200; i++) { + Class eventClass = UnloadableClass.load(ExceedMaxEvent.class); + emitCommitter(eventClass); + classes.add(eventClass); + } + List events = Events.fromRecording(r); + if (events.size() != 4200) { + throw new Exception("Expected 4200 'Max' events"); + } + int emitCommitterCount = 0; + int textExceedMaxCount = 0; + for (RecordedEvent event : events) { + RecordedStackTrace s = event.getStackTrace(); + if (s == null) { + System.out.println(event); + throw new Exception("Expected stack trace for 'Max' event"); + } + + RecordedFrame f = s.getFrames().get(0); + if (!f.isJavaFrame()) { + throw new Exception("Expected Java frame for 'Max' event"); + } + String methodName = f.getMethod().getName(); + switch (methodName) { + case "emitCommitter": + emitCommitterCount++; + break; + case "testExceedMax": + textExceedMaxCount++; + break; + default: + System.out.println(event); + throw new Exception("Unexpected top frame " + methodName + " for 'Max' event"); + } + } + // Can't match exact because filters from previous tests may be in use + // or because filters added by JDK events filters + if (emitCommitterCount == 0) { + throw new Exception("Expected at least some events with emitCommitter() as top frame, found " + emitCommitterCount); + } + if (textExceedMaxCount < 500) { + throw new Exception("Expected at least 500 events with testExceedMax() as top frame, found " + textExceedMaxCount); + } + } + } + + // Tests that event classes with @StackFilter that are unloaded + // reuses the memory slot used to bookkeep things in native + private static void testReuse() throws Exception { + try (Recording r = new Recording()) { + r.enable("Reuse"); + r.start(); + for (int i = 0; i < 48; i++) { + Class eventClass = UnloadableClass.load(ReuseEvent.class); + emitCommitter(eventClass); + if (i % 16 == 0) { + System.gc(); + rotate(); + } + } + r.stop(); + List events = Events.fromRecording(r); + if (events.size() != 48) { + throw new Exception("Expected 48 'Reuse' events"); + } + for (RecordedEvent event : events) { + assertTopFrame(event, "testReuse"); + } + } + + } + + // This test registers a stack filter, emits an event with the filter + // and unregisters it. While this is happening, another + // filter is being used. + private static void testUnload() throws Exception { + try (Recording r = new Recording()) { + r.start(); + Class eventClass = UnloadableClass.load(UnloadEvent.class); + emitCommitter(eventClass); + EventType type = getType("Unload"); + if (type == null) { + throw new Exception("Expected event type named 'Unload'"); + } + eventClass = null; + while (true) { + System.gc(); + rotate(); + type = getType("Unload"); + if (type == null) { + return; + } + System.out.println("Unload class not unloaded. Retrying ..."); + } + } + } + + private static void testMethodFilter() throws Exception { + try (Recording r = new Recording()) { + r.enable(MethodFilterEvent.class); + r.start(); + foo(); + bar(); + empty(); + r.stop(); + List events = Events.fromRecording(r); + if (events.isEmpty()) { + throw new Exception("Excected events"); + } + + RecordedEvent e1 = events.get(0); + assertTopFrame(e1, "testMethodFilter"); + + RecordedEvent e2 = events.get(1); + assertTopFrame(e2, "bar"); + + RecordedEvent e3 = events.get(2); + assertTopFrame(e3, "empty"); + } + } + + private static void testClassFilter() throws Exception { + try (Recording r = new Recording()) { + r.enable(MethodFilterEvent.class); + r.start(); + Quux.one(); + r.stop(); + List events = Events.fromRecording(r); + if (events.isEmpty()) { + throw new Exception("Excected events"); + } + + RecordedEvent e = events.get(0); + assertTopFrame(e, "qux"); + for (RecordedFrame f : e.getStackTrace().getFrames()) { + if (f.getMethod().getType().getName().contains("Quux")) { + System.out.println(e); + throw new Exception("Didn't expect Quux class in stack trace"); + } + } + } + } + + private static void empty() { + EmptyEvent event = new EmptyEvent(); + event.commit(); + } + + static void foo() { + baz(); + } + + static void bar() { + baz(); + } + + static void baz() { + MethodFilterEvent event = new MethodFilterEvent(); + event.commit(); + } + + static void qux() { + ClassFilterEvent event = new ClassFilterEvent(); + event.commit(); + } + + private static void rotate() { + try (Recording r = new Recording()) { + r.start(); + } + } + + private static EventType getType(String name) { + for (EventType et : FlightRecorder.getFlightRecorder().getEventTypes()) { + if (et.getName().equals(name)) { + return et; + } + + } + return null; + } + + private static void emitCommitter(Class eventClass) throws Exception { + Event event = eventClass.getConstructor().newInstance(); + event.commit(); + } + + private static void assertTopFrame(RecordedEvent event, String methodName) throws Exception { + RecordedStackTrace stackTrace = event.getStackTrace(); + if (stackTrace == null) { + System.out.println(event); + throw new Exception("No stack trace found when looking for top frame '" + methodName + "'"); + } + RecordedFrame frame = stackTrace.getFrames().get(0); + RecordedMethod method = frame.getMethod(); + if (!methodName.equals(method.getName())) { + System.out.println(event); + throw new Exception("Expected top frame '" + methodName + "'"); + } + } +} diff --git a/test/jdk/jdk/jfr/api/metadata/annotations/UnloadableClass.java b/test/jdk/jdk/jfr/api/metadata/annotations/UnloadableClass.java new file mode 100644 index 00000000000..4a4a57a0e55 --- /dev/null +++ b/test/jdk/jdk/jfr/api/metadata/annotations/UnloadableClass.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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.metadata.annotations; + +import java.io.DataInputStream; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.security.cert.Certificate; + +/* Purpose of this class is to load a specified class in its + * own class loader, but delegate every other class. + */ +public final class UnloadableClass extends ClassLoader { + private final String className; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Class load(Class clazz) throws ClassNotFoundException { + UnloadableClass cl = new UnloadableClass(clazz.getName()); + return cl.loadClass(cl.className); + } + + private UnloadableClass(String className) { + super("Class loader for class " + className, ClassLoader.getSystemClassLoader()); + this.className = className; + } + + public Class loadClass(String name) throws ClassNotFoundException { + if (!className.equals(name)) { + return super.loadClass(name); + } + String resourceName = name.replace('.', '/') + ".class"; + try (var is = getResourceAsStream(resourceName); var dis = new DataInputStream(is)) { + int size = is.available(); + byte buffer[] = new byte[size]; + dis.readFully(buffer); + CodeSource cs = new CodeSource(getResource(resourceName), (Certificate[]) null); + ProtectionDomain pd = new ProtectionDomain(cs, null); + return defineClass(name, buffer, 0, buffer.length, pd); + } catch (Exception e) { + throw new InternalError(e); + } + } +} diff --git a/test/jdk/jdk/jfr/event/os/TestProcessStart.java b/test/jdk/jdk/jfr/event/os/TestProcessStart.java index 32d3be0e980..bcf216857bb 100644 --- a/test/jdk/jdk/jfr/event/os/TestProcessStart.java +++ b/test/jdk/jdk/jfr/event/os/TestProcessStart.java @@ -47,7 +47,7 @@ public class TestProcessStart { public static void main(String[] args) throws Throwable { try (Recording recording = new Recording()) { - recording.enable(EVENT_NAME); + recording.enable(EVENT_NAME).withStackTrace(); recording.start(); List commandList = new ArrayList<>(); if (Platform.isWindows()) { @@ -74,6 +74,7 @@ public class TestProcessStart { Events.assertField(event, "pid").equal(p.pid()); Events.assertField(event, "directory").equal(pb.directory().toString()); Events.assertField(event, "command").equal(command.toString()); + Events.assertTopFrame(event, TestProcessStart.class, "main"); } } } diff --git a/test/jdk/jdk/jfr/event/runtime/TestThreadSleepEvent.java b/test/jdk/jdk/jfr/event/runtime/TestThreadSleepEvent.java index afd96b66540..c084808ce79 100644 --- a/test/jdk/jdk/jfr/event/runtime/TestThreadSleepEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestThreadSleepEvent.java @@ -56,13 +56,7 @@ public class TestThreadSleepEvent { recording.enable(EVENT_NAME).withoutThreshold().withStackTrace(); recording.start(); Thread.sleep(SLEEP_TIME_MS); - Thread virtualThread = Thread.ofVirtual().start(() -> { - try { - Thread.sleep(SLEEP_TIME_MS); - } catch (InterruptedException ie) { - throw new RuntimeException(ie); - } - }); + Thread virtualThread = Thread.ofVirtual().start(TestThreadSleepEvent::virtualSleep); virtualThread.join(); recording.stop(); @@ -74,14 +68,24 @@ public class TestThreadSleepEvent { System.out.println(event.getStackTrace()); if (event.getThread().getJavaThreadId() == Thread.currentThread().getId()) { threadCount--; + Events.assertTopFrame(event, TestThreadSleepEvent.class, "main"); Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS)); } if (event.getThread().getJavaThreadId() == virtualThread.getId()) { threadCount--; + Events.assertTopFrame(event, TestThreadSleepEvent.class, "virtualSleep"); Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS)); } } Asserts.assertEquals(threadCount, 0, "Could not find all expected events"); } } + + private static void virtualSleep() { + try { + Thread.sleep(SLEEP_TIME_MS); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } } diff --git a/test/jdk/jdk/jfr/event/security/TestSecurityPropertyModificationEvent.java b/test/jdk/jdk/jfr/event/security/TestSecurityPropertyModificationEvent.java index 0c1e5b32c5c..ff5919b2590 100644 --- a/test/jdk/jdk/jfr/event/security/TestSecurityPropertyModificationEvent.java +++ b/test/jdk/jdk/jfr/event/security/TestSecurityPropertyModificationEvent.java @@ -58,7 +58,7 @@ public class TestSecurityPropertyModificationEvent { } try (Recording recording = new Recording()) { - recording.enable(EventNames.SecurityProperty); + recording.enable(EventNames.SecurityProperty).withStackTrace(); recording.start(); for (String key: keys) { Security.setProperty(key, keyValue); @@ -78,6 +78,7 @@ public class TestSecurityPropertyModificationEvent { if (keys.contains(e.getString("key"))) { Events.assertField(e, "value").equal(keyValue); i++; + Events.assertTopFrame(e, TestSecurityPropertyModificationEvent.class, "main"); } else { System.out.println(events); throw new Exception("Unexpected event at index:" + i); diff --git a/test/jdk/jdk/jfr/event/security/TestSecurityProviderServiceEvent.java b/test/jdk/jdk/jfr/event/security/TestSecurityProviderServiceEvent.java index a886a7e3b20..df801c51907 100644 --- a/test/jdk/jdk/jfr/event/security/TestSecurityProviderServiceEvent.java +++ b/test/jdk/jdk/jfr/event/security/TestSecurityProviderServiceEvent.java @@ -52,32 +52,38 @@ public class TestSecurityProviderServiceEvent { public static void main(String[] args) throws Exception { testAlg(cipherFunc, "AES", "SunJCE", - "SunEC", "Cipher", 1, Collections.emptyList()); + "SunEC", "Cipher", 1, Collections.emptyList(), + javax.crypto.Cipher.class.getName(), "getInstance"); testAlg(signatureFunc, "SHA256withRSA", "SunRsaSign", - "SunEC", "Signature", 2, List.of("MessageDigest")); + "SunEC", "Signature", 2, List.of("MessageDigest"), + "sun.security.jca.GetInstance", "getService"); testAlg(messageDigestFunc, "SHA-512", "SUN", - "SunEC", "MessageDigest", 1, Collections.emptyList()); + "SunEC", "MessageDigest", 1, Collections.emptyList(), + "sun.security.jca.GetInstance", "getService"); testAlg(keystoreFunc, "PKCS12", "SUN", - "SunEC", "KeyStore", 1, Collections.emptyList()); + "SunEC", "KeyStore", 1, Collections.emptyList(), + "sun.security.jca.GetInstance", "getService"); testAlg(certPathBuilderFunc, "PKIX", "SUN", - "SunEC", "CertPathBuilder", 2, List.of("CertificateFactory")); + "SunEC", "CertPathBuilder", 2, List.of("CertificateFactory"), + "sun.security.jca.GetInstance", "getService"); } private static void testAlg(BiFunction bif, String alg, String workingProv, String brokenProv, String algType, - int expected, List other) throws Exception { + int expected, List other, + String clazz, String method) throws Exception { // bootstrap security Provider services Provider p = bif.apply(alg, workingProv); try (Recording recording = new Recording()) { - recording.enable(EventNames.SecurityProviderService); + recording.enable(EventNames.SecurityProviderService).withStackTrace(); recording.start(); p = bif.apply(alg, workingProv); bif.apply(alg, brokenProv); recording.stop(); List events = Events.fromRecording(recording); Asserts.assertEquals(events.size(), expected, "Incorrect number of events"); - assertEvent(events, algType, alg, p.getName(), other); + assertEvent(events, algType, alg, p.getName(), other, clazz, method); } } @@ -137,7 +143,8 @@ public class TestSecurityProviderServiceEvent { }; private static void assertEvent(List events, String type, - String alg, String workingProv, List other) { + String alg, String workingProv, List other, String clazz, + String method) { boolean secondaryEventOK = other.isEmpty() ? true : false; for (RecordedEvent e : events) { if (other.contains(e.getValue("type"))) { @@ -148,10 +155,10 @@ public class TestSecurityProviderServiceEvent { Events.assertField(e, "provider").equal(workingProv); Events.assertField(e, "type").equal(type); Events.assertField(e, "algorithm").equal(alg); + Events.assertTopFrame(e, clazz, method); } if (!secondaryEventOK) { throw new RuntimeException("Secondary events missing"); } - } } diff --git a/test/jdk/jdk/jfr/event/security/TestTLSHandshakeEvent.java b/test/jdk/jdk/jfr/event/security/TestTLSHandshakeEvent.java index 48e1fdfdd59..7c5240a2a72 100644 --- a/test/jdk/jdk/jfr/event/security/TestTLSHandshakeEvent.java +++ b/test/jdk/jdk/jfr/event/security/TestTLSHandshakeEvent.java @@ -43,7 +43,7 @@ import jdk.test.lib.security.TestTLSHandshake; public class TestTLSHandshakeEvent { public static void main(String[] args) throws Exception { try (Recording recording = new Recording()) { - recording.enable(EventNames.TLSHandshake); + recording.enable(EventNames.TLSHandshake).withStackTrace(); recording.start(); TestTLSHandshake handshake = new TestTLSHandshake(); handshake.run(); @@ -63,6 +63,10 @@ public class TestTLSHandshakeEvent { Events.assertField(e, "protocolVersion").equal(handshake.protocolVersion); Events.assertField(e, "certificateId").equal(TestTLSHandshake.CERT_ID); Events.assertField(e, "cipherSuite").equal(TestTLSHandshake.CIPHER_SUITE); + var method = e.getStackTrace().getFrames().get(0).getMethod(); + if (method.getName().equals("recordEvent")) { + throw new Exception("Didn't expected recordEvent as top frame"); + } return; } } diff --git a/test/jdk/jdk/jfr/jvm/TestGetStackTraceId.java b/test/jdk/jdk/jfr/jvm/TestGetStackTraceId.java index cdb8a9a5c43..8a23e551408 100644 --- a/test/jdk/jdk/jfr/jvm/TestGetStackTraceId.java +++ b/test/jdk/jdk/jfr/jvm/TestGetStackTraceId.java @@ -52,7 +52,7 @@ public class TestGetStackTraceId { } private static void assertMaxSkip() { - Asserts.assertEquals(JVM.getStackTraceId(Integer.MAX_VALUE), 0L, "Insane skip level " + Asserts.assertEquals(JVM.getStackTraceId(Integer.MAX_VALUE, -1), 0L, "Insane skip level " + Integer.MAX_VALUE + " should not return a valid stack trace id"); } @@ -64,6 +64,6 @@ public class TestGetStackTraceId { if (depth > 0) { return getStackIdOfDepth(depth - 1); } - return JVM.getStackTraceId(0); + return JVM.getStackTraceId(0, -1); } } diff --git a/test/lib/jdk/test/lib/jfr/Events.java b/test/lib/jdk/test/lib/jfr/Events.java index 620e5b0fcc3..e3e59798179 100644 --- a/test/lib/jdk/test/lib/jfr/Events.java +++ b/test/lib/jdk/test/lib/jfr/Events.java @@ -365,20 +365,42 @@ public class Events { return false; } + public static void assertTopFrame(RecordedEvent event, Class expectedClass, String expectedMethodName) { + assertTopFrame(event, expectedClass.getName(), expectedMethodName); + } + + public static void assertTopFrame(RecordedEvent event, String expectedClass, String expectedMethodName) { + RecordedStackTrace stackTrace = event.getStackTrace(); + Asserts.assertNotNull(stackTrace, "Missing stack trace"); + RecordedFrame topFrame = stackTrace.getFrames().get(0); + if (isFrame(topFrame, expectedClass, expectedMethodName)) { + return; + } + String expected = expectedClass + "::" + expectedMethodName; + Asserts.fail("Expected top frame " + expected + ". Found " + topFrame); + } + public static void assertFrame(RecordedEvent event, Class expectedClass, String expectedMethodName) { RecordedStackTrace stackTrace = event.getStackTrace(); Asserts.assertNotNull(stackTrace, "Missing stack trace"); for (RecordedFrame frame : stackTrace.getFrames()) { - if (frame.isJavaFrame()) { - RecordedMethod method = frame.getMethod(); - RecordedClass type = method.getType(); - if (expectedClass.getName().equals(type.getName())) { - if (expectedMethodName.equals(method.getName())) { - return; - } - } + if (isFrame(frame, expectedClass.getName(), expectedMethodName)) { + return; } } Asserts.fail("Expected " + expectedClass.getName() + "::"+ expectedMethodName + " in stack trace"); } + + private static boolean isFrame(RecordedFrame frame, String expectedClass, String expectedMethodName) { + if (frame.isJavaFrame()) { + RecordedMethod method = frame.getMethod(); + RecordedClass type = method.getType(); + if (expectedClass.equals(type.getName())) { + if (expectedMethodName.equals(method.getName())) { + return true; + } + } + } + return false; + } }