8257602: Introduce JFR Event Throttling and new jdk.ObjectAllocationSample event (enabled by default)
Co-authored-by: Jaroslav Bachorik <jbachorik@openjdk.org> Reviewed-by: egahlin, jbachorik
This commit is contained in:
parent
026b09cf64
commit
502a5241e5
@ -172,6 +172,7 @@ public class GenerateJfrFiles {
|
||||
boolean startTime;
|
||||
String period = "";
|
||||
boolean cutoff;
|
||||
boolean throttle;
|
||||
boolean experimental;
|
||||
long id;
|
||||
boolean isEvent;
|
||||
@ -194,6 +195,7 @@ public class GenerateJfrFiles {
|
||||
pos.writeBoolean(startTime);
|
||||
pos.writeUTF(period);
|
||||
pos.writeBoolean(cutoff);
|
||||
pos.writeBoolean(throttle);
|
||||
pos.writeBoolean(experimental);
|
||||
pos.writeLong(id);
|
||||
pos.writeBoolean(isEvent);
|
||||
@ -490,6 +492,7 @@ public class GenerateJfrFiles {
|
||||
currentType.startTime = getBoolean(attributes, "startTime", true);
|
||||
currentType.period = getString(attributes, "period");
|
||||
currentType.cutoff = getBoolean(attributes, "cutoff", false);
|
||||
currentType.throttle = getBoolean(attributes, "throttle", false);
|
||||
currentType.commitState = getString(attributes, "commitState");
|
||||
currentType.isEvent = "Event".equals(qName);
|
||||
currentType.isRelation = "Relation".equals(qName);
|
||||
@ -759,6 +762,7 @@ public class GenerateJfrFiles {
|
||||
out.write(" void set_starttime(const Ticks&) const {}");
|
||||
out.write(" void set_endtime(const Ticks&) const {}");
|
||||
out.write(" bool should_commit() const { return false; }");
|
||||
out.write(" bool is_started() const { return false; }");
|
||||
out.write(" static bool is_enabled() { return false; }");
|
||||
out.write(" void commit() {}");
|
||||
out.write("};");
|
||||
@ -820,6 +824,7 @@ public class GenerateJfrFiles {
|
||||
out.write(" static const bool hasStackTrace = " + event.stackTrace + ";");
|
||||
out.write(" static const bool isInstant = " + !event.startTime + ";");
|
||||
out.write(" static const bool hasCutoff = " + event.cutoff + ";");
|
||||
out.write(" static const bool hasThrottle = " + event.throttle + ";");
|
||||
out.write(" static const bool isRequestable = " + !event.period.isEmpty() + ";");
|
||||
out.write(" static const JfrEventId eventId = Jfr" + event.name + "Event;");
|
||||
out.write("");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2020, 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
|
||||
@ -24,14 +24,86 @@
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "gc/shared/allocTracer.hpp"
|
||||
#include "gc/shared/threadLocalAllocBuffer.inline.hpp"
|
||||
#include "jfr/jfrEvents.hpp"
|
||||
#include "runtime/handles.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
#if INCLUDE_JFR
|
||||
#include "jfr/support/jfrAllocationTracer.hpp"
|
||||
#endif
|
||||
|
||||
static THREAD_LOCAL int64_t _last_allocated_bytes = 0;
|
||||
|
||||
inline void send_allocation_sample(const Klass* klass, int64_t allocated_bytes) {
|
||||
assert(allocated_bytes > 0, "invariant");
|
||||
EventObjectAllocationSample event;
|
||||
if (event.should_commit()) {
|
||||
const size_t weight = allocated_bytes - _last_allocated_bytes;
|
||||
assert(weight > 0, "invariant");
|
||||
event.set_objectClass(klass);
|
||||
event.set_weight(weight);
|
||||
event.commit();
|
||||
_last_allocated_bytes = allocated_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool send_allocation_sample_with_result(const Klass* klass, int64_t allocated_bytes) {
|
||||
assert(allocated_bytes > 0, "invariant");
|
||||
EventObjectAllocationSample event;
|
||||
if (event.should_commit()) {
|
||||
const size_t weight = allocated_bytes - _last_allocated_bytes;
|
||||
assert(weight > 0, "invariant");
|
||||
event.set_objectClass(klass);
|
||||
event.set_weight(weight);
|
||||
event.commit();
|
||||
_last_allocated_bytes = allocated_bytes;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline intptr_t estimate_tlab_size_bytes(Thread* thread) {
|
||||
assert(thread != NULL, "invariant");
|
||||
const size_t desired_tlab_size_bytes = thread->tlab().desired_size() * HeapWordSize;
|
||||
const size_t alignment_reserve_bytes = thread->tlab().alignment_reserve_in_bytes();
|
||||
assert(desired_tlab_size_bytes > alignment_reserve_bytes, "invariant");
|
||||
return static_cast<intptr_t>(desired_tlab_size_bytes - alignment_reserve_bytes);
|
||||
}
|
||||
|
||||
inline int64_t load_allocated_bytes(Thread* thread) {
|
||||
const int64_t allocated_bytes = thread->allocated_bytes();
|
||||
if (allocated_bytes < _last_allocated_bytes) {
|
||||
// A hw thread can detach and reattach to the VM, and when it does,
|
||||
// it gets a new JavaThread representation. The thread local variable
|
||||
// tracking _last_allocated_bytes is mapped to the existing hw thread,
|
||||
// so it needs to be reset.
|
||||
_last_allocated_bytes = 0;
|
||||
}
|
||||
return allocated_bytes == _last_allocated_bytes ? 0 : allocated_bytes;
|
||||
}
|
||||
|
||||
// To avoid large objects from being undersampled compared to the regular TLAB samples,
|
||||
// the data amount is normalized as if it was a TLAB, giving a number of TLAB sampling attempts to the large object.
|
||||
static void normalize_as_tlab_and_send_allocation_samples(Klass* klass, intptr_t obj_alloc_size_bytes, Thread* thread) {
|
||||
const int64_t allocated_bytes = load_allocated_bytes(thread);
|
||||
assert(allocated_bytes > 0, "invariant"); // obj_alloc_size_bytes is already attributed to allocated_bytes at this point.
|
||||
if (!UseTLAB) {
|
||||
send_allocation_sample(klass, allocated_bytes);
|
||||
return;
|
||||
}
|
||||
const intptr_t tlab_size_bytes = estimate_tlab_size_bytes(thread);
|
||||
if (allocated_bytes - _last_allocated_bytes < tlab_size_bytes) {
|
||||
return;
|
||||
}
|
||||
assert(obj_alloc_size_bytes > 0, "invariant");
|
||||
do {
|
||||
if (send_allocation_sample_with_result(klass, allocated_bytes)) {
|
||||
return;
|
||||
}
|
||||
obj_alloc_size_bytes -= tlab_size_bytes;
|
||||
} while (obj_alloc_size_bytes > 0);
|
||||
}
|
||||
|
||||
void AllocTracer::send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread) {
|
||||
JFR_ONLY(JfrAllocationTracer tracer(obj, alloc_size, thread);)
|
||||
EventObjectAllocationOutsideTLAB event;
|
||||
@ -40,6 +112,7 @@ void AllocTracer::send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size
|
||||
event.set_allocationSize(alloc_size);
|
||||
event.commit();
|
||||
}
|
||||
normalize_as_tlab_and_send_allocation_samples(klass, static_cast<intptr_t>(alloc_size), thread);
|
||||
}
|
||||
|
||||
void AllocTracer::send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread) {
|
||||
@ -51,6 +124,11 @@ void AllocTracer::send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_
|
||||
event.set_tlabSize(tlab_size);
|
||||
event.commit();
|
||||
}
|
||||
const int64_t allocated_bytes = load_allocated_bytes(thread);
|
||||
if (allocated_bytes == 0) {
|
||||
return;
|
||||
}
|
||||
send_allocation_sample(klass, allocated_bytes);
|
||||
}
|
||||
|
||||
void AllocTracer::send_allocation_requiring_gc_event(size_t size, uint gcId) {
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "jfr/recorder/repository/jfrRepository.hpp"
|
||||
#include "jfr/recorder/repository/jfrChunkRotation.hpp"
|
||||
#include "jfr/recorder/repository/jfrChunkWriter.hpp"
|
||||
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
||||
#include "jfr/recorder/service/jfrOptionSet.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
|
||||
#include "jfr/recorder/stringpool/jfrStringPool.hpp"
|
||||
@ -175,6 +176,11 @@ NO_TRANSITION(jboolean, jfr_set_cutoff(JNIEnv* env, jobject jvm, jlong event_typ
|
||||
return JfrEventSetting::set_cutoff(event_type_id, cutoff_ticks) ? JNI_TRUE : JNI_FALSE;
|
||||
NO_TRANSITION_END
|
||||
|
||||
NO_TRANSITION(jboolean, jfr_set_throttle(JNIEnv* env, jobject jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms))
|
||||
JfrEventThrottler::configure(static_cast<JfrEventId>(event_type_id), event_sample_size, period_ms);
|
||||
return JNI_TRUE;
|
||||
NO_TRANSITION_END
|
||||
|
||||
NO_TRANSITION(jboolean, jfr_should_rotate_disk(JNIEnv* env, jobject jvm))
|
||||
return JfrChunkRotation::should_rotate() ? JNI_TRUE : JNI_FALSE;
|
||||
NO_TRANSITION_END
|
||||
|
@ -132,6 +132,8 @@ jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jobject jvm);
|
||||
|
||||
jboolean JNICALL jfr_set_cutoff(JNIEnv* env, jobject jvm, jlong event_type_id, jlong cutoff_ticks);
|
||||
|
||||
jboolean JNICALL jfr_set_throttle(JNIEnv* env, jobject jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms);
|
||||
|
||||
void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean, jboolean);
|
||||
|
||||
jboolean JNICALL jfr_should_rotate_disk(JNIEnv* env, jobject jvm);
|
||||
|
@ -81,6 +81,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
|
||||
(char*)"setForceInstrumentation", (char*)"(Z)V", (void*)jfr_set_force_instrumentation,
|
||||
(char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count,
|
||||
(char*)"setCutoff", (char*)"(JJ)Z", (void*)jfr_set_cutoff,
|
||||
(char*)"setThrottle", (char*)"(JJJ)Z", (void*)jfr_set_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,
|
||||
|
@ -175,7 +175,7 @@ void ObjectSampler::sample(HeapWord* obj, size_t allocated, JavaThread* thread)
|
||||
record_stacktrace(thread);
|
||||
// try enter critical section
|
||||
JfrTryLock tryLock(&_lock);
|
||||
if (!tryLock.has_lock()) {
|
||||
if (!tryLock.acquired()) {
|
||||
log_trace(jfr, oldobject, sampling)("Skipping old object sample due to lock contention");
|
||||
return;
|
||||
}
|
||||
|
@ -614,6 +614,12 @@
|
||||
<Field type="ulong" contentType="bytes" name="allocationSize" label="Allocation Size" />
|
||||
</Event>
|
||||
|
||||
<Event name="ObjectAllocationSample" category="Java Application" label="Object Allocation Sample" thread="true" stackTrace="true" startTime="false" throttle="true">
|
||||
<Field type="Class" name="objectClass" label="Object Class" description="Class of allocated object" />
|
||||
<Field type="long" contentType="bytes" name="weight" label="Sample Weight"
|
||||
description="The relative weight of the sample. Aggregating the weights for a large number of samples, for a particular class, thread or stack trace, gives a statistically accurate representation of the allocation pressure" />
|
||||
</Event>
|
||||
|
||||
<Event name="OldObjectSample" category="Java Virtual Machine, Profiling" label="Old Object Sample" description="A potential memory leak" stackTrace="true" thread="true"
|
||||
startTime="false" cutoff="true">
|
||||
<Field type="Ticks" name="allocationTime" label="Allocation Time" />
|
||||
|
@ -70,6 +70,7 @@
|
||||
<xs:attribute name="stackTrace" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="period" type="periodType" use="optional" />
|
||||
<xs:attribute name="cutoff" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="throttle" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="commitState" type="xs:string" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "jfr/recorder/jfrRecorder.hpp"
|
||||
#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
|
||||
#include "jfr/recorder/repository/jfrRepository.hpp"
|
||||
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
||||
#include "jfr/recorder/service/jfrOptionSet.hpp"
|
||||
#include "jfr/recorder/service/jfrPostBox.hpp"
|
||||
#include "jfr/recorder/service/jfrRecorderService.hpp"
|
||||
@ -289,6 +290,9 @@ bool JfrRecorder::create_components() {
|
||||
if (!create_thread_sampling()) {
|
||||
return false;
|
||||
}
|
||||
if (!create_event_throttler()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -362,6 +366,10 @@ bool JfrRecorder::create_thread_sampling() {
|
||||
return _thread_sampling != NULL;
|
||||
}
|
||||
|
||||
bool JfrRecorder::create_event_throttler() {
|
||||
return JfrEventThrottler::create();
|
||||
}
|
||||
|
||||
void JfrRecorder::destroy_components() {
|
||||
JfrJvmtiAgent::destroy();
|
||||
if (_post_box != NULL) {
|
||||
@ -396,6 +404,7 @@ void JfrRecorder::destroy_components() {
|
||||
JfrThreadSampling::destroy();
|
||||
_thread_sampling = NULL;
|
||||
}
|
||||
JfrEventThrottler::destroy();
|
||||
}
|
||||
|
||||
bool JfrRecorder::create_recorder_thread() {
|
||||
|
@ -53,6 +53,7 @@ class JfrRecorder : public JfrCHeapObj {
|
||||
static bool create_storage();
|
||||
static bool create_stringpool();
|
||||
static bool create_thread_sampling();
|
||||
static bool create_event_throttler();
|
||||
static bool create_components();
|
||||
static void destroy_components();
|
||||
static void on_recorder_thread_exit();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2020, 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
|
||||
@ -26,6 +26,7 @@
|
||||
#define SHARE_JFR_RECORDER_SERVICE_JFREVENT_HPP
|
||||
|
||||
#include "jfr/recorder/jfrEventSetting.inline.hpp"
|
||||
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
||||
#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
|
||||
#include "jfr/utilities/jfrTime.hpp"
|
||||
#include "jfr/utilities/jfrTypes.hpp"
|
||||
@ -63,9 +64,14 @@ class JfrEvent {
|
||||
jlong _start_time;
|
||||
jlong _end_time;
|
||||
bool _started;
|
||||
bool _untimed;
|
||||
bool _should_commit;
|
||||
bool _evaluated;
|
||||
|
||||
protected:
|
||||
JfrEvent(EventStartTime timing=TIMED) : _start_time(0), _end_time(0), _started(false)
|
||||
JfrEvent(EventStartTime timing=TIMED) : _start_time(0), _end_time(0),
|
||||
_started(false), _untimed(timing == UNTIMED),
|
||||
_should_commit(false), _evaluated(false)
|
||||
#ifdef ASSERT
|
||||
, _verifier()
|
||||
#endif
|
||||
@ -79,19 +85,12 @@ class JfrEvent {
|
||||
}
|
||||
|
||||
void commit() {
|
||||
if (!should_commit()) {
|
||||
assert(!_verifier.committed(), "event already committed");
|
||||
if (!should_write()) {
|
||||
return;
|
||||
}
|
||||
assert(!_verifier.committed(), "event already committed");
|
||||
if (_start_time == 0) {
|
||||
set_starttime(JfrTicks::now());
|
||||
} else if (_end_time == 0) {
|
||||
set_endtime(JfrTicks::now());
|
||||
}
|
||||
if (should_write()) {
|
||||
write_event();
|
||||
DEBUG_ONLY(_verifier.set_committed();)
|
||||
}
|
||||
write_event();
|
||||
DEBUG_ONLY(_verifier.set_committed();)
|
||||
}
|
||||
|
||||
public:
|
||||
@ -147,16 +146,44 @@ class JfrEvent {
|
||||
return T::hasStackTrace;
|
||||
}
|
||||
|
||||
bool should_commit() {
|
||||
bool is_started() const {
|
||||
return _started;
|
||||
}
|
||||
|
||||
bool should_commit() {
|
||||
if (!_started) {
|
||||
return false;
|
||||
}
|
||||
if (_untimed) {
|
||||
return true;
|
||||
}
|
||||
if (_evaluated) {
|
||||
return _should_commit;
|
||||
}
|
||||
_should_commit = evaluate();
|
||||
_evaluated = true;
|
||||
return _should_commit;
|
||||
}
|
||||
|
||||
private:
|
||||
bool should_write() {
|
||||
if (T::isInstant || T::isRequestable || T::hasCutoff) {
|
||||
return true;
|
||||
return _started && (_evaluated ? _should_commit : evaluate());
|
||||
}
|
||||
|
||||
bool evaluate() {
|
||||
assert(_started, "invariant");
|
||||
if (_start_time == 0) {
|
||||
set_starttime(JfrTicks::now());
|
||||
} else if (_end_time == 0) {
|
||||
set_endtime(JfrTicks::now());
|
||||
}
|
||||
return (_end_time - _start_time) >= JfrEventSetting::threshold(T::eventId);
|
||||
if (T::isInstant || T::isRequestable) {
|
||||
return T::hasThrottle ? JfrEventThrottler::accept(T::eventId, _untimed ? 0 : _start_time) : true;
|
||||
}
|
||||
if (_end_time - _start_time < JfrEventSetting::threshold(T::eventId)) {
|
||||
return false;
|
||||
}
|
||||
return T::hasThrottle ? JfrEventThrottler::accept(T::eventId, _untimed ? 0 : _end_time) : true;
|
||||
}
|
||||
|
||||
void write_event() {
|
||||
|
276
src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
Normal file
276
src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "jfr/recorder/service/jfrEventThrottler.hpp"
|
||||
#include "jfr/utilities/jfrSpinlockHelper.hpp"
|
||||
#include "logging/log.hpp"
|
||||
|
||||
constexpr static const JfrSamplerParams _disabled_params = {
|
||||
0, // sample points per window
|
||||
0, // window duration ms
|
||||
0, // window lookback count
|
||||
false // reconfigure
|
||||
};
|
||||
|
||||
static JfrEventThrottler* _throttler = NULL;
|
||||
|
||||
JfrEventThrottler::JfrEventThrottler(JfrEventId event_id) :
|
||||
JfrAdaptiveSampler(),
|
||||
_last_params(),
|
||||
_sample_size(0),
|
||||
_period_ms(0),
|
||||
_sample_size_ewma(0),
|
||||
_event_id(event_id),
|
||||
_disabled(false),
|
||||
_update(false) {}
|
||||
|
||||
bool JfrEventThrottler::create() {
|
||||
assert(_throttler == NULL, "invariant");
|
||||
_throttler = new JfrEventThrottler(JfrObjectAllocationSampleEvent);
|
||||
return _throttler != NULL && _throttler->initialize();
|
||||
}
|
||||
|
||||
void JfrEventThrottler::destroy() {
|
||||
delete _throttler;
|
||||
_throttler = NULL;
|
||||
}
|
||||
|
||||
// There is currently only one throttler instance, for the jdk.ObjectAllocationSample event.
|
||||
// When introducing additional throttlers, also add a lookup map keyed by event id.
|
||||
JfrEventThrottler* JfrEventThrottler::for_event(JfrEventId event_id) {
|
||||
assert(_throttler != NULL, "JfrEventThrottler has not been properly initialized");
|
||||
assert(event_id == JfrObjectAllocationSampleEvent, "Event type has an unconfigured throttler");
|
||||
return event_id == JfrObjectAllocationSampleEvent ? _throttler : NULL;
|
||||
}
|
||||
|
||||
void JfrEventThrottler::configure(JfrEventId event_id, int64_t sample_size, int64_t period_ms) {
|
||||
if (event_id != JfrObjectAllocationSampleEvent) {
|
||||
return;
|
||||
}
|
||||
assert(_throttler != NULL, "JfrEventThrottler has not been properly initialized");
|
||||
_throttler->configure(sample_size, period_ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* The event throttler currently only supports a single configuration option, a rate, but more may be added in the future:
|
||||
*
|
||||
* We configure to throttle dynamically, to maintain a continuous, maximal event emission rate per time period.
|
||||
*
|
||||
* - sample_size size of the event sample set
|
||||
* - period_ms time period expressed in milliseconds
|
||||
*/
|
||||
void JfrEventThrottler::configure(int64_t sample_size, int64_t period_ms) {
|
||||
JfrSpinlockHelper mutex(&_lock);
|
||||
_sample_size = sample_size;
|
||||
_period_ms = period_ms;
|
||||
_update = true;
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
// Predicate for event selection.
|
||||
bool JfrEventThrottler::accept(JfrEventId event_id, int64_t timestamp /* 0 */) {
|
||||
JfrEventThrottler* const throttler = for_event(event_id);
|
||||
if (throttler == NULL) return true;
|
||||
return _throttler->_disabled ? true : _throttler->sample(timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* The window_lookback_count defines the history in number of windows to take into account
|
||||
* when the JfrAdaptiveSampler engine is calcualting an expected weigthed moving average (EWMA) over the population.
|
||||
* Technically, it determines the alpha coefficient in the EMWA formula.
|
||||
*/
|
||||
constexpr static const size_t default_window_lookback_count = 25; // 25 windows == 5 seconds (for default window duration of 200 ms)
|
||||
|
||||
/*
|
||||
* Rates lower than or equal to the 'low rate upper bound', are considered special.
|
||||
* They will use a single window of whatever duration, because the rates are so low they
|
||||
* do not justify the overhead of more frequent window rotations.
|
||||
*/
|
||||
constexpr static const intptr_t low_rate_upper_bound = 9;
|
||||
constexpr static const size_t window_divisor = 5;
|
||||
|
||||
constexpr static const int64_t MINUTE = 60 * MILLIUNITS;
|
||||
constexpr static const int64_t TEN_PER_1000_MS_IN_MINUTES = 600;
|
||||
constexpr static const int64_t HOUR = 60 * MINUTE;
|
||||
constexpr static const int64_t TEN_PER_1000_MS_IN_HOURS = 36000;
|
||||
constexpr static const int64_t DAY = 24 * HOUR;
|
||||
constexpr static const int64_t TEN_PER_1000_MS_IN_DAYS = 864000;
|
||||
|
||||
inline void set_window_lookback(JfrSamplerParams& params) {
|
||||
if (params.window_duration_ms <= MILLIUNITS) {
|
||||
params.window_lookback_count = default_window_lookback_count; // 5 seconds
|
||||
return;
|
||||
}
|
||||
if (params.window_duration_ms == MINUTE) {
|
||||
params.window_lookback_count = 5; // 5 windows == 5 minutes
|
||||
return;
|
||||
}
|
||||
params.window_lookback_count = 1; // 1 window == 1 hour or 1 day
|
||||
}
|
||||
|
||||
inline void set_low_rate(JfrSamplerParams& params, int64_t event_sample_size, int64_t period_ms) {
|
||||
params.sample_points_per_window = event_sample_size;
|
||||
params.window_duration_ms = period_ms;
|
||||
}
|
||||
|
||||
// If the throttler is off, it accepts all events.
|
||||
constexpr static const int64_t event_throttler_off = -2;
|
||||
|
||||
/*
|
||||
* Set the number of sample points and window duration.
|
||||
*/
|
||||
inline void set_sample_points_and_window_duration(JfrSamplerParams& params, int64_t sample_size, int64_t period_ms) {
|
||||
assert(sample_size != event_throttler_off, "invariant");
|
||||
assert(sample_size >= 0, "invariant");
|
||||
assert(period_ms >= 1000, "invariant");
|
||||
if (sample_size <= low_rate_upper_bound) {
|
||||
set_low_rate(params, sample_size, period_ms);
|
||||
return;
|
||||
} else if (period_ms == MINUTE && sample_size < TEN_PER_1000_MS_IN_MINUTES) {
|
||||
set_low_rate(params, sample_size, period_ms);
|
||||
return;
|
||||
} else if (period_ms == HOUR && sample_size < TEN_PER_1000_MS_IN_HOURS) {
|
||||
set_low_rate(params, sample_size, period_ms);
|
||||
return;
|
||||
} else if (period_ms == DAY && sample_size < TEN_PER_1000_MS_IN_DAYS) {
|
||||
set_low_rate(params, sample_size, period_ms);
|
||||
return;
|
||||
}
|
||||
assert(period_ms % window_divisor == 0, "invariant");
|
||||
params.sample_points_per_window = sample_size / window_divisor;
|
||||
params.window_duration_ms = period_ms / window_divisor;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the input event sample size is large enough, normalize to per 1000 ms
|
||||
*/
|
||||
inline void normalize(int64_t* sample_size, int64_t* period_ms) {
|
||||
assert(sample_size != NULL, "invariant");
|
||||
assert(period_ms != NULL, "invariant");
|
||||
if (*period_ms == MILLIUNITS) {
|
||||
return;
|
||||
}
|
||||
if (*period_ms == MINUTE) {
|
||||
if (*sample_size >= TEN_PER_1000_MS_IN_MINUTES) {
|
||||
*sample_size /= 60;
|
||||
*period_ms /= 60;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (*period_ms == HOUR) {
|
||||
if (*sample_size >= TEN_PER_1000_MS_IN_HOURS) {
|
||||
*sample_size /= 3600;
|
||||
*period_ms /= 3600;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (*sample_size >= TEN_PER_1000_MS_IN_DAYS) {
|
||||
*sample_size /= 86400;
|
||||
*period_ms /= 86400;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_disabled(int64_t event_sample_size) {
|
||||
return event_sample_size == event_throttler_off;
|
||||
}
|
||||
|
||||
const JfrSamplerParams& JfrEventThrottler::update_params(const JfrSamplerWindow* expired) {
|
||||
_disabled = is_disabled(_sample_size);
|
||||
if (_disabled) {
|
||||
return _disabled_params;
|
||||
}
|
||||
normalize(&_sample_size, &_period_ms);
|
||||
set_sample_points_and_window_duration(_last_params, _sample_size, _period_ms);
|
||||
set_window_lookback(_last_params);
|
||||
_sample_size_ewma = 0;
|
||||
_last_params.reconfigure = true;
|
||||
_update = false;
|
||||
return _last_params;
|
||||
}
|
||||
|
||||
/*
|
||||
* Exponentially Weighted Moving Average (EWMA):
|
||||
*
|
||||
* Y is a datapoint (at time t)
|
||||
* S is the current EMWA (at time t-1)
|
||||
* alpha represents the degree of weighting decrease, a constant smoothing factor between 0 and 1.
|
||||
*
|
||||
* A higher alpha discounts older observations faster.
|
||||
* Returns the new EWMA for S
|
||||
*/
|
||||
|
||||
inline double exponentially_weighted_moving_average(double Y, double alpha, double S) {
|
||||
return alpha * Y + (1 - alpha) * S;
|
||||
}
|
||||
|
||||
inline double compute_ewma_alpha_coefficient(size_t lookback_count) {
|
||||
return lookback_count <= 1 ? 1 : static_cast<double>(1) / static_cast<double>(lookback_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* To start debugging the throttler: -Xlog:jfr+system+throttle=debug
|
||||
* It will log details of each expired window together with an average sample size.
|
||||
*
|
||||
* Excerpt:
|
||||
*
|
||||
* "jdk.ObjectAllocationSample: avg.sample size: 19.8377, window set point: 20 ..."
|
||||
*
|
||||
* Monitoring the relation of average sample size to the window set point, i.e the target,
|
||||
* is a good indicator of how the throttler is performing over time.
|
||||
*
|
||||
* Note: there is currently only one throttler instance, for the ObjectAllocationSample event.
|
||||
* When introducing additional throttlers, also provide a map from the event id to the event name.
|
||||
*/
|
||||
static void log(const JfrSamplerWindow* expired, double* sample_size_ewma) {
|
||||
assert(sample_size_ewma != NULL, "invariant");
|
||||
if (log_is_enabled(Debug, jfr, system, throttle)) {
|
||||
*sample_size_ewma = exponentially_weighted_moving_average(expired->sample_size(), compute_ewma_alpha_coefficient(expired->params().window_lookback_count), *sample_size_ewma);
|
||||
log_debug(jfr, system, throttle)("jdk.ObjectAllocationSample: avg.sample size: %0.4f, window set point: %zu, sample size: %zu, population size: %zu, ratio: %.4f, window duration: %zu ms\n",
|
||||
*sample_size_ewma, expired->params().sample_points_per_window, expired->sample_size(), expired->population_size(),
|
||||
expired->population_size() == 0 ? 0 : (double)expired->sample_size() / (double)expired->population_size(),
|
||||
expired->params().window_duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the feedback control loop.
|
||||
*
|
||||
* The JfrAdaptiveSampler engine calls this when a sampler window has expired, providing
|
||||
* us with an opportunity to perform some analysis. To reciprocate, we returns a set of
|
||||
* parameters, possibly updated, for the engine to apply to the next window.
|
||||
*
|
||||
* Try to keep relatively quick, since the engine is currently inside a critical section,
|
||||
* in the process of rotating windows.
|
||||
*/
|
||||
const JfrSamplerParams& JfrEventThrottler::next_window_params(const JfrSamplerWindow* expired) {
|
||||
assert(expired != NULL, "invariant");
|
||||
assert(_lock, "invariant");
|
||||
log(expired, &_sample_size_ewma);
|
||||
if (_update) {
|
||||
return update_params(expired); // Updates _last_params in-place.
|
||||
}
|
||||
return _disabled ? _disabled_params : _last_params;
|
||||
}
|
57
src/hotspot/share/jfr/recorder/service/jfrEventThrottler.hpp
Normal file
57
src/hotspot/share/jfr/recorder/service/jfrEventThrottler.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_JFR_RECORDER_SERVICE_JFREVENTTHROTTLER_HPP
|
||||
#define SHARE_JFR_RECORDER_SERVICE_JFREVENTTHROTTLER_HPP
|
||||
|
||||
#include "jfrfiles/jfrEventIds.hpp"
|
||||
#include "jfr/support/jfrAdaptiveSampler.hpp"
|
||||
|
||||
class JfrEventThrottler : public JfrAdaptiveSampler {
|
||||
friend class JfrRecorder;
|
||||
private:
|
||||
JfrSamplerParams _last_params;
|
||||
int64_t _sample_size;
|
||||
int64_t _period_ms;
|
||||
double _sample_size_ewma;
|
||||
JfrEventId _event_id;
|
||||
bool _disabled;
|
||||
bool _update;
|
||||
|
||||
static bool create();
|
||||
static void destroy();
|
||||
JfrEventThrottler(JfrEventId event_id);
|
||||
void configure(int64_t event_sample_size, int64_t period_ms);
|
||||
|
||||
const JfrSamplerParams& update_params(const JfrSamplerWindow* expired);
|
||||
const JfrSamplerParams& next_window_params(const JfrSamplerWindow* expired);
|
||||
static JfrEventThrottler* for_event(JfrEventId event_id);
|
||||
|
||||
public:
|
||||
static void configure(JfrEventId event_id, int64_t event_sample_size, int64_t period_ms);
|
||||
static bool accept(JfrEventId event_id, int64_t timestamp = 0);
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_RECORDER_SERVICE_JFREVENTTHROTTLER_HPP
|
385
src/hotspot/share/jfr/support/jfrAdaptiveSampler.cpp
Normal file
385
src/hotspot/share/jfr/support/jfrAdaptiveSampler.cpp
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "jfr/support/jfrAdaptiveSampler.hpp"
|
||||
#include "jfr/utilities/jfrRandom.inline.hpp"
|
||||
#include "jfr/utilities/jfrSpinlockHelper.hpp"
|
||||
#include "jfr/utilities/jfrTime.hpp"
|
||||
#include "jfr/utilities/jfrTimeConverter.hpp"
|
||||
#include "jfr/utilities/jfrTryLock.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include <cmath>
|
||||
|
||||
JfrSamplerWindow::JfrSamplerWindow() :
|
||||
_params(),
|
||||
_end_ticks(0),
|
||||
_sampling_interval(1),
|
||||
_projected_population_size(0),
|
||||
_measured_population_size(0) {}
|
||||
|
||||
JfrAdaptiveSampler::JfrAdaptiveSampler() :
|
||||
_prng(this),
|
||||
_window_0(NULL),
|
||||
_window_1(NULL),
|
||||
_active_window(NULL),
|
||||
_avg_population_size(0),
|
||||
_ewma_population_size_alpha(0),
|
||||
_acc_debt_carry_limit(0),
|
||||
_acc_debt_carry_count(0),
|
||||
_lock(0) {}
|
||||
|
||||
JfrAdaptiveSampler::~JfrAdaptiveSampler() {
|
||||
delete _window_0;
|
||||
delete _window_1;
|
||||
}
|
||||
|
||||
bool JfrAdaptiveSampler::initialize() {
|
||||
assert(_window_0 == NULL, "invariant");
|
||||
_window_0 = new JfrSamplerWindow();
|
||||
if (_window_0 == NULL) {
|
||||
return false;
|
||||
}
|
||||
assert(_window_1 == NULL, "invariant");
|
||||
_window_1 = new JfrSamplerWindow();
|
||||
if (_window_1 == NULL) {
|
||||
return false;
|
||||
}
|
||||
_active_window = _window_0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The entry point to the sampler.
|
||||
*/
|
||||
bool JfrAdaptiveSampler::sample(int64_t timestamp) {
|
||||
bool expired_window;
|
||||
const bool result = active_window()->sample(timestamp, &expired_window);
|
||||
if (expired_window) {
|
||||
JfrTryLock mutex(&_lock);
|
||||
if (mutex.acquired()) {
|
||||
rotate_window(timestamp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline const JfrSamplerWindow* JfrAdaptiveSampler::active_window() const {
|
||||
return Atomic::load_acquire(&_active_window);
|
||||
}
|
||||
|
||||
inline int64_t now() {
|
||||
return JfrTicks::now().value();
|
||||
}
|
||||
|
||||
inline bool JfrSamplerWindow::is_expired(int64_t timestamp) const {
|
||||
const int64_t end_ticks = Atomic::load(&_end_ticks);
|
||||
return timestamp == 0 ? now() >= end_ticks : timestamp >= end_ticks;
|
||||
}
|
||||
|
||||
bool JfrSamplerWindow::sample(int64_t timestamp, bool* expired_window) const {
|
||||
assert(expired_window != NULL, "invariant");
|
||||
*expired_window = is_expired(timestamp);
|
||||
return *expired_window ? false : sample();
|
||||
}
|
||||
|
||||
inline bool JfrSamplerWindow::sample() const {
|
||||
const size_t ordinal = Atomic::add(&_measured_population_size, static_cast<size_t>(1));
|
||||
return ordinal <= _projected_population_size && ordinal % _sampling_interval == 0;
|
||||
}
|
||||
|
||||
// Called exclusively by the holder of the lock when a window is determined to have expired.
|
||||
void JfrAdaptiveSampler::rotate_window(int64_t timestamp) {
|
||||
assert(_lock, "invariant");
|
||||
const JfrSamplerWindow* const current = active_window();
|
||||
assert(current != NULL, "invariant");
|
||||
if (!current->is_expired(timestamp)) {
|
||||
// Someone took care of it.
|
||||
return;
|
||||
}
|
||||
rotate(current);
|
||||
}
|
||||
|
||||
// Subclasses can call this to immediately trigger a reconfiguration of the sampler.
|
||||
// There is no need to await the expiration of the current active window.
|
||||
void JfrAdaptiveSampler::reconfigure() {
|
||||
assert(_lock, "invariant");
|
||||
rotate(active_window());
|
||||
}
|
||||
|
||||
// Call next_window_param() to report the expired window and to retreive params for the next window.
|
||||
void JfrAdaptiveSampler::rotate(const JfrSamplerWindow* expired) {
|
||||
assert(expired == active_window(), "invariant");
|
||||
install(configure(next_window_params(expired), expired));
|
||||
}
|
||||
|
||||
inline void JfrAdaptiveSampler::install(const JfrSamplerWindow* next) {
|
||||
assert(next != active_window(), "invariant");
|
||||
Atomic::release_store(&_active_window, next);
|
||||
}
|
||||
|
||||
const JfrSamplerWindow* JfrAdaptiveSampler::configure(const JfrSamplerParams& params, const JfrSamplerWindow* expired) {
|
||||
assert(_lock, "invariant");
|
||||
if (params.reconfigure) {
|
||||
// Store updated params once to both windows.
|
||||
const_cast<JfrSamplerWindow*>(expired)->_params = params;
|
||||
next_window(expired)->_params = params;
|
||||
configure(params);
|
||||
}
|
||||
JfrSamplerWindow* const next = set_rate(params, expired);
|
||||
next->initialize(params);
|
||||
return next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Exponentially Weighted Moving Average (EWMA):
|
||||
*
|
||||
* Y is a datapoint (at time t)
|
||||
* S is the current EMWA (at time t-1)
|
||||
* alpha represents the degree of weighting decrease, a constant smoothing factor between 0 and 1.
|
||||
*
|
||||
* A higher alpha discounts older observations faster.
|
||||
* Returns the new EWMA for S
|
||||
*/
|
||||
|
||||
inline double exponentially_weighted_moving_average(double Y, double alpha, double S) {
|
||||
return alpha * Y + (1 - alpha) * S;
|
||||
}
|
||||
|
||||
inline double compute_ewma_alpha_coefficient(size_t lookback_count) {
|
||||
return lookback_count <= 1 ? 1 : static_cast<double>(1) / static_cast<double>(lookback_count);
|
||||
}
|
||||
|
||||
inline size_t compute_accumulated_debt_carry_limit(const JfrSamplerParams& params) {
|
||||
if (params.window_duration_ms == 0 || params.window_duration_ms >= MILLIUNITS) {
|
||||
return 1;
|
||||
}
|
||||
return MILLIUNITS / params.window_duration_ms;
|
||||
}
|
||||
|
||||
void JfrAdaptiveSampler::configure(const JfrSamplerParams& params) {
|
||||
assert(params.reconfigure, "invariant");
|
||||
_avg_population_size = 0;
|
||||
_ewma_population_size_alpha = compute_ewma_alpha_coefficient(params.window_lookback_count);
|
||||
_acc_debt_carry_limit = compute_accumulated_debt_carry_limit(params);
|
||||
_acc_debt_carry_count = _acc_debt_carry_limit;
|
||||
params.reconfigure = false;
|
||||
}
|
||||
|
||||
inline int64_t millis_to_countertime(int64_t millis) {
|
||||
return JfrTimeConverter::nanos_to_countertime(millis * NANOSECS_PER_MILLISEC);
|
||||
}
|
||||
|
||||
void JfrSamplerWindow::initialize(const JfrSamplerParams& params) {
|
||||
assert(_sampling_interval >= 1, "invariant");
|
||||
if (params.window_duration_ms == 0) {
|
||||
Atomic::store(&_end_ticks, static_cast<int64_t>(0));
|
||||
return;
|
||||
}
|
||||
Atomic::store(&_measured_population_size, static_cast<size_t>(0));
|
||||
const int64_t end_ticks = now() + millis_to_countertime(params.window_duration_ms);
|
||||
Atomic::store(&_end_ticks, end_ticks);
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on what it has learned from the past, the sampler creates a future 'projection',
|
||||
* a speculation, or model, of what the situation will be like during the next window.
|
||||
* This projection / model is used to derive values for the parameters, which are estimates for
|
||||
* collecting a sample set that, should the model hold, is as close as possible to the target,
|
||||
* i.e. the set point, which is a function of the number of sample_points_per_window + amortization.
|
||||
* The model is a geometric distribution over the number of trials / selections required until success.
|
||||
* For each window, the sampling interval is a random variable from this geometric distribution.
|
||||
*/
|
||||
JfrSamplerWindow* JfrAdaptiveSampler::set_rate(const JfrSamplerParams& params, const JfrSamplerWindow* expired) {
|
||||
JfrSamplerWindow* const next = next_window(expired);
|
||||
assert(next != expired, "invariant");
|
||||
const size_t sample_size = project_sample_size(params, expired);
|
||||
if (sample_size == 0) {
|
||||
next->_projected_population_size = 0;
|
||||
return next;
|
||||
}
|
||||
next->_sampling_interval = derive_sampling_interval(sample_size, expired);
|
||||
assert(next->_sampling_interval >= 1, "invariant");
|
||||
next->_projected_population_size = sample_size * next->_sampling_interval;
|
||||
return next;
|
||||
}
|
||||
|
||||
inline JfrSamplerWindow* JfrAdaptiveSampler::next_window(const JfrSamplerWindow* expired) const {
|
||||
assert(expired != NULL, "invariant");
|
||||
return expired == _window_0 ? _window_1 : _window_0;
|
||||
}
|
||||
|
||||
size_t JfrAdaptiveSampler::project_sample_size(const JfrSamplerParams& params, const JfrSamplerWindow* expired) {
|
||||
return params.sample_points_per_window + amortize_debt(expired);
|
||||
}
|
||||
|
||||
/*
|
||||
* When the sampler is configured to maintain a rate, is employs the concepts
|
||||
* of 'debt' and 'accumulated debt'. 'Accumulated debt' can be thought of as
|
||||
* a cumulative error term, and is indicative for how much the sampler is
|
||||
* deviating from a set point, i.e. the ideal target rate. Debt accumulates naturally
|
||||
* as a function of undersampled windows, caused by system fluctuations,
|
||||
* i.e. too small populations.
|
||||
*
|
||||
* A specified rate is implicitly a _maximal_ rate, so the sampler must ensure
|
||||
* to respect this 'limit'. Rates are normalized as per-second ratios, hence the
|
||||
* limit to respect is on a per second basis. During this second, the sampler
|
||||
* has freedom to dynamically re-adjust, and it does so by 'amortizing'
|
||||
* accumulated debt over a certain number of windows that fall within the second.
|
||||
*
|
||||
* Intuitively, accumulated debt 'carry over' from the predecessor to the successor
|
||||
* window if within the allowable time frame (determined in # of 'windows' given by
|
||||
* _acc_debt_carry_limit). The successor window will sample more points to make amends,
|
||||
* or 'amortize' debt accumulated by its predecessor(s).
|
||||
*/
|
||||
size_t JfrAdaptiveSampler::amortize_debt(const JfrSamplerWindow* expired) {
|
||||
assert(expired != NULL, "invariant");
|
||||
const intptr_t accumulated_debt = expired->accumulated_debt();
|
||||
assert(accumulated_debt <= 0, "invariant");
|
||||
if (_acc_debt_carry_count == _acc_debt_carry_limit) {
|
||||
_acc_debt_carry_count = 1;
|
||||
return 0;
|
||||
}
|
||||
++_acc_debt_carry_count;
|
||||
return -accumulated_debt; // negation
|
||||
}
|
||||
|
||||
inline size_t JfrSamplerWindow::max_sample_size() const {
|
||||
return _projected_population_size / _sampling_interval;
|
||||
}
|
||||
|
||||
// The sample size is derived from the measured population size.
|
||||
size_t JfrSamplerWindow::sample_size() const {
|
||||
const size_t size = population_size();
|
||||
return size > _projected_population_size ? max_sample_size() : size / _sampling_interval;
|
||||
}
|
||||
|
||||
size_t JfrSamplerWindow::population_size() const {
|
||||
return Atomic::load(&_measured_population_size);
|
||||
}
|
||||
|
||||
intptr_t JfrSamplerWindow::accumulated_debt() const {
|
||||
return _projected_population_size == 0 ? 0 : static_cast<intptr_t>(_params.sample_points_per_window - max_sample_size()) + debt();
|
||||
}
|
||||
|
||||
intptr_t JfrSamplerWindow::debt() const {
|
||||
return _projected_population_size == 0 ? 0 : static_cast<intptr_t>(sample_size() - _params.sample_points_per_window);
|
||||
}
|
||||
|
||||
/*
|
||||
* Inverse transform sampling from a uniform to a geometric distribution.
|
||||
*
|
||||
* PMF: f(x) = P(X=x) = ((1-p)^x-1)p
|
||||
*
|
||||
* CDF: F(x) = P(X<=x) = 1 - (1-p)^x
|
||||
*
|
||||
* Inv
|
||||
* CDF: F'(u) = ceil( ln(1-u) / ln(1-p) ) // u = random uniform, 0.0 < u < 1.0
|
||||
*
|
||||
*/
|
||||
inline size_t next_geometric(double p, double u) {
|
||||
assert(u >= 0.0, "invariant");
|
||||
assert(u <= 1.0, "invariant");
|
||||
if (u == 0.0) {
|
||||
u = 0.01;
|
||||
} else if (u == 1.0) {
|
||||
u = 0.99;
|
||||
}
|
||||
// Inverse CDF for the geometric distribution.
|
||||
return ceil(log(1.0 - u) / log(1.0 - p));
|
||||
}
|
||||
|
||||
size_t JfrAdaptiveSampler::derive_sampling_interval(double sample_size, const JfrSamplerWindow* expired) {
|
||||
assert(sample_size > 0, "invariant");
|
||||
const size_t population_size = project_population_size(expired);
|
||||
if (population_size <= sample_size) {
|
||||
return 1;
|
||||
}
|
||||
assert(population_size > 0, "invariant");
|
||||
const double projected_probability = sample_size / population_size;
|
||||
return next_geometric(projected_probability, _prng.next_uniform());
|
||||
}
|
||||
|
||||
// The projected population size is an exponentially weighted moving average, a function of the window_lookback_count.
|
||||
inline size_t JfrAdaptiveSampler::project_population_size(const JfrSamplerWindow* expired) {
|
||||
assert(expired != NULL, "invariant");
|
||||
_avg_population_size = exponentially_weighted_moving_average(expired->population_size(), _ewma_population_size_alpha, _avg_population_size);
|
||||
return _avg_population_size;
|
||||
}
|
||||
|
||||
/* GTEST support */
|
||||
JfrGTestFixedRateSampler::JfrGTestFixedRateSampler(size_t sample_points_per_window, size_t window_duration_ms, size_t lookback_count) : JfrAdaptiveSampler(), _params() {
|
||||
_sample_size_ewma = 0.0;
|
||||
_params.sample_points_per_window = sample_points_per_window;
|
||||
_params.window_duration_ms = window_duration_ms;
|
||||
_params.window_lookback_count = lookback_count;
|
||||
_params.reconfigure = true;
|
||||
}
|
||||
|
||||
bool JfrGTestFixedRateSampler::initialize() {
|
||||
const bool result = JfrAdaptiveSampler::initialize();
|
||||
JfrSpinlockHelper mutex(&_lock);
|
||||
reconfigure();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* To start debugging the sampler: -Xlog:jfr+system+throttle=debug
|
||||
* It will log details of each expired window together with an average sample size.
|
||||
*
|
||||
* Excerpt:
|
||||
*
|
||||
* "JfrGTestFixedRateSampler: avg.sample size: 19.8377, window set point: 20 ..."
|
||||
*
|
||||
* Monitoring the relation of average sample size to the window set point, i.e the target,
|
||||
* is a good indicator of how the sampler is performing over time.
|
||||
*
|
||||
*/
|
||||
static void log(const JfrSamplerWindow* expired, double* sample_size_ewma) {
|
||||
assert(sample_size_ewma != NULL, "invariant");
|
||||
if (log_is_enabled(Debug, jfr, system, throttle)) {
|
||||
*sample_size_ewma = exponentially_weighted_moving_average(expired->sample_size(), compute_ewma_alpha_coefficient(expired->params().window_lookback_count), *sample_size_ewma);
|
||||
log_debug(jfr, system, throttle)("JfrGTestFixedRateSampler: avg.sample size: %0.4f, window set point: %zu, sample size: %zu, population size: %zu, ratio: %.4f, window duration: %zu ms\n",
|
||||
*sample_size_ewma, expired->params().sample_points_per_window, expired->sample_size(), expired->population_size(),
|
||||
expired->population_size() == 0 ? 0 : (double)expired->sample_size() / (double)expired->population_size(),
|
||||
expired->params().window_duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the feedback control loop.
|
||||
*
|
||||
* The JfrAdaptiveSampler engine calls this when a sampler window has expired, providing
|
||||
* us with an opportunity to perform some analysis.To reciprocate, we returns a set of
|
||||
* parameters, possibly updated, for the engine to apply to the next window.
|
||||
*/
|
||||
const JfrSamplerParams& JfrGTestFixedRateSampler::next_window_params(const JfrSamplerWindow* expired) {
|
||||
assert(expired != NULL, "invariant");
|
||||
assert(_lock, "invariant");
|
||||
log(expired, &_sample_size_ewma);
|
||||
return _params;
|
||||
}
|
155
src/hotspot/share/jfr/support/jfrAdaptiveSampler.hpp
Normal file
155
src/hotspot/share/jfr/support/jfrAdaptiveSampler.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. 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_SUPPORT_JFRADAPTIVESAMPLER_HPP
|
||||
#define SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
|
||||
|
||||
#include "jfr/utilities/jfrAllocation.hpp"
|
||||
#include "jfr/utilities/jfrRandom.hpp"
|
||||
|
||||
/*
|
||||
* The terminology is mostly from the domain of statistics:
|
||||
*
|
||||
* Population - a set of elements of interest.
|
||||
* Sample - a subset of elements from a population selected by a defined procedure.
|
||||
* Sample point - an element of a sample (sub)set.
|
||||
* Sampling interval - the distance between which measurements are taken, also referred to as 'nth selection'
|
||||
* Debt - an error term, signifying the deviation from a configured set point.
|
||||
* Amortization - a projection or strategy to recover accumulated debt.
|
||||
* Window - as in time window or time frame. The sampler sees the evolution of the system in time slices, i.e. in windows.
|
||||
* Rotate - the process of retiring an expired window and installing a new window with updated parameters.
|
||||
*
|
||||
* The adaptive sampler will guarantee a maximum number of sample points selected from a populuation
|
||||
* during a certain time interval. It is using fixed size time windows and adjusts the sampling interval for the next
|
||||
* window based on what it learned in the past. Each window has a set point, which is the target number of sample points
|
||||
* to select. The sampler keeps a cumulative error term, called 'accumulated debt', which is a measure
|
||||
* for how much the sampler is deviating from the set point over time. The maximum number of sample points selected
|
||||
* during an individual window is the set point + the accumulated debt.
|
||||
* The 'accumulated debt' also works as a 'spike damper', smoothing out the extremes in a way that the overall
|
||||
* target rate is obeyed without highly over- or under-sampled windows.
|
||||
*
|
||||
* Sample point selection is defined by a sampling interval, which gives instructions for selecting the 'nth' element
|
||||
* in a population. Which 'nth' to select is a random variable from a geometric distribution, recalculated for each window.
|
||||
*
|
||||
* Each window is configured individually, by an instance of the JfrSamplerParams struct. On window expiration,
|
||||
* but before switching in the next window, the sampler calls a subclass with the just expired window as an argument.
|
||||
.* A subclass can inspect the window to study the history of the system and also get an overview of how the sampler
|
||||
* is performing to help draw inferences. Based on what it learned, it can choose to let the sampler re-apply an updated
|
||||
* set of parameters to the next, upcoming, window. This is a basic feedback control loop to be developed further,
|
||||
* perhaps evolving more elaborate sampling schemes in the future.
|
||||
*
|
||||
* Using the JfrAdaptiveSampler, we can let a user specify at a high level, for example that he/she would like a
|
||||
* maximum rate of n sample points per second. Naturally, lower rates will be reported if the system does not produce
|
||||
* a population to sustain the requested rate, but n per second is respected as a maximum limit, hence it will never
|
||||
* report a rate higher than n per second.
|
||||
*
|
||||
* One good use of the sampler is to employ it as a throttler, or regulator, to help shape large data sets into smaller,
|
||||
* more managable subsets while still keeping the data somewhat representative.
|
||||
*
|
||||
*/
|
||||
|
||||
struct JfrSamplerParams {
|
||||
size_t sample_points_per_window; // The number of sample points to target per window.
|
||||
size_t window_duration_ms;
|
||||
size_t window_lookback_count; // The number of data points (windows) to include when calculating a moving average for the population size.
|
||||
mutable bool reconfigure; // The sampler should issue a reconfiguration because some parameter changed.
|
||||
};
|
||||
|
||||
class JfrSamplerWindow : public JfrCHeapObj {
|
||||
friend class JfrAdaptiveSampler;
|
||||
private:
|
||||
JfrSamplerParams _params;
|
||||
volatile int64_t _end_ticks;
|
||||
size_t _sampling_interval;
|
||||
size_t _projected_population_size;
|
||||
mutable volatile size_t _measured_population_size;
|
||||
|
||||
JfrSamplerWindow();
|
||||
void initialize(const JfrSamplerParams& params);
|
||||
size_t max_sample_size() const;
|
||||
bool is_expired(int64_t timestamp) const;
|
||||
bool sample() const;
|
||||
bool sample(int64_t timestamp, bool* is_expired) const;
|
||||
|
||||
public:
|
||||
size_t population_size() const;
|
||||
size_t sample_size() const;
|
||||
intptr_t debt() const;
|
||||
intptr_t accumulated_debt() const;
|
||||
const JfrSamplerParams& params() const {
|
||||
return _params;
|
||||
}
|
||||
};
|
||||
|
||||
class JfrAdaptiveSampler : public JfrCHeapObj {
|
||||
private:
|
||||
JfrPRNG _prng;
|
||||
JfrSamplerWindow* _window_0;
|
||||
JfrSamplerWindow* _window_1;
|
||||
const JfrSamplerWindow* _active_window;
|
||||
double _avg_population_size;
|
||||
double _ewma_population_size_alpha;
|
||||
size_t _acc_debt_carry_limit;
|
||||
size_t _acc_debt_carry_count;
|
||||
|
||||
void rotate_window(int64_t timestamp);
|
||||
void rotate(const JfrSamplerWindow* expired);
|
||||
const JfrSamplerWindow* active_window() const;
|
||||
JfrSamplerWindow* next_window(const JfrSamplerWindow* expired) const;
|
||||
void install(const JfrSamplerWindow* next);
|
||||
|
||||
size_t amortize_debt(const JfrSamplerWindow* expired);
|
||||
size_t derive_sampling_interval(double sample_size, const JfrSamplerWindow* expired);
|
||||
size_t project_population_size(const JfrSamplerWindow* expired);
|
||||
size_t project_sample_size(const JfrSamplerParams& params, const JfrSamplerWindow* expired);
|
||||
JfrSamplerWindow* set_rate(const JfrSamplerParams& params, const JfrSamplerWindow* expired);
|
||||
|
||||
void configure(const JfrSamplerParams& params);
|
||||
const JfrSamplerWindow* configure(const JfrSamplerParams& params, const JfrSamplerWindow* expired);
|
||||
|
||||
protected:
|
||||
volatile int _lock;
|
||||
JfrAdaptiveSampler();
|
||||
virtual ~JfrAdaptiveSampler();
|
||||
virtual bool initialize();
|
||||
virtual const JfrSamplerParams& next_window_params(const JfrSamplerWindow* expired) = 0;
|
||||
void reconfigure();
|
||||
|
||||
public:
|
||||
bool sample(int64_t timestamp = 0);
|
||||
};
|
||||
|
||||
/* GTEST support */
|
||||
class JfrGTestFixedRateSampler : public JfrAdaptiveSampler {
|
||||
private:
|
||||
JfrSamplerParams _params;
|
||||
double _sample_size_ewma;
|
||||
public:
|
||||
JfrGTestFixedRateSampler(size_t sample_points_per_window, size_t window_duration_ms, size_t lookback_count);
|
||||
virtual bool initialize();
|
||||
const JfrSamplerParams& next_window_params(const JfrSamplerWindow* expired);
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
|
@ -54,6 +54,7 @@
|
||||
JFR_LOG_TAG(jfr, system, parser) \
|
||||
JFR_LOG_TAG(jfr, system, metadata) \
|
||||
JFR_LOG_TAG(jfr, system, streaming) \
|
||||
JFR_LOG_TAG(jfr, system, throttle) \
|
||||
JFR_LOG_TAG(jfr, metadata) \
|
||||
JFR_LOG_TAG(jfr, event) \
|
||||
JFR_LOG_TAG(jfr, setting) \
|
||||
|
41
src/hotspot/share/jfr/utilities/jfrRandom.hpp
Normal file
41
src/hotspot/share/jfr/utilities/jfrRandom.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, Google and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_JFR_UTILITIES_JFRRANDOM_HPP
|
||||
#define SHARE_JFR_UTILITIES_JFRRANDOM_HPP
|
||||
|
||||
#include "jfr/utilities/jfrAllocation.hpp"
|
||||
|
||||
// Cheap pseudorandom number generator
|
||||
|
||||
class JfrPRNG : public JfrCHeapObj {
|
||||
private:
|
||||
mutable uint64_t _rnd;
|
||||
public:
|
||||
JfrPRNG(const void* seed);
|
||||
double next_uniform() const;
|
||||
};
|
||||
|
||||
#endif // SHARE_JFR_UTILITIES_JFRRANDOM_HPP
|
60
src/hotspot/share/jfr/utilities/jfrRandom.inline.hpp
Normal file
60
src/hotspot/share/jfr/utilities/jfrRandom.inline.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, Google and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_JFR_UTILITIES_JFRRANDOM_INLINE_HPP
|
||||
#define SHARE_JFR_UTILITIES_JFRRANDOM_INLINE_HPP
|
||||
|
||||
#include "jfr/utilities/jfrRandom.hpp"
|
||||
|
||||
inline JfrPRNG::JfrPRNG(const void* seed) : _rnd(reinterpret_cast<uint64_t>(seed)) {
|
||||
assert(seed != NULL, "invariant");
|
||||
}
|
||||
|
||||
// Returns the next prng value.
|
||||
// pRNG is: aX+b mod c with a = 0x5DEECE66D, b = 0xB, c = 1<<48
|
||||
// This is the lrand64 generator.
|
||||
inline uint64_t next(uint64_t rnd) {
|
||||
static const uint64_t PrngMult = 0x5DEECE66DLL;
|
||||
static const uint64_t PrngAdd = 0xB;
|
||||
static const uint64_t PrngModPower = 48;
|
||||
static const uint64_t PrngModMask = (static_cast<uint64_t>(1) << PrngModPower) - 1;
|
||||
return (PrngMult * rnd + PrngAdd) & PrngModMask;
|
||||
}
|
||||
|
||||
inline double JfrPRNG::next_uniform() const {
|
||||
_rnd = next(_rnd);
|
||||
// Take the top 26 bits as the random number
|
||||
// (This plus a 1<<58 sampling bound gives a max possible step of
|
||||
// 5194297183973780480 bytes. In this case,
|
||||
// for sample_parameter = 1<<19, max possible step is
|
||||
// 9448372 bytes (24 bits).
|
||||
static const uint64_t PrngModPower = 48; // Number of bits in prng
|
||||
// The uint32_t cast is to prevent a (hard-to-reproduce) NAN
|
||||
// under piii debug for some binaries.
|
||||
// the n_rand value is between 0 and 2**26-1 so it needs to be normalized by dividing by 2**26 (67108864)
|
||||
return (static_cast<uint32_t>(_rnd >> (PrngModPower - 26)) / static_cast<double>(67108864));
|
||||
}
|
||||
|
||||
#endif // SHARE_JFR_UTILITIES_JFRRANDOM_INLINE_HPP
|
@ -33,20 +33,20 @@
|
||||
class JfrTryLock {
|
||||
private:
|
||||
volatile int* const _lock;
|
||||
bool _has_lock;
|
||||
bool _acquired;
|
||||
|
||||
public:
|
||||
JfrTryLock(volatile int* lock) : _lock(lock), _has_lock(Atomic::cmpxchg(lock, 0, 1) == 0) {}
|
||||
JfrTryLock(volatile int* lock) : _lock(lock), _acquired(Atomic::cmpxchg(lock, 0, 1) == 0) {}
|
||||
|
||||
~JfrTryLock() {
|
||||
if (_has_lock) {
|
||||
if (_acquired) {
|
||||
OrderAccess::fence();
|
||||
*_lock = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_lock() const {
|
||||
return _has_lock;
|
||||
bool acquired() const {
|
||||
return _acquired;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -179,6 +179,7 @@
|
||||
LOG_TAG(task) \
|
||||
DEBUG_ONLY(LOG_TAG(test)) \
|
||||
LOG_TAG(thread) \
|
||||
LOG_TAG(throttle) \
|
||||
LOG_TAG(time) \
|
||||
LOG_TAG(timer) \
|
||||
LOG_TAG(tlab) \
|
||||
|
@ -378,7 +378,7 @@ bool ObjectMonitor::enter(TRAPS) {
|
||||
|
||||
JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);)
|
||||
EventJavaMonitorEnter event;
|
||||
if (event.should_commit()) {
|
||||
if (event.is_started()) {
|
||||
event.set_monitorClass(object()->klass());
|
||||
// Set an address that is 'unique enough', such that events close in
|
||||
// time and with the same address are likely (but not guaranteed) to
|
||||
|
@ -50,6 +50,7 @@ import jdk.jfr.internal.settings.EnabledSetting;
|
||||
import jdk.jfr.internal.settings.PeriodSetting;
|
||||
import jdk.jfr.internal.settings.StackTraceSetting;
|
||||
import jdk.jfr.internal.settings.ThresholdSetting;
|
||||
import jdk.jfr.internal.settings.ThrottleSetting;
|
||||
|
||||
// This class can't have a hard reference from PlatformEventType, since it
|
||||
// holds SettingControl instances that need to be released
|
||||
@ -69,6 +70,7 @@ public final class EventControl {
|
||||
private static final Type TYPE_STACK_TRACE = TypeLibrary.createType(StackTraceSetting.class);
|
||||
private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
|
||||
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
|
||||
private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.class);
|
||||
|
||||
private final ArrayList<SettingInfo> settingInfos = new ArrayList<>();
|
||||
private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
|
||||
@ -76,7 +78,6 @@ public final class EventControl {
|
||||
private final String idName;
|
||||
|
||||
EventControl(PlatformEventType eventType) {
|
||||
addControl(Enabled.NAME, defineEnabled(eventType));
|
||||
if (eventType.hasDuration()) {
|
||||
addControl(Threshold.NAME, defineThreshold(eventType));
|
||||
}
|
||||
@ -89,6 +90,10 @@ public final class EventControl {
|
||||
if (eventType.hasCutoff()) {
|
||||
addControl(Cutoff.NAME, defineCutoff(eventType));
|
||||
}
|
||||
if (eventType.hasThrottle()) {
|
||||
addControl(Throttle.NAME, defineThrottle(eventType));
|
||||
}
|
||||
addControl(Enabled.NAME, defineEnabled(eventType));
|
||||
|
||||
ArrayList<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
|
||||
remove(eventType, aes, Threshold.class);
|
||||
@ -96,6 +101,7 @@ public final class EventControl {
|
||||
remove(eventType, aes, Enabled.class);
|
||||
remove(eventType, aes, StackTrace.class);
|
||||
remove(eventType, aes, Cutoff.class);
|
||||
remove(eventType, aes, Throttle.class);
|
||||
aes.trimToSize();
|
||||
eventType.setAnnotations(aes);
|
||||
this.type = eventType;
|
||||
@ -252,6 +258,15 @@ public final class EventControl {
|
||||
return new Control(new CutoffSetting(type), def);
|
||||
}
|
||||
|
||||
private static Control defineThrottle(PlatformEventType type) {
|
||||
Throttle throttle = type.getAnnotation(Throttle.class);
|
||||
String def = Throttle.DEFAULT;
|
||||
if (throttle != null) {
|
||||
def = throttle.value();
|
||||
}
|
||||
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THROTTLE, Throttle.NAME, def, Collections.emptyList()));
|
||||
return new Control(new ThrottleSetting(type), def);
|
||||
}
|
||||
|
||||
private static Control definePeriod(PlatformEventType type) {
|
||||
Period period = type.getAnnotation(Period.class);
|
||||
|
@ -516,6 +516,18 @@ public final class JVM {
|
||||
*/
|
||||
public native boolean setCutoff(long eventTypeId, long cutoffTicks);
|
||||
|
||||
/**
|
||||
* Sets the event emission rate in event sample size per time unit.
|
||||
*
|
||||
* Determines how events are throttled.
|
||||
*
|
||||
* @param eventTypeId the id of the event type
|
||||
* @param eventSampleSize event sample size
|
||||
* @param period_ms time period in milliseconds
|
||||
* @return true, if it could be set
|
||||
*/
|
||||
public native boolean setThrottle(long eventTypeId, long eventSampleSize, long period_ms);
|
||||
|
||||
/**
|
||||
* Emit old object sample events.
|
||||
*
|
||||
|
@ -66,22 +66,26 @@ public enum LogTag {
|
||||
* Covers streaming (for Hotspot developers)
|
||||
*/
|
||||
JFR_SYSTEM_STREAMING(7),
|
||||
/**
|
||||
* Covers throttling (for Hotspot developers)
|
||||
*/
|
||||
JFR_SYSTEM_THROTTLE(8),
|
||||
/**
|
||||
* Covers metadata for Java user (for Hotspot developers)
|
||||
*/
|
||||
JFR_METADATA(8),
|
||||
JFR_METADATA(9),
|
||||
/**
|
||||
* Covers events (for users of the JDK)
|
||||
*/
|
||||
JFR_EVENT(9),
|
||||
JFR_EVENT(10),
|
||||
/**
|
||||
* Covers setting (for users of the JDK)
|
||||
*/
|
||||
JFR_SETTING(10),
|
||||
JFR_SETTING(11),
|
||||
/**
|
||||
* Covers usage of jcmd with JFR
|
||||
*/
|
||||
JFR_DCMD(11);
|
||||
JFR_DCMD(12);
|
||||
|
||||
/* set from native side */
|
||||
volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized
|
||||
|
@ -81,6 +81,7 @@ public final class MetadataLoader {
|
||||
private final boolean startTime;
|
||||
private final boolean stackTrace;
|
||||
private final boolean cutoff;
|
||||
private final boolean throttle;
|
||||
private final boolean isEvent;
|
||||
private final boolean isRelation;
|
||||
private final boolean experimental;
|
||||
@ -101,6 +102,7 @@ public final class MetadataLoader {
|
||||
startTime = dis.readBoolean();
|
||||
period = dis.readUTF();
|
||||
cutoff = dis.readBoolean();
|
||||
throttle = dis.readBoolean();
|
||||
experimental = dis.readBoolean();
|
||||
id = dis.readLong();
|
||||
isEvent = dis.readBoolean();
|
||||
@ -304,6 +306,9 @@ public final class MetadataLoader {
|
||||
if (t.cutoff) {
|
||||
aes.add(new AnnotationElement(Cutoff.class, Cutoff.INFINITY));
|
||||
}
|
||||
if (t.throttle) {
|
||||
aes.add(new AnnotationElement(Throttle.class, Throttle.DEFAULT));
|
||||
}
|
||||
}
|
||||
if (t.experimental) {
|
||||
aes.add(EXPERIMENTAL);
|
||||
|
@ -76,6 +76,7 @@ public final class MetadataRepository {
|
||||
pEventType.setHasDuration(eventType.getAnnotation(Threshold.class) != null);
|
||||
pEventType.setHasStackTrace(eventType.getAnnotation(StackTrace.class) != null);
|
||||
pEventType.setHasCutoff(eventType.getAnnotation(Cutoff.class) != null);
|
||||
pEventType.setHasThrottle(eventType.getAnnotation(Throttle.class) != null);
|
||||
pEventType.setHasPeriod(eventType.getAnnotation(Period.class) != null);
|
||||
// Must add hook before EventControl is created as it removes
|
||||
// annotations, such as Period and Threshold.
|
||||
|
@ -59,6 +59,7 @@ public final class PlatformEventType extends Type {
|
||||
private boolean hasDuration = true;
|
||||
private boolean hasPeriod = true;
|
||||
private boolean hasCutoff = false;
|
||||
private boolean hasThrottle = false;
|
||||
private boolean isInstrumented;
|
||||
private boolean markForInstrumentation;
|
||||
private boolean registered = true;
|
||||
@ -142,6 +143,10 @@ public final class PlatformEventType extends Type {
|
||||
this.hasCutoff = hasCutoff;
|
||||
}
|
||||
|
||||
public void setHasThrottle(boolean hasThrottle) {
|
||||
this.hasThrottle = hasThrottle;
|
||||
}
|
||||
|
||||
public void setCutoff(long cutoffNanos) {
|
||||
if (isJVM) {
|
||||
long cutoffTicks = Utils.nanosToTicks(cutoffNanos);
|
||||
@ -149,6 +154,12 @@ public final class PlatformEventType extends Type {
|
||||
}
|
||||
}
|
||||
|
||||
public void setThrottle(long eventSampleSize, long period_ms) {
|
||||
if (isJVM) {
|
||||
JVM.getJVM().setThrottle(getId(), eventSampleSize, period_ms);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHasPeriod(boolean hasPeriod) {
|
||||
this.hasPeriod = hasPeriod;
|
||||
}
|
||||
@ -169,6 +180,10 @@ public final class PlatformEventType extends Type {
|
||||
return this.hasCutoff;
|
||||
}
|
||||
|
||||
public boolean hasThrottle() {
|
||||
return this.hasThrottle;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
76
src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java
Normal file
76
src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. 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;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import jdk.jfr.MetadataDefinition;
|
||||
|
||||
/**
|
||||
* Event annotation, determines the event emission rate in events per time unit.
|
||||
*
|
||||
* This setting is only supported for JVM events.
|
||||
*
|
||||
* @since 16
|
||||
*/
|
||||
@MetadataDefinition
|
||||
@Target({ ElementType.TYPE })
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Throttle {
|
||||
/**
|
||||
* Settings name {@code "throttle"} for configuring an event emission rate in events per time unit.
|
||||
*/
|
||||
public final static String NAME = "throttle";
|
||||
public final static String DEFAULT = "off";
|
||||
|
||||
/**
|
||||
* Throttle, for example {@code "100/s"}.
|
||||
* <p>
|
||||
* String representation of a non-negative {@code Long} value followed by a slash ("/")
|
||||
* and one of the following units<br>
|
||||
* {@code "ns"} (nanoseconds)<br>
|
||||
* {@code "us"} (microseconds)<br>
|
||||
* {@code "ms"} (milliseconds)<br>
|
||||
* {@code "s"} (seconds)<br>
|
||||
* {@code "m"} (minutes)<br>
|
||||
* {@code "h"} (hours)<br>
|
||||
* {@code "d"} (days)<br>
|
||||
* <p>
|
||||
* Example values, {@code "6000/m"}, {@code "10/ms"} and {@code "200/s"}.
|
||||
* When zero is specified, for example {@code "0/s"}, no events are emitted.
|
||||
* When {@code "off"} is specified, all events are emitted.
|
||||
*
|
||||
* @return the throttle value, default {@code "off"} not {@code null}
|
||||
*
|
||||
*/
|
||||
String value() default DEFAULT;
|
||||
}
|
@ -49,6 +49,7 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -69,6 +70,7 @@ public final class Utils {
|
||||
|
||||
private static final Object flushObject = new Object();
|
||||
private static final String INFINITY = "infinity";
|
||||
private static final String OFF = "off";
|
||||
public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
|
||||
public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
|
||||
public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
|
||||
@ -78,7 +80,6 @@ public final class Utils {
|
||||
|
||||
private static Boolean SAVE_GENERATED;
|
||||
|
||||
|
||||
private static final Duration MICRO_SECOND = Duration.ofNanos(1_000);
|
||||
private static final Duration SECOND = Duration.ofSeconds(1);
|
||||
private static final Duration MINUTE = Duration.ofMinutes(1);
|
||||
@ -88,6 +89,7 @@ public final class Utils {
|
||||
private static final int MILL_SIGNIFICANT_FIGURES = 3;
|
||||
private static final int DISPLAY_NANO_DIGIT = 3;
|
||||
private static final int BASE = 10;
|
||||
private static long THROTTLE_OFF = -2;
|
||||
|
||||
|
||||
public static void checkAccessFlightRecorder() throws SecurityException {
|
||||
@ -204,6 +206,94 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
enum ThrottleUnit {
|
||||
NANOSECONDS("ns", TimeUnit.NANOSECONDS, TimeUnit.SECONDS.toNanos(1), TimeUnit.SECONDS.toMillis(1)),
|
||||
MICROSECONDS("us", TimeUnit.MICROSECONDS, TimeUnit.SECONDS.toNanos(1) / 1000, TimeUnit.SECONDS.toMillis(1)),
|
||||
MILLISECONDS("ms", TimeUnit.MILLISECONDS, TimeUnit.SECONDS.toMillis(1), TimeUnit.SECONDS.toMillis(1)),
|
||||
SECONDS("s", TimeUnit.SECONDS, 1, TimeUnit.SECONDS.toMillis(1)),
|
||||
MINUTES("m", TimeUnit.MINUTES, 1, TimeUnit.MINUTES.toMillis(1)),
|
||||
HOUR("h", TimeUnit.HOURS, 1, TimeUnit.HOURS.toMillis(1)),
|
||||
DAY("d", TimeUnit.DAYS, 1, TimeUnit.DAYS.toMillis(1));
|
||||
|
||||
private final String text;
|
||||
private final TimeUnit timeUnit;
|
||||
private final long factor;
|
||||
private final long millis;
|
||||
|
||||
ThrottleUnit(String t, TimeUnit u, long factor, long millis) {
|
||||
this.text = t;
|
||||
this.timeUnit = u;
|
||||
this.factor = factor;
|
||||
this.millis = millis;
|
||||
}
|
||||
|
||||
private static ThrottleUnit parse(String s) {
|
||||
if (s.equals(OFF)) {
|
||||
return MILLISECONDS;
|
||||
}
|
||||
return unit(parseThrottleString(s, false));
|
||||
}
|
||||
|
||||
private static ThrottleUnit unit(String s) {
|
||||
if (s.endsWith("ns") || s.endsWith("us") || s.endsWith("ms")) {
|
||||
return value(s.substring(s.length() - 2));
|
||||
}
|
||||
if (s.endsWith("s") || s.endsWith("m") || s.endsWith("h") || s.endsWith("d")) {
|
||||
return value(s.substring(s.length() - 1));
|
||||
}
|
||||
throw new NumberFormatException("'" + s + "' is not a valid time unit.");
|
||||
}
|
||||
|
||||
private static ThrottleUnit value(String s) {
|
||||
for (ThrottleUnit t : values()) {
|
||||
if (t.text.equals(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
throw new NumberFormatException("'" + s + "' is not a valid time unit.");
|
||||
}
|
||||
|
||||
static long asMillis(String s) {
|
||||
return parse(s).millis;
|
||||
}
|
||||
|
||||
static long normalizeValueAsMillis(long value, String s) {
|
||||
return value * parse(s).factor;
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwThrottleNumberFormatException(String s) {
|
||||
throw new NumberFormatException("'" + s + "' is not valid. Should be a non-negative numeric value followed by a delimiter. i.e. '/', and then followed by a unit e.g. 100/s.");
|
||||
}
|
||||
|
||||
// Expected input format is "x/y" where x is a non-negative long
|
||||
// and y is a time unit. Split the string at the delimiter.
|
||||
private static String parseThrottleString(String s, boolean value) {
|
||||
String[] split = s.split("/");
|
||||
if (split.length != 2) {
|
||||
throwThrottleNumberFormatException(s);
|
||||
}
|
||||
return value ? split[0].trim() : split[1].trim();
|
||||
}
|
||||
|
||||
public static long parseThrottleValue(String s) {
|
||||
if (s.equals(OFF)) {
|
||||
return THROTTLE_OFF;
|
||||
}
|
||||
String parsedValue = parseThrottleString(s, true);
|
||||
long normalizedValue = 0;
|
||||
try {
|
||||
normalizedValue = ThrottleUnit.normalizeValueAsMillis(Long.parseLong(parsedValue), s);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throwThrottleNumberFormatException(s);
|
||||
}
|
||||
return normalizedValue;
|
||||
}
|
||||
|
||||
public static long parseThrottleTimeUnit(String s) {
|
||||
return ThrottleUnit.asMillis(s);
|
||||
}
|
||||
|
||||
public static long parseTimespanWithInfinity(String s) {
|
||||
if (INFINITY.equals(s)) {
|
||||
return Long.MAX_VALUE;
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. 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 java.util.concurrent.TimeUnit.MICROSECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import jdk.jfr.Description;
|
||||
import jdk.jfr.Label;
|
||||
import jdk.jfr.MetadataDefinition;
|
||||
import jdk.jfr.Name;
|
||||
import jdk.jfr.Timespan;
|
||||
import jdk.jfr.internal.PlatformEventType;
|
||||
import jdk.jfr.internal.Type;
|
||||
import jdk.jfr.internal.Utils;
|
||||
|
||||
@MetadataDefinition
|
||||
@Label("Event Emission Throttle")
|
||||
@Description("Throttles the emission rate for an event")
|
||||
@Name(Type.SETTINGS_PREFIX + "Throttle")
|
||||
public final class ThrottleSetting extends JDKSettingControl {
|
||||
private final static long typeId = Type.getTypeId(ThrottleSetting.class);
|
||||
private final static long OFF = -2;
|
||||
private String value = "0/s";
|
||||
private final PlatformEventType eventType;
|
||||
|
||||
public ThrottleSetting(PlatformEventType eventType) {
|
||||
this.eventType = Objects.requireNonNull(eventType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String combine(Set<String> values) {
|
||||
long max = OFF;
|
||||
String text = "off";
|
||||
for (String value : values) {
|
||||
long l = parseValueSafe(value);
|
||||
if (l > max) {
|
||||
text = value;
|
||||
max = l;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static long parseValueSafe(String s) {
|
||||
long value = 0L;
|
||||
try {
|
||||
value = Utils.parseThrottleValue(s);
|
||||
} catch (NumberFormatException nfe) {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String s) {
|
||||
long size = 0;
|
||||
long millis = 1000;
|
||||
try {
|
||||
size = Utils.parseThrottleValue(s);
|
||||
millis = Utils.parseThrottleTimeUnit(s);
|
||||
this.value = s;
|
||||
} catch (NumberFormatException nfe) {
|
||||
}
|
||||
eventType.setThrottle(size, millis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static boolean isType(long typeId) {
|
||||
return ThrottleSetting.typeId == typeId;
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +274,7 @@
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectCount">
|
||||
<setting name="enabled" control="memory-profiling-enabled-all">false</setting>
|
||||
<setting name="enabled" control="heap-statistics-enabled">false</setting>
|
||||
<setting name="period">everyChunk</setting>
|
||||
</event>
|
||||
|
||||
@ -443,11 +443,11 @@
|
||||
</event>
|
||||
|
||||
<event name="jdk.PromoteObjectInNewPLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
|
||||
<setting name="enabled" control="promotion-enabled">false</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.PromoteObjectOutsidePLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
|
||||
<setting name="enabled" control="promotion-enabled">false</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.ConcurrentModeFailure">
|
||||
@ -605,12 +605,18 @@
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectAllocationInNewTLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
|
||||
<setting name="enabled">false</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectAllocationOutsideTLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
|
||||
<setting name="enabled">false</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectAllocationSample">
|
||||
<setting name="enabled" control="enable-object-allocation">true</setting>
|
||||
<setting name="throttle" control="object-allocation-rate">150/s</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
@ -826,21 +832,41 @@
|
||||
<test name="gc-level" operator="equal" value="all"/>
|
||||
</condition>
|
||||
|
||||
<selection name="memory-profiling" default="off" label="Memory Profiling">
|
||||
<selection name="memory-profiling" default="low" label="Memory Profiling">
|
||||
<option label="Off" name="off">off</option>
|
||||
<option label="Object Allocation" name="low">low</option>
|
||||
<option label="Object Allocation and Promotion" name="medium">medium</option>
|
||||
<option label="All, including Heap Statistics (May cause long full GCs)" name="all">all</option>
|
||||
</selection>
|
||||
|
||||
<condition name="memory-profiling-enabled-medium" true="true" false="false">
|
||||
<condition name="memory-profiling-enabled-low" true="true" false="false">
|
||||
<test name="memory-profiling" operator="equal" value="low"/>
|
||||
</condition>
|
||||
|
||||
<condition name="object-allocation-enabled" true="true" false="false">
|
||||
<or>
|
||||
<test name="memory-profiling" operator="equal" value="low"/>
|
||||
<test name="memory-profiling" operator="equal" value="medium"/>
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</or>
|
||||
</condition>
|
||||
|
||||
<condition name="object-allocation-rate" true="300/s" false="150/s">
|
||||
<or>
|
||||
<test name="memory-profiling" operator="equal" value="medium"/>
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</or>
|
||||
</condition>
|
||||
|
||||
<condition name="promotion-enabled" true="true" false="false">
|
||||
<or>
|
||||
<test name="memory-profiling" operator="equal" value="medium"/>
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</or>
|
||||
</condition>
|
||||
|
||||
<condition name="memory-profiling-enabled-all" true="true" false="false">
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
<condition name="heap-statistics-enabled" true="true" false="false">
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</condition>
|
||||
|
||||
<selection name="compiler-level" default="normal" label="Compiler">
|
||||
|
@ -274,7 +274,7 @@
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectCount">
|
||||
<setting name="enabled" control="memory-profiling-enabled-all">false</setting>
|
||||
<setting name="enabled" control="heap-statistics-enabled">false</setting>
|
||||
<setting name="period">everyChunk</setting>
|
||||
</event>
|
||||
|
||||
@ -443,11 +443,11 @@
|
||||
</event>
|
||||
|
||||
<event name="jdk.PromoteObjectInNewPLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
|
||||
<setting name="enabled" control="promotion-enabled">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.PromoteObjectOutsidePLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
|
||||
<setting name="enabled" control="promotion-enabled">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.ConcurrentModeFailure">
|
||||
@ -605,12 +605,18 @@
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectAllocationInNewTLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
|
||||
<setting name="enabled">false</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectAllocationOutsideTLAB">
|
||||
<setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
|
||||
<setting name="enabled">false</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
<event name="jdk.ObjectAllocationSample">
|
||||
<setting name="enabled" control="enable-object-allocation">true</setting>
|
||||
<setting name="throttle" control="object-allocation-rate">300/s</setting>
|
||||
<setting name="stackTrace">true</setting>
|
||||
</event>
|
||||
|
||||
@ -826,23 +832,42 @@
|
||||
<test name="gc-level" operator="equal" value="all"/>
|
||||
</condition>
|
||||
|
||||
<selection name="memory-profiling" default="medium" label="Memory Profiling">
|
||||
<selection name="memory-profiling" default="low" label="Memory Profiling">
|
||||
<option label="Off" name="off">off</option>
|
||||
<option label="Object Allocation" name="low">low</option>
|
||||
<option label="Object Allocation and Promotion" name="medium">medium</option>
|
||||
<option label="All, including Heap Statistics (May cause long full GCs)" name="all">all</option>
|
||||
</selection>
|
||||
|
||||
<condition name="memory-profiling-enabled-medium" true="true" false="false">
|
||||
<condition name="memory-profiling-enabled-low" true="true" false="false">
|
||||
<test name="memory-profiling" operator="equal" value="low"/>
|
||||
</condition>
|
||||
|
||||
<condition name="object-allocation-enabled" true="true" false="false">
|
||||
<or>
|
||||
<test name="memory-profiling" operator="equal" value="low"/>
|
||||
<test name="memory-profiling" operator="equal" value="medium"/>
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</or>
|
||||
</condition>
|
||||
|
||||
<condition name="object-allocation-rate" true="300/s" false="150/s">
|
||||
<or>
|
||||
<test name="memory-profiling" operator="equal" value="medium"/>
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</or>
|
||||
</condition>
|
||||
|
||||
<condition name="promotion-enabled" true="true" false="false">
|
||||
<or>
|
||||
<test name="memory-profiling" operator="equal" value="medium"/>
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</or>
|
||||
</condition>
|
||||
|
||||
<condition name="memory-profiling-enabled-all" true="true" false="false">
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
<condition name="heap-statistics-enabled" true="true" false="false">
|
||||
<test name="memory-profiling" operator="equal" value="all"/>
|
||||
</condition>
|
||||
|
||||
<selection name="compiler-level" default="detailed" label="Compiler">
|
||||
<option label="Off" name="off">off</option>
|
||||
<option label="Normal" name="normal">normal</option>
|
||||
|
281
test/hotspot/gtest/jfr/test_adaptiveSampler.cpp
Normal file
281
test/hotspot/gtest/jfr/test_adaptiveSampler.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, Datadog, Inc. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
|
||||
// This test performs mocking of certain JVM functionality. This works by
|
||||
// including the source file under test inside an anonymous namespace (which
|
||||
// prevents linking conflicts) with the mocked symbols redefined.
|
||||
|
||||
// The include list should mirror the one found in the included source file -
|
||||
// with the ones that should pick up the mocks removed. Those should be included
|
||||
// later after the mocks have been defined.
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "jfr/utilities/jfrAllocation.hpp"
|
||||
#include "jfr/utilities/jfrRandom.inline.hpp"
|
||||
#include "jfr/utilities/jfrSpinlockHelper.hpp"
|
||||
#include "jfr/utilities/jfrTime.hpp"
|
||||
#include "jfr/utilities/jfrTimeConverter.hpp"
|
||||
#include "jfr/utilities/jfrTryLock.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
#include "unittest.hpp"
|
||||
|
||||
// #undef SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
|
||||
|
||||
namespace {
|
||||
class MockJfrTimeConverter : public ::JfrTimeConverter {
|
||||
public:
|
||||
static double nano_to_counter_multiplier(bool is_os_time = false) {
|
||||
return 1.0;
|
||||
}
|
||||
static jlong counter_to_nanos(jlong c, bool is_os_time = false) {
|
||||
return c;
|
||||
}
|
||||
static jlong counter_to_millis(jlong c, bool is_os_time = false) {
|
||||
return c * NANOS_PER_MILLISEC;
|
||||
}
|
||||
static jlong nanos_to_countertime(jlong c, bool as_os_time = false) {
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
class MockJfrTickValue {
|
||||
private:
|
||||
jlong _ticks;
|
||||
public:
|
||||
MockJfrTickValue(jlong ticks) : _ticks(ticks) {};
|
||||
jlong value() {
|
||||
return _ticks;
|
||||
}
|
||||
};
|
||||
class MockJfrTicks {
|
||||
public:
|
||||
static jlong tick;
|
||||
static MockJfrTickValue now() {
|
||||
return MockJfrTickValue(tick);
|
||||
}
|
||||
};
|
||||
|
||||
jlong MockJfrTicks::tick = 0;
|
||||
|
||||
// Reincluding source files in the anonymous namespace unfortunately seems to
|
||||
// behave strangely with precompiled headers (only when using gcc though)
|
||||
#ifndef DONT_USE_PRECOMPILED_HEADER
|
||||
#define DONT_USE_PRECOMPILED_HEADER
|
||||
#endif
|
||||
|
||||
#define JfrTicks MockJfrTicks
|
||||
#define JfrTimeConverter MockJfrTimeConverter
|
||||
|
||||
#include "jfr/support/jfrAdaptiveSampler.hpp"
|
||||
#include "jfr/support/jfrAdaptiveSampler.cpp"
|
||||
|
||||
#undef JfrTimeConverter
|
||||
#undef JfrTicks
|
||||
} // anonymous namespace
|
||||
|
||||
class JfrGTestAdaptiveSampling : public ::testing::Test {
|
||||
protected:
|
||||
const int max_population_per_window = 2000;
|
||||
const int min_population_per_window = 2;
|
||||
const int window_count = 10000;
|
||||
const clock_t window_duration_ms = 100;
|
||||
const size_t expected_sample_points_per_window = 50;
|
||||
const size_t expected_sample_points = expected_sample_points_per_window * (size_t)window_count;
|
||||
const size_t window_lookback_count = 50; // 50 windows == 5 seconds (for a window duration of 100 ms)
|
||||
const double max_sample_bias = 0.11;
|
||||
|
||||
void SetUp() {
|
||||
// Ensure that tests are separated in time by spreading them by 24hrs apart
|
||||
MockJfrTicks::tick += (24 * 60 * 60) * NANOSECS_PER_SEC;
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
void assertDistributionProperties(int distr_slots, jlong* population, jlong* sample, size_t population_size, size_t sample_size, const char* msg) {
|
||||
size_t population_sum = 0;
|
||||
size_t sample_sum = 0;
|
||||
for (int i = 0; i < distr_slots; i++) {
|
||||
population_sum += i * population[i];
|
||||
sample_sum += i * sample[i];
|
||||
}
|
||||
|
||||
double population_mean = population_sum / (double)population_size;
|
||||
double sample_mean = sample_sum / (double)sample_size;
|
||||
|
||||
double population_variance = 0;
|
||||
double sample_variance = 0;
|
||||
for (int i = 0; i < distr_slots; i++) {
|
||||
double population_diff = i - population_mean;
|
||||
population_variance = population[i] * population_diff * population_diff;
|
||||
|
||||
double sample_diff = i - sample_mean;
|
||||
sample_variance = sample[i] * sample_diff * sample_diff;
|
||||
}
|
||||
population_variance = population_variance / (population_size - 1);
|
||||
sample_variance = sample_variance / (sample_size - 1);
|
||||
double population_stdev = sqrt(population_variance);
|
||||
double sample_stdev = sqrt(sample_variance);
|
||||
|
||||
// make sure the standard deviation is ok
|
||||
EXPECT_NEAR(population_stdev, sample_stdev, 0.5) << msg;
|
||||
// make sure that the subsampled set mean is within 2-sigma of the original set mean
|
||||
EXPECT_NEAR(population_mean, sample_mean, population_stdev) << msg;
|
||||
// make sure that the original set mean is within 2-sigma of the subsampled set mean
|
||||
EXPECT_NEAR(sample_mean, population_mean, sample_stdev) << msg;
|
||||
}
|
||||
|
||||
typedef size_t(JfrGTestAdaptiveSampling::* incoming)() const;
|
||||
void test(incoming inc, size_t events_per_window, double expectation, const char* description);
|
||||
|
||||
public:
|
||||
size_t incoming_uniform() const {
|
||||
return os::random() % max_population_per_window + min_population_per_window;
|
||||
}
|
||||
|
||||
size_t incoming_bursty_10_percent() const {
|
||||
bool is_burst = (os::random() % 100) < 10; // 10% burst chance
|
||||
return is_burst ? max_population_per_window : min_population_per_window;
|
||||
}
|
||||
|
||||
size_t incoming_bursty_90_percent() const {
|
||||
bool is_burst = (os::random() % 100) < 90; // 90% burst chance
|
||||
return is_burst ? max_population_per_window : min_population_per_window;
|
||||
}
|
||||
|
||||
size_t incoming_low_rate() const {
|
||||
return min_population_per_window;
|
||||
}
|
||||
|
||||
size_t incoming_high_rate() const {
|
||||
return max_population_per_window;
|
||||
}
|
||||
|
||||
size_t incoming_burst_eval(size_t& count, size_t mod_value) const {
|
||||
return count++ % 10 == mod_value ? max_population_per_window : 0;
|
||||
}
|
||||
|
||||
size_t incoming_early_burst() const {
|
||||
static size_t count = 1;
|
||||
return incoming_burst_eval(count, 1);
|
||||
}
|
||||
|
||||
size_t incoming_mid_burst() const {
|
||||
static size_t count = 1;
|
||||
return incoming_burst_eval(count, 5);
|
||||
}
|
||||
|
||||
size_t incoming_late_burst() const {
|
||||
static size_t count = 1;
|
||||
return incoming_burst_eval(count, 0);
|
||||
}
|
||||
};
|
||||
|
||||
void JfrGTestAdaptiveSampling::test(JfrGTestAdaptiveSampling::incoming inc, size_t sample_points_per_window, double error_factor, const char* const description) {
|
||||
assert(description != NULL, "invariant");
|
||||
char output[1024] = "Adaptive sampling: ";
|
||||
strcat(output, description);
|
||||
fprintf(stdout, "=== %s\n", output);
|
||||
jlong population[100] = { 0 };
|
||||
jlong sample[100] = { 0 };
|
||||
::JfrGTestFixedRateSampler sampler = ::JfrGTestFixedRateSampler(expected_sample_points_per_window, window_duration_ms, window_lookback_count);
|
||||
EXPECT_TRUE(sampler.initialize());
|
||||
|
||||
size_t population_size = 0;
|
||||
size_t sample_size = 0;
|
||||
for (int t = 0; t < window_count; t++) {
|
||||
const size_t incoming_events = (this->*inc)();
|
||||
for (size_t i = 0; i < incoming_events; i++) {
|
||||
++population_size;
|
||||
size_t index = os::random() % 100;
|
||||
population[index] += 1;
|
||||
if (sampler.sample()) {
|
||||
++sample_size;
|
||||
sample[index] += 1;
|
||||
}
|
||||
}
|
||||
MockJfrTicks::tick += window_duration_ms * NANOSECS_PER_MILLISEC + 1;
|
||||
sampler.sample(); // window rotation
|
||||
}
|
||||
|
||||
const size_t target_sample_size = sample_points_per_window * window_count;
|
||||
EXPECT_NEAR(target_sample_size, sample_size, expected_sample_points * error_factor) << output;
|
||||
strcat(output, ", hit distribution");
|
||||
assertDistributionProperties(100, population, sample, population_size, sample_size, output);
|
||||
}
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, uniform_rate) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_uniform, expected_sample_points_per_window, 0.05, "random uniform, all samples");
|
||||
}
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, low_rate) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_low_rate, min_population_per_window, 0.05, "low rate");
|
||||
}
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, high_rate) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_high_rate, expected_sample_points_per_window, 0.02, "high rate");
|
||||
}
|
||||
|
||||
// We can think of the windows as splitting up a time period, for example a second (window_duration_ms = 100)
|
||||
// The burst tests for early, mid and late apply a burst rate at a selected window, with other windows having no incoming input.
|
||||
//
|
||||
// - early during the first window of a new time period
|
||||
// - mid during the middle window of a new time period
|
||||
// - late during the last window of a new time period
|
||||
//
|
||||
// The tests verify the total sample size correspond to the selected bursts:
|
||||
//
|
||||
// - early start of a second -> each second will have sampled the window set point for a single window only since no debt has accumulated into the new time period.
|
||||
// - mid middle of the second -> each second will have sampled the window set point + accumulated debt for the first 4 windows.
|
||||
// - late end of the second -> each second will have sampled the window set point + accumulated debt for the first 9 windows (i.e. it will have sampled all)
|
||||
//
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, early_burst) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_early_burst, expected_sample_points_per_window, 0.9, "early burst");
|
||||
}
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, mid_burst) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_mid_burst, expected_sample_points_per_window, 0.5, "mid burst");
|
||||
}
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, late_burst) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_late_burst, expected_sample_points_per_window, 0.0, "late burst");
|
||||
}
|
||||
|
||||
// These are randomized burst tests
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, bursty_rate_10_percent) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_bursty_10_percent, expected_sample_points_per_window, 0.96, "bursty 10%");
|
||||
}
|
||||
|
||||
TEST_VM_F(JfrGTestAdaptiveSampling, bursty_rate_90_percent) {
|
||||
test(&JfrGTestAdaptiveSampling::incoming_bursty_10_percent, expected_sample_points_per_window, 0.96, "bursty 90%");
|
||||
}
|
@ -464,7 +464,7 @@ jdk_jfr_sanity = \
|
||||
jdk/jfr/api/recording/event/TestLoadEventAfterStart.java \
|
||||
jdk/jfr/api/recording/state/TestState.java \
|
||||
jdk/jfr/event/os/TestCPULoad.java \
|
||||
jdk/jfr/event/compiler/TestAllocInNewTLAB.java \
|
||||
jdk/jfr/event/allocation/TestObjectAllocationSampleEvent.java \
|
||||
jdk/jfr/jcmd/TestJcmdStartStopDefault.java \
|
||||
jdk/jfr/event/io/TestFileStreamEvents.java \
|
||||
jdk/jfr/event/compiler/TestCompilerCompile.java \
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2020, 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
|
||||
@ -23,18 +23,15 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.compiler;
|
||||
package jdk.jfr.event.allocation;
|
||||
|
||||
import static java.lang.Math.floor;
|
||||
import static jdk.test.lib.Asserts.assertGreaterThanOrEqual;
|
||||
import static jdk.test.lib.Asserts.assertLessThanOrEqual;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
import jdk.test.lib.Asserts;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -42,8 +39,8 @@ import jdk.test.lib.jfr.Events;
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 jdk.jfr.event.compiler.TestAllocInNewTLAB
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 -Xint jdk.jfr.event.compiler.TestAllocInNewTLAB
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 jdk.jfr.event.allocation.TestObjectAllocationInNewTLABEvent
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 -Xint jdk.jfr.event.allocation.TestObjectAllocationInNewTLABEvent
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -56,56 +53,32 @@ import jdk.test.lib.jfr.Events;
|
||||
* max TLAB waste at refill is set to minimum (-XX:TLABRefillWasteFraction=1),
|
||||
* to provoke a new TLAB creation.
|
||||
*/
|
||||
public class TestAllocInNewTLAB {
|
||||
public class TestObjectAllocationInNewTLABEvent {
|
||||
private final static String EVENT_NAME = EventNames.ObjectAllocationInNewTLAB;
|
||||
|
||||
private static final int BYTE_ARRAY_OVERHEAD = 16; // Extra bytes used by a byte array.
|
||||
private static final int OBJECT_SIZE = 100 * 1024;
|
||||
private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops
|
||||
private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops.
|
||||
private static final int OBJECTS_TO_ALLOCATE = 100;
|
||||
private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
|
||||
private static final int INITIAL_TLAB_SIZE = 100 * 1024;
|
||||
private static int countAllTlabs; // Count all matching tlab allocations.
|
||||
private static int countFullTlabs; // Count matching tlab allocations with full tlab size.
|
||||
|
||||
// make sure allocation isn't dead code eliminated
|
||||
// Make sure allocation isn't dead code eliminated.
|
||||
public static byte[] tmp;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Recording recording = new Recording();
|
||||
recording.enable(EVENT_NAME).withThreshold(Duration.ofMillis(0));
|
||||
|
||||
recording.enable(EVENT_NAME);
|
||||
recording.start();
|
||||
System.gc();
|
||||
for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
|
||||
tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
|
||||
}
|
||||
allocate();
|
||||
recording.stop();
|
||||
|
||||
int countAllTlabs = 0; // Count all matching tlab allocations.
|
||||
int countFullTlabs = 0; // Count matching tlab allocations with full tlab size.
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
if (!EVENT_NAME.equals(event.getEventType().getName())) {
|
||||
continue;
|
||||
}
|
||||
System.out.println("Event:" + event);
|
||||
|
||||
long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
|
||||
long tlabSize = Events.assertField(event, "tlabSize").atLeast(allocationSize).getValue();
|
||||
String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
|
||||
|
||||
boolean isMyEvent = Thread.currentThread().getId() == event.getThread().getJavaThreadId()
|
||||
&& className.equals(BYTE_ARRAY_CLASS_NAME)
|
||||
&& (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT);
|
||||
if (isMyEvent) {
|
||||
countAllTlabs++;
|
||||
if (tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE || tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE_ALT) {
|
||||
countFullTlabs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verifyRecording(recording);
|
||||
int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
|
||||
assertGreaterThanOrEqual(countAllTlabs, minCount, "Too few tlab objects allocated");
|
||||
assertLessThanOrEqual(countAllTlabs, OBJECTS_TO_ALLOCATE, "Too many tlab objects allocated");
|
||||
Asserts.assertGreaterThanOrEqual(countAllTlabs, minCount, "Too few tlab objects allocated");
|
||||
Asserts.assertLessThanOrEqual(countAllTlabs, OBJECTS_TO_ALLOCATE, "Too many tlab objects allocated");
|
||||
|
||||
// For most GCs we expect the size of each tlab to be
|
||||
// INITIAL_TLAB_SIZE + ALLOCATION_SIZE, but that is not always true for G1.
|
||||
@ -119,7 +92,33 @@ public class TestAllocInNewTLAB {
|
||||
// It is only the last tlab in each region that has a smaller size.
|
||||
// This means that at least 50% of the allocated tlabs should
|
||||
// have the expected size (1 full tlab, and 1 fractional tlab).
|
||||
assertGreaterThanOrEqual(2*countFullTlabs, countAllTlabs, "Too many fractional tlabs.");
|
||||
Asserts.assertGreaterThanOrEqual(2*countFullTlabs, countAllTlabs, "Too many fractional tlabs.");
|
||||
}
|
||||
|
||||
private static void allocate() {
|
||||
for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
|
||||
tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyRecording(Recording recording) throws Exception {
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
verify(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verify(RecordedEvent event) {
|
||||
if (Thread.currentThread().getId() != event.getThread().getJavaThreadId()) {
|
||||
return;
|
||||
}
|
||||
long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
|
||||
long tlabSize = Events.assertField(event, "tlabSize").atLeast(allocationSize).getValue();
|
||||
String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
|
||||
if (className.equals(BYTE_ARRAY_CLASS_NAME) && (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT)) {
|
||||
countAllTlabs++;
|
||||
if (tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE || tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE_ALT) {
|
||||
countFullTlabs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2020, 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
|
||||
@ -23,18 +23,15 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.event.compiler;
|
||||
package jdk.jfr.event.allocation;
|
||||
|
||||
import static java.lang.Math.floor;
|
||||
import static jdk.test.lib.Asserts.assertGreaterThanOrEqual;
|
||||
import static jdk.test.lib.Asserts.assertLessThanOrEqual;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
import jdk.test.lib.Asserts;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -42,8 +39,8 @@ import jdk.test.lib.jfr.Events;
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 jdk.jfr.event.compiler.TestAllocOutsideTLAB
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 -Xint jdk.jfr.event.compiler.TestAllocOutsideTLAB
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 jdk.jfr.event.allocation.TestObjectAllocationOutsideTLABEvent
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 -Xint jdk.jfr.event.allocation.TestObjectAllocationOutsideTLABEvent
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -56,7 +53,7 @@ import jdk.test.lib.jfr.Events;
|
||||
* max TLAB waste at refill is set to 256 (-XX:TLABRefillWasteFraction=256),
|
||||
* to prevent a new TLAB creation.
|
||||
*/
|
||||
public class TestAllocOutsideTLAB {
|
||||
public class TestObjectAllocationOutsideTLABEvent {
|
||||
private static final String EVENT_NAME = EventNames.ObjectAllocationOutsideTLAB;
|
||||
|
||||
private static final int BYTE_ARRAY_OVERHEAD = 16; // Extra bytes used by a byte array
|
||||
@ -64,39 +61,43 @@ public class TestAllocOutsideTLAB {
|
||||
private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops
|
||||
private static final int OBJECTS_TO_ALLOCATE = 100;
|
||||
private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
|
||||
private static int eventCount;
|
||||
|
||||
public static byte[] tmp; // Used to prevent optimizer from removing code.
|
||||
// Make sure allocation isn't dead code eliminated.
|
||||
public static byte[] tmp;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Recording recording = new Recording();
|
||||
recording.enable(EVENT_NAME).withThreshold(Duration.ofMillis(0));
|
||||
recording.enable(EVENT_NAME);
|
||||
recording.start();
|
||||
allocate();
|
||||
recording.stop();
|
||||
verifyRecording(recording);
|
||||
int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
|
||||
Asserts.assertGreaterThanOrEqual(eventCount, minCount, "Too few objects allocated");
|
||||
Asserts.assertLessThanOrEqual(eventCount, OBJECTS_TO_ALLOCATE, "Too many objects allocated");
|
||||
}
|
||||
|
||||
private static void allocate() {
|
||||
for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
|
||||
tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
|
||||
}
|
||||
recording.stop();
|
||||
|
||||
int countEvents = 0;
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
if (!EVENT_NAME.equals(event.getEventType().getName())) {
|
||||
continue;
|
||||
}
|
||||
System.out.println("Event:" + event);
|
||||
|
||||
long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
|
||||
String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
|
||||
|
||||
boolean isMyEvent = Thread.currentThread().getId() == event.getThread().getJavaThreadId()
|
||||
&& className.equals(BYTE_ARRAY_CLASS_NAME)
|
||||
&& (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT);
|
||||
if (isMyEvent) {
|
||||
++countEvents;
|
||||
}
|
||||
}
|
||||
|
||||
int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
|
||||
assertGreaterThanOrEqual(countEvents, minCount, "Too few tlab objects allocated");
|
||||
assertLessThanOrEqual(countEvents, OBJECTS_TO_ALLOCATE, "Too many tlab objects allocated");
|
||||
}
|
||||
|
||||
private static void verifyRecording(Recording recording) throws Exception {
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
verify(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verify(RecordedEvent event) {
|
||||
if (Thread.currentThread().getId() != event.getThread().getJavaThreadId()) {
|
||||
return;
|
||||
}
|
||||
long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
|
||||
String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
|
||||
if (className.equals(BYTE_ARRAY_CLASS_NAME) && (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT)) {
|
||||
++eventCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.event.allocation;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Tests ObjectAllocationSampleEvent
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=2k -XX:-ResizeTLAB jdk.jfr.event.allocation.TestObjectAllocationSampleEvent
|
||||
*/
|
||||
public class TestObjectAllocationSampleEvent {
|
||||
private static final String EVENT_NAME = EventNames.ObjectAllocationSample;
|
||||
private static final int OBJECT_SIZE = 4 * 1024;
|
||||
private static final int OBJECTS_TO_ALLOCATE = 16;
|
||||
private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
|
||||
|
||||
// Make sure allocation isn't dead code eliminated.
|
||||
public static byte[] tmp;
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
CountDownLatch delivered = new CountDownLatch(1);
|
||||
Thread current = Thread.currentThread();
|
||||
try (RecordingStream rs = new RecordingStream()) {
|
||||
rs.enable(EVENT_NAME);
|
||||
rs.onEvent(EVENT_NAME, e -> {
|
||||
if (verify(e, current)) {
|
||||
delivered.countDown();
|
||||
}
|
||||
});
|
||||
rs.startAsync();
|
||||
for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
|
||||
tmp = new byte[OBJECT_SIZE];
|
||||
}
|
||||
delivered.await();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean verify(RecordedEvent event, Thread thread) {
|
||||
if (thread.getId() != event.getThread().getJavaThreadId()) {
|
||||
return false;
|
||||
}
|
||||
if (Events.assertField(event, "objectClass.name").notEmpty().getValue().equals(BYTE_ARRAY_CLASS_NAME)) {
|
||||
Events.assertField(event, "weight").atLeast(1L);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.event.allocation;
|
||||
|
||||
import static java.lang.Math.floor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.test.lib.jfr.EventNames;
|
||||
import jdk.test.lib.jfr.Events;
|
||||
import jdk.test.lib.Asserts;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test that when an object is allocated outside a TLAB an event will be triggered.
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseTLAB -XX:TLABSize=2k -XX:-ResizeTLAB jdk.jfr.event.allocation.TestObjectAllocationSampleEventThrottling
|
||||
*/
|
||||
|
||||
public class TestObjectAllocationSampleEventThrottling {
|
||||
private static final String EVENT_NAME = EventNames.ObjectAllocationSample;
|
||||
|
||||
private static final int BYTE_ARRAY_OVERHEAD = 16; // Extra bytes used by a byte array
|
||||
private static final int OBJECT_SIZE = 100 * 1024;
|
||||
private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops
|
||||
private static final int OBJECTS_TO_ALLOCATE = 100;
|
||||
private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
|
||||
private static int eventCount;
|
||||
|
||||
// Make sure allocation isn't dead code eliminated.
|
||||
public static byte[] tmp;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testZeroPerSecond();
|
||||
testThrottleSettings();
|
||||
}
|
||||
|
||||
private static void testZeroPerSecond() throws Exception {
|
||||
Recording r1 = new Recording();
|
||||
setThrottle(r1, "0/s");
|
||||
r1.start();
|
||||
allocate();
|
||||
r1.stop();
|
||||
List<RecordedEvent> events = Events.fromRecording(r1);
|
||||
Asserts.assertTrue(events.isEmpty(), "throttle rate 0/s should not emit any events");
|
||||
}
|
||||
|
||||
private static void testThrottleSettings() throws Exception {
|
||||
Recording r1 = new Recording();
|
||||
// 0/s will not emit any events
|
||||
setThrottle(r1, "0/s");
|
||||
r1.start();
|
||||
Recording r2 = new Recording();
|
||||
// 1/ns is a *very* high emit rate, it should trump the previous 0/s value
|
||||
// to allow the allocation sample events to be recorded.
|
||||
setThrottle(r2, "1/ns");
|
||||
r2.start();
|
||||
allocate();
|
||||
r2.stop();
|
||||
r1.stop();
|
||||
verifyRecording(r2);
|
||||
int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
|
||||
Asserts.assertGreaterThanOrEqual(eventCount, minCount, "Too few object samples allocated");
|
||||
List<RecordedEvent> events = Events.fromRecording(r1);
|
||||
Asserts.assertFalse(events.isEmpty(), "r1 should also have events");
|
||||
}
|
||||
|
||||
private static void setThrottle(Recording recording, String rate) {
|
||||
recording.enable(EVENT_NAME).with("throttle", rate);
|
||||
}
|
||||
|
||||
private static void allocate() {
|
||||
for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
|
||||
tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyRecording(Recording recording) throws Exception {
|
||||
for (RecordedEvent event : Events.fromRecording(recording)) {
|
||||
verify(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verify(RecordedEvent event) {
|
||||
if (Thread.currentThread().getId() != event.getThread().getJavaThreadId()) {
|
||||
return;
|
||||
}
|
||||
if (Events.assertField(event, "objectClass.name").notEmpty().getValue().equals(BYTE_ARRAY_CLASS_NAME)) {
|
||||
Events.assertField(event, "weight").atLeast(1L);
|
||||
++eventCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -165,6 +165,7 @@ public class EventNames {
|
||||
public final static String CodeCacheFull = PREFIX + "CodeCacheFull";
|
||||
public final static String ObjectAllocationInNewTLAB = PREFIX + "ObjectAllocationInNewTLAB";
|
||||
public final static String ObjectAllocationOutsideTLAB = PREFIX + "ObjectAllocationOutsideTLAB";
|
||||
public final static String ObjectAllocationSample = PREFIX + "ObjectAllocationSample";
|
||||
public final static String Deoptimization = PREFIX + "Deoptimization";
|
||||
|
||||
// OS
|
||||
|
Loading…
x
Reference in New Issue
Block a user