Skip to content

Commit 6680cf5

Browse files
authored
feat(profiler): wall-clock signal suppression for idle threads (#560)
* feat: wall-clock precheck and signal suppression * fix * fix: fix build + tests * fix: fix mem leaks in tests * fix: track wall precheck block state in thread filter * fix: arm wall precheck after recording sample * fix: include wait states in wall precheck suppression * Fix ProfiledThread ownership in park_state_ut * Add Java block-state bridge for wall-clock precheck * Fix wall-clock thread filter reset * Gate wall-clock precheck on untraced context * fix: avoid exact suppression for unowned blocked states * fix: address ownership correctness review * fix: address thread filter review * fix: factorize code and add support for jvmti * fix: fix wall-clock counters and misleading comment * fix: apply review about test and unused stuff * fix: apply review about drainSuppressedSampledRun * fix: clean up branch based on PR review recommendations * fix: remove TaskBlock snapshot mechanism * fix: preserve wall precheck tail weight * fix: cover wall precheck tail flush in test * fix: internalize owned blocking hooks
1 parent b045bb2 commit 6680cf5

30 files changed

Lines changed: 2176 additions & 81 deletions

ddprof-lib/src/main/cpp/arguments.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,14 @@ Error Arguments::parse(const char *args) {
379379
_jvmtistacks = true;
380380
}
381381

382+
CASE("wallprecheck")
383+
if (value != NULL) {
384+
_wall_precheck = strcmp(value, "false") != 0 && strcmp(value, "0") != 0;
385+
} else {
386+
// No value means enable
387+
_wall_precheck = true;
388+
}
389+
382390
CASE("wallsampler")
383391
if (value != NULL) {
384392
switch (value[0]) {

ddprof-lib/src/main/cpp/arguments.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class Arguments {
168168
long _cpu;
169169
long _wall;
170170
bool _wall_collapsing;
171+
bool _wall_precheck;
171172
int _wall_threads_per_tick;
172173
WallclockSampler _wallclock_sampler;
173174
long _memory;
@@ -207,6 +208,7 @@ class Arguments {
207208
_cpu(-1),
208209
_wall(-1),
209210
_wall_collapsing(false),
211+
_wall_precheck(false),
210212
_wall_threads_per_tick(DEFAULT_WALL_THREADS_PER_TICK),
211213
_wallclock_sampler(ASGCT),
212214
_memory(-1),

ddprof-lib/src/main/cpp/counters.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
X(AGCT_NATIVE_NO_JAVA_CONTEXT, "agct_native_no_java_context") \
6666
X(AGCT_BLOCKED_IN_VM, "agct_blocked_in_vm") \
6767
X(SKIPPED_WALLCLOCK_UNWINDS, "skipped_wallclock_unwinds") \
68+
X(WC_SIGNAL_SUPPRESSED_SAMPLED_RUN, "wc_signals_suppressed_sampled_run") \
69+
X(WC_UNOWNED_BLOCKED_SUPPRESSED, "wc_unowned_blocked_suppressed") \
70+
X(WC_UNOWNED_BLOCKED_RECORDED, "wc_unowned_blocked_recorded") \
71+
X(WC_SIGNAL_QUEUE_FULL, "wc_signals_queue_full") \
6872
X(UNWINDING_TIME_ASYNC, "unwinding_ticks_async") \
6973
X(UNWINDING_TIME_JVMTI, "unwinding_ticks_jvmti") \
7074
X(CALLTRACE_STORAGE_DROPPED, "calltrace_storage_dropped_traces") \

ddprof-lib/src/main/cpp/event.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,13 @@ class WallClockEpochEvent {
122122
u32 _num_failed_samples;
123123
u32 _num_exited_threads;
124124
u32 _num_permission_denied;
125+
u64 _num_suppressed_sampled_run;
125126

126127
WallClockEpochEvent(u64 start_time)
127128
: _dirty(false), _start_time(start_time), _duration_millis(0),
128129
_num_samplable_threads(0), _num_successful_samples(0),
129130
_num_failed_samples(0), _num_exited_threads(0),
130-
_num_permission_denied(0) {}
131+
_num_permission_denied(0), _num_suppressed_sampled_run(0) {}
131132

132133
bool hasChanged() { return _dirty; }
133134

@@ -166,13 +167,21 @@ class WallClockEpochEvent {
166167
}
167168
}
168169

170+
void addNumSuppressedSampledRun(u64 n) {
171+
if (n > 0) {
172+
_dirty = true;
173+
_num_suppressed_sampled_run += n;
174+
}
175+
}
176+
169177
void endEpoch(u64 millis) { _duration_millis = millis; }
170178

171179
void clean() { _dirty = false; }
172180

173181
void newEpoch(u64 start_time) {
174182
_dirty = false;
175183
_start_time = start_time;
184+
_num_suppressed_sampled_run = 0;
176185
}
177186
};
178187

ddprof-lib/src/main/cpp/flightRecorder.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,7 @@ void Recording::recordWallClockEpoch(Buffer *buf, WallClockEpochEvent *event) {
18221822
buf->putVar64(event->_num_failed_samples);
18231823
buf->putVar64(event->_num_exited_threads);
18241824
buf->putVar64(event->_num_permission_denied);
1825+
buf->putVar64(event->_num_suppressed_sampled_run);
18251826
writeEventSizePrefix(buf, start);
18261827
flushIfNeeded(buf);
18271828
}
@@ -2102,7 +2103,7 @@ void FlightRecorder::recordHeapUsage(int lock_index, long value, bool live) {
21022103
}
21032104
}
21042105

2105-
void FlightRecorder::recordEvent(int lock_index, int tid, u64 call_trace_id,
2106+
bool FlightRecorder::recordEvent(int lock_index, int tid, u64 call_trace_id,
21062107
int event_type, Event *event) {
21072108
OptionalSharedLockGuard locker(&_rec_lock);
21082109
if (locker.ownsLock()) {
@@ -2137,16 +2138,20 @@ void FlightRecorder::recordEvent(int lock_index, int tid, u64 call_trace_id,
21372138
case BCI_NATIVE_SOCKET:
21382139
rec->recordNativeSocketSample(buf, tid, call_trace_id, (NativeSocketEvent *)event);
21392140
break;
2141+
default:
2142+
return false;
21402143
}
21412144
rec->flushIfNeeded(buf);
21422145
rec->addThread(lock_index, tid);
2146+
return true;
21432147
}
21442148
} else {
21452149
Counters::increment(SAMPLES_DROPPED_REC_LOCK);
21462150
}
2151+
return false;
21472152
}
21482153

2149-
void FlightRecorder::recordEventDelegated(int lock_index, int tid,
2154+
bool FlightRecorder::recordEventDelegated(int lock_index, int tid,
21502155
u64 correlation_id, int event_type,
21512156
Event *event) {
21522157
OptionalSharedLockGuard locker(&_rec_lock);
@@ -2165,14 +2170,16 @@ void FlightRecorder::recordEventDelegated(int lock_index, int tid,
21652170
break;
21662171
default:
21672172
// Delegation is only wired for CPU/wall samples in v1.
2168-
break;
2173+
return false;
21692174
}
21702175
rec->flushIfNeeded(buf);
21712176
rec->addThread(lock_index, tid);
2177+
return true;
21722178
}
21732179
} else {
21742180
Counters::increment(SAMPLES_DROPPED_REC_LOCK);
21752181
}
2182+
return false;
21762183
}
21772184

21782185
void FlightRecorder::recordLog(LogLevel level, const char *message,

ddprof-lib/src/main/cpp/flightRecorder.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,14 +446,14 @@ class FlightRecorder {
446446

447447
bool active() const { return _rec != NULL; }
448448

449-
void recordEvent(int lock_index, int tid, u64 call_trace_id, int event_type,
449+
bool recordEvent(int lock_index, int tid, u64 call_trace_id, int event_type,
450450
Event *event);
451451

452452
// Emit a BCI_CPU / BCI_WALL sample with no stack-trace attached to our
453453
// recording. `correlation_id` is the same jlong passed to the HotSpot
454454
// RequestStackTrace extension so downstream tooling can join our event with
455455
// the JVM-emitted jdk.StackTraceRequest.
456-
void recordEventDelegated(int lock_index, int tid, u64 correlation_id,
456+
bool recordEventDelegated(int lock_index, int tid, u64 correlation_id,
457457
int event_type, Event *event);
458458

459459
void recordLog(LogLevel level, const char *message, size_t len);

ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
#include "jvmThread.h"
1212

1313
VMThread* VMThread::current() {
14+
assert(VM::isHotspot());
1415
return VMThread::cast(JVMThread::current());
1516
}
1617

1718
VMThread* VMThread::fromJavaThread(JNIEnv* env, jthread thread) {
19+
assert(VM::isHotspot());
1820
assert(_eetop != nullptr);
1921
if (_eetop != nullptr) {
2022
jlong eetop = env->GetLongField(thread, _eetop);

ddprof-lib/src/main/cpp/javaApi.cpp

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#include "counters.h"
2525
#include "common.h"
2626
#include "engine.h"
27-
#include "hotspot/vmStructs.h"
27+
#include "hotspot/vmStructs.inline.h"
2828
#include "incbin.h"
2929
#include "jvmThread.h"
3030
#include "os.h"
@@ -155,10 +155,13 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
155155
slot_id = thread_filter->registerThread();
156156
current->setFilterSlotId(slot_id);
157157
}
158-
158+
159159
if (unlikely(slot_id == -1)) {
160160
return; // Failed to register thread
161161
}
162+
// Reset suppression state so a new thread occupying this slot does not inherit
163+
// stale state from its predecessor. Must happen before add().
164+
thread_filter->resetSlotRunState(slot_id);
162165
thread_filter->add(tid, slot_id);
163166
}
164167

@@ -314,6 +317,95 @@ Java_com_datadoghq_profiler_JavaProfiler_recordQueueEnd0(
314317
Profiler::instance()->recordQueueTime(tid, &event);
315318
}
316319

320+
extern "C" DLLEXPORT void JNICALL
321+
Java_com_datadoghq_profiler_JavaProfiler_parkEnter0(JNIEnv *env, jclass unused) {
322+
ProfiledThread *current = ProfiledThread::current();
323+
if (current == nullptr) {
324+
return;
325+
}
326+
bool first_park = current->parkEnter();
327+
ThreadFilter *tf = Profiler::instance()->threadFilter();
328+
if (first_park && tf->enabled()) {
329+
ThreadFilter::SlotID slot_id = current->filterSlotId();
330+
if (slot_id >= 0) {
331+
current->setParkBlockToken(
332+
tf->enterBlockedRun(slot_id, OSThreadState::CONDVAR_WAIT));
333+
}
334+
}
335+
}
336+
337+
extern "C" DLLEXPORT void JNICALL
338+
Java_com_datadoghq_profiler_JavaProfiler_parkExit0(
339+
JNIEnv *env, jclass unused, jlong blocker, jlong unblockingSpanId) {
340+
ProfiledThread *current = ProfiledThread::current();
341+
if (current == nullptr) {
342+
return;
343+
}
344+
u64 park_block_token = 0;
345+
if (!current->parkExit(park_block_token) || park_block_token == 0) {
346+
return;
347+
}
348+
ThreadFilter *tf = Profiler::instance()->threadFilter();
349+
if (tf->enabled()) {
350+
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(park_block_token);
351+
if (current->filterSlotId() == slot_id) {
352+
tf->exitBlockedRun(slot_id, ThreadFilter::tokenGeneration(park_block_token));
353+
}
354+
}
355+
}
356+
357+
static bool decodeJavaBlockState(jint state, OSThreadState &decoded) {
358+
if (state == static_cast<jint>(OSThreadState::SLEEPING)) {
359+
decoded = OSThreadState::SLEEPING;
360+
return true;
361+
}
362+
decoded = OSThreadState::UNKNOWN;
363+
return false;
364+
}
365+
366+
extern "C" DLLEXPORT jlong JNICALL
367+
Java_com_datadoghq_profiler_JavaProfiler_blockEnter0(
368+
JNIEnv *env, jclass unused, jint state) {
369+
OSThreadState decoded;
370+
if (!decodeJavaBlockState(state, decoded)) {
371+
return 0;
372+
}
373+
ProfiledThread *current = ProfiledThread::current();
374+
if (current == nullptr) {
375+
return 0;
376+
}
377+
ThreadFilter *tf = Profiler::instance()->threadFilter();
378+
if (!tf->enabled()) {
379+
return 0;
380+
}
381+
ThreadFilter::SlotID slot_id = current->filterSlotId();
382+
if (slot_id < 0) {
383+
return 0;
384+
}
385+
return static_cast<jlong>(tf->enterBlockedRun(slot_id, decoded));
386+
}
387+
388+
extern "C" DLLEXPORT void JNICALL
389+
Java_com_datadoghq_profiler_JavaProfiler_blockExit0(
390+
JNIEnv *env, jclass unused, jlong token) {
391+
u64 block_token = static_cast<u64>(token);
392+
if (block_token == 0) {
393+
return;
394+
}
395+
ProfiledThread *current = ProfiledThread::current();
396+
if (current == nullptr) {
397+
return;
398+
}
399+
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(block_token);
400+
if (current->filterSlotId() != slot_id) {
401+
return;
402+
}
403+
ThreadFilter *tf = Profiler::instance()->threadFilter();
404+
if (tf->enabled()) {
405+
tf->exitBlockedRun(slot_id, ThreadFilter::tokenGeneration(block_token));
406+
}
407+
}
408+
317409
extern "C" DLLEXPORT jlong JNICALL
318410
Java_com_datadoghq_profiler_JavaProfiler_currentTicks0(JNIEnv *env,
319411
jclass unused) {

ddprof-lib/src/main/cpp/jfrMetadata.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ void JfrMetadata::initialize(
155155
<< field("numExitedThreads", T_INT,
156156
"Number of Exited Threads Before Handling Signal")
157157
<< field("numPermissionDenied", T_INT,
158-
"Number of Permission Denied Errors"))
158+
"Number of Permission Denied Errors")
159+
<< field("numSuppressedSampledRun", T_LONG,
160+
"Signals suppressed by the wall-clock once-per-run filter"))
159161

160162
<< (type("datadog.ObjectSample", T_ALLOC, "Allocation sample")
161163
<< category("Datadog", "Profiling")

0 commit comments

Comments
 (0)