8314745: JFR: @StackFilter

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2023-11-22 20:48:42 +00:00
parent aac4318431
commit 6016536ab9
34 changed files with 982 additions and 52 deletions

View File

@ -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
*/ */

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

View 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);
}

View 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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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; }

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View 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();
}

View File

@ -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;

View File

@ -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)

View File

@ -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)) {

View File

@ -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);
} }

View File

@ -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);
}
} }

View File

@ -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;
}
} }

View File

@ -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]);

View File

@ -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);
} }

View 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 + "'");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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");
} }
} }
} }

View File

@ -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);
}
}
} }

View File

@ -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);

View File

@ -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");
} }
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);
} }
} }

View File

@ -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;
}
} }