8358666: [REDO] Implement JEP 509: JFR CPU-Time Profiling
Reviewed-by: mgronlun
This commit is contained in:
parent
48b97ac0e0
commit
ace70a6d6a
@ -1505,6 +1505,14 @@ bool PosixSignals::is_sig_ignored(int sig) {
|
||||
}
|
||||
}
|
||||
|
||||
void* PosixSignals::get_signal_handler_for_signal(int sig) {
|
||||
struct sigaction oact;
|
||||
if (sigaction(sig, (struct sigaction*)nullptr, &oact) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
return get_signal_handler(&oact);
|
||||
}
|
||||
|
||||
static void signal_sets_init() {
|
||||
sigemptyset(&preinstalled_sigs);
|
||||
|
||||
|
@ -52,6 +52,8 @@ public:
|
||||
|
||||
static bool is_sig_ignored(int sig);
|
||||
|
||||
static void* get_signal_handler_for_signal(int sig);
|
||||
|
||||
static void hotspot_sigmask(Thread* thread);
|
||||
|
||||
static void print_signal_handler(outputStream* st, int sig, char* buf, size_t buflen);
|
||||
|
@ -32,7 +32,8 @@
|
||||
|
||||
inline bool Jfr::has_sample_request(JavaThread* jt) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
return jt->jfr_thread_local()->has_sample_request();
|
||||
JfrThreadLocal* tl = jt->jfr_thread_local();
|
||||
return tl->has_sample_request() || tl->has_cpu_time_jfr_requests();
|
||||
}
|
||||
|
||||
inline void Jfr::check_and_process_sample_request(JavaThread* jt) {
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include "jfr/jfr.hpp"
|
||||
#include "jfr/jfrEvents.hpp"
|
||||
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
|
||||
#include "jfr/periodic/sampling/jfrThreadSampler.hpp"
|
||||
#include "jfr/recorder/jfrEventSetting.hpp"
|
||||
#include "jfr/recorder/jfrRecorder.hpp"
|
||||
@ -169,6 +170,11 @@ NO_TRANSITION(jboolean, jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_ty
|
||||
return JNI_TRUE;
|
||||
NO_TRANSITION_END
|
||||
|
||||
JVM_ENTRY_NO_ENV(void, jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt))
|
||||
JfrEventSetting::set_enabled(JfrCPUTimeSampleEvent, rate > 0);
|
||||
JfrCPUTimeThreadSampling::set_rate(rate, auto_adapt == JNI_TRUE);
|
||||
JVM_END
|
||||
|
||||
NO_TRANSITION(void, jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong event_type_id, jlong value))
|
||||
JfrEventSetting::set_miscellaneous(event_type_id, value);
|
||||
const JfrEventId typed_event_id = (JfrEventId)event_type_id;
|
||||
|
@ -129,6 +129,8 @@ jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jclass jvm);
|
||||
|
||||
jboolean JNICALL jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms);
|
||||
|
||||
void JNICALL jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt);
|
||||
|
||||
void JNICALL jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong id, jlong value);
|
||||
|
||||
void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jclass jvm, jlong cutoff_ticks, jboolean, jboolean);
|
||||
|
@ -83,6 +83,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
|
||||
(char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count,
|
||||
(char*)"setMiscellaneous", (char*)"(JJ)V", (void*)jfr_set_miscellaneous,
|
||||
(char*)"setThrottle", (char*)"(JJJ)Z", (void*)jfr_set_throttle,
|
||||
(char*)"setCPUThrottle", (char*)"(DZ)V", (void*)jfr_set_cpu_throttle,
|
||||
(char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples,
|
||||
(char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk,
|
||||
(char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread,
|
||||
|
@ -962,6 +962,22 @@
|
||||
<Field type="ThreadState" name="state" label="Thread State" />
|
||||
</Event>
|
||||
|
||||
<Event name="CPUTimeSample" category="Java Virtual Machine, Profiling" label="CPU Time Method Sample"
|
||||
description="Snapshot of a threads state from the CPU time sampler. The throttle can be either an upper bound for the event emission rate, e.g. 100/s, or the cpu-time period, e.g. 10ms, with s, ms, us and ns supported as time units."
|
||||
throttle="true" thread="false" experimental="true" startTime="false">
|
||||
<Field type="StackTrace" name="stackTrace" label="Stack Trace" />
|
||||
<Field type="Thread" name="eventThread" label="Thread" />
|
||||
<Field type="boolean" name="failed" label="Failed" description="Failed to obtain the stack trace" />
|
||||
<Field type="Tickspan" name="samplingPeriod" label="CPU Time Sampling Period"/>
|
||||
<Field type="boolean" name="biased" label="Biased" description="The sample is safepoint-biased" />
|
||||
</Event>
|
||||
|
||||
<Event name="CPUTimeSamplesLost" category="Java Virtual Machine, Profiling" label="CPU Time Method Profiling Lost Samples" description="Records that the CPU time sampler lost samples"
|
||||
thread="false" stackTrace="false" startTime="false" experimental="true">
|
||||
<Field type="int" name="lostSamples" label="Lost Samples" />
|
||||
<Field type="Thread" name="eventThread" label="Thread" />
|
||||
</Event>
|
||||
|
||||
<Event name="ThreadDump" category="Java Virtual Machine, Runtime" label="Thread Dump" period="everyChunk">
|
||||
<Field type="string" name="result" label="Thread Dump" />
|
||||
</Event>
|
||||
|
@ -0,0 +1,780 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
|
||||
#include "logging/log.hpp"
|
||||
|
||||
|
||||
#if defined(LINUX)
|
||||
#include "jfr/periodic/sampling/jfrThreadSampling.hpp"
|
||||
#include "jfr/support/jfrThreadLocal.hpp"
|
||||
#include "jfr/utilities/jfrTime.hpp"
|
||||
#include "jfr/utilities/jfrThreadIterator.hpp"
|
||||
#include "jfr/utilities/jfrTypes.hpp"
|
||||
#include "jfrfiles/jfrEventClasses.hpp"
|
||||
#include "memory/resourceArea.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/javaThread.hpp"
|
||||
#include "runtime/osThread.hpp"
|
||||
#include "runtime/safepointMechanism.inline.hpp"
|
||||
#include "runtime/threadSMR.hpp"
|
||||
#include "runtime/vmOperation.hpp"
|
||||
#include "runtime/vmThread.hpp"
|
||||
#include "utilities/ticks.hpp"
|
||||
|
||||
#include "signals_posix.hpp"
|
||||
|
||||
static const int64_t AUTOADAPT_INTERVAL_MS = 100;
|
||||
|
||||
static bool is_excluded(JavaThread* jt) {
|
||||
return jt->is_hidden_from_external_view() ||
|
||||
jt->jfr_thread_local()->is_excluded() ||
|
||||
jt->is_JfrRecorder_thread();
|
||||
}
|
||||
|
||||
static JavaThread* get_java_thread_if_valid() {
|
||||
Thread* raw_thread = Thread::current_or_null_safe();
|
||||
if (raw_thread == nullptr) {
|
||||
// probably while shutting down
|
||||
return nullptr;
|
||||
}
|
||||
assert(raw_thread->is_Java_thread(), "invariant");
|
||||
JavaThread* jt = JavaThread::cast(raw_thread);
|
||||
if (is_excluded(jt) || jt->is_exiting()) {
|
||||
return nullptr;
|
||||
}
|
||||
return jt;
|
||||
}
|
||||
|
||||
JfrCPUTimeTraceQueue::JfrCPUTimeTraceQueue(u4 capacity) :
|
||||
_data(nullptr), _capacity(capacity), _head(0), _lost_samples(0) {
|
||||
if (capacity != 0) {
|
||||
_data = JfrCHeapObj::new_array<JfrCPUTimeSampleRequest>(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
JfrCPUTimeTraceQueue::~JfrCPUTimeTraceQueue() {
|
||||
if (_data != nullptr) {
|
||||
assert(_capacity != 0, "invariant");
|
||||
JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest));
|
||||
}
|
||||
}
|
||||
|
||||
bool JfrCPUTimeTraceQueue::enqueue(JfrCPUTimeSampleRequest& request) {
|
||||
assert(JavaThread::current()->jfr_thread_local()->is_cpu_time_jfr_enqueue_locked(), "invariant");
|
||||
assert(&JavaThread::current()->jfr_thread_local()->cpu_time_jfr_queue() == this, "invariant");
|
||||
u4 elementIndex;
|
||||
do {
|
||||
elementIndex = Atomic::load_acquire(&_head);
|
||||
if (elementIndex >= _capacity) {
|
||||
return false;
|
||||
}
|
||||
} while (Atomic::cmpxchg(&_head, elementIndex, elementIndex + 1) != elementIndex);
|
||||
_data[elementIndex] = request;
|
||||
return true;
|
||||
}
|
||||
|
||||
JfrCPUTimeSampleRequest& JfrCPUTimeTraceQueue::at(u4 index) {
|
||||
assert(index < _head, "invariant");
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
static volatile u4 _lost_samples_sum = 0;
|
||||
|
||||
u4 JfrCPUTimeTraceQueue::size() const {
|
||||
return Atomic::load_acquire(&_head);
|
||||
}
|
||||
|
||||
void JfrCPUTimeTraceQueue::set_size(u4 size) {
|
||||
Atomic::release_store(&_head, size);
|
||||
}
|
||||
|
||||
u4 JfrCPUTimeTraceQueue::capacity() const {
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
void JfrCPUTimeTraceQueue::set_capacity(u4 capacity) {
|
||||
_head = 0;
|
||||
if (_data != nullptr) {
|
||||
assert(_capacity != 0, "invariant");
|
||||
JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest));
|
||||
}
|
||||
if (capacity != 0) {
|
||||
_data = JfrCHeapObj::new_array<JfrCPUTimeSampleRequest>(capacity);
|
||||
} else {
|
||||
_data = nullptr;
|
||||
}
|
||||
_capacity = capacity;
|
||||
}
|
||||
|
||||
bool JfrCPUTimeTraceQueue::is_empty() const {
|
||||
return Atomic::load_acquire(&_head) == 0;
|
||||
}
|
||||
|
||||
u4 JfrCPUTimeTraceQueue::lost_samples() const {
|
||||
return Atomic::load(&_lost_samples);
|
||||
}
|
||||
|
||||
void JfrCPUTimeTraceQueue::increment_lost_samples() {
|
||||
Atomic::inc(&_lost_samples_sum);
|
||||
Atomic::inc(&_lost_samples);
|
||||
}
|
||||
|
||||
u4 JfrCPUTimeTraceQueue::get_and_reset_lost_samples() {
|
||||
return Atomic::xchg(&_lost_samples, (u4)0);
|
||||
}
|
||||
|
||||
void JfrCPUTimeTraceQueue::resize(u4 capacity) {
|
||||
if (capacity != _capacity) {
|
||||
set_capacity(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeTraceQueue::resize_for_period(u4 period_millis) {
|
||||
u4 capacity = CPU_TIME_QUEUE_CAPACITY;
|
||||
if (period_millis > 0 && period_millis < 10) {
|
||||
capacity = (u4) ((double) capacity * 10 / period_millis);
|
||||
}
|
||||
resize(capacity);
|
||||
}
|
||||
|
||||
void JfrCPUTimeTraceQueue::clear() {
|
||||
Atomic::release_store(&_head, (u4)0);
|
||||
}
|
||||
|
||||
static int64_t compute_sampling_period(double rate) {
|
||||
if (rate == 0) {
|
||||
return 0;
|
||||
}
|
||||
return os::active_processor_count() * 1000000000.0 / rate;
|
||||
}
|
||||
|
||||
class JfrCPUSamplerThread : public NonJavaThread {
|
||||
friend class JfrCPUTimeThreadSampling;
|
||||
private:
|
||||
Semaphore _sample;
|
||||
NonJavaThread* _sampler_thread;
|
||||
double _rate;
|
||||
bool _auto_adapt;
|
||||
volatile int64_t _current_sampling_period_ns;
|
||||
volatile bool _disenrolled;
|
||||
// top bit is used to indicate that no signal handler should proceed
|
||||
volatile u4 _active_signal_handlers;
|
||||
volatile bool _is_async_processing_of_cpu_time_jfr_requests_triggered;
|
||||
volatile bool _warned_about_timer_creation_failure;
|
||||
volatile bool _signal_handler_installed;
|
||||
|
||||
static const u4 STOP_SIGNAL_BIT = 0x80000000;
|
||||
|
||||
JfrCPUSamplerThread(double rate, bool auto_adapt);
|
||||
|
||||
void start_thread();
|
||||
|
||||
void enroll();
|
||||
void disenroll();
|
||||
void update_all_thread_timers();
|
||||
|
||||
void auto_adapt_period_if_needed();
|
||||
|
||||
void set_rate(double rate, bool auto_adapt);
|
||||
int64_t get_sampling_period() const { return Atomic::load(&_current_sampling_period_ns); };
|
||||
|
||||
void sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now);
|
||||
|
||||
// process the queues for all threads that are in native state (and requested to be processed)
|
||||
void stackwalk_threads_in_native();
|
||||
bool create_timer_for_thread(JavaThread* thread, timer_t &timerid);
|
||||
|
||||
void stop_signal_handlers();
|
||||
|
||||
// returns false if the stop signal bit was set, true otherwise
|
||||
bool increment_signal_handler_count();
|
||||
|
||||
void decrement_signal_handler_count();
|
||||
|
||||
void initialize_active_signal_handler_counter();
|
||||
|
||||
protected:
|
||||
virtual void post_run();
|
||||
public:
|
||||
virtual const char* name() const { return "JFR CPU Sampler Thread"; }
|
||||
virtual const char* type_name() const { return "JfrCPUTimeSampler"; }
|
||||
void run();
|
||||
void on_javathread_create(JavaThread* thread);
|
||||
void on_javathread_terminate(JavaThread* thread);
|
||||
|
||||
void handle_timer_signal(siginfo_t* info, void* context);
|
||||
bool init_timers();
|
||||
void stop_timer();
|
||||
|
||||
void trigger_async_processing_of_cpu_time_jfr_requests();
|
||||
};
|
||||
|
||||
JfrCPUSamplerThread::JfrCPUSamplerThread(double rate, bool auto_adapt) :
|
||||
_sample(),
|
||||
_sampler_thread(nullptr),
|
||||
_rate(rate),
|
||||
_auto_adapt(auto_adapt),
|
||||
_current_sampling_period_ns(compute_sampling_period(rate)),
|
||||
_disenrolled(true),
|
||||
_active_signal_handlers(STOP_SIGNAL_BIT),
|
||||
_is_async_processing_of_cpu_time_jfr_requests_triggered(false),
|
||||
_warned_about_timer_creation_failure(false),
|
||||
_signal_handler_installed(false) {
|
||||
assert(rate >= 0, "invariant");
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::trigger_async_processing_of_cpu_time_jfr_requests() {
|
||||
Atomic::release_store(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true);
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::on_javathread_create(JavaThread* thread) {
|
||||
if (thread->is_hidden_from_external_view() || thread->is_JfrRecorder_thread() ||
|
||||
!Atomic::load_acquire(&_signal_handler_installed)) {
|
||||
return;
|
||||
}
|
||||
JfrThreadLocal* tl = thread->jfr_thread_local();
|
||||
assert(tl != nullptr, "invariant");
|
||||
tl->cpu_time_jfr_queue().resize_for_period(_current_sampling_period_ns / 1000000);
|
||||
timer_t timerid;
|
||||
if (create_timer_for_thread(thread, timerid)) {
|
||||
tl->set_cpu_timer(&timerid);
|
||||
} else {
|
||||
if (!Atomic::or_then_fetch(&_warned_about_timer_creation_failure, true)) {
|
||||
log_warning(jfr)("Failed to create timer for a thread");
|
||||
}
|
||||
tl->deallocate_cpu_time_jfr_queue();
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::on_javathread_terminate(JavaThread* thread) {
|
||||
JfrThreadLocal* tl = thread->jfr_thread_local();
|
||||
assert(tl != nullptr, "invariant");
|
||||
timer_t* timer = tl->cpu_timer();
|
||||
if (timer == nullptr) {
|
||||
return; // no timer was created for this thread
|
||||
}
|
||||
tl->unset_cpu_timer();
|
||||
tl->deallocate_cpu_time_jfr_queue();
|
||||
s4 lost_samples = tl->cpu_time_jfr_queue().lost_samples();
|
||||
if (lost_samples > 0) {
|
||||
JfrCPUTimeThreadSampling::send_lost_event(JfrTicks::now(), JfrThreadLocal::thread_id(thread), lost_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::start_thread() {
|
||||
if (os::create_thread(this, os::os_thread)) {
|
||||
os::start_thread(this);
|
||||
} else {
|
||||
log_error(jfr)("Failed to create thread for thread sampling");
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::enroll() {
|
||||
if (Atomic::cmpxchg(&_disenrolled, true, false)) {
|
||||
Atomic::store(&_warned_about_timer_creation_failure, false);
|
||||
initialize_active_signal_handler_counter();
|
||||
log_trace(jfr)("Enrolling CPU thread sampler");
|
||||
_sample.signal();
|
||||
if (!init_timers()) {
|
||||
log_error(jfr)("Failed to initialize timers for CPU thread sampler");
|
||||
disenroll();
|
||||
return;
|
||||
}
|
||||
log_trace(jfr)("Enrolled CPU thread sampler");
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::disenroll() {
|
||||
if (!Atomic::cmpxchg(&_disenrolled, false, true)) {
|
||||
log_trace(jfr)("Disenrolling CPU thread sampler");
|
||||
if (Atomic::load_acquire(&_signal_handler_installed)) {
|
||||
stop_timer();
|
||||
stop_signal_handlers();
|
||||
}
|
||||
_sample.wait();
|
||||
log_trace(jfr)("Disenrolled CPU thread sampler");
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::run() {
|
||||
assert(_sampler_thread == nullptr, "invariant");
|
||||
_sampler_thread = this;
|
||||
int64_t last_auto_adapt_check = os::javaTimeNanos();
|
||||
while (true) {
|
||||
if (!_sample.trywait()) {
|
||||
// disenrolled
|
||||
_sample.wait();
|
||||
}
|
||||
_sample.signal();
|
||||
|
||||
if (os::javaTimeNanos() - last_auto_adapt_check > AUTOADAPT_INTERVAL_MS * 1000000) {
|
||||
auto_adapt_period_if_needed();
|
||||
last_auto_adapt_check = os::javaTimeNanos();
|
||||
}
|
||||
|
||||
if (Atomic::cmpxchg(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true, false)) {
|
||||
stackwalk_threads_in_native();
|
||||
}
|
||||
os::naked_sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::stackwalk_threads_in_native() {
|
||||
ResourceMark rm;
|
||||
// Required to prevent JFR from sampling through an ongoing safepoint
|
||||
MutexLocker tlock(Threads_lock);
|
||||
ThreadsListHandle tlh;
|
||||
Thread* current = Thread::current();
|
||||
for (size_t i = 0; i < tlh.list()->length(); i++) {
|
||||
JavaThread* jt = tlh.list()->thread_at(i);
|
||||
JfrThreadLocal* tl = jt->jfr_thread_local();
|
||||
if (tl->wants_async_processing_of_cpu_time_jfr_requests()) {
|
||||
if (jt->thread_state() != _thread_in_native || !tl->try_acquire_cpu_time_jfr_dequeue_lock()) {
|
||||
tl->set_do_async_processing_of_cpu_time_jfr_requests(false);
|
||||
continue;
|
||||
}
|
||||
if (jt->has_last_Java_frame()) {
|
||||
JfrThreadSampling::process_cpu_time_request(jt, tl, current, false);
|
||||
} else {
|
||||
tl->set_do_async_processing_of_cpu_time_jfr_requests(false);
|
||||
}
|
||||
tl->release_cpu_time_jfr_queue_lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static volatile size_t count = 0;
|
||||
|
||||
void JfrCPUTimeThreadSampling::send_empty_event(const JfrTicks &start_time, traceid tid, Tickspan cpu_time_period) {
|
||||
EventCPUTimeSample event(UNTIMED);
|
||||
event.set_failed(true);
|
||||
event.set_starttime(start_time);
|
||||
event.set_eventThread(tid);
|
||||
event.set_stackTrace(0);
|
||||
event.set_samplingPeriod(cpu_time_period);
|
||||
event.set_biased(false);
|
||||
event.commit();
|
||||
}
|
||||
|
||||
|
||||
static volatile size_t biased_count = 0;
|
||||
|
||||
void JfrCPUTimeThreadSampling::send_event(const JfrTicks &start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased) {
|
||||
EventCPUTimeSample event(UNTIMED);
|
||||
event.set_failed(false);
|
||||
event.set_starttime(start_time);
|
||||
event.set_eventThread(tid);
|
||||
event.set_stackTrace(sid);
|
||||
event.set_samplingPeriod(cpu_time_period);
|
||||
event.set_biased(biased);
|
||||
event.commit();
|
||||
Atomic::inc(&count);
|
||||
if (biased) {
|
||||
Atomic::inc(&biased_count);
|
||||
}
|
||||
if (Atomic::load(&count) % 1000 == 0) {
|
||||
log_debug(jfr)("CPU thread sampler sent %zu events, lost %d, biased %zu\n", Atomic::load(&count), Atomic::load(&_lost_samples_sum), Atomic::load(&biased_count));
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::send_lost_event(const JfrTicks &time, traceid tid, s4 lost_samples) {
|
||||
if (!EventCPUTimeSamplesLost::is_enabled()) {
|
||||
return;
|
||||
}
|
||||
EventCPUTimeSamplesLost event(UNTIMED);
|
||||
event.set_starttime(time);
|
||||
event.set_lostSamples(lost_samples);
|
||||
event.set_eventThread(tid);
|
||||
event.commit();
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::post_run() {
|
||||
this->NonJavaThread::post_run();
|
||||
delete this;
|
||||
}
|
||||
|
||||
static JfrCPUTimeThreadSampling* _instance = nullptr;
|
||||
|
||||
JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() {
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() {
|
||||
assert(_instance == nullptr, "invariant");
|
||||
_instance = new JfrCPUTimeThreadSampling();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::destroy() {
|
||||
if (_instance != nullptr) {
|
||||
delete _instance;
|
||||
_instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JfrCPUTimeThreadSampling::JfrCPUTimeThreadSampling() : _sampler(nullptr) {}
|
||||
|
||||
JfrCPUTimeThreadSampling::~JfrCPUTimeThreadSampling() {
|
||||
if (_sampler != nullptr) {
|
||||
_sampler->disenroll();
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::create_sampler(double rate, bool auto_adapt) {
|
||||
assert(_sampler == nullptr, "invariant");
|
||||
_sampler = new JfrCPUSamplerThread(rate, auto_adapt);
|
||||
_sampler->start_thread();
|
||||
_sampler->enroll();
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::update_run_state(double rate, bool auto_adapt) {
|
||||
if (rate != 0) {
|
||||
if (_sampler == nullptr) {
|
||||
create_sampler(rate, auto_adapt);
|
||||
} else {
|
||||
_sampler->set_rate(rate, auto_adapt);
|
||||
_sampler->enroll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_sampler != nullptr) {
|
||||
_sampler->set_rate(rate /* 0 */, auto_adapt);
|
||||
_sampler->disenroll();
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) {
|
||||
assert(rate >= 0, "invariant");
|
||||
if (_instance == nullptr) {
|
||||
return;
|
||||
}
|
||||
instance().set_rate_value(rate, auto_adapt);
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::set_rate_value(double rate, bool auto_adapt) {
|
||||
if (_sampler != nullptr) {
|
||||
_sampler->set_rate(rate, auto_adapt);
|
||||
}
|
||||
update_run_state(rate, auto_adapt);
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread *thread) {
|
||||
if (_instance != nullptr && _instance->_sampler != nullptr) {
|
||||
_instance->_sampler->on_javathread_create(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread *thread) {
|
||||
if (_instance != nullptr && _instance->_sampler != nullptr) {
|
||||
_instance->_sampler->on_javathread_terminate(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests() {
|
||||
if (_instance != nullptr && _instance->_sampler != nullptr) {
|
||||
_instance->_sampler->trigger_async_processing_of_cpu_time_jfr_requests();
|
||||
}
|
||||
}
|
||||
|
||||
void handle_timer_signal(int signo, siginfo_t* info, void* context) {
|
||||
assert(_instance != nullptr, "invariant");
|
||||
_instance->handle_timer_signal(info, context);
|
||||
}
|
||||
|
||||
|
||||
void JfrCPUTimeThreadSampling::handle_timer_signal(siginfo_t* info, void* context) {
|
||||
if (info->si_code != SI_TIMER) {
|
||||
// not the signal we are interested in
|
||||
return;
|
||||
}
|
||||
assert(_sampler != nullptr, "invariant");
|
||||
|
||||
if (!_sampler->increment_signal_handler_count()) {
|
||||
return;
|
||||
}
|
||||
_sampler->handle_timer_signal(info, context);
|
||||
_sampler->decrement_signal_handler_count();
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now) {
|
||||
JfrSampleRequestBuilder::build_cpu_time_sample_request(request, ucontext, jt, jt->jfr_thread_local(), now);
|
||||
}
|
||||
|
||||
static bool check_state(JavaThread* thread) {
|
||||
switch (thread->thread_state()) {
|
||||
case _thread_in_Java:
|
||||
case _thread_in_native:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::handle_timer_signal(siginfo_t* info, void* context) {
|
||||
JfrTicks now = JfrTicks::now();
|
||||
JavaThread* jt = get_java_thread_if_valid();
|
||||
if (jt == nullptr) {
|
||||
return;
|
||||
}
|
||||
JfrThreadLocal* tl = jt->jfr_thread_local();
|
||||
JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue();
|
||||
if (!check_state(jt)) {
|
||||
queue.increment_lost_samples();
|
||||
return;
|
||||
}
|
||||
if (!tl->try_acquire_cpu_time_jfr_enqueue_lock()) {
|
||||
queue.increment_lost_samples();
|
||||
return;
|
||||
}
|
||||
|
||||
JfrCPUTimeSampleRequest request;
|
||||
// the sampling period might be too low for the current Linux configuration
|
||||
// so samples might be skipped and we have to compute the actual period
|
||||
int64_t period = get_sampling_period() * (info->si_overrun + 1);
|
||||
request._cpu_time_period = Ticks(period / 1000000000.0 * JfrTime::frequency()) - Ticks(0);
|
||||
sample_thread(request._request, context, jt, tl, now);
|
||||
|
||||
if (queue.enqueue(request)) {
|
||||
if (queue.size() == 1) {
|
||||
tl->set_has_cpu_time_jfr_requests(true);
|
||||
SafepointMechanism::arm_local_poll_release(jt);
|
||||
}
|
||||
} else {
|
||||
queue.increment_lost_samples();
|
||||
}
|
||||
|
||||
if (jt->thread_state() == _thread_in_native) {
|
||||
if (!tl->wants_async_processing_of_cpu_time_jfr_requests()) {
|
||||
tl->set_do_async_processing_of_cpu_time_jfr_requests(true);
|
||||
JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests();
|
||||
}
|
||||
} else {
|
||||
tl->set_do_async_processing_of_cpu_time_jfr_requests(false);
|
||||
}
|
||||
|
||||
tl->release_cpu_time_jfr_queue_lock();
|
||||
}
|
||||
|
||||
static const int SIG = SIGPROF;
|
||||
|
||||
static void set_timer_time(timer_t timerid, int64_t period_nanos) {
|
||||
struct itimerspec its;
|
||||
if (period_nanos == 0) {
|
||||
its.it_interval.tv_sec = 0;
|
||||
its.it_interval.tv_nsec = 0;
|
||||
} else {
|
||||
its.it_interval.tv_sec = period_nanos / NANOSECS_PER_SEC;
|
||||
its.it_interval.tv_nsec = period_nanos % NANOSECS_PER_SEC;
|
||||
}
|
||||
its.it_value = its.it_interval;
|
||||
if (timer_settime(timerid, 0, &its, nullptr) == -1) {
|
||||
warning("Failed to set timer for thread sampling: %s", os::strerror(os::get_last_error()));
|
||||
}
|
||||
}
|
||||
|
||||
bool JfrCPUSamplerThread::create_timer_for_thread(JavaThread* thread, timer_t& timerid) {
|
||||
struct sigevent sev;
|
||||
sev.sigev_notify = SIGEV_THREAD_ID;
|
||||
sev.sigev_signo = SIG;
|
||||
sev.sigev_value.sival_ptr = nullptr;
|
||||
((int*)&sev.sigev_notify)[1] = thread->osthread()->thread_id();
|
||||
clockid_t clock;
|
||||
int err = pthread_getcpuclockid(thread->osthread()->pthread_id(), &clock);
|
||||
if (err != 0) {
|
||||
log_error(jfr)("Failed to get clock for thread sampling: %s", os::strerror(err));
|
||||
return false;
|
||||
}
|
||||
if (timer_create(clock, &sev, &timerid) < 0) {
|
||||
return false;
|
||||
}
|
||||
int64_t period = get_sampling_period();
|
||||
if (period != 0) {
|
||||
set_timer_time(timerid, period);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void JfrCPUSamplerThread::stop_signal_handlers() {
|
||||
// set the stop signal bit
|
||||
Atomic::or_then_fetch(&_active_signal_handlers, STOP_SIGNAL_BIT, memory_order_acq_rel);
|
||||
while (Atomic::load_acquire(&_active_signal_handlers) > STOP_SIGNAL_BIT) {
|
||||
// wait for all signal handlers to finish
|
||||
os::naked_short_nanosleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// returns false if the stop signal bit was set, true otherwise
|
||||
bool JfrCPUSamplerThread::increment_signal_handler_count() {
|
||||
// increment the count of active signal handlers
|
||||
u4 old_value = Atomic::fetch_then_add(&_active_signal_handlers, (u4)1, memory_order_acq_rel);
|
||||
if ((old_value & STOP_SIGNAL_BIT) != 0) {
|
||||
// if the stop signal bit was set, we are not allowed to increment
|
||||
Atomic::dec(&_active_signal_handlers, memory_order_acq_rel);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::decrement_signal_handler_count() {
|
||||
Atomic::dec(&_active_signal_handlers, memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::initialize_active_signal_handler_counter() {
|
||||
Atomic::release_store(&_active_signal_handlers, (u4)0);
|
||||
}
|
||||
|
||||
class VM_JFRInitializeCPUTimeSampler : public VM_Operation {
|
||||
private:
|
||||
JfrCPUSamplerThread* const _sampler;
|
||||
|
||||
public:
|
||||
VM_JFRInitializeCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {}
|
||||
|
||||
VMOp_Type type() const { return VMOp_JFRInitializeCPUTimeSampler; }
|
||||
void doit() {
|
||||
JfrJavaThreadIterator iter;
|
||||
while (iter.has_next()) {
|
||||
_sampler->on_javathread_create(iter.next());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
bool JfrCPUSamplerThread::init_timers() {
|
||||
// install sig handler for sig
|
||||
void* prev_handler = PosixSignals::get_signal_handler_for_signal(SIG);
|
||||
if ((prev_handler != SIG_DFL && prev_handler != SIG_IGN && prev_handler != (void*)::handle_timer_signal) ||
|
||||
PosixSignals::install_generic_signal_handler(SIG, (void*)::handle_timer_signal) == (void*)-1) {
|
||||
log_error(jfr)("Conflicting SIGPROF handler found: %p. CPUTimeSample events will not be recorded", prev_handler);
|
||||
return false;
|
||||
}
|
||||
Atomic::release_store(&_signal_handler_installed, true);
|
||||
VM_JFRInitializeCPUTimeSampler op(this);
|
||||
VMThread::execute(&op);
|
||||
return true;
|
||||
}
|
||||
|
||||
class VM_JFRTerminateCPUTimeSampler : public VM_Operation {
|
||||
private:
|
||||
JfrCPUSamplerThread* const _sampler;
|
||||
|
||||
public:
|
||||
VM_JFRTerminateCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {}
|
||||
|
||||
VMOp_Type type() const { return VMOp_JFRTerminateCPUTimeSampler; }
|
||||
void doit() {
|
||||
JfrJavaThreadIterator iter;
|
||||
while (iter.has_next()) {
|
||||
JavaThread *thread = iter.next();
|
||||
JfrThreadLocal* tl = thread->jfr_thread_local();
|
||||
timer_t* timer = tl->cpu_timer();
|
||||
if (timer == nullptr) {
|
||||
continue;
|
||||
}
|
||||
tl->deallocate_cpu_time_jfr_queue();
|
||||
tl->unset_cpu_timer();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void JfrCPUSamplerThread::stop_timer() {
|
||||
VM_JFRTerminateCPUTimeSampler op(this);
|
||||
VMThread::execute(&op);
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::auto_adapt_period_if_needed() {
|
||||
int64_t current_period = get_sampling_period();
|
||||
if (_auto_adapt || current_period == -1) {
|
||||
int64_t period = compute_sampling_period(_rate);
|
||||
if (period != current_period) {
|
||||
Atomic::store(&_current_sampling_period_ns, period);
|
||||
update_all_thread_timers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::set_rate(double rate, bool auto_adapt) {
|
||||
_rate = rate;
|
||||
_auto_adapt = auto_adapt;
|
||||
if (_rate > 0 && Atomic::load_acquire(&_disenrolled) == false) {
|
||||
auto_adapt_period_if_needed();
|
||||
} else {
|
||||
Atomic::store(&_current_sampling_period_ns, compute_sampling_period(rate));
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUSamplerThread::update_all_thread_timers() {
|
||||
int64_t period_millis = get_sampling_period();
|
||||
ThreadsListHandle tlh;
|
||||
for (size_t i = 0; i < tlh.length(); i++) {
|
||||
JavaThread* thread = tlh.thread_at(i);
|
||||
JfrThreadLocal* tl = thread->jfr_thread_local();
|
||||
assert(tl != nullptr, "invariant");
|
||||
timer_t* timer = tl->cpu_timer();
|
||||
if (timer != nullptr) {
|
||||
set_timer_time(*timer, period_millis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void warn() {
|
||||
static bool displayed_warning = false;
|
||||
if (!displayed_warning) {
|
||||
warning("CPU time method sampling not supported in JFR on your platform");
|
||||
displayed_warning = true;
|
||||
}
|
||||
}
|
||||
|
||||
static JfrCPUTimeThreadSampling* _instance = nullptr;
|
||||
|
||||
JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() {
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() {
|
||||
_instance = new JfrCPUTimeThreadSampling();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::destroy() {
|
||||
delete _instance;
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) {
|
||||
if (rate != 0) {
|
||||
warn();
|
||||
}
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread* thread) {
|
||||
}
|
||||
|
||||
void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread* thread) {
|
||||
}
|
||||
|
||||
#endif // defined(LINUX) && defined(INCLUDE_JFR)
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. 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_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP
|
||||
#define SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP
|
||||
|
||||
#include "jfr/utilities/jfrAllocation.hpp"
|
||||
|
||||
class JavaThread;
|
||||
|
||||
#if defined(LINUX)
|
||||
|
||||
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
||||
#include "jfr/utilities/jfrTypes.hpp"
|
||||
|
||||
struct JfrCPUTimeSampleRequest {
|
||||
JfrSampleRequest _request;
|
||||
Tickspan _cpu_time_period;
|
||||
|
||||
JfrCPUTimeSampleRequest() {}
|
||||
};
|
||||
|
||||
// Fixed size async-signal-safe SPSC linear queue backed by an array.
|
||||
// Designed to be only used under lock and read linearly
|
||||
class JfrCPUTimeTraceQueue {
|
||||
|
||||
// the default queue capacity, scaled if the sampling period is smaller than 10ms
|
||||
// when the thread is started
|
||||
static const u4 CPU_TIME_QUEUE_CAPACITY = 500;
|
||||
|
||||
JfrCPUTimeSampleRequest* _data;
|
||||
u4 _capacity;
|
||||
// next unfilled index
|
||||
volatile u4 _head;
|
||||
|
||||
volatile u4 _lost_samples;
|
||||
|
||||
public:
|
||||
JfrCPUTimeTraceQueue(u4 capacity);
|
||||
|
||||
~JfrCPUTimeTraceQueue();
|
||||
|
||||
// signal safe, but can't be interleaved with dequeue
|
||||
bool enqueue(JfrCPUTimeSampleRequest& trace);
|
||||
|
||||
JfrCPUTimeSampleRequest& at(u4 index);
|
||||
|
||||
u4 size() const;
|
||||
|
||||
void set_size(u4 size);
|
||||
|
||||
u4 capacity() const;
|
||||
|
||||
// deletes all samples in the queue
|
||||
void set_capacity(u4 capacity);
|
||||
|
||||
bool is_empty() const;
|
||||
|
||||
u4 lost_samples() const;
|
||||
|
||||
void increment_lost_samples();
|
||||
|
||||
// returns the previous lost samples count
|
||||
u4 get_and_reset_lost_samples();
|
||||
|
||||
void resize(u4 capacity);
|
||||
|
||||
void resize_for_period(u4 period_millis);
|
||||
|
||||
void clear();
|
||||
|
||||
};
|
||||
|
||||
|
||||
class JfrCPUSamplerThread;
|
||||
|
||||
class JfrCPUTimeThreadSampling : public JfrCHeapObj {
|
||||
friend class JfrRecorder;
|
||||
private:
|
||||
|
||||
JfrCPUSamplerThread* _sampler;
|
||||
|
||||
void create_sampler(double rate, bool auto_adapt);
|
||||
void set_rate_value(double rate, bool auto_adapt);
|
||||
|
||||
JfrCPUTimeThreadSampling();
|
||||
~JfrCPUTimeThreadSampling();
|
||||
|
||||
static JfrCPUTimeThreadSampling& instance();
|
||||
static JfrCPUTimeThreadSampling* create();
|
||||
static void destroy();
|
||||
|
||||
void update_run_state(double rate, bool auto_adapt);
|
||||
|
||||
public:
|
||||
static void set_rate(double rate, bool auto_adapt);
|
||||
|
||||
static void on_javathread_create(JavaThread* thread);
|
||||
static void on_javathread_terminate(JavaThread* thread);
|
||||
void handle_timer_signal(siginfo_t* info, void* context);
|
||||
|
||||
static void send_empty_event(const JfrTicks& start_time, traceid tid, Tickspan cpu_time_period);
|
||||
static void send_event(const JfrTicks& start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased);
|
||||
static void send_lost_event(const JfrTicks& time, traceid tid, s4 lost_samples);
|
||||
|
||||
static void trigger_async_processing_of_cpu_time_jfr_requests();
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// a basic implementation on other platforms that
|
||||
// emits warnings
|
||||
|
||||
class JfrCPUTimeThreadSampling : public JfrCHeapObj {
|
||||
friend class JfrRecorder;
|
||||
private:
|
||||
static JfrCPUTimeThreadSampling& instance();
|
||||
static JfrCPUTimeThreadSampling* create();
|
||||
static void destroy();
|
||||
|
||||
public:
|
||||
static void set_rate(double rate, bool auto_adapt);
|
||||
|
||||
static void on_javathread_create(JavaThread* thread);
|
||||
static void on_javathread_terminate(JavaThread* thread);
|
||||
};
|
||||
|
||||
#endif // defined(LINUX)
|
||||
|
||||
|
||||
#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP
|
@ -24,6 +24,7 @@
|
||||
#include "asm/codeBuffer.hpp"
|
||||
#include "interpreter/interpreter.hpp"
|
||||
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
||||
#include "jfr/utilities/jfrTime.hpp"
|
||||
#include "runtime/continuationEntry.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
#include "runtime/javaThread.inline.hpp"
|
||||
@ -171,7 +172,7 @@ static bool build(JfrSampleRequest& request, intptr_t* fp, JavaThread* jt) {
|
||||
assert(request._sample_sp != nullptr, "invariant");
|
||||
assert(request._sample_pc != nullptr, "invariant");
|
||||
assert(jt != nullptr, "invariant");
|
||||
assert(jt->thread_state() == _thread_in_Java, "invariant");
|
||||
assert(jt->thread_state() == _thread_in_Java || jt->thread_state() == _thread_in_native, "invariant");
|
||||
|
||||
// 1. Interpreter frame?
|
||||
if (is_interpreter(request)) {
|
||||
@ -303,3 +304,33 @@ JfrSampleResult JfrSampleRequestBuilder::build_java_sample_request(const void* u
|
||||
}
|
||||
return set_biased_java_sample(request, tl, jt);
|
||||
}
|
||||
|
||||
|
||||
// A biased sample request is denoted by an empty bcp and an empty pc.
|
||||
static inline void set_cpu_time_biased_sample(JfrSampleRequest& request, JavaThread* jt) {
|
||||
if (request._sample_bcp != nullptr) {
|
||||
request._sample_bcp = nullptr;
|
||||
}
|
||||
assert(request._sample_bcp == nullptr, "invariant");
|
||||
request._sample_pc = nullptr;
|
||||
}
|
||||
|
||||
void JfrSampleRequestBuilder::build_cpu_time_sample_request(JfrSampleRequest& request,
|
||||
void* ucontext,
|
||||
JavaThread* jt,
|
||||
JfrThreadLocal* tl,
|
||||
JfrTicks& now) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
request._sample_ticks = now;
|
||||
|
||||
// Prioritize the ljf, if one exists.
|
||||
request._sample_sp = jt->last_Java_sp();
|
||||
if (request._sample_sp == nullptr || !build_from_ljf(request, tl, jt)) {
|
||||
intptr_t* fp;
|
||||
request._sample_pc = os::fetch_frame_from_context(ucontext, reinterpret_cast<intptr_t**>(&request._sample_sp), &fp);
|
||||
assert(sp_in_stack(request, jt), "invariant");
|
||||
if (!build(request, fp, jt)) {
|
||||
set_cpu_time_biased_sample(request, jt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,11 @@ class JfrSampleRequestBuilder : AllStatic {
|
||||
static JfrSampleResult build_java_sample_request(const void* ucontext,
|
||||
JfrThreadLocal* tl,
|
||||
JavaThread* jt);
|
||||
static void build_cpu_time_sample_request(JfrSampleRequest &request,
|
||||
void* ucontext,
|
||||
JavaThread* jt,
|
||||
JfrThreadLocal* tl,
|
||||
JfrTicks& now);
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEREQUEST_HPP
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "code/nmethod.hpp"
|
||||
#include "interpreter/interpreter.hpp"
|
||||
#include "jfr/jfrEvents.hpp"
|
||||
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
|
||||
#include "jfr/periodic/sampling/jfrSampleMonitor.hpp"
|
||||
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
||||
#include "jfr/periodic/sampling/jfrThreadSampling.hpp"
|
||||
@ -161,7 +162,7 @@ static inline bool is_valid(const PcDesc* pc_desc) {
|
||||
return pc_desc != nullptr && pc_desc->scope_decode_offset() != DebugInformationRecorder::serialized_null;
|
||||
}
|
||||
|
||||
static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt) {
|
||||
static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt, bool& biased) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
|
||||
if (!jt->has_last_Java_frame()) {
|
||||
@ -178,6 +179,7 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame,
|
||||
// A biased sample is requested or no code blob.
|
||||
top_frame = jt->last_frame();
|
||||
in_continuation = is_in_continuation(top_frame, jt);
|
||||
biased = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -227,6 +229,8 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame,
|
||||
|
||||
assert(!stream.current()->is_safepoint_blob_frame(), "invariant");
|
||||
|
||||
biased = true;
|
||||
|
||||
// Search the first frame that is above the sampled sp.
|
||||
for (; !stream.is_done(); stream.next()) {
|
||||
frame* const current = stream.current();
|
||||
@ -250,6 +254,7 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame,
|
||||
const PcDesc* const pc_desc = get_pc_desc(sampled_nm, sampled_pc);
|
||||
if (is_valid(pc_desc)) {
|
||||
current->adjust_pc(pc_desc->real_pc(sampled_nm));
|
||||
biased = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -270,8 +275,9 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick
|
||||
assert(current != nullptr, "invariant");
|
||||
|
||||
frame top_frame;
|
||||
bool biased = false;
|
||||
bool in_continuation;
|
||||
if (!compute_top_frame(request, top_frame, in_continuation, jt)) {
|
||||
if (!compute_top_frame(request, top_frame, in_continuation, jt, biased)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -293,6 +299,42 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef LINUX
|
||||
static void record_cpu_time_thread(const JfrCPUTimeSampleRequest& request, const JfrTicks& now, const JfrThreadLocal* tl, JavaThread* jt, Thread* current) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
assert(tl != nullptr, "invariant");
|
||||
assert(current != nullptr, "invariant");
|
||||
frame top_frame;
|
||||
bool biased = false;
|
||||
bool in_continuation = false;
|
||||
bool could_compute_top_frame = compute_top_frame(request._request, top_frame, in_continuation, jt, biased);
|
||||
const traceid tid = in_continuation ? tl->vthread_id_with_epoch_update(jt) : JfrThreadLocal::jvm_thread_id(jt);
|
||||
|
||||
if (!could_compute_top_frame) {
|
||||
JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period);
|
||||
return;
|
||||
}
|
||||
traceid sid;
|
||||
{
|
||||
ResourceMark rm(current);
|
||||
JfrStackTrace stacktrace;
|
||||
if (!stacktrace.record(jt, top_frame, in_continuation, request._request)) {
|
||||
// Unable to record stacktrace. Fail.
|
||||
JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period);
|
||||
return;
|
||||
}
|
||||
sid = JfrStackTraceRepository::add(stacktrace);
|
||||
}
|
||||
assert(sid != 0, "invariant");
|
||||
|
||||
|
||||
JfrCPUTimeThreadSampling::send_event(request._request._sample_ticks, sid, tid, request._cpu_time_period, biased);
|
||||
if (current == jt) {
|
||||
send_safepoint_latency_event(request._request, now, sid, jt);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) {
|
||||
assert(tl != nullptr, "invariant");
|
||||
assert(jt != nullptr, "invariant");
|
||||
@ -308,6 +350,49 @@ static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, Jav
|
||||
assert(!tl->has_enqueued_requests(), "invariant");
|
||||
}
|
||||
|
||||
static void drain_enqueued_cpu_time_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current, bool lock) {
|
||||
assert(tl != nullptr, "invariant");
|
||||
assert(jt != nullptr, "invariant");
|
||||
assert(current != nullptr, "invariant");
|
||||
#ifdef LINUX
|
||||
tl->set_do_async_processing_of_cpu_time_jfr_requests(false);
|
||||
if (lock) {
|
||||
tl->acquire_cpu_time_jfr_dequeue_lock();
|
||||
}
|
||||
JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue();
|
||||
for (u4 i = 0; i < queue.size(); i++) {
|
||||
record_cpu_time_thread(queue.at(i), now, tl, jt, current);
|
||||
}
|
||||
queue.clear();
|
||||
assert(queue.is_empty(), "invariant");
|
||||
tl->set_has_cpu_time_jfr_requests(false);
|
||||
if (queue.lost_samples() > 0) {
|
||||
JfrCPUTimeThreadSampling::send_lost_event( now, JfrThreadLocal::thread_id(jt), queue.get_and_reset_lost_samples());
|
||||
}
|
||||
if (lock) {
|
||||
tl->release_cpu_time_jfr_queue_lock();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Entry point for a thread that has been sampled in native code and has a pending JFR CPU time request.
|
||||
void JfrThreadSampling::process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
|
||||
const JfrTicks now = JfrTicks::now();
|
||||
drain_enqueued_cpu_time_requests(now, tl, jt, current, lock);
|
||||
}
|
||||
|
||||
static void drain_all_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) {
|
||||
assert(tl != nullptr, "invariant");
|
||||
assert(jt != nullptr, "invariant");
|
||||
assert(current != nullptr, "invariant");
|
||||
drain_enqueued_requests(now, tl, jt, current);
|
||||
if (tl->has_cpu_time_jfr_requests()) {
|
||||
drain_enqueued_cpu_time_requests(now, tl, jt, current, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Only entered by the JfrSampler thread.
|
||||
bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) {
|
||||
assert(tl != nullptr, "invairant");
|
||||
@ -382,5 +467,6 @@ void JfrThreadSampling::process_sample_request(JavaThread* jt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
drain_enqueued_requests(now, tl, jt, jt);
|
||||
drain_all_enqueued_requests(now, tl, jt, jt);
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,10 @@ class Thread;
|
||||
|
||||
class JfrThreadSampling : AllStatic {
|
||||
friend class JfrSamplerThread;
|
||||
friend class JfrCPUSamplerThread;
|
||||
private:
|
||||
static bool process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread);
|
||||
static void process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock);
|
||||
public:
|
||||
static void process_sample_request(JavaThread* jt);
|
||||
};
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "jfr/jni/jfrJavaSupport.hpp"
|
||||
#include "jfr/leakprofiler/sampling/objectSampler.hpp"
|
||||
#include "jfr/periodic/jfrOSInterface.hpp"
|
||||
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
|
||||
#include "jfr/periodic/sampling/jfrThreadSampler.hpp"
|
||||
#include "jfr/recorder/jfrRecorder.hpp"
|
||||
#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
|
||||
@ -304,6 +305,9 @@ bool JfrRecorder::create_components() {
|
||||
if (!create_thread_sampler()) {
|
||||
return false;
|
||||
}
|
||||
if (!create_cpu_time_thread_sampling()) {
|
||||
return false;
|
||||
}
|
||||
if (!create_event_throttler()) {
|
||||
return false;
|
||||
}
|
||||
@ -318,6 +322,7 @@ static JfrStackTraceRepository* _stack_trace_repository;
|
||||
static JfrStringPool* _stringpool = nullptr;
|
||||
static JfrOSInterface* _os_interface = nullptr;
|
||||
static JfrThreadSampler* _thread_sampler = nullptr;
|
||||
static JfrCPUTimeThreadSampling* _cpu_time_thread_sampling = nullptr;
|
||||
static JfrCheckpointManager* _checkpoint_manager = nullptr;
|
||||
|
||||
bool JfrRecorder::create_java_event_writer() {
|
||||
@ -390,6 +395,12 @@ bool JfrRecorder::create_thread_sampler() {
|
||||
return _thread_sampler != nullptr;
|
||||
}
|
||||
|
||||
bool JfrRecorder::create_cpu_time_thread_sampling() {
|
||||
assert(_cpu_time_thread_sampling == nullptr, "invariant");
|
||||
_cpu_time_thread_sampling = JfrCPUTimeThreadSampling::create();
|
||||
return _cpu_time_thread_sampling != nullptr;
|
||||
}
|
||||
|
||||
bool JfrRecorder::create_event_throttler() {
|
||||
return JfrEventThrottler::create();
|
||||
}
|
||||
@ -428,6 +439,10 @@ void JfrRecorder::destroy_components() {
|
||||
JfrThreadSampler::destroy();
|
||||
_thread_sampler = nullptr;
|
||||
}
|
||||
if (_cpu_time_thread_sampling != nullptr) {
|
||||
JfrCPUTimeThreadSampling::destroy();
|
||||
_cpu_time_thread_sampling = nullptr;
|
||||
}
|
||||
JfrEventThrottler::destroy();
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ class JfrRecorder : public JfrCHeapObj {
|
||||
static bool create_storage();
|
||||
static bool create_stringpool();
|
||||
static bool create_thread_sampler();
|
||||
static bool create_cpu_time_thread_sampling();
|
||||
static bool create_event_throttler();
|
||||
static bool create_components();
|
||||
static void destroy_components();
|
||||
|
@ -34,6 +34,7 @@ constexpr static const JfrSamplerParams _disabled_params = {
|
||||
false // reconfigure
|
||||
};
|
||||
|
||||
static JfrEventThrottler* _disabled_cpu_time_sample_throttler = nullptr;
|
||||
static JfrEventThrottler* _object_allocation_throttler = nullptr;
|
||||
static JfrEventThrottler* _safepoint_latency_throttler = nullptr;
|
||||
|
||||
@ -48,6 +49,9 @@ JfrEventThrottler::JfrEventThrottler(JfrEventId event_id) :
|
||||
_update(false) {}
|
||||
|
||||
bool JfrEventThrottler::create() {
|
||||
assert(_disabled_cpu_time_sample_throttler == nullptr, "invariant");
|
||||
_disabled_cpu_time_sample_throttler = new JfrEventThrottler(JfrCPUTimeSampleEvent);
|
||||
_disabled_cpu_time_sample_throttler->_disabled = true;
|
||||
assert(_object_allocation_throttler == nullptr, "invariant");
|
||||
_object_allocation_throttler = new JfrEventThrottler(JfrObjectAllocationSampleEvent);
|
||||
if (_object_allocation_throttler == nullptr || !_object_allocation_throttler->initialize()) {
|
||||
@ -59,6 +63,8 @@ bool JfrEventThrottler::create() {
|
||||
}
|
||||
|
||||
void JfrEventThrottler::destroy() {
|
||||
delete _disabled_cpu_time_sample_throttler;
|
||||
_disabled_cpu_time_sample_throttler = nullptr;
|
||||
delete _object_allocation_throttler;
|
||||
_object_allocation_throttler = nullptr;
|
||||
delete _safepoint_latency_throttler;
|
||||
@ -69,15 +75,19 @@ void JfrEventThrottler::destroy() {
|
||||
// and another for the SamplingLatency event.
|
||||
// When introducing many more throttlers, consider adding a lookup map keyed by event id.
|
||||
JfrEventThrottler* JfrEventThrottler::for_event(JfrEventId event_id) {
|
||||
assert(_disabled_cpu_time_sample_throttler != nullptr, "Disabled CPU time throttler has not been properly initialized");
|
||||
assert(_object_allocation_throttler != nullptr, "ObjectAllocation throttler has not been properly initialized");
|
||||
assert(_safepoint_latency_throttler != nullptr, "SafepointLatency throttler has not been properly initialized");
|
||||
assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent, "Event type has an unconfigured throttler");
|
||||
assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent || event_id == JfrCPUTimeSampleEvent, "Event type has an unconfigured throttler");
|
||||
if (event_id == JfrObjectAllocationSampleEvent) {
|
||||
return _object_allocation_throttler;
|
||||
}
|
||||
if (event_id == JfrSafepointLatencyEvent) {
|
||||
return _safepoint_latency_throttler;
|
||||
}
|
||||
if (event_id == JfrCPUTimeSampleEvent) {
|
||||
return _disabled_cpu_time_sample_throttler;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "jfr/jni/jfrJavaSupport.hpp"
|
||||
#include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp"
|
||||
#include "jfr/periodic/jfrThreadCPULoadEvent.hpp"
|
||||
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
|
||||
#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
|
||||
#include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp"
|
||||
#include "jfr/recorder/jfrRecorder.hpp"
|
||||
@ -78,7 +79,15 @@ JfrThreadLocal::JfrThreadLocal() :
|
||||
_enqueued_requests(false),
|
||||
_vthread(false),
|
||||
_notified(false),
|
||||
_dead(false) {
|
||||
_dead(false)
|
||||
#ifdef LINUX
|
||||
,_cpu_timer(nullptr),
|
||||
_cpu_time_jfr_locked(UNLOCKED),
|
||||
_has_cpu_time_jfr_requests(false),
|
||||
_cpu_time_jfr_queue(0),
|
||||
_do_async_processing_of_cpu_time_jfr_requests(false)
|
||||
#endif
|
||||
{
|
||||
Thread* thread = Thread::current_or_null();
|
||||
_parent_trace_id = thread != nullptr ? jvm_thread_id(thread) : (traceid)0;
|
||||
}
|
||||
@ -129,7 +138,9 @@ void JfrThreadLocal::on_start(Thread* t) {
|
||||
if (JfrRecorder::is_recording()) {
|
||||
JfrCheckpointManager::write_checkpoint(t);
|
||||
if (t->is_Java_thread()) {
|
||||
send_java_thread_start_event(JavaThread::cast(t));
|
||||
JavaThread *const jt = JavaThread::cast(t);
|
||||
JfrCPUTimeThreadSampling::on_javathread_create(jt);
|
||||
send_java_thread_start_event(jt);
|
||||
}
|
||||
}
|
||||
if (t->jfr_thread_local()->has_cached_stack_trace()) {
|
||||
@ -221,6 +232,7 @@ void JfrThreadLocal::on_exit(Thread* t) {
|
||||
if (t->is_Java_thread()) {
|
||||
JavaThread* const jt = JavaThread::cast(t);
|
||||
send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt));
|
||||
JfrCPUTimeThreadSampling::on_javathread_terminate(jt);
|
||||
JfrThreadCPULoadEvent::send_event_for_thread(jt);
|
||||
}
|
||||
release(tl, Thread::current()); // because it could be that Thread::current() != t
|
||||
@ -537,3 +549,85 @@ Arena* JfrThreadLocal::dcmd_arena(JavaThread* jt) {
|
||||
tl->_dcmd_arena = arena;
|
||||
return arena;
|
||||
}
|
||||
|
||||
|
||||
#ifdef LINUX
|
||||
|
||||
void JfrThreadLocal::set_cpu_timer(timer_t* timer) {
|
||||
if (_cpu_timer == nullptr) {
|
||||
_cpu_timer = JfrCHeapObj::new_array<timer_t>(1);
|
||||
}
|
||||
*_cpu_timer = *timer;
|
||||
}
|
||||
|
||||
void JfrThreadLocal::unset_cpu_timer() {
|
||||
if (_cpu_timer != nullptr) {
|
||||
timer_delete(*_cpu_timer);
|
||||
JfrCHeapObj::free(_cpu_timer, sizeof(timer_t));
|
||||
_cpu_timer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
timer_t* JfrThreadLocal::cpu_timer() const {
|
||||
return _cpu_timer;
|
||||
}
|
||||
|
||||
bool JfrThreadLocal::is_cpu_time_jfr_enqueue_locked() {
|
||||
return Atomic::load_acquire(&_cpu_time_jfr_locked) == ENQUEUE;
|
||||
}
|
||||
|
||||
bool JfrThreadLocal::is_cpu_time_jfr_dequeue_locked() {
|
||||
return Atomic::load_acquire(&_cpu_time_jfr_locked) == DEQUEUE;
|
||||
}
|
||||
|
||||
bool JfrThreadLocal::try_acquire_cpu_time_jfr_enqueue_lock() {
|
||||
return Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, ENQUEUE) == UNLOCKED;
|
||||
}
|
||||
|
||||
bool JfrThreadLocal::try_acquire_cpu_time_jfr_dequeue_lock() {
|
||||
CPUTimeLockState got;
|
||||
while (true) {
|
||||
CPUTimeLockState got = Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE);
|
||||
if (got == UNLOCKED) {
|
||||
return true; // successfully locked for dequeue
|
||||
}
|
||||
if (got == DEQUEUE) {
|
||||
return false; // already locked for dequeue
|
||||
}
|
||||
// else wait for the lock to be released from a signal handler
|
||||
}
|
||||
}
|
||||
|
||||
void JfrThreadLocal::acquire_cpu_time_jfr_dequeue_lock() {
|
||||
while (Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE) != UNLOCKED);
|
||||
}
|
||||
|
||||
void JfrThreadLocal::release_cpu_time_jfr_queue_lock() {
|
||||
Atomic::release_store(&_cpu_time_jfr_locked, UNLOCKED);
|
||||
}
|
||||
|
||||
void JfrThreadLocal::set_has_cpu_time_jfr_requests(bool has_requests) {
|
||||
Atomic::release_store(&_has_cpu_time_jfr_requests, has_requests);
|
||||
}
|
||||
|
||||
bool JfrThreadLocal::has_cpu_time_jfr_requests() {
|
||||
return Atomic::load_acquire(&_has_cpu_time_jfr_requests);
|
||||
}
|
||||
|
||||
JfrCPUTimeTraceQueue& JfrThreadLocal::cpu_time_jfr_queue() {
|
||||
return _cpu_time_jfr_queue;
|
||||
}
|
||||
|
||||
void JfrThreadLocal::deallocate_cpu_time_jfr_queue() {
|
||||
cpu_time_jfr_queue().resize(0);
|
||||
}
|
||||
|
||||
void JfrThreadLocal::set_do_async_processing_of_cpu_time_jfr_requests(bool wants) {
|
||||
Atomic::release_store(&_do_async_processing_of_cpu_time_jfr_requests, wants);
|
||||
}
|
||||
|
||||
bool JfrThreadLocal::wants_async_processing_of_cpu_time_jfr_requests() {
|
||||
return Atomic::load_acquire(&_do_async_processing_of_cpu_time_jfr_requests);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -33,6 +33,10 @@
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
|
||||
#ifdef LINUX
|
||||
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
|
||||
#endif
|
||||
|
||||
class Arena;
|
||||
class JavaThread;
|
||||
class JfrBuffer;
|
||||
@ -79,6 +83,22 @@ class JfrThreadLocal {
|
||||
bool _dead;
|
||||
bool _sampling_critical_section;
|
||||
|
||||
#ifdef LINUX
|
||||
timer_t* _cpu_timer;
|
||||
|
||||
enum CPUTimeLockState {
|
||||
UNLOCKED,
|
||||
// locked for enqueuing
|
||||
ENQUEUE,
|
||||
// locked for dequeuing
|
||||
DEQUEUE
|
||||
};
|
||||
volatile CPUTimeLockState _cpu_time_jfr_locked;
|
||||
volatile bool _has_cpu_time_jfr_requests;
|
||||
JfrCPUTimeTraceQueue _cpu_time_jfr_queue;
|
||||
volatile bool _do_async_processing_of_cpu_time_jfr_requests;
|
||||
#endif
|
||||
|
||||
JfrBuffer* install_native_buffer() const;
|
||||
JfrBuffer* install_java_buffer() const;
|
||||
void release(Thread* t);
|
||||
@ -342,6 +362,39 @@ class JfrThreadLocal {
|
||||
void set_thread_blob(const JfrBlobHandle& handle);
|
||||
const JfrBlobHandle& thread_blob() const;
|
||||
|
||||
// CPU time sampling
|
||||
#ifdef LINUX
|
||||
void set_cpu_timer(timer_t* timer);
|
||||
void unset_cpu_timer();
|
||||
timer_t* cpu_timer() const;
|
||||
|
||||
// The CPU time JFR lock has three different states:
|
||||
// - ENQUEUE: lock for enqueuing CPU time requests
|
||||
// - DEQUEUE: lock for dequeuing CPU time requests
|
||||
// - UNLOCKED: no lock held
|
||||
// This ensures that we can safely enqueue and dequeue CPU time requests,
|
||||
// without interleaving
|
||||
|
||||
bool is_cpu_time_jfr_enqueue_locked();
|
||||
bool is_cpu_time_jfr_dequeue_locked();
|
||||
|
||||
bool try_acquire_cpu_time_jfr_enqueue_lock();
|
||||
bool try_acquire_cpu_time_jfr_dequeue_lock();
|
||||
void acquire_cpu_time_jfr_dequeue_lock();
|
||||
void release_cpu_time_jfr_queue_lock();
|
||||
|
||||
void set_has_cpu_time_jfr_requests(bool has_events);
|
||||
bool has_cpu_time_jfr_requests();
|
||||
|
||||
JfrCPUTimeTraceQueue& cpu_time_jfr_queue();
|
||||
void deallocate_cpu_time_jfr_queue();
|
||||
|
||||
void set_do_async_processing_of_cpu_time_jfr_requests(bool wants);
|
||||
bool wants_async_processing_of_cpu_time_jfr_requests();
|
||||
#else
|
||||
bool has_cpu_time_jfr_requests() { return false; }
|
||||
#endif
|
||||
|
||||
// Hooks
|
||||
static void on_start(Thread* t);
|
||||
static void on_exit(Thread* t);
|
||||
|
@ -78,6 +78,7 @@ class JavaThread;
|
||||
// - WorkerThread
|
||||
// - WatcherThread
|
||||
// - JfrThreadSampler
|
||||
// - JfrCPUSamplerThread
|
||||
// - LogAsyncWriter
|
||||
//
|
||||
// All Thread subclasses must be either JavaThread or NonJavaThread.
|
||||
|
@ -115,6 +115,8 @@
|
||||
template(JFROldObject) \
|
||||
template(JvmtiPostObjectFree) \
|
||||
template(RendezvousGCThreads) \
|
||||
template(JFRInitializeCPUTimeSampler) \
|
||||
template(JFRTerminateCPUTimeSampler) \
|
||||
template(ReinitializeMDO)
|
||||
|
||||
class Thread;
|
||||
|
@ -236,6 +236,7 @@ class TimeInstant : public Rep<TimeSource> {
|
||||
friend class TimePartitionsTest;
|
||||
friend class GCTimerTest;
|
||||
friend class CompilerEvent;
|
||||
friend class JfrCPUSamplerThread;
|
||||
};
|
||||
|
||||
#if INCLUDE_JFR
|
||||
|
@ -51,6 +51,7 @@ import jdk.jfr.internal.settings.EnabledSetting;
|
||||
import jdk.jfr.internal.settings.LevelSetting;
|
||||
import jdk.jfr.internal.settings.MethodSetting;
|
||||
import jdk.jfr.internal.settings.PeriodSetting;
|
||||
import jdk.jfr.internal.settings.CPUThrottleSetting;
|
||||
import jdk.jfr.internal.settings.StackTraceSetting;
|
||||
import jdk.jfr.internal.settings.ThresholdSetting;
|
||||
import jdk.jfr.internal.settings.ThrottleSetting;
|
||||
@ -326,6 +327,9 @@ public final class EventControl {
|
||||
private static Control defineThrottle(PlatformEventType type) {
|
||||
String def = type.getAnnotationValue(Throttle.class, ThrottleSetting.DEFAULT_VALUE);
|
||||
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THROTTLE, Throttle.NAME, def, Collections.emptyList()));
|
||||
if (type.getName().equals("jdk.CPUTimeSample")) {
|
||||
return new Control(new CPUThrottleSetting(type), def);
|
||||
}
|
||||
return new Control(new ThrottleSetting(type, def), def);
|
||||
}
|
||||
|
||||
|
@ -270,6 +270,16 @@ public final class JVM {
|
||||
*/
|
||||
public static native void setMethodSamplingPeriod(long type, long periodMillis);
|
||||
|
||||
/**
|
||||
* Set the maximum event emission rate for the CPU time sampler
|
||||
*
|
||||
* Setting rate to 0 turns off the CPU time sampler.
|
||||
*
|
||||
* @param rate the new rate in events per second
|
||||
* @param autoAdapt true if the rate should be adapted automatically
|
||||
*/
|
||||
public static native void setCPUThrottle(double rate, boolean autoAdapt);
|
||||
|
||||
/**
|
||||
* Sets the file where data should be written.
|
||||
*
|
||||
|
@ -30,8 +30,10 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.jfr.SettingDescriptor;
|
||||
import jdk.jfr.events.ActiveSettingEvent;
|
||||
import jdk.jfr.internal.periodic.PeriodicEvents;
|
||||
import jdk.jfr.internal.util.ImplicitFields;
|
||||
import jdk.jfr.internal.util.TimespanRate;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
import jdk.jfr.internal.tracing.Modification;
|
||||
|
||||
@ -45,6 +47,7 @@ public final class PlatformEventType extends Type {
|
||||
private final boolean isJVM;
|
||||
private final boolean isJDK;
|
||||
private final boolean isMethodSampling;
|
||||
private final boolean isCPUTimeMethodSampling;
|
||||
private final List<SettingDescriptor> settings = new ArrayList<>(5);
|
||||
private final boolean dynamicSettings;
|
||||
private final int stackTraceOffset;
|
||||
@ -56,6 +59,7 @@ public final class PlatformEventType extends Type {
|
||||
private boolean stackTraceEnabled = true;
|
||||
private long thresholdTicks = 0;
|
||||
private long period = 0;
|
||||
private TimespanRate cpuRate;
|
||||
private boolean hasHook;
|
||||
|
||||
private boolean beginChunk;
|
||||
@ -75,6 +79,7 @@ public final class PlatformEventType extends Type {
|
||||
this.dynamicSettings = dynamicSettings;
|
||||
this.isJVM = Type.isDefinedByJVM(id);
|
||||
this.isMethodSampling = determineMethodSampling();
|
||||
this.isCPUTimeMethodSampling = isJVM && name.equals(Type.EVENT_NAME_PREFIX + "CPUTimeSample");
|
||||
this.isJDK = isJDK;
|
||||
this.stackTraceOffset = determineStackTraceOffset();
|
||||
}
|
||||
@ -191,6 +196,15 @@ public final class PlatformEventType extends Type {
|
||||
}
|
||||
}
|
||||
|
||||
public void setCPUThrottle(TimespanRate rate) {
|
||||
if (isCPUTimeMethodSampling) {
|
||||
this.cpuRate = rate;
|
||||
if (isEnabled()) {
|
||||
JVM.setCPUThrottle(rate.rate(), rate.autoAdapt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setHasPeriod(boolean hasPeriod) {
|
||||
this.hasPeriod = hasPeriod;
|
||||
}
|
||||
@ -251,6 +265,9 @@ public final class PlatformEventType extends Type {
|
||||
if (isMethodSampling) {
|
||||
long p = enabled ? period : 0;
|
||||
JVM.setMethodSamplingPeriod(getId(), p);
|
||||
} else if (isCPUTimeMethodSampling) {
|
||||
TimespanRate r = enabled ? cpuRate : new TimespanRate(0, false);
|
||||
JVM.setCPUThrottle(r.rate(), r.autoAdapt());
|
||||
} else {
|
||||
JVM.setEnabled(getId(), enabled);
|
||||
}
|
||||
@ -388,6 +405,10 @@ public final class PlatformEventType extends Type {
|
||||
return isMethodSampling;
|
||||
}
|
||||
|
||||
public boolean isCPUTimeMethodSampling() {
|
||||
return isCPUTimeMethodSampling;
|
||||
}
|
||||
|
||||
public void setStackFilterId(long id) {
|
||||
startFilterId = id;
|
||||
}
|
||||
|
@ -39,7 +39,8 @@ label = "Active Settings"
|
||||
table = "COLUMN 'Event Type', 'Enabled', 'Threshold',
|
||||
'Stack Trace','Period','Cutoff', 'Throttle'
|
||||
FORMAT none, missing:whitespace, missing:whitespace, missing:whitespace,
|
||||
missing:whitespace, missing:whitespace, missing:whitespace
|
||||
missing:whitespace, missing:whitespace, missing:whitespace,
|
||||
missing:whitespace
|
||||
SELECT E.id, LAST_BATCH(E.value), LAST_BATCH(T.value),
|
||||
LAST_BATCH(S.value), LAST_BATCH(P.value),
|
||||
LAST_BATCH(C.value), LAST_BATCH(U.value)
|
||||
@ -400,6 +401,28 @@ table = "COLUMN 'Method', 'Samples', 'Percent'
|
||||
SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*)
|
||||
FROM ExecutionSample GROUP BY T LIMIT 25"
|
||||
|
||||
[application.cpu-time-hot-methods]
|
||||
label = "Java Methods that Execute the Most from CPU Time Sampler"
|
||||
table = "COLUMN 'Method', 'Samples', 'Percent'
|
||||
FORMAT none, none, normalized
|
||||
SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*)
|
||||
FROM CPUTimeSample GROUP BY T LIMIT 25"
|
||||
|
||||
[application.cpu-time-statistics]
|
||||
label = "CPU Time Sample Statistics"
|
||||
form = "COLUMN 'Successful Samples', 'Failed Samples', 'Biased Samples', 'Total Samples', 'Lost Samples'
|
||||
SELECT COUNT(S.startTime), COUNT(F.startTime), COUNT(B.startTime), Count(A.startTime), SUM(L.lostSamples)
|
||||
FROM
|
||||
CPUTimeSample AS S,
|
||||
CPUTimeSample AS F,
|
||||
CPUTimeSample AS A,
|
||||
CPUTimeSample AS B,
|
||||
CPUTimeSamplesLost AS L
|
||||
WHERE
|
||||
S.failed = 'false' AND
|
||||
F.failed = 'true' AND
|
||||
B.biased = 'true'"
|
||||
|
||||
[jvm.jdk-agents]
|
||||
label = "JDK Agents"
|
||||
table = "COLUMN 'Time', 'Initialization', 'Name', 'Options'
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. All rights reserved.
|
||||
* Copyright (c) 2025 SAP SE. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.internal.settings;
|
||||
|
||||
import static jdk.jfr.internal.util.TimespanUnit.SECONDS;
|
||||
import static jdk.jfr.internal.util.TimespanUnit.MILLISECONDS;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.jfr.Description;
|
||||
import jdk.jfr.SettingControl;
|
||||
import jdk.jfr.Label;
|
||||
import jdk.jfr.MetadataDefinition;
|
||||
import jdk.jfr.Name;
|
||||
import jdk.jfr.internal.PlatformEventType;
|
||||
import jdk.jfr.internal.Type;
|
||||
import jdk.jfr.internal.util.TimespanRate;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
|
||||
@MetadataDefinition
|
||||
@Label("CPUThrottleSetting")
|
||||
@Description("Upper bounds the emission rate for CPU time samples")
|
||||
@Name(Type.SETTINGS_PREFIX + "Rate")
|
||||
public final class CPUThrottleSetting extends SettingControl {
|
||||
public static final String DEFAULT_VALUE = "0/s";
|
||||
private final PlatformEventType eventType;
|
||||
private String value = DEFAULT_VALUE;
|
||||
|
||||
public CPUThrottleSetting(PlatformEventType eventType) {
|
||||
this.eventType = Objects.requireNonNull(eventType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String combine(Set<String> values) {
|
||||
TimespanRate max = null;
|
||||
for (String value : values) {
|
||||
TimespanRate rate = TimespanRate.of(value);
|
||||
if (rate != null) {
|
||||
if (max == null || rate.isHigher(max)) {
|
||||
max = rate;
|
||||
}
|
||||
max = new TimespanRate(max.rate(), max.autoAdapt() || rate.autoAdapt());
|
||||
}
|
||||
}
|
||||
// "off" is not supported
|
||||
return Objects.requireNonNullElse(max.toString(), DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
TimespanRate rate = TimespanRate.of(value);
|
||||
if (rate != null) {
|
||||
eventType.setCPUThrottle(rate);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
@ -55,4 +55,8 @@ public record Rate(long amount, TimespanUnit unit) {
|
||||
private double inNanos() {
|
||||
return (double) amount / unit.nanos;
|
||||
}
|
||||
|
||||
public double perSecond() {
|
||||
return inNanos() * 1_000_000_000.0;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. All rights reserved.
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.util;
|
||||
|
||||
import jdk.jfr.internal.settings.CPUThrottleSetting;
|
||||
|
||||
/**
|
||||
* A rate or fixed period, see {@link jdk.jfr.internal.Rate}
|
||||
*/
|
||||
public record TimespanRate(double rate, boolean autoAdapt) {
|
||||
|
||||
public static TimespanRate of(String text) {
|
||||
if (text.equals("off")) {
|
||||
text = CPUThrottleSetting.DEFAULT_VALUE;
|
||||
}
|
||||
boolean isPeriod = !text.contains("/");
|
||||
if (isPeriod) {
|
||||
var period = ValueParser.parseTimespanWithInfinity(text, Long.MAX_VALUE);
|
||||
if (period == Long.MAX_VALUE) {
|
||||
return null;
|
||||
}
|
||||
if (period == 0) {
|
||||
return new TimespanRate(0, false);
|
||||
}
|
||||
return new TimespanRate(Runtime.getRuntime().availableProcessors() / (period / 1_000_000_000.0), false);
|
||||
}
|
||||
Rate r = Rate.of(text);
|
||||
if (r == null) {
|
||||
return null;
|
||||
}
|
||||
return new TimespanRate(r.perSecond(), true);
|
||||
}
|
||||
|
||||
public boolean isHigher(TimespanRate that) {
|
||||
return rate() > that.rate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (autoAdapt) {
|
||||
return String.format("%d/ns", (long)(rate * 1_000_000_000L));
|
||||
}
|
||||
return String.format("%dns", (long)(Runtime.getRuntime().availableProcessors() / rate * 1_000_000_000L));
|
||||
}
|
||||
}
|
@ -226,6 +226,16 @@
|
||||
<setting name="throttle">off</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.CPUTimeSample">
|
||||
<setting name="enabled" control="method-sampling-enabled">false</setting>
|
||||
<setting name="throttle">500/s</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.CPUTimeSamplesLost">
|
||||
<setting name="enabled" control="method-sampling-enabled">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.SafepointBegin">
|
||||
<setting name="enabled">true</setting>
|
||||
<setting name="threshold">10 ms</setting>
|
||||
|
@ -206,6 +206,16 @@
|
||||
<setting name="period" control="method-sampling-native-interval">20 ms</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.CPUTimeSample">
|
||||
<setting name="enabled">false</setting>
|
||||
<setting name="throttle">10ms</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.CPUTimeSamplesLost">
|
||||
<setting name="enabled">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.MethodTrace">
|
||||
<setting name="enabled">true</setting>
|
||||
<setting name="filter" control="method-trace"></setting>
|
||||
|
@ -89,7 +89,10 @@ public class TestLookForUntestedEvents {
|
||||
|
||||
// Experimental events
|
||||
private static final Set<String> experimentalEvents = Set.of(
|
||||
"Flush", "SyncOnValueBasedClass");
|
||||
"Flush", "SyncOnValueBasedClass", "CPUTimeSample", "CPUTimeSamplesLost");
|
||||
|
||||
// Subset of the experimental events that should have tests
|
||||
private static final Set<String> experimentalButTestedEvents = Set.of("CPUTimeSample");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) {
|
||||
@ -110,7 +113,9 @@ public class TestLookForUntestedEvents {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Set<String> eventsNotCoveredByTest = new HashSet<>(jfrEventTypes);
|
||||
for (String event : jfrEventTypes) {
|
||||
Set<String> checkedEvents = new HashSet<>(jfrEventTypes);
|
||||
checkedEvents.addAll(experimentalButTestedEvents);
|
||||
for (String event : checkedEvents) {
|
||||
for (Path p : paths) {
|
||||
if (findStringInFile(p, event)) {
|
||||
eventsNotCoveredByTest.remove(event);
|
||||
|
176
test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java
Normal file
176
test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 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.event.profiling;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordedFrame;
|
||||
import jdk.jfr.consumer.RecordedStackTrace;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
import jdk.test.lib.jfr.RecurseThread;
|
||||
|
||||
public class BaseTestFullStackTrace {
|
||||
private final static int MAX_DEPTH = 64; // currently hardcoded in jvm
|
||||
|
||||
private final String eventName;
|
||||
private final String threadFieldName;
|
||||
|
||||
public BaseTestFullStackTrace(String eventName, String threadFieldName) {
|
||||
this.eventName = eventName;
|
||||
this.threadFieldName = threadFieldName;
|
||||
}
|
||||
|
||||
public void run() throws Throwable {
|
||||
RecurseThread[] threads = new RecurseThread[3];
|
||||
for (int i = 0; i < threads.length; ++i) {
|
||||
int depth = MAX_DEPTH - 1 + i;
|
||||
threads[i] = new RecurseThread(depth);
|
||||
threads[i].setName("recursethread-" + depth);
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
for (RecurseThread thread : threads) {
|
||||
while (!thread.isInRunLoop()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
assertStackTraces(threads);
|
||||
|
||||
for (RecurseThread thread : threads) {
|
||||
thread.quit();
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertStackTraces(RecurseThread[] threads) throws Throwable {
|
||||
while (true) {
|
||||
try (Recording recording = new Recording()) {
|
||||
if (eventName.equals(EventNames.CPUTimeSample)) {
|
||||
recording.enable(eventName).with("throttle", "50ms");
|
||||
} else {
|
||||
recording.enable(eventName).withPeriod(Duration.ofMillis(50));
|
||||
}
|
||||
recording.start();
|
||||
Thread.sleep(500);
|
||||
recording.stop();
|
||||
if (hasValidStackTraces(recording, threads)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable {
|
||||
boolean[] isEventFound = new boolean[threads.length];
|
||||
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
System.out.println("Event: " + event);
|
||||
String threadName = Events.assertField(event, threadFieldName + ".javaName").getValue();
|
||||
long threadId = Events.assertField(event, threadFieldName + ".javaThreadId").getValue();
|
||||
|
||||
for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) {
|
||||
RecurseThread currThread = threads[threadIndex];
|
||||
if (threadId == currThread.getId()) {
|
||||
System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth);
|
||||
Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name");
|
||||
if ("recurseEnd".equals(getTopMethodName(event))) {
|
||||
isEventFound[threadIndex] = true;
|
||||
checkEvent(event, currThread.totalDepth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < threads.length; ++i) {
|
||||
String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n";
|
||||
System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]);
|
||||
}
|
||||
for (int i = 0; i < threads.length; ++i) {
|
||||
if(!isEventFound[i]) {
|
||||
// no assertion, let's retry.
|
||||
// Could be race condition, i.e safe point during Thread.sleep
|
||||
System.out.println("Failed to validate all threads, will retry.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getTopMethodName(RecordedEvent event) {
|
||||
List<RecordedFrame> frames = event.getStackTrace().getFrames();
|
||||
Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty");
|
||||
return frames.getFirst().getMethod().getName();
|
||||
}
|
||||
|
||||
private void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable {
|
||||
RecordedStackTrace stacktrace = null;
|
||||
try {
|
||||
stacktrace = event.getStackTrace();
|
||||
List<RecordedFrame> frames = stacktrace.getFrames();
|
||||
Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth);
|
||||
List<String> expectedMethods = getExpectedMethods(expectedDepth);
|
||||
Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error.");
|
||||
|
||||
for (int i = 0; i < frames.size(); ++i) {
|
||||
String name = frames.get(i).getMethod().getName();
|
||||
String expectedName = expectedMethods.get(i);
|
||||
System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName);
|
||||
Asserts.assertEquals(name, expectedName, "Wrong method name");
|
||||
}
|
||||
|
||||
boolean isTruncated = stacktrace.isTruncated();
|
||||
boolean isTruncateExpected = expectedDepth > MAX_DEPTH;
|
||||
Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected);
|
||||
|
||||
String firstMethod = frames.getLast().getMethod().getName();
|
||||
boolean isFullTrace = "run".equals(firstMethod);
|
||||
String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace);
|
||||
Asserts.assertTrue(isTruncated != isFullTrace, msg);
|
||||
} catch (Throwable t) {
|
||||
System.out.println(String.format("stacktrace:%n%s", stacktrace));
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getExpectedMethods(int depth) {
|
||||
List<String> methods = new ArrayList<>();
|
||||
methods.add("recurseEnd");
|
||||
for (int i = 0; i < depth - 2; ++i) {
|
||||
methods.add((i % 2) == 0 ? "recurseA" : "recurseB");
|
||||
}
|
||||
methods.add("run");
|
||||
if (depth > MAX_DEPTH) {
|
||||
methods = methods.subList(0, MAX_DEPTH);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.profiling;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.RecurseThread;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.hasJFR & os.family == "linux"
|
||||
* @library /test/lib
|
||||
* @modules jdk.jfr/jdk.jfr.internal
|
||||
* @run main/timeout=30 jdk.jfr.event.profiling.TestCPUTimeAndExecutionSample
|
||||
*/
|
||||
public class TestCPUTimeAndExecutionSample {
|
||||
|
||||
static String sampleEvent = EventNames.CPUTimeSample;
|
||||
|
||||
// The period is set to 1100 ms to provoke the 1000 ms
|
||||
// threshold in the JVM for os::naked_short_sleep().
|
||||
public static void main(String[] args) throws Exception {
|
||||
run(EventNames.ExecutionSample);
|
||||
run(EventNames.CPUTimeSample);
|
||||
run(EventNames.ExecutionSample);
|
||||
run(EventNames.CPUTimeSample);
|
||||
}
|
||||
|
||||
private static void run(String eventType) {
|
||||
RecurseThread t = new RecurseThread(50);
|
||||
t.setDaemon(true);
|
||||
try (RecordingStream rs = new RecordingStream()) {
|
||||
rs.enable(sampleEvent).with("throttle", "1000/s");
|
||||
rs.onEvent(sampleEvent, e -> {
|
||||
t.quit();
|
||||
rs.close();
|
||||
});
|
||||
t.start();
|
||||
rs.start();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.profiling;
|
||||
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.hasJFR & os.family == "linux"
|
||||
* @library /test/lib
|
||||
* @build jdk.jfr.event.profiling.BaseTestFullStackTrace
|
||||
* @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleFullStackTrace
|
||||
*/
|
||||
public class TestCPUTimeSampleFullStackTrace {
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
new BaseTestFullStackTrace(EventNames.CPUTimeSample, "eventThread").run();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.profiling;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.jfr.internal.JVM;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
|
||||
/*
|
||||
* Tests that creating multiple recordings after another is possible.
|
||||
* @test
|
||||
* @requires vm.hasJFR & os.family == "linux"
|
||||
* @library /test/lib
|
||||
* @modules jdk.jfr/jdk.jfr.internal
|
||||
* @run main jdk.jfr.event.profiling.TestCPUTimeSampleMultipleRecordings
|
||||
*/
|
||||
public class TestCPUTimeSampleMultipleRecordings {
|
||||
|
||||
static String nativeEvent = EventNames.CPUTimeSample;
|
||||
|
||||
static volatile boolean alive = true;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Thread t = new Thread(TestCPUTimeSampleMultipleRecordings::nativeMethod);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
try (RecordingStream rs = new RecordingStream()) {
|
||||
rs.enable(nativeEvent).with("throttle", "1ms");
|
||||
rs.onEvent(nativeEvent, e -> {
|
||||
alive = false;
|
||||
rs.close();
|
||||
});
|
||||
|
||||
rs.start();
|
||||
}
|
||||
}
|
||||
alive = false;
|
||||
}
|
||||
|
||||
public static void nativeMethod() {
|
||||
while (alive) {
|
||||
JVM.getPid();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.event.profiling;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.jfr.internal.JVM;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.hasJFR & os.family == "linux"
|
||||
* @library /test/lib
|
||||
* @modules jdk.jfr/jdk.jfr.internal
|
||||
* @run main jdk.jfr.event.profiling.TestCPUTimeSampleNative
|
||||
*/
|
||||
public class TestCPUTimeSampleNative {
|
||||
|
||||
static String nativeEvent = EventNames.CPUTimeSample;
|
||||
|
||||
static volatile boolean alive = true;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (RecordingStream rs = new RecordingStream()) {
|
||||
rs.enable(nativeEvent).with("throttle", "1ms");
|
||||
rs.onEvent(nativeEvent, e -> {
|
||||
alive = false;
|
||||
rs.close();
|
||||
});
|
||||
Thread t = new Thread(TestCPUTimeSampleNative::nativeMethod);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
rs.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void nativeMethod() {
|
||||
while (alive) {
|
||||
JVM.getPid();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.profiling;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Comparator;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.hasJFR & os.family == "linux"
|
||||
* @library /test/lib
|
||||
* @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleThrottling
|
||||
*/
|
||||
public class TestCPUTimeSampleThrottling {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testZeroPerSecond();
|
||||
testThrottleSettings();
|
||||
testThrottleSettingsPeriod();
|
||||
}
|
||||
|
||||
private static void testZeroPerSecond() throws Exception {
|
||||
Asserts.assertTrue(0L == countEvents(1000, "0/s").count());
|
||||
}
|
||||
|
||||
private static void testThrottleSettings() throws Exception {
|
||||
long count = countEvents(1000,
|
||||
Runtime.getRuntime().availableProcessors() * 2 + "/s").count();
|
||||
Asserts.assertTrue(count > 0 && count < 3,
|
||||
"Expected between 0 and 3 events, got " + count);
|
||||
}
|
||||
|
||||
private static void testThrottleSettingsPeriod() throws Exception {
|
||||
float rate = countEvents(1000, "10ms").rate();
|
||||
Asserts.assertTrue(rate > 90 && rate < 110, "Expected around 100 events per second, got " + rate);
|
||||
}
|
||||
|
||||
private record EventCount(long count, float time) {
|
||||
float rate() {
|
||||
return count / time;
|
||||
}
|
||||
}
|
||||
|
||||
private static EventCount countEvents(int timeMs, String rate) throws Exception {
|
||||
try(Recording recording = new Recording()) {
|
||||
recording.enable(EventNames.CPUTimeSample)
|
||||
.with("throttle", rate);
|
||||
|
||||
var bean = ManagementFactory.getThreadMXBean();
|
||||
|
||||
recording.start();
|
||||
|
||||
long startThreadCpuTime = bean.getCurrentThreadCpuTime();
|
||||
|
||||
wasteCPU(timeMs);
|
||||
|
||||
long spendCPUTime = bean.getCurrentThreadCpuTime() - startThreadCpuTime;
|
||||
|
||||
recording.stop();
|
||||
|
||||
long eventCount = Events.fromRecording(recording).stream()
|
||||
.filter(e -> e.getThread().getJavaName()
|
||||
.equals(Thread.currentThread().getName()))
|
||||
.count();
|
||||
|
||||
System.out.println("Event count: " + eventCount + ", CPU time: " + spendCPUTime / 1_000_000_000f + "s");
|
||||
|
||||
return new EventCount(eventCount, spendCPUTime / 1_000_000_000f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void wasteCPU(int durationMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
double i = 0;
|
||||
while (System.currentTimeMillis() - start < durationMs) {
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
i = Math.sqrt(i * Math.pow(Math.sqrt(Math.random()), Math.random()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.profiling;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.RecurseThread;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.hasJFR & os.family == "linux"
|
||||
* @library /test/lib
|
||||
* @modules jdk.jfr/jdk.jfr.internal
|
||||
* @run main jdk.jfr.event.profiling.TestCPUTimeSamplingLongPeriod
|
||||
*/
|
||||
public class TestCPUTimeSamplingLongPeriod {
|
||||
|
||||
static String sampleEvent = EventNames.CPUTimeSample;
|
||||
|
||||
// The period is set to 1100 ms to provoke the 1000 ms
|
||||
// threshold in the JVM for os::naked_short_sleep().
|
||||
public static void main(String[] args) throws Exception {
|
||||
RecurseThread t = new RecurseThread(50);
|
||||
t.setDaemon(true);
|
||||
try (RecordingStream rs = new RecordingStream()) {
|
||||
rs.enable(sampleEvent).with("throttle", "1100ms");
|
||||
rs.onEvent(sampleEvent, e -> {
|
||||
t.quit();
|
||||
rs.close();
|
||||
});
|
||||
t.start();
|
||||
rs.start();
|
||||
}
|
||||
}
|
||||
}
|
@ -23,147 +23,20 @@
|
||||
|
||||
package jdk.jfr.event.profiling;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordedFrame;
|
||||
import jdk.jfr.consumer.RecordedStackTrace;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
import jdk.test.lib.jfr.RecurseThread;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.hasJFR
|
||||
* @requires vm.opt.DeoptimizeALot != true
|
||||
* @library /test/lib
|
||||
* @build jdk.jfr.event.profiling.BaseTestFullStackTrace
|
||||
* @run main/othervm jdk.jfr.event.profiling.TestFullStackTrace
|
||||
*/
|
||||
public class TestFullStackTrace {
|
||||
private final static String EVENT_NAME = EventNames.ExecutionSample;
|
||||
private final static int MAX_DEPTH = 64; // currently hardcoded in jvm
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
RecurseThread[] threads = new RecurseThread[3];
|
||||
for (int i = 0; i < threads.length; ++i) {
|
||||
int depth = MAX_DEPTH - 1 + i;
|
||||
threads[i] = new RecurseThread(depth);
|
||||
threads[i].setName("recursethread-" + depth);
|
||||
threads[i].start();
|
||||
new BaseTestFullStackTrace(EventNames.ExecutionSample, "sampledThread").run();
|
||||
}
|
||||
|
||||
for (RecurseThread thread : threads) {
|
||||
while (!thread.isInRunLoop()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
assertStackTraces(threads);
|
||||
|
||||
for (RecurseThread thread : threads) {
|
||||
thread.quit();
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertStackTraces( RecurseThread[] threads) throws Throwable {
|
||||
Recording recording= null;
|
||||
do {
|
||||
recording = new Recording();
|
||||
recording.enable(EVENT_NAME).withPeriod(Duration.ofMillis(50));
|
||||
recording.start();
|
||||
Thread.sleep(500);
|
||||
recording.stop();
|
||||
} while (!hasValidStackTraces(recording, threads));
|
||||
}
|
||||
|
||||
private static boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable {
|
||||
boolean[] isEventFound = new boolean[threads.length];
|
||||
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
//System.out.println("Event: " + event);
|
||||
String threadName = Events.assertField(event, "sampledThread.javaName").getValue();
|
||||
long threadId = Events.assertField(event, "sampledThread.javaThreadId").getValue();
|
||||
|
||||
for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) {
|
||||
RecurseThread currThread = threads[threadIndex];
|
||||
if (threadId == currThread.getId()) {
|
||||
System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth);
|
||||
Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name");
|
||||
if ("recurseEnd".equals(getTopMethodName(event))) {
|
||||
isEventFound[threadIndex] = true;
|
||||
checkEvent(event, currThread.totalDepth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < threads.length; ++i) {
|
||||
String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n";
|
||||
System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]);
|
||||
}
|
||||
for (int i = 0; i < threads.length; ++i) {
|
||||
if(!isEventFound[i]) {
|
||||
// no assertion, let's retry.
|
||||
// Could be race condition, i.e safe point during Thread.sleep
|
||||
System.out.println("Failed to validate all threads, will retry.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String getTopMethodName(RecordedEvent event) {
|
||||
List<RecordedFrame> frames = event.getStackTrace().getFrames();
|
||||
Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty");
|
||||
return frames.getFirst().getMethod().getName();
|
||||
}
|
||||
|
||||
private static void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable {
|
||||
RecordedStackTrace stacktrace = null;
|
||||
try {
|
||||
stacktrace = event.getStackTrace();
|
||||
List<RecordedFrame> frames = stacktrace.getFrames();
|
||||
Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth);
|
||||
List<String> expectedMethods = getExpectedMethods(expectedDepth);
|
||||
Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error.");
|
||||
|
||||
for (int i = 0; i < frames.size(); ++i) {
|
||||
String name = frames.get(i).getMethod().getName();
|
||||
String expectedName = expectedMethods.get(i);
|
||||
System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName);
|
||||
Asserts.assertEquals(name, expectedName, "Wrong method name");
|
||||
}
|
||||
|
||||
boolean isTruncated = stacktrace.isTruncated();
|
||||
boolean isTruncateExpected = expectedDepth > MAX_DEPTH;
|
||||
Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected);
|
||||
|
||||
String firstMethod = frames.getLast().getMethod().getName();
|
||||
boolean isFullTrace = "run".equals(firstMethod);
|
||||
String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace);
|
||||
Asserts.assertTrue(isTruncated != isFullTrace, msg);
|
||||
} catch (Throwable t) {
|
||||
System.out.println(String.format("stacktrace:%n%s", stacktrace));
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> getExpectedMethods(int depth) {
|
||||
List<String> methods = new ArrayList<>();
|
||||
methods.add("recurseEnd");
|
||||
for (int i = 0; i < depth - 2; ++i) {
|
||||
methods.add((i % 2) == 0 ? "recurseA" : "recurseB");
|
||||
}
|
||||
methods.add("run");
|
||||
if (depth > MAX_DEPTH) {
|
||||
methods = methods.subList(0, MAX_DEPTH);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SAP SE. 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 test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/*
|
||||
* A class used to create a simple deep call stack for testing purposes
|
||||
*/
|
||||
public class RecursiveMethods {
|
||||
|
||||
/** Method that uses recursion to produce a call stack of at least {@code depth} depth */
|
||||
public static int entry(int depth) {
|
||||
return method2(--depth);
|
||||
}
|
||||
|
||||
private static int method2(int depth) {
|
||||
return method3(--depth);
|
||||
}
|
||||
|
||||
private static int method3(int depth) {
|
||||
return method4(--depth);
|
||||
}
|
||||
|
||||
private static int method4(int depth) {
|
||||
return method5(--depth);
|
||||
}
|
||||
|
||||
private static int method5(int depth) {
|
||||
return method6(--depth);
|
||||
}
|
||||
|
||||
private static int method6(int depth) {
|
||||
return method7(--depth);
|
||||
}
|
||||
|
||||
private static int method7(int depth) {
|
||||
return method8(--depth);
|
||||
}
|
||||
|
||||
private static int method8(int depth) {
|
||||
return method9(--depth);
|
||||
}
|
||||
|
||||
private static int method9(int depth) {
|
||||
return method10(--depth);
|
||||
}
|
||||
|
||||
private static int method10(int depth) {
|
||||
if (depth > 0) {
|
||||
return entry(--depth);
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
}
|
@ -77,6 +77,8 @@ public class EventNames {
|
||||
public static final String ThreadAllocationStatistics = PREFIX + "ThreadAllocationStatistics";
|
||||
public static final String ExecutionSample = PREFIX + "ExecutionSample";
|
||||
public static final String NativeMethodSample = PREFIX + "NativeMethodSample";
|
||||
public static final String CPUTimeSample = PREFIX + "CPUTimeSample";
|
||||
public static final String CPUTimeSamplesLost = PREFIX + "CPUTimeSamplesLost";
|
||||
public static final String ThreadDump = PREFIX + "ThreadDump";
|
||||
public static final String OldObjectSample = PREFIX + "OldObjectSample";
|
||||
public static final String SymbolTableStatistics = PREFIX + "SymbolTableStatistics";
|
||||
|
Loading…
x
Reference in New Issue
Block a user