8342818: Implement JEP 509: JFR CPU-Time Profiling

Reviewed-by: mgronlun, mdoerr, pchilanomate, apangin, shade
This commit is contained in:
Johannes Bechberger 2025-06-04 22:08:58 +00:00
parent 3cf3e4bbec
commit 5b27e9c2df
41 changed files with 2178 additions and 140 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,769 @@
/*
* 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) :
_capacity(capacity), _head(0), _lost_samples(0) {
_data = JfrCHeapObj::new_array<JfrCPUTimeSampleRequest>(capacity);
}
JfrCPUTimeTraceQueue::~JfrCPUTimeTraceQueue() {
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;
JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest));
_data = JfrCHeapObj::new_array<JfrCPUTimeSampleRequest>(capacity);
_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;
}
timer_delete(*timer);
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,6 +78,7 @@ class JavaThread;
// - WorkerThread
// - WatcherThread
// - JfrThreadSampler
// - JfrCPUSamplerThread
// - LogAsyncWriter
//
// All Thread subclasses must be either JavaThread or NonJavaThread.

View File

@ -115,6 +115,8 @@
template(JFROldObject) \
template(JvmtiPostObjectFree) \
template(RendezvousGCThreads) \
template(JFRInitializeCPUTimeSampler) \
template(JFRTerminateCPUTimeSampler) \
template(ReinitializeMDO)
class Thread;

View File

@ -236,6 +236,7 @@ class TimeInstant : public Rep<TimeSource> {
friend class TimePartitionsTest;
friend class GCTimerTest;
friend class CompilerEvent;
friend class JfrCPUSamplerThread;
};
#if INCLUDE_JFR

View File

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

View File

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

View File

@ -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,13 @@ public final class PlatformEventType extends Type {
}
}
public void setCPUThrottle(TimespanRate rate) {
if (isCPUTimeMethodSampling) {
this.cpuRate = rate;
JVM.setCPUThrottle(rate.rate(), rate.autoAdapt());
}
}
public void setHasPeriod(boolean hasPeriod) {
this.hasPeriod = hasPeriod;
}
@ -251,6 +263,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 +403,10 @@ public final class PlatformEventType extends Type {
return isMethodSampling;
}
public boolean isCPUTimeMethodSampling() {
return isCPUTimeMethodSampling;
}
public void setStackFilterId(long id) {
startFilterId = id;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
for (RecurseThread thread : threads) {
while (!thread.isInRunLoop()) {
Thread.sleep(20);
}
}
assertStackTraces(threads);
for (RecurseThread thread : threads) {
thread.quit();
thread.join();
}
new BaseTestFullStackTrace(EventNames.ExecutionSample, "sampledThread").run();
}
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;
}
}

View File

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

View File

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