8314745: JFR: @StackFilter
Reviewed-by: mgronlun
This commit is contained in:
parent
aac4318431
commit
6016536ab9
@ -525,6 +525,33 @@ const char* JfrJavaSupport::c_str(jstring string, Thread* thread, bool c_heap /*
|
||||
return string != nullptr ? c_str(resolve_non_null(string), thread, c_heap) : nullptr;
|
||||
}
|
||||
|
||||
static Symbol** allocate_symbol_array(bool c_heap, int length, Thread* thread) {
|
||||
return c_heap ?
|
||||
NEW_C_HEAP_ARRAY(Symbol*, length, mtTracing) :
|
||||
NEW_RESOURCE_ARRAY_IN_THREAD(thread, Symbol*, length);
|
||||
}
|
||||
|
||||
Symbol** JfrJavaSupport::symbol_array(jobjectArray string_array, JavaThread* thread, intptr_t* result_array_size, bool c_heap /* false */) {
|
||||
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread));
|
||||
assert(string_array != nullptr, "invariant");
|
||||
assert(result_array_size != nullptr, "invariant");
|
||||
objArrayOop arrayOop = objArrayOop(resolve_non_null(string_array));
|
||||
const int length = arrayOop->length();
|
||||
*result_array_size = length;
|
||||
Symbol** result_array = allocate_symbol_array(c_heap, length, thread);
|
||||
assert(result_array != nullptr, "invariant");
|
||||
for (int i = 0; i < length; i++) {
|
||||
oop object = arrayOop->obj_at(i);
|
||||
Symbol* symbol = nullptr;
|
||||
if (object != nullptr) {
|
||||
const char* text = c_str(arrayOop->obj_at(i), thread, c_heap);
|
||||
symbol = SymbolTable::new_symbol(text);
|
||||
}
|
||||
result_array[i] = symbol;
|
||||
}
|
||||
return result_array;
|
||||
}
|
||||
|
||||
/*
|
||||
* Exceptions and errors
|
||||
*/
|
||||
|
@ -86,6 +86,7 @@ class JfrJavaSupport : public AllStatic {
|
||||
static Klass* klass(const jobject handle);
|
||||
static const char* c_str(jstring string, Thread* thread, bool c_heap = false);
|
||||
static const char* c_str(oop string, Thread* thread, bool c_heap = false);
|
||||
static Symbol** symbol_array(jobjectArray string_array, JavaThread* thread, intptr_t* result_size, bool c_heap = false);
|
||||
|
||||
// exceptions
|
||||
static void throw_illegal_state_exception(const char* message, TRAPS);
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include "jfr/recorder/repository/jfrEmergencyDump.hpp"
|
||||
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
||||
#include "jfr/recorder/service/jfrOptionSet.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackFilter.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
|
||||
#include "jfr/recorder/stringpool/jfrStringPool.hpp"
|
||||
#include "jfr/jni/jfrJavaSupport.hpp"
|
||||
@ -239,8 +241,8 @@ JVM_ENTRY_NO_ENV(jlong, jfr_class_id(JNIEnv* env, jclass jvm, jclass jc))
|
||||
return JfrTraceId::load(jc);
|
||||
JVM_END
|
||||
|
||||
JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip))
|
||||
return JfrStackTraceRepository::record(thread, skip);
|
||||
JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id))
|
||||
return JfrStackTraceRepository::record(thread, skip, stack_filter_id);
|
||||
JVM_END
|
||||
|
||||
JVM_ENTRY_NO_ENV(void, jfr_log(JNIEnv* env, jclass jvm, jint tag_set, jint level, jstring message))
|
||||
@ -397,3 +399,11 @@ JVM_END
|
||||
JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes))
|
||||
EventDataLoss::commit(bytes, min_jlong);
|
||||
JVM_END
|
||||
|
||||
JVM_ENTRY_NO_ENV(jlong, jfr_register_stack_filter(JNIEnv* env, jclass jvm, jobjectArray classes, jobjectArray methods))
|
||||
return JfrStackFilterRegistry::add(classes, methods, thread);
|
||||
JVM_END
|
||||
|
||||
JVM_ENTRY_NO_ENV(void, jfr_unregister_stack_filter(JNIEnv* env, jclass jvm, jlong id))
|
||||
JfrStackFilterRegistry::remove(id);
|
||||
JVM_END
|
||||
|
@ -57,7 +57,7 @@ jlong JNICALL jfr_class_id(JNIEnv* env, jclass jvm, jclass jc);
|
||||
|
||||
jstring JNICALL jfr_get_pid(JNIEnv* env, jclass jvm);
|
||||
|
||||
jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip);
|
||||
jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id);
|
||||
|
||||
jlong JNICALL jfr_elapsed_frequency(JNIEnv* env, jclass jvm);
|
||||
|
||||
@ -159,6 +159,10 @@ jlong JNICALL jfr_host_total_memory(JNIEnv* env, jclass jvm);
|
||||
|
||||
void JNICALL jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes);
|
||||
|
||||
jlong JNICALL jfr_register_stack_filter(JNIEnv* env, jobject classes, jobject methods);
|
||||
|
||||
jlong JNICALL jfr_unregister_stack_filter(JNIEnv* env, jlong start_filter_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -46,7 +46,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
|
||||
(char*)"getAllEventClasses", (char*)"()Ljava/util/List;", (void*)jfr_get_all_event_classes,
|
||||
(char*)"getClassId", (char*)"(Ljava/lang/Class;)J", (void*)jfr_class_id,
|
||||
(char*)"getPid", (char*)"()Ljava/lang/String;", (void*)jfr_get_pid,
|
||||
(char*)"getStackTraceId", (char*)"(I)J", (void*)jfr_stacktrace_id,
|
||||
(char*)"getStackTraceId", (char*)"(IJ)J", (void*)jfr_stacktrace_id,
|
||||
(char*)"getThreadId", (char*)"(Ljava/lang/Thread;)J", (void*)jfr_id_for_thread,
|
||||
(char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency,
|
||||
(char*)"subscribeLogLevel", (char*)"(Ljdk/jfr/internal/LogTag;I)V", (void*)jfr_subscribe_log_level,
|
||||
@ -97,7 +97,9 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
|
||||
(char*)"isInstrumented", (char*)"(Ljava/lang/Class;)Z", (void*) jfr_is_class_instrumented,
|
||||
(char*)"isContainerized", (char*)"()Z", (void*) jfr_is_containerized,
|
||||
(char*)"hostTotalMemory", (char*)"()J", (void*) jfr_host_total_memory,
|
||||
(char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss
|
||||
(char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss,
|
||||
(char*)"registerStackFilter", (char*)"([Ljava/lang/String;[Ljava/lang/String;)J", (void*)jfr_register_stack_filter,
|
||||
(char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter
|
||||
};
|
||||
|
||||
const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod);
|
||||
|
61
src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.cpp
Normal file
61
src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackFilter.hpp"
|
||||
#include "oops/method.hpp"
|
||||
#include "oops/symbol.hpp"
|
||||
|
||||
JfrStackFilter::JfrStackFilter(Symbol** class_names, Symbol** method_names, size_t count)
|
||||
: _count(count),
|
||||
_class_names(class_names),
|
||||
_method_names(method_names) {
|
||||
assert(_class_names != nullptr, "invariant");
|
||||
assert(_method_names != nullptr, "invariant");
|
||||
}
|
||||
|
||||
bool JfrStackFilter::match(const Method* method) const {
|
||||
assert(method != nullptr, "Invariant");
|
||||
const Symbol* const method_name = method->name();
|
||||
const Symbol* const klass_name = method->klass_name();
|
||||
for (size_t i = 0; i < _count; i++) {
|
||||
const Symbol* m = _method_names[i];
|
||||
if (m == nullptr || m == method_name) {
|
||||
const Symbol* c = _class_names[i];
|
||||
if (c == nullptr || c == klass_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JfrStackFilter::~JfrStackFilter() {
|
||||
for (size_t i = 0; i < _count; i++) {
|
||||
Symbol::maybe_decrement_refcount(_method_names[i]);
|
||||
Symbol::maybe_decrement_refcount(_class_names[i]);
|
||||
}
|
||||
FREE_C_HEAP_ARRAY(Symbol*, _method_names);
|
||||
FREE_C_HEAP_ARRAY(Symbol*, _class_names);
|
||||
}
|
45
src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.hpp
Normal file
45
src/hotspot/share/jfr/recorder/stacktrace/jfrStackFilter.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKFILTER_HPP
|
||||
#define SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKFILTER_HPP
|
||||
|
||||
#include "jfr/utilities/jfrAllocation.hpp"
|
||||
|
||||
class Mathod;
|
||||
class Symbol;
|
||||
|
||||
class JfrStackFilter : public JfrCHeapObj {
|
||||
private:
|
||||
size_t _count;
|
||||
Symbol** _class_names;
|
||||
Symbol** _method_names;
|
||||
|
||||
public:
|
||||
JfrStackFilter(Symbol** class_names, Symbol** method_names, size_t count);
|
||||
~JfrStackFilter();
|
||||
bool match(const Method* method) const;
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKFILTER_HPP
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "jfr/jni/jfrJavaSupport.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackFilter.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp"
|
||||
#include "logging/log.hpp"
|
||||
|
||||
static const intptr_t STACK_FILTER_ELEMENTS_SIZE = 4096;
|
||||
static const intptr_t STACK_FILTER_ERROR_CODE = -1;
|
||||
static const JfrStackFilter* _elements[STACK_FILTER_ELEMENTS_SIZE];
|
||||
static intptr_t _free_list[STACK_FILTER_ELEMENTS_SIZE];
|
||||
static intptr_t _index = 0;
|
||||
static intptr_t _free_list_index = 0;
|
||||
|
||||
int64_t JfrStackFilterRegistry::add(jobjectArray classes, jobjectArray methods, JavaThread* jt) {
|
||||
intptr_t c_size = 0;
|
||||
Symbol** class_names = JfrJavaSupport::symbol_array(classes, jt, &c_size, true);
|
||||
assert(class_names != nullptr, "invariant");
|
||||
intptr_t m_size = 0;
|
||||
Symbol** method_names = JfrJavaSupport::symbol_array(methods, jt, &m_size, true);
|
||||
assert(method_names != nullptr, "invariant");
|
||||
if (c_size != m_size) {
|
||||
FREE_C_HEAP_ARRAY(Symbol*, class_names);
|
||||
FREE_C_HEAP_ARRAY(Symbol*, method_names);
|
||||
JfrJavaSupport::throw_internal_error("Method array size doesn't match class array size", jt);
|
||||
return STACK_FILTER_ERROR_CODE;
|
||||
}
|
||||
assert(c_size >= 0, "invariant");
|
||||
const JfrStackFilter* filter = new JfrStackFilter(class_names, method_names, static_cast<size_t>(c_size));
|
||||
return JfrStackFilterRegistry::add(filter);
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
static bool range_check(int64_t idx) {
|
||||
return idx < STACK_FILTER_ELEMENTS_SIZE && idx >= 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t JfrStackFilterRegistry::add(const JfrStackFilter* filter) {
|
||||
if (_free_list_index > 0) {
|
||||
assert(range_check(_free_list_index), "invariant");
|
||||
const intptr_t free_index = _free_list[_free_list_index - 1];
|
||||
_elements[free_index] = filter;
|
||||
_free_list_index--;
|
||||
return free_index;
|
||||
}
|
||||
if (_index >= STACK_FILTER_ELEMENTS_SIZE - 1) {
|
||||
log_warning(jfr)("Maximum number of @StackFrame in use has been reached.");
|
||||
return STACK_FILTER_ERROR_CODE;
|
||||
}
|
||||
assert(range_check(_index), "invariant");
|
||||
_elements[_index] = filter;
|
||||
return _index++;
|
||||
}
|
||||
|
||||
const JfrStackFilter* JfrStackFilterRegistry::lookup(int64_t id) {
|
||||
if (id < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(range_check(id), "invariant");
|
||||
return _elements[id];
|
||||
}
|
||||
|
||||
void JfrStackFilterRegistry::remove(int64_t index) {
|
||||
assert(range_check(index), "invariant");
|
||||
delete _elements[index];
|
||||
if (_free_list_index < STACK_FILTER_ELEMENTS_SIZE - 1) {
|
||||
assert(range_check(_free_list_index), "invariant");
|
||||
_free_list[_free_list_index++] = index;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_JFR_RECORDER_STACKTRACE_JFRSTCKFILTERREGISTRY_HPP
|
||||
#define SHARE_JFR_RECORDER_STACKTRACE_JFRSTCKFILTERREGISTRY_HPP
|
||||
|
||||
#include "jni.h"
|
||||
#include "jfr/utilities/jfrAllocation.hpp"
|
||||
|
||||
class JavaThread;
|
||||
class JfrStackFilter;
|
||||
|
||||
class JfrStackFilterRegistry : AllStatic {
|
||||
private:
|
||||
static int64_t add(const JfrStackFilter* frame);
|
||||
public:
|
||||
static int64_t add(jobjectArray classes, jobjectArray methods, JavaThread* jt);
|
||||
static void remove(int64_t id);
|
||||
static const JfrStackFilter* lookup(int64_t id);
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTCKFILTERREGISTRY_HPP
|
@ -30,6 +30,8 @@
|
||||
#include "jfr/recorder/storage/jfrBuffer.hpp"
|
||||
#include "jfr/support/jfrMethodLookup.hpp"
|
||||
#include "jfr/support/jfrThreadLocal.hpp"
|
||||
#include "jfrStackFilter.hpp"
|
||||
#include "jfrStackFilterRegistry.hpp"
|
||||
#include "memory/allocation.inline.hpp"
|
||||
#include "oops/instanceKlass.inline.hpp"
|
||||
#include "runtime/continuation.hpp"
|
||||
@ -284,7 +286,7 @@ bool JfrStackTrace::record_async(JavaThread* jt, const frame& frame) {
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
|
||||
bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip, int64_t stack_filter_id) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
assert(jt == Thread::current(), "invariant");
|
||||
assert(jt->thread_state() != _thread_in_native, "invariant");
|
||||
@ -302,6 +304,7 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
|
||||
}
|
||||
vfs.next_vframe();
|
||||
}
|
||||
const JfrStackFilter* stack_filter = JfrStackFilterRegistry::lookup(stack_filter_id);
|
||||
_hash = 1;
|
||||
while (!vfs.at_end()) {
|
||||
if (count >= _max_frames) {
|
||||
@ -309,6 +312,12 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
|
||||
break;
|
||||
}
|
||||
const Method* method = vfs.method();
|
||||
if (stack_filter != nullptr) {
|
||||
if (stack_filter->match(method)) {
|
||||
vfs.next_vframe();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const traceid mid = JfrTraceId::load(method);
|
||||
u1 type = vfs.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
|
||||
int bci = 0;
|
||||
@ -335,13 +344,13 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
bool JfrStackTrace::record(JavaThread* current_thread, int skip) {
|
||||
bool JfrStackTrace::record(JavaThread* current_thread, int skip, int64_t stack_filter_id) {
|
||||
assert(current_thread != nullptr, "invariant");
|
||||
assert(current_thread == Thread::current(), "invariant");
|
||||
if (!current_thread->has_last_Java_frame()) {
|
||||
return false;
|
||||
}
|
||||
return record(current_thread, current_thread->last_frame(), skip);
|
||||
return record(current_thread, current_thread->last_frame(), skip, stack_filter_id);
|
||||
}
|
||||
|
||||
void JfrStackFrame::resolve_lineno() const {
|
||||
|
@ -93,8 +93,8 @@ class JfrStackTrace : public JfrCHeapObj {
|
||||
void set_reached_root(bool reached_root) { _reached_root = reached_root; }
|
||||
void resolve_linenos() const;
|
||||
|
||||
bool record(JavaThread* current_thread, int skip);
|
||||
bool record(JavaThread* current_thread, const frame& frame, int skip);
|
||||
bool record(JavaThread* current_thread, int skip, int64_t stack_frame_id);
|
||||
bool record(JavaThread* current_thread, const frame& frame, int skip, int64_t stack_frame_id);
|
||||
bool record_async(JavaThread* other_thread, const frame& frame);
|
||||
|
||||
bool have_lineno() const { return _lineno; }
|
||||
|
@ -146,7 +146,7 @@ size_t JfrStackTraceRepository::clear(JfrStackTraceRepository& repo) {
|
||||
return processed;
|
||||
}
|
||||
|
||||
traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */) {
|
||||
traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */, int64_t stack_filter_id /* -1 */) {
|
||||
assert(current_thread == Thread::current(), "invariant");
|
||||
JfrThreadLocal* const tl = current_thread->jfr_thread_local();
|
||||
assert(tl != nullptr, "invariant");
|
||||
@ -163,13 +163,14 @@ traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */
|
||||
}
|
||||
assert(frames != nullptr, "invariant");
|
||||
assert(tl->stackframes() == frames, "invariant");
|
||||
return instance().record(JavaThread::cast(current_thread), skip, frames, tl->stackdepth());
|
||||
return instance().record(JavaThread::cast(current_thread), skip, stack_filter_id, frames, tl->stackdepth());
|
||||
}
|
||||
|
||||
traceid JfrStackTraceRepository::record(JavaThread* current_thread, int skip, JfrStackFrame *frames, u4 max_frames) {
|
||||
traceid JfrStackTraceRepository::record(JavaThread* current_thread, int skip, int64_t stack_filter_id, JfrStackFrame *frames, u4 max_frames) {
|
||||
JfrStackTrace stacktrace(frames, max_frames);
|
||||
return stacktrace.record(current_thread, skip) ? add(instance(), stacktrace) : 0;
|
||||
return stacktrace.record(current_thread, skip, stack_filter_id) ? add(instance(), stacktrace) : 0;
|
||||
}
|
||||
|
||||
traceid JfrStackTraceRepository::add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace) {
|
||||
traceid tid = repo.add_trace(stacktrace);
|
||||
if (tid == 0) {
|
||||
@ -191,7 +192,7 @@ void JfrStackTraceRepository::record_for_leak_profiler(JavaThread* current_threa
|
||||
assert(tl != nullptr, "invariant");
|
||||
assert(!tl->has_cached_stack_trace(), "invariant");
|
||||
JfrStackTrace stacktrace(tl->stackframes(), tl->stackdepth());
|
||||
stacktrace.record(current_thread, skip);
|
||||
stacktrace.record(current_thread, skip, -1);
|
||||
const traceid hash = stacktrace.hash();
|
||||
if (hash != 0) {
|
||||
tl->set_cached_stack_trace_id(add(leak_profiler_instance(), stacktrace), hash);
|
||||
|
@ -67,10 +67,10 @@ class JfrStackTraceRepository : public JfrCHeapObj {
|
||||
traceid add_trace(const JfrStackTrace& stacktrace);
|
||||
static traceid add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace);
|
||||
static traceid add(const JfrStackTrace& stacktrace);
|
||||
traceid record(JavaThread* current_thread, int skip, JfrStackFrame* frames, u4 max_frames);
|
||||
traceid record(JavaThread* current_thread, int skip, int64_t stack_filter_id, JfrStackFrame* frames, u4 max_frames);
|
||||
|
||||
public:
|
||||
static traceid record(Thread* current_thread, int skip = 0);
|
||||
static traceid record(Thread* current_thread, int skip = 0, int64_t stack_filter_id = -1);
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKTRACEREPOSITORY_HPP
|
||||
|
@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields;
|
||||
@Description("Operating system process started")
|
||||
@MirrorEvent(className = "jdk.internal.event.ProcessStartEvent")
|
||||
@RemoveFields("duration")
|
||||
@StackFilter({"java.lang.ProcessBuilder", "java.lang.Runtime::exec"})
|
||||
public final class ProcessStartEvent extends AbstractJDKEvent {
|
||||
@Label("Process Id")
|
||||
public long pid;
|
||||
|
@ -35,6 +35,7 @@ import jdk.jfr.internal.RemoveFields;
|
||||
@Description("Modification of Security property")
|
||||
@MirrorEvent(className = "jdk.internal.event.SecurityPropertyModificationEvent")
|
||||
@RemoveFields("duration")
|
||||
@StackFilter({"java.security.Security::setProperty"})
|
||||
public final class SecurityPropertyModificationEvent extends AbstractJDKEvent {
|
||||
@Label("Key")
|
||||
public String key;
|
||||
|
@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields;
|
||||
@Description("Details of Provider.getInstance(String type, String algorithm) calls")
|
||||
@MirrorEvent(className = "jdk.internal.event.SecurityProviderServiceEvent")
|
||||
@RemoveFields("duration")
|
||||
@StackFilter({"java.security.Provider::getService"})
|
||||
public final class SecurityProviderServiceEvent extends AbstractJDKEvent {
|
||||
@Label("Type of Service")
|
||||
public String type;
|
||||
|
65
src/jdk.jfr/share/classes/jdk/jfr/events/StackFilter.java
Normal file
65
src/jdk.jfr/share/classes/jdk/jfr/events/StackFilter.java
Normal file
@ -0,0 +1,65 @@
|
||||
package jdk.jfr.events;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import jdk.jfr.MetadataDefinition;
|
||||
|
||||
/**
|
||||
* Event annotation, specifies method names or classes to exclude in a stack
|
||||
* trace.
|
||||
* <p>
|
||||
* The following example illustrates how the {@code StackFilter} annotation can
|
||||
* be used to remove the {@code Logger::log} method in a stack trace:
|
||||
*
|
||||
* {@snippet :
|
||||
* package com.example;
|
||||
*
|
||||
* @Name("example.LogMessage")
|
||||
* @Label("Log Message")
|
||||
* @StackFilter("com.example.Logger::log")
|
||||
* class LogMessage extends Event {
|
||||
* @Label("Message")
|
||||
* String message;
|
||||
* }
|
||||
*
|
||||
* public class Logger {
|
||||
*
|
||||
* public static void log(String message) {
|
||||
* System.out.print(Instant.now() + " : " + message);
|
||||
* LogMessage event = new LogMessage();
|
||||
* event.message = message;
|
||||
* event.commit();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @since 22
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MetadataDefinition
|
||||
public @interface StackFilter {
|
||||
/**
|
||||
* The methods or classes that should not be part of an event stack trace.
|
||||
* <p>
|
||||
* A filter is formed by using the fully qualified class name concatenated with
|
||||
* the method name using {@code "::"} as separator, for example
|
||||
* {@code "java.lang.String::toString"}
|
||||
* <p>
|
||||
* If only the name of a class is specified, for example {@code
|
||||
* "java.lang.String"}, all methods in that class are filtered out.
|
||||
* <p>
|
||||
* Methods can't be qualified using method parameters or return types.
|
||||
* <p>
|
||||
* Instance methods belonging to an interface can't be filtered out.
|
||||
* <p>
|
||||
* Wilcards are not permitted.
|
||||
*
|
||||
* @return the method names, not {@code null}
|
||||
*/
|
||||
String[] value();
|
||||
}
|
@ -39,6 +39,7 @@ import jdk.jfr.internal.RemoveFields;
|
||||
@Description("Parameters used in TLS Handshake")
|
||||
@MirrorEvent(className = "jdk.internal.event.TLSHandshakeEvent")
|
||||
@RemoveFields("duration")
|
||||
@StackFilter("sun.security.ssl.Finished::recordEvent")
|
||||
public final class TLSHandshakeEvent extends AbstractJDKEvent {
|
||||
@Label("Peer Host")
|
||||
public String peerHost;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -35,6 +35,9 @@ import jdk.jfr.internal.MirrorEvent;
|
||||
@Label("Java Thread Sleep")
|
||||
@Name("jdk.ThreadSleep")
|
||||
@MirrorEvent(className = "jdk.internal.event.ThreadSleepEvent")
|
||||
@StackFilter({"java.lang.Thread::afterSleep",
|
||||
"java.lang.Thread::sleepNanos",
|
||||
"java.lang.Thread::sleep"})
|
||||
public final class ThreadSleepEvent extends AbstractJDKEvent {
|
||||
@Label("Sleep Time")
|
||||
@Timespan(Timespan.NANOSECONDS)
|
||||
|
@ -34,6 +34,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.internal.module.Checks;
|
||||
import jdk.internal.module.Modules;
|
||||
import jdk.jfr.AnnotationElement;
|
||||
import jdk.jfr.Enabled;
|
||||
@ -44,6 +45,8 @@ import jdk.jfr.SettingDefinition;
|
||||
import jdk.jfr.StackTrace;
|
||||
import jdk.jfr.Threshold;
|
||||
import jdk.jfr.events.ActiveSettingEvent;
|
||||
import jdk.jfr.events.StackFilter;
|
||||
import jdk.jfr.internal.JVM;
|
||||
import jdk.jfr.internal.settings.CutoffSetting;
|
||||
import jdk.jfr.internal.settings.EnabledSetting;
|
||||
import jdk.jfr.internal.settings.PeriodSetting;
|
||||
@ -65,6 +68,7 @@ public final class EventControl {
|
||||
private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
|
||||
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
|
||||
private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.class);
|
||||
private static final long STACK_FILTER_ID = Type.getTypeId(StackFilter.class);
|
||||
|
||||
private final ArrayList<SettingControl> settingControls = new ArrayList<>();
|
||||
private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
|
||||
@ -89,6 +93,7 @@ public final class EventControl {
|
||||
}
|
||||
addControl(Enabled.NAME, defineEnabled(eventType));
|
||||
|
||||
addStackFilters(eventType);
|
||||
List<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
|
||||
remove(eventType, aes, Threshold.class);
|
||||
remove(eventType, aes, Period.class);
|
||||
@ -96,11 +101,84 @@ public final class EventControl {
|
||||
remove(eventType, aes, StackTrace.class);
|
||||
remove(eventType, aes, Cutoff.class);
|
||||
remove(eventType, aes, Throttle.class);
|
||||
remove(eventType, aes, StackFilter.class);
|
||||
eventType.setAnnotations(aes);
|
||||
this.type = eventType;
|
||||
this.idName = String.valueOf(eventType.getId());
|
||||
}
|
||||
|
||||
private void addStackFilters(PlatformEventType eventType) {
|
||||
String[] filter = getStackFilter(eventType);
|
||||
if (filter != null) {
|
||||
int size = filter.length;
|
||||
List<String> types = new ArrayList<>(size);
|
||||
List<String> methods = new ArrayList<>(size);
|
||||
for (String frame : filter) {
|
||||
int index = frame.indexOf("::");
|
||||
String clazz = null;
|
||||
String method = null;
|
||||
boolean valid = false;
|
||||
if (index != -1) {
|
||||
clazz = frame.substring(0, index);
|
||||
method = frame.substring(index + 2);
|
||||
if (clazz.isEmpty()) {
|
||||
clazz = null;
|
||||
valid = isValidMethod(method);
|
||||
} else {
|
||||
valid = isValidType(clazz) && isValidMethod(method);
|
||||
}
|
||||
} else {
|
||||
clazz = frame;
|
||||
valid = isValidType(frame);
|
||||
}
|
||||
if (valid) {
|
||||
if (clazz == null) {
|
||||
types.add(null);
|
||||
} else {
|
||||
types.add(clazz.replace(".", "/"));
|
||||
}
|
||||
// If unqualified class name equals method name, it's a constructor
|
||||
String className = clazz.substring(clazz.lastIndexOf(".") + 1);
|
||||
if (className.equals(method)) {
|
||||
method = "<init>";
|
||||
}
|
||||
methods.add(method);
|
||||
} else {
|
||||
Logger.log(LogTag.JFR, LogLevel.WARN, "@StackFrameFilter element ignored, not a valid Java identifier.");
|
||||
}
|
||||
}
|
||||
if (!types.isEmpty()) {
|
||||
String[] typeArray = types.toArray(new String[0]);
|
||||
String[] methodArray = methods.toArray(new String[0]);
|
||||
long id = MetadataRepository.getInstance().registerStackFilter(typeArray, methodArray);
|
||||
eventType.setStackFilterId(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getStackFilter(PlatformEventType eventType) {
|
||||
for (var a : eventType.getAnnotationElements()) {
|
||||
if (a.getTypeId() == STACK_FILTER_ID) {
|
||||
return (String[])a.getValue("value");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isValidType(String className) {
|
||||
if (className.length() < 1 || className.length() > 65535) {
|
||||
return false;
|
||||
}
|
||||
return Checks.isClassName(className);
|
||||
}
|
||||
|
||||
private boolean isValidMethod(String method) {
|
||||
if (method.length() < 1 || method.length() > 65535) {
|
||||
return false;
|
||||
}
|
||||
return Checks.isJavaIdentifier(method);
|
||||
}
|
||||
|
||||
private boolean hasControl(String name) {
|
||||
for (NamedControl nc : namedControls) {
|
||||
if (name.equals(nc.name)) {
|
||||
|
@ -149,10 +149,15 @@ public final class JVM {
|
||||
*
|
||||
* Requires that JFR has been started with {@link #createNativeJFR()}
|
||||
*
|
||||
* @param skipCount number of frames to skip
|
||||
* @param skipCount number of frames to skip, or 0 if no frames should be
|
||||
* skipped
|
||||
*
|
||||
* @param ID ID of the filter that should be used, or -1 if no filter should
|
||||
* be used
|
||||
*
|
||||
* @return a unique stack trace identifier
|
||||
*/
|
||||
public static native long getStackTraceId(int skipCount);
|
||||
public static native long getStackTraceId(int skipCount, long stackFilerId);
|
||||
|
||||
/**
|
||||
* Return identifier for thread
|
||||
@ -628,4 +633,30 @@ public final class JVM {
|
||||
* @param bytes number of bytes that were lost
|
||||
*/
|
||||
public static native void emitDataLoss(long bytes);
|
||||
|
||||
/**
|
||||
* Registers stack filters that should be used with getStackTrace(int, long)
|
||||
* <p>
|
||||
* Method name at an array index is for class at the same array index.
|
||||
* <p>
|
||||
* This method should be called holding the MetadataRepository lock and before
|
||||
* bytecode for the associated event class has been added.
|
||||
*
|
||||
* @param classes, name of classes, for example {"java/lang/String"}, not
|
||||
* {@code null}
|
||||
* @param methods, name of method, for example {"toString"}, not {@code null}
|
||||
*
|
||||
* @return an ID that can be used to unregister the start frames, or -1 if it could not be registered
|
||||
*/
|
||||
public static native long registerStackFilter(String[] classes, String[] methods);
|
||||
|
||||
/**
|
||||
* Unregisters a set of stack filters.
|
||||
* <p>
|
||||
* This method should be called holding the MetadataRepository lock and after
|
||||
* the associated event class has been unloaded.
|
||||
*
|
||||
* @param stackFilterId the stack filter ID to unregister
|
||||
*/
|
||||
public static native void unregisterStackFilter(long stackFilterId);
|
||||
}
|
||||
|
@ -321,6 +321,9 @@ public final class MetadataRepository {
|
||||
if (!knownIds.contains(pe.getId())) {
|
||||
if (!pe.isJVM()) {
|
||||
pe.setRegistered(false);
|
||||
if (pe.hasStackFilters()) {
|
||||
JVM.unregisterStackFilter(pe.getStackFilterId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -355,4 +358,8 @@ public final class MetadataRepository {
|
||||
public synchronized List<Type> getVisibleTypes() {
|
||||
return TypeLibrary.getVisibleTypes();
|
||||
}
|
||||
|
||||
public synchronized long registerStackFilter(String[] typeArray, String[] methodArray) {
|
||||
return JVM.registerStackFilter(typeArray, methodArray);
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public final class PlatformEventType extends Type {
|
||||
private final List<SettingDescriptor> settings = new ArrayList<>(5);
|
||||
private final boolean dynamicSettings;
|
||||
private final int stackTraceOffset;
|
||||
private long startFilterId = -1;
|
||||
|
||||
// default values
|
||||
private boolean largeSize = false;
|
||||
@ -339,4 +340,16 @@ public final class PlatformEventType extends Type {
|
||||
public boolean isMethodSampling() {
|
||||
return isMethodSampling;
|
||||
}
|
||||
|
||||
public void setStackFilterId(long id) {
|
||||
startFilterId = id;
|
||||
}
|
||||
|
||||
public boolean hasStackFilters() {
|
||||
return startFilterId >= 0;
|
||||
}
|
||||
|
||||
public long getStackFilterId() {
|
||||
return startFilterId;
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ import jdk.jfr.Timestamp;
|
||||
import jdk.jfr.ValueDescriptor;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
import jdk.jfr.internal.util.ImplicitFields;
|
||||
import jdk.internal.module.Modules;
|
||||
|
||||
public final class TypeLibrary {
|
||||
private static boolean implicitFieldTypes;
|
||||
@ -165,7 +166,6 @@ public final class TypeLibrary {
|
||||
for (ValueDescriptor v : type.getFields()) {
|
||||
values.add(invokeAnnotation(annotation, v.getName()));
|
||||
}
|
||||
|
||||
return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null);
|
||||
}
|
||||
return null;
|
||||
@ -178,6 +178,15 @@ public final class TypeLibrary {
|
||||
} catch (NoSuchMethodException e1) {
|
||||
throw (Error) new InternalError("Could not locate method " + methodName + " in annotation " + annotation.getClass().getName());
|
||||
}
|
||||
// Add export from JDK proxy module
|
||||
if (annotation.getClass().getClassLoader() == null) {
|
||||
if (annotation.getClass().getName().contains("Proxy")) {
|
||||
Module proxyModule = annotation.getClass().getModule();
|
||||
String proxyPackage = annotation.getClass().getPackageName();
|
||||
Module jfrModule = TypeLibrary.class.getModule();
|
||||
Modules.addExports(proxyModule, proxyPackage, jfrModule);
|
||||
}
|
||||
}
|
||||
SecuritySupport.setAccessible(m);
|
||||
try {
|
||||
return m.invoke(annotation, new Object[0]);
|
||||
|
@ -188,7 +188,7 @@ public final class EventWriter {
|
||||
|
||||
public void putStackTrace() {
|
||||
if (eventType.getStackTraceEnabled()) {
|
||||
putLong(JVM.getStackTraceId(eventType.getStackTraceOffset()));
|
||||
putLong(JVM.getStackTraceId(eventType.getStackTraceOffset(), eventType.getStackFilterId()));
|
||||
} else {
|
||||
putLong(0L);
|
||||
}
|
||||
|
319
test/jdk/jdk/jfr/api/metadata/annotations/TestStackFilter.java
Normal file
319
test/jdk/jdk/jfr/api/metadata/annotations/TestStackFilter.java
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.api.metadata.annotations;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.jfr.api.metadata.annotations.UnloadableClass;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.AnnotationElement;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordedFrame;
|
||||
import jdk.jfr.consumer.RecordedMethod;
|
||||
import jdk.jfr.consumer.RecordedStackTrace;
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.jfr.events.StackFilter;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.Name;
|
||||
import jdk.jfr.EventType;
|
||||
import jdk.jfr.EventFactory;
|
||||
import jdk.jfr.FlightRecorder;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
import jdk.test.lib.jfr.TestClassLoader;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @modules jdk.jfr/jdk.jfr.events
|
||||
* @library /test/lib /test/jdk
|
||||
* @run main/othervm -Xlog:jfr=warning jdk.jfr.api.metadata.annotations.TestStackFilter
|
||||
*/
|
||||
public class TestStackFilter {
|
||||
private static class Quux {
|
||||
private static void one() throws Exception {
|
||||
two();
|
||||
}
|
||||
private static void two() throws Exception {
|
||||
three();
|
||||
}
|
||||
|
||||
private static void three() throws Exception {
|
||||
TestStackFilter.qux();
|
||||
}
|
||||
}
|
||||
private final static String PACKAGE = "jdk.jfr.api.metadata.annotations.TestStackFilter";
|
||||
private final static String M1 = PACKAGE + "::foo";
|
||||
private final static String M2 = PACKAGE + "::baz";
|
||||
private final static String C1 = PACKAGE + "$Quux";
|
||||
|
||||
@StackFilter({ M1, M2 })
|
||||
@Name("MethodFilter")
|
||||
public static class MethodFilterEvent extends Event {
|
||||
}
|
||||
|
||||
@StackFilter(C1)
|
||||
@Name("ClassFilter")
|
||||
public static class ClassFilterEvent extends Event {
|
||||
}
|
||||
|
||||
@StackFilter({})
|
||||
@Name("Empty")
|
||||
public static class EmptyEvent extends Event {
|
||||
}
|
||||
|
||||
@StackFilter(PACKAGE + "::testUnload")
|
||||
@Name("Unload")
|
||||
public static class UnloadEvent extends Event {
|
||||
}
|
||||
|
||||
@StackFilter(PACKAGE + "::emitCommitter")
|
||||
@Name("Reuse")
|
||||
public static class ReuseEvent extends Event {
|
||||
}
|
||||
|
||||
@StackFilter(PACKAGE + "::emitCommitter")
|
||||
@Name("Max")
|
||||
public static class ExceedMaxEvent extends Event {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testMethodFilter();
|
||||
testClassFilter();
|
||||
testUnload();
|
||||
testReuse();
|
||||
testExceedMax();
|
||||
}
|
||||
|
||||
// Use more stack filters than there is capacity for
|
||||
private static void testExceedMax() throws Exception {
|
||||
try (Recording r = new Recording()) {
|
||||
r.start();
|
||||
// Maximum number of simultaneous event classes that can
|
||||
// use a filter is 4096. Additional filters will be ignored.
|
||||
var classes = new ArrayList<>();
|
||||
for (int i = 0; i < 4200; i++) {
|
||||
Class<ExceedMaxEvent> eventClass = UnloadableClass.load(ExceedMaxEvent.class);
|
||||
emitCommitter(eventClass);
|
||||
classes.add(eventClass);
|
||||
}
|
||||
List<RecordedEvent> events = Events.fromRecording(r);
|
||||
if (events.size() != 4200) {
|
||||
throw new Exception("Expected 4200 'Max' events");
|
||||
}
|
||||
int emitCommitterCount = 0;
|
||||
int textExceedMaxCount = 0;
|
||||
for (RecordedEvent event : events) {
|
||||
RecordedStackTrace s = event.getStackTrace();
|
||||
if (s == null) {
|
||||
System.out.println(event);
|
||||
throw new Exception("Expected stack trace for 'Max' event");
|
||||
}
|
||||
|
||||
RecordedFrame f = s.getFrames().get(0);
|
||||
if (!f.isJavaFrame()) {
|
||||
throw new Exception("Expected Java frame for 'Max' event");
|
||||
}
|
||||
String methodName = f.getMethod().getName();
|
||||
switch (methodName) {
|
||||
case "emitCommitter":
|
||||
emitCommitterCount++;
|
||||
break;
|
||||
case "testExceedMax":
|
||||
textExceedMaxCount++;
|
||||
break;
|
||||
default:
|
||||
System.out.println(event);
|
||||
throw new Exception("Unexpected top frame " + methodName + " for 'Max' event");
|
||||
}
|
||||
}
|
||||
// Can't match exact because filters from previous tests may be in use
|
||||
// or because filters added by JDK events filters
|
||||
if (emitCommitterCount == 0) {
|
||||
throw new Exception("Expected at least some events with emitCommitter() as top frame, found " + emitCommitterCount);
|
||||
}
|
||||
if (textExceedMaxCount < 500) {
|
||||
throw new Exception("Expected at least 500 events with testExceedMax() as top frame, found " + textExceedMaxCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that event classes with @StackFilter that are unloaded
|
||||
// reuses the memory slot used to bookkeep things in native
|
||||
private static void testReuse() throws Exception {
|
||||
try (Recording r = new Recording()) {
|
||||
r.enable("Reuse");
|
||||
r.start();
|
||||
for (int i = 0; i < 48; i++) {
|
||||
Class<ReuseEvent> eventClass = UnloadableClass.load(ReuseEvent.class);
|
||||
emitCommitter(eventClass);
|
||||
if (i % 16 == 0) {
|
||||
System.gc();
|
||||
rotate();
|
||||
}
|
||||
}
|
||||
r.stop();
|
||||
List<RecordedEvent> events = Events.fromRecording(r);
|
||||
if (events.size() != 48) {
|
||||
throw new Exception("Expected 48 'Reuse' events");
|
||||
}
|
||||
for (RecordedEvent event : events) {
|
||||
assertTopFrame(event, "testReuse");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This test registers a stack filter, emits an event with the filter
|
||||
// and unregisters it. While this is happening, another
|
||||
// filter is being used.
|
||||
private static void testUnload() throws Exception {
|
||||
try (Recording r = new Recording()) {
|
||||
r.start();
|
||||
Class<UnloadEvent> eventClass = UnloadableClass.load(UnloadEvent.class);
|
||||
emitCommitter(eventClass);
|
||||
EventType type = getType("Unload");
|
||||
if (type == null) {
|
||||
throw new Exception("Expected event type named 'Unload'");
|
||||
}
|
||||
eventClass = null;
|
||||
while (true) {
|
||||
System.gc();
|
||||
rotate();
|
||||
type = getType("Unload");
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
System.out.println("Unload class not unloaded. Retrying ...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void testMethodFilter() throws Exception {
|
||||
try (Recording r = new Recording()) {
|
||||
r.enable(MethodFilterEvent.class);
|
||||
r.start();
|
||||
foo();
|
||||
bar();
|
||||
empty();
|
||||
r.stop();
|
||||
List<RecordedEvent> events = Events.fromRecording(r);
|
||||
if (events.isEmpty()) {
|
||||
throw new Exception("Excected events");
|
||||
}
|
||||
|
||||
RecordedEvent e1 = events.get(0);
|
||||
assertTopFrame(e1, "testMethodFilter");
|
||||
|
||||
RecordedEvent e2 = events.get(1);
|
||||
assertTopFrame(e2, "bar");
|
||||
|
||||
RecordedEvent e3 = events.get(2);
|
||||
assertTopFrame(e3, "empty");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testClassFilter() throws Exception {
|
||||
try (Recording r = new Recording()) {
|
||||
r.enable(MethodFilterEvent.class);
|
||||
r.start();
|
||||
Quux.one();
|
||||
r.stop();
|
||||
List<RecordedEvent> events = Events.fromRecording(r);
|
||||
if (events.isEmpty()) {
|
||||
throw new Exception("Excected events");
|
||||
}
|
||||
|
||||
RecordedEvent e = events.get(0);
|
||||
assertTopFrame(e, "qux");
|
||||
for (RecordedFrame f : e.getStackTrace().getFrames()) {
|
||||
if (f.getMethod().getType().getName().contains("Quux")) {
|
||||
System.out.println(e);
|
||||
throw new Exception("Didn't expect Quux class in stack trace");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void empty() {
|
||||
EmptyEvent event = new EmptyEvent();
|
||||
event.commit();
|
||||
}
|
||||
|
||||
static void foo() {
|
||||
baz();
|
||||
}
|
||||
|
||||
static void bar() {
|
||||
baz();
|
||||
}
|
||||
|
||||
static void baz() {
|
||||
MethodFilterEvent event = new MethodFilterEvent();
|
||||
event.commit();
|
||||
}
|
||||
|
||||
static void qux() {
|
||||
ClassFilterEvent event = new ClassFilterEvent();
|
||||
event.commit();
|
||||
}
|
||||
|
||||
private static void rotate() {
|
||||
try (Recording r = new Recording()) {
|
||||
r.start();
|
||||
}
|
||||
}
|
||||
|
||||
private static EventType getType(String name) {
|
||||
for (EventType et : FlightRecorder.getFlightRecorder().getEventTypes()) {
|
||||
if (et.getName().equals(name)) {
|
||||
return et;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void emitCommitter(Class<? extends Event> eventClass) throws Exception {
|
||||
Event event = eventClass.getConstructor().newInstance();
|
||||
event.commit();
|
||||
}
|
||||
|
||||
private static void assertTopFrame(RecordedEvent event, String methodName) throws Exception {
|
||||
RecordedStackTrace stackTrace = event.getStackTrace();
|
||||
if (stackTrace == null) {
|
||||
System.out.println(event);
|
||||
throw new Exception("No stack trace found when looking for top frame '" + methodName + "'");
|
||||
}
|
||||
RecordedFrame frame = stackTrace.getFrames().get(0);
|
||||
RecordedMethod method = frame.getMethod();
|
||||
if (!methodName.equals(method.getName())) {
|
||||
System.out.println(event);
|
||||
throw new Exception("Expected top frame '" + methodName + "'");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.api.metadata.annotations;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
/* Purpose of this class is to load a specified class in its
|
||||
* own class loader, but delegate every other class.
|
||||
*/
|
||||
public final class UnloadableClass<T> extends ClassLoader {
|
||||
private final String className;
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public static <T> Class<T> load(Class<T> clazz) throws ClassNotFoundException {
|
||||
UnloadableClass cl = new UnloadableClass(clazz.getName());
|
||||
return cl.loadClass(cl.className);
|
||||
}
|
||||
|
||||
private UnloadableClass(String className) {
|
||||
super("Class loader for class " + className, ClassLoader.getSystemClassLoader());
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
if (!className.equals(name)) {
|
||||
return super.loadClass(name);
|
||||
}
|
||||
String resourceName = name.replace('.', '/') + ".class";
|
||||
try (var is = getResourceAsStream(resourceName); var dis = new DataInputStream(is)) {
|
||||
int size = is.available();
|
||||
byte buffer[] = new byte[size];
|
||||
dis.readFully(buffer);
|
||||
CodeSource cs = new CodeSource(getResource(resourceName), (Certificate[]) null);
|
||||
ProtectionDomain pd = new ProtectionDomain(cs, null);
|
||||
return defineClass(name, buffer, 0, buffer.length, pd);
|
||||
} catch (Exception e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ public class TestProcessStart {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable(EVENT_NAME);
|
||||
recording.enable(EVENT_NAME).withStackTrace();
|
||||
recording.start();
|
||||
List<String> commandList = new ArrayList<>();
|
||||
if (Platform.isWindows()) {
|
||||
@ -74,6 +74,7 @@ public class TestProcessStart {
|
||||
Events.assertField(event, "pid").equal(p.pid());
|
||||
Events.assertField(event, "directory").equal(pb.directory().toString());
|
||||
Events.assertField(event, "command").equal(command.toString());
|
||||
Events.assertTopFrame(event, TestProcessStart.class, "main");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,13 +56,7 @@ public class TestThreadSleepEvent {
|
||||
recording.enable(EVENT_NAME).withoutThreshold().withStackTrace();
|
||||
recording.start();
|
||||
Thread.sleep(SLEEP_TIME_MS);
|
||||
Thread virtualThread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
Thread.sleep(SLEEP_TIME_MS);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException(ie);
|
||||
}
|
||||
});
|
||||
Thread virtualThread = Thread.ofVirtual().start(TestThreadSleepEvent::virtualSleep);
|
||||
virtualThread.join();
|
||||
recording.stop();
|
||||
|
||||
@ -74,14 +68,24 @@ public class TestThreadSleepEvent {
|
||||
System.out.println(event.getStackTrace());
|
||||
if (event.getThread().getJavaThreadId() == Thread.currentThread().getId()) {
|
||||
threadCount--;
|
||||
Events.assertTopFrame(event, TestThreadSleepEvent.class, "main");
|
||||
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
|
||||
}
|
||||
if (event.getThread().getJavaThreadId() == virtualThread.getId()) {
|
||||
threadCount--;
|
||||
Events.assertTopFrame(event, TestThreadSleepEvent.class, "virtualSleep");
|
||||
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
|
||||
}
|
||||
}
|
||||
Asserts.assertEquals(threadCount, 0, "Could not find all expected events");
|
||||
}
|
||||
}
|
||||
|
||||
private static void virtualSleep() {
|
||||
try {
|
||||
Thread.sleep(SLEEP_TIME_MS);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException(ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class TestSecurityPropertyModificationEvent {
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable(EventNames.SecurityProperty);
|
||||
recording.enable(EventNames.SecurityProperty).withStackTrace();
|
||||
recording.start();
|
||||
for (String key: keys) {
|
||||
Security.setProperty(key, keyValue);
|
||||
@ -78,6 +78,7 @@ public class TestSecurityPropertyModificationEvent {
|
||||
if (keys.contains(e.getString("key"))) {
|
||||
Events.assertField(e, "value").equal(keyValue);
|
||||
i++;
|
||||
Events.assertTopFrame(e, TestSecurityPropertyModificationEvent.class, "main");
|
||||
} else {
|
||||
System.out.println(events);
|
||||
throw new Exception("Unexpected event at index:" + i);
|
||||
|
@ -52,32 +52,38 @@ public class TestSecurityProviderServiceEvent {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testAlg(cipherFunc, "AES", "SunJCE",
|
||||
"SunEC", "Cipher", 1, Collections.emptyList());
|
||||
"SunEC", "Cipher", 1, Collections.emptyList(),
|
||||
javax.crypto.Cipher.class.getName(), "getInstance");
|
||||
testAlg(signatureFunc, "SHA256withRSA", "SunRsaSign",
|
||||
"SunEC", "Signature", 2, List.of("MessageDigest"));
|
||||
"SunEC", "Signature", 2, List.of("MessageDigest"),
|
||||
"sun.security.jca.GetInstance", "getService");
|
||||
testAlg(messageDigestFunc, "SHA-512", "SUN",
|
||||
"SunEC", "MessageDigest", 1, Collections.emptyList());
|
||||
"SunEC", "MessageDigest", 1, Collections.emptyList(),
|
||||
"sun.security.jca.GetInstance", "getService");
|
||||
testAlg(keystoreFunc, "PKCS12", "SUN",
|
||||
"SunEC", "KeyStore", 1, Collections.emptyList());
|
||||
"SunEC", "KeyStore", 1, Collections.emptyList(),
|
||||
"sun.security.jca.GetInstance", "getService");
|
||||
testAlg(certPathBuilderFunc, "PKIX", "SUN",
|
||||
"SunEC", "CertPathBuilder", 2, List.of("CertificateFactory"));
|
||||
"SunEC", "CertPathBuilder", 2, List.of("CertificateFactory"),
|
||||
"sun.security.jca.GetInstance", "getService");
|
||||
}
|
||||
|
||||
private static void testAlg(BiFunction<String, String, Provider> bif, String alg,
|
||||
String workingProv, String brokenProv, String algType,
|
||||
int expected, List<String> other) throws Exception {
|
||||
int expected, List<String> other,
|
||||
String clazz, String method) throws Exception {
|
||||
// bootstrap security Provider services
|
||||
Provider p = bif.apply(alg, workingProv);
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable(EventNames.SecurityProviderService);
|
||||
recording.enable(EventNames.SecurityProviderService).withStackTrace();
|
||||
recording.start();
|
||||
p = bif.apply(alg, workingProv);
|
||||
bif.apply(alg, brokenProv);
|
||||
recording.stop();
|
||||
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||
Asserts.assertEquals(events.size(), expected, "Incorrect number of events");
|
||||
assertEvent(events, algType, alg, p.getName(), other);
|
||||
assertEvent(events, algType, alg, p.getName(), other, clazz, method);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +143,8 @@ public class TestSecurityProviderServiceEvent {
|
||||
};
|
||||
|
||||
private static void assertEvent(List<RecordedEvent> events, String type,
|
||||
String alg, String workingProv, List<String> other) {
|
||||
String alg, String workingProv, List<String> other, String clazz,
|
||||
String method) {
|
||||
boolean secondaryEventOK = other.isEmpty() ? true : false;
|
||||
for (RecordedEvent e : events) {
|
||||
if (other.contains(e.getValue("type"))) {
|
||||
@ -148,10 +155,10 @@ public class TestSecurityProviderServiceEvent {
|
||||
Events.assertField(e, "provider").equal(workingProv);
|
||||
Events.assertField(e, "type").equal(type);
|
||||
Events.assertField(e, "algorithm").equal(alg);
|
||||
Events.assertTopFrame(e, clazz, method);
|
||||
}
|
||||
if (!secondaryEventOK) {
|
||||
throw new RuntimeException("Secondary events missing");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ import jdk.test.lib.security.TestTLSHandshake;
|
||||
public class TestTLSHandshakeEvent {
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable(EventNames.TLSHandshake);
|
||||
recording.enable(EventNames.TLSHandshake).withStackTrace();
|
||||
recording.start();
|
||||
TestTLSHandshake handshake = new TestTLSHandshake();
|
||||
handshake.run();
|
||||
@ -63,6 +63,10 @@ public class TestTLSHandshakeEvent {
|
||||
Events.assertField(e, "protocolVersion").equal(handshake.protocolVersion);
|
||||
Events.assertField(e, "certificateId").equal(TestTLSHandshake.CERT_ID);
|
||||
Events.assertField(e, "cipherSuite").equal(TestTLSHandshake.CIPHER_SUITE);
|
||||
var method = e.getStackTrace().getFrames().get(0).getMethod();
|
||||
if (method.getName().equals("recordEvent")) {
|
||||
throw new Exception("Didn't expected recordEvent as top frame");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public class TestGetStackTraceId {
|
||||
}
|
||||
|
||||
private static void assertMaxSkip() {
|
||||
Asserts.assertEquals(JVM.getStackTraceId(Integer.MAX_VALUE), 0L, "Insane skip level "
|
||||
Asserts.assertEquals(JVM.getStackTraceId(Integer.MAX_VALUE, -1), 0L, "Insane skip level "
|
||||
+ Integer.MAX_VALUE + " should not return a valid stack trace id");
|
||||
}
|
||||
|
||||
@ -64,6 +64,6 @@ public class TestGetStackTraceId {
|
||||
if (depth > 0) {
|
||||
return getStackIdOfDepth(depth - 1);
|
||||
}
|
||||
return JVM.getStackTraceId(0);
|
||||
return JVM.getStackTraceId(0, -1);
|
||||
}
|
||||
}
|
||||
|
@ -365,20 +365,42 @@ public class Events {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void assertTopFrame(RecordedEvent event, Class<?> expectedClass, String expectedMethodName) {
|
||||
assertTopFrame(event, expectedClass.getName(), expectedMethodName);
|
||||
}
|
||||
|
||||
public static void assertTopFrame(RecordedEvent event, String expectedClass, String expectedMethodName) {
|
||||
RecordedStackTrace stackTrace = event.getStackTrace();
|
||||
Asserts.assertNotNull(stackTrace, "Missing stack trace");
|
||||
RecordedFrame topFrame = stackTrace.getFrames().get(0);
|
||||
if (isFrame(topFrame, expectedClass, expectedMethodName)) {
|
||||
return;
|
||||
}
|
||||
String expected = expectedClass + "::" + expectedMethodName;
|
||||
Asserts.fail("Expected top frame " + expected + ". Found " + topFrame);
|
||||
}
|
||||
|
||||
public static void assertFrame(RecordedEvent event, Class<?> expectedClass, String expectedMethodName) {
|
||||
RecordedStackTrace stackTrace = event.getStackTrace();
|
||||
Asserts.assertNotNull(stackTrace, "Missing stack trace");
|
||||
for (RecordedFrame frame : stackTrace.getFrames()) {
|
||||
if (frame.isJavaFrame()) {
|
||||
RecordedMethod method = frame.getMethod();
|
||||
RecordedClass type = method.getType();
|
||||
if (expectedClass.getName().equals(type.getName())) {
|
||||
if (expectedMethodName.equals(method.getName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isFrame(frame, expectedClass.getName(), expectedMethodName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Asserts.fail("Expected " + expectedClass.getName() + "::"+ expectedMethodName + " in stack trace");
|
||||
}
|
||||
|
||||
private static boolean isFrame(RecordedFrame frame, String expectedClass, String expectedMethodName) {
|
||||
if (frame.isJavaFrame()) {
|
||||
RecordedMethod method = frame.getMethod();
|
||||
RecordedClass type = method.getType();
|
||||
if (expectedClass.equals(type.getName())) {
|
||||
if (expectedMethodName.equals(method.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user