8358619: Fix interval recomputation in CPU Time Profiler

Reviewed-by: jbachorik, mgronlun
This commit is contained in:
Johannes Bechberger 2025-07-15 10:58:02 +00:00
parent 9697e5bf74
commit c70258ca1c
10 changed files with 198 additions and 87 deletions

View File

@ -170,9 +170,15 @@ 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))
JVM_ENTRY_NO_ENV(void, jfr_set_cpu_rate(JNIEnv* env, jclass jvm, jdouble rate))
JfrEventSetting::set_enabled(JfrCPUTimeSampleEvent, rate > 0);
JfrCPUTimeThreadSampling::set_rate(rate, auto_adapt == JNI_TRUE);
JfrCPUTimeThreadSampling::set_rate(rate);
JVM_END
JVM_ENTRY_NO_ENV(void, jfr_set_cpu_period(JNIEnv* env, jclass jvm, jlong period_nanos))
assert(period_nanos >= 0, "invariant");
JfrEventSetting::set_enabled(JfrCPUTimeSampleEvent, period_nanos > 0);
JfrCPUTimeThreadSampling::set_period(period_nanos);
JVM_END
NO_TRANSITION(void, jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong event_type_id, jlong value))

View File

@ -129,7 +129,9 @@ 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_cpu_rate(JNIEnv* env, jclass jvm, jdouble rate);
void JNICALL jfr_set_cpu_period(JNIEnv* env, jclass jvm, jlong period_nanos);
void JNICALL jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong id, jlong value);

View File

