diff --git a/src/hotspot/os/posix/signals_posix.cpp b/src/hotspot/os/posix/signals_posix.cpp
index e900d5695ae..0157d354f40 100644
--- a/src/hotspot/os/posix/signals_posix.cpp
+++ b/src/hotspot/os/posix/signals_posix.cpp
@@ -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);
diff --git a/src/hotspot/os/posix/signals_posix.hpp b/src/hotspot/os/posix/signals_posix.hpp
index 9deade4db18..c1cb70df153 100644
--- a/src/hotspot/os/posix/signals_posix.hpp
+++ b/src/hotspot/os/posix/signals_posix.hpp
@@ -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);
diff --git a/src/hotspot/share/jfr/jfr.inline.hpp b/src/hotspot/share/jfr/jfr.inline.hpp
index bdb47f600e6..5b6fc62d0ea 100644
--- a/src/hotspot/share/jfr/jfr.inline.hpp
+++ b/src/hotspot/share/jfr/jfr.inline.hpp
@@ -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) {
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
index 6f1c1936574..bc2412a90c1 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
@@ -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;
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
index 9c78c6239d4..dbea7f0180d 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
@@ -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);
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
index 33a564dee2f..82ef93d95b2 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
@@ -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,
diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml
index 9c04ec3dca1..03daca946f6 100644
--- a/src/hotspot/share/jfr/metadata/metadata.xml
+++ b/src/hotspot/share/jfr/metadata/metadata.xml
@@ -962,6 +962,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp
new file mode 100644
index 00000000000..2f063123a3d
--- /dev/null
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp
@@ -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(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(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)
diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp
new file mode 100644
index 00000000000..7c0545f4772
--- /dev/null
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp
@@ -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
diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp
index f8e63e2e344..7049df0198b 100644
--- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp
@@ -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(&request._sample_sp), &fp);
+ assert(sp_in_stack(request, jt), "invariant");
+ if (!build(request, fp, jt)) {
+ set_cpu_time_biased_sample(request, jt);
+ }
+ }
+}
diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp
index 6567e7f8bff..20e737e0cbf 100644
--- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp
@@ -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
diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp
index aa72c29cf50..ddc9d59b295 100644
--- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp
@@ -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);
}
+
diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp
index d4c4bc0d873..3c4d1a126b0 100644
--- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp
@@ -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);
};
diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp
index 384305862ca..dd75cb2929f 100644
--- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp
+++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp
@@ -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();
}
diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp
index b917904c5fb..3099c8ad344 100644
--- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp
+++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp
@@ -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();
diff --git a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
index 0befaae7751..f660a01c04c 100644
--- a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
+++ b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
@@ -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;
}
diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
index 503aa85e02f..4b805a98a32 100644
--- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
+++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
@@ -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(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
diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp
index 8e545d9c429..715a2c44f93 100644
--- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp
+++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp
@@ -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);
diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp
index f3a06d5efd2..772ef7bbe82 100644
--- a/src/hotspot/share/runtime/thread.hpp
+++ b/src/hotspot/share/runtime/thread.hpp
@@ -78,6 +78,7 @@ class JavaThread;
// - WorkerThread
// - WatcherThread
// - JfrThreadSampler
+// - JfrCPUSamplerThread
// - LogAsyncWriter
//
// All Thread subclasses must be either JavaThread or NonJavaThread.
diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp
index 50d85944485..ac5d37381f9 100644
--- a/src/hotspot/share/runtime/vmOperation.hpp
+++ b/src/hotspot/share/runtime/vmOperation.hpp
@@ -115,6 +115,8 @@
template(JFROldObject) \
template(JvmtiPostObjectFree) \
template(RendezvousGCThreads) \
+ template(JFRInitializeCPUTimeSampler) \
+ template(JFRTerminateCPUTimeSampler) \
template(ReinitializeMDO)
class Thread;
diff --git a/src/hotspot/share/utilities/ticks.hpp b/src/hotspot/share/utilities/ticks.hpp
index 8d2bbc7ce1f..88dc9a787f9 100644
--- a/src/hotspot/share/utilities/ticks.hpp
+++ b/src/hotspot/share/utilities/ticks.hpp
@@ -236,6 +236,7 @@ class TimeInstant : public Rep {
friend class TimePartitionsTest;
friend class GCTimerTest;
friend class CompilerEvent;
+ friend class JfrCPUSamplerThread;
};
#if INCLUDE_JFR
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
index f43e45b724e..2ea4725abc8 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
@@ -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);
}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
index e0eaef74b4c..b91f0c337b2 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
@@ -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.
*
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
index 32b59bca4c0..b65a26f3aad 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
@@ -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 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;
}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini
index a6192db1ab0..a2ac74142f4 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini
@@ -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'
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java
new file mode 100644
index 00000000000..c18aeef2132
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java
@@ -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 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;
+ }
+}
+
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java
index f32436a5e0f..2632cd63848 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java
@@ -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;
+ }
}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java
new file mode 100644
index 00000000000..5d671310e3c
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java
@@ -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));
+ }
+}
diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc
index 293af26746f..541d1d3aa2f 100644
--- a/src/jdk.jfr/share/conf/jfr/default.jfc
+++ b/src/jdk.jfr/share/conf/jfr/default.jfc
@@ -226,6 +226,16 @@
off
+
+ false
+ 500/s
+ true
+
+
+
+ true
+
+
true
10 ms
diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc
index 89a9022d11e..9cec2d9a70f 100644
--- a/src/jdk.jfr/share/conf/jfr/profile.jfc
+++ b/src/jdk.jfr/share/conf/jfr/profile.jfc
@@ -206,6 +206,16 @@
20 ms
+
+ false
+ 10ms
+ true
+
+
+
+ true
+
+
true
diff --git a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java
index 5b8aacfb1d2..d6e126a493f 100644
--- a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java
+++ b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java
@@ -89,7 +89,10 @@ public class TestLookForUntestedEvents {
// Experimental events
private static final Set experimentalEvents = Set.of(
- "Flush", "SyncOnValueBasedClass");
+ "Flush", "SyncOnValueBasedClass", "CPUTimeSample", "CPUTimeSamplesLost");
+
+ // Subset of the experimental events that should have tests
+ private static final Set 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 eventsNotCoveredByTest = new HashSet<>(jfrEventTypes);
- for (String event : jfrEventTypes) {
+ Set checkedEvents = new HashSet<>(jfrEventTypes);
+ checkedEvents.addAll(experimentalButTestedEvents);
+ for (String event : checkedEvents) {
for (Path p : paths) {
if (findStringInFile(p, event)) {
eventsNotCoveredByTest.remove(event);
diff --git a/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java
new file mode 100644
index 00000000000..de211b8c454
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java
@@ -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 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 frames = stacktrace.getFrames();
+ Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth);
+ List 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 getExpectedMethods(int depth) {
+ List 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;
+ }
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java
new file mode 100644
index 00000000000..eb8d33832b5
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java
@@ -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();
+ }
+ }
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java
new file mode 100644
index 00000000000..0d1108346ab
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java
@@ -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();
+ }
+
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java
new file mode 100644
index 00000000000..133df36684c
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java
@@ -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();
+ }
+ }
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java
new file mode 100644
index 00000000000..1617bce4ba3
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java
@@ -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();
+ }
+ }
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java
new file mode 100644
index 00000000000..55b350ad096
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java
@@ -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()));
+ }
+ }
+ }
+
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java
new file mode 100644
index 00000000000..69d32d48282
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java
@@ -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();
+ }
+ }
+}
diff --git a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java
index b06f9eed03d..c337a8128ab 100644
--- a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java
+++ b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java
@@ -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 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 frames = stacktrace.getFrames();
- Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth);
- List 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 getExpectedMethods(int depth) {
- List 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;
- }
}
diff --git a/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java
new file mode 100644
index 00000000000..8dc77fd38da
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java
@@ -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;
+ }
+}
diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java
index 904abe8e3e2..a00898358a8 100644
--- a/test/lib/jdk/test/lib/jfr/EventNames.java
+++ b/test/lib/jdk/test/lib/jfr/EventNames.java
@@ -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";