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;
|
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
|
* Exceptions and errors
|
||||||
*/
|
*/
|
||||||
|
@ -86,6 +86,7 @@ class JfrJavaSupport : public AllStatic {
|
|||||||
static Klass* klass(const jobject handle);
|
static Klass* klass(const jobject handle);
|
||||||
static const char* c_str(jstring string, Thread* thread, bool c_heap = false);
|
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 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
|
// exceptions
|
||||||
static void throw_illegal_state_exception(const char* message, TRAPS);
|
static void throw_illegal_state_exception(const char* message, TRAPS);
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
#include "jfr/recorder/repository/jfrEmergencyDump.hpp"
|
#include "jfr/recorder/repository/jfrEmergencyDump.hpp"
|
||||||
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
||||||
#include "jfr/recorder/service/jfrOptionSet.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/stacktrace/jfrStackTraceRepository.hpp"
|
||||||
#include "jfr/recorder/stringpool/jfrStringPool.hpp"
|
#include "jfr/recorder/stringpool/jfrStringPool.hpp"
|
||||||
#include "jfr/jni/jfrJavaSupport.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);
|
return JfrTraceId::load(jc);
|
||||||
JVM_END
|
JVM_END
|
||||||
|
|
||||||
JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip))
|
JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id))
|
||||||
return JfrStackTraceRepository::record(thread, skip);
|
return JfrStackTraceRepository::record(thread, skip, stack_filter_id);
|
||||||
JVM_END
|
JVM_END
|
||||||
|
|
||||||
JVM_ENTRY_NO_ENV(void, jfr_log(JNIEnv* env, jclass jvm, jint tag_set, jint level, jstring message))
|
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))
|
JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes))
|
||||||
EventDataLoss::commit(bytes, min_jlong);
|
EventDataLoss::commit(bytes, min_jlong);
|
||||||
JVM_END
|
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);
|
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);
|
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);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -46,7 +46,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
|
|||||||
(char*)"getAllEventClasses", (char*)"()Ljava/util/List;", (void*)jfr_get_all_event_classes,
|
(char*)"getAllEventClasses", (char*)"()Ljava/util/List;", (void*)jfr_get_all_event_classes,
|
||||||
(char*)"getClassId", (char*)"(Ljava/lang/Class;)J", (void*)jfr_class_id,
|
(char*)"getClassId", (char*)"(Ljava/lang/Class;)J", (void*)jfr_class_id,
|
||||||
(char*)"getPid", (char*)"()Ljava/lang/String;", (void*)jfr_get_pid,
|
(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*)"getThreadId", (char*)"(Ljava/lang/Thread;)J", (void*)jfr_id_for_thread,
|
||||||
(char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency,
|
(char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency,
|
||||||
(char*)"subscribeLogLevel", (char*)"(Ljdk/jfr/internal/LogTag;I)V", (void*)jfr_subscribe_log_level,
|
(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*)"isInstrumented", (char*)"(Ljava/lang/Class;)Z", (void*) jfr_is_class_instrumented,
|
||||||
(char*)"isContainerized", (char*)"()Z", (void*) jfr_is_containerized,
|
(char*)"isContainerized", (char*)"()Z", (void*) jfr_is_containerized,
|
||||||
(char*)"hostTotalMemory", (char*)"()J", (void*) jfr_host_total_memory,
|
(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);
|
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/recorder/storage/jfrBuffer.hpp"
|
||||||
#include "jfr/support/jfrMethodLookup.hpp"
|
#include "jfr/support/jfrMethodLookup.hpp"
|
||||||
#include "jfr/support/jfrThreadLocal.hpp"
|
#include "jfr/support/jfrThreadLocal.hpp"
|
||||||
|
#include "jfrStackFilter.hpp"
|
||||||
|
#include "jfrStackFilterRegistry.hpp"
|
||||||
#include "memory/allocation.inline.hpp"
|
#include "memory/allocation.inline.hpp"
|
||||||
#include "oops/instanceKlass.inline.hpp"
|
#include "oops/instanceKlass.inline.hpp"
|
||||||
#include "runtime/continuation.hpp"
|
#include "runtime/continuation.hpp"
|
||||||
@ -284,7 +286,7 @@ bool JfrStackTrace::record_async(JavaThread* jt, const frame& frame) {
|
|||||||
return count > 0;
|
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 != nullptr, "invariant");
|
||||||
assert(jt == Thread::current(), "invariant");
|
assert(jt == Thread::current(), "invariant");
|
||||||
assert(jt->thread_state() != _thread_in_native, "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();
|
vfs.next_vframe();
|
||||||
}
|
}
|
||||||
|
const JfrStackFilter* stack_filter = JfrStackFilterRegistry::lookup(stack_filter_id);
|
||||||
_hash = 1;
|
_hash = 1;
|
||||||
while (!vfs.at_end()) {
|
while (!vfs.at_end()) {
|
||||||
if (count >= _max_frames) {
|
if (count >= _max_frames) {
|
||||||
@ -309,6 +312,12 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const Method* method = vfs.method();
|
const Method* method = vfs.method();
|
||||||
|
if (stack_filter != nullptr) {
|
||||||
|
if (stack_filter->match(method)) {
|
||||||
|
vfs.next_vframe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
const traceid mid = JfrTraceId::load(method);
|
const traceid mid = JfrTraceId::load(method);
|
||||||
u1 type = vfs.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
|
u1 type = vfs.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
|
||||||
int bci = 0;
|
int bci = 0;
|
||||||
@ -335,13 +344,13 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
|
|||||||
return count > 0;
|
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 != nullptr, "invariant");
|
||||||
assert(current_thread == Thread::current(), "invariant");
|
assert(current_thread == Thread::current(), "invariant");
|
||||||
if (!current_thread->has_last_Java_frame()) {
|
if (!current_thread->has_last_Java_frame()) {
|
||||||
return false;
|
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 {
|
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 set_reached_root(bool reached_root) { _reached_root = reached_root; }
|
||||||
void resolve_linenos() const;
|
void resolve_linenos() const;
|
||||||
|
|
||||||
bool record(JavaThread* current_thread, int skip);
|
bool record(JavaThread* current_thread, int skip, int64_t stack_frame_id);
|
||||||
bool record(JavaThread* current_thread, const frame& frame, int skip);
|
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 record_async(JavaThread* other_thread, const frame& frame);
|
||||||
|
|
||||||
bool have_lineno() const { return _lineno; }
|
bool have_lineno() const { return _lineno; }
|
||||||
|
@ -146,7 +146,7 @@ size_t JfrStackTraceRepository::clear(JfrStackTraceRepository& repo) {
|
|||||||
return processed;
|
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");
|
assert(current_thread == Thread::current(), "invariant");
|
||||||
JfrThreadLocal* const tl = current_thread->jfr_thread_local();
|
JfrThreadLocal* const tl = current_thread->jfr_thread_local();
|
||||||
assert(tl != nullptr, "invariant");
|
assert(tl != nullptr, "invariant");
|
||||||
@ -163,13 +163,14 @@ traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */
|
|||||||
}
|
}
|
||||||
assert(frames != nullptr, "invariant");
|
assert(frames != nullptr, "invariant");
|
||||||
assert(tl->stackframes() == frames, "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);
|
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 JfrStackTraceRepository::add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace) {
|
||||||
traceid tid = repo.add_trace(stacktrace);
|
traceid tid = repo.add_trace(stacktrace);
|
||||||
if (tid == 0) {
|
if (tid == 0) {
|
||||||
@ -191,7 +192,7 @@ void JfrStackTraceRepository::record_for_leak_profiler(JavaThread* current_threa
|
|||||||
assert(tl != nullptr, "invariant");
|
assert(tl != nullptr, "invariant");
|
||||||
assert(!tl->has_cached_stack_trace(), "invariant");
|
assert(!tl->has_cached_stack_trace(), "invariant");
|
||||||
JfrStackTrace stacktrace(tl->stackframes(), tl->stackdepth());
|
JfrStackTrace stacktrace(tl->stackframes(), tl->stackdepth());
|
||||||
stacktrace.record(current_thread, skip);
|
stacktrace.record(current_thread, skip, -1);
|
||||||
const traceid hash = stacktrace.hash();
|
const traceid hash = stacktrace.hash();
|
||||||
if (hash != 0) {
|
if (hash != 0) {
|
||||||
tl->set_cached_stack_trace_id(add(leak_profiler_instance(), stacktrace), hash);
|
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);
|
traceid add_trace(const JfrStackTrace& stacktrace);
|
||||||
static traceid add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace);
|
static traceid add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace);
|
||||||
static traceid add(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:
|
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
|
#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKTRACEREPOSITORY_HPP
|
||||||
|
@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields;
|
|||||||
@Description("Operating system process started")
|
@Description("Operating system process started")
|
||||||
@MirrorEvent(className = "jdk.internal.event.ProcessStartEvent")
|
@MirrorEvent(className = "jdk.internal.event.ProcessStartEvent")
|
||||||
@RemoveFields("duration")
|
@RemoveFields("duration")
|
||||||
|
@StackFilter({"java.lang.ProcessBuilder", "java.lang.Runtime::exec"})
|
||||||
public final class ProcessStartEvent extends AbstractJDKEvent {
|
public final class ProcessStartEvent extends AbstractJDKEvent {
|
||||||
@Label("Process Id")
|
@Label("Process Id")
|
||||||
public long pid;
|
public long pid;
|
||||||
|
@ -35,6 +35,7 @@ import jdk.jfr.internal.RemoveFields;
|
|||||||
@Description("Modification of Security property")
|
@Description("Modification of Security property")
|
||||||
@MirrorEvent(className = "jdk.internal.event.SecurityPropertyModificationEvent")
|
@MirrorEvent(className = "jdk.internal.event.SecurityPropertyModificationEvent")
|
||||||
@RemoveFields("duration")
|
@RemoveFields("duration")
|
||||||
|
@StackFilter({"java.security.Security::setProperty"})
|
||||||
public final class SecurityPropertyModificationEvent extends AbstractJDKEvent {
|
public final class SecurityPropertyModificationEvent extends AbstractJDKEvent {
|
||||||
@Label("Key")
|
@Label("Key")
|
||||||
public String key;
|
public String key;
|
||||||
|
@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields;
|
|||||||
@Description("Details of Provider.getInstance(String type, String algorithm) calls")
|
@Description("Details of Provider.getInstance(String type, String algorithm) calls")
|
||||||
@MirrorEvent(className = "jdk.internal.event.SecurityProviderServiceEvent")
|
@MirrorEvent(className = "jdk.internal.event.SecurityProviderServiceEvent")
|
||||||
@RemoveFields("duration")
|
@RemoveFields("duration")
|
||||||
|
@StackFilter({"java.security.Provider::getService"})
|
||||||
public final class SecurityProviderServiceEvent extends AbstractJDKEvent {
|
public final class SecurityProviderServiceEvent extends AbstractJDKEvent {
|
||||||
@Label("Type of Service")
|
@Label("Type of Service")
|
||||||
public String type;
|
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")
|
@Description("Parameters used in TLS Handshake")
|
||||||
@MirrorEvent(className = "jdk.internal.event.TLSHandshakeEvent")
|
@MirrorEvent(className = "jdk.internal.event.TLSHandshakeEvent")
|
||||||
@RemoveFields("duration")
|
@RemoveFields("duration")
|
||||||
|
@StackFilter("sun.security.ssl.Finished::recordEvent")
|
||||||
public final class TLSHandshakeEvent extends AbstractJDKEvent {
|
public final class TLSHandshakeEvent extends AbstractJDKEvent {
|
||||||
@Label("Peer Host")
|
@Label("Peer Host")
|
||||||
public String peerHost;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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")
|
@Label("Java Thread Sleep")
|
||||||
@Name("jdk.ThreadSleep")
|
@Name("jdk.ThreadSleep")
|
||||||
@MirrorEvent(className = "jdk.internal.event.ThreadSleepEvent")
|
@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 {
|
public final class ThreadSleepEvent extends AbstractJDKEvent {
|
||||||
@Label("Sleep Time")
|
@Label("Sleep Time")
|
||||||
@Timespan(Timespan.NANOSECONDS)
|
@Timespan(Timespan.NANOSECONDS)
|
||||||
|
@ -34,6 +34,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.internal.module.Checks;
|
||||||
import jdk.internal.module.Modules;
|
import jdk.internal.module.Modules;
|
||||||
import jdk.jfr.AnnotationElement;
|
import jdk.jfr.AnnotationElement;
|
||||||
import jdk.jfr.Enabled;
|
import jdk.jfr.Enabled;
|
||||||
@ -44,6 +45,8 @@ import jdk.jfr.SettingDefinition;
|
|||||||
import jdk.jfr.StackTrace;
|
import jdk.jfr.StackTrace;
|
||||||
import jdk.jfr.Threshold;
|
import jdk.jfr.Threshold;
|
||||||
import jdk.jfr.events.ActiveSettingEvent;
|
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.CutoffSetting;
|
||||||
import jdk.jfr.internal.settings.EnabledSetting;
|
import jdk.jfr.internal.settings.EnabledSetting;
|
||||||
import jdk.jfr.internal.settings.PeriodSetting;
|
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_PERIOD = TypeLibrary.createType(PeriodSetting.class);
|
||||||
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
|
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
|
||||||
private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.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<SettingControl> settingControls = new ArrayList<>();
|
||||||
private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
|
private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
|
||||||
@ -89,6 +93,7 @@ public final class EventControl {
|
|||||||
}
|
}
|
||||||
addControl(Enabled.NAME, defineEnabled(eventType));
|
addControl(Enabled.NAME, defineEnabled(eventType));
|
||||||
|
|
||||||
|
addStackFilters(eventType);
|
||||||
List<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
|
List<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
|
||||||
remove(eventType, aes, Threshold.class);
|
remove(eventType, aes, Threshold.class);
|
||||||
remove(eventType, aes, Period.class);
|
remove(eventType, aes, Period.class);
|
||||||
@ -96,11 +101,84 @@ public final class EventControl {
|
|||||||
remove(eventType, aes, StackTrace.class);
|
remove(eventType, aes, StackTrace.class);
|
||||||
remove(eventType, aes, Cutoff.class);
|
remove(eventType, aes, Cutoff.class);
|
||||||
remove(eventType, aes, Throttle.class);
|
remove(eventType, aes, Throttle.class);
|
||||||
|
remove(eventType, aes, StackFilter.class);
|
||||||
eventType.setAnnotations(aes);
|
eventType.setAnnotations(aes);
|
||||||
this.type = eventType;
|
this.type = eventType;
|
||||||
this.idName = String.valueOf(eventType.getId());
|
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) {
|
private boolean hasControl(String name) {
|
||||||
for (NamedControl nc : namedControls) {
|
for (NamedControl nc : namedControls) {
|
||||||
if (name.equals(nc.name)) {
|
if (name.equals(nc.name)) {
|
||||||
|
@ -149,10 +149,15 @@ public final class JVM {
|
|||||||
*
|
*
|
||||||
* Requires that JFR has been started with {@link #createNativeJFR()}
|
* 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
|
* @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
|
* Return identifier for thread
|
||||||
@ -628,4 +633,30 @@ public final class JVM {
|
|||||||
* @param bytes number of bytes that were lost
|
* @param bytes number of bytes that were lost
|
||||||
*/
|
*/
|
||||||
public static native void emitDataLoss(long bytes);
|
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 (!knownIds.contains(pe.getId())) {
|
||||||
if (!pe.isJVM()) {
|
if (!pe.isJVM()) {
|
||||||
pe.setRegistered(false);
|
pe.setRegistered(false);
|
||||||
|
if (pe.hasStackFilters()) {
|
||||||
|
JVM.unregisterStackFilter(pe.getStackFilterId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,4 +358,8 @@ public final class MetadataRepository {
|
|||||||
public synchronized List<Type> getVisibleTypes() {
|
public synchronized List<Type> getVisibleTypes() {
|
||||||
return TypeLibrary.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 List<SettingDescriptor> settings = new ArrayList<>(5);
|
||||||
private final boolean dynamicSettings;
|
private final boolean dynamicSettings;
|
||||||
private final int stackTraceOffset;
|
private final int stackTraceOffset;
|
||||||
|
private long startFilterId = -1;
|
||||||
|
|
||||||
// default values
|
// default values
|
||||||
private boolean largeSize = false;
|
private boolean largeSize = false;
|
||||||
@ -339,4 +340,16 @@ public final class PlatformEventType extends Type {
|
|||||||
public boolean isMethodSampling() {
|
public boolean isMethodSampling() {
|
||||||
return 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.ValueDescriptor;
|
||||||
import jdk.jfr.internal.util.Utils;
|
import jdk.jfr.internal.util.Utils;
|
||||||
import jdk.jfr.internal.util.ImplicitFields;
|
import jdk.jfr.internal.util.ImplicitFields;
|
||||||
|
import jdk.internal.module.Modules;
|
||||||
|
|
||||||
public final class TypeLibrary {
|
public final class TypeLibrary {
|
||||||
private static boolean implicitFieldTypes;
|
private static boolean implicitFieldTypes;
|
||||||
@ -165,7 +166,6 @@ public final class TypeLibrary {
|
|||||||
for (ValueDescriptor v : type.getFields()) {
|
for (ValueDescriptor v : type.getFields()) {
|
||||||
values.add(invokeAnnotation(annotation, v.getName()));
|
values.add(invokeAnnotation(annotation, v.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null);
|
return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -178,6 +178,15 @@ public final class TypeLibrary {
|
|||||||
} catch (NoSuchMethodException e1) {
|
} catch (NoSuchMethodException e1) {
|
||||||
throw (Error) new InternalError("Could not locate method " + methodName + " in annotation " + annotation.getClass().getName());
|
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);
|
SecuritySupport.setAccessible(m);
|
||||||
try {
|
try {
|
||||||
return m.invoke(annotation, new Object[0]);
|
return m.invoke(annotation, new Object[0]);
|
||||||
|
@ -188,7 +188,7 @@ public final class EventWriter {
|
|||||||
|
|
||||||
public void putStackTrace() {
|
public void putStackTrace() {
|
||||||
if (eventType.getStackTraceEnabled()) {
|
if (eventType.getStackTraceEnabled()) {
|
||||||
putLong(JVM.getStackTraceId(eventType.getStackTraceOffset()));
|
putLong(JVM.getStackTraceId(eventType.getStackTraceOffset(), eventType.getStackFilterId()));
|
||||||
} else {
|
} else {
|
||||||
putLong(0L);
|
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 {
|
public static void main(String[] args) throws Throwable {
|
||||||
|
|
||||||
try (Recording recording = new Recording()) {
|
try (Recording recording = new Recording()) {
|
||||||
recording.enable(EVENT_NAME);
|
recording.enable(EVENT_NAME).withStackTrace();
|
||||||
recording.start();
|
recording.start();
|
||||||
List<String> commandList = new ArrayList<>();
|
List<String> commandList = new ArrayList<>();
|
||||||
if (Platform.isWindows()) {
|
if (Platform.isWindows()) {
|
||||||
@ -74,6 +74,7 @@ public class TestProcessStart {
|
|||||||
Events.assertField(event, "pid").equal(p.pid());
|
Events.assertField(event, "pid").equal(p.pid());
|
||||||
Events.assertField(event, "directory").equal(pb.directory().toString());
|
Events.assertField(event, "directory").equal(pb.directory().toString());
|
||||||
Events.assertField(event, "command").equal(command.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.enable(EVENT_NAME).withoutThreshold().withStackTrace();
|
||||||
recording.start();
|
recording.start();
|
||||||
Thread.sleep(SLEEP_TIME_MS);
|
Thread.sleep(SLEEP_TIME_MS);
|
||||||
Thread virtualThread = Thread.ofVirtual().start(() -> {
|
Thread virtualThread = Thread.ofVirtual().start(TestThreadSleepEvent::virtualSleep);
|
||||||
try {
|
|
||||||
Thread.sleep(SLEEP_TIME_MS);
|
|
||||||
} catch (InterruptedException ie) {
|
|
||||||
throw new RuntimeException(ie);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
virtualThread.join();
|
virtualThread.join();
|
||||||
recording.stop();
|
recording.stop();
|
||||||
|
|
||||||
@ -74,14 +68,24 @@ public class TestThreadSleepEvent {
|
|||||||
System.out.println(event.getStackTrace());
|
System.out.println(event.getStackTrace());
|
||||||
if (event.getThread().getJavaThreadId() == Thread.currentThread().getId()) {
|
if (event.getThread().getJavaThreadId() == Thread.currentThread().getId()) {
|
||||||
threadCount--;
|
threadCount--;
|
||||||
|
Events.assertTopFrame(event, TestThreadSleepEvent.class, "main");
|
||||||
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
|
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
|
||||||
}
|
}
|
||||||
if (event.getThread().getJavaThreadId() == virtualThread.getId()) {
|
if (event.getThread().getJavaThreadId() == virtualThread.getId()) {
|
||||||
threadCount--;
|
threadCount--;
|
||||||
|
Events.assertTopFrame(event, TestThreadSleepEvent.class, "virtualSleep");
|
||||||
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
|
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Asserts.assertEquals(threadCount, 0, "Could not find all expected events");
|
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()) {
|
try (Recording recording = new Recording()) {
|
||||||
recording.enable(EventNames.SecurityProperty);
|
recording.enable(EventNames.SecurityProperty).withStackTrace();
|
||||||
recording.start();
|
recording.start();
|
||||||
for (String key: keys) {
|
for (String key: keys) {
|
||||||
Security.setProperty(key, keyValue);
|
Security.setProperty(key, keyValue);
|
||||||
@ -78,6 +78,7 @@ public class TestSecurityPropertyModificationEvent {
|
|||||||
if (keys.contains(e.getString("key"))) {
|
if (keys.contains(e.getString("key"))) {
|
||||||
Events.assertField(e, "value").equal(keyValue);
|
Events.assertField(e, "value").equal(keyValue);
|
||||||
i++;
|
i++;
|
||||||
|
Events.assertTopFrame(e, TestSecurityPropertyModificationEvent.class, "main");
|
||||||
} else {
|
} else {
|
||||||
System.out.println(events);
|
System.out.println(events);
|
||||||
throw new Exception("Unexpected event at index:" + i);
|
throw new Exception("Unexpected event at index:" + i);
|
||||||
|
@ -52,32 +52,38 @@ public class TestSecurityProviderServiceEvent {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
testAlg(cipherFunc, "AES", "SunJCE",
|
testAlg(cipherFunc, "AES", "SunJCE",
|
||||||
"SunEC", "Cipher", 1, Collections.emptyList());
|
"SunEC", "Cipher", 1, Collections.emptyList(),
|
||||||
|
javax.crypto.Cipher.class.getName(), "getInstance");
|
||||||
testAlg(signatureFunc, "SHA256withRSA", "SunRsaSign",
|
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",
|
testAlg(messageDigestFunc, "SHA-512", "SUN",
|
||||||
"SunEC", "MessageDigest", 1, Collections.emptyList());
|
"SunEC", "MessageDigest", 1, Collections.emptyList(),
|
||||||
|
"sun.security.jca.GetInstance", "getService");
|
||||||
testAlg(keystoreFunc, "PKCS12", "SUN",
|
testAlg(keystoreFunc, "PKCS12", "SUN",
|
||||||
"SunEC", "KeyStore", 1, Collections.emptyList());
|
"SunEC", "KeyStore", 1, Collections.emptyList(),
|
||||||
|
"sun.security.jca.GetInstance", "getService");
|
||||||
testAlg(certPathBuilderFunc, "PKIX", "SUN",
|
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,
|
private static void testAlg(BiFunction<String, String, Provider> bif, String alg,
|
||||||
String workingProv, String brokenProv, String algType,
|
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
|
// bootstrap security Provider services
|
||||||
Provider p = bif.apply(alg, workingProv);
|
Provider p = bif.apply(alg, workingProv);
|
||||||
|
|
||||||
try (Recording recording = new Recording()) {
|
try (Recording recording = new Recording()) {
|
||||||
recording.enable(EventNames.SecurityProviderService);
|
recording.enable(EventNames.SecurityProviderService).withStackTrace();
|
||||||
recording.start();
|
recording.start();
|
||||||
p = bif.apply(alg, workingProv);
|
p = bif.apply(alg, workingProv);
|
||||||
bif.apply(alg, brokenProv);
|
bif.apply(alg, brokenProv);
|
||||||
recording.stop();
|
recording.stop();
|
||||||
List<RecordedEvent> events = Events.fromRecording(recording);
|
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||||
Asserts.assertEquals(events.size(), expected, "Incorrect number of events");
|
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,
|
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;
|
boolean secondaryEventOK = other.isEmpty() ? true : false;
|
||||||
for (RecordedEvent e : events) {
|
for (RecordedEvent e : events) {
|
||||||
if (other.contains(e.getValue("type"))) {
|
if (other.contains(e.getValue("type"))) {
|
||||||
@ -148,10 +155,10 @@ public class TestSecurityProviderServiceEvent {
|
|||||||
Events.assertField(e, "provider").equal(workingProv);
|
Events.assertField(e, "provider").equal(workingProv);
|
||||||
Events.assertField(e, "type").equal(type);
|
Events.assertField(e, "type").equal(type);
|
||||||
Events.assertField(e, "algorithm").equal(alg);
|
Events.assertField(e, "algorithm").equal(alg);
|
||||||
|
Events.assertTopFrame(e, clazz, method);
|
||||||
}
|
}
|
||||||
if (!secondaryEventOK) {
|
if (!secondaryEventOK) {
|
||||||
throw new RuntimeException("Secondary events missing");
|
throw new RuntimeException("Secondary events missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ import jdk.test.lib.security.TestTLSHandshake;
|
|||||||
public class TestTLSHandshakeEvent {
|
public class TestTLSHandshakeEvent {
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
try (Recording recording = new Recording()) {
|
try (Recording recording = new Recording()) {
|
||||||
recording.enable(EventNames.TLSHandshake);
|
recording.enable(EventNames.TLSHandshake).withStackTrace();
|
||||||
recording.start();
|
recording.start();
|
||||||
TestTLSHandshake handshake = new TestTLSHandshake();
|
TestTLSHandshake handshake = new TestTLSHandshake();
|
||||||
handshake.run();
|
handshake.run();
|
||||||
@ -63,6 +63,10 @@ public class TestTLSHandshakeEvent {
|
|||||||
Events.assertField(e, "protocolVersion").equal(handshake.protocolVersion);
|
Events.assertField(e, "protocolVersion").equal(handshake.protocolVersion);
|
||||||
Events.assertField(e, "certificateId").equal(TestTLSHandshake.CERT_ID);
|
Events.assertField(e, "certificateId").equal(TestTLSHandshake.CERT_ID);
|
||||||
Events.assertField(e, "cipherSuite").equal(TestTLSHandshake.CIPHER_SUITE);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ public class TestGetStackTraceId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void assertMaxSkip() {
|
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");
|
+ Integer.MAX_VALUE + " should not return a valid stack trace id");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +64,6 @@ public class TestGetStackTraceId {
|
|||||||
if (depth > 0) {
|
if (depth > 0) {
|
||||||
return getStackIdOfDepth(depth - 1);
|
return getStackIdOfDepth(depth - 1);
|
||||||
}
|
}
|
||||||
return JVM.getStackTraceId(0);
|
return JVM.getStackTraceId(0, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,20 +365,42 @@ public class Events {
|
|||||||
return false;
|
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) {
|
public static void assertFrame(RecordedEvent event, Class<?> expectedClass, String expectedMethodName) {
|
||||||
RecordedStackTrace stackTrace = event.getStackTrace();
|
RecordedStackTrace stackTrace = event.getStackTrace();
|
||||||
Asserts.assertNotNull(stackTrace, "Missing stack trace");
|
Asserts.assertNotNull(stackTrace, "Missing stack trace");
|
||||||
for (RecordedFrame frame : stackTrace.getFrames()) {
|
for (RecordedFrame frame : stackTrace.getFrames()) {
|
||||||
if (frame.isJavaFrame()) {
|
if (isFrame(frame, expectedClass.getName(), expectedMethodName)) {
|
||||||
RecordedMethod method = frame.getMethod();
|
return;
|
||||||
RecordedClass type = method.getType();
|
|
||||||
if (expectedClass.getName().equals(type.getName())) {
|
|
||||||
if (expectedMethodName.equals(method.getName())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Asserts.fail("Expected " + expectedClass.getName() + "::"+ expectedMethodName + " in stack trace");
|
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