Skip to content

Commit f327184

Browse files
committed
feat: add paired TaskBlock lifecycle API
1 parent c133852 commit f327184

6 files changed

Lines changed: 264 additions & 451 deletions

File tree

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

Lines changed: 71 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -396,20 +396,6 @@ static bool decodeJavaBlockState(jint state, OSThreadState &decoded) {
396396
return false;
397397
}
398398

399-
static OSThreadState decodeTaskBlockObservedState(jint state) {
400-
switch (static_cast<OSThreadState>(state)) {
401-
case OSThreadState::MONITOR_WAIT:
402-
case OSThreadState::CONDVAR_WAIT:
403-
case OSThreadState::OBJECT_WAIT:
404-
case OSThreadState::SLEEPING:
405-
case OSThreadState::SYSCALL:
406-
case OSThreadState::IO_WAIT:
407-
return static_cast<OSThreadState>(state);
408-
default:
409-
return OSThreadState::UNKNOWN;
410-
}
411-
}
412-
413399
static bool snapshotAndExitBlockedRun(jlong token, BlockRunSnapshot *snapshot) {
414400
if (token <= 0) {
415401
return false;
@@ -433,23 +419,6 @@ static bool snapshotAndExitBlockedRun(jlong token, BlockRunSnapshot *snapshot) {
433419
return tf->exitBlockedRun(slot_id, generation);
434420
}
435421

436-
static void writeBlockRunSnapshot(JNIEnv *env, jlongArray snapshot_array,
437-
const BlockRunSnapshot *snapshot) {
438-
if (snapshot_array == nullptr || env->GetArrayLength(snapshot_array) < 3) {
439-
return;
440-
}
441-
jlong values[3] = {0, 0, static_cast<jlong>(OSThreadState::UNKNOWN)};
442-
if (snapshot != nullptr) {
443-
OSThreadState observed_state = snapshot->sampled_state != OSThreadState::UNKNOWN
444-
? snapshot->sampled_state
445-
: snapshot->active_state;
446-
values[0] = snapshot->has_stack_reference ? static_cast<jlong>(snapshot->call_trace_id) : 0;
447-
values[1] = snapshot->has_stack_reference ? static_cast<jlong>(snapshot->correlation_id) : 0;
448-
values[2] = static_cast<jlong>(observed_state);
449-
}
450-
env->SetLongArrayRegion(snapshot_array, 0, 3, values);
451-
}
452-
453422
extern "C" DLLEXPORT jlong JNICALL
454423
Java_com_datadoghq_profiler_JavaProfiler_blockEnter0(
455424
JNIEnv *env, jclass unused, jint state) {
@@ -479,18 +448,79 @@ Java_com_datadoghq_profiler_JavaProfiler_blockExit0(
479448
snapshotAndExitBlockedRun(token, nullptr);
480449
}
481450

482-
extern "C" DLLEXPORT void JNICALL
483-
Java_com_datadoghq_profiler_JavaProfiler_blockExitWithSnapshot0(
484-
JNIEnv *env, jclass unused, jlong token, jlongArray snapshotArray) {
451+
extern "C" DLLEXPORT jlong JNICALL
452+
Java_com_datadoghq_profiler_JavaProfiler_beginTaskBlock0(
453+
JNIEnv *env, jclass unused, jint state) {
454+
OSThreadState decoded;
455+
if (!decodeJavaBlockState(state, decoded)) {
456+
return 0;
457+
}
458+
ProfiledThread* current = ProfiledThread::current();
459+
if (current == nullptr) {
460+
return 0;
461+
}
462+
if (VM::isHotspot() && VM::hotspot_version() >= 21) {
463+
VMThread* carrier = VMThread::current();
464+
if (carrier != nullptr && carrier->isCarryingVirtualThread()) {
465+
return 0;
466+
}
467+
}
468+
Profiler* profiler = Profiler::instance();
469+
if (!profiler->isRunning() || !profiler->taskBlockAsyncActive()) {
470+
return 0;
471+
}
472+
ThreadFilter* tf = profiler->threadFilter();
473+
ThreadFilter::SlotID slot_id = current->filterSlotId();
474+
if (!tf->enabled() || slot_id < 0) {
475+
return 0;
476+
}
477+
u64 token = tf->enterBlockedRun(slot_id, decoded, BlockRunOwner::JAVA);
478+
if (!current->taskBlockEnter(token, TSC::ticks(), ContextApi::snapshot())) {
479+
tf->exitBlockedRun(slot_id, ThreadFilter::tokenGeneration(token));
480+
return 0;
481+
}
482+
return static_cast<jlong>(token);
483+
}
484+
485+
extern "C" DLLEXPORT jboolean JNICALL
486+
Java_com_datadoghq_profiler_JavaProfiler_endTaskBlock0(
487+
JNIEnv *env, jclass unused, jlong token, jlong blocker,
488+
jlong unblockingSpanId) {
489+
ProfiledThread* current = ProfiledThread::current();
490+
if (current == nullptr || token <= 0) {
491+
return JNI_FALSE;
492+
}
493+
u64 start_ticks = 0;
494+
Context context{};
495+
if (!current->taskBlockExit(static_cast<u64>(token), start_ticks, context)) {
496+
return JNI_FALSE;
497+
}
498+
Profiler* profiler = Profiler::instance();
499+
bool activity = profiler->tryEnterTaskBlockActivity();
500+
if (!activity) {
501+
profiler->waitForTaskBlockRotation();
502+
}
503+
ThreadFilter* tf = profiler->threadFilter();
504+
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(static_cast<u64>(token));
485505
BlockRunSnapshot snapshot{};
486-
snapshot.active_state = OSThreadState::UNKNOWN;
487-
snapshot.sampled_state = OSThreadState::UNKNOWN;
488-
snapshot.owner = BlockRunOwner::NONE;
489-
if (snapshotAndExitBlockedRun(token, &snapshot)) {
490-
writeBlockRunSnapshot(env, snapshotArray, &snapshot);
491-
} else {
492-
writeBlockRunSnapshot(env, snapshotArray, nullptr);
506+
bool exited = current->filterSlotId() == slot_id &&
507+
tf->snapshotAndExitBlockedRun(
508+
slot_id, ThreadFilter::tokenGeneration(static_cast<u64>(token)), &snapshot);
509+
if (!activity) {
510+
Counters::increment(TASK_BLOCK_DROPPED_ROTATION);
511+
return JNI_FALSE;
512+
}
513+
bool recorded = false;
514+
if (exited) {
515+
OSThreadState observed_state = snapshot.sampled_state != OSThreadState::UNKNOWN
516+
? snapshot.sampled_state : snapshot.active_state;
517+
recorded = recordTaskBlockWithStackReferenceIfEligible(
518+
current->tid(), start_ticks, TSC::ticks(), context, (u64)blocker,
519+
(u64)unblockingSpanId, snapshot.call_trace_id,
520+
snapshot.correlation_id, observed_state, true);
493521
}
522+
profiler->leaveTaskBlockActivity();
523+
return recorded ? JNI_TRUE : JNI_FALSE;
494524
}
495525

496526
extern "C" DLLEXPORT jboolean JNICALL
@@ -528,40 +558,6 @@ Java_com_datadoghq_profiler_JavaProfiler_recordTaskBlockWithContext0(
528558
: JNI_FALSE;
529559
}
530560

531-
extern "C" DLLEXPORT jboolean JNICALL
532-
Java_com_datadoghq_profiler_JavaProfiler_recordTaskBlockFromContext0(
533-
JNIEnv *env, jclass unused, jint tid, jlong startTicks, jlong endTicks,
534-
jlong blocker, jlong unblockingSpanId, jlong spanId, jlong rootSpanId) {
535-
// Drain-thread path: called from background drain thread on behalf of the sleeping thread.
536-
// tid is the OS tid of the sleeping thread; span context is explicit to bypass OTEP TLS.
537-
if ((int)tid < 0) {
538-
return JNI_FALSE;
539-
}
540-
Context ctx{(u64)spanId, (u64)rootSpanId};
541-
return recordTaskBlockWithStackReferenceIfEligible(
542-
(int)tid, (u64)startTicks, (u64)endTicks, ctx, (u64)blocker,
543-
(u64)unblockingSpanId, 0, 0, OSThreadState::UNKNOWN)
544-
? JNI_TRUE
545-
: JNI_FALSE;
546-
}
547-
548-
extern "C" DLLEXPORT jboolean JNICALL
549-
Java_com_datadoghq_profiler_JavaProfiler_recordTaskBlockFromContextWithStackReference0(
550-
JNIEnv *env, jclass unused, jint tid, jlong startTicks, jlong endTicks,
551-
jlong blocker, jlong unblockingSpanId, jlong spanId, jlong rootSpanId,
552-
jlong callTraceId, jlong correlationId, jint observedBlockingState) {
553-
if ((int)tid < 0) {
554-
return JNI_FALSE;
555-
}
556-
Context ctx{(u64)spanId, (u64)rootSpanId};
557-
return recordTaskBlockWithStackReferenceIfEligible(
558-
(int)tid, (u64)startTicks, (u64)endTicks, ctx, (u64)blocker,
559-
(u64)unblockingSpanId, (u64)callTraceId, (u64)correlationId,
560-
decodeTaskBlockObservedState(observedBlockingState))
561-
? JNI_TRUE
562-
: JNI_FALSE;
563-
}
564-
565561
extern "C" DLLEXPORT jlong JNICALL
566562
Java_com_datadoghq_profiler_JavaProfiler_currentTicks0(JNIEnv *env,
567563
jclass unused) {

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class ProfiledThread : public ThreadLocalData {
8484
u64 _park_start_ticks;
8585
u64 _park_block_token;
8686
Context _park_context;
87+
u64 _task_block_start_ticks;
88+
u64 _task_block_token;
89+
Context _task_block_context;
8790
u64 _monitor_start_ticks;
8891
Context _monitor_context;
8992
u64 _monitor_blocker;
@@ -110,6 +113,7 @@ class ProfiledThread : public ThreadLocalData {
110113
: ThreadLocalData(), _jmp_buf(nullptr), _pc(0), _sp(0), _span_id(0), _crash_depth(0), _tid(tid), _cpu_epoch(0),
111114
_wall_epoch(0), _call_trace_id(0), _recording_epoch(0), _misc_flags(0),
112115
_park_start_ticks(0), _park_block_token(0), _park_context{},
116+
_task_block_start_ticks(0), _task_block_token(0), _task_block_context{},
113117
_monitor_start_ticks(0), _monitor_context{}, _monitor_blocker(0),
114118
_monitor_block_token(0), _monitor_block_state(OSThreadState::UNKNOWN),
115119
_filter_slot_id(-1), _init_window(0),
@@ -350,6 +354,26 @@ class ProfiledThread : public ThreadLocalData {
350354
return false;
351355
}
352356

357+
inline bool taskBlockEnter(u64 token, u64 start_ticks, const Context& context) {
358+
if (token == 0 || _task_block_token != 0) {
359+
return false;
360+
}
361+
_task_block_start_ticks = start_ticks;
362+
_task_block_context = context;
363+
_task_block_token = token;
364+
return true;
365+
}
366+
367+
inline bool taskBlockExit(u64 token, u64& start_ticks, Context& context) {
368+
if (token == 0 || _task_block_token != token) {
369+
return false;
370+
}
371+
start_ticks = _task_block_start_ticks;
372+
context = _task_block_context;
373+
_task_block_token = 0;
374+
return true;
375+
}
376+
353377
inline void setParkBlockToken(u64 token) {
354378
_park_block_token = token;
355379
}

ddprof-lib/src/main/java/com/datadoghq/profiler/JavaProfiler.java

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -397,16 +397,6 @@ void blockExit(long token) {
397397
blockExit0(token);
398398
}
399399

400-
/**
401-
* Clears a blocked interval and snapshots reconstruction metadata before native state is reset.
402-
*
403-
* @param token opaque token returned by {@link #blockEnter(int)}
404-
* @param snapshot output array: [callTraceId, correlationId, observedBlockingState]
405-
*/
406-
public void blockExit(long token, long[] snapshot) {
407-
blockExitWithSnapshot0(token, snapshot);
408-
}
409-
410400
/**
411401
* Get the ticks for the current thread.
412402
* @return ticks
@@ -415,13 +405,6 @@ public long getCurrentTicks() {
415405
return currentTicks0();
416406
}
417407

418-
/**
419-
* Returns the OS-level thread ID (tid) of the calling thread.
420-
*/
421-
public int getCurrentThreadId() {
422-
return getTid0();
423-
}
424-
425408
/**
426409
* Reports whether native {@code Object.wait} TaskBlock callbacks were disabled in favor of
427410
* Java-owned wait instrumentation. Synchronized monitor contention remains native-owned.
@@ -431,10 +414,29 @@ public boolean isMonitorEventsDelegated() {
431414
}
432415

433416
/**
434-
* Returns the TSC frequency in Hz (ticks per second).
417+
* Begins an explicitly instrumented blocking interval on the current platform thread.
418+
* The returned token is bound to the current thread and must be passed to
419+
* {@link #endTaskBlock(long, long, long)}.
420+
*
421+
* @param state native {@code OSThreadState} value; currently only {@code SLEEPING} is accepted
422+
* @return an opaque token, or {@code 0} when the interval could not be armed
423+
*/
424+
public long beginTaskBlock(int state) {
425+
return beginTaskBlock0(state);
426+
}
427+
428+
/**
429+
* Ends a blocking interval created by {@link #beginTaskBlock(int)} and records its
430+
* {@code TaskBlock} event when it satisfies the profiler's eligibility rules.
431+
* Lifecycle state is cleared even when no event is recorded.
432+
*
433+
* @param token opaque token returned by {@link #beginTaskBlock(int)}
434+
* @param blocker stable identifier describing the blocking resource
435+
* @param unblockingSpanId span responsible for unblocking the interval, or {@code 0}
436+
* @return {@code true} when an event was recorded
435437
*/
436-
public long getTscFrequency() {
437-
return tscFrequency0();
438+
public boolean endTaskBlock(long token, long blocker, long unblockingSpanId) {
439+
return endTaskBlock0(token, blocker, unblockingSpanId);
438440
}
439441

440442
/**
@@ -460,34 +462,6 @@ public boolean recordTaskBlockWithContext(long startTicks, long endTicks, long b
460462
return recordTaskBlockWithContext0(startTicks, endTicks, blocker, unblockingSpanId, spanId, rootSpanId);
461463
}
462464

463-
/**
464-
* Attempts to record a TaskBlock event attributed to an explicit thread ID and explicit span
465-
* context. This overload has no stack-reference metadata, so a recorder thread cannot use it to
466-
* emit a self-contained TaskBlock on behalf of another thread.
467-
*
468-
* @return {@code true} if the event was recorded; {@code false} if it was skipped by
469-
* eligibility rules or could not be recorded
470-
*/
471-
public boolean recordTaskBlockFromContext(int tid, long startTicks, long endTicks,
472-
long blocker, long unblockingSpanId, long spanId, long rootSpanId) {
473-
return recordTaskBlockFromContext0(tid, startTicks, endTicks, blocker, unblockingSpanId, spanId, rootSpanId);
474-
}
475-
476-
/**
477-
* Records a TaskBlock event with explicit thread, span context, and stack-reference metadata.
478-
* This is the required overload when recording from a background thread on behalf of {@code tid}.
479-
*
480-
* @return {@code true} if the event was recorded; {@code false} if it was skipped by
481-
* eligibility rules or could not be recorded
482-
*/
483-
public boolean recordTaskBlockFromContext(int tid, long startTicks, long endTicks,
484-
long blocker, long unblockingSpanId, long spanId, long rootSpanId,
485-
long callTraceId, long correlationId, int observedBlockingState) {
486-
return recordTaskBlockFromContextWithStackReference0(tid, startTicks, endTicks, blocker,
487-
unblockingSpanId, spanId, rootSpanId, callTraceId, correlationId,
488-
observedBlockingState);
489-
}
490-
491465
/**
492466
* If the profiler is built in debug mode, returns counters recorded during profile execution.
493467
* These are for whitebox testing and not intended for production use.
@@ -544,7 +518,9 @@ private static ThreadContext initializeThreadContext() {
544518

545519
private static native void blockExit0(long token);
546520

547-
private static native void blockExitWithSnapshot0(long token, long[] snapshot);
521+
private static native long beginTaskBlock0(int state);
522+
523+
private static native boolean endTaskBlock0(long token, long blocker, long unblockingSpanId);
548524

549525
private static native long currentTicks0();
550526

@@ -556,13 +532,6 @@ private static native boolean recordTaskBlock0(long startTicks, long endTicks,
556532
private static native boolean recordTaskBlockWithContext0(long startTicks, long endTicks,
557533
long blocker, long unblockingSpanId, long spanId, long rootSpanId);
558534

559-
private static native boolean recordTaskBlockFromContext0(int tid, long startTicks, long endTicks,
560-
long blocker, long unblockingSpanId, long spanId, long rootSpanId);
561-
562-
private static native boolean recordTaskBlockFromContextWithStackReference0(int tid, long startTicks,
563-
long endTicks, long blocker, long unblockingSpanId, long spanId, long rootSpanId,
564-
long callTraceId, long correlationId, int observedBlockingState);
565-
566535
private static native void mallocArenaMax0(int max);
567536

568537
private static native String getStatus0();

0 commit comments

Comments
 (0)