diff --git a/src/hotspot/share/classfile/modules.hpp b/src/hotspot/share/classfile/modules.hpp index 33a5ec77ccb0b..d6d81263449c9 100644 --- a/src/hotspot/share/classfile/modules.hpp +++ b/src/hotspot/share/classfile/modules.hpp @@ -29,6 +29,7 @@ #include "runtime/handles.hpp" class ModuleEntryTable; +class SerializeClosure; class Symbol; class Modules : AllStatic { diff --git a/src/hotspot/share/jfr/instrumentation/jfrClassTransformer.cpp b/src/hotspot/share/jfr/instrumentation/jfrClassTransformer.cpp new file mode 100644 index 0000000000000..2748099972065 --- /dev/null +++ b/src/hotspot/share/jfr/instrumentation/jfrClassTransformer.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2016, 2025, 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 "classfile/classFileParser.hpp" +#include "classfile/classFileStream.hpp" +#include "classfile/classLoadInfo.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "classfile/symbolTable.hpp" +#include "jfr/instrumentation/jfrClassTransformer.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "logging/log.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "prims/jvmtiRedefineClasses.hpp" +#include "prims/jvmtiThreadState.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/javaThread.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/macros.hpp" + +static void log_pending_exception(oop throwable) { + assert(throwable != nullptr, "invariant"); + oop msg = java_lang_Throwable::message(throwable); + if (msg != nullptr) { + char* text = java_lang_String::as_utf8_string(msg); + if (text != nullptr) { + log_error(jfr, system) ("%s", text); + } + } +} + +// On initial class load. +void JfrClassTransformer::cache_class_file_data(InstanceKlass* new_ik, const ClassFileStream* new_stream, const JavaThread* thread) { + assert(new_ik != nullptr, "invariant"); + assert(new_stream != nullptr, "invariant"); + assert(thread != nullptr, "invariant"); + assert(!thread->has_pending_exception(), "invariant"); + if (!JfrOptionSet::allow_retransforms()) { + return; + } + const jint stream_len = new_stream->length(); + JvmtiCachedClassFileData* p = + (JvmtiCachedClassFileData*)NEW_C_HEAP_ARRAY_RETURN_NULL(u1, offset_of(JvmtiCachedClassFileData, data) + stream_len, mtInternal); + if (p == nullptr) { + log_error(jfr, system)("Allocation using C_HEAP_ARRAY for %zu bytes failed in JfrEventClassTransformer::cache_class_file_data", + static_cast(offset_of(JvmtiCachedClassFileData, data) + stream_len)); + return; + } + p->length = stream_len; + memcpy(p->data, new_stream->buffer(), stream_len); + new_ik->set_cached_class_file(p); +} + +InstanceKlass* JfrClassTransformer::create_instance_klass(InstanceKlass*& ik, ClassFileStream* stream, bool is_initial_load, JavaThread* thread) { + if (stream == nullptr) { + if (is_initial_load) { + log_error(jfr, system)("JfrClassTransformer: unable to create ClassFileStream for %s", ik->external_name()); + } + return nullptr; + } + InstanceKlass* const new_ik = create_new_instance_klass(ik, stream, thread); + if (new_ik == nullptr) { + if (is_initial_load) { + log_error(jfr, system)("JfrClassTransformer: unable to create InstanceKlass for %s", ik->external_name()); + } + } + return new_ik; +} + +void JfrClassTransformer::copy_traceid(const InstanceKlass* ik, const InstanceKlass* new_ik) { + assert(ik != nullptr, "invariant"); + assert(new_ik != nullptr, "invariant"); + new_ik->set_trace_id(ik->trace_id()); + assert(TRACE_ID(ik) == TRACE_ID(new_ik), "invariant"); +} + +InstanceKlass* JfrClassTransformer::create_new_instance_klass(InstanceKlass* ik, ClassFileStream* stream, TRAPS) { + assert(stream != nullptr, "invariant"); + ResourceMark rm(THREAD); + ClassLoaderData* const cld = ik->class_loader_data(); + Handle pd(THREAD, ik->protection_domain()); + Symbol* const class_name = ik->name(); + ClassLoadInfo cl_info(pd); + ClassFileParser new_parser(stream, + class_name, + cld, + &cl_info, + ClassFileParser::INTERNAL, // internal visibility + THREAD); + if (HAS_PENDING_EXCEPTION) { + log_pending_exception(PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + return nullptr; + } + const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr(); + InstanceKlass* const new_ik = new_parser.create_instance_klass(false, *cl_inst_info, THREAD); + if (HAS_PENDING_EXCEPTION) { + log_pending_exception(PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + return nullptr; + } + assert(new_ik != nullptr, "invariant"); + assert(new_ik->name() != nullptr, "invariant"); + assert(ik->name() == new_ik->name(), "invariant"); + return new_ik; +} + +// Redefining / retransforming? +const Klass* JfrClassTransformer::find_existing_klass(const InstanceKlass* ik, JavaThread* thread) { + assert(ik != nullptr, "invariant"); + assert(thread != nullptr, "invariant"); + JvmtiThreadState* const state = thread->jvmti_thread_state(); + return state != nullptr ? klass_being_redefined(ik, state) : nullptr; +} + +const Klass* JfrClassTransformer::klass_being_redefined(const InstanceKlass* ik, JvmtiThreadState* state) { + assert(ik != nullptr, "invariant"); + assert(state != nullptr, "invariant"); + const GrowableArray* const redef_klasses = state->get_classes_being_redefined(); + if (redef_klasses == nullptr || redef_klasses->is_empty()) { + return nullptr; + } + for (int i = 0; i < redef_klasses->length(); ++i) { + const Klass* const existing_klass = redef_klasses->at(i); + assert(existing_klass != nullptr, "invariant"); + if (ik->name() == existing_klass->name() && ik->class_loader_data() == existing_klass->class_loader_data()) { + // 'ik' is a scratch klass. Return the klass being redefined. + return existing_klass; + } + } + return nullptr; +} + +// On redefine / retransform, in case an agent modified the class, the original bytes are cached onto the scratch klass. +void JfrClassTransformer::transfer_cached_class_file_data(InstanceKlass* ik, InstanceKlass* new_ik, const ClassFileParser& parser, JavaThread* thread) { + assert(ik != nullptr, "invariant"); + assert(new_ik != nullptr, "invariant"); + JvmtiCachedClassFileData* const p = ik->get_cached_class_file(); + if (p != nullptr) { + new_ik->set_cached_class_file(p); + ik->set_cached_class_file(nullptr); + return; + } + // No cached classfile indicates that no agent modified the klass. + // This means that the parser is holding the original bytes. Hence, we cache it onto the scratch klass. + const ClassFileStream* const stream = parser.clone_stream(); + cache_class_file_data(new_ik, stream, thread); +} + +void JfrClassTransformer::rewrite_klass_pointer(InstanceKlass*& ik, InstanceKlass* new_ik, ClassFileParser& parser, const JavaThread* thread) { + assert(ik != nullptr, "invariant"); + assert(new_ik != nullptr, "invariant"); + assert(thread != nullptr, "invariant"); + assert(TRACE_ID(ik) == TRACE_ID(new_ik), "invariant"); + assert(!thread->has_pending_exception(), "invariant"); + // Assign original InstanceKlass* back onto "its" parser object for proper destruction. + parser.set_klass_to_deallocate(ik); + // Finally rewrite the original pointer to the newly created InstanceKlass. + ik = new_ik; +} + diff --git a/src/hotspot/share/jfr/instrumentation/jfrClassTransformer.hpp b/src/hotspot/share/jfr/instrumentation/jfrClassTransformer.hpp new file mode 100644 index 0000000000000..3a2629bbc97d9 --- /dev/null +++ b/src/hotspot/share/jfr/instrumentation/jfrClassTransformer.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, 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_INSTRUMENTATION_JFRCLASSTRANSFORMER_HPP +#define SHARE_JFR_INSTRUMENTATION_JFRCLASSTRANSFORMER_HPP + +#include "memory/allStatic.hpp" +#include "utilities/exceptions.hpp" + +class ClassFileParser; +class ClassFileStream; +class InstanceKlass; + +/* + * Contains common functionality used by method and event instrumentation. + */ +class JfrClassTransformer : AllStatic { + private: + static InstanceKlass* create_new_instance_klass(InstanceKlass* ik, ClassFileStream* stream, TRAPS); + static const Klass* klass_being_redefined(const InstanceKlass* ik, JvmtiThreadState* state); + + public: + static const Klass* find_existing_klass(const InstanceKlass* ik, JavaThread* thread); + static InstanceKlass* create_instance_klass(InstanceKlass*& ik, ClassFileStream* stream, bool is_initial_load, JavaThread* thread); + static void copy_traceid(const InstanceKlass* ik, const InstanceKlass* new_ik); + static void transfer_cached_class_file_data(InstanceKlass* ik, InstanceKlass* new_ik, const ClassFileParser& parser, JavaThread* thread); + static void rewrite_klass_pointer(InstanceKlass*& ik, InstanceKlass* new_ik, ClassFileParser& parser, const JavaThread* thread); + static void cache_class_file_data(InstanceKlass* new_ik, const ClassFileStream* new_stream, const JavaThread* thread); +}; + +#endif // SHARE_JFR_INSTRUMENTATION_JFRCLASSTRANSFORMER_HPP diff --git a/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp index 52d7280716afc..5e7f65aeec1e8 100644 --- a/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp +++ b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp @@ -24,41 +24,35 @@ #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" -#include "classfile/classLoadInfo.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/modules.hpp" #include "classfile/stackMapTable.hpp" #include "classfile/symbolTable.hpp" -#include "classfile/verificationType.hpp" #include "interpreter/bytecodes.hpp" -#include "jvm.h" +#include "jfr/instrumentation/jfrClassTransformer.hpp" #include "jfr/instrumentation/jfrEventClassTransformer.hpp" #include "jfr/jfr.hpp" #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/jni/jfrUpcalls.hpp" -#include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/support/jfrAnnotationElementIterator.hpp" +#include "jfr/support/jfrAnnotationIterator.hpp" #include "jfr/support/jfrJdkJfrEvent.hpp" -#include "jfr/utilities/jfrBigEndian.hpp" #include "jfr/writers/jfrBigEndianWriter.hpp" +#include "jvm.h" #include "logging/log.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "oops/array.hpp" -#include "oops/constMethod.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/method.hpp" -#include "prims/jvmtiRedefineClasses.hpp" -#include "prims/jvmtiThreadState.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.hpp" #include "runtime/jniHandles.hpp" -#include "runtime/os.hpp" #include "utilities/exceptions.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/growableArray.hpp" #include "utilities/macros.hpp" static const u2 number_of_new_methods = 5; @@ -166,208 +160,14 @@ static u1 boolean_method_code_attribute[] = { 0x0, // attributes_count }; -/* - Annotation layout. - - enum { // initial annotation layout - atype_off = 0, // utf8 such as 'Ljava/lang/annotation/Retention;' - count_off = 2, // u2 such as 1 (one value) - member_off = 4, // utf8 such as 'value' - tag_off = 6, // u1 such as 'c' (type) or 'e' (enum) - e_tag_val = 'e', - e_type_off = 7, // utf8 such as 'Ljava/lang/annotation/RetentionPolicy;' - e_con_off = 9, // utf8 payload, such as 'SOURCE', 'CLASS', 'RUNTIME' - e_size = 11, // end of 'e' annotation - c_tag_val = 'c', // payload is type - c_con_off = 7, // utf8 payload, such as 'I' - c_size = 9, // end of 'c' annotation - s_tag_val = 's', // payload is String - s_con_off = 7, // utf8 payload, such as 'Ljava/lang/String;' - s_size = 9, - min_size = 6 // smallest possible size (zero members) - }; -*/ - -static int skip_annotation_value(const address, int, int); // fwd decl - -// Skip an annotation. Return >=limit if there is any problem. -static int next_annotation_index(const address buffer, int limit, int index) { - assert(buffer != nullptr, "invariant"); - index += 2; // skip atype - if ((index += 2) >= limit) { - return limit; - } - int nof_members = JfrBigEndian::read(buffer + index - 2); - while (--nof_members >= 0 && index < limit) { - index += 2; // skip member - index = skip_annotation_value(buffer, limit, index); - } - return index; +static JfrAnnotationElementIterator elements_iterator(const InstanceKlass* ik, const JfrAnnotationIterator& it) { + const address buffer = it.buffer(); + int current = it.current(); + int next = it.next(); + assert(current < next, "invariant"); + return JfrAnnotationElementIterator(ik, buffer + current, next - current); } -// Skip an annotation value. Return >=limit if there is any problem. -static int skip_annotation_value(const address buffer, int limit, int index) { - assert(buffer != nullptr, "invariant"); - // value := switch (tag:u1) { - // case B, C, I, S, Z, D, F, J, c: con:u2; - // case e: e_class:u2 e_name:u2; - // case s: s_con:u2; - // case [: do(nval:u2) {value}; - // case @: annotation; - // case s: s_con:u2; - // } - if ((index += 1) >= limit) { - return limit; - } - const u1 tag = buffer[index - 1]; - switch (tag) { - case 'B': - case 'C': - case 'I': - case 'S': - case 'Z': - case 'D': - case 'F': - case 'J': - case 'c': - case 's': - index += 2; // skip con or s_con - break; - case 'e': - index += 4; // skip e_class, e_name - break; - case '[': - { - if ((index += 2) >= limit) { - return limit; - } - int nof_values = JfrBigEndian::read(buffer + index - 2); - while (--nof_values >= 0 && index < limit) { - index = skip_annotation_value(buffer, limit, index); - } - } - break; - case '@': - index = next_annotation_index(buffer, limit, index); - break; - default: - return limit; // bad tag byte - } - return index; -} - -static constexpr const int number_of_elements_offset = 2; -static constexpr const int element_name_offset = number_of_elements_offset + 2; -static constexpr const int element_name_size = 2; -static constexpr const int value_type_relative_offset = 2; -static constexpr const int value_relative_offset = value_type_relative_offset + 1; - -// see JVMS - 4.7.16. The RuntimeVisibleAnnotations Attribute - -class AnnotationElementIterator : public StackObj { - private: - const InstanceKlass* _ik; - const address _buffer; - const int _limit; // length of annotation - mutable int _current; // element - mutable int _next; // element - - int value_index() const { - return JfrBigEndian::read(_buffer + _current + value_relative_offset); - } - - public: - AnnotationElementIterator(const InstanceKlass* ik, address buffer, int limit) : _ik(ik), - _buffer(buffer), - _limit(limit), - _current(element_name_offset), - _next(element_name_offset) { - assert(_buffer != nullptr, "invariant"); - assert(_next == element_name_offset, "invariant"); - assert(_current == element_name_offset, "invariant"); - } - - bool has_next() const { - return _next < _limit; - } - - void move_to_next() const { - assert(has_next(), "invariant"); - _current = _next; - if (_next < _limit) { - _next = skip_annotation_value(_buffer, _limit, _next + element_name_size); - } - assert(_next <= _limit, "invariant"); - assert(_current <= _limit, "invariant"); - } - - int number_of_elements() const { - return JfrBigEndian::read(_buffer + number_of_elements_offset); - } - - const Symbol* name() const { - assert(_current < _next, "invariant"); - return _ik->constants()->symbol_at(JfrBigEndian::read(_buffer + _current)); - } - - char value_type() const { - return JfrBigEndian::read(_buffer + _current + value_type_relative_offset); - } - - jint read_int() const { - return _ik->constants()->int_at(value_index()); - } - - bool read_bool() const { - return read_int() != 0; - } -}; - -class AnnotationIterator : public StackObj { - private: - const InstanceKlass* _ik; - // ensure _limit field is declared before _buffer - int _limit; // length of annotations array - const address _buffer; - mutable int _current; // annotation - mutable int _next; // annotation - - public: - AnnotationIterator(const InstanceKlass* ik, AnnotationArray* ar) : _ik(ik), - _limit(ar != nullptr ? ar->length() : 0), - _buffer(_limit > 2 ? ar->adr_at(2) : nullptr), - _current(0), - _next(0) { - if (_limit >= 2) { - _limit -= 2; // subtract sizeof(u2) number of annotations field - } - } - bool has_next() const { - return _next < _limit; - } - - void move_to_next() const { - assert(has_next(), "invariant"); - _current = _next; - if (_next < _limit) { - _next = next_annotation_index(_buffer, _limit, _next); - } - assert(_next <= _limit, "invariant"); - assert(_current <= _limit, "invariant"); - } - - const AnnotationElementIterator elements() const { - assert(_current < _next, "invariant"); - return AnnotationElementIterator(_ik, _buffer + _current, _next - _current); - } - - const Symbol* type() const { - assert(_buffer != nullptr, "invariant"); - assert(_current < _limit, "invariant"); - return _ik->constants()->symbol_at(JfrBigEndian::read(_buffer + _current)); - } -}; - static const char value_name[] = "value"; static bool has_annotation(const InstanceKlass* ik, const Symbol* annotation_type, bool default_value, bool& value) { assert(annotation_type != nullptr, "invariant"); @@ -375,8 +175,7 @@ static bool has_annotation(const InstanceKlass* ik, const Symbol* annotation_typ if (class_annotations == nullptr) { return false; } - - const AnnotationIterator annotation_iterator(ik, class_annotations); + const JfrAnnotationIterator annotation_iterator(ik, class_annotations); while (annotation_iterator.has_next()) { annotation_iterator.move_to_next(); if (annotation_iterator.type() == annotation_type) { @@ -384,7 +183,7 @@ static bool has_annotation(const InstanceKlass* ik, const Symbol* annotation_typ static const Symbol* value_symbol = SymbolTable::probe(value_name, sizeof value_name - 1); assert(value_symbol != nullptr, "invariant"); - const AnnotationElementIterator element_iterator = annotation_iterator.elements(); + JfrAnnotationElementIterator element_iterator = elements_iterator(ik, annotation_iterator); if (!element_iterator.has_next()) { // Default values are not stored in the annotation element, so if the // element-value pair is empty, return the default value. @@ -1555,57 +1354,6 @@ static ClassFileStream* retransform_bytes(const Klass* existing_klass, const Cla return new ClassFileStream(new_bytes, size_of_new_bytes, nullptr); } -// On initial class load. -static void cache_class_file_data(InstanceKlass* new_ik, const ClassFileStream* new_stream, const JavaThread* thread) { - assert(new_ik != nullptr, "invariant"); - assert(new_stream != nullptr, "invariant"); - assert(thread != nullptr, "invariant"); - assert(!thread->has_pending_exception(), "invariant"); - if (!JfrOptionSet::allow_retransforms()) { - return; - } - const jint stream_len = new_stream->length(); - JvmtiCachedClassFileData* p = - (JvmtiCachedClassFileData*)NEW_C_HEAP_ARRAY_RETURN_NULL(u1, offset_of(JvmtiCachedClassFileData, data) + stream_len, mtInternal); - if (p == nullptr) { - log_error(jfr, system)("Allocation using C_HEAP_ARRAY for %zu bytes failed in JfrEventClassTransformer::cache_class_file_data", - static_cast(offset_of(JvmtiCachedClassFileData, data) + stream_len)); - return; - } - p->length = stream_len; - memcpy(p->data, new_stream->buffer(), stream_len); - new_ik->set_cached_class_file(p); -} - -// On redefine / retransform, in case an agent modified the class, the original bytes are cached onto the scratch klass. -static void transfer_cached_class_file_data(InstanceKlass* ik, InstanceKlass* new_ik, const ClassFileParser& parser, JavaThread* thread) { - assert(ik != nullptr, "invariant"); - assert(new_ik != nullptr, "invariant"); - JvmtiCachedClassFileData* const p = ik->get_cached_class_file(); - if (p != nullptr) { - new_ik->set_cached_class_file(p); - ik->set_cached_class_file(nullptr); - return; - } - // No cached classfile indicates that no agent modified the klass. - // This means that the parser is holding the original bytes. Hence, we cache it onto the scratch klass. - const ClassFileStream* const stream = parser.clone_stream(); - cache_class_file_data(new_ik, stream, thread); -} - -static void rewrite_klass_pointer(InstanceKlass*& ik, InstanceKlass* new_ik, ClassFileParser& parser, const JavaThread* thread) { - assert(ik != nullptr, "invariant"); - assert(new_ik != nullptr, "invariant"); - assert(thread != nullptr, "invariant"); - assert(IS_EVENT_OR_HOST_KLASS(new_ik), "invariant"); - assert(TRACE_ID(ik) == TRACE_ID(new_ik), "invariant"); - assert(!thread->has_pending_exception(), "invariant"); - // Assign original InstanceKlass* back onto "its" parser object for proper destruction. - parser.set_klass_to_deallocate(ik); - // Finally rewrite the original pointer to the newly created InstanceKlass. - ik = new_ik; -} - // If code size is 1, it is 0xb1, i.e. the return instruction. static inline bool is_commit_method_instrumented(const Method* m) { assert(m != nullptr, "invariant"); @@ -1658,92 +1406,11 @@ static void bless_commit_method(const InstanceKlass* new_ik) { bless_instance_commit_method(methods); } -static void copy_traceid(const InstanceKlass* ik, const InstanceKlass* new_ik) { - assert(ik != nullptr, "invariant"); - assert(new_ik != nullptr, "invariant"); - new_ik->set_trace_id(ik->trace_id()); - assert(TRACE_ID(ik) == TRACE_ID(new_ik), "invariant"); -} - -static const Klass* klass_being_redefined(const InstanceKlass* ik, JvmtiThreadState* state) { - assert(ik != nullptr, "invariant"); - assert(state != nullptr, "invariant"); - const GrowableArray* const redef_klasses = state->get_classes_being_redefined(); - if (redef_klasses == nullptr || redef_klasses->is_empty()) { - return nullptr; - } - for (int i = 0; i < redef_klasses->length(); ++i) { - const Klass* const existing_klass = redef_klasses->at(i); - assert(existing_klass != nullptr, "invariant"); - if (ik->name() == existing_klass->name() && ik->class_loader_data() == existing_klass->class_loader_data()) { - // 'ik' is a scratch klass. Return the klass being redefined. - return existing_klass; - } - } - return nullptr; -} - -// Redefining / retransforming? -static const Klass* find_existing_klass(const InstanceKlass* ik, JavaThread* thread) { - assert(ik != nullptr, "invariant"); - assert(thread != nullptr, "invariant"); - JvmtiThreadState* const state = thread->jvmti_thread_state(); - return state != nullptr ? klass_being_redefined(ik, state) : nullptr; -} - -static InstanceKlass* create_new_instance_klass(InstanceKlass* ik, ClassFileStream* stream, TRAPS) { - assert(stream != nullptr, "invariant"); - ResourceMark rm(THREAD); - ClassLoaderData* const cld = ik->class_loader_data(); - Handle pd(THREAD, ik->protection_domain()); - Symbol* const class_name = ik->name(); - const char* const klass_name = class_name != nullptr ? class_name->as_C_string() : ""; - ClassLoadInfo cl_info(pd); - ClassFileParser new_parser(stream, - class_name, - cld, - &cl_info, - ClassFileParser::INTERNAL, // internal visibility - THREAD); - if (HAS_PENDING_EXCEPTION) { - log_pending_exception(PENDING_EXCEPTION); - CLEAR_PENDING_EXCEPTION; - return nullptr; - } - const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr(); - InstanceKlass* const new_ik = new_parser.create_instance_klass(false, *cl_inst_info, THREAD); - if (HAS_PENDING_EXCEPTION) { - log_pending_exception(PENDING_EXCEPTION); - CLEAR_PENDING_EXCEPTION; - return nullptr; - } - assert(new_ik != nullptr, "invariant"); - assert(new_ik->name() != nullptr, "invariant"); - assert(strncmp(ik->name()->as_C_string(), new_ik->name()->as_C_string(), strlen(ik->name()->as_C_string())) == 0, "invariant"); - return new_ik; -} - -static InstanceKlass* create_instance_klass(InstanceKlass*& ik, ClassFileStream* stream, bool is_initial_load, JavaThread* thread) { - if (stream == nullptr) { - if (is_initial_load) { - log_error(jfr, system)("JfrEventClassTransformer: unable to create ClassFileStream for %s", ik->external_name()); - } - return nullptr; - } - InstanceKlass* const new_ik = create_new_instance_klass(ik, stream, thread); - if (new_ik == nullptr) { - if (is_initial_load) { - log_error(jfr, system)("JfrEventClassTransformer: unable to create InstanceKlass for %s", ik->external_name()); - } - } - return new_ik; -} - static void transform(InstanceKlass*& ik, ClassFileParser& parser, JavaThread* thread) { assert(IS_EVENT_OR_HOST_KLASS(ik), "invariant"); bool is_instrumented = false; ClassFileStream* stream = nullptr; - const Klass* const existing_klass = find_existing_klass(ik, thread); + const Klass* const existing_klass = JfrClassTransformer::find_existing_klass(ik, thread); if (existing_klass != nullptr) { // There is already a klass defined, implying we are redefining / retransforming. stream = retransform_bytes(existing_klass, parser, is_instrumented, thread); @@ -1751,20 +1418,20 @@ static void transform(InstanceKlass*& ik, ClassFileParser& parser, JavaThread* t // No existing klass, implying this is the initial load. stream = JdkJfrEvent::is(ik) ? schema_extend_event_klass_bytes(ik, parser, thread) : schema_extend_event_subklass_bytes(ik, parser, is_instrumented, thread); } - InstanceKlass* const new_ik = create_instance_klass(ik, stream, existing_klass == nullptr, thread); + InstanceKlass* const new_ik = JfrClassTransformer::create_instance_klass(ik, stream, existing_klass == nullptr, thread); if (new_ik == nullptr) { return; } if (existing_klass != nullptr) { - transfer_cached_class_file_data(ik, new_ik, parser, thread); + JfrClassTransformer::transfer_cached_class_file_data(ik, new_ik, parser, thread); } else { - cache_class_file_data(new_ik, stream, thread); + JfrClassTransformer::cache_class_file_data(new_ik, stream, thread); } if (is_instrumented && JdkJfrEvent::is_subklass(new_ik)) { bless_commit_method(new_ik); } - copy_traceid(ik, new_ik); - rewrite_klass_pointer(ik, new_ik, parser, thread); + JfrClassTransformer::copy_traceid(ik, new_ik); + JfrClassTransformer::rewrite_klass_pointer(ik, new_ik, parser, thread); } // Target for the JFR_ON_KLASS_CREATION hook. diff --git a/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp index 31dd55f702d2d..9fb6cf87b727d 100644 --- a/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp +++ b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp @@ -78,19 +78,6 @@ extern "C" void JNICALL jfr_on_class_file_load_hook(jvmtiEnv *jvmti_env, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { - if (class_being_redefined == nullptr) { - return; - } - JavaThread* jt = JavaThread::thread_from_jni_environment(jni_env); - DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt));; - ThreadInVMfromNative tvmfn(jt); - JfrUpcalls::on_retransform(JfrTraceId::load_raw(class_being_redefined), - class_being_redefined, - class_data_len, - class_data, - new_class_data_len, - new_class_data, - jt); } // caller needs ResourceMark @@ -175,6 +162,10 @@ void JfrJvmtiAgent::retransform_classes(JNIEnv* env, jobjectArray classes_array, } } } + retransform_classes(env, classes, classes_count, THREAD); +} + +void JfrJvmtiAgent::retransform_classes(JNIEnv* env, jclass* classes, jint classes_count, TRAPS) { DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); const jvmtiError result = jfr_jvmti_env->RetransformClasses(classes_count, classes); if (result != JVMTI_ERROR_NONE) { diff --git a/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp index 22c1b5c62560b..d82ea3a612b54 100644 --- a/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp +++ b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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 @@ -36,6 +36,7 @@ class JfrJvmtiAgent : public JfrCHeapObj { static void destroy() NOT_JVMTI_RETURN; public: static void retransform_classes(JNIEnv* env, jobjectArray classes, TRAPS) NOT_JVMTI_RETURN; + static void retransform_classes(JNIEnv* env, jclass* classes, jint classes_count, TRAPS) NOT_JVMTI_RETURN; }; #endif // SHARE_JFR_INSTRUMENTATION_JFRJVMTIAGENT_HPP diff --git a/src/hotspot/share/jfr/jfr.cpp b/src/hotspot/share/jfr/jfr.cpp index 99416519e4012..f43aab6bdfc49 100644 --- a/src/hotspot/share/jfr/jfr.cpp +++ b/src/hotspot/share/jfr/jfr.cpp @@ -22,6 +22,7 @@ * */ +#include "jfr/instrumentation/jfrEventClassTransformer.hpp" #include "jfr/jfr.hpp" #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/leakProfiler.hpp" @@ -31,8 +32,13 @@ #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/recorder/repository/jfrRepository.hpp" +#include "jfr/support/jfrKlassExtension.hpp" #include "jfr/support/jfrResolution.hpp" #include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/support/methodtracer/jfrMethodTracer.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/klass.hpp" #include "runtime/java.hpp" #include "runtime/javaThread.hpp" @@ -72,6 +78,22 @@ void Jfr::on_unloading_classes() { } } +void Jfr::on_klass_creation(InstanceKlass*& ik, ClassFileParser& parser, TRAPS) { + if (IS_EVENT_OR_HOST_KLASS(ik)) { + JfrEventClassTransformer::on_klass_creation(ik, parser, THREAD); + return; + } + if (JfrMethodTracer::in_use()) { + JfrMethodTracer::on_klass_creation(ik, parser, THREAD); + } +} + +void Jfr::on_klass_redefinition(const InstanceKlass* ik, Thread* thread) { + assert(JfrMethodTracer::in_use(), "invariant"); + JfrMethodTracer::on_klass_redefinition(ik, thread); +} + + bool Jfr::is_excluded(Thread* t) { return JfrJavaSupport::is_excluded(t); } diff --git a/src/hotspot/share/jfr/jfr.hpp b/src/hotspot/share/jfr/jfr.hpp index ce6db8a164d4a..471389dfb8b62 100644 --- a/src/hotspot/share/jfr/jfr.hpp +++ b/src/hotspot/share/jfr/jfr.hpp @@ -33,7 +33,9 @@ class CallInfo; class ciKlass; class ciMethod; +class ClassFileParser; class GraphBuilder; +class InstanceKlass; class JavaThread; struct JavaVMOption; class Klass; @@ -58,6 +60,8 @@ class Jfr : AllStatic { static bool is_excluded(Thread* thread); static void include_thread(Thread* thread); static void exclude_thread(Thread* thread); + static void on_klass_creation(InstanceKlass*& ik, ClassFileParser& parser, TRAPS); + static void on_klass_redefinition(const InstanceKlass* ik, Thread* thread); static void on_thread_start(Thread* thread); static void on_thread_exit(Thread* thread); static void on_resolution(const CallInfo& info, TRAPS); diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp index 25badd191a5ea..1dd7168e9e519 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp @@ -35,6 +35,7 @@ #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" #include "jfr/support/jfrThreadId.inline.hpp" #include "logging/log.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/instanceOop.hpp" #include "oops/klass.inline.hpp" @@ -929,3 +930,14 @@ bool JfrJavaSupport::compute_field_offset(int &dest_offset, dest_offset = fd.offset(); return true; } + +jlongArray JfrJavaSupport::create_long_array(GrowableArray* array, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(array != nullptr, "invariant"); + assert(array->is_nonempty(), "invariant"); + const int length = array->length(); + assert(length > 0, "invariant"); + typeArrayOop obj = oopFactory::new_typeArray(T_LONG, length, CHECK_NULL); + ArrayAccess<>::arraycopy_from_native(&array->first(), obj, typeArrayOopDesc::element_offset(0), length); + return static_cast(JfrJavaSupport::local_jni_handle(obj, THREAD)); +} diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp index 19ff332c118f8..118782bc61e56 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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 @@ -30,6 +30,7 @@ class Klass; class outputStream; +template class GrowableArray; class JfrJavaSupport : public AllStatic { public: @@ -88,6 +89,7 @@ class JfrJavaSupport : public AllStatic { static const char* c_str(oop string, Thread* thread, bool c_heap = false); static void free_c_str(const char* str, bool c_heap); static Symbol** symbol_array(jobjectArray string_array, JavaThread* thread, intptr_t* result_size, bool c_heap = false); + static jlongArray create_long_array(GrowableArray* array, TRAPS); // 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 d932f073d4b4c..6f1c1936574f1 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -49,6 +49,7 @@ #include "jfr/support/jfrDeprecationManager.hpp" #include "jfr/support/jfrJdkJfrEvent.hpp" #include "jfr/support/jfrKlassUnloading.hpp" +#include "jfr/support/methodtracer/jfrMethodTracer.hpp" #include "jfr/utilities/jfrJavaLog.hpp" #include "jfr/utilities/jfrTimeConverter.hpp" #include "jfr/utilities/jfrTime.hpp" @@ -437,3 +438,11 @@ NO_TRANSITION(jboolean, jfr_is_product(JNIEnv* env, jclass jvm)) return false; #endif NO_TRANSITION_END + +JVM_ENTRY_NO_ENV(jlongArray, jfr_set_method_trace_filters(JNIEnv* env, jclass jvm, jobjectArray classes, jobjectArray methods, jobjectArray annotations, jintArray modifications)) + return JfrMethodTracer::set_filters(env, classes, methods, annotations, modifications, thread); +JVM_END + +JVM_ENTRY_NO_ENV(jlongArray, jfr_drain_stale_method_tracer_ids(JNIEnv* env, jclass jvm)) + return JfrMethodTracer::drain_stale_class_ids(thread); +JVM_END diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 4519b99891dd5..9c78c6239d42d 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ -169,6 +169,10 @@ jlong JNICALL jfr_nanos_now(JNIEnv* env, jclass jvm); jboolean JNICALL jfr_is_product(JNIEnv* env, jclass jvm); +jlongArray JNICALL jfr_set_method_trace_filters(JNIEnv* env, jclass jvm, jobjectArray classes, jobjectArray methods, jobjectArray annotations, jintArray modifications); + +jlongArray JNICALL jfr_drain_stale_method_tracer_ids(JNIEnv* env, jclass); + #ifdef __cplusplus } #endif diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 433da3e2f437f..33a564dee2fd6 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -101,7 +101,9 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (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, (char*)"nanosNow", (char*)"()J", (void*)jfr_nanos_now, - (char*)"isProduct", (char*)"()Z", (void*)jfr_is_product + (char*)"isProduct", (char*)"()Z", (void*)jfr_is_product, + (char*)"setMethodTraceFilters", (char*)"([Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[I)[J", (void*)jfr_set_method_trace_filters, + (char*)"drainStaleMethodTracerIds", (char*)"()[J", (void*)jfr_drain_stale_method_tracer_ids }; const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod); diff --git a/src/hotspot/share/jfr/jni/jfrUpcalls.cpp b/src/hotspot/share/jfr/jni/jfrUpcalls.cpp index 718e62a9ce816..2e814cef87557 100644 --- a/src/hotspot/share/jfr/jni/jfrUpcalls.cpp +++ b/src/hotspot/share/jfr/jni/jfrUpcalls.cpp @@ -22,12 +22,16 @@ * */ +#include "classfile/classFileStream.hpp" #include "classfile/javaClasses.hpp" +#include "classfile/moduleEntry.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/jni/jfrUpcalls.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" #include "jfr/support/jfrJdkJfrEvent.hpp" +#include "jfr/support/methodtracer/jfrTracedMethod.hpp" #include "jvm_io.h" #include "logging/log.hpp" #include "memory/oopFactory.hpp" @@ -36,7 +40,9 @@ #include "oops/typeArrayKlass.hpp" #include "oops/typeArrayOop.inline.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/javaCalls.hpp" #include "runtime/javaThread.hpp" +#include "runtime/jniHandles.inline.hpp" #include "runtime/os.hpp" #include "utilities/exceptions.hpp" @@ -47,6 +53,10 @@ static Symbol* bytes_for_eager_instrumentation_sym = nullptr; static Symbol* bytes_for_eager_instrumentation_sig_sym = nullptr; static Symbol* unhide_internal_types_sym = nullptr; static Symbol* unhide_internal_types_sig_sym = nullptr; +static Symbol* on_method_trace_sym = nullptr; +static Symbol* on_method_trace_sig_sym = nullptr; +static Symbol* publish_method_timers_for_klass_sym = nullptr; +static Symbol* publish_method_timers_for_klass_sig_sym = nullptr; static bool initialize(TRAPS) { static bool initialized = false; @@ -59,7 +69,11 @@ static bool initialize(TRAPS) { bytes_for_eager_instrumentation_sig_sym = SymbolTable::new_permanent_symbol("(JZZLjava/lang/Class;[B)[B"); unhide_internal_types_sym = SymbolTable::new_permanent_symbol("unhideInternalTypes"); unhide_internal_types_sig_sym = SymbolTable::new_permanent_symbol("()V"); - initialized = unhide_internal_types_sig_sym != nullptr; + on_method_trace_sym = SymbolTable::new_permanent_symbol("onMethodTrace"); + on_method_trace_sig_sym = SymbolTable::new_permanent_symbol("(Ljava/lang/Module;Ljava/lang/ClassLoader;Ljava/lang/String;[B[J[Ljava/lang/String;[Ljava/lang/String;[I)[B"); + publish_method_timers_for_klass_sym = SymbolTable::new_permanent_symbol("publishMethodTimersForClass"); + publish_method_timers_for_klass_sig_sym = SymbolTable::new_permanent_symbol("(J)V"); + initialized = publish_method_timers_for_klass_sig_sym != nullptr; } return initialized; } @@ -207,3 +221,103 @@ bool JfrUpcalls::unhide_internal_types(TRAPS) { } return true; } + +// Caller needs ResourceMark +ClassFileStream* JfrUpcalls::on_method_trace(InstanceKlass* ik, const ClassFileStream* stream, GrowableArray* methods, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(stream != nullptr, "invariant"); + assert(methods != nullptr, "invariant"); + assert(methods->is_nonempty(), "invariant"); + initialize(THREAD); + Klass* klass = SystemDictionary::resolve_or_fail(jvm_upcalls_class_sym, true, CHECK_NULL); + assert(klass != nullptr, "invariant"); + + HandleMark hm(THREAD); + + ModuleEntry* module_entry = ik->module(); + oop module = nullptr; + if (module_entry != nullptr) { + module = module_entry->module(); + } + instanceHandle module_handle(THREAD, (instanceOop)module); + + // ClassLoader + oop class_loader = ik->class_loader(); + instanceHandle class_loader_handle(THREAD, (instanceOop)class_loader); + + // String class name + Handle class_name_h = java_lang_String::create_from_symbol(ik->name(), CHECK_NULL); + + // new byte[] + int size = stream->length(); + typeArrayOop bytecode_array = oopFactory::new_byteArray(size, CHECK_NULL); + typeArrayHandle h_bytecode_array(THREAD, bytecode_array); + + // Copy ClassFileStream bytes to byte[] + const jbyte* src = reinterpret_cast(stream->buffer()); + ArrayAccess<>::arraycopy_from_native(src, bytecode_array, typeArrayOopDesc::element_offset(0), size); + + int method_count = methods->length(); + + // new long[method_count] + typeArrayOop id_array = oopFactory::new_longArray(method_count, CHECK_NULL); + typeArrayHandle h_id_array(THREAD, id_array); + + // new String[method_count] + objArrayOop name_array = oopFactory::new_objArray(vmClasses::String_klass(), method_count, CHECK_NULL); + objArrayHandle h_name_array(THREAD, name_array); + + // new String[method_count] + objArrayOop signature_array = oopFactory::new_objArray(vmClasses::String_klass(), method_count, CHECK_NULL); + objArrayHandle h_signature_array(THREAD, signature_array); + + // new int[method_count] + typeArrayOop modification_array = oopFactory::new_intArray(method_count, CHECK_NULL); + typeArrayHandle h_modification_array(THREAD, modification_array); + + // Fill in arrays + for (int i = 0; i < method_count; i++) { + JfrTracedMethod method = methods->at(i); + h_id_array->long_at_put(i, method.id()); + Handle name = java_lang_String::create_from_symbol(method.name(), CHECK_NULL); + h_name_array->obj_at_put(i, name()); + Handle signature = java_lang_String::create_from_symbol(method.signature(), CHECK_NULL); + h_signature_array->obj_at_put(i, signature()); + h_modification_array->int_at_put(i, method.modification()); + } + + // Call JVMUpcalls::onMethodTrace + JavaCallArguments args; + JavaValue result(T_ARRAY); + args.push_oop(module_handle); + args.push_oop(class_loader_handle); + args.push_oop(class_name_h); + args.push_oop(h_bytecode_array); + args.push_oop(h_id_array); + args.push_oop(h_name_array); + args.push_oop(h_signature_array); + args.push_oop(h_modification_array); + JavaCalls::call_static(&result, klass, on_method_trace_sym, on_method_trace_sig_sym, &args, CHECK_NULL); + + oop return_object = result.get_oop(); + if (return_object != nullptr) { + assert(return_object->is_typeArray(), "invariant"); + assert(TypeArrayKlass::cast(return_object->klass())->element_type() == T_BYTE, "invariant"); + typeArrayOop byte_array = typeArrayOop(return_object); + int length = byte_array->length(); + u1* buffer = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, u1, length); + ArrayAccess<>::arraycopy_to_native<>(byte_array, typeArrayOopDesc::element_offset(0), buffer, length); + return new ClassFileStream(buffer, length, stream->source(), stream->from_boot_loader_modules_image()); + } + return nullptr; +} + +void JfrUpcalls::publish_method_timers_for_klass(traceid klass_id, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + Klass* const klass = SystemDictionary::resolve_or_fail(jvm_upcalls_class_sym, true, CHECK); + assert(klass != nullptr, "invariant"); + JavaCallArguments args; + JavaValue result(T_VOID); + args.push_long(static_cast(klass_id)); + JavaCalls::call_static(&result, klass, publish_method_timers_for_klass_sym, publish_method_timers_for_klass_sig_sym, &args, CHECK); +} diff --git a/src/hotspot/share/jfr/jni/jfrUpcalls.hpp b/src/hotspot/share/jfr/jni/jfrUpcalls.hpp index 54196eadb9d9a..bb06f593deb53 100644 --- a/src/hotspot/share/jfr/jni/jfrUpcalls.hpp +++ b/src/hotspot/share/jfr/jni/jfrUpcalls.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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 @@ -26,10 +26,15 @@ #define SHARE_JFR_JNI_JFRUPCALLS_HPP #include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrTypes.hpp" #include "jni.h" #include "utilities/exceptions.hpp" +class ClassFileStream; +class InstanceKlass; +class JfrTracedMethod; class JavaThread; +template class GrowableArray; // // Upcalls to Java for instrumentation purposes. @@ -55,6 +60,13 @@ class JfrUpcalls : AllStatic { unsigned char** new_class_data, TRAPS); + // Caller needs ResourceMark + static ClassFileStream* on_method_trace(InstanceKlass* ik, const ClassFileStream* stream, + GrowableArray* methods, + TRAPS); + + static void publish_method_timers_for_klass(traceid klass_id, TRAPS); + static bool unhide_internal_types(TRAPS); }; diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 47f78a88abd0d..9c04ec3dca1b5 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1292,6 +1292,16 @@ + + + + + + + + + + diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 91a696c6aa56b..00d41a10bf0ed 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -321,6 +321,10 @@ TRACE_REQUEST_FUNC(JavaAgent) {} TRACE_REQUEST_FUNC(NativeAgent) {} #endif // INCLUDE_JVMTI +TRACE_REQUEST_FUNC(MethodTiming) { + // Emitted in Java, but defined in native to have Method type field. +} + TRACE_REQUEST_FUNC(ThreadContextSwitchRate) { double rate = 0.0; int ret_val = OS_ERR; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp index a1b10e8cfb873..a8248b7714e82 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp @@ -35,6 +35,8 @@ #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.inline.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/support/jfrKlassUnloading.hpp" +#include "jfr/support/methodtracer/jfrInstrumentedClass.hpp" +#include "jfr/support/methodtracer/jfrMethodTracer.hpp" #include "jfr/utilities/jfrHashtable.hpp" #include "jfr/utilities/jfrTypes.hpp" #include "jfr/writers/jfrTypeWriterHost.hpp" @@ -480,6 +482,54 @@ static void do_primitives() { write_primitive(_writer, nullptr); // void.class } +static void do_method_tracer_klasses() { + assert(JfrTraceIdEpoch::has_method_tracer_changed_tag_state(), "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(_subsystem_callback != nullptr, "invariant"); + GrowableArray* const instrumented = JfrMethodTracer::instrumented_classes(); + assert(instrumented != nullptr, "invariant"); + assert(instrumented->length() > 0, "invariant"); + for (int i = 0; i < instrumented->length(); ++i) { + JfrInstrumentedClass& jic = instrumented->at(i); + if (jic.unloaded()) { + continue; + } + if (JfrKlassUnloading::is_unloaded(jic.trace_id(), previous_epoch())) { + jic.set_unloaded(true); + continue; + } + assert(jic.trace_id() == JfrTraceId::load_raw(jic.instance_klass()), "invariant"); + assert(JfrTraceId::has_sticky_bit(jic.instance_klass()), "invariant"); + if (current_epoch()) { + JfrTraceId::load(jic.instance_klass()); // enqueue klass for this epoch + } else { + _subsystem_callback->do_artifact(jic.instance_klass()); // process directly + } + } + JfrTraceIdEpoch::reset_method_tracer_tag_state(); +} + +static void clear_method_tracer_klasses() { + assert_locked_or_safepoint (ClassLoaderDataGraph_lock); + assert(previous_epoch(), "invariant"); + GrowableArray* const instrumented = JfrMethodTracer::instrumented_classes(); + assert(instrumented != nullptr, "invariant"); + const int length = instrumented->length(); + bool trim = false; + for (int i = 0; i < length; ++i) { + JfrInstrumentedClass& jic = instrumented->at(i); + if (jic.unloaded()) { + trim = true; + continue; + } + if (JfrKlassUnloading::is_unloaded(jic.trace_id(), true)) { + jic.set_unloaded(true); + trim = true; + } + } + JfrMethodTracer::trim_instrumented_classes(trim); +} + static void do_unloading_klass(Klass* klass) { assert(klass != nullptr, "invariant"); assert(_subsystem_callback != nullptr, "invariant"); @@ -487,6 +537,9 @@ static void do_unloading_klass(Klass* klass) { return; } if (JfrKlassUnloading::on_unload(klass)) { + if (JfrTraceId::has_sticky_bit(klass)) { + JfrMethodTracer::add_to_unloaded_set(klass); + } _subsystem_callback->do_artifact(klass); } } @@ -504,9 +557,13 @@ static void do_klasses() { return; } if (is_initial_typeset_for_chunk()) { - // Only write the primitive classes once per chunk. + // Only write the primitive and method tracer classes once per chunk. do_primitives(); } + if (JfrTraceIdEpoch::has_method_tracer_changed_tag_state()) { + do_method_tracer_klasses(); + } + JfrTraceIdLoadBarrier::do_klasses(&do_klass, previous_epoch()); } @@ -1206,6 +1263,7 @@ static size_t teardown() { const size_t total_count = _artifacts->total_count(); if (previous_epoch()) { clear_klasses_and_methods(); + clear_method_tracer_klasses(); JfrKlassUnloading::clear(); _artifacts->increment_checkpoint_id(); _initial_type_set = true; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.hpp index d9211e8cb8c23..378d1af23cc67 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -86,6 +86,7 @@ class JfrTraceId : public AllStatic { // through load barrier static traceid load(const Klass* klass); + static traceid load_previous_epoch(const Klass* klass); static traceid load(jclass jc, bool raw = false); static traceid load(const Method* method); static traceid load(const Klass* klass, const Method* method); @@ -102,6 +103,7 @@ class JfrTraceId : public AllStatic { static traceid load_raw(const Klass* klass); static traceid load_raw(jclass jc); static traceid load_raw(const Method* method); + static traceid load_raw(const Klass* holder, const Method* method); static traceid load_raw(const ModuleEntry* module); static traceid load_raw(const PackageEntry* package); static traceid load_raw(const ClassLoaderData* cld); @@ -136,6 +138,18 @@ class JfrTraceId : public AllStatic { static bool is_event_host(const jclass jc); static void tag_as_event_host(const Klass* k); static void tag_as_event_host(const jclass jc); + + // Sticky bits and timing bits + static bool has_sticky_bit(const Klass* k); + static bool has_sticky_bit(const Method* method); + static void set_sticky_bit(const Klass* k); + static void set_sticky_bit(const Method* method); + static void clear_sticky_bit(const Klass* k); + static void clear_sticky_bit(const Method* method); + static bool has_timing_bit(const Klass* k); + static void set_timing_bit(const Klass* k); + static void clear_timing_bit(const Klass* k); + }; #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEID_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp index aa99a8383eb56..2b2c435d9863e 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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,6 +34,7 @@ #include "jfr/support/jfrKlassExtension.hpp" #include "oops/klass.hpp" #include "runtime/javaThread.inline.hpp" +#include "runtime/mutexLocker.hpp" #include "utilities/debug.hpp" inline traceid JfrTraceId::load(const Klass* klass) { @@ -77,7 +78,11 @@ inline traceid JfrTraceId::load_leakp(const Klass* klass, const Method* method) } inline traceid JfrTraceId::load_leakp_previous_epoch(const Klass* klass, const Method* method) { - return JfrTraceIdLoadBarrier::load_leakp_previuos_epoch(klass, method); + return JfrTraceIdLoadBarrier::load_leakp_previous_epoch(klass, method); +} + +inline traceid JfrTraceId::load_previous_epoch(const Klass* klass) { + return JfrTraceIdLoadBarrier::load_previous_epoch(klass); } template @@ -90,8 +95,15 @@ inline traceid JfrTraceId::load_raw(const Klass* klass) { return raw_load(klass); } +inline traceid JfrTraceId::load_raw(const Klass* holder, const Method* method) { + assert(holder != nullptr, "invariant"); + assert(method != nullptr, "invariant"); + assert(method->method_holder() == holder, "invariant"); + return METHOD_ID(holder, method); +} + inline traceid JfrTraceId::load_raw(const Method* method) { - return (METHOD_ID(method->method_holder(), method)); + return load_raw(method->method_holder(), method); } inline traceid JfrTraceId::load_raw(const ModuleEntry* module) { @@ -156,4 +168,67 @@ inline void JfrTraceId::tag_as_event_host(const Klass* k) { assert(IS_EVENT_HOST_KLASS(k), "invariant"); } +inline bool JfrTraceId::has_sticky_bit(const Klass* k) { + assert(k != nullptr, "invariant"); + return HAS_STICKY_BIT(k); +} + +inline void JfrTraceId::set_sticky_bit(const Klass* k) { + assert(k != nullptr, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(!has_sticky_bit(k), "invariant"); + SET_STICKY_BIT(k); + assert(has_sticky_bit(k), "invariant"); +} + +inline void JfrTraceId::clear_sticky_bit(const Klass* k) { + assert(k != nullptr, "invarriant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(JfrTraceId::has_sticky_bit(k), "invariant"); + CLEAR_STICKY_BIT(k); + assert(!JfrTraceId::has_sticky_bit(k), "invariant"); +} + +inline bool JfrTraceId::has_sticky_bit(const Method* method) { + assert(method != nullptr, "invariant"); + return METHOD_HAS_STICKY_BIT(method); +} + +inline void JfrTraceId::set_sticky_bit(const Method* method) { + assert(method != nullptr, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(!has_sticky_bit(method), "invariant"); + SET_METHOD_STICKY_BIT(method); + assert(has_sticky_bit(method), "invariant"); +} + +inline void JfrTraceId::clear_sticky_bit(const Method* method) { + assert(method != nullptr, "invarriant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(JfrTraceId::has_sticky_bit(method), "invariant"); + CLEAR_STICKY_BIT_METHOD(method); + assert(!JfrTraceId::has_sticky_bit(method), "invariant"); +} + +inline bool JfrTraceId::has_timing_bit(const Klass* k) { + assert(k != nullptr, "invariant"); + return HAS_TIMING_BIT(k); +} + +inline void JfrTraceId::set_timing_bit(const Klass* k) { + assert(k != nullptr, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(!has_timing_bit(k), "invariant"); + SET_TIMING_BIT(k); + assert(has_timing_bit(k), "invariant"); +} + +inline void JfrTraceId::clear_timing_bit(const Klass* k) { + assert(k != nullptr, "invarriant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(JfrTraceId::has_timing_bit(k), "invariant"); + CLEAR_TIMING_BIT(k); + assert(!JfrTraceId::has_timing_bit(k), "invariant"); +} + #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEID_INLINE_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp index 743923d674cdf..8c2b8cdd0ec11 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp @@ -24,6 +24,8 @@ #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" #include "jfr/support/jfrThreadId.inline.hpp" +#include "runtime/atomic.hpp" +#include "runtime/mutex.hpp" #include "runtime/safepoint.hpp" /* @@ -35,6 +37,7 @@ */ u2 JfrTraceIdEpoch::_generation = 0; JfrSignal JfrTraceIdEpoch::_tag_state; +bool JfrTraceIdEpoch::_method_tracer_state = false; bool JfrTraceIdEpoch::_epoch_state = false; bool JfrTraceIdEpoch::_synchronizing = false; @@ -59,3 +62,21 @@ void JfrTraceIdEpoch::end_epoch_shift() { OrderAccess::storestore(); _synchronizing = false; } + +bool JfrTraceIdEpoch::is_synchronizing() { + return Atomic::load_acquire(&_synchronizing); +} + +void JfrTraceIdEpoch::set_method_tracer_tag_state() { + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + Atomic::release_store(&_method_tracer_state, true); +} + +void JfrTraceIdEpoch::reset_method_tracer_tag_state() { + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + Atomic::release_store(&_method_tracer_state, false); +} + +bool JfrTraceIdEpoch::has_method_tracer_changed_tag_state() { + return Atomic::load_acquire(&_method_tracer_state); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp index b74a07c23c2e0..10ea9643971cd 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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 @@ -28,7 +28,6 @@ #include "jfr/utilities/jfrSignal.hpp" #include "jfr/utilities/jfrTypes.hpp" #include "memory/allStatic.hpp" -#include "runtime/atomic.hpp" #define BIT 1 #define METHOD_BIT (BIT << 2) @@ -57,6 +56,7 @@ class JfrTraceIdEpoch : AllStatic { private: static u2 _generation; static JfrSignal _tag_state; + static bool _method_tracer_state; static bool _epoch_state; static bool _synchronizing; @@ -92,9 +92,7 @@ class JfrTraceIdEpoch : AllStatic { return _epoch_state ? (u1)0 : (u1)1; } - static bool is_synchronizing() { - return Atomic::load_acquire(&_synchronizing); - } + static bool is_synchronizing(); static uint8_t this_epoch_bit() { return _epoch_state ? EPOCH_1_BIT : EPOCH_0_BIT; @@ -121,7 +119,7 @@ class JfrTraceIdEpoch : AllStatic { } static bool has_changed_tag_state() { - return _tag_state.is_signaled_with_reset(); + return _tag_state.is_signaled_with_reset() || has_method_tracer_changed_tag_state(); } static bool has_changed_tag_state_no_reset() { @@ -135,6 +133,10 @@ class JfrTraceIdEpoch : AllStatic { static address signal_address() { return _tag_state.signaled_address(); } + + static void set_method_tracer_tag_state(); + static void reset_method_tracer_tag_state(); + static bool has_method_tracer_changed_tag_state(); }; #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDEPOCH_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.hpp index ff984d05c19fa..20a12ee75e2be 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, 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 @@ -72,6 +72,7 @@ class JfrTraceIdLoadBarrier : AllStatic { friend class JfrIntrinsicSupport; friend class JfrStackTrace; friend class JfrThreadSampler; + friend class JfrTraceTagging; private: static bool initialize(); static void clear(); @@ -83,16 +84,17 @@ class JfrTraceIdLoadBarrier : AllStatic { public: static traceid load(const ClassLoaderData* cld); static traceid load(const Klass* klass); + static traceid load_previous_epoch(const Klass* klass); static traceid load(const Klass* klass, const Method* method); static traceid load(const Method* method); static traceid load(const ModuleEntry* module); static traceid load(const PackageEntry* package); static traceid load_leakp(const Klass* klass); // leak profiler static traceid load_leakp(const Klass* klass, const Method* method); // leak profiler - static traceid load_leakp_previuos_epoch(const Klass* klass, const Method* method); // leak profiler - static void do_klasses(void f(Klass*), bool previous_epoch = false); + static traceid load_leakp_previous_epoch(const Klass* klass, const Method* method); // leak profiler static traceid load_no_enqueue(const Klass* klass, const Method* method); static traceid load_no_enqueue(const Method* method); + static void do_klasses(void f(Klass*), bool previous_epoch = false); }; #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDLOADBARRIER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.inline.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.inline.hpp index 5f986121ea020..ac1c11af2f8f0 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.inline.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdLoadBarrier.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, 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 @@ -172,15 +172,12 @@ inline traceid JfrTraceIdLoadBarrier::load_leakp(const Klass* klass, const Metho return (METHOD_ID(klass, method)); } -inline traceid JfrTraceIdLoadBarrier::load_leakp_previuos_epoch(const Klass* klass, const Method* method) { +inline traceid JfrTraceIdLoadBarrier::load_leakp_previous_epoch(const Klass* klass, const Method* method) { assert(klass != nullptr, "invariant"); assert(method != nullptr, "invariant"); assert(klass == method->method_holder(), "invariant"); assert(METHOD_AND_CLASS_USED_PREVIOUS_EPOCH(klass), "invariant"); if (METHOD_FLAG_NOT_USED_PREVIOUS_EPOCH(method)) { - // the method is already logically tagged, just like the klass, - // but because of redefinition, the latest Method* - // representation might not have a reified tag. SET_TRANSIENT(method); assert(METHOD_FLAG_USED_PREVIOUS_EPOCH(method), "invariant"); } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdMacros.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdMacros.hpp index 392e36f2c3c22..27cf66cc1fe76 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdMacros.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdMacros.hpp @@ -51,14 +51,17 @@ #define EPOCH_CLEARED_BITS (EPOCH_1_CLEARED_BIT | EPOCH_0_CLEARED_BIT) #define LEAKP_META_BIT (BIT << 2) #define LEAKP_BIT (LEAKP_META_BIT << META_SHIFT) +#define TIMING_BIT LEAKP_BIT // Alias #define TRANSIENT_META_BIT (BIT << 3) #define TRANSIENT_BIT (TRANSIENT_META_BIT << META_SHIFT) #define SERIALIZED_META_BIT (BIT << 4) #define SERIALIZED_BIT (SERIALIZED_META_BIT << META_SHIFT) +#define STICKY_META_BIT (BIT << 5) +#define STICKY_BIT (STICKY_META_BIT << META_SHIFT) #define BLESSED_METHOD_BIT (JDK_JFR_EVENT_SUBKLASS) #define TRACE_ID_SHIFT 16 #define METHOD_ID_NUM_MASK ((1 << TRACE_ID_SHIFT) - 1) -#define META_BITS (SERIALIZED_BIT | TRANSIENT_BIT | LEAKP_BIT | EPOCH_1_CLEARED_BIT | EPOCH_0_CLEARED_BIT) +#define META_BITS (STICKY_BIT | SERIALIZED_BIT | TRANSIENT_BIT | LEAKP_BIT | EPOCH_1_CLEARED_BIT | EPOCH_0_CLEARED_BIT) #define EVENT_BITS (EVENT_HOST_KLASS | JDK_JFR_EVENT_KLASS | JDK_JFR_EVENT_SUBKLASS) #define TAG_BITS (EPOCH_1_METHOD_BIT | EPOCH_0_METHOD_BIT | EPOCH_1_BIT | EPOCH_0_BIT) #define ALL_BITS (META_BITS | EVENT_BITS | TAG_BITS) @@ -90,20 +93,20 @@ #define METHOD_META_MASK_CLEAR(method, mask) (JfrTraceIdBits::meta_mask_store(mask, method)) // predicates -#define USED_THIS_EPOCH(ptr) (TRACE_ID_PREDICATE(ptr, (TRANSIENT_BIT | THIS_EPOCH_BIT))) +#define USED_THIS_EPOCH(ptr) (TRACE_ID_PREDICATE(ptr, (STICKY_BIT | TRANSIENT_BIT | THIS_EPOCH_BIT))) #define NOT_USED_THIS_EPOCH(ptr) (!(USED_THIS_EPOCH(ptr))) -#define USED_PREVIOUS_EPOCH(ptr) (TRACE_ID_PREDICATE(ptr, (TRANSIENT_BIT | PREVIOUS_EPOCH_BIT))) -#define USED_ANY_EPOCH(ptr) (TRACE_ID_PREDICATE(ptr, (TRANSIENT_BIT | EPOCH_1_BIT | EPOCH_0_BIT))) -#define METHOD_USED_THIS_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (THIS_EPOCH_METHOD_BIT))) +#define USED_PREVIOUS_EPOCH(ptr) (TRACE_ID_PREDICATE(ptr, (STICKY_BIT | TRANSIENT_BIT | PREVIOUS_EPOCH_BIT))) +#define USED_ANY_EPOCH(ptr) (TRACE_ID_PREDICATE(ptr, (STICKY_BIT | TRANSIENT_BIT | EPOCH_1_BIT | EPOCH_0_BIT))) +#define METHOD_USED_THIS_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (STICKY_BIT | THIS_EPOCH_METHOD_BIT))) #define METHOD_NOT_USED_THIS_EPOCH(kls) (!(METHOD_USED_THIS_EPOCH(kls))) -#define METHOD_USED_PREVIOUS_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (PREVIOUS_EPOCH_METHOD_BIT))) +#define METHOD_USED_PREVIOUS_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (STICKY_BIT | PREVIOUS_EPOCH_METHOD_BIT))) #define METHOD_USED_ANY_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (EPOCH_1_METHOD_BIT | EPOCH_0_METHOD_BIT))) #define METHOD_AND_CLASS_USED_THIS_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (THIS_EPOCH_METHOD_AND_CLASS_BITS))) #define METHOD_AND_CLASS_USED_PREVIOUS_EPOCH(kls) (TRACE_ID_PREDICATE(kls, (PREVIOUS_EPOCH_METHOD_AND_CLASS_BITS))) #define METHOD_AND_CLASS_USED_ANY_EPOCH(kls) (METHOD_USED_ANY_EPOCH(kls) && USED_ANY_EPOCH(kls)) -#define METHOD_FLAG_USED_THIS_EPOCH(method) (METHOD_FLAG_PREDICATE(method, (TRANSIENT_BIT | THIS_EPOCH_BIT))) +#define METHOD_FLAG_USED_THIS_EPOCH(method) (METHOD_FLAG_PREDICATE(method, (STICKY_BIT | TRANSIENT_BIT | THIS_EPOCH_BIT))) #define METHOD_FLAG_NOT_USED_THIS_EPOCH(method) (!(METHOD_FLAG_USED_THIS_EPOCH(method))) -#define METHOD_FLAG_USED_PREVIOUS_EPOCH(method) (METHOD_FLAG_PREDICATE(method, (TRANSIENT_BIT | PREVIOUS_EPOCH_BIT))) +#define METHOD_FLAG_USED_PREVIOUS_EPOCH(method) (METHOD_FLAG_PREDICATE(method, (STICKY_BIT | TRANSIENT_BIT | PREVIOUS_EPOCH_BIT))) #define METHOD_FLAG_USED_PREVIOUS_EPOCH_BIT(method) (METHOD_FLAG_PREDICATE(method, (PREVIOUS_EPOCH_BIT))) #define METHOD_FLAG_NOT_USED_PREVIOUS_EPOCH(method) (!(METHOD_FLAG_USED_PREVIOUS_EPOCH(method))) #define IS_METHOD_BLESSED(method) (METHOD_FLAG_PREDICATE(method, BLESSED_METHOD_BIT)) @@ -132,16 +135,22 @@ // meta #define META_MASK (~(SERIALIZED_META_BIT | TRANSIENT_META_BIT | LEAKP_META_BIT)) #define SET_LEAKP(ptr) (TRACE_ID_META_TAG(ptr, LEAKP_META_BIT)) +#define SET_TIMING_BIT(ptr) (SET_LEAKP(ptr)) #define IS_LEAKP(ptr) (TRACE_ID_PREDICATE(ptr, LEAKP_BIT)) +#define HAS_TIMING_BIT(ptr) (IS_LEAKP(ptr)) #define IS_NOT_LEAKP(ptr) (!(IS_LEAKP(ptr))) +#define HAS_NOT_TIMING_BIT(ptr) (IS_NOT_LEAKP(ptr)) #define SET_TRANSIENT(ptr) (TRACE_ID_META_TAG(ptr, TRANSIENT_META_BIT)) #define IS_TRANSIENT(ptr) (TRACE_ID_PREDICATE(ptr, TRANSIENT_BIT)) #define IS_NOT_TRANSIENT(ptr) (!(IS_TRANSIENT(ptr))) +#define IS_SERIALIZED(ptr) (TRACE_ID_PREDICATE(ptr, SERIALIZED_BIT)) +#define IS_NOT_SERIALIZED(ptr) (!(IS_SERIALIZED(ptr))) #define SET_SERIALIZED(ptr) (TRACE_ID_META_TAG(ptr, SERIALIZED_META_BIT)) +#define HAS_STICKY_BIT(ptr) (TRACE_ID_PREDICATE(ptr, STICKY_BIT)) +#define HAS_NOT_STICKY_BIT(ptr) (!(HAS_STICKY_BIT(ptr))) +#define SET_STICKY_BIT(ptr) (TRACE_ID_META_TAG(ptr, STICKY_META_BIT)) #define IS_THIS_EPOCH_CLEARED_BIT_SET(ptr) (TRACE_ID_PREDICATE(ptr, (THIS_EPOCH_BIT << META_SHIFT))) #define IS_PREVIOUS_EPOCH_CLEARED_BIT_SET(ptr) (TRACE_ID_PREDICATE(ptr, (PREVIOUS_EPOCH_BIT << META_SHIFT))) -#define IS_SERIALIZED(ptr) (TRACE_ID_PREDICATE(ptr, SERIALIZED_BIT)) -#define IS_NOT_SERIALIZED(ptr) (!(IS_SERIALIZED(ptr))) #define SHOULD_TAG(ptr) (NOT_USED_THIS_EPOCH(ptr)) #define SHOULD_TAG_KLASS_METHOD(ptr) (METHOD_NOT_USED_THIS_EPOCH(ptr)) #define CLEAR_SERIALIZED(ptr) (TRACE_ID_META_MASK_CLEAR(ptr, META_MASK)) @@ -151,6 +160,9 @@ #define SET_METHOD_SERIALIZED(method) (METHOD_META_TAG(method, SERIALIZED_META_BIT)) #define METHOD_IS_SERIALIZED(method) (METHOD_FLAG_PREDICATE(method, SERIALIZED_BIT)) #define METHOD_IS_NOT_SERIALIZED(method) (!(METHOD_IS_SERIALIZED(method))) +#define SET_METHOD_STICKY_BIT(method) (METHOD_META_TAG(method, STICKY_META_BIT)) +#define METHOD_HAS_STICKY_BIT(method) (METHOD_FLAG_PREDICATE(method, STICKY_BIT)) +#define METHOD_HAS_NOT_STICKY_BIT(method) (!(METHOD_HAS_STICKY_BIT(method))) #define SET_METHOD_LEAKP(method) (METHOD_META_TAG(method, LEAKP_META_BIT)) #define METHOD_IS_LEAKP(method) (METHOD_FLAG_PREDICATE(method, LEAKP_BIT)) #define METHOD_IS_NOT_LEAKP(method) (!(METHOD_IS_LEAKP(method))) @@ -160,7 +172,10 @@ #define CLEAR_SERIALIZED_METHOD(method) (METHOD_META_MASK_CLEAR(method, META_MASK)) #define SET_PREVIOUS_EPOCH_METHOD_CLEARED_BIT(ptr) (METHOD_META_TAG(ptr, PREVIOUS_EPOCH_BIT)) #define CLEAR_LEAKP(ptr) (TRACE_ID_META_MASK_CLEAR(ptr, (~(LEAKP_META_BIT)))) +#define CLEAR_TIMING_BIT(ptr) (CLEAR_LEAKP(ptr)) #define CLEAR_LEAKP_METHOD(method) (METHOD_META_MASK_CLEAR(method, (~(LEAKP_META_BIT)))) +#define CLEAR_STICKY_BIT(ptr) (TRACE_ID_META_MASK_CLEAR(ptr, (~(STICKY_META_BIT)))) +#define CLEAR_STICKY_BIT_METHOD(method) (METHOD_META_MASK_CLEAR(method, (~(STICKY_META_BIT)))) #define CLEAR_THIS_EPOCH_CLEARED_BIT(ptr) (TRACE_ID_META_MASK_CLEAR(ptr,(~(THIS_EPOCH_BIT)))) #define CLEAR_THIS_EPOCH_METHOD_CLEARED_BIT(ptr) (METHOD_META_MASK_CLEAR(ptr,(~(THIS_EPOCH_BIT)))) #define IS_THIS_EPOCH_METHOD_CLEARED(ptr) (METHOD_FLAG_PREDICATE(method, THIS_EPOCH_BIT)) diff --git a/src/hotspot/share/jfr/support/jfrAnnotationElementIterator.cpp b/src/hotspot/share/jfr/support/jfrAnnotationElementIterator.cpp new file mode 100644 index 0000000000000..e318891242e07 --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrAnnotationElementIterator.cpp @@ -0,0 +1,109 @@ +/* +* Copyright (c) 2016, 2025, 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 "jfr/support/jfrAnnotationElementIterator.hpp" +#include "jfr/support/jfrAnnotationIterator.hpp" +#include "jfr/utilities/jfrBigEndian.hpp" +#include "oops/constantPool.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/symbol.hpp" + +/* + Annotation layout. + + enum { // initial annotation layout + atype_off = 0, // utf8 such as 'Ljava/lang/annotation/Retention;' + count_off = 2, // u2 such as 1 (one value) + member_off = 4, // utf8 such as 'value' + tag_off = 6, // u1 such as 'c' (type) or 'e' (enum) + e_tag_val = 'e', + e_type_off = 7, // utf8 such as 'Ljava/lang/annotation/RetentionPolicy;' + e_con_off = 9, // utf8 payload, such as 'SOURCE', 'CLASS', 'RUNTIME' + e_size = 11, // end of 'e' annotation + c_tag_val = 'c', // payload is type + c_con_off = 7, // utf8 payload, such as 'I' + c_size = 9, // end of 'c' annotation + s_tag_val = 's', // payload is String + s_con_off = 7, // utf8 payload, such as 'Ljava/lang/String;' + s_size = 9, + min_size = 6 // smallest possible size (zero members) + }; + + See JVMS - 4.7.16. The RuntimeVisibleAnnotations Attribute + +*/ + +static constexpr const int number_of_elements_offset = 2; +static constexpr const int element_name_offset = number_of_elements_offset + 2; +static constexpr const int element_name_size = 2; +static constexpr const int value_type_relative_offset = 2; +static constexpr const int value_relative_offset = value_type_relative_offset + 1; + +JfrAnnotationElementIterator::JfrAnnotationElementIterator(const InstanceKlass* ik, address buffer, int limit) : + _ik(ik), + _buffer(buffer), + _limit(limit), + _current(element_name_offset), + _next(element_name_offset) { + assert(_buffer != nullptr, "invariant"); + assert(_next == element_name_offset, "invariant"); assert(_current == element_name_offset, "invariant"); +} + +int JfrAnnotationElementIterator::value_index() const { + return JfrBigEndian::read(_buffer + _current + value_relative_offset); +} + +bool JfrAnnotationElementIterator::has_next() const { + return _next < _limit; +} + +void JfrAnnotationElementIterator::move_to_next() const { + assert(has_next(), "invariant"); + _current = _next; + if (_next < _limit) { + _next = JfrAnnotationIterator::skip_annotation_value(_buffer, _limit, _next + element_name_size); + } + assert(_next <= _limit, "invariant"); assert(_current <= _limit, "invariant"); +} + +int JfrAnnotationElementIterator::number_of_elements() const { + return JfrBigEndian::read(_buffer + number_of_elements_offset); +} + +const Symbol* JfrAnnotationElementIterator::name() const { + assert(_current < _next, "invariant"); + return _ik->constants()->symbol_at(JfrBigEndian::read(_buffer + _current)); +} + +char JfrAnnotationElementIterator::value_type() const { + return JfrBigEndian::read(_buffer + _current + value_type_relative_offset); +} + +jint JfrAnnotationElementIterator::read_int() const { + return _ik->constants()->int_at(value_index()); +} + +bool JfrAnnotationElementIterator::read_bool() const { + return read_int() != 0; +} diff --git a/src/hotspot/share/jfr/support/jfrAnnotationElementIterator.hpp b/src/hotspot/share/jfr/support/jfrAnnotationElementIterator.hpp new file mode 100644 index 0000000000000..9d8b35080fbba --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrAnnotationElementIterator.hpp @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2016, 2025, 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_SUPPORT_JFRANNOTATIONELEMENTITERATOR_HPP +#define SHARE_JFR_SUPPORT_JFRANNOTATIONELEMENTITERATOR_HPP + +#include "jni.h" +#include "utilities/globalDefinitions.hpp" +#include "memory/allocation.hpp" + +class InstanceKlass; +class Symbol; + +class JfrAnnotationElementIterator : public StackObj { + private: + const InstanceKlass* _ik; + const address _buffer; + const int _limit; // length of annotation + mutable int _current; // element + mutable int _next; // element + int value_index() const; + + public: + JfrAnnotationElementIterator(const InstanceKlass* ik, address buffer, int limit); + bool has_next() const; + void move_to_next() const; + int number_of_elements() const; + bool read_bool() const; + jint read_int() const; + char value_type() const; + const Symbol* name() const; +}; + +#endif // SHARE_JFR_SUPPORT_JFRANNOTATIONELEMENTITERATOR_HPP diff --git a/src/hotspot/share/jfr/support/jfrAnnotationIterator.cpp b/src/hotspot/share/jfr/support/jfrAnnotationIterator.cpp new file mode 100644 index 0000000000000..a5fc133ba1612 --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrAnnotationIterator.cpp @@ -0,0 +1,138 @@ +/* +* Copyright (c) 2016, 2025, 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 "jfr/support/jfrAnnotationIterator.hpp" +#include "jfr/utilities/jfrBigEndian.hpp" +#include "oops/constantPool.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/symbol.hpp" + +JfrAnnotationIterator::JfrAnnotationIterator(const InstanceKlass* ik, AnnotationArray* ar) : + _ik(ik), + _limit(ar != nullptr ? ar->length() : 0), + _buffer(_limit > 2 ? ar->adr_at(2) : nullptr), + _current(0), + _next(0) { + if (_limit >= 2) { + _limit -= 2; // subtract sizeof(u2) number of annotations field + } +} + +bool JfrAnnotationIterator::has_next() const { + return _next < _limit; +} + +void JfrAnnotationIterator::move_to_next() const { + assert(has_next(), "invariant"); + _current = _next; + if (_next < _limit) { + _next = next_annotation_index(_buffer, _limit, _next); + } + assert(_next <= _limit, "invariant"); + assert(_current <= _limit, "invariant"); +} + +const Symbol* JfrAnnotationIterator::type() const { + assert(_buffer != nullptr, "invariant"); + assert(_current < _limit, "invariant"); + return _ik->constants()->symbol_at(JfrBigEndian::read(_buffer + _current)); +} + +address JfrAnnotationIterator::buffer() const { + return _buffer; +} + +int JfrAnnotationIterator::current() const { + return _current; +} + +int JfrAnnotationIterator::next() const { + return _next; +} + +// Skip an annotation. Return >=limit if there is any problem. +int JfrAnnotationIterator::next_annotation_index(const address buffer, int limit, int index) { + assert(buffer != nullptr, "invariant"); + index += 2; // skip atype + if ((index += 2) >= limit) { + return limit; + } + int nof_members = JfrBigEndian::read(buffer + index - 2); + while (--nof_members >= 0 && index < limit) { + index += 2; // skip member + index = skip_annotation_value(buffer, limit, index); + } + return index; +} + +// Skip an annotation value. Return >=limit if there is any problem. +int JfrAnnotationIterator::skip_annotation_value(const address buffer, int limit, int index) { + assert(buffer != nullptr, "invariant"); + // value := switch (tag:u1) { + // case B, C, I, S, Z, D, F, J, c: con:u2; + // case e: e_class:u2 e_name:u2; + // case s: s_con:u2; + // case [: do(nval:u2) {value}; + // case @: annotation; + // case s: s_con:u2; + // } + if ((index += 1) >= limit) { + return limit; + } + const u1 tag = buffer[index - 1]; + switch (tag) { + case 'B': + case 'C': + case 'I': + case 'S': + case 'Z': + case 'D': + case 'F': + case 'J': + case 'c': + case 's': + index += 2; // skip con or s_con + break; + case 'e': + index += 4; // skip e_class, e_name + break; + case '[': + { + if ((index += 2) >= limit) { + return limit; + } + int nof_values = JfrBigEndian::read(buffer + index - 2); + while (--nof_values >= 0 && index < limit) { + index = skip_annotation_value(buffer, limit, index); + } + } + break; + case '@': + index = next_annotation_index(buffer, limit, index); + break; + default: + return limit; // bad tag byte + } + return index; +} diff --git a/src/hotspot/share/jfr/support/jfrAnnotationIterator.hpp b/src/hotspot/share/jfr/support/jfrAnnotationIterator.hpp new file mode 100644 index 0000000000000..5f62287e862d2 --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrAnnotationIterator.hpp @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2016, 2025, 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_SUPPORT_JFRANNOTATIONITERATOR_HPP +#define SHARE_JFR_SUPPORT_JFRANNOTATIONITERATOR_HPP + +#include "memory/allocation.hpp" +#include "oops/annotations.hpp" + +class Symbol; +class InstanceKlass; + +class JfrAnnotationIterator: public StackObj { + friend class JfrAnnotationElementIterator; + private: + const InstanceKlass* _ik; + // ensure _limit field is declared before _buffer + int _limit; // length of annotations array + const address _buffer; + mutable int _current; // annotation + mutable int _next; // annotation + static int skip_annotation_value(const address buffer, int limit, int index); + static int next_annotation_index(const address buffer, int limit, int index); + + public: + JfrAnnotationIterator(const InstanceKlass* ik, AnnotationArray* ar); + bool has_next() const; + void move_to_next() const; + const Symbol* type() const; + address buffer() const; + int current() const; + int next() const; +}; + +#endif // SHARE_JFR_SUPPORT_JFRANNOTATIONITERATOR_HPP diff --git a/src/hotspot/share/jfr/support/jfrKlassExtension.hpp b/src/hotspot/share/jfr/support/jfrKlassExtension.hpp index 7652b94b26b87..8f096347c778d 100644 --- a/src/hotspot/share/jfr/support/jfrKlassExtension.hpp +++ b/src/hotspot/share/jfr/support/jfrKlassExtension.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2012, 2025, 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,7 +25,7 @@ #ifndef SHARE_JFR_SUPPORT_JFRKLASSEXTENSION_HPP #define SHARE_JFR_SUPPORT_JFRKLASSEXTENSION_HPP -#include "jfr/instrumentation/jfrEventClassTransformer.hpp" +#include "jfr/jfr.hpp" #include "jfr/support/jfrTraceIdExtension.hpp" #define DEFINE_KLASS_TRACE_ID_OFFSET \ @@ -37,8 +37,11 @@ #define JDK_JFR_EVENT_KLASS 32 #define EVENT_HOST_KLASS 64 #define EVENT_RESERVED 128 +#define EVENT_STICKY_BIT 8192 #define IS_EVENT_KLASS(ptr) (((ptr)->trace_id() & (JDK_JFR_EVENT_KLASS | JDK_JFR_EVENT_SUBKLASS)) != 0) #define IS_EVENT_OR_HOST_KLASS(ptr) (((ptr)->trace_id() & (JDK_JFR_EVENT_KLASS | JDK_JFR_EVENT_SUBKLASS | EVENT_HOST_KLASS)) != 0) -#define ON_KLASS_CREATION(k, p, t) if (IS_EVENT_OR_HOST_KLASS(k)) JfrEventClassTransformer::on_klass_creation(k, p, t) +#define KLASS_HAS_STICKY_BIT(ptr) (((ptr)->trace_id() & STICKY_BIT) != 0) +#define ON_KLASS_REDEFINITION(k, t) if (KLASS_HAS_STICKY_BIT(k)) Jfr::on_klass_redefinition(k, t) +#define ON_KLASS_CREATION(k, p, t) Jfr::on_klass_creation(k, p, t) #endif // SHARE_JFR_SUPPORT_JFRKLASSEXTENSION_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp new file mode 100644 index 0000000000000..37f2d438bc6c7 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, 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 "classfile/classLoaderDataGraph.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/support/jfrKlassUnloading.hpp" +#include "jfr/support/methodtracer/jfrClassFilterClosure.hpp" +#include "jfr/support/methodtracer/jfrFilter.hpp" +#include "jfr/support/methodtracer/jfrFilterManager.hpp" +#include "jfr/support/methodtracer/jfrInstrumentedClass.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/resizeableResourceHash.hpp" + +constexpr static unsigned int TABLE_SIZE = 1009; +constexpr static unsigned int MAX_TABLE_SIZE = 0x3fffffff; + +JfrFilterClassClosure::JfrFilterClassClosure(JavaThread* thread) : + _new_filter(JfrFilterManager::current()), + _classes_to_modify(new ClosureSet(TABLE_SIZE, MAX_TABLE_SIZE)), + _thread(thread) { + assert(_new_filter != nullptr, "invariant"); +} + +static inline jclass mirror_as_local_jni_handle(const InstanceKlass* ik, JavaThread* thread) { + assert(ik != nullptr, "invariant"); + assert(thread != nullptr, "invariant"); + return reinterpret_cast(JfrJavaSupport::local_jni_handle(ik->java_mirror(), thread)); +} + +inline bool JfrFilterClassClosure::match(const InstanceKlass* ik) const { + assert(_new_filter != nullptr, "invariant"); + return _new_filter->can_instrument_class(ik) && _new_filter->match(ik); +} + +void JfrFilterClassClosure::do_klass(Klass* k) { + assert(k != nullptr, "invariant"); + if (k->is_instance_klass()) { + const InstanceKlass* const ik = InstanceKlass::cast(k); + if (match(ik)) { + assert(ik->is_loader_alive(), "invariant"); + const traceid klass_id = JfrTraceId::load_raw(ik); + if (!_classes_to_modify->contains(klass_id)) { + jclass mirror = mirror_as_local_jni_handle(ik, _thread); + _classes_to_modify->put(klass_id, mirror); + } + } + } +} + +ClosureSet* JfrFilterClassClosure::to_modify() const { + assert(_classes_to_modify != nullptr, "invariant"); + return _classes_to_modify; +} + +int JfrFilterClassClosure::number_of_classes() const { + assert(_classes_to_modify != nullptr, "invariant"); + return _classes_to_modify->number_of_entries(); +} + +void JfrFilterClassClosure::iterate_all_classes(GrowableArray* instrumented_klasses) { + assert(instrumented_klasses != nullptr, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + + // First we process the instrumented_klasses list. The fact that a klass is on that list implies + // it matched _some_ previous filter, but we don't know which one. The nice thing is we don't need to know, + // because a klass has the STICKY_BIT set for those methods that matched _some_ previous filter. + // We, therefore, put these klasses directly into the classes_to_modify set. We also need to do this + // because some klasses on the instrumented_klasses list may not have reached the point of add_to_hierarchy yet. + // For those klasses, the ClassLoaderDataGraph iterator would not deliver them on iteration. + + if (instrumented_klasses->is_nonempty()) { + for (int i = 0; i < instrumented_klasses->length(); ++i) { + if (JfrKlassUnloading::is_unloaded(instrumented_klasses->at(i).trace_id())) { + continue; + } + const InstanceKlass* const ik = instrumented_klasses->at(i).instance_klass(); + assert(ik != nullptr, "invariant"); + assert(ik->is_loader_alive(), "invariant"); + assert(JfrTraceId::has_sticky_bit(ik), "invariant"); + const traceid klass_id = JfrTraceId::load_raw(ik); + assert(!_classes_to_modify->contains(klass_id), "invariant"); + jclass mirror = mirror_as_local_jni_handle(ik, _thread); + _classes_to_modify->put(klass_id, mirror); + } + } + ClassLoaderDataGraph::loaded_classes_do_keepalive(this); +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp new file mode 100644 index 0000000000000..a23b27eb12a63 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRFILTERCLASSCLOSURE_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRFILTERCLASSCLOSURE_HPP + +#include "jni.h" +#include "memory/iterator.hpp" +#include "jfr/support/methodtracer/jfrInstrumentedClass.hpp" +#include "jfr/utilities/jfrRelation.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +class JavaThread; +class JfrFilter; +class Klass; + +template class GrowableArray; + +template class ResizeableResourceHashtable; + +// Knuth multiplicative hashing. +inline uint32_t knuth_hash(const traceid& id) { + const uint32_t v = static_cast(id); + return v * UINT32_C(2654435761); +} + +typedef ResizeableResourceHashtable ClosureSet; + +// +// Class that collects classes that should be retransformed, +// either for adding instrumentation by matching the current +// filter or for removing old instrumentation. +// +class JfrFilterClassClosure : public KlassClosure { + private: + const JfrFilter* const _new_filter; + ClosureSet* const _classes_to_modify; + JavaThread* const _thread; + + bool match(const InstanceKlass* klass) const; + void do_klass(Klass* k); + + public: + JfrFilterClassClosure(JavaThread* thread); + void iterate_all_classes(GrowableArray* instrumented_klasses); + // Returned set is Resource allocated. + ClosureSet* to_modify() const; + int number_of_classes() const; +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRFILTERCLASSCLOSURE_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrFilter.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrFilter.cpp new file mode 100644 index 0000000000000..2a977f9e82319 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrFilter.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2025, 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 "classfile/moduleEntry.hpp" +#include "classfile/vmClasses.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/support/jfrAnnotationIterator.hpp" +#include "jfr/support/jfrJdkJfrEvent.hpp" +#include "jfr/support/methodtracer/jfrFilter.hpp" +#include "logging/log.hpp" +#include "logging/logMessage.hpp" +#include "memory/resourceArea.hpp" +#include "oops/method.inline.hpp" +#include "oops/symbol.hpp" + +JfrFilter::JfrFilter(Symbol** class_names, + Symbol** method_names, + Symbol** annotation_names, + int* modifications, + int count) : + _class_names(class_names), + _method_names(method_names), + _annotation_names(annotation_names), + _modifications(modifications), + _count(count) {} + +JfrFilter::~JfrFilter() { + for (int i = 0; i < _count; i++) { + Symbol::maybe_decrement_refcount(_class_names[i]); + Symbol::maybe_decrement_refcount(_method_names[i]); + Symbol::maybe_decrement_refcount(_annotation_names[i]); + } + FREE_C_HEAP_ARRAY(Symbol*, _class_names); + FREE_C_HEAP_ARRAY(Symbol*, _method_names); + FREE_C_HEAP_ARRAY(Symbol*, _annotation_names); + FREE_C_HEAP_ARRAY(int, _modifications); +} + +bool JfrFilter::can_instrument_module(const ModuleEntry* module) const { + if (module == nullptr) { + return true; + } + const Symbol* name = module->name(); + if (name == nullptr) { + return true; + } + if (name->equals("jdk.jfr", 7)) { + return false; + } + return true; +} + +bool JfrFilter::can_instrument_class(const InstanceKlass* ik) const { + assert(ik != nullptr, "invariant"); + if (JfrTraceId::has_sticky_bit(ik)) { + return true; + } + if (ik->is_hidden()) { + return false; + } + if (JdkJfrEvent::is_a(ik)) { + return false; + } + if (ik == vmClasses::Continuation_klass()) { + return false; + } + return can_instrument_module(ik->module()); +} + +// can_intrument(InstanceKlass*) is not called in this method +// to avoid executing the same code for every method in a class +bool JfrFilter::can_instrument_method(const Method* method) const { + assert(method != nullptr, "invariant"); + if (JfrTraceId::has_sticky_bit(method)) { + return true; + } + if (method->is_abstract()) { + return false; + } + if (method->is_synthetic()) { + return false; + } + if (method->is_native()) { + return false; + } + if (method->is_compiled_lambda_form()) { + return false; + } + return true; +} + +bool JfrFilter::match_annotations(const InstanceKlass* ik, AnnotationArray* annotations, const Symbol* symbol, bool log) const { + assert(ik != nullptr, "invariant"); + assert(symbol != nullptr, "invariant"); + if (annotations == nullptr) { + return false; + } + JfrAnnotationIterator it(ik, annotations); + while (it.has_next()) { + it.move_to_next(); + const Symbol* current = it.type(); + bool equal = current == symbol; + if (log && log_is_enabled(Trace, methodtrace)) { + ResourceMark rm; + log_trace(jfr, methodtrace)( + "match_annotations: Class %s has annotation %s %s", + ik->external_name(), + current->as_C_string(), + (equal ? "true" : "false") + ); + } + if (equal) { + return true; + } + } + return false; +} + +int JfrFilter::combine_bits(int a, int b) { + if (a == NONE) { + return b; + } + if (b == NONE) { + return a; + } + return a | b; +} + +int JfrFilter::class_modifications(const InstanceKlass* ik, bool log) const { + assert(ik != nullptr, "invariant"); + AnnotationArray* class_annotations = ik->class_annotations(); + if (class_annotations == nullptr) { + return NONE; + } + int result = NONE; + for (int i = 0; i < _count; i++) { + const Symbol* annotation_filter = _annotation_names[i]; + if (annotation_filter != nullptr && match_annotations(ik, class_annotations, annotation_filter, log)) { + result = combine_bits(result, _modifications[i]); + if (log && log_is_enabled(Trace, methodtrace)) { + ResourceMark rm; + log_trace(jfr, methodtrace)("Class_modifications: %s bits = %d", ik->external_name(), result); + } + } + } + return result; +} + +bool JfrFilter::match(const InstanceKlass* ik) const { + assert(ik != nullptr, "invariant"); + if (class_modifications(ik, false) != NONE) { + return true; + } + const Array* methods = ik->methods(); + const int method_count = methods->length(); + for (int i = 0; i < method_count; i++) { + if (method_modifications(methods->at(i)) != NONE) { + return true; + } + } + return false; +} + +int JfrFilter::method_modifications(const Method* method) const { + assert(method != nullptr, "invariant"); + InstanceKlass* klass = method->method_holder(); + int result = NONE; + for (int i = 0; i < _count; i++) { + Symbol* annotation_name = _annotation_names[i]; + if (annotation_name != nullptr) { + if (match_annotations(klass, method->annotations(), annotation_name, false)) { + result = combine_bits(result, _modifications[i]); + } + } else { + Symbol* class_name = _class_names[i]; + if (class_name == nullptr || klass->name() == class_name) { + Symbol* method_name = _method_names[i]; + if (method_name == nullptr || (method->name() == method_name && can_instrument_method(method))) { + result = combine_bits(result, _modifications[i]); + } + } + } + } + return result; +} + +void JfrFilter::log(const char* caption) const { + assert(caption != nullptr, "invariant"); + if (!log_is_enabled(Debug, jfr, methodtrace)) { + return; + } + LogMessage(jfr,methodtrace) msg; + msg.debug("%s = {", caption); + for (int i = 0; i < _count; i++) { + const Symbol* m = _method_names[i]; + const Symbol* c = _class_names[i]; + const Symbol* a = _annotation_names[i]; + const char* modification = modification_to_text(_modifications[i]); + + if (a != nullptr) { + char annotation_buffer[100]; + a->as_klass_external_name(annotation_buffer, 100); + size_t length = strlen(annotation_buffer); + if (length > 2) { + annotation_buffer[length - 1] = '\0'; // remove trailing ';' + msg.debug(" @%s %s", annotation_buffer + 1, modification); // Skip 'L' + } + } else { + char class_buffer[100]; + if (c != nullptr) { + c->as_klass_external_name(class_buffer, 100); + } else { + class_buffer[0] = '\0'; + } + if (m != nullptr) { + char method_buffer[100]; + m->as_klass_external_name(method_buffer, 100); + msg.debug(" %s::%s %s", class_buffer, method_buffer, modification); + } else { + msg.debug(" %s %s", class_buffer, modification); + } + } + } + msg.debug("}"); +} + +bool JfrFilter::is_timing(int modification) { + return modification == NONE ? false : (modification & TIMING) != 0; +} + +bool JfrFilter::is_tracing(int modification) { + return modification == NONE ? false : (modification & TRACING) != 0; +} + +const char* JfrFilter::modification_to_text(int modification) { + switch (modification) { + case 0: + return "-timing -tracing"; + case TIMING: + return "+timing"; + case TRACING: + return "+tracing"; + case TIMING + TRACING: + return "+timing +tracing"; + default: + ShouldNotReachHere(); + }; + return "unknown modification"; +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrFilter.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrFilter.hpp new file mode 100644 index 0000000000000..665a2cafad384 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrFilter.hpp @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRFILTER_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRFILTER_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" +#include "oops/annotations.hpp" + +class InstanceKlass; +class JavaThread; +class Method; +class ModuleEntry; +class Symbol; + +// +// Class that holds the configured filters. +// +// For information on how they are configured, +// see jdk.jfr.internal.JVM::setMethodTraceFilters(...). +// +class JfrFilter : public JfrCHeapObj { + friend class JfrFilterManager; + private: + static constexpr int TIMING = 1; + static constexpr int TRACING = 2; + + Symbol** _class_names; + Symbol** _method_names; + Symbol** _annotation_names; + int* _modifications; + int _count; + + JfrFilter(Symbol** class_names, + Symbol** method_names, + Symbol** annotation_names, + int* modifications, + int count); + public: + ~JfrFilter(); + bool can_instrument_method(const Method* m) const; + bool can_instrument_class(const InstanceKlass* m) const; + bool can_instrument_module(const ModuleEntry* ik) const; + int class_modifications(const InstanceKlass* klass, bool log) const; + int method_modifications(const Method* method) const; + bool match(const InstanceKlass* klass) const; + bool match_annotations(const InstanceKlass* klass, AnnotationArray* annotation, const Symbol* symbol, bool log) const; + void log(const char* caption) const; + + static constexpr int NONE = -1; + static bool is_timing(int modification); + static bool is_tracing(int modification); + static const char* modification_to_text(int modification); + static int combine_bits(int a, int b); +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRFILTER_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrFilterManager.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrFilterManager.cpp new file mode 100644 index 0000000000000..00e6b8b6e2abb --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrFilterManager.cpp @@ -0,0 +1,144 @@ +/* +* Copyright (c) 2025, 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 "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/support/methodtracer/jfrFilter.hpp" +#include "jfr/support/methodtracer/jfrFilterManager.hpp" +#include "logging/log.hpp" +#include "oops/symbol.hpp" +#include "oops/typeArrayOop.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "utilities/growableArray.hpp" + +const JfrFilter* JfrFilterManager::_current = nullptr; + +// Track the set of previous filters during a chunk / epoch. +static constexpr const int initial_array_size = 4; +static GrowableArray* _previous_filters_epoch_0 = nullptr; +static GrowableArray* _previous_filters_epoch_1 = nullptr; + +static GrowableArray* c_heap_allocate_array(int size = initial_array_size) { + return new (mtTracing) GrowableArray(size, mtTracing); +} + +static GrowableArray* previous_filters_epoch_0() { + if (_previous_filters_epoch_0 == nullptr) { + _previous_filters_epoch_0 = c_heap_allocate_array(initial_array_size); + } + return _previous_filters_epoch_0; +} + +static GrowableArray* previous_filters_epoch_1() { + if (_previous_filters_epoch_1 == nullptr) { + _previous_filters_epoch_1 = c_heap_allocate_array(initial_array_size); + } + return _previous_filters_epoch_1; +} + +static GrowableArray* get_previous_filters(u1 epoch) { + return epoch == 0 ? previous_filters_epoch_0() : previous_filters_epoch_1(); +} + +static GrowableArray* get_previous_filters() { + return get_previous_filters(JfrTraceIdEpoch::current()); +} + +static GrowableArray* get_previous_filters_previous_epoch() { + return get_previous_filters(JfrTraceIdEpoch::previous()); +} + +static void add_previous_filter(const JfrFilter* previous_filter) { + if (previous_filter != nullptr) { + get_previous_filters()->append(previous_filter); + } +} + +const JfrFilter* JfrFilterManager::current() { + return Atomic::load_acquire(&_current); +} + +void JfrFilterManager::install(const JfrFilter* new_filter) { + assert(new_filter != nullptr, "invariant"); + add_previous_filter(Atomic::xchg(&_current, new_filter)); + new_filter->log("New filter installed"); +} + +static void delete_filters(GrowableArray* filters) { + assert(filters != nullptr, "invariant"); + for (int i = 0; i < filters->length(); ++i) { + delete filters->at(i); + } + filters->clear(); +} + +void JfrFilterManager::clear_previous_filters() { + delete_filters(get_previous_filters_previous_epoch()); +} + +bool JfrFilterManager::install(jobjectArray classes, jobjectArray methods, jobjectArray annotations, jintArray modification_array, JavaThread* jt) { + assert(classes != nullptr, "invariant"); + assert(methods != nullptr, "invariant"); + assert(annotations != nullptr, "invariant"); + assert(modification_array != nullptr, "invariant"); + + if (!JfrOptionSet::can_retransform()) { + log_info(jfr, methodtrace)("Flight Recorder retransform has been set to false. New method filter is ignored."); + return false; + } + + intptr_t class_size = 0; + Symbol** class_names = JfrJavaSupport::symbol_array(classes, jt, &class_size, true); + assert(class_names != nullptr, "invariant"); + + intptr_t method_size = 0; + Symbol** method_names = JfrJavaSupport::symbol_array(methods, jt, &method_size, true); + assert(method_names != nullptr, "invariant"); + + intptr_t annotation_size = 0; + Symbol** annotation_names = JfrJavaSupport::symbol_array(annotations, jt, &annotation_size, true); + assert(annotation_names != nullptr, "invariant"); + + typeArrayOop ta = typeArrayOop(JfrJavaSupport::resolve_non_null(modification_array)); + const typeArrayHandle modification_tah(jt, ta); + const int modification_size = modification_tah->length(); + int* const modifications = NEW_C_HEAP_ARRAY(int, modification_size, mtTracing); + for (int i = 0; i < modification_size; i++) { + modifications[i] = modification_tah->int_at(i); + } + if (class_size != method_size || class_size != annotation_size || class_size != modification_size) { + FREE_C_HEAP_ARRAY(Symbol*, class_names); + FREE_C_HEAP_ARRAY(Symbol*, method_names); + FREE_C_HEAP_ARRAY(Symbol*, annotation_names); + FREE_C_HEAP_ARRAY(int, modifications); + JfrJavaSupport::throw_internal_error("Method array sizes don't match", jt); + return false; + } + const JfrFilter* const new_filter = new JfrFilter(class_names, method_names, annotation_names, modifications, modification_size); + assert(new_filter != nullptr, "invariant"); + install(new_filter); + return true; +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrFilterManager.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrFilterManager.hpp new file mode 100644 index 0000000000000..904c8447a2ecc --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrFilterManager.hpp @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRFILTERMANAGER_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRFILTERMANAGER_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" + +class JfrFilter; +class JavaThread; +class Symbol; + +// +// Class that manages memory for JfrFilter objects to ensure they +// are not deleted until we have transitioned to the next epoch, which ensures +// they are no longer in use. +// +class JfrFilterManager : public AllStatic { + friend class JfrMethodTracer; + private: + static const JfrFilter* _current; + static void install(const JfrFilter* filter); + static void clear_previous_filters(); + + public: + static const JfrFilter* current(); + static bool install(jobjectArray classses, + jobjectArray methods, + jobjectArray annotations, + jintArray modifications, + JavaThread* jt); +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRFILTERMANAGER_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrInstrumentedClass.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrInstrumentedClass.hpp new file mode 100644 index 0000000000000..eb775581e1f66 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrInstrumentedClass.hpp @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRINSTRUMENTEDCLASS_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRINSTRUMENTEDCLASS_HPP + +#include "jfr/utilities/jfrTypes.hpp" + +class InstanceKlass; + +// +// Class that holds classes that are currently being instrumented and +// if the have been unloaded. +// +class JfrInstrumentedClass { + private: + traceid _trace_id; + const InstanceKlass* _instance_klass; + bool _unloaded; + + public: + JfrInstrumentedClass(traceid trace_id = 0, const InstanceKlass* instance_klass = nullptr, bool unloaded = false) : + _trace_id(trace_id), _instance_klass(instance_klass), _unloaded(unloaded) { + } + + const InstanceKlass* instance_klass() const { + return _instance_klass; + } + + traceid trace_id() const { + return _trace_id; + } + + jlong trace_id_as_jlong() const { + return static_cast(_trace_id); + } + + void set_unloaded(bool unloaded) { + _unloaded = unloaded; + } + + bool unloaded() const { + return _unloaded; + } + + bool operator==(const JfrInstrumentedClass& rhs) { + return _trace_id == rhs._trace_id; + } +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRINSTRUMENTEDCLASS_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrMethodProcessor.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrMethodProcessor.cpp new file mode 100644 index 0000000000000..d3226729beb2b --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrMethodProcessor.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, 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 "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/support/methodtracer/jfrFilter.hpp" +#include "jfr/support/methodtracer/jfrFilterManager.hpp" +#include "jfr/support/methodtracer/jfrMethodProcessor.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "logging/log.hpp" +#include "oops/instanceKlass.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/growableArray.hpp" + +JfrMethodProcessor::JfrMethodProcessor(const InstanceKlass* ik, Thread* thread) : + _klass(ik), + _methods(nullptr), + _thread(thread), + _has_timing(false), + _log(log_is_enabled(Debug, jfr, methodtrace)) { + assert(ik != nullptr, "invariant"); + assert(Thread::current() == thread, "invariant"); + process(); +} + +JfrMethodProcessor::~JfrMethodProcessor() { + assert(_thread != nullptr, "invariant"); + if (_methods != nullptr) { + // Removal of pushed metadata keep-alive entries. + for (int i = 0; i < _methods->length(); ++i) { + Method* const method = const_cast(_methods->at(i).method()); + if (method != nullptr) { + const int idx = _thread->metadata_handles()->find_from_end(method); + assert(idx >= 0, "invariant"); + _thread->metadata_handles()->remove_at(idx); + } + } + } +} + +void JfrMethodProcessor::update_methods(const InstanceKlass* ik) { + assert(ik != nullptr, "invariant"); + assert(_methods != nullptr, "invariant"); + const Array* const ik_methods = ik->methods(); + assert(ik_methods != nullptr, "invariant"); + for (int i = 0; i < _methods->length(); ++i) { + const uint32_t idx = _methods->at(i).methods_array_index(); + Method* const method = ik_methods->at(idx); + assert(method != nullptr, "invariant"); + _methods->at(i).set_method(method); + // This is to keep the method from being unloaded during redefine / retransform. + // Equivalent functionality to that provided by the methodHandle. Unfortunately, + // we cannot use that directly because our handles would reside not on the stack + // but in an Arena managed by a thread-local ResourceArea, which is not allowed. + // Removal of pushed metadata entries happens in the destructor. + _thread->metadata_handles()->push(method); + } +} + +void JfrMethodProcessor::set_timing(int modification) { + if (_has_timing) { + return; + } + if (modification > 0 && (modification & 1)) { + _has_timing = true; + } +} + + +static void log(const Method* method, traceid id, int new_modification) { + assert(method != nullptr, "invariant"); + const char* timing = JfrFilter::is_timing(new_modification) ? "+timing" : "-timing"; + const char* tracing = JfrFilter::is_tracing(new_modification) ? "+tracing" : "-tracing"; + stringStream param_stream; + method->signature()->print_as_signature_external_parameters(¶m_stream); + const char* param_string = param_stream.as_string(); + + stringStream ss; + ss.print("%s", method->method_holder()->external_name()); + ss.print("::"); + ss.print("%s", method->name()->as_C_string()); + ss.print("("); + if (strlen(param_string) < 30) { + ss.print("%s", param_string); + } else { + ss.print("..."); + } + ss.print(")"); + log_debug(jfr, methodtrace)("Modify bytecode for %s %s %s (Method ID: " UINT64_FORMAT_X ")", ss.as_string(), timing, tracing, id); +} + +void JfrMethodProcessor::process() { + const JfrFilter* const filter = JfrFilterManager::current(); + assert(filter != nullptr, "invariant"); + if (!filter->can_instrument_class(_klass)) { + return; + } + const int class_modifications = filter->class_modifications(_klass, false); + const Array* const methods = _klass->methods(); + const int method_count = methods->length(); + for (int i = 0; i < method_count; i++) { + const Method* const m = methods->at(i); + assert(m != nullptr, "invariant"); + if (filter->can_instrument_method(m)) { + const int new_modification = JfrFilter::combine_bits(class_modifications, filter->method_modifications(m)); + if (new_modification != JfrFilter::NONE || JfrTraceId::has_sticky_bit(m)) { + // Allocate lazy, most classes will not match a filter + if (_methods == nullptr) { + _methods = new GrowableArray(); + } + set_timing(new_modification); + const int modification = new_modification == JfrFilter::NONE ? 0 : new_modification; + JfrTracedMethod traced_method(_klass, m, modification, i); + _methods->append(traced_method); + if (_log) { + log(m, traced_method.id(), modification); + } + } + } + } +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrMethodProcessor.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrMethodProcessor.hpp new file mode 100644 index 0000000000000..0f8d6a065c615 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrMethodProcessor.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRMETHODPROCESSOR_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRMETHODPROCESSOR_HPP + +#include "jfr/support/methodtracer/jfrTracedMethod.hpp" +#include "memory/allocation.hpp" + +class InstanceKlass; +class JfrFilter; +class Thread; + +template class GrowableArray; + +// +// Class responsible for determining which methods in an InstanceKlass +// that should be instrumented and tagged. +// +class JfrMethodProcessor: public StackObj { + private: + const InstanceKlass* const _klass; + GrowableArray* _methods; + Thread* _thread; + bool _has_timing; + bool _log; + + void set_timing(int modification); + void process(); + + public: + JfrMethodProcessor(const InstanceKlass* klass, Thread* thread); + ~JfrMethodProcessor(); + + bool has_methods() const { + return _methods != nullptr; + } + + const GrowableArray* methods() const { + return _methods; + } + + GrowableArray* methods() { + return _methods; + } + + void update_methods(const InstanceKlass* ik); + + bool has_timing() const { + return _has_timing; + } +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRMETHODPROCESSOR_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp new file mode 100644 index 0000000000000..73701445f7014 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2025, 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 "classfile/classFileParser.hpp" +#include "classfile/classFileStream.hpp" +#include "classfile/moduleEntry.hpp" +#include "classfile/modules.hpp" +#include "classfile/symbolTable.hpp" +#include "jfr/instrumentation/jfrClassTransformer.hpp" +#include "jfr/instrumentation/jfrJvmtiAgent.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/jni/jfrUpcalls.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" +#include "jfr/support/jfrKlassUnloading.hpp" +#include "jfr/support/methodtracer/jfrClassFilterClosure.hpp" +#include "jfr/support/methodtracer/jfrFilter.hpp" +#include "jfr/support/methodtracer/jfrFilterManager.hpp" +#include "jfr/support/methodtracer/jfrInstrumentedClass.hpp" +#include "jfr/support/methodtracer/jfrMethodProcessor.hpp" +#include "jfr/support/methodtracer/jfrMethodTracer.hpp" +#include "jfr/support/methodtracer/jfrTracedMethod.hpp" +#include "jfr/support/methodtracer/jfrTraceTagging.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/safepoint.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/resizeableResourceHash.hpp" + +ModuleEntry* JfrMethodTracer::_jdk_jfr_module = nullptr; +GrowableArray* JfrMethodTracer::_instrumented_classes = nullptr; +GrowableArray* JfrMethodTracer::_timing_entries = nullptr; + +// Quick and unlocked check to see if the Method Tracer has been activated. +// This is flipped to not null the first time a filter is set and will stay non-null forever. +bool JfrMethodTracer::in_use() { + return JfrFilterManager::current() != nullptr; +} + +static void clear(GrowableArray* instrumented_classes) { + assert(instrumented_classes != nullptr, "invariant"); + if (instrumented_classes->is_nonempty()) { + instrumented_classes->clear(); + JfrTraceIdEpoch::reset_method_tracer_tag_state(); + } +} + +jlongArray JfrMethodTracer::set_filters(JNIEnv* env, jobjectArray classes, jobjectArray methods, jobjectArray annotations, jintArray modifications, TRAPS) { + assert(env != nullptr, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + // This operation, if successful, atomically installs a JfrFilter object to represent all passed in filters. + if (!JfrFilterManager::install(classes, methods, annotations, modifications, THREAD)) { + return nullptr; + } + + ResourceMark rm(THREAD); + JfrFilterClassClosure filter_class_closure(THREAD); + { + MutexLocker lock(ClassLoaderDataGraph_lock); + filter_class_closure.iterate_all_classes(instrumented_classes()); + ::clear(instrumented_classes()); + } + retransform(env, filter_class_closure, THREAD); + MutexLocker lock(ClassLoaderDataGraph_lock); + if (_timing_entries->is_empty()) { + return nullptr; + } + jlongArray array = JfrJavaSupport::create_long_array(_timing_entries, THREAD); + _timing_entries->clear(); + return array; +} + +class MirrorClosure { + private: + jclass* const _classes; + int _size; + int _idx; + public: + MirrorClosure(int size) : _classes(NEW_RESOURCE_ARRAY(jclass, size)), _size(size), _idx(0) {} + + jclass* classes() const { + return _classes; + } + + bool operator()(const traceid& key, const jclass& mirror) { + assert(_classes != nullptr, "invariant"); + assert(_idx < _size, "invariant"); + _classes[_idx++] = mirror; + return true; + } +}; + +void JfrMethodTracer::retransform(JNIEnv* env, const JfrFilterClassClosure& classes, TRAPS) { + log_debug(jfr, methodtrace)("Issuing Retransform Classes"); + const int class_count = classes.number_of_classes(); + if (class_count > 0) { + ThreadToNativeFromVM transition(THREAD); + const MirrorClosure closure(class_count); + classes.to_modify()->iterate_all(closure); + JfrJvmtiAgent::retransform_classes(env, closure.classes(), class_count, THREAD); + } +} + +static void handle_no_bytecode_result(const Klass* klass) { + assert(klass != nullptr, "invariant"); + if (JfrTraceId::has_sticky_bit(klass)) { + MutexLocker lock(ClassLoaderDataGraph_lock); + JfrTraceTagging::clear_sticky_bit(InstanceKlass::cast(klass)); + } +} + +void JfrMethodTracer::on_klass_creation(InstanceKlass*& ik, ClassFileParser& parser, TRAPS) { + assert(ik != nullptr, "invariant"); + assert(in_use(), "invariant"); + + ResourceMark rm(THREAD); + + // 1. Is the ik the initial load, i.e.the first InstanceKlass, or a scratch klass, denoting a redefine / retransform? + const Klass* const existing_klass = JfrClassTransformer::find_existing_klass(ik, THREAD); + const bool is_retransform = existing_klass != nullptr; + + // 2. Test the ik and its methods against the currently installed filter object. + JfrMethodProcessor mp(is_retransform ? InstanceKlass::cast(existing_klass) : ik, THREAD); + if (!mp.has_methods()) { + return; + } + + // 3. We matched one or serveral filters. Now construct a new bytecode representation with instrumented methods in accordance with matched instructions. + const ClassFileStream* clone = parser.clone_stream(); + ClassFileStream* const result = JfrUpcalls::on_method_trace(ik, clone, mp.methods(), THREAD); + if (result == nullptr) { + // If no bytecode is returned, either an error occurred during transformation, but more + // likely the matched instructions were negative, i.e. instructions to remove existing instrumentation + // and so Java added no new instrumentation. By not returning a bytecode result, the klass is restored to its original, non-instrumented, version. + handle_no_bytecode_result(is_retransform ? InstanceKlass::cast(existing_klass) : ik); + return; + } + // 4. Now create a new InstanceKlass representation from the modified bytecode. + InstanceKlass* const new_ik = JfrClassTransformer::create_instance_klass(ik, result, !is_retransform, THREAD); + if (new_ik == nullptr) { + return; + } + // 5. Replace the passed in ik with the newly constructed, new_ik. + JfrClassTransformer::copy_traceid(ik, new_ik); // copy existing traceid + if (is_retransform) { + // Keep the original cached class file data from the existing class. + JfrClassTransformer::transfer_cached_class_file_data(ik, new_ik, parser, THREAD); + JfrClassTransformer::rewrite_klass_pointer(ik, new_ik, parser, THREAD); // The ik is modified to point to new_ik here. + const InstanceKlass* const existing_ik = InstanceKlass::cast(existing_klass); + mp.update_methods(existing_ik); + existing_ik->module()->add_read(jdk_jfr_module()); + // By setting the sticky bit on the existng klass, we receive a callback into on_klass_redefinition (see below) + // when our new methods are installed into the existing klass as part of retransformation / redefinition. + // Only when we know our new methods have been installed can we add the klass to the instrumented list (done as part of callback). + JfrTraceTagging::install_sticky_bit_for_retransform_klass(existing_ik, mp.methods(), mp.has_timing()); + return; + } + // Initial class load. + JfrClassTransformer::cache_class_file_data(new_ik, clone, THREAD); // save the initial class file bytes (clone stream) + JfrClassTransformer::rewrite_klass_pointer(ik, new_ik, parser, THREAD); // The ik is modified to point to new_ik here. + mp.update_methods(ik); + // On initial class load the newly created klass can be installed into the instrumented class list directly. + add_instrumented_class(ik, mp.methods()); + if (mp.has_timing()) { + // After having installed the newly created klass into the list, perform an upcall to publish the associated TimedClass. + JfrUpcalls::publish_method_timers_for_klass(JfrTraceId::load_raw(ik), THREAD); + } +} + +static inline void log_add(const InstanceKlass* ik) { + assert(ik != nullptr, "invariant"); + if (log_is_enabled(Debug, jfr, methodtrace)) { + ResourceMark rm; + const traceid klass_id = JfrTraceId::load_raw(ik); + log_debug(jfr, methodtrace)("Adding class %s to instrumented list (Klass ID: " UINT64_FORMAT_X ")", ik->external_name(), klass_id); + } +} + +void JfrMethodTracer::add_timing_entry(const InstanceKlass* ik, traceid klass_id) { + assert(ik != nullptr, "invariant"); + assert(_timing_entries != nullptr, "invariant"); + if (JfrTraceId::has_timing_bit(ik)) { + JfrTraceId::clear_timing_bit(ik); + _timing_entries->append(klass_id); + } +} + +// At this point we have installed our new retransformed methods into the original klass, which is ik. +// jvmtiRedefineClassses::redefine_single_class() has finished so we are still at a safepoint. +// If the original klass is not already in the list, add it and also dynamically tag all +// artifacts that have the sticky bit set. If the klass has an associated TimedClass, +// also add the klass to the list of _timing_entries for publication. +void JfrMethodTracer::on_klass_redefinition(const InstanceKlass* ik, Thread* thread) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invarint"); + assert(ik->has_been_redefined(), "invariant"); + assert(JfrTraceId::has_sticky_bit(ik), "invariant"); + assert(in_use(), "invariant"); + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + + const traceid klass_id = JfrTraceId::load_raw(ik); + const JfrInstrumentedClass jic(klass_id, ik, false); + + if (instrumented_classes()->find(jic) == -1) { // not already existing + const int idx = instrumented_classes()->append(jic); + if (idx == 0) { + assert(!JfrTraceIdEpoch::has_method_tracer_changed_tag_state(), "invariant"); + JfrTraceIdEpoch::set_method_tracer_tag_state(); + } + add_timing_entry(ik, klass_id); + JfrTraceTagging::set_dynamic_tag_for_sticky_bit(ik); + log_add(ik); + } +} + +#ifdef ASSERT +static bool in_instrumented_list(const InstanceKlass* ik, const GrowableArray* list) { + assert(ik != nullptr, "invariant"); + assert(list != nullptr, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + const JfrInstrumentedClass jic(JfrTraceId::load_raw(ik), ik, false); + return list->find(jic) != -1; +} +#endif + +void JfrMethodTracer::add_instrumented_class(InstanceKlass* ik, GrowableArray* methods) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invariant"); + assert(methods->is_nonempty(), "invariant"); + ik->module()->add_read(jdk_jfr_module()); + MutexLocker lock(ClassLoaderDataGraph_lock); + assert(!in_instrumented_list(ik, instrumented_classes()), "invariant"); + JfrTraceTagging::set_dynamic_tag(ik, methods); + JfrTraceTagging::set_sticky_bit(ik, methods); + const JfrInstrumentedClass jik(JfrTraceId::load_raw(ik), ik, false); + const int idx = instrumented_classes()->append(jik); + if (idx == 0) { + JfrTraceIdEpoch::set_method_tracer_tag_state(); + } + assert(in_instrumented_list(ik, instrumented_classes()), "invariant"); + log_add(ik); +} + +ModuleEntry* JfrMethodTracer::jdk_jfr_module() { + if (_jdk_jfr_module == nullptr) { + MutexLocker ml(Module_lock); + ModuleEntryTable* const table = Modules::get_module_entry_table(Handle()); + Symbol* jfr_module_name = SymbolTable::probe("jdk.jfr", 7); + assert(jfr_module_name != nullptr, "jdk.jfr name could not be found"); + _jdk_jfr_module = table->lookup_only(jfr_module_name); + assert(_jdk_jfr_module != nullptr, "jdk.jfr module could not be found"); + } + return _jdk_jfr_module; +} + +// Track the set of unloaded class ids during a chunk / epoch. +static GrowableArray* _unloaded_class_ids_0 = nullptr; +static GrowableArray* _unloaded_class_ids_1 = nullptr; +static GrowableArray* _current_unloaded_class_ids = nullptr; +static GrowableArray* _stale_class_ids = nullptr; +static GrowableArray* _empty_class_ids = nullptr; + +jlongArray JfrMethodTracer::drain_stale_class_ids(TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD);) + if (!in_use()) { + return nullptr; + } + MutexLocker lock(ClassLoaderDataGraph_lock); + assert(_stale_class_ids != nullptr, "invariant"); + if (_stale_class_ids == _empty_class_ids) { + return nullptr; + } + assert(_stale_class_ids != _empty_class_ids, "invariant"); + assert(_stale_class_ids->is_nonempty(), "invariant"); + jlongArray array = JfrJavaSupport::create_long_array(_stale_class_ids, THREAD); + _stale_class_ids->clear(); + assert(_stale_class_ids->is_empty(), "invariant"); + _stale_class_ids = _empty_class_ids; + return array; +} + +static constexpr const int initial_array_size = 256; + +static GrowableArray* c_heap_allocate_array(int size = initial_array_size) { + return new (mtTracing) GrowableArray(size, mtTracing); +} + +static GrowableArray* unloaded_class_ids_0() { + if (_unloaded_class_ids_0 == nullptr) { + _unloaded_class_ids_0 = c_heap_allocate_array(initial_array_size); + } + return _unloaded_class_ids_0; +} + +static GrowableArray* unloaded_class_ids_1() { + if (_unloaded_class_ids_1 == nullptr) { + _unloaded_class_ids_1 = c_heap_allocate_array(initial_array_size); + } + return _unloaded_class_ids_1; +} + +GrowableArray* JfrMethodTracer::instrumented_classes() { + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + if (_instrumented_classes == nullptr) { + _instrumented_classes = new (mtTracing) GrowableArray(256, mtTracing); + _empty_class_ids = new (mtTracing) GrowableArray(0, mtTracing); + _stale_class_ids = _empty_class_ids; + _current_unloaded_class_ids = unloaded_class_ids_0(); + _timing_entries = new (mtTracing) GrowableArray(256, mtTracing); + } + return _instrumented_classes; +} + +// Invoked from JfrTypeSet on class unloading of sticky bit-tagged classes. +void JfrMethodTracer::add_to_unloaded_set(const Klass* k) { + assert(k != nullptr, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + assert(JfrTraceId::has_sticky_bit(k), "invariant"); + assert(_current_unloaded_class_ids != nullptr, "invariant"); + assert(_current_unloaded_class_ids->find(JfrTraceId::load_raw(k)) == -1, "invariant"); + _current_unloaded_class_ids->append(static_cast(JfrTraceId::load_raw(k))); +} + +// Invoked from JfrTypeSet after having finalized rotation. +void JfrMethodTracer::trim_instrumented_classes(bool trim) { + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + if (trim) { + GrowableArray* trimmed_classes = new (mtTracing) GrowableArray(256, mtTracing); + for (int i = 0; i < _instrumented_classes->length(); i++) { + const JfrInstrumentedClass& jic = _instrumented_classes->at(i); + if (jic.unloaded()) { + assert(JfrKlassUnloading::is_unloaded(jic.trace_id(), true), "invariant"); + assert(_stale_class_ids->find(jic.trace_id()) != -1 || _current_unloaded_class_ids->find(jic.trace_id()) != -1, "invariant"); + continue; + } + trimmed_classes->append(jic); + } + delete _instrumented_classes; + _instrumented_classes = trimmed_classes; + } + + if (instrumented_classes()->is_nonempty()) { + if (!JfrTraceIdEpoch::has_method_tracer_changed_tag_state()) { + // Turn the tag state back on for next chunk. + JfrTraceIdEpoch::set_method_tracer_tag_state(); + } + } + + // Clearing out filters that were used during the previous epoch. + JfrFilterManager::clear_previous_filters(); + + // Tracking unloading of sticky bit-tagged classes. + // + // We want to delay publishing an unloaded class until the very last moment. + // Because of our tagging scheme, writing events for classes that have unloaded + // is okay under the invariant that events are written in the same epoch during + // which the class unloaded. We save classes that unloaded during an epoch and + // publish them upon epoch rotation. + // + // Precautions are necessary because of complexities involving physical recording start/stop, + // where we must be careful not to rotate away saved unloaded class IDs before they have been drained. + if (_stale_class_ids == _empty_class_ids) { + if (_current_unloaded_class_ids->is_nonempty()) { + // Since we have rotated, we publicize the list of classes unloaded during the previous epoch. + log_debug(jfr, methodtrace)("Since we have rotated, we publicize the list of classes unloaded during the previous epoch."); + _stale_class_ids = _current_unloaded_class_ids; + // Rotating the sets for tracking the unloaded class ids. + _current_unloaded_class_ids = _current_unloaded_class_ids == unloaded_class_ids_0() ? unloaded_class_ids_1() : unloaded_class_ids_0(); + } + return; + } + + // The previously published unloaded classes are yet to be drained. + // Most likely because we are now starting a new physical recording. + // Move over all newly unloaded class IDs to make them available for drainage. + assert(_stale_class_ids != _current_unloaded_class_ids, "invariant"); + if (_current_unloaded_class_ids->is_nonempty()) { + log_debug(jfr, methodtrace)("Appending unloaded classes during the previous epoch."); + _stale_class_ids->appendAll(_current_unloaded_class_ids); + _current_unloaded_class_ids->clear(); + } + assert(_current_unloaded_class_ids->is_empty(), "invariant"); +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.hpp new file mode 100644 index 0000000000000..1c44a93fcb28e --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRMETHODTRACER_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRMETHODTRACER_HPP + +#include "jni.h" +#include "jfr/support/methodtracer/jfrInstrumentedClass.hpp" +#include "jfr/support/methodtracer/jfrTracedMethod.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "memory/allocation.hpp" + +class ClassFileParser; +class InstanceKlass; +class JavaThread; +class JfrFilterClassClosure; +class Klass; +class ModuleEntry; + +template class GrowableArray; + +// +// Class responsible for installing and evaluating filters, collecting methods to +// be instrumented and calling Java to create the appropriate bytecode. +/// +class JfrMethodTracer: AllStatic { + private: + static ModuleEntry* _jdk_jfr_module; // Guarded by Module_lock + static GrowableArray* _instrumented_classes; // Guarded by ClassLoaderDataGraph_lock + static GrowableArray* _timing_entries; // Guarded by ClassLoaderDataGraph_lock + + static ModuleEntry* jdk_jfr_module(); + static void add_timing_entry(const InstanceKlass* ik, traceid klass_id); + static void retransform(JNIEnv* env, const JfrFilterClassClosure& classes, TRAPS); + static void add_instrumented_class(InstanceKlass* ik, GrowableArray* methods); + + public: + static bool in_use(); + static jlongArray drain_stale_class_ids(TRAPS); + static void add_to_unloaded_set(const Klass* k); + static void trim_instrumented_classes(bool trim); + static GrowableArray* instrumented_classes(); + static void on_klass_redefinition(const InstanceKlass* ik, Thread* thread); + static void on_klass_creation(InstanceKlass*& ik, ClassFileParser& parser, TRAPS); + static jlongArray set_filters(JNIEnv* env, + jobjectArray classes, + jobjectArray methods, + jobjectArray annotations, + jintArray modifications, + TRAPS); +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRMETHODTRACER_HPP diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrTraceTagging.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrTraceTagging.cpp new file mode 100644 index 0000000000000..e5018212768fb --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrTraceTagging.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2025, 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 "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" +#include "jfr/support/methodtracer/jfrInstrumentedClass.hpp" +#include "jfr/support/methodtracer/jfrTraceTagging.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "utilities/growableArray.hpp" + +void JfrTraceTagging::tag_dynamic(const InstanceKlass* ik) { + JfrTraceIdLoadBarrier::load_barrier(ik); +} + +void JfrTraceTagging::tag_dynamic(const Method* method) { + JfrTraceId::load_no_enqueue(method); +} + +void JfrTraceTagging::tag_dynamic(const InstanceKlass* ik, const GrowableArray* methods) { + assert(ik != nullptr, "invariant"); + assert(methods != nullptr, "invariant"); + + for (int i = 0; i < methods->length(); ++i) { + const Method* const method = methods->at(i).method(); + assert(method != nullptr, "invariant"); + if (!method->is_old()) { + tag_dynamic(method); + continue; + } + // A redefinition / retransformation interleaved. + // Find and tag the latest version of the method. + tag_dynamic(ik->method_with_orig_idnum(method->orig_method_idnum())); + } +} + +void JfrTraceTagging::set_dynamic_tag(const InstanceKlass* ik, const GrowableArray* methods) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invariant"); + + tag_dynamic(ik, methods); + tag_dynamic(ik); +} + +void JfrTraceTagging::set_dynamic_tag_for_sticky_bit(const InstanceKlass* ik) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invariant"); + assert(JfrTraceId::has_sticky_bit(ik), "invariant"); + + const int length = ik->methods()->length(); + for (int i = 0; i < length; ++i) { + const Method* const m = ik->methods()->at(i); + if (JfrTraceId::has_sticky_bit(m)) { + tag_dynamic(m); + } + } + tag_dynamic(ik); +} + +void JfrTraceTagging::tag_sticky(const InstanceKlass* ik) { + JfrTraceId::set_sticky_bit(ik); +} + +void JfrTraceTagging::tag_sticky(const Method* method) { + JfrTraceId::set_sticky_bit(method); +} + +void JfrTraceTagging::tag_sticky(const InstanceKlass* ik, const GrowableArray* methods) { + assert(ik != nullptr, "invariant"); + assert(methods != nullptr, "invariant"); + + for (int i = 0; i < methods->length(); ++i) { + const Method* const method = methods->at(i).method(); + assert(method != nullptr, "invariant"); + if (!method->is_old()) { + tag_sticky(method); + continue; + } + // A redefinition / retransformation interleaved. + // Find and tag the latest version of the method. + tag_sticky(ik->method_with_orig_idnum(method->orig_method_idnum())); + } +} + +void JfrTraceTagging::tag_timing(const InstanceKlass* ik) { + JfrTraceId::set_timing_bit(ik); +} + +void JfrTraceTagging::install_sticky_bit_for_retransform_klass(const InstanceKlass* ik, const GrowableArray* methods, bool timing) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invariant"); + + MutexLocker lock(ClassLoaderDataGraph_lock); + if (JfrTraceId::has_sticky_bit(ik)) { + clear_sticky_bit(ik); + } + tag_sticky(ik, methods); + tag_sticky(ik); + if (timing) { + tag_timing(ik); + } +} + +void JfrTraceTagging::set_sticky_bit(const InstanceKlass* ik, const GrowableArray* methods) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + + tag_sticky(ik, methods); + tag_sticky(ik); +} + +void JfrTraceTagging::clear_sticky_bit(const InstanceKlass* ik, bool dynamic_tag /* true */) { + assert(ik != nullptr, "invariant"); + assert(!ik->is_scratch_class(), "invariant"); + assert(JfrTraceId::has_sticky_bit(ik), "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + + const Array* const methods = ik->methods(); + assert(methods != nullptr, "invariant"); + const int length = methods->length(); + for (int i = 0; i < length; ++i) { + const Method* const m = methods->at(i); + if (JfrTraceId::has_sticky_bit(m)) { + if (dynamic_tag) { + tag_dynamic(m); + } + JfrTraceId::clear_sticky_bit(m); + } + } + if (dynamic_tag) { + tag_dynamic(ik); + } + JfrTraceId::clear_sticky_bit(ik); + if (JfrTraceId::has_timing_bit(ik)) { + JfrTraceId::clear_timing_bit(ik); + } + assert(!JfrTraceId::has_timing_bit(ik), "invariant"); +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrTraceTagging.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrTraceTagging.hpp new file mode 100644 index 0000000000000..d2aa6f8bb04ac --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrTraceTagging.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRTRACETAGGING_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRTRACETAGGING_HPP + +#include "jfr/support/methodtracer/jfrTracedMethod.hpp" +#include "memory/allStatic.hpp" + +class InstanceKlass; +class Method; + +template class GrowableArray; + +// +// Class responsible for setting setting sticky, epoch, and timing bits. +// +class JfrTraceTagging : AllStatic { + private: + static void tag_dynamic(const InstanceKlass* ik); + static void tag_dynamic(const InstanceKlass* ik, const GrowableArray* methods); + static void tag_dynamic(const Method* method); + static void tag_sticky(const InstanceKlass* ik); + static void tag_sticky(const Method* method); + static void tag_sticky(const InstanceKlass* ik, const GrowableArray* methods); + static void tag_timing(const InstanceKlass* ik); + public: + static void set_dynamic_tag(const InstanceKlass* ik, const GrowableArray* methods); + static void set_dynamic_tag_for_sticky_bit(const InstanceKlass* ik); + static void install_sticky_bit_for_retransform_klass(const InstanceKlass* existing_klass, const GrowableArray* methods, bool timing); + static void set_sticky_bit(const InstanceKlass* ik, const GrowableArray* methods); + static void clear_sticky_bit(const InstanceKlass* ik, bool dynamic_tag = true); +}; + +#endif /* SHARE_JFR_SUPPORT_METHODTRACER_JFRTRACETAGGING_HPP */ diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrTracedMethod.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrTracedMethod.cpp new file mode 100644 index 0000000000000..e6580c9b4234e --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrTracedMethod.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, 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 "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/support/methodtracer/jfrTracedMethod.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" + +JfrTracedMethod::JfrTracedMethod() : + _id(0), _name(nullptr), _signature(nullptr), + _method(nullptr), _modification(0), _methods_array_index(0) { +} + +JfrTracedMethod::JfrTracedMethod(const InstanceKlass* ik, + const Method* method, + int32_t modification, + int32_t methods_array_index) : + _id(JfrTraceId::load_raw(ik, method)), _name(method->name()), + _signature(method->signature()), _method(nullptr), + _modification(modification), _methods_array_index(methods_array_index) { + assert(_method == nullptr, "invariant"); +} + +void JfrTracedMethod::set_method(const Method* method) { + assert(method != nullptr, "invariant"); + assert(JfrTraceId::load_raw(method) == _id, "invariant"); + assert(_method == nullptr, "invariant"); + _method = method; +} diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrTracedMethod.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrTracedMethod.hpp new file mode 100644 index 0000000000000..2c789c8f4fac7 --- /dev/null +++ b/src/hotspot/share/jfr/support/methodtracer/jfrTracedMethod.hpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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_SUPPORT_METHODTRACER_JFRTRACEDMETHOD_HPP +#define SHARE_JFR_SUPPORT_METHODTRACER_JFRTRACEDMETHOD_HPP + +#include "jfr/utilities/jfrTypes.hpp" + +class InstanceKlass; +class Method; +class Symbol; + +// +// Method that has been filtered out for tracing, +// may or may not yet be instrumented. +// +class JfrTracedMethod { + private: + traceid _id; + const Symbol* _name; + const Symbol* _signature; + const Method* _method; + int32_t _modification; + int32_t _methods_array_index; + + public: + JfrTracedMethod(); + JfrTracedMethod(const InstanceKlass* ik, + const Method* method, + int32_t modification, + int32_t methods_array_index); + + traceid id() const { + return _id; + } + + const Symbol* name() const { + return _name; + } + + const Symbol* signature() const { + return _signature; + } + + const Method* method() const { + return _method; + } + + void set_method(const Method* method); + + int32_t modification() const { + return _modification; + } + + int32_t methods_array_index() const { + return _methods_array_index; + } +}; + +#endif // SHARE_JFR_SUPPORT_METHODTRACER_JFRTRACEDMETHOD_HPP diff --git a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp index 70404d22e3ff0..034f5d204740b 100644 --- a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp +++ b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp @@ -62,7 +62,8 @@ JFR_LOG_TAG(jfr, event) \ JFR_LOG_TAG(jfr, setting) \ JFR_LOG_TAG(jfr, dcmd) \ - JFR_LOG_TAG(jfr, start) + JFR_LOG_TAG(jfr, start) \ + JFR_LOG_TAG(jfr, methodtrace) /* NEW TAGS, DONT FORGET TO UPDATE JAVA SIDE */ #endif // SHARE_JFR_UTILITIES_JFRLOGTAGSETS_HPP diff --git a/src/hotspot/share/jfr/utilities/jfrRelation.hpp b/src/hotspot/share/jfr/utilities/jfrRelation.hpp index a6a15181d1ed8..9c7dce3d4512e 100644 --- a/src/hotspot/share/jfr/utilities/jfrRelation.hpp +++ b/src/hotspot/share/jfr/utilities/jfrRelation.hpp @@ -32,6 +32,10 @@ inline int compare_traceid(const traceid& lhs, const traceid& rhs) { return lhs > rhs ? 1 : (lhs < rhs) ? -1 : 0; } +inline bool equals_traceid(const traceid& lhs, const traceid& rhs) { + return lhs == rhs; +} + inline int sort_traceid(traceid* lhs, traceid* rhs) { return compare_traceid(*lhs, *rhs); } diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index d61e2461e8da6..a0bd07ed61bb1 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -127,6 +127,7 @@ class outputStream; LOG_TAG(metaspace) \ LOG_TAG(methodcomparator) \ LOG_TAG(methodhandles) \ + LOG_TAG(methodtrace) \ LOG_TAG(mirror) \ LOG_TAG(mmu) \ LOG_TAG(module) \ diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 124193c5a824d..e95c78c7dd6d7 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2957,8 +2957,8 @@ void InstanceKlass::set_minor_version(u2 minor_version) { _constants->set_minor_ u2 InstanceKlass::major_version() const { return _constants->major_version(); } void InstanceKlass::set_major_version(u2 major_version) { _constants->set_major_version(major_version); } -InstanceKlass* InstanceKlass::get_klass_version(int version) { - for (InstanceKlass* ik = this; ik != nullptr; ik = ik->previous_versions()) { +const InstanceKlass* InstanceKlass::get_klass_version(int version) const { + for (const InstanceKlass* ik = this; ik != nullptr; ik = ik->previous_versions()) { if (ik->constants()->version() == version) { return ik; } @@ -4456,7 +4456,7 @@ void InstanceKlass::add_previous_version(InstanceKlass* scratch_class, #endif // INCLUDE_JVMTI -Method* InstanceKlass::method_with_idnum(int idnum) { +Method* InstanceKlass::method_with_idnum(int idnum) const { Method* m = nullptr; if (idnum < methods()->length()) { m = methods()->at(idnum); @@ -4475,7 +4475,7 @@ Method* InstanceKlass::method_with_idnum(int idnum) { } -Method* InstanceKlass::method_with_orig_idnum(int idnum) { +Method* InstanceKlass::method_with_orig_idnum(int idnum) const { if (idnum >= methods()->length()) { return nullptr; } @@ -4495,13 +4495,12 @@ Method* InstanceKlass::method_with_orig_idnum(int idnum) { } -Method* InstanceKlass::method_with_orig_idnum(int idnum, int version) { - InstanceKlass* holder = get_klass_version(version); +Method* InstanceKlass::method_with_orig_idnum(int idnum, int version) const { + const InstanceKlass* holder = get_klass_version(version); if (holder == nullptr) { return nullptr; // The version of klass is gone, no method is found } - Method* method = holder->method_with_orig_idnum(idnum); - return method; + return holder->method_with_orig_idnum(idnum); } #if INCLUDE_JVMTI diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 0c211c8c14b43..52589e2cdd964 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -349,9 +349,9 @@ class InstanceKlass: public Klass { // methods Array* methods() const { return _methods; } void set_methods(Array* a) { _methods = a; } - Method* method_with_idnum(int idnum); - Method* method_with_orig_idnum(int idnum); - Method* method_with_orig_idnum(int idnum, int version); + Method* method_with_idnum(int idnum) const; + Method* method_with_orig_idnum(int idnum) const; + Method* method_with_orig_idnum(int idnum, int version) const; // method ordering Array* method_ordering() const { return _method_ordering; } @@ -688,7 +688,7 @@ class InstanceKlass: public Klass { InstanceKlass* previous_versions() const { return nullptr; } #endif - InstanceKlass* get_klass_version(int version); + const InstanceKlass* get_klass_version(int version) const; bool has_been_redefined() const { return _misc_flags.has_been_redefined(); } void set_has_been_redefined() { _misc_flags.set_has_been_redefined(true); } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index 33ae15fd80531..af9d973d2f142 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -4386,6 +4386,8 @@ void VM_RedefineClasses::redefine_single_class(Thread* current, jclass the_jclas // keep track of previous versions of this class the_class->add_previous_version(scratch_class, emcp_method_count); + JFR_ONLY(ON_KLASS_REDEFINITION(the_class, current);) + _timer_rsc_phase1.stop(); if (log_is_enabled(Info, redefine, class, timer)) { _timer_rsc_phase2.start(); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java new file mode 100644 index 0000000000000..3eb3116db552e --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, 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.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.Timespan; +import jdk.jfr.internal.RemoveFields; + +@Name("jdk.MethodTiming") +@Label("Method Timing") +@Category({ "Java Virtual Machine", "Method Tracing" }) +@RemoveFields({ "duration", "eventThread", "stackTrace" }) +public final class MethodTimingEvent extends AbstractJDKEvent { + + @Label("Method") + public long method; + + @Label("Invocations") + public long invocations; + + @Label("Average Time") + @Timespan + public long average; + + public static void commit(long start, long method, long invocations, long average) { + // Generated by JFR + } + + public static long timestamp() { + // Generated by JFR + return 0; + } + + public static boolean enabled() { + // Generated by JFR + return false; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTraceEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTraceEvent.java new file mode 100644 index 0000000000000..b76c6c4173814 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTraceEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025, 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.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("jdk.MethodTrace") +@Label("Method Trace") +@Category({ "Java Virtual Machine", "Method Tracing" }) +@StackTrace(true) +public final class MethodTraceEvent extends AbstractJDKEvent { + + @Label("Method") + private long method; + + public static void commit(long start, long duration, long method) { + // Generated by JFR + } + + public static long timestamp() { + // Generated by JFR + return 0; + } + + public static boolean enabled() { + // Generated by JFR + return false; + } +} 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 5082395115203..f43e45b724eb9 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -49,10 +49,12 @@ import jdk.jfr.internal.settings.CutoffSetting; import jdk.jfr.internal.settings.EnabledSetting; import jdk.jfr.internal.settings.LevelSetting; +import jdk.jfr.internal.settings.MethodSetting; import jdk.jfr.internal.settings.PeriodSetting; import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThrottleSetting; +import jdk.jfr.internal.tracing.Modification; import jdk.jfr.internal.util.Utils; // This class can't have a hard reference from PlatformEventType, since it @@ -70,6 +72,7 @@ record NamedControl(String name, Control control) { private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.class); private static final long STACK_FILTER_ID = Type.getTypeId(StackFilter.class); private static final Type TYPE_LEVEL = TypeLibrary.createType(LevelSetting.class); + private static final Type TYPE_METHOD_FILTER = TypeLibrary.createType(MethodSetting.class); private final ArrayList settingControls = new ArrayList<>(); private final ArrayList namedControls = new ArrayList<>(5); @@ -95,6 +98,10 @@ record NamedControl(String name, Control control) { if (eventType.hasLevel()) { addControl(Level.NAME, defineLevel(eventType)); } + if (eventType.getModification() != Modification.NONE) { + addControl("filter", defineMethodFilter(eventType, eventType.getModification())); + } + addControl(Enabled.NAME, defineEnabled(eventType)); addStackFilters(eventType); @@ -335,11 +342,23 @@ private static Control definePeriod(PlatformEventType type) { return new Control(new PeriodSetting(type, def), def); } + private Control defineMethodFilter(PlatformEventType type, Modification modification) { + String def = ""; + type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_METHOD_FILTER, "filter", def, Collections.emptyList())); + return new Control(new MethodSetting(type, modification, def), def); + } + void disable() { for (NamedControl nc : namedControls) { if (nc.control.isType(EnabledSetting.class)) { nc.control.setValue("false"); return; + } else { + String v = nc.control.getDefaultValue(); + // Avoids slow retransformation during shutdown + if (v != null && !PlatformRecorder.isInShutDown()) { + nc.control.setValue(v); + } } } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java index f0b7dcc2bf2ba..a28f1fdd41f9f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java @@ -42,7 +42,11 @@ import jdk.jfr.events.ContainerMemoryUsageEvent; import jdk.jfr.events.DirectBufferStatisticsEvent; import jdk.jfr.events.InitialSecurityPropertyEvent; +import jdk.jfr.events.MethodTimingEvent; +import jdk.jfr.events.MethodTraceEvent; import jdk.jfr.internal.periodic.PeriodicEvents; +import jdk.jfr.internal.tracing.PlatformTracer; +import jdk.jfr.tracing.MethodTracer; public final class JDKEvents { @@ -73,6 +77,8 @@ public final class JDKEvents { jdk.internal.event.X509ValidationEvent.class, DirectBufferStatisticsEvent.class, InitialSecurityPropertyEvent.class, + MethodTraceEvent.class, + MethodTimingEvent.class, }; private static final Runnable emitExceptionStatistics = JDKEvents::emitExceptionStatistics; @@ -83,6 +89,7 @@ public final class JDKEvents { private static final Runnable emitContainerMemoryUsage = JDKEvents::emitContainerMemoryUsage; private static final Runnable emitContainerIOUsage = JDKEvents::emitContainerIOUsage; private static final Runnable emitInitialSecurityProperties = JDKEvents::emitInitialSecurityProperties; + private static final Runnable emitMethodTiming = JDKEvents::emitMethodTiming; private static Metrics containerMetrics = null; private static boolean initializationTriggered; @@ -96,6 +103,7 @@ public static synchronized void initialize() { PeriodicEvents.addJavaEvent(jdk.internal.event.ExceptionStatisticsEvent.class, emitExceptionStatistics); PeriodicEvents.addJavaEvent(DirectBufferStatisticsEvent.class, emitDirectBufferStatistics); PeriodicEvents.addJavaEvent(InitialSecurityPropertyEvent.class, emitInitialSecurityProperties); + PeriodicEvents.addJavaEvent(MethodTimingEvent.class, emitMethodTiming); initializeContainerEvents(); JFRTracing.enable(); @@ -200,6 +208,7 @@ public static void remove() { PeriodicEvents.removeEvent(emitExceptionStatistics); PeriodicEvents.removeEvent(emitDirectBufferStatistics); PeriodicEvents.removeEvent(emitInitialSecurityProperties); + PeriodicEvents.removeEvent(emitMethodTiming); PeriodicEvents.removeEvent(emitContainerConfiguration); PeriodicEvents.removeEvent(emitContainerCPUUsage); @@ -224,4 +233,10 @@ private static void emitInitialSecurityProperties() { } } } + + private static void emitMethodTiming() { + if (MethodTimingEvent.enabled()) { + PlatformTracer.emitTiming(); + } + } } 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 1c6859990db31..e0eaef74b4c44 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, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, 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 @@ -682,4 +682,68 @@ public final class JVM { * @return {@code true} if this is a product build, {@code false} otherwise. */ public static native boolean isProduct(); + + /** + * Sets method tracing filters. + * + * A filter can be a class, a method, or an annotation. + * + * For example, the following three filters: + *
    + *
  • Method timing on all methods in class com.foo.Bar
  • + *
  • Method tracing on the method com.foo.Bar::baz
  • + *
  • Method timing and tracing on all methods or classes with the annotation @com.foo.Foo
  • + *
+ * can be set using the following code: + *
+     * String[] classes = new String[3];
+     * classes[0] = "com/foo/Bar";
+     * classes[1] = "com/foo/Bar";
+     * classes[2] = null;
+     *
+     * String[] methods = new String[3];
+     * methods[0] = null;
+     * methods[1] = "baz";
+     * methods[2] = null;
+     *
+     * String[] annotations = new String[3];
+     * annotations[0] = null;
+     * annotations[1] = null;
+     * annotations[2] = "com/foo/Foo";
+     *
+     * int[] modifications = new int[3];
+     * modifications[0] = 1; // filter should apply to timing
+     * modifications[1] = 2; // filter should apply to tracing
+     * modifications[2] = 1 | 2; // filter should apply to both timing and tracing
+     *
+     * JVM.setMethodTraceFilters(classes, methods, annotations, modifications);
+     * 
+ * The filter will be applied to currently and future loaded classes. + *

+ * If a method is overloaded, the filter matches against all methods. It's not possible + * to match specific method parameters or annotation values. + *

+ * Only one type of a filter - class, method, or annotation - can be used per array index. + *

+ * If the filter is matched, JVMUpcalls::onMethodTrace will be invoked with + * the bytecode. If a filter is replaced, and method no longer requires instrumentation, + * the method will also be called with modification = 0; + * + * @param classes, not {@code null}, array of class names + * @param methods, not {@code null}, array of method names + * @param annotations, not {@code null}, array of annotation names + * @param modifications, not {@code null}, array of modification flags + * @return the published IDs, or null if no classes has been published. + */ + public static native long[] setMethodTraceFilters( + String[] classes, + String[] methods, + String[] annotations, + int[] modification); + /** + * Returns IDs for method-traced classes that have been unloaded. + * + * @return the unloaded IDs, or null if no unloading has occurred. + */ + public static native long[] drainStaleMethodTracerIds(); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java index 18b76cc3ff9fb..a82f47cac5502 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java @@ -25,10 +25,11 @@ package jdk.jfr.internal; import java.lang.reflect.Modifier; - import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.util.Bytecode; import jdk.jfr.internal.util.Utils; +import jdk.jfr.internal.tracing.PlatformTracer; + /** * All upcalls from the JVM should go through this class. * @@ -159,4 +160,35 @@ static Thread createRecorderThread(ThreadGroup systemThreadGroup, ClassLoader co thread.setContextClassLoader(contextClassLoader); return thread; } + + /** + * Called by the JVM to update method tracing instrumentation. + *

+ * @param module the module the class belongs to + * @param classLoader the class loader the class is being loaded for + * @param className the internal class name, i.e. java/lang/String. + * @param bytecode the bytecode to modify + * @param methodIds the method IDs + * @param names constant pool indices of method names + * @param signatures constant pool indices of method signatures + * @param modifications integer mask describing the modification + * + * @return the instrumented bytecode, or null if the class can't or shouldn't be modified. + */ + public static byte[] onMethodTrace(Module module, ClassLoader classLoader, String className, + byte[] bytecode, long[] methodIds, String[] names, String[] signatures, + int[] modifications) { + return PlatformTracer.onMethodTrace(module, classLoader, className, + bytecode, methodIds, names, signatures, + modifications); + } + + /** + * Called by the JVM to publish a class ID that can safely be used by the Method Timing event. + *

+ * @param classId the methods to be published + */ + public static void publishMethodTimersForClass(long classId) { + PlatformTracer.publishClass(classId); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java index 4babd74c73da4..855222bc8a425 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java @@ -101,7 +101,12 @@ public enum LogTag { /** * -XX:StartFlightRecording */ - JFR_START(16); + JFR_START(16), + /** + * Covers usage of MethodTiming and MethodTrace events + */ + JFR_METHODTRACE(17), + ; /* set from native side */ volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized 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 5c44f2416cf4e..1c9c985fb26db 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -205,7 +205,6 @@ private EventConfiguration makeConfiguration(Class recordings = new ArrayList<>(); private static final List changeListeners = new ArrayList<>(); private final Repository repository; @@ -67,7 +66,6 @@ public final class PlatformRecorder { private Timer timer; private long recordingCounter = 0; private RepositoryChunk currentChunk; - private boolean inShutdown; private boolean runPeriodicTask; public PlatformRecorder() throws Exception { @@ -150,12 +148,12 @@ public static void notifyRecorderInitialized(FlightRecorder recorder) { } } - synchronized void setInShutDown() { - this.inShutdown = true; + static void setInShutDown() { + inShutdown = true; } - synchronized boolean isInShutDown() { - return this.inShutdown; + static boolean isInShutDown() { + return inShutdown; } // called by shutdown hook diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java index d71c20968a089..b07a71dd4d5dd 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java @@ -172,7 +172,7 @@ public boolean stop(String reason) { dumpStopped(dest); Logger.log(LogTag.JFR, LogLevel.INFO, "Wrote recording \"" + getName() + "\" (" + getId() + ") to " + dest.getRealPathText()); notifyIfStateChanged(newState, oldState); - boolean reportOnExit = recorder.isInShutDown() && !reports.isEmpty(); + boolean reportOnExit = PlatformRecorder.isInShutDown() && !reports.isEmpty(); if (!reportOnExit) { close(); // remove if copied out, unless we are in shutdown and there are reports to report. } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java index 98bc0c86bfaf5..25cb202b0c24d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java @@ -31,6 +31,7 @@ public final class SecuritySupport { private static final Module JFR_MODULE = Event.class.getModule(); + private static final String TRACING_PACKAGE_NAME = "jdk.jfr.tracing"; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); static { @@ -62,6 +63,10 @@ static void addEventsExport(Class clazz) { Modules.addExports(JFR_MODULE, "jdk.jfr.events", clazz.getModule()); } + public static void addTracingExport() { + Modules.addExports(JFR_MODULE, TRACING_PACKAGE_NAME); + } + static void addReadEdge(Class clazz) { Modules.addReads(clazz.getModule(), JFR_MODULE); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java index df9fdc2815b70..d34938fdfd800 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java @@ -46,7 +46,7 @@ public void run() { // starting any "real" operations. In low memory situations, // we would like to take an OOM as early as possible. tlabDummyObject = new Object(); - recorder.setInShutDown(); + PlatformRecorder.setInShutDown(); for (PlatformRecording recording : recorder.getRecordings()) { if (recording.getDumpOnExit() && recording.getState() == RecordingState.RUNNING) { dump(recording); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Field.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Field.java index 794d6370c572f..90cd1c40a7640 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Field.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -135,6 +135,8 @@ final class Field { // Text to render if value is missing, typically used when value is null public String missingText = "N/A"; + public int precision = -1; + public Field(FilteredType type, String name) { this.type = type; this.name = name; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldFormatter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldFormatter.java index 117dc6d01f3c5..989e2231eb185 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldFormatter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -123,7 +123,7 @@ private static String format(Field field, Object object, boolean compact) { if (d.equals(ChronoUnit.FOREVER.getDuration())) { return "Indefinite"; } - return ValueFormatter.formatDuration(d); + return ValueFormatter.formatDuration(d, field.precision); } if (object instanceof Instant instant) { return ValueFormatter.formatTimestamp(instant); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryParser.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryParser.java index dfaeab38b85a6..51cb2cb03e741 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryParser.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.function.Consumer; import jdk.jfr.internal.query.Configuration.Truncate; @@ -280,6 +281,11 @@ private Property property() throws ParseException { if (text.startsWith("cell-height:")) { yield cellHeight(text.substring("cell-height:".length())); } + // This option is experimental and may not work properly + // with rounding and truncation. + if (text.startsWith("ms-precision:")) { + yield millisPrecision(text.substring("ms-precision:".length())); + } throw new ParseException("Unknown formatter '" + text + "'", position()); } }; @@ -306,6 +312,18 @@ private Consumer cellHeight(String height) throws ParseException { } } + private Consumer millisPrecision(String digits) throws ParseException { + try { + int d = Integer.parseInt(digits); + if (d < 0) { + throw new ParseException("Expected 'precision:' to be at least 0' ", position()); + } + return field -> field.precision = d; + } catch (NumberFormatException nfe) { + throw new ParseException("Not valid number for 'precision:' " + digits, position()); + } + } + public int position() { return tokenizer.getPosition(); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index 51dc2ca11bff9..a6192db1ab00d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -1,5 +1,5 @@ ; -; Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. +; Copyright (c) 2023, 2025, 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 @@ -446,6 +446,19 @@ table = "COLUMN 'Alloc. Time', 'Application Method', 'Object Age', 'Heap Usage' SELECT LAST_BATCH(allocationTime), LAST_BATCH(stackTrace.topApplicationFrame), LAST_BATCH(objectAge), LAST_BATCH(lastKnownHeapUsage) FROM OldObjectSample GROUP BY stackTrace.topApplicationFrame ORDER BY allocationTime" +[application.method-timing] +label = "Method Timing" +table = "COLUMN 'Timed Method', 'Invocations', 'Average Time' + FORMAT none, none, ms-precision:6 + SELECT LAST_BATCH(method) AS M, LAST_BATCH(invocations), LAST_BATCH(average) + FROM jdk.MethodTiming GROUP BY method ORDER BY average" + +[application.method-calls] +label = "Method Calls" +table = "COLUMN 'Traced Method', 'Caller', 'Invocations' + SELECT method as M, stackTrace.topFrame.method AS S, COUNT(*) AS C + FROM jdk.MethodTrace GROUP BY M, S ORDER BY C DESC" + [application.modules] label = "Modules" table = "SELECT LAST(source.name) AS S FROM ModuleRequire GROUP BY source.name ORDER BY S" diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/FilterSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/FilterSetting.java new file mode 100644 index 0000000000000..3741a5932b373 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/FilterSetting.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.settings; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeSet; + +import jdk.jfr.SettingControl; +import jdk.jfr.internal.PlatformEventType; + +/** + * Base class for settings that consists of semicolon-separated filters. + */ +public abstract class FilterSetting extends SettingControl { + private final List activeFilters = new ArrayList<>(); + private final PlatformEventType eventType; + private final String defaultValue; + private String value; + + public FilterSetting(PlatformEventType eventType, String defaultValue) { + this.eventType = Objects.requireNonNull(eventType); + this.defaultValue = defaultValue; + } + + protected abstract void apply(PlatformEventType eventType, List text); + + protected abstract boolean isValid(String text); + + @Override + public final String combine(Set settingValues) { + List filters = normalize(settingValues); + if (!filters.isEmpty()) { + return format(filters); + } + return defaultValue; + } + + @Override + public final void setValue(String settingValue) { + List filters = normalize(Set.of(settingValue)); + if (activeFilters.equals(filters)) { + return; + } + apply(eventType, filters); + this.value = settingValue; + this.activeFilters.clear(); + this.activeFilters.addAll(filters); + } + + @Override + public final String getValue() { + return value; + } + + // Split, trim, sort and remove duplicates filters. + private List normalize(Set settingValues) { + Set filters = new TreeSet<>(); + for (String value : settingValues) { + for (String filter : value.split(";")) { + filter = filter.strip(); + if (!filter.isEmpty() && !filter.isBlank() && isValid(filter)) { + filters.add(filter); + } + } + } + return new ArrayList<>(filters); + } + + private static String format(List filters) { + StringJoiner sj = new StringJoiner(";"); + filters.forEach(sj::add); + return sj.toString(); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/MethodSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/MethodSetting.java new file mode 100644 index 0000000000000..f819b1b951d9d --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/MethodSetting.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.settings; + +import java.util.List; + +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.internal.PlatformEventType; +import jdk.jfr.internal.Type; +import jdk.jfr.internal.tracing.Modification; +import jdk.jfr.internal.tracing.PlatformTracer; + +@MetadataDefinition +@Label("Filter") +@Description("Methods to be filtered") +@Name(Type.SETTINGS_PREFIX + "Filter") +public final class MethodSetting extends FilterSetting { + private final Modification modification; + private static volatile boolean initialized; + + public MethodSetting(PlatformEventType eventType, Modification modification, String defaultValue) { + super(eventType, defaultValue); + this.modification = modification; + } + + public boolean isValid(String text) { + return PlatformTracer.isValidFilter(text); + } + + @Override + protected void apply(PlatformEventType eventType, List filters) { + if (!initialized) { + PlatformTracer.initialize(); + initialized = true; + } + PlatformTracer.setFilters(modification, filters); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/ExcludeList.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/ExcludeList.java new file mode 100644 index 0000000000000..8aa889dd68d5c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/ExcludeList.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +// // The JVM will skip all classes in the jdk.jfr module, so it's not added here. +public final class ExcludeList { + private static final String[] EXCLUDED_CLASSES = { + // Used by MethodTiming event to accumulate invocations. + "java/util/concurrent/atomic/AtomicLong", + // Used by EventWriter + "sun/misc/Unsafe", + "jdk/internal/misc/Unsafe;", + }; + + private static final String[] EXCLUDED_PREFIX = { + // Used by MethodTiming event to store invocations, including inner classes. + "java/util/concurrent/ConcurrentHashMap", + // Can't trigger of these classes during PlatformTracer::onMethodTrace(...) + "jdk/internal/", // jdk/internal/classfile, jdk/internal/loader and jdk/internal/foreign + "java/lang/classfile/" + }; + + private static final String[] EXCLUDED_METHODS = { + // Long used by MethodTiming event when looking up entry for timing entry + "java.lang.Long::", + "java.lang.Long::valueOf", + "java.lang.Number::" + }; + + public static boolean containsMethod(String methodName) { + for (String method : EXCLUDED_METHODS) { + if (method.equals(methodName)) { + return true; + } + } + return false; + } + + public static boolean containsClass(String className) { + for (String clazz: EXCLUDED_CLASSES) { + if (clazz.equals(className)) { + return true; + } + } + for (String prefix : EXCLUDED_PREFIX) { + if (className.startsWith(prefix)) { + return true; + } + } + return false; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Filter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Filter.java new file mode 100644 index 0000000000000..782cc3e56d1d1 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Filter.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import jdk.internal.module.Checks; + +/** + * Class that represents the filter a user can specify for the MethodTrace and + * MethodTiming event. + */ +record Filter(String className, String methodName, String annotationName, Modification modification) { + + static Filter of(String filter, Modification modification) { + if (filter.startsWith("@")) { + return ofAnnotation(filter, modification); + } + if (filter.contains("::")) { + return ofMethod(filter, modification); + } + return ofClass(filter, modification); + } + + private static Filter ofAnnotation(String filter, Modification modification) { + String annotation = filter.substring(1); + if (Checks.isClassName(annotation)) { + return new Filter(null, null, annotation, modification); + } + return null; + } + + private static Filter ofMethod(String filter, Modification modification) { + int index = filter.indexOf("::"); + String classPart = filter.substring(0, index); + String methodPart = filter.substring(index + 2); + if (methodPart.isEmpty()) { + // Don't allow "foo.Bar::". User should specify "foo.Bar". + return null; + } + + if (isMethod(methodPart)) { + // Method name only, i.e. "::baz" + if (classPart.isEmpty()) { + return new Filter(null, methodPart, null, modification); + } + // Fully qualified method name, i.e. "foo.Bar::baz" + if (isValidClassName(classPart)) { + return new Filter(classPart, methodPart, null, modification); + } + } + return null; + } + + private static boolean isMethod(String methodName) { + if (methodName.equals("") || methodName.equals("")) { + return true; + } + return Checks.isJavaIdentifier(methodName); + } + + private static Filter ofClass(String filter, Modification modification) { + if (isValidClassName(filter)) { + return new Filter(filter, null, null, modification); + } + return null; + } + + private static boolean isValidClassName(String text) { + return Checks.isClassName(text); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java new file mode 100644 index 0000000000000..dbafca4ed3cea --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassFile.ClassHierarchyResolverOption; +import java.lang.classfile.ClassFile.Option; +import java.lang.classfile.ClassHierarchyResolver; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.MethodTransform; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; + +/** + * Class that adds bytecode instrumentation for a class. + */ +final class Instrumentation { + private final Map modificationMap = new LinkedHashMap<>(); + private final String className; + private final ClassLoader classLoader; + private final byte[] bytecode; + + public Instrumentation(ClassLoader classLoader, String internalClassName, byte[] bytecode) { + this.className = internalClassName.replace("/", "."); + this.classLoader = classLoader; + this.bytecode = bytecode; + } + + public void addMethod(long methodId, String name, String signature, int modification) { + modificationMap.put(name + signature, new Method(methodId, Modification.valueOf(modification), className + "::" + name)); + } + + public List getMethods() { + return new ArrayList<>(modificationMap.values()); + } + + public byte[] generateBytecode() { + boolean[] modified = new boolean[1]; + ClassFile classFile = ClassFile.of(resolverOption()); + ClassModel classModel = classFile.parse(bytecode); + byte[] generated = classFile.build(classModel.thisClass().asSymbol(), classBuilder -> { + for (var ce : classModel) { + if (modifyClassElement(classBuilder, ce)) { + modified[0] = true; + } else { + classBuilder.with(ce); + } + } + }); + return modified[0] ? generated : null; + } + + private Option resolverOption() { + return ClassHierarchyResolverOption.of(resolver()); + } + + private ClassHierarchyResolver resolver() { + if (classLoader == null) { + return ClassHierarchyResolver.ofResourceParsing(ClassLoader.getSystemClassLoader()); + } else { + return ClassHierarchyResolver.ofResourceParsing(classLoader); + } + } + + private boolean modifyClassElement(ClassBuilder classBuilder, ClassElement ce) { + if (ce instanceof MethodModel mm) { + String method = mm.methodName().stringValue(); + String signature = mm.methodType().stringValue(); + String full = method + signature; + Method tm = modificationMap.get(full); + if (tm != null) { + Modification m = tm.modification(); + if (m.tracing() || m.timing()) { + return modifyMethod(classBuilder, mm, tm); + } + } + } + return false; + } + + private boolean modifyMethod(ClassBuilder classBuilder, MethodModel m, Method method) { + var code = m.code(); + if (code.isPresent()) { + if (classLoader == null && ExcludeList.containsMethod(method.name())) { + String msg = "Risk of recursion, skipping bytecode generation of " + method.name(); + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.DEBUG, msg); + return false; + } + MethodTransform s = MethodTransform.ofStateful( + () -> MethodTransform.transformingCode(new Transform(method)) + ); + classBuilder.transformMethod(m, s); + return true; + } + return false; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java new file mode 100644 index 0000000000000..d685083153d6c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; + +/** + * Class that holds information about an instrumented method. + */ +record Method(long methodId, Modification modification, String name) { + @Override + public String toString() { + return name + (modification.timing() ? " +timing" : " -timing") + (modification.tracing() ? " +tracing" : " -tracing") + " (Method ID: " + String.format("0x%08X)", methodId); + } + + public long classId() { + return methodId() >> 16; + } + + public boolean isTiming() { + return modification.timing(); + } + + public void log(String msg) { + if (Logger.shouldLog(LogTag.JFR_METHODTRACE, LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.DEBUG, msg + " for " + this); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Modification.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Modification.java new file mode 100644 index 0000000000000..3f252214d9250 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Modification.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +/** + * Class that holds the type of instrumentation that can be applied to a method. + */ +public record Modification(boolean timing, boolean tracing) { + public static final Modification TIMING = new Modification(true, false); + public static final Modification TRACING = new Modification(false, true); + public static final Modification NONE = new Modification(false, false); + + static Modification valueOf(int traceType) { + return new Modification((traceType & 1) != 0, (traceType & 2) != 0); + } + + int toInt() { + return (timing ? 1 : 0) + (tracing ? 2 : 0); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java new file mode 100644 index 0000000000000..9ebbec5d451b8 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import jdk.jfr.events.MethodTimingEvent; +import jdk.jfr.internal.JVM; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; +import jdk.jfr.internal.MetadataRepository; +import jdk.jfr.internal.SecuritySupport; +import jdk.jfr.internal.Type; +import jdk.jfr.internal.util.Bytecode; +import jdk.jfr.tracing.MethodTracer; + +/** + * Class that contains the Method Tracer implementation. + *

+ * By placing the implementation in jdk.jfr.internal.tracing package instead of + * the jdk.jfr.tracing package fewer internals are exposed to the application. + */ +public final class PlatformTracer { + private static final ConcurrentHashMap timedMethods = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap timedClasses = new ConcurrentHashMap<>(); + + private static List traceFilters = List.of(); + private static List timingFilters = List.of(); + private static TimedMethod OBJECT; + + public static byte[] onMethodTrace(Module module, ClassLoader classLoader, String className, + byte[] oldBytecode, long[] ids, String[] names, String[] signatures, + int[] modifications) { + if (classLoader == null && ExcludeList.containsClass(className)) { + log(LogLevel.DEBUG, "Risk of recursion, skipping bytecode generation", module, className); + return null; + } + try { + Instrumentation instrumentation = new Instrumentation(classLoader, className, oldBytecode); + for (int i = 0; i < ids.length; i++) { + instrumentation.addMethod(ids[i], names[i], signatures[i], modifications[i]); + } + updateTiming(instrumentation.getMethods()); + return instrumentation.generateBytecode(); // Returns null if bytecode was not modified. + } catch (ClassCircularityError cce) { + log(LogLevel.WARN, "Class circularity error, skipping instrumentation", module, className); + return null; + } catch (Throwable t) { + log(LogLevel.WARN, "Unexpected error " + t.getMessage() + ". Skipping instrumentation", module, className); + return null; + } + } + + private static void updateTiming(List methods) { + boolean removeClass = true; + for (Method method : methods) { + if (method.isTiming()) { + removeClass = false; + } + updateTiming(method); + } + if (removeClass) { + Long classId = methods.getFirst().classId(); + TimedClass timedClass = timedClasses.remove(classId); + if (timedClass != null) { + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.DEBUG, "TimedClass removed (Klass ID " + String.format("0x%08X)", classId)); + } + } + } + private static void updateTiming(Method method) { + if (!timedMethods.containsKey(method.methodId())) { + if (method.isTiming()) { + // Timing started + TimedClass timedClass = timedClasses.computeIfAbsent(method.classId(), id -> new TimedClass()); + TimedMethod entry = timedClass.add(method); + timedMethods.put(method.methodId(), entry); + if ("java.lang.Object::".equals(method.name())) { + OBJECT = entry; + } + method.log("Timing entry added"); + } + return; + } + if (!method.isTiming()) { + TimedClass timedClass = timedClasses.get(method.classId()); + if (timedClass != null) { + timedClass.remove(method); + } + timedMethods.remove(method.methodId()); + method.log("Timing entry removed"); + } + } + + private static void log(LogLevel level, String message, Module module, String className) { + if (!Logger.shouldLog(LogTag.JFR_METHODTRACE, level)) { + return; + } + StringBuilder s = new StringBuilder(); + s.append(message); + s.append(" for "); + s.append(className.replace("/", ".")); + s.append(" in module "); + s.append(module.getName()); + s.append(" and class loader " + module.getClassLoader()); + Logger.log(LogTag.JFR_METHODTRACE, level, s.toString()); + } + + public static void emitTiming() { + // Metadata lock prevents rotation/flush while emitting events. + synchronized (MetadataRepository.getInstance()) { + removeClasses(JVM.drainStaleMethodTracerIds()); + long timestamp = MethodTimingEvent.timestamp(); + for (var tc : timedClasses.values()) { + tc.emit(timestamp); + } + } + } + + public static void addObjectTiming(long duration) { + OBJECT.invocations().getAndIncrement(); + OBJECT.time().addAndGet(duration); + } + + public static void addTiming(long id, long duration) { + TimedMethod entry = timedMethods.get(id); + if (entry != null) { + entry.invocations().getAndIncrement(); + entry.time().addAndGet(duration); + } + } + + public static boolean isValidFilter(String text) { + return Filter.of(text, null) != null; + } + + public static void setFilters(Modification modification, List filters) { + publishClasses(applyFilter(modification, filters)); + } + + private static long[] applyFilter(Modification modification, List filters) { + boolean hadFilters = hasFilters(); + if (modification.tracing()) { + traceFilters = makeFilters(filters, modification); + } + if (modification.timing()) { + timingFilters = makeFilters(filters, modification); + } + if (hadFilters || hasFilters()) { + int size = filterCount(); + List allFilters = new ArrayList<>(size); + allFilters.addAll(traceFilters); + allFilters.addAll(timingFilters); + String[] classes = new String[size]; + String[] methods = new String[size]; + String[] annotations = new String[size]; + int[] modifications = new int[size]; + for (int index = 0; index < size; index++) { + Filter filter = allFilters.get(index); + classes[index] = Bytecode.internalName(filter.className()); + methods[index] = filter.methodName(); + annotations[index] = Bytecode.descriptorName(filter.annotationName()); + modifications[index] = filter.modification().toInt(); + } + return JVM.setMethodTraceFilters(classes, methods, annotations, modifications); + } + return null; + } + + private static void removeClasses(long[] classIds) { + if (classIds == null) { + return; + } + for (int i = 0; i < classIds.length; i++) { + TimedClass timedClass = timedClasses.remove(classIds[i]); + if (timedClass != null) { + for (TimedMethod tm : timedClass.methods()) { + timedMethods.remove(tm.method().methodId()); + tm.method().log("Timing entry unloaded"); + } + if (Logger.shouldLog(LogTag.JFR_METHODTRACE, LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.DEBUG, "TimedClass unloaded and removed for klass ID " + String.format("0x%08X", classIds[i])); + } + } + } + } + + private static void publishClasses(long[] classIds) { + if (classIds == null) { + return; + } + for (int i = 0; i < classIds.length; i++) { + publishClass(classIds[i]); + } + } + + public static void publishClass(long classId) { + TimedClass timedClass = timedClasses.get(classId); + // The class may be null if a class is drained/unloaded before + // it is being published by setFilter(). + if (timedClass != null) { + timedClass.publish(); + } + } + + private static boolean hasFilters() { + return filterCount() > 0; + } + + private static int filterCount() { + return traceFilters.size() + timingFilters.size(); + } + + private static List makeFilters(List filterTexts, Modification modification) { + List filters = new ArrayList<>(filterTexts.size()); + for (String filterText : filterTexts) { + Filter filter = Filter.of(filterText, modification); + if (filter != null) { + filters.add(filter); + } + } + return filters; + } + + private static void reset() { + timedMethods.clear(); + timedClasses.clear(); + } + + // This method has three purposes: + // + // 1) Load classes before instrumentation to avoid recursion in class + // initializers when onMethodTrace(...) is called by the JVM. + // + // 2) Warm up methods used by the PlatformTracer class to reduce the observer + // effect later. + // + // 3) Export the jdk.jfr.tracing package to all other modules. + // + // This method takes 1-10 milliseconds to run and is only executed once, + // provided a user has specified a non-empty filter for the MethodTrace or + // MethodTiming event. + public static void initialize() { + try { + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.DEBUG, "Method tracer initialization started."); + Thread current = Thread.currentThread(); + JVM.exclude(current); + long methodId = 16384126; + long classId = methodId >> 16; + ClassLoader cl = null; + String className = " java/lang/String"; + Module m = String.class.getModule(); + var is = ClassLoader.getSystemClassLoader().getResourceAsStream("java/lang/String.class"); + byte[] oldBytecode = is.readAllBytes(); + is.close(); + long[] ids = { methodId }; + String[] names = { "" }; + String[] signatures = { "()V" }; + int[] modifications = { 3 }; + byte[] bytes = onMethodTrace(m, cl, className, oldBytecode, ids, names, signatures, modifications); + if (bytes == null) { + throw new Exception("Could not generate bytecode"); + } + publishClass(classId); + for (int id = 0; id < 25_000; id++) { + MethodTracer.timing(MethodTracer.timestamp(), methodId); + MethodTracer.trace(MethodTracer.timestamp(), methodId); + MethodTracer.traceTiming(MethodTracer.timestamp(), methodId); + } + reset(); + JVM.include(current); + SecuritySupport.addTracingExport(); + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.DEBUG, "Method tracer initialization complete."); + } catch (Exception e) { + Logger.log(LogTag.JFR_METHODTRACE, LogLevel.WARN, "Method tracer initialization failed. " + e.getMessage()); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java new file mode 100644 index 0000000000000..936a8cf835d35 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Collection; + +import jdk.jfr.events.MethodTimingEvent; + +/** + * Holds timed method for a class. Used when publishing method ids. + */ +public final class TimedClass { + private final ConcurrentHashMap methods = new ConcurrentHashMap<>(); + + public TimedMethod add(Method method) { + return methods.computeIfAbsent(method.methodId(), id -> new TimedMethod(method)); + } + + public void remove(Method method) { + methods.remove(method.methodId()); + } + + public void publish() { + for (TimedMethod t : methods.values()) { + t.published().set(true); + t.method().log("Timing entry published"); + } + } + + Collection methods() { + return methods.values(); + } + + public void emit(long timestamp) { + for (var tm : methods.values()) { + if (tm.published().get()) { + long methodId = tm.method().methodId(); + long invocations = tm.invocations().get(); + long time = tm.time().get(); + long average = invocations == 0 ? Long.MIN_VALUE : time / invocations; + MethodTimingEvent.commit(timestamp, methodId, invocations, average); + tm.method().log("Emitted event"); + } + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java new file mode 100644 index 0000000000000..185fab624216f --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Record class that holds invocation measurements used by the MethodTiming + * event. + *

+ * Fields in record classes are truly final so might help to have a record here. + */ +record TimedMethod(AtomicLong invocations, AtomicLong time, Method method, AtomicBoolean published) { + TimedMethod(Method method) { + this(new AtomicLong(), new AtomicLong(), method, new AtomicBoolean()); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java new file mode 100644 index 0000000000000..cd65a119cee4d --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.tracing; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.TypeKind; +import java.lang.classfile.instruction.ReturnInstruction; +import java.lang.classfile.instruction.ThrowInstruction; +import java.lang.constant.ClassDesc; + +import jdk.jfr.internal.util.Bytecode; +import jdk.jfr.internal.util.Bytecode.MethodDesc; +import jdk.jfr.tracing.MethodTracer; + +/** + * Class that transforms the bytecode of a method so it can call the appropriate + * methods in the jdk.jfr.tracing.MethodTracer class. + *

+ * The method ID is determined by native code. + */ +final class Transform implements CodeTransform { + private static final ClassDesc METHOD_TRACER_CLASS = ClassDesc.of(MethodTracer.class.getName()); + private static final MethodDesc TRACE_METHOD = MethodDesc.of("trace", "(JJ)V"); + private static final MethodDesc TIMING_METHOD = MethodDesc.of("timing", "(JJ)V"); + private static final MethodDesc TRACE_TIMING_METHOD = MethodDesc.of("traceTiming", "(JJ)V"); + private static final MethodDesc TIMESTAMP_METHOD = MethodDesc.of("timestamp", "()J"); + + private final Method method; + private int timestampSlot = -1; + + Transform(Method method) { + this.method = method; + } + + @Override + public final void accept(CodeBuilder builder, CodeElement element) { + if (timestampSlot == -1) { + timestampSlot = invokeTimestamp(builder); + builder.lstore(timestampSlot); + } + if (element instanceof ReturnInstruction || element instanceof ThrowInstruction) { + builder.lload(timestampSlot); + builder.ldc(method.methodId()); + Modification modification = method.modification(); + boolean objectInit = method.name().equals("java.lang.Object::"); + String suffix = objectInit ? "ObjectInit" : ""; + if (modification.timing()) { + if (modification.tracing()) { + invokeTraceTiming(builder, suffix); + } else { + invokeTiming(builder, suffix); + } + } else { + if (modification.tracing()) { + invokeTrace(builder, suffix); + } + } + } + builder.with(element); + } + + public static void invokeTiming(CodeBuilder builder, String suffix) { + builder.invokestatic(METHOD_TRACER_CLASS, TIMING_METHOD.name() + suffix, TIMING_METHOD.descriptor()); + } + + public static void invokeTrace(CodeBuilder builder, String suffix) { + builder.invokestatic(METHOD_TRACER_CLASS, TRACE_METHOD.name() + suffix, TRACE_METHOD.descriptor()); + } + + public static void invokeTraceTiming(CodeBuilder builder, String suffix) { + builder.invokestatic(METHOD_TRACER_CLASS, TRACE_TIMING_METHOD.name() + suffix, TRACE_TIMING_METHOD.descriptor()); + } + + public static int invokeTimestamp(CodeBuilder builder) { + Bytecode.invokestatic(builder, METHOD_TRACER_CLASS, TIMESTAMP_METHOD); + return builder.allocateLocal(TypeKind.LONG); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java index 3cea5e8064388..9db92e4d4feaf 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java @@ -101,6 +101,14 @@ public static ClassDesc classDesc(ValueDescriptor v) { }; } + public static String internalName(String className) { + return className != null ? className.replace(".", "/") : null; + } + + public static String descriptorName(String className) { + return className != null ? ("L" + internalName(className) + ";") : null; + } + public static ClassDesc classDesc(Class clazz) { return ClassDesc.ofDescriptor(clazz.descriptorString()); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueFormatter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueFormatter.java index 53a11101944fa..e99df10d698a8 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueFormatter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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 @@ -110,27 +110,36 @@ public static String formatNumber(Number n) { } public static String formatDuration(Duration d) { + return formatDuration(d, -1); + } + + public static String formatDuration(Duration d, int precision) { Duration roundedDuration = roundDuration(d); if (roundedDuration.equals(Duration.ZERO)) { return "0 s"; } else if (roundedDuration.isNegative()) { - return "-" + formatPositiveDuration(roundedDuration.abs()); + return "-" + formatPositiveDuration(roundedDuration.abs(), precision); } else { - return formatPositiveDuration(roundedDuration); + return formatPositiveDuration(roundedDuration, precision); } } - private static String formatPositiveDuration(Duration d){ + public static String formatPositiveDuration(Duration d, int precision) { if (d.compareTo(MICRO_SECOND) < 0) { // 0.000001 ms - 0.000999 ms + if (precision == -1) { + precision = 6; + } double outputMs = (double) d.toNanosPart() / 1_000_000; - return String.format("%.6f ms", outputMs); + return String.format("%." + precision + "f ms", outputMs); } else if (d.compareTo(SECOND) < 0) { // 0.001 ms - 999 ms - int valueLength = countLength(d.toNanosPart()); - int outputDigit = NANO_SIGNIFICANT_FIGURES - valueLength; + if (precision == -1) { + int valueLength = countLength(d.toNanosPart()); + precision = NANO_SIGNIFICANT_FIGURES - valueLength; + } double outputMs = (double) d.toNanosPart() / 1_000_000; - return String.format("%." + outputDigit + "f ms", outputMs); + return String.format("%." + precision + "f ms", outputMs); } else if (d.compareTo(MINUTE) < 0) { // 1.00 s - 59.9 s int valueLength = countLength(d.toSecondsPart()); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/package-info.java b/src/jdk.jfr/share/classes/jdk/jfr/package-info.java index 50666d738ef93..bd5b197d7fce2 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/package-info.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/package-info.java @@ -176,6 +176,31 @@ * {@code "true"},
* {@code "false"} * + * + * {@code filter} + * Specifies the filter for the event + * {@code ""} (empty string) + * An empty string if no filter is used. Otherwise, a + * filter that can be used with the jdk.MethodTrace or + * jdk.MethodTiming events and follows this grammar:
+ * {@snippet : + * filter ::= target (";" target)* + * target ::= class | class-method | method | annotation + * class ::= identifier ("." identifier)* + * class-method ::= class method + * method ::= "::" method-name + * method-name ::= identifier | "" | "" + * annotation ::= "@" class + * identifier ::= see JLS 3.8 + * } + * + * {@code "java.lang.String"}
+ * {@code "::"}
+ * {@code "java.util.HashMap::resize"}
+ * {@code "java.io.FileDescriptor::;java.io.FileDescriptor::close"}
+ * {@code "@jakarta.ws.rs.GET"}
+ * + * * * *

diff --git a/src/jdk.jfr/share/classes/jdk/jfr/tracing/MethodTracer.java b/src/jdk.jfr/share/classes/jdk/jfr/tracing/MethodTracer.java new file mode 100644 index 0000000000000..0d7b8072194ab --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/tracing/MethodTracer.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, 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.jfr.tracing; + +import jdk.jfr.events.MethodTimingEvent; +import jdk.jfr.events.MethodTraceEvent; +import jdk.jfr.internal.JVM; +import jdk.jfr.internal.tracing.PlatformTracer; + +/** + * This class serves as the frontend for method tracing capabilities. The + * jdk.jfr.tracing package is exported to all modules when the first method + * tracing filter is applied. + *

+ * A malicious user could craft bytecode that invoke these methods with an + * invalid method ID, resulting in an event where the method field is + * incorrect or {@code null}. This is considered acceptable. + */ +public final class MethodTracer { + + private MethodTracer() { + } + + public static long timestamp() { + return JVM.counterTime(); + } + + public static void traceObjectInit(long startTime, long methodId) { + long endTime = JVM.counterTime(); + long duration = endTime - startTime; + if (MethodTraceEvent.enabled() && JVM.getEventWriter() != null) { + MethodTraceEvent.commit(startTime, duration, methodId); + } + } + + public static void timingObjectInit(long startTime, long methodId) { + long endTime = JVM.counterTime(); + long duration = endTime - startTime; + if (MethodTimingEvent.enabled()) { + PlatformTracer.addObjectTiming(duration); + } + } + + public static void traceTimingObjectInit(long startTime, long methodId) { + long endTime = JVM.counterTime(); + long duration = endTime - startTime; + if (MethodTraceEvent.enabled() && JVM.getEventWriter() != null) { + MethodTraceEvent.commit(startTime, duration, methodId); + } + if (MethodTimingEvent.enabled()) { + PlatformTracer.addObjectTiming(duration); + } + } + + public static void trace(long startTime, long methodId) { + long endTime = JVM.counterTime(); + long duration = endTime - startTime; + if (MethodTraceEvent.enabled()) { + MethodTraceEvent.commit(startTime, duration, methodId); + } + } + + public static void timing(long startTime, long methodId) { + long endTime = JVM.counterTime(); + long duration = endTime - startTime; + if (MethodTimingEvent.enabled()) { + PlatformTracer.addTiming(methodId, duration); + } + } + + public static void traceTiming(long startTime, long methodId) { + long endTime = JVM.counterTime(); + long duration = endTime - startTime; + if (MethodTimingEvent.enabled()) { + PlatformTracer.addTiming(methodId, duration); + } + if (MethodTraceEvent.enabled()) { + MethodTraceEvent.commit(startTime, duration, methodId); + } + } +} diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 9f3a34618d3ef..293af26746f1a 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -206,6 +206,19 @@ 20 ms + + true + + 0 ms + true + + + + true + + endChunk + + false true @@ -1158,6 +1171,10 @@ 20 ms + + + + false diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 4c9f4b4f8ec63..89a9022d11eb4 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -206,6 +206,19 @@ 20 ms + + true + + 0 ms + true + + + + true + + endChunk + + false false @@ -1157,6 +1170,10 @@ 10 ms + + + + false diff --git a/test/jdk/jdk/jfr/api/settings/TestSettingControl.java b/test/jdk/jdk/jfr/api/settings/TestSettingControl.java index ef38626eedb75..e1c9ff1ea4760 100644 --- a/test/jdk/jdk/jfr/api/settings/TestSettingControl.java +++ b/test/jdk/jdk/jfr/api/settings/TestSettingControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -64,7 +64,8 @@ public String eventSettingName() { new SettingTest("level", "forRemoval", "jdk.DeprecatedInvocation", List.of("off", "forRemoval")), new SettingTest("period", "everyChunk", "jdk.ExceptionStatistics", List.of("everyChunk", "60 s", "1 s")), new SettingTest("cutoff", "infinity", "jdk.OldObjectSample", List.of("0 ms", "1 s", "infinity")), - new SettingTest("throttle", "off", "jdk.ObjectAllocationSample", List.of("off", "100/s", "10/ms")) + new SettingTest("throttle", "off", "jdk.ObjectAllocationSample", List.of("off", "100/s", "10/ms")), + new SettingTest("filter", "", "jdk.MethodTrace", List.of("", "foo.bar::Baz", "com.example.Test;foo.bar::Baz")) ); public static void main(String... args) throws Exception { diff --git a/test/jdk/jdk/jfr/event/tracing/Apple.java b/test/jdk/jdk/jfr/event/tracing/Apple.java new file mode 100644 index 0000000000000..b88f39339472c --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/Apple.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ TYPE, METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Apple { +} diff --git a/test/jdk/jdk/jfr/event/tracing/Banana.java b/test/jdk/jdk/jfr/event/tracing/Banana.java new file mode 100644 index 0000000000000..072e6a5955f37 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/Banana.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ TYPE, METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Banana { +} diff --git a/test/jdk/jdk/jfr/event/tracing/Car.java b/test/jdk/jdk/jfr/event/tracing/Car.java new file mode 100644 index 0000000000000..2f3f5a5802a40 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/Car.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +public final class Car implements Runnable { + + public void run() { + System.out.println("Car is running. Class loader name " + this.getClass().getClassLoader().getName()); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/StaticInitializer.java b/test/jdk/jdk/jfr/event/tracing/StaticInitializer.java new file mode 100644 index 0000000000000..15485660bf491 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/StaticInitializer.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +public final class StaticInitializer { + public static String TRIGGERED; + static { + System.out.println("Executing StaticInitializer::"); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestClinit.java b/test/jdk/jdk/jfr/event/tracing/TestClinit.java new file mode 100644 index 0000000000000..f2356fb8d9f09 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestClinit.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that can be instrumented. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @build jdk.jfr.event.tracing.StaticInitializer + * @run main/othervm -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestClinit + **/ +public class TestClinit { + private static final String PACKAGE_NAME = TestClinit.class.getPackageName(); + private static final String CLINIT_CLASS_NAME = PACKAGE_NAME + ".StaticInitializer"; + private static final String CLINIT_METHOD_NAME = CLINIT_CLASS_NAME + "::"; + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable("jdk.MethodTrace") + .with("filter", CLINIT_CLASS_NAME); + r.enable("jdk.MethodTiming") + .with("filter", CLINIT_METHOD_NAME) + .with("period", "endChunk"); + + r.start(); + StaticInitializer.TRIGGERED = "true"; + r.stop(); + + List events = Events.fromRecording(r); + Events.assertEventCount(events, 2); + + RecordedEvent traceEvent = Events.getFirst(events, "jdk.MethodTrace"); + Events.assertTopFrame(traceEvent, TestClinit.class.getName(), "main"); + assertClinitMethod(traceEvent); + + RecordedEvent timingEvent = Events.getFirst(events, "jdk.MethodTiming"); + assertClinitMethod(timingEvent); + } + } + + private static void assertClinitMethod(RecordedEvent event) throws Exception { + RecordedMethod method = event.getValue("method"); + if (!method.getName().equals("")) { + System.out.println(event); + throw new Exception("Expected , was " + method.getName()); + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestCombinedFilters.java b/test/jdk/jdk/jfr/event/tracing/TestCombinedFilters.java new file mode 100644 index 0000000000000..d794a6945556f --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestCombinedFilters.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import jdk.jfr.EventType; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.event.tracing.Apple; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that the union of annotation-based, class-based and + * method-based filters can be used simultaneously. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @build jdk.jfr.event.tracing.Apple + * @run main/othervm -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestCombinedFilters + **/ +public class TestCombinedFilters { + private static final String APPLE_ANNOTATION = Apple.class.getName(); + private static final String TRACE_EVENT = "jdk.MethodTrace"; + private static final String TIMING_EVENT = "jdk.MethodTiming"; + private static final String FOO_CLASS = Foo.class.getName(); + + public static class Foo { + @Apple + static void bar() { + System.out.println("Executing Foo:bar"); + } + + static void baz() { + System.out.println("Executing Foo:baz"); + } + + static void qux() { + System.out.println("Executing Foo:qux"); + } + } + + record TestEvent(String event, String type, String method) { + } + + public static void main(String... args) throws Exception { + String traceFilter = "@" + APPLE_ANNOTATION + ";" + FOO_CLASS + "::bar"; + String timingFilter = Foo.class.getName(); + try (Recording r = new Recording()) { + r.enable(TRACE_EVENT).with("filter", traceFilter); + r.enable(TIMING_EVENT).with("filter", timingFilter).with("period", "endChunk"); + for (var entry : r.getSettings().entrySet()) { + System.out.println(entry.getKey() + "=" + entry.getValue()); + } + r.start(); + Foo.bar(); + Foo.baz(); + Foo.qux(); + r.stop(); + var list = List.of(new TestEvent(TRACE_EVENT, FOO_CLASS, "bar"), + new TestEvent(TIMING_EVENT, FOO_CLASS, ""), + new TestEvent(TIMING_EVENT, FOO_CLASS, "bar"), + new TestEvent(TIMING_EVENT, FOO_CLASS, "baz"), + new TestEvent(TIMING_EVENT, FOO_CLASS, "qux")); + List expected = new ArrayList<>(list); + List events = Events.fromRecording(r); + for (RecordedEvent event : events) { + System.out.println(event); + } + Events.hasEvents(events); + for (RecordedEvent e : events) { + RecordedMethod method = e.getValue("method"); + String className = method.getType().getName(); + String eventTypeName = e.getEventType().getName(); + TestEvent testEvent = new TestEvent(eventTypeName, className, method.getName()); + if (!expected.remove(testEvent)) { + throw new Exception("Unexpected event " + testEvent); + } + } + if (!expected.isEmpty()) { + throw new Exception("Missing events " + expected); + } + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestFilterClass.java b/test/jdk/jdk/jfr/event/tracing/TestFilterClass.java new file mode 100644 index 0000000000000..e89813d444f7b --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestFilterClass.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; + +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that class filters work as expected. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestFilterClass + **/ +public class TestFilterClass { + private static final String THIS_CLASS = TestFilterClass.class.getName(); + private static final String EVENT_NAME = "jdk.MethodTrace"; + + interface Interface { + void foo(); + + void bar(); + + public static void baz() { + System.out.println("Executing Interface::baz"); + } + } + + static class Implementation implements Interface { + public void foo() { + System.out.println("Executing Implementation::foo"); + } + + @Override + public void bar() { + throw new Error("Should not happen"); + } + } + + enum Enum { + VALUE; + + public void bar() { + System.out.println("Executing Enum::bar"); + } + } + + record Record(int value) { + } + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable(EVENT_NAME) + .with("filter", THIS_CLASS + "$Implementation;" + + THIS_CLASS + "$Interface;" + + THIS_CLASS + "$Enum;" + + THIS_CLASS + "$Record"); + r.start(); + Interface.baz(); + new Implementation().foo(); + Enum.VALUE.bar(); + new Record(4711).value(); + r.stop(); + var list = new ArrayList<>(List.of(THIS_CLASS + "$Interface::baz", THIS_CLASS + "$Implementation::", THIS_CLASS + "$Implementation::foo", THIS_CLASS + "$Enum::", + THIS_CLASS + "$Enum::", THIS_CLASS + "$Enum::bar", THIS_CLASS + "$Record::", THIS_CLASS + "$Record::value")); + var events = Events.fromRecording(r); + System.out.println(list); + Events.hasEvents(events); + for (RecordedEvent event : events) { + System.out.println(event); + RecordedMethod method = event.getValue("method"); + String name = method.getType().getName() + "::" + method.getName(); + if (!list.remove(name)) { + throw new Exception("Unexpected method '" + name + "' in event"); + } + } + if (!list.isEmpty()) { + throw new Exception("Expected events for the methods " + list); + } + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestFilterClassAnnotation.java b/test/jdk/jdk/jfr/event/tracing/TestFilterClassAnnotation.java new file mode 100644 index 0000000000000..fddf82ca4195a --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestFilterClassAnnotation.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + + +import jdk.jfr.event.tracing.Apple; +import jdk.jfr.event.tracing.Banana; + + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +import java.lang.annotation.Target; +import java.util.HashSet; +import java.util.Set; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; + +/** + * @test + * @summary Tests class-annotation-based filtering. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm + * -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestFilterClassAnnotation + */ + +// @Banana and Apple tests multiple annotations and that the target is not the first annotation +@Banana +@Apple +public class TestFilterClassAnnotation { + private static final String EVENT_NAME = "jdk.MethodTrace"; + + // Class Foo tests inner and interface classes + @Apple + interface Foo { + // Method duck() tests that static methods in interfaces can be instrumented + private static void duck() { + System.out.println("Executing method: duck()"); + } + + // Method eggplant() tests that abstract method doesn't interfere in the + // instrumentation + void eggplant(); + } + + // Method ant() tests that the same method annotation as the class doesn't + // interfere + @Apple + private static void ant() { + System.out.println("Executing method: ant()"); + } + + // Method bear() tests that other method annotation doesn't interfere + @Banana + private static void bear() { + System.out.println("Executing method: bear()"); + } + + // Method cat() tests that a method in an annotated class is instrumented + private static void cat() { + System.out.println("Executing method: cat()"); + } + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable(EVENT_NAME) + .with("filter", "@" + Apple.class.getName()); + r.start(); + ant(); + bear(); + cat(); + Foo.duck(); + r.stop(); + + var set = new HashSet<>(Set.of("ant", "bear", "cat", "duck")); + var events = Events.fromRecording(r); + Events.hasEvents(events); + for (RecordedEvent e : events) { + System.out.println(e); + RecordedMethod method = e.getValue("method"); + if (!set.remove(method.getName())) { + throw new Exception("Unexpected method '" + method.getName() + "' in event"); + } + RecordedFrame topFrame = e.getStackTrace().getFrames().get(0); + String topMethod = topFrame.getMethod().getName(); + if (!topMethod.equals("main")) { + throw new Exception("Expected method to be called from main"); + } + } + if (!set.isEmpty()) { + throw new Exception("Expected events for the methods " + set); + } + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestFilterMethod.java b/test/jdk/jdk/jfr/event/tracing/TestFilterMethod.java new file mode 100644 index 0000000000000..82356ac1bfdb2 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestFilterMethod.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that a method filter (e.g., class::method) works as expected. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestFilterMethod + */ +public class TestFilterMethod { + private static final String THIS_CLASS = TestFilterMethod.class.getName(); + private static final String EVENT_NAME = "jdk.MethodTrace"; + + // Tests implicit constructor + static public class SomeClass { + public void override() { + throw new Error("Should not happen"); + } + } + + // Tests explicit constructor + static public class OtherClass extends SomeClass { + public OtherClass() { + System.out.println("Executing Otherclass::Otherclass()"); + } + } + + // Tests method override + static public class SomeSubclass extends SomeClass { + public void override() { + System.out.println("Executing SomeSubclass::override()"); + } + } + + // Tests method in enum + enum Enum { + VALUE; + + static void enumMethod() { + System.out.println("Executing Enum:enumMethod"); + } + } + + // Tests method in interface + interface Interface { + public static void staticMethod() { + System.out.println("Executing Interface::staticMethod"); + } + + public void instanceMethod(); + } + + static class Implementation implements Interface { + @Override + public void instanceMethod() { + } + } + + // Tests normal method + public static void overload() { + System.out.println("Executing overload()"); + } + + // Tests overloaded method + public static void overload(int value) { + System.out.println("Executing overload(" + value + ")"); + } + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable(EVENT_NAME) + .with("filter", THIS_CLASS + "$SomeSubclass::override;" + + THIS_CLASS + "$OtherClass::;" + + THIS_CLASS + "::overload;" + + THIS_CLASS + "$Enum::enumMethod;" + + THIS_CLASS + "$Interface::staticMethod;" + + THIS_CLASS + "$Implementation::instanceMethod"); + r.start(); + new SomeSubclass().override(); + new OtherClass(); + overload(); + overload(1); + Enum.enumMethod(); + Interface.staticMethod(); + new Implementation().instanceMethod(); + r.stop(); + + var set = new ArrayList<>(List.of( + "", // OtherClass: + "override", + "overload", // overload() + "overload", // overload(int) + "enumMethod", + "staticMethod", + "instanceMethod")); + var events = Events.fromRecording(r); + Events.hasEvents(events); + for (RecordedEvent event : events) { + System.out.println(event); + RecordedMethod m = event.getValue("method"); + if (!set.remove(m.getName())) { + throw new Exception("Unexpected method '" + m.getName() + "' in event"); + } + RecordedFrame topFrame = event.getStackTrace().getFrames().get(0); + String topMethod = topFrame.getMethod().getName(); + if (!topMethod.equals("main")) { + throw new Exception("Expected method to be called from main"); + } + } + if (!set.isEmpty()) { + throw new Exception("Expected events for the methods " + set); + } + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestFilterMethodAnnotation.java b/test/jdk/jdk/jfr/event/tracing/TestFilterMethodAnnotation.java new file mode 100644 index 0000000000000..62420e3900aff --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestFilterMethodAnnotation.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.HashSet; +import java.util.Set; + +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.event.tracing.Apple; +import jdk.jfr.event.tracing.Banana; +import jdk.jfr.event.tracing.TestFilterMethodAnnotation.Foo; + +/** + * @test + * @summary Tests method-annotation-based filtering. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestFilterMethodAnnotation + */ +public class TestFilterMethodAnnotation { + + static String EVENT_NAME = "jdk.MethodTrace"; + + // Tests that abstract method is ignored + static abstract class Foo { + @Apple + abstract void baz(); + } + + // Tests tracing of an inner class + static class Bar extends Foo { + @Override + // Tests method override + @Apple + void baz() { + System.out.println("Executing Bar::baz()"); + } + + @Apple + void qux() { + System.out.println("Executing Bar::qux()"); + } + } + + // Tests tracing of method with multiple annotations and the target not being + // first + @Banana + @Apple + private static void ant() { + System.out.println("Executing method: ant()"); + } + + // Tests that overloaded method with the same name is not traced + private static void ant(int i) { + System.out.println("Executing method: apple(" + i + ")"); + } + + // Tests that non-annotated method is not traced + private static void bear() { + System.out.println("Executing method: bear()"); + } + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable(EVENT_NAME) + .with("filter", "@" + Apple.class.getName()); + r.start(); + ant(); + ant(4711); + bear(); + Bar bar = new Bar(); + bar.baz(); + bar.qux(); + r.stop(); + var set = new HashSet<>(Set.of("ant", "baz", "qux")); + var events = Events.fromRecording(r); + Events.hasEvents(events); + for (RecordedEvent event : events) { + System.out.println(event); + RecordedMethod method = event.getValue("method"); + String methodName = method.getName(); + if (!set.remove(methodName)) { + throw new Exception("Unexpected method " + methodName + "() in event"); + } + RecordedFrame topFrame = event.getStackTrace().getFrames().get(0); + if (!topFrame.getMethod().getName().equals("main")) { + throw new Exception("Expected method to be called from main"); + } + } + if (!set.isEmpty()) { + throw new Exception("Expected events for the methods " + set); + } + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java b/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java new file mode 100644 index 0000000000000..834d4ab4989ef --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CopyOnWriteArrayList; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that methods are instrumented correctly. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm + * -Xlog:jfr+methodtrace=debug + * jdk.jfr.event.tracing.TestInstrumentation + **/ +public class TestInstrumentation { + private static Object nullObject; + + public static void main(String... args) throws Exception { + List traceEvents = new CopyOnWriteArrayList<>(); + List timingEvents = new CopyOnWriteArrayList<>(); + try (RecordingStream r = new RecordingStream()) { + r.setReuse(false); + String filter = TestInstrumentation.class.getName(); + r.enable("jdk.MethodTrace") + .with("filter", filter); + r.enable("jdk.MethodTiming") + .with("filter", filter) + .with("period", "endChunk"); + r.onEvent("jdk.MethodTrace", traceEvents::add); + r.onEvent("jdk.MethodTiming", timingEvents::add); + r.startAsync(); + try { + whileTrue(); + } catch (NullPointerException npe) { + // As expected + } + recursive(3); + switchExpression(0); + switchExpression(1); + switchExpression(2); + multipleReturns(); + multipleReturns(); + multipleReturns(); + multipleReturns(); + multipleReturns(); + try { + exception(); + } catch (Exception e) { + } + try { + deepException(); + } catch (Exception e) { + } + r.stop(); + } + verifyTracing(traceEvents); + verifyTiming(timingEvents); + } + + private static void verifyTracing(List events) throws Exception { + Map map = buildMethodMap(events, false); + printMap("Tracing:", map); + assertMethod(map, "exception", 2); + assertMethod(map, "switchExpression", 3); + assertMethod(map, "recursive", 4); + assertMethod(map, "multipleReturns", 5); + if (!map.isEmpty()) { + throw new Exception("Found unexpected methods " + map.keySet()); + } + } + + private static void verifyTiming(List events) throws Exception { + Map map = buildMethodMap(events, true); + printMap("Timing:", map); + assertMethod(map, "exception", 2); + assertMethod(map, "switchExpression", 3); + assertMethod(map, "recursive", 4); + assertMethod(map, "multipleReturns", 5); + for (var entry : map.entrySet()) { + long invocations = entry.getValue(); + if (invocations != 0L) { + throw new Exception("Unexpected " + invocations + " invocations for method " + entry.getKey()); + } + } + } + + private static void printMap(String caption, Map map) { + System.out.println(caption); + for (var entry : map.entrySet()) { + System.out.println(entry.getKey() + " = " + entry.getValue()); + } + System.out.println(); + } + + private static void assertMethod(Map map, String method, long value) throws Exception { + if (!map.containsKey(method)) { + throw new Exception("Missing method " + method); + } + if (!map.get(method).equals(value)) { + throw new Exception("Expected value " + value + " for method " + method); + } + map.remove(method); + } + + private static Map buildMethodMap(List events, boolean invocations) { + Map map = new HashMap<>(); + for (RecordedEvent e : events) { + RecordedMethod m = e.getValue("method"); + String name = m.getName(); + long add = invocations ? e.getLong("invocations") : 1; + map.compute(name, (key, value) -> (value == null) ? add : value + add); + } + return map; + } + + public static void whileTrue() { + while (true) { + nullObject.toString(); + } + } + + public static void recursive(int depth) { + if (depth > 0) { + recursive(depth - 1); + } else { + return; + } + } + + public static String switchExpression(int value) { + return switch (value) { + case 0 -> "zero"; + case 1 -> "one"; + default -> "number"; + }; + } + + public static void multipleReturns() { + Random r = new Random(); + int v = r.nextInt(5); + if (v == 0) { + return; + } + switch (v) { + case 1: + return; + case 2: + return; + } + return; + } + + public static void exception() throws Exception { + throw new Exception(""); + } + + public static void deepException() throws Exception { + exception(); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestMethodTiming.java b/test/jdk/jdk/jfr/event/tracing/TestMethodTiming.java new file mode 100644 index 0000000000000..1de0e5e20eb20 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestMethodTiming.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.time.Duration; +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.Event; +import jdk.jfr.StackTrace; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Basic test of the MethodTiming event. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.event.tracing.TestMethodTiming + **/ +public class TestMethodTiming { + private static final String EVENT_NAME = "jdk.MethodTiming"; + + @StackTrace(false) + static class TimeMeasureEvent extends Event { + public String id; + } + + public static void main(String... args) throws Exception { + testCount(); + testDuration(); + } + + private static void testDuration() throws Exception { + try (Recording r = new Recording()) { + String filter = TestMethodTiming.class.getName() + "::takeNap"; + r.enable(EVENT_NAME).with("period", "endChunk").with("filter", filter); + r.start(); + + TimeMeasureEvent maxEvent = new TimeMeasureEvent(); + maxEvent.id = "max"; + maxEvent.begin(); + takeNap(); + maxEvent.commit(); + r.stop(); + List events = Events.fromRecording(r); + for (var e : events) { + System.out.println(e); + } + if (events.size() != 3) { + throw new Exception("Expected three events: TimeMeasureEvent::id=max, TimeMeasureEventid=min and MethodTiming::method=takeNap()"); + } + RecordedEvent max = findWitdId(events, "max"); + RecordedEvent min = findWitdId(events, "min"); + + events.remove(min); + events.remove(max); + Duration minDuration = min.getDuration(); + Duration maxDuration = max.getDuration(); + RecordedEvent timingEvent = events.get(0); + Duration d = timingEvent.getDuration("average"); + if (d.compareTo(min.getDuration()) < 0) { + throw new Exception("Expected duration to be at least " + minDuration + ", was " + d); + } + if (d.compareTo(max.getDuration()) > 0) { + throw new Exception("Expected duration to be at most " + maxDuration + ", was " + d); + } + RecordedMethod method = timingEvent.getValue("method"); + String methodName = method.getType().getName() + "::" + method.getName() + " " + method.getDescriptor(); + String expected = TestMethodTiming.class.getName() + "::takeNap ()V"; + if (!methodName.equals(expected)) { + System.out.println(expected); + throw new Exception("Expected method " + expected + " in event, but was " +methodName); + } + if (timingEvent.getLong("invocations") != 1) { + throw new Exception("Expected one invocation"); + } + } + } + + private static RecordedEvent findWitdId(List events, String id) throws Exception { + for (RecordedEvent event : events) { + if (event.hasField("id")) { + if (event.getString("id").equals(id)) { + return event; + } + } + } + throw new Exception("Could not find event with ID " + id); + } + + private static void takeNap() throws Exception { + TimeMeasureEvent minEvent = new TimeMeasureEvent(); + minEvent.begin(); + minEvent.id = "min"; + Thread.sleep(10); + minEvent.commit(); + } + + private static void testCount() throws Exception { + long invocations = 100_000; + try (Recording r = new Recording()) { + zebra(); + String filter = TestMethodTiming.class.getName() + "::zebra"; + r.enable(EVENT_NAME).with("period", "endChunk").with("filter", filter); + r.start(); + for (int i = 0; i < invocations; i++) { + zebra(); + } + r.stop(); + List events = Events.fromRecording(r); + Events.hasEvents(events); + for (RecordedEvent event : events) { + Events.assertField(event, "invocations").equal(invocations); + } + } + } + + private static void zebra() { + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestMethodTrace.java b/test/jdk/jdk/jfr/event/tracing/TestMethodTrace.java new file mode 100644 index 0000000000000..a5fd14306278e --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestMethodTrace.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.Event; +import jdk.jfr.StackTrace; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Basic test of the MethodTrace event. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestMethodTrace + **/ +public class TestMethodTrace { + private static final String EVENT_NAME = "jdk.MethodTrace"; + private static final String CLASS_NAME = TestMethodTrace.class.getName(); + + @StackTrace(false) + private static class OuterMeasurement extends Event { + } + + @StackTrace(false) + private static class InnerMeasurement extends Event { + } + + public static void main(String... args) throws Exception { + AtomicReference o = new AtomicReference<>(); + AtomicReference i = new AtomicReference<>(); + AtomicReference e = new AtomicReference<>(); + try (RecordingStream s = new RecordingStream()) { + s.enable(EVENT_NAME).with("filter", CLASS_NAME + "::bar"); + s.onEvent(EVENT_NAME, e::set); + s.onEvent(OuterMeasurement.class.getName(), o::set); + s.onEvent(InnerMeasurement.class.getName(), i::set); + s.startAsync(); + foo(); + s.stop(); + } + RecordedEvent event = e.get(); + RecordedEvent outer = o.get(); + RecordedEvent inner = i.get(); + System.out.println(event); + + System.out.println("Outer start : " + outer.getStartTime()); + System.out.println(" Method Trace start : " + event.getStartTime()); + System.out.println(" Inner start : " + inner.getStartTime()); + System.out.println(" Inner end : " + inner.getEndTime()); + System.out.println(" Method Trace end : " + event.getEndTime()); + System.out.println("Outer end : " + outer.getEndTime()); + + if (event.getStartTime().isBefore(outer.getStartTime())) { + throw new Exception("Too early start time"); + } + if (event.getStartTime().isAfter(inner.getStartTime())) { + throw new Exception("Too late start time"); + } + if (event.getEndTime().isBefore(inner.getEndTime())) { + throw new Exception("Too early end time"); + } + if (event.getEndTime().isAfter(outer.getEndTime())) { + throw new Exception("Too late end time"); + } + RecordedMethod method = event.getValue("method"); + if (!method.getName().equals("bar")) { + throw new Exception("Expected method too be bar()"); + } + RecordedMethod topMethod = event.getStackTrace().getFrames().get(0).getMethod(); + if (!topMethod.getName().equals("foo")) { + throw new Exception("Expected top frame too be foo()"); + } + } + + private static void foo() { + OuterMeasurement event = new OuterMeasurement(); + event.begin(); + bar(); + event.commit(); + } + + private static void bar() { + InnerMeasurement event = new InnerMeasurement(); + event.begin(); + event.commit(); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestMultipleRecordings.java b/test/jdk/jdk/jfr/event/tracing/TestMultipleRecordings.java new file mode 100644 index 0000000000000..b06fe0198102f --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestMultipleRecordings.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.EventNames; +/** + * @test + * @summary Tests that method tracing can be used with multiple recordings. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm + * -Xlog:jfr+methodtrace=debug + * jdk.jfr.event.tracing.TestMultipleRecordings + **/ +public class TestMultipleRecordings { + private static final String METHOD_TRACE = "jdk.MethodTrace"; + private static final String METHOD_TIMING = "jdk.MethodTiming"; + private static final String CLASS_NAME = TestMultipleRecordings.class.getName(); + + public static void main(String... args) throws Exception { + testNestedMethodTrace(); + testNestedMethodTiming(); + } + + private static void testNestedMethodTiming() throws Exception { + List outerEvents = new ArrayList<>(); + List innerEvents = new ArrayList<>(); + + runNested(METHOD_TIMING, outerEvents, innerEvents); + var outerBatches = groupByEndTime(outerEvents); + System.out.println("Number of outer batches: " + outerBatches.size()); + // Outer started + assertTimingBatch("outer: started", outerBatches.get(0), Map.of("foo", 0, "baz", 0)); + assertTimingBatch("outer: initial to bestarted", outerBatches.get(1), Map.of("foo", 1, "baz", 1)); + // Inner started + assertTimingBatch("outer: inner started", outerBatches.get(2), Map.of("foo", 1, "baz", 1, "bar", 0)); + assertTimingBatch("outer: inner ended", outerBatches.get(3), Map.of("foo", 2, "baz", 2, "bar", 1)); + // Inner stopped + assertTimingBatch("outer: only outer", outerBatches.get(4), Map.of("foo", 2, "baz", 2)); + assertTimingBatch("outer: ending", outerBatches.get(5), Map.of("foo", 3, "baz", 3)); + // Outer stopped + + var innerBatches = groupByEndTime(innerEvents); + System.out.println("Number of inner batches: " + innerBatches.size()); + assertTimingBatch("inner: started", innerBatches.get(0), Map.of("foo", 1, "baz", 1, "bar", 0)); + assertTimingBatch("inner: ended", innerBatches.get(1), Map.of("foo", 2, "baz", 2, "bar", 1)); + } + + private static void assertTimingBatch(String batchName, List events, Map expected) throws Exception { + Map map = new HashMap<>(); + for (RecordedEvent e : events) { + RecordedMethod m = e.getValue("method"); + String name = m.getName(); + int invocations = (int) e.getLong("invocations"); + map.put(name, invocations); + } + if (!map.equals(expected)) { + printBatch("Expected:", expected); + printBatch("Was:", map); + throw new Exception("Batch '" + batchName + "' not as expected"); + } + } + + private static void printBatch(String name, Map batch) { + System.out.println(name); + for (var entry : batch.entrySet()) { + System.out.println(entry.getKey() + " = " + entry.getValue()); + } + } + + private static List> groupByEndTime(List events) { + var listList = new ArrayList>(); + List list = null; + Instant last = null; + while (!events.isEmpty()) { + RecordedEvent event = removeEarliest(events); + Instant timestamp = event.getEndTime(); + if (last == null || !timestamp.equals(last)) { + list = new ArrayList(); + listList.add(list); + } + list.add(event); + last = event.getEndTime(); + } + return listList; + } + + private static RecordedEvent removeEarliest(List events) { + RecordedEvent earliest = null; + for (RecordedEvent event : events) { + if (earliest == null || event.getEndTime().isBefore(earliest.getEndTime())) { + earliest = event; + } + } + events.remove(earliest); + return earliest; + } + + private static void testNestedMethodTrace() throws Exception { + List outerEvents = new ArrayList<>(); + List innerEvents = new ArrayList<>(); + + runNested(METHOD_TRACE, outerEvents, innerEvents); + + assertMethodTraceEvents(outerEvents, "Outer", "foo", 3); + assertMethodTraceEvents(outerEvents, "Outer", "bar", 1); + assertMethodTraceEvents(outerEvents, "Outer", "baz", 3); + assertMethodTraceEvents(innerEvents, "Inner", "foo", 1); + assertMethodTraceEvents(innerEvents, "Inner", "bar", 1); + assertMethodTraceEvents(innerEvents, "Inner", "baz", 1); + } + + private static void runNested(String eventName, List outerEvents, List innerEvents) + throws IOException { + try (Recording outer = new Recording()) { + outer.enable(eventName).with("filter", + CLASS_NAME + "::foo;" + + CLASS_NAME + "::baz"); + outer.start(); + foo(); + bar(); + baz(); + nap(); + try (Recording inner = new Recording()) { + inner.enable(eventName).with("filter", + CLASS_NAME + "::foo;" + + CLASS_NAME + "::bar"); + inner.start(); + foo(); + bar(); + baz(); + inner.stop(); + innerEvents.addAll(Events.fromRecording(inner)); + nap(); + } + foo(); + bar(); + baz(); + nap(); + outer.stop(); + outerEvents.addAll(Events.fromRecording(outer)); + } + } + + // Ensure that periodic events at endChunk get a different + // timestamp than periodic events at beginChunk + private static void nap() { + Instant time = Instant.now(); + do { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // ignore + } + } while (time.plus(Duration.ofMillis(10)).isAfter(Instant.now())); + } + + private static void assertMethodTraceEvents(List events, String context, String methodName, int expected) throws Exception { + int actual = 0; + for (RecordedEvent event : events) { + RecordedMethod method = event.getValue("method"); + if (method.getName().equals(methodName)) { + actual++; + } + } + if (actual != expected) { + System.out.println(context); + for (RecordedEvent event : events) { + System.out.println(event); + } + throw new Exception(context + ": expected " + expected + " events for method " + methodName + ", got actual " + actual); + } + } + + private static void foo() { + System.out.println("Executing: foo()"); + } + + private static void bar() { + System.out.println("Executing: bar()"); + } + + private static void baz() { + System.out.println("Executing: baz()"); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestMultipleThreads.java b/test/jdk/jdk/jfr/event/tracing/TestMultipleThreads.java new file mode 100644 index 0000000000000..5b51d2346619d --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestMultipleThreads.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.ArrayList; +import java.util.List; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that tracing and timing work when using multiple threads. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm + * -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestMultipleThreads + **/ +public class TestMultipleThreads { + private static final String METHOD_PREFIX = TestMultipleThreads.class.getName() + "::method"; + private static final String TRACE_EVENT = "jdk.MethodTrace"; + private static final String TIMING_EVENT = "jdk.MethodTiming"; + private static int METHOD_COUNT = 5; + private static int THREAD_COUNT = 5; + private static int INVOCATIONS_PER_THREAD = 25_000; // Low enough to fit one chunk + private static int INVOCATIONS_PER_METHOD = THREAD_COUNT * INVOCATIONS_PER_THREAD / METHOD_COUNT; + + public static class TestThread extends Thread { + public void run() { + for (int i = 0; i < INVOCATIONS_PER_THREAD; i++) { + switch (i % METHOD_COUNT) { + case 0 -> method0(); + case 1 -> method1(); + case 2 -> method2(); + case 3 -> method3(); + case 4 -> method4(); + } + } + } + } + + public static void main(String... args) throws Exception { + List traceEvents = new ArrayList<>(); + List timingEvents = new ArrayList<>(); + try (RecordingStream r = new RecordingStream()) { + r.enable(TRACE_EVENT).with("filter", + METHOD_PREFIX + "0;" + + METHOD_PREFIX + "2;"); + r.enable(TIMING_EVENT).with("filter", + METHOD_PREFIX + "0;" + + METHOD_PREFIX + "1;" + + METHOD_PREFIX + "2;" + + METHOD_PREFIX + "3;" + + METHOD_PREFIX + "4;") + .with("period", "endChunk"); + List threads = new ArrayList<>(); + for (int i = 0; i < THREAD_COUNT; i++) { + threads.add(new TestThread()); + } + r.setReuse(false); + r.onEvent(TRACE_EVENT, traceEvents::add); + r.onEvent(TIMING_EVENT, timingEvents::add); + r.startAsync(); + for (TestThread t : threads) { + t.start(); + } + for (TestThread t : threads) { + t.join(); + } + r.stop(); + verifyTraceEvents(traceEvents); + for (RecordedEvent event : timingEvents) { + System.out.println(event); + } + verifyTimingEvents(timingEvents); + } + } + + private static void verifyTimingEvents(List events) throws Exception { + for (RecordedEvent e : events) { + long invocations = e.getLong("invocations"); + if (invocations != INVOCATIONS_PER_METHOD) { + RecordedMethod method = e.getValue("method"); + String msg = "Expected " + INVOCATIONS_PER_METHOD + " invocations for "; + msg += method.getName() + ", but got " + invocations; + throw new Exception(msg); + } + } + if (events.size() != METHOD_COUNT) { + throw new Exception("Expected " + METHOD_COUNT + " timing events, one per method"); + } + } + + private static void verifyTraceEvents(List events) throws Exception { + int expected = 2 * INVOCATIONS_PER_METHOD; + if (events.size() != expected) { + throw new Exception("Expected " + expected + " event, but got " + events.size()); + } + } + + private static void method0() { + } + + private static void method1() { + } + + private static void method2() { + } + + private static void method3() { + } + + private static void method4() { + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestRestrictedClasses.java b/test/jdk/jdk/jfr/event/tracing/TestRestrictedClasses.java new file mode 100644 index 0000000000000..2bc567e23f151 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestRestrictedClasses.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import jdk.jfr.FlightRecorder; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @summary Tests that restricted classes cannot be timed or traced. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.event.tracing.TestRestrictedClasses + **/ +public class TestRestrictedClasses { + + public static void main(String... args) throws Exception { + testJdkJfr(); + testConcurrentHashMap(); + testConcurrentHashMapNode(); + testAtomicLong(); + } + + private static void testJdkJfr() throws Exception { + testDebug(FlightRecorder.class.getName(), null); + } + + private static void testConcurrentHashMapNode() throws Exception { + testDebug(ConcurrentHashMap.class.getName() + "$Node", "Risk of recursion, skipping bytecode generation for java.util.concurrent.ConcurrentHashMap$Node"); + } + + private static void testConcurrentHashMap() throws Exception { + testDebug(ConcurrentHashMap.class.getName(), "Risk of recursion, skipping bytecode generation for java.util.concurrent.ConcurrentHashMap"); + } + + private static void testAtomicLong() throws Exception { + testDebug(AtomicLong.class.getName(), "Risk of recursion, skipping bytecode generation for java.util.concurrent.atomic.AtomicLong"); + } + + private static void testDebug(String clazz, String expected) throws Exception { + List cmds = new ArrayList<>(); + cmds.add("-Xlog:jfr+methodtrace=debug"); + cmds.add("-XX:StartFlightRecording:method-trace=" + clazz); + cmds.add("-version"); + OutputAnalyzer out = ProcessTools.executeTestJava(cmds); + out.shouldHaveExitValue(0); + if (expected != null) { + out.shouldContain(expected); + } + // Check that bytecode was not generated + out.shouldNotMatch("Bytecode generation"); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestRetransformFalse.java b/test/jdk/jdk/jfr/event/tracing/TestRetransformFalse.java new file mode 100644 index 0000000000000..60bcda4e40b37 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestRetransformFalse.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that tracing doesn't work retransformation disabled. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm + * -Xlog:jfr+methodtrace=info + * -XX:FlightRecorderOptions:retransform=false + * jdk.jfr.event.tracing.TestRetransformFalse false + * @run main/othervm -Xlog:jfr+methodtrace=info + * -Xlog:jfr+methodtrace=info + * -XX:FlightRecorderOptions:retransform=true + * jdk.jfr.event.tracing.TestRetransformFalse true + **/ +public class TestRetransformFalse { + private static final String FILTER = "jdk.jfr.event.tracing.TestRetransformFalse::foo"; + public static void main(String... args) throws Exception { + boolean retransform = switch (args[0]) { + case "true" -> true; + case "false" -> false; + default -> throw new Exception("Test error, expected 'true' or 'false' argument to test."); + }; + System.out.println("Testing -XX:FlightRecorderOptions:retransform=" + retransform); + try (Recording r = new Recording()) { + r.enable("jdk.MethodTrace") + .with("filter", FILTER); + r.enable("jdk.MethodTiming") + .with("filter", FILTER) + .with("period", "endChunk"); + r.start(); + foo(); + r.stop(); + List events = Events.fromRecording(r); + System.out.println(events); + if (retransform) { + Events.assertEventCount(events, 2); + } else { + Events.assertEventCount(events, 0); + } + } + } + + private static void foo() { + System.out.println("Running Foo"); + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestWithClassLoaders.java b/test/jdk/jdk/jfr/event/tracing/TestWithClassLoaders.java new file mode 100644 index 0000000000000..0dca1aa97ccd5 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestWithClassLoaders.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that filters can be applied to classes across multiple class loaders and that + * method tracing works after class unloading. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @build jdk.jfr.event.tracing.Car + * @run main/othervm -XX:-DisableExplicitGC -Xlog:jfr+methodtrace=trace + * jdk.jfr.event.tracing.TestWithClassLoaders + **/ +public class TestWithClassLoaders { + private static final String METHOD_TRACE = "jdk.MethodTrace"; + private static final String METHOD_TIMING = "jdk.MethodTiming"; + private static final String CLASS_NAME = "jdk.jfr.event.tracing.Car"; + + public static void main(String... args) throws Exception { + var traceEvents = new CopyOnWriteArrayList(); + var timingEvents = new CopyOnWriteArrayList(); + try (var r = new RecordingStream()) { + Runnable beforeCar = createCar("before"); + r.setReuse(false); + r.enable(METHOD_TRACE) + .with("filter", CLASS_NAME + "::run"); + r.enable(METHOD_TIMING) + .with("filter", CLASS_NAME + "::run").with("period", "endChunk"); + r.onEvent(METHOD_TRACE, traceEvents::add); + r.onEvent(METHOD_TIMING, timingEvents::add); + r.startAsync(); + Runnable duringCar = createCar("during"); + Runnable garbageCar = createCar("garbage"); + beforeCar.run(); + duringCar.run(); + garbageCar.run(); + garbageCar = null; + System.gc(); + System.gc(); + r.stop(); + System.out.println("Method Trace events:"); + System.out.println(traceEvents); + if (traceEvents.size() != 3) { + throw new Exception("Expected 3 Method Trace events, one for each class loader"); + } + for (RecordedEvent event : traceEvents) { + RecordedMethod method = event.getValue("method"); + String methodName = method.getName(); + if (!methodName.equals("run")) { + throw new Exception("Expected method name to be 'run'"); + } + } + System.out.println("Method Timing events:"); + System.out.println(timingEvents); + if (timingEvents.size() != 3) { + throw new Exception("Expected 3 Method Timing events, one for each class loader"); + } + int totalInvocations = 0; + for (RecordedEvent event : timingEvents) { + totalInvocations += event.getLong("invocations"); + } + if (totalInvocations != 3) { + throw new Exception("Expected three invocations in total, was " + totalInvocations); + } + } + } + + public static Runnable createCar(String name) throws Exception { + byte[] bytes = loadCarBytes(); + ClassLoader parent = TestWithClassLoaders.class.getClassLoader(); + CarLoader loader = new CarLoader(name, bytes, parent); + Class clazz = loader.loadClass(CLASS_NAME); + Object instance = clazz.getConstructor().newInstance(); + return (Runnable) instance; + } + + private static byte[] loadCarBytes() throws IOException { + String location = "/" + CLASS_NAME.replaceAll("\\.", "/").concat(".class"); + try (var is = TestWithClassLoaders.class.getResourceAsStream(location)) { + return is.readAllBytes(); + } + } + + public static class CarLoader extends ClassLoader { + private final byte[] bytes; + + public CarLoader(String name, byte[] bytes, ClassLoader parent) { + super(name, parent); + this.bytes = bytes; + } + + protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { + Class clazz = findLoadedClass(className); + if (clazz == null && className.equals(CLASS_NAME)) { + clazz = defineClass(className, bytes, 0, bytes.length); + } else { + clazz = super.loadClass(className, resolve); + } + if (resolve) { + resolveClass(clazz); + } + return clazz; + } + } +} diff --git a/test/jdk/jdk/jfr/event/tracing/TestWithModules.java b/test/jdk/jdk/jfr/event/tracing/TestWithModules.java new file mode 100644 index 0000000000000..2b115a6b65c05 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestWithModules.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2025, 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.event.tracing; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.jfr.Recording; +import java.util.spi.ToolProvider; + +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests applying filters to methods in both exported and unexported packages + * of a named module. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.event.tracing.TestWithModules + **/ +public class TestWithModules { + + /** Directory structure: + |-src + |-application + | |-Main.java + |-module + |-module-info.java + |-test + |-exported + | |- Exported.java + |-unexported + |- UnExported.java + **/ + private static String MODULE_INFO = + """ + module test.exported { + exports test.exported; + } + """; + + private static String EXPORTED_CLASS = + """ + package test.exported; + + import test.unexported.Unexported; + + public class Exported { + public static void run() { + System.out.println("Exported::run executed!"); + Unexported.run(); + } + } + """; + + private static String UNEXPORTED_CLASS = + """ + package test.unexported; + + public class Unexported { + public static void run() { + System.out.println("Unexported::run executed!"); + } + } + """; + + private static String MAIN_CLASS = + """ + import test.exported.Exported; + import jdk.jfr.Recording; + import java.nio.file.Path; + + public class Main { + public static void main(String... args) throws Exception { + Path file = Path.of(args[0]); + boolean before = args[1].equals("run-before"); + System.out.println("Main before=" + before); + try(Recording r = new Recording()) { + if (before) { + // Load class before JFR starts + Exported.run(); + } + r.enable("jdk.MethodTrace").with("filter", "test.unexported.Unexported::run"); + r.enable("jdk.MethodTiming").with("filter", "test.unexported.Unexported::run").with("period", "endChunk"); + r.start(); + System.out.println("About to run with instrumented"); + Exported.run(); + r.stop(); + r.dump(file); + System.out.println("Dump written " + file); + } + } + } + """; + + public static void main(String... args) throws Exception { + Path src = Path.of("src").toAbsolutePath(); + Path modulePath = materializeModule(src); + Path mainFile = materializeMain(src); + Path output = Files.createDirectory(Path.of("output").toAbsolutePath()); + List srcFiles = Files.walk(modulePath).filter(Files::isRegularFile).toList(); + List arguments = new ArrayList<>(); + arguments.add("-d"); + arguments.add(output.toString()); + for (Path p : srcFiles) { + arguments.add(p.toAbsolutePath().toString()); + } + if (!compile(arguments)) { + throw new Exception("Could not compile classes"); + } + testClassloadBefore(mainFile, output); + testClassloadDuring(mainFile, output); + } + + private static Path materializeMain(Path src) throws IOException { + Path srcApplication = Files.createDirectories(src.resolve("application")); + Path mainFile = srcApplication.resolve("Main.java"); + Files.writeString(mainFile, MAIN_CLASS); + return mainFile; + } + + private static void testClassloadBefore(Path mainFile, Path modulePath) throws Exception { + Path file = Path.of("before.jfr").toAbsolutePath(); + execute(file, mainFile, modulePath, true); + verifyRecording("already loaded class", file); + } + + private static void testClassloadDuring(Path mainFile, Path modulePath) throws Exception { + Path file = Path.of("during.jfr").toAbsolutePath(); + execute(file, mainFile, modulePath, false); + verifyRecording("loading of class", file); + } + + private static void verifyRecording(String title, Path file) throws Exception { + List traceEvents = new ArrayList<>(); + List timingEvents = new ArrayList<>(); + System.out.println("********* Verifying " + title + " ********"); + try (EventStream s = EventStream.openFile(file)) { + s.setReuse(false); + s.onEvent("jdk.MethodTrace", traceEvents::add); + s.onEvent("jdk.MethodTiming", timingEvents::add); + s.onEvent(System.out::println); + s.start(); + } + assertMethod(traceEvents, "test.unexported.Unexported", "run"); + assertMethod(timingEvents, "test.unexported.Unexported", "run"); + assertMethodTimingCount(timingEvents.get(0), 1); + } + + private static void assertMethodTimingCount(RecordedEvent event, int expected) throws Exception { + long invocations = event.getLong("invocations"); + if (invocations != expected) { + throw new Exception("Expected invocations to be " + expected + ", but was " + invocations); + } + } + + private static void assertMethod(List events, String className, String methodName) throws Exception { + for (RecordedEvent event : events) { + RecordedMethod method = event.getValue("method"); + if (method.getName().equals(methodName) && method.getType().getName().equals(className)) { + return; + } + } + throw new Exception("Expected method named " + className + "::" + methodName); + } + + private static void execute(Path jfrFile, Path mainFile, Path modulePath, boolean before) throws Exception { + String[] c = new String[7]; + c[0] = "--module-path"; + c[1] = modulePath.toString(); + c[2] = "--add-modules"; + c[3] = "test.exported"; + c[4] = mainFile.toString(); + c[5] = jfrFile.toString(); + c[6] = before ? "run-before" : "not-run-before"; + OutputAnalyzer oa = ProcessTools.executeTestJava(c); + oa.waitFor(); + oa.shouldHaveExitValue(0); + } + + private static Path materializeModule(Path src) throws IOException { + Path srcModule = Files.createDirectories(src.resolve("module")); + Path moduleFile = srcModule.resolve("module-info.java"); + Files.writeString(moduleFile, MODULE_INFO); + + Path exported = Files.createDirectories(srcModule.resolve("test").resolve("exported")); + Path exportedJava = exported.resolve("Exported.java"); + Files.writeString(exportedJava, EXPORTED_CLASS); + + Path unexported = Files.createDirectories(srcModule.resolve("test").resolve("unexported")); + Path unexportedJava = unexported.resolve("Unexported.java"); + Files.writeString(unexportedJava, UNEXPORTED_CLASS); + + return srcModule; + } + + private static boolean compile(List arguments) { + Optional tp = ToolProvider.findFirst("javac"); + if (tp.isEmpty()) { + return false; + } + var tool = tp.get(); + String[] options = arguments.toArray(String[]::new); + int ret = tool.run(System.out, System.err, options); + return ret == 0; + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index b2d8bcb12bfff..904abe8e3e297 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -91,6 +91,8 @@ public class EventNames { public static final String NativeAgent = PREFIX + "NativeAgent"; public static final String DeprecatedInvocation = PREFIX + "DeprecatedInvocation"; public static final String SafepointLatency = PREFIX + "SafepointLatency"; + public static final String MethodTiming = PREFIX + "MethodTiming"; + public static final String MethodTrace = PREFIX + "MethodTrace"; // This event is hard to test public static final String ReservedStackActivation = PREFIX + "ReservedStackActivation"; diff --git a/test/lib/jdk/test/lib/jfr/Events.java b/test/lib/jdk/test/lib/jfr/Events.java index 5676b7021d621..8bbf22ca63a40 100644 --- a/test/lib/jdk/test/lib/jfr/Events.java +++ b/test/lib/jdk/test/lib/jfr/Events.java @@ -361,6 +361,16 @@ public static void hasNotEvent(List events, String name) throws I } } + public static RecordedEvent getFirst(List events, String name) throws Exception { + for (RecordedEvent event : events) { + if (event.getEventType().getName().equals(name)) { + return event; + } + } + Asserts.fail("Missing event " + name + " in recording " + events.toString()); + return null; + } + private static boolean containsEvent(List events, String name) { for (RecordedEvent event : events) { if (event.getEventType().getName().equals(name)) { @@ -370,6 +380,12 @@ private static boolean containsEvent(List events, String name) { return false; } + public static void assertEventCount(List events, int count) throws Exception { + if (events.size() != count) { + throw new Exception("Expected " + count + " events, found " + events.size()); + } + } + public static void assertTopFrame(RecordedEvent event, Class expectedClass, String expectedMethodName) { assertTopFrame(event, expectedClass.getName(), expectedMethodName); }