@ -83,7 +83,8 @@ 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*)"setCPURate", (char*)"(D)V", (void*)jfr_set_cpu_rate,
(char*)"setCPUPeriod", (char*)"(J)V", (void*)jfr_set_cpu_period,
(char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples,
(char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk,
(char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread,

View File

@ -45,7 +45,7 @@
#include "signals_posix.hpp"
static const int64_t AUTOADAPT_INTERVAL_MS = 100;
static const int64_t RECOMPUTE_INTERVAL_MS = 100;
static bool is_excluded(JavaThread* jt) {
return jt->is_hidden_from_external_view() ||
@ -163,20 +163,42 @@ void JfrCPUTimeTraceQueue::clear() {
Atomic::release_store(&_head, (u4)0);
}
static int64_t compute_sampling_period(double rate) {
if (rate == 0) {
return 0;
// A throttle is either a rate or a fixed period
class JfrCPUSamplerThrottle {
union {
double _rate;
u8 _period_nanos;
};
bool _is_rate;
public:
JfrCPUSamplerThrottle(double rate) : _rate(rate), _is_rate(true) {
assert(rate >= 0, "invariant");
}
return os::active_processor_count() * 1000000000.0 / rate;
}
JfrCPUSamplerThrottle(u8 period_nanos) : _period_nanos(period_nanos), _is_rate(false) {}
bool enabled() const { return _is_rate ? _rate > 0 : _period_nanos > 0; }
int64_t compute_sampling_period() const {
if (_is_rate) {
if (_rate == 0) {
return 0;
}
return os::active_processor_count() * 1000000000.0 / _rate;
}
return _period_nanos;
}
};
class JfrCPUSamplerThread : public NonJavaThread {
friend class JfrCPUTimeThreadSampling;
private:
Semaphore _sample;
NonJavaThread* _sampler_thread;
double _rate;
bool _auto_adapt;
JfrCPUSamplerThrottle _throttle;
volatile int64_t _current_sampling_period_ns;
volatile bool _disenrolled;
// top bit is used to indicate that no signal handler should proceed
@ -187,7 +209,7 @@ class JfrCPUSamplerThread : public NonJavaThread {
static const u4 STOP_SIGNAL_BIT = 0x80000000;
JfrCPUSamplerThread(double rate, bool auto_adapt);
JfrCPUSamplerThread(JfrCPUSamplerThrottle& throttle);
void start_thread();
@ -195,9 +217,9 @@ class JfrCPUSamplerThread : public NonJavaThread {
void disenroll();
void update_all_thread_timers();
void auto_adapt_period_if_needed();
void recompute_period_if_needed();
void set_rate(double rate, bool auto_adapt);
void set_throttle(JfrCPUSamplerThrottle& throttle);
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);
@ -231,18 +253,16 @@ public:
void trigger_async_processing_of_cpu_time_jfr_requests();
};
JfrCPUSamplerThread::JfrCPUSamplerThread(double rate, bool auto_adapt) :
JfrCPUSamplerThread::JfrCPUSamplerThread(JfrCPUSamplerThrottle& throttle) :
_sample(),
_sampler_thread(nullptr),
_rate(rate),
_auto_adapt(auto_adapt),
_current_sampling_period_ns(compute_sampling_period(rate)),
_throttle(throttle),
_current_sampling_period_ns(throttle.compute_sampling_period()),
_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() {
@ -321,7 +341,7 @@ void JfrCPUSamplerThread::disenroll() {
void JfrCPUSamplerThread::run() {
assert(_sampler_thread == nullptr, "invariant");
_sampler_thread = this;
int64_t last_auto_adapt_check = os::javaTimeNanos();
int64_t last_recompute_check = os::javaTimeNanos();
while (true) {
if (!_sample.trywait()) {
// disenrolled
@ -329,9 +349,9 @@ void JfrCPUSamplerThread::run() {
}
_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 (os::javaTimeNanos() - last_recompute_check > RECOMPUTE_INTERVAL_MS * 1000000) {
recompute_period_if_needed();
last_recompute_check = os::javaTimeNanos();
}
if (Atomic::cmpxchg(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true, false)) {
@ -442,42 +462,50 @@ JfrCPUTimeThreadSampling::~JfrCPUTimeThreadSampling() {
}
}
void JfrCPUTimeThreadSampling::create_sampler(double rate, bool auto_adapt) {
void JfrCPUTimeThreadSampling::create_sampler(JfrCPUSamplerThrottle& throttle) {
assert(_sampler == nullptr, "invariant");
_sampler = new JfrCPUSamplerThread(rate, auto_adapt);
_sampler = new JfrCPUSamplerThread(throttle);
_sampler->start_thread();
_sampler->enroll();
}
void JfrCPUTimeThreadSampling::update_run_state(double rate, bool auto_adapt) {
if (rate != 0) {
void JfrCPUTimeThreadSampling::update_run_state(JfrCPUSamplerThrottle& throttle) {
if (throttle.enabled()) {
if (_sampler == nullptr) {
create_sampler(rate, auto_adapt);
create_sampler(throttle);
} else {
_sampler->set_rate(rate, auto_adapt);
_sampler->set_throttle(throttle);
_sampler->enroll();
}
return;
}
if (_sampler != nullptr) {
_sampler->set_rate(rate /* 0 */, auto_adapt);
_sampler->set_throttle(throttle);
_sampler->disenroll();
}
}
void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) {
assert(rate >= 0, "invariant");
void JfrCPUTimeThreadSampling::set_rate(double rate) {
if (_instance == nullptr) {
return;
}
instance().set_rate_value(rate, auto_adapt);
JfrCPUSamplerThrottle throttle(rate);
instance().set_throttle_value(throttle);
}
void JfrCPUTimeThreadSampling::set_rate_value(double rate, bool auto_adapt) {
if (_sampler != nullptr) {
_sampler->set_rate(rate, auto_adapt);
void JfrCPUTimeThreadSampling::set_period(u8 nanos) {
if (_instance == nullptr) {
return;
}
update_run_state(rate, auto_adapt);
JfrCPUSamplerThrottle throttle(nanos);
instance().set_throttle_value(throttle);
}
void JfrCPUTimeThreadSampling::set_throttle_value(JfrCPUSamplerThrottle& throttle) {
if (_sampler != nullptr) {
_sampler->set_throttle(throttle);
}
update_run_state(throttle);
}
void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread *thread) {
@ -704,24 +732,21 @@ void JfrCPUSamplerThread::stop_timer() {
VMThread::execute(&op);
}
void JfrCPUSamplerThread::auto_adapt_period_if_needed() {
void JfrCPUSamplerThread::recompute_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();
}
int64_t period = _throttle.compute_sampling_period();
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();
void JfrCPUSamplerThread::set_throttle(JfrCPUSamplerThrottle& throttle) {
_throttle = throttle;
if (_throttle.enabled() && Atomic::load_acquire(&_disenrolled) == false) {
recompute_period_if_needed();
} else {
Atomic::store(&_current_sampling_period_ns, compute_sampling_period(rate));
Atomic::store(&_current_sampling_period_ns, _throttle.compute_sampling_period());
}
}
@ -765,12 +790,18 @@ void JfrCPUTimeThreadSampling::destroy() {
_instance = nullptr;
}
void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) {
void JfrCPUTimeThreadSampling::set_rate(double rate) {
if (rate != 0) {
warn();
}
}
void JfrCPUTimeThreadSampling::set_period(u8 period_nanos) {
if (period_nanos != 0) {
warn();
}
}
void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread* thread) {
}

View File

@ -95,14 +95,16 @@ public:
class JfrCPUSamplerThread;
class JfrCPUSamplerThrottle;
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);
void create_sampler(JfrCPUSamplerThrottle& throttle);
void set_throttle_value(JfrCPUSamplerThrottle& throttle);
JfrCPUTimeThreadSampling();
~JfrCPUTimeThreadSampling();
@ -111,10 +113,13 @@ class JfrCPUTimeThreadSampling : public JfrCHeapObj {
static JfrCPUTimeThreadSampling* create();
static void destroy();
void update_run_state(double rate, bool auto_adapt);
void update_run_state(JfrCPUSamplerThrottle& throttle);
static void set_rate(JfrCPUSamplerThrottle& throttle);
public:
static void set_rate(double rate, bool auto_adapt);
static void set_rate(double rate);
static void set_period(u8 nanos);
static void on_javathread_create(JavaThread* thread);
static void on_javathread_terminate(JavaThread* thread);
@ -140,7 +145,8 @@ private:
static void destroy();
public:
static void set_rate(double rate, bool auto_adapt);
static void set_rate(double rate);
static void set_period(u8 nanos);
static void on_javathread_create(JavaThread* thread);
static void on_javathread_terminate(JavaThread* thread);

View File

@ -273,12 +273,24 @@ public final class JVM {
/**
* Set the maximum event emission rate for the CPU time sampler
*
* Use {@link #setCPUPeriod(long)} if you want a fixed sampling period instead.
*
* 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);
public static native void setCPURate(double rate);
/**
* Set the fixed CPU time sampler period.
*
* Use {@link #setCPURate(double)} if you want a fixed rate with an auto-adjusted period instead.
*
* Setting period to 0 turns off the CPU time sampler.
*
* @param periodNanos the new fixed period in nanoseconds
*/
public static native void setCPUPeriod(long periodNanos);
/**
* Sets the file where data should be written.

View File

@ -204,7 +204,11 @@ public final class PlatformEventType extends Type {
if (isCPUTimeMethodSampling) {
this.cpuRate = rate;
if (isEnabled()) {
JVM.setCPUThrottle(rate.rate(), rate.autoAdapt());
if (rate.isRate()) {
JVM.setCPURate(rate.rate());
} else {
JVM.setCPUPeriod(rate.periodNanos());
}
}
}
}
@ -270,8 +274,12 @@ public final class PlatformEventType extends Type {
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());
TimespanRate r = enabled ? cpuRate : TimespanRate.OFF;
if (r.isRate()) {
JVM.setCPURate(r.rate());
} else {
JVM.setCPUPeriod(r.periodNanos());
}
} else {
JVM.setEnabled(getId(), enabled);
}

View File

@ -58,18 +58,18 @@ public final class CPUThrottleSetting extends SettingControl {
@Override
public String combine(Set<String> values) {
TimespanRate max = null;
TimespanRate highestRate = null;
for (String value : values) {
TimespanRate rate = TimespanRate.of(value);
if (rate != null) {
if (max == null || rate.isHigher(max)) {
max = rate;
if (highestRate == null) {
highestRate = rate;
} else {
highestRate = TimespanRate.selectHigherResolution(highestRate, rate);
}
max = new TimespanRate(max.rate(), max.autoAdapt() || rate.autoAdapt());
}
}
// "off" is not supported
return Objects.requireNonNullElse(max.toString(), DEFAULT_VALUE);
return Objects.requireNonNullElse(highestRate.toString(), DEFAULT_VALUE);
}
@Override

View File

@ -30,11 +30,22 @@ 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 record TimespanRate(double rate, long periodNanos, boolean isRate) {
public static final TimespanRate OFF = new TimespanRate(0, 0, false);
/**
* Parses the rate string. Supports
*
* <ul>
* <li>off</li>
* <li>time value like "1ms"</li>
* <li>rate value like "10/s"</li>
* </ul>
*/
public static TimespanRate of(String text) {
if (text.equals("off")) {
text = CPUThrottleSetting.DEFAULT_VALUE;
return OFF;
}
boolean isPeriod = !text.contains("/");
if (isPeriod) {
@ -43,26 +54,62 @@ public record TimespanRate(double rate, boolean autoAdapt) {
return null;
}
if (period == 0) {
return new TimespanRate(0, false);
return OFF;
}
return new TimespanRate(Runtime.getRuntime().availableProcessors() / (period / 1_000_000_000.0), false);
return new TimespanRate(0, period, false);
}
Rate r = Rate.of(text);
if (r == null) {
return null;
}
return new TimespanRate(r.perSecond(), true);
return new TimespanRate(r.perSecond(), 0, true);
}
public boolean isHigher(TimespanRate that) {
return rate() > that.rate();
public static TimespanRate selectHigherResolution(TimespanRate a, TimespanRate b) {
if (a.isRate && b.isRate) {
return a.rate() > b.rate() ? a : b;
}
if (!a.isRate && !b.isRate) {
return a.periodNanos() < b.periodNanos() ? a : b;
}
if (a.isRate) {
double bRate = Runtime.getRuntime().availableProcessors() * (1_000_000_000.0 / b.periodNanos());
return new TimespanRate(Math.max(a.rate(), bRate), 0, true);
}
double aRate = Runtime.getRuntime().availableProcessors() * (1_000_000_000.0 / a.periodNanos());
return new TimespanRate(Math.max(aRate, b.rate()), 0, true);
}
@Override
public String toString() {
if (autoAdapt) {
return String.format("%d/ns", (long)(rate * 1_000_000_000L));
if (isRate) {
return toRateString();
}
return String.format("%dns", (long)(Runtime.getRuntime().availableProcessors() / rate * 1_000_000_000L));
return toPeriodString();
}
private String toRateString() {
// idea: try to use the smallest unit possible where the rate is still an integer
// start with seconds, then try minutes, hours, etc.
assert isRate;
if (rate == 0) {
return "0/s";
}
for (TimespanUnit unit : TimespanUnit.values()) {
double value = rate / unit.nanos * 1_000_000_000.0;
if (value % 1 == 0) {
return String.format("%d/%s", (long)value, unit.text);
}
}
// fallback to days if no smaller unit is found
return String.format("%d/%s", (long)(rate / TimespanUnit.DAYS.nanos * 1_000_000_000.0), TimespanUnit.DAYS.text);
}
private String toPeriodString() {
assert !isRate;
if (periodNanos == 0) {
return "0ms";
}
return String.format("%dns", periodNanos);
}
}

View File

@ -38,23 +38,21 @@ import jdk.test.lib.jfr.RecurseThread;
*/
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);
run(EventNames.CPUTimeSample, "throttle", "1000/s");
run(EventNames.ExecutionSample, "period", "1100ms");
run(EventNames.CPUTimeSample, "throttle", "1100ms");
run(EventNames.ExecutionSample, "period", "1000ms");
}
private static void run(String eventType) {
private static void run(String eventType, String attribute, String value) {
RecurseThread t = new RecurseThread(50);
t.setDaemon(true);
try (RecordingStream rs = new RecordingStream()) {
rs.enable(sampleEvent).with("throttle", "1000/s");
rs.onEvent(sampleEvent, e -> {
rs.enable(eventType).with(attribute, value);
rs.onEvent(eventType, e -> {
t.quit();
rs.close();
});