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;
}
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
*/

View File

@ -86,6 +86,7 @@ class JfrJavaSupport : public AllStatic {
static Klass* klass(const jobject handle);
static const char* c_str(jstring string, Thread* thread, bool c_heap = false);
static const char* c_str(oop string, Thread* thread, bool c_heap = false);
static Symbol** symbol_array(jobjectArray string_array, JavaThread* thread, intptr_t* result_size, bool c_heap = false);
// exceptions
static void throw_illegal_state_exception(const char* message, TRAPS);

View File

@ -36,6 +36,8 @@
#include "jfr/recorder/repository/jfrEmergencyDump.hpp"
#include "jfr/recorder/service/jfrEventThrottler.hpp"
#include "jfr/recorder/service/jfrOptionSet.hpp"
#include "jfr/recorder/stacktrace/jfrStackFilter.hpp"
#include "jfr/recorder/stacktrace/jfrStackFilterRegistry.hpp"
#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
#include "jfr/recorder/stringpool/jfrStringPool.hpp"
#include "jfr/jni/jfrJavaSupport.hpp"
@ -239,8 +241,8 @@ JVM_ENTRY_NO_ENV(jlong, jfr_class_id(JNIEnv* env, jclass jvm, jclass jc))
return JfrTraceId::load(jc);
JVM_END
JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip))
return JfrStackTraceRepository::record(thread, skip);
JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id))
return JfrStackTraceRepository::record(thread, skip, stack_filter_id);
JVM_END
JVM_ENTRY_NO_ENV(void, jfr_log(JNIEnv* env, jclass jvm, jint tag_set, jint level, jstring message))
@ -397,3 +399,11 @@ JVM_END
JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes))
EventDataLoss::commit(bytes, min_jlong);
JVM_END
JVM_ENTRY_NO_ENV(jlong, jfr_register_stack_filter(JNIEnv* env, jclass jvm, jobjectArray classes, jobjectArray methods))
return JfrStackFilterRegistry::add(classes, methods, thread);
JVM_END
JVM_ENTRY_NO_ENV(void, jfr_unregister_stack_filter(JNIEnv* env, jclass jvm, jlong id))
JfrStackFilterRegistry::remove(id);
JVM_END

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);
jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip);
jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id);
jlong JNICALL jfr_elapsed_frequency(JNIEnv* env, jclass jvm);
@ -159,6 +159,10 @@ jlong JNICALL jfr_host_total_memory(JNIEnv* env, jclass jvm);
void JNICALL jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes);
jlong JNICALL jfr_register_stack_filter(JNIEnv* env, jobject classes, jobject methods);
jlong JNICALL jfr_unregister_stack_filter(JNIEnv* env, jlong start_filter_id);
#ifdef __cplusplus
}
#endif

View File

@ -46,7 +46,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
(char*)"getAllEventClasses", (char*)"()Ljava/util/List;", (void*)jfr_get_all_event_classes,
(char*)"getClassId", (char*)"(Ljava/lang/Class;)J", (void*)jfr_class_id,
(char*)"getPid", (char*)"()Ljava/lang/String;", (void*)jfr_get_pid,
(char*)"getStackTraceId", (char*)"(I)J", (void*)jfr_stacktrace_id,
(char*)"getStackTraceId", (char*)"(IJ)J", (void*)jfr_stacktrace_id,
(char*)"getThreadId", (char*)"(Ljava/lang/Thread;)J", (void*)jfr_id_for_thread,
(char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency,
(char*)"subscribeLogLevel", (char*)"(Ljdk/jfr/internal/LogTag;I)V", (void*)jfr_subscribe_log_level,
@ -97,7 +97,9 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
(char*)"isInstrumented", (char*)"(Ljava/lang/Class;)Z", (void*) jfr_is_class_instrumented,
(char*)"isContainerized", (char*)"()Z", (void*) jfr_is_containerized,
(char*)"hostTotalMemory", (char*)"()J", (void*) jfr_host_total_memory,
(char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss
(char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss,
(char*)"registerStackFilter", (char*)"([Ljava/lang/String;[Ljava/lang/String;)J", (void*)jfr_register_stack_filter,
(char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter
};
const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod);

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/support/jfrMethodLookup.hpp"
#include "jfr/support/jfrThreadLocal.hpp"
#include "jfrStackFilter.hpp"
#include "jfrStackFilterRegistry.hpp"
#include "memory/allocation.inline.hpp"
#include "oops/instanceKlass.inline.hpp"
#include "runtime/continuation.hpp"
@ -284,7 +286,7 @@ bool JfrStackTrace::record_async(JavaThread* jt, const frame& frame) {
return count > 0;
}
bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip, int64_t stack_filter_id) {
assert(jt != nullptr, "invariant");
assert(jt == Thread::current(), "invariant");
assert(jt->thread_state() != _thread_in_native, "invariant");
@ -302,6 +304,7 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
}
vfs.next_vframe();
}
const JfrStackFilter* stack_filter = JfrStackFilterRegistry::lookup(stack_filter_id);
_hash = 1;
while (!vfs.at_end()) {
if (count >= _max_frames) {
@ -309,6 +312,12 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
break;
}
const Method* method = vfs.method();
if (stack_filter != nullptr) {
if (stack_filter->match(method)) {
vfs.next_vframe();
continue;
}
}
const traceid mid = JfrTraceId::load(method);
u1 type = vfs.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
int bci = 0;
@ -335,13 +344,13 @@ bool JfrStackTrace::record(JavaThread* jt, const frame& frame, int skip) {
return count > 0;
}
bool JfrStackTrace::record(JavaThread* current_thread, int skip) {
bool JfrStackTrace::record(JavaThread* current_thread, int skip, int64_t stack_filter_id) {
assert(current_thread != nullptr, "invariant");
assert(current_thread == Thread::current(), "invariant");
if (!current_thread->has_last_Java_frame()) {
return false;
}
return record(current_thread, current_thread->last_frame(), skip);
return record(current_thread, current_thread->last_frame(), skip, stack_filter_id);
}
void JfrStackFrame::resolve_lineno() const {

View File

@ -93,8 +93,8 @@ class JfrStackTrace : public JfrCHeapObj {
void set_reached_root(bool reached_root) { _reached_root = reached_root; }
void resolve_linenos() const;
bool record(JavaThread* current_thread, int skip);
bool record(JavaThread* current_thread, const frame& frame, int skip);
bool record(JavaThread* current_thread, int skip, int64_t stack_frame_id);
bool record(JavaThread* current_thread, const frame& frame, int skip, int64_t stack_frame_id);
bool record_async(JavaThread* other_thread, const frame& frame);
bool have_lineno() const { return _lineno; }

View File

@ -146,7 +146,7 @@ size_t JfrStackTraceRepository::clear(JfrStackTraceRepository& repo) {
return processed;
}
traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */) {
traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */, int64_t stack_filter_id /* -1 */) {
assert(current_thread == Thread::current(), "invariant");
JfrThreadLocal* const tl = current_thread->jfr_thread_local();
assert(tl != nullptr, "invariant");
@ -163,13 +163,14 @@ traceid JfrStackTraceRepository::record(Thread* current_thread, int skip /* 0 */
}
assert(frames != nullptr, "invariant");
assert(tl->stackframes() == frames, "invariant");
return instance().record(JavaThread::cast(current_thread), skip, frames, tl->stackdepth());
return instance().record(JavaThread::cast(current_thread), skip, stack_filter_id, frames, tl->stackdepth());
}
traceid JfrStackTraceRepository::record(JavaThread* current_thread, int skip, JfrStackFrame *frames, u4 max_frames) {
traceid JfrStackTraceRepository::record(JavaThread* current_thread, int skip, int64_t stack_filter_id, JfrStackFrame *frames, u4 max_frames) {
JfrStackTrace stacktrace(frames, max_frames);
return stacktrace.record(current_thread, skip) ? add(instance(), stacktrace) : 0;
return stacktrace.record(current_thread, skip, stack_filter_id) ? add(instance(), stacktrace) : 0;
}
traceid JfrStackTraceRepository::add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace) {
traceid tid = repo.add_trace(stacktrace);
if (tid == 0) {
@ -191,7 +192,7 @@ void JfrStackTraceRepository::record_for_leak_profiler(JavaThread* current_threa
assert(tl != nullptr, "invariant");
assert(!tl->has_cached_stack_trace(), "invariant");
JfrStackTrace stacktrace(tl->stackframes(), tl->stackdepth());
stacktrace.record(current_thread, skip);
stacktrace.record(current_thread, skip, -1);
const traceid hash = stacktrace.hash();
if (hash != 0) {
tl->set_cached_stack_trace_id(add(leak_profiler_instance(), stacktrace), hash);

View File

@ -67,10 +67,10 @@ class JfrStackTraceRepository : public JfrCHeapObj {
traceid add_trace(const JfrStackTrace& stacktrace);
static traceid add(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace);
static traceid add(const JfrStackTrace& stacktrace);
traceid record(JavaThread* current_thread, int skip, JfrStackFrame* frames, u4 max_frames);
traceid record(JavaThread* current_thread, int skip, int64_t stack_filter_id, JfrStackFrame* frames, u4 max_frames);
public:
static traceid record(Thread* current_thread, int skip = 0);
static traceid record(Thread* current_thread, int skip = 0, int64_t stack_filter_id = -1);
};
#endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKTRACEREPOSITORY_HPP

View File

@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields;
@Description("Operating system process started")
@MirrorEvent(className = "jdk.internal.event.ProcessStartEvent")
@RemoveFields("duration")
@StackFilter({"java.lang.ProcessBuilder", "java.lang.Runtime::exec"})
public final class ProcessStartEvent extends AbstractJDKEvent {
@Label("Process Id")
public long pid;

View File

@ -35,6 +35,7 @@ import jdk.jfr.internal.RemoveFields;
@Description("Modification of Security property")
@MirrorEvent(className = "jdk.internal.event.SecurityPropertyModificationEvent")
@RemoveFields("duration")
@StackFilter({"java.security.Security::setProperty"})
public final class SecurityPropertyModificationEvent extends AbstractJDKEvent {
@Label("Key")
public String key;

View File

@ -38,6 +38,7 @@ import jdk.jfr.internal.RemoveFields;
@Description("Details of Provider.getInstance(String type, String algorithm) calls")
@MirrorEvent(className = "jdk.internal.event.SecurityProviderServiceEvent")
@RemoveFields("duration")
@StackFilter({"java.security.Provider::getService"})
public final class SecurityProviderServiceEvent extends AbstractJDKEvent {
@Label("Type of Service")
public String type;

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")
@MirrorEvent(className = "jdk.internal.event.TLSHandshakeEvent")
@RemoveFields("duration")
@StackFilter("sun.security.ssl.Finished::recordEvent")
public final class TLSHandshakeEvent extends AbstractJDKEvent {
@Label("Peer Host")
public String peerHost;

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.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,6 +35,9 @@ import jdk.jfr.internal.MirrorEvent;
@Label("Java Thread Sleep")
@Name("jdk.ThreadSleep")
@MirrorEvent(className = "jdk.internal.event.ThreadSleepEvent")
@StackFilter({"java.lang.Thread::afterSleep",
"java.lang.Thread::sleepNanos",
"java.lang.Thread::sleep"})
public final class ThreadSleepEvent extends AbstractJDKEvent {
@Label("Sleep Time")
@Timespan(Timespan.NANOSECONDS)

View File

@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jdk.internal.module.Checks;
import jdk.internal.module.Modules;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Enabled;
@ -44,6 +45,8 @@ import jdk.jfr.SettingDefinition;
import jdk.jfr.StackTrace;
import jdk.jfr.Threshold;
import jdk.jfr.events.ActiveSettingEvent;
import jdk.jfr.events.StackFilter;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.settings.CutoffSetting;
import jdk.jfr.internal.settings.EnabledSetting;
import jdk.jfr.internal.settings.PeriodSetting;
@ -65,6 +68,7 @@ public final class EventControl {
private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.class);
private static final long STACK_FILTER_ID = Type.getTypeId(StackFilter.class);
private final ArrayList<SettingControl> settingControls = new ArrayList<>();
private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
@ -89,6 +93,7 @@ public final class EventControl {
}
addControl(Enabled.NAME, defineEnabled(eventType));
addStackFilters(eventType);
List<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
remove(eventType, aes, Threshold.class);
remove(eventType, aes, Period.class);
@ -96,11 +101,84 @@ public final class EventControl {
remove(eventType, aes, StackTrace.class);
remove(eventType, aes, Cutoff.class);
remove(eventType, aes, Throttle.class);
remove(eventType, aes, StackFilter.class);
eventType.setAnnotations(aes);
this.type = eventType;
this.idName = String.valueOf(eventType.getId());
}
private void addStackFilters(PlatformEventType eventType) {
String[] filter = getStackFilter(eventType);
if (filter != null) {
int size = filter.length;
List<String> types = new ArrayList<>(size);
List<String> methods = new ArrayList<>(size);
for (String frame : filter) {
int index = frame.indexOf("::");
String clazz = null;
String method = null;
boolean valid = false;
if (index != -1) {
clazz = frame.substring(0, index);
method = frame.substring(index + 2);
if (clazz.isEmpty()) {
clazz = null;
valid = isValidMethod(method);
} else {
valid = isValidType(clazz) && isValidMethod(method);
}
} else {
clazz = frame;
valid = isValidType(frame);
}
if (valid) {
if (clazz == null) {
types.add(null);
} else {
types.add(clazz.replace(".", "/"));
}
// If unqualified class name equals method name, it's a constructor
String className = clazz.substring(clazz.lastIndexOf(".") + 1);
if (className.equals(method)) {
method = "<init>";
}
methods.add(method);
} else {
Logger.log(LogTag.JFR, LogLevel.WARN, "@StackFrameFilter element ignored, not a valid Java identifier.");
}
}
if (!types.isEmpty()) {
String[] typeArray = types.toArray(new String[0]);
String[] methodArray = methods.toArray(new String[0]);
long id = MetadataRepository.getInstance().registerStackFilter(typeArray, methodArray);
eventType.setStackFilterId(id);
}
}
}
private String[] getStackFilter(PlatformEventType eventType) {
for (var a : eventType.getAnnotationElements()) {
if (a.getTypeId() == STACK_FILTER_ID) {
return (String[])a.getValue("value");
}
}
return null;
}
private boolean isValidType(String className) {
if (className.length() < 1 || className.length() > 65535) {
return false;
}
return Checks.isClassName(className);
}
private boolean isValidMethod(String method) {
if (method.length() < 1 || method.length() > 65535) {
return false;
}
return Checks.isJavaIdentifier(method);
}
private boolean hasControl(String name) {
for (NamedControl nc : namedControls) {
if (name.equals(nc.name)) {

View File

@ -149,10 +149,15 @@ public final class JVM {
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*
* @param skipCount number of frames to skip
* @param skipCount number of frames to skip, or 0 if no frames should be
* skipped
*
* @param ID ID of the filter that should be used, or -1 if no filter should
* be used
*
* @return a unique stack trace identifier
*/
public static native long getStackTraceId(int skipCount);
public static native long getStackTraceId(int skipCount, long stackFilerId);
/**
* Return identifier for thread
@ -628,4 +633,30 @@ public final class JVM {
* @param bytes number of bytes that were lost
*/
public static native void emitDataLoss(long bytes);
/**
* Registers stack filters that should be used with getStackTrace(int, long)
* <p>
* Method name at an array index is for class at the same array index.
* <p>
* This method should be called holding the MetadataRepository lock and before
* bytecode for the associated event class has been added.
*
* @param classes, name of classes, for example {"java/lang/String"}, not
* {@code null}
* @param methods, name of method, for example {"toString"}, not {@code null}
*
* @return an ID that can be used to unregister the start frames, or -1 if it could not be registered
*/
public static native long registerStackFilter(String[] classes, String[] methods);
/**
* Unregisters a set of stack filters.
* <p>
* This method should be called holding the MetadataRepository lock and after
* the associated event class has been unloaded.
*
* @param stackFilterId the stack filter ID to unregister
*/
public static native void unregisterStackFilter(long stackFilterId);
}

View File

@ -321,6 +321,9 @@ public final class MetadataRepository {
if (!knownIds.contains(pe.getId())) {
if (!pe.isJVM()) {
pe.setRegistered(false);
if (pe.hasStackFilters()) {
JVM.unregisterStackFilter(pe.getStackFilterId());
}
}
}
}
@ -355,4 +358,8 @@ public final class MetadataRepository {
public synchronized List<Type> getVisibleTypes() {
return TypeLibrary.getVisibleTypes();
}
public synchronized long registerStackFilter(String[] typeArray, String[] methodArray) {
return JVM.registerStackFilter(typeArray, methodArray);
}
}

View File

@ -46,6 +46,7 @@ public final class PlatformEventType extends Type {
private final List<SettingDescriptor> settings = new ArrayList<>(5);
private final boolean dynamicSettings;
private final int stackTraceOffset;
private long startFilterId = -1;
// default values
private boolean largeSize = false;
@ -339,4 +340,16 @@ public final class PlatformEventType extends Type {
public boolean isMethodSampling() {
return isMethodSampling;
}
public void setStackFilterId(long id) {
startFilterId = id;
}
public boolean hasStackFilters() {
return startFilterId >= 0;
}
public long getStackFilterId() {
return startFilterId;
}
}

View File

@ -60,6 +60,7 @@ import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.util.Utils;
import jdk.jfr.internal.util.ImplicitFields;
import jdk.internal.module.Modules;
public final class TypeLibrary {
private static boolean implicitFieldTypes;
@ -165,7 +166,6 @@ public final class TypeLibrary {
for (ValueDescriptor v : type.getFields()) {
values.add(invokeAnnotation(annotation, v.getName()));
}
return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null);
}
return null;
@ -178,6 +178,15 @@ public final class TypeLibrary {
} catch (NoSuchMethodException e1) {
throw (Error) new InternalError("Could not locate method " + methodName + " in annotation " + annotation.getClass().getName());
}
// Add export from JDK proxy module
if (annotation.getClass().getClassLoader() == null) {
if (annotation.getClass().getName().contains("Proxy")) {
Module proxyModule = annotation.getClass().getModule();
String proxyPackage = annotation.getClass().getPackageName();
Module jfrModule = TypeLibrary.class.getModule();
Modules.addExports(proxyModule, proxyPackage, jfrModule);
}
}
SecuritySupport.setAccessible(m);
try {
return m.invoke(annotation, new Object[0]);

View File

@ -188,7 +188,7 @@ public final class EventWriter {
public void putStackTrace() {
if (eventType.getStackTraceEnabled()) {
putLong(JVM.getStackTraceId(eventType.getStackTraceOffset()));
putLong(JVM.getStackTraceId(eventType.getStackTraceOffset(), eventType.getStackFilterId()));
} else {
putLong(0L);
}

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 {
try (Recording recording = new Recording()) {
recording.enable(EVENT_NAME);
recording.enable(EVENT_NAME).withStackTrace();
recording.start();
List<String> commandList = new ArrayList<>();
if (Platform.isWindows()) {
@ -74,6 +74,7 @@ public class TestProcessStart {
Events.assertField(event, "pid").equal(p.pid());
Events.assertField(event, "directory").equal(pb.directory().toString());
Events.assertField(event, "command").equal(command.toString());
Events.assertTopFrame(event, TestProcessStart.class, "main");
}
}
}

View File

@ -56,13 +56,7 @@ public class TestThreadSleepEvent {
recording.enable(EVENT_NAME).withoutThreshold().withStackTrace();
recording.start();
Thread.sleep(SLEEP_TIME_MS);
Thread virtualThread = Thread.ofVirtual().start(() -> {
try {
Thread.sleep(SLEEP_TIME_MS);
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
});
Thread virtualThread = Thread.ofVirtual().start(TestThreadSleepEvent::virtualSleep);
virtualThread.join();
recording.stop();
@ -74,14 +68,24 @@ public class TestThreadSleepEvent {
System.out.println(event.getStackTrace());
if (event.getThread().getJavaThreadId() == Thread.currentThread().getId()) {
threadCount--;
Events.assertTopFrame(event, TestThreadSleepEvent.class, "main");
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
}
if (event.getThread().getJavaThreadId() == virtualThread.getId()) {
threadCount--;
Events.assertTopFrame(event, TestThreadSleepEvent.class, "virtualSleep");
Events.assertDuration(event, "time", Duration.ofMillis(SLEEP_TIME_MS));
}
}
Asserts.assertEquals(threadCount, 0, "Could not find all expected events");
}
}
private static void virtualSleep() {
try {
Thread.sleep(SLEEP_TIME_MS);
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
}

View File

@ -58,7 +58,7 @@ public class TestSecurityPropertyModificationEvent {
}
try (Recording recording = new Recording()) {
recording.enable(EventNames.SecurityProperty);
recording.enable(EventNames.SecurityProperty).withStackTrace();
recording.start();
for (String key: keys) {
Security.setProperty(key, keyValue);
@ -78,6 +78,7 @@ public class TestSecurityPropertyModificationEvent {
if (keys.contains(e.getString("key"))) {
Events.assertField(e, "value").equal(keyValue);
i++;
Events.assertTopFrame(e, TestSecurityPropertyModificationEvent.class, "main");
} else {
System.out.println(events);
throw new Exception("Unexpected event at index:" + i);

View File

@ -52,32 +52,38 @@ public class TestSecurityProviderServiceEvent {
public static void main(String[] args) throws Exception {
testAlg(cipherFunc, "AES", "SunJCE",
"SunEC", "Cipher", 1, Collections.emptyList());
"SunEC", "Cipher", 1, Collections.emptyList(),
javax.crypto.Cipher.class.getName(), "getInstance");
testAlg(signatureFunc, "SHA256withRSA", "SunRsaSign",
"SunEC", "Signature", 2, List.of("MessageDigest"));
"SunEC", "Signature", 2, List.of("MessageDigest"),
"sun.security.jca.GetInstance", "getService");
testAlg(messageDigestFunc, "SHA-512", "SUN",
"SunEC", "MessageDigest", 1, Collections.emptyList());
"SunEC", "MessageDigest", 1, Collections.emptyList(),
"sun.security.jca.GetInstance", "getService");
testAlg(keystoreFunc, "PKCS12", "SUN",
"SunEC", "KeyStore", 1, Collections.emptyList());
"SunEC", "KeyStore", 1, Collections.emptyList(),
"sun.security.jca.GetInstance", "getService");
testAlg(certPathBuilderFunc, "PKIX", "SUN",
"SunEC", "CertPathBuilder", 2, List.of("CertificateFactory"));
"SunEC", "CertPathBuilder", 2, List.of("CertificateFactory"),
"sun.security.jca.GetInstance", "getService");
}
private static void testAlg(BiFunction<String, String, Provider> bif, String alg,
String workingProv, String brokenProv, String algType,
int expected, List<String> other) throws Exception {
int expected, List<String> other,
String clazz, String method) throws Exception {
// bootstrap security Provider services
Provider p = bif.apply(alg, workingProv);
try (Recording recording = new Recording()) {
recording.enable(EventNames.SecurityProviderService);
recording.enable(EventNames.SecurityProviderService).withStackTrace();
recording.start();
p = bif.apply(alg, workingProv);
bif.apply(alg, brokenProv);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
Asserts.assertEquals(events.size(), expected, "Incorrect number of events");
assertEvent(events, algType, alg, p.getName(), other);
assertEvent(events, algType, alg, p.getName(), other, clazz, method);
}
}
@ -137,7 +143,8 @@ public class TestSecurityProviderServiceEvent {
};
private static void assertEvent(List<RecordedEvent> events, String type,
String alg, String workingProv, List<String> other) {
String alg, String workingProv, List<String> other, String clazz,
String method) {
boolean secondaryEventOK = other.isEmpty() ? true : false;
for (RecordedEvent e : events) {
if (other.contains(e.getValue("type"))) {
@ -148,10 +155,10 @@ public class TestSecurityProviderServiceEvent {
Events.assertField(e, "provider").equal(workingProv);
Events.assertField(e, "type").equal(type);
Events.assertField(e, "algorithm").equal(alg);
Events.assertTopFrame(e, clazz, method);
}
if (!secondaryEventOK) {
throw new RuntimeException("Secondary events missing");
}
}
}

View File

@ -43,7 +43,7 @@ import jdk.test.lib.security.TestTLSHandshake;
public class TestTLSHandshakeEvent {
public static void main(String[] args) throws Exception {
try (Recording recording = new Recording()) {
recording.enable(EventNames.TLSHandshake);
recording.enable(EventNames.TLSHandshake).withStackTrace();
recording.start();
TestTLSHandshake handshake = new TestTLSHandshake();
handshake.run();
@ -63,6 +63,10 @@ public class TestTLSHandshakeEvent {
Events.assertField(e, "protocolVersion").equal(handshake.protocolVersion);
Events.assertField(e, "certificateId").equal(TestTLSHandshake.CERT_ID);
Events.assertField(e, "cipherSuite").equal(TestTLSHandshake.CIPHER_SUITE);
var method = e.getStackTrace().getFrames().get(0).getMethod();
if (method.getName().equals("recordEvent")) {
throw new Exception("Didn't expected recordEvent as top frame");
}
return;
}
}

View File

@ -52,7 +52,7 @@ public class TestGetStackTraceId {
}
private static void assertMaxSkip() {
Asserts.assertEquals(JVM.getStackTraceId(Integer.MAX_VALUE), 0L, "Insane skip level "
Asserts.assertEquals(JVM.getStackTraceId(Integer.MAX_VALUE, -1), 0L, "Insane skip level "
+ Integer.MAX_VALUE + " should not return a valid stack trace id");
}
@ -64,6 +64,6 @@ public class TestGetStackTraceId {
if (depth > 0) {
return getStackIdOfDepth(depth - 1);
}
return JVM.getStackTraceId(0);
return JVM.getStackTraceId(0, -1);
}
}

View File

@ -365,20 +365,42 @@ public class Events {
return false;
}
public static void assertTopFrame(RecordedEvent event, Class<?> expectedClass, String expectedMethodName) {
assertTopFrame(event, expectedClass.getName(), expectedMethodName);
}
public static void assertTopFrame(RecordedEvent event, String expectedClass, String expectedMethodName) {
RecordedStackTrace stackTrace = event.getStackTrace();
Asserts.assertNotNull(stackTrace, "Missing stack trace");
RecordedFrame topFrame = stackTrace.getFrames().get(0);
if (isFrame(topFrame, expectedClass, expectedMethodName)) {
return;
}
String expected = expectedClass + "::" + expectedMethodName;
Asserts.fail("Expected top frame " + expected + ". Found " + topFrame);
}
public static void assertFrame(RecordedEvent event, Class<?> expectedClass, String expectedMethodName) {
RecordedStackTrace stackTrace = event.getStackTrace();
Asserts.assertNotNull(stackTrace, "Missing stack trace");
for (RecordedFrame frame : stackTrace.getFrames()) {
if (frame.isJavaFrame()) {
RecordedMethod method = frame.getMethod();
RecordedClass type = method.getType();
if (expectedClass.getName().equals(type.getName())) {
if (expectedMethodName.equals(method.getName())) {
return;
}
}
if (isFrame(frame, expectedClass.getName(), expectedMethodName)) {
return;
}
}
Asserts.fail("Expected " + expectedClass.getName() + "::"+ expectedMethodName + " in stack trace");
}
private static boolean isFrame(RecordedFrame frame, String expectedClass, String expectedMethodName) {
if (frame.isJavaFrame()) {
RecordedMethod method = frame.getMethod();
RecordedClass type = method.getType();
if (expectedClass.equals(type.getName())) {
if (expectedMethodName.equals(method.getName())) {
return true;
}
}
}
return false;
}
}