Skip to content
Draft
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright 2026, Datadog, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.datadoghq.native.config

Expand Down Expand Up @@ -149,7 +164,7 @@ object ConfigurationPresets {
config.compilerArgs.set(
listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version)
)
config.linkerArgs.set(commonLinuxLinkerArgs())
config.linkerArgs.set(commonLinuxLinkerArgs() + listOf("-Wl,-z,nodelete"))
}
Platform.MACOS -> {
config.compilerArgs.set(
Expand Down
116 changes: 62 additions & 54 deletions ddprof-lib/src/main/cpp/javaApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,29 @@ class JniString {
};

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_init0(JNIEnv *env, jclass unused) {
Java_com_datadoghq_profiler_JavaProfiler_init0(
JNIEnv *env, jclass unused, jboolean delegateMonitorWaitEvents) {
Error error = Profiler::instance()->init();
if (error) {
throwNew(env, "java/lang/IllegalStateException", error.message());
return JNI_FALSE;
}

// JavaVM* has already been stored when the native library was loaded so we can pass nullptr here
return VM::initProfilerBridge(nullptr, true);
ProfilerBridgeInitResult result =
VM::initProfilerBridge(nullptr, true, delegateMonitorWaitEvents);
if (result == ProfilerBridgeInitResult::MONITOR_EVENTS_DELEGATION_CONFLICT) {
throwNew(env, "java/lang/IllegalStateException",
"Monitor-event ownership conflicts with the profiler's "
"process-wide initialization");
return JNI_FALSE;
}
if (result != ProfilerBridgeInitResult::SUCCESS) {
throwNew(env, "java/lang/IllegalStateException",
"Failed to initialize the profiler bridge");
return JNI_FALSE;
}
return JNI_TRUE;
}

extern "C" DLLEXPORT void JNICALL
Expand All @@ -94,6 +108,12 @@ Java_com_datadoghq_profiler_JavaProfiler_getTid0(JNIEnv *env, jclass unused) {
return OS::threadId();
}

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_monitorWaitEventsDelegated0(
JNIEnv *env, jclass unused) {
return VM::monitorWaitEventsDelegated();
}

extern "C" DLLEXPORT jstring JNICALL
Java_com_datadoghq_profiler_JavaProfiler_execute0(JNIEnv *env, jobject unused,
jstring command) {
Expand Down Expand Up @@ -137,32 +157,6 @@ Java_com_datadoghq_profiler_JavaProfiler_getSamples(JNIEnv *env,
return (jlong)Profiler::instance()->total_samples();
}

// some duplication between add and remove, though we want to avoid having an extra branch in the hot path

static ThreadFilter::SlotID ensureCurrentThreadFilterSlot(
ThreadFilter *thread_filter, ProfiledThread *current) {
int tid = current->tid();
if (unlikely(tid < 0)) {
return -1;
}

ThreadFilter::SlotID slot_id = current->filterSlotId();
if (likely(slot_id >= 0)) {
if (likely(thread_filter->activeSlotForId(slot_id, tid) != nullptr)) {
return slot_id;
}
current->setFilterSlotId(-1);
}

// Startup can register this TID centrally, but it cannot update another
// pthread's TLS. registerThread(tid) reuses that existing slot.
slot_id = thread_filter->registerThread(tid);
if (slot_id >= 0) {
current->setFilterSlotId(slot_id);
}
return slot_id;
}

// JavaCritical is faster JNI, but more restrictive - parameters and return value have to be
// primitives or arrays of primitive types.
// We direct corresponding JNI calls to JavaCritical to make sure the parameters/return value
Expand All @@ -184,7 +178,7 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
return;
}

int slot_id = ensureCurrentThreadFilterSlot(thread_filter, current);
int slot_id = thread_filter->ensureCurrentThreadSlot(current);
if (unlikely(slot_id < 0)) {
return; // Failed to register thread
}
Expand Down Expand Up @@ -360,43 +354,56 @@ Java_com_datadoghq_profiler_JavaProfiler_recordQueueEnd0(
}

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_parkEnter0(JNIEnv *env, jclass unused) {
Java_com_datadoghq_profiler_JavaProfiler_parkEnter0(
JNIEnv *env, jclass unused, jthread thread) {
if (!JVMSupport::isPlatformThread(env, thread)) {
return JNI_FALSE;
}
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
if (current == nullptr) {
return JNI_FALSE;
}
bool first_park = current->parkEnter();
ThreadFilter *tf = Profiler::instance()->threadFilter();
if (first_park && tf->registryActive()) {
ThreadFilter::SlotID slot_id = ensureCurrentThreadFilterSlot(tf, current);
Context context = ContextApi::snapshot();
if (!current->parkEnter(TSC::ticks(), context)) {
return JNI_FALSE;
}

Profiler *profiler = Profiler::instance();
ThreadFilter *tf = profiler->threadFilter();
if (context.spanId == 0 && tf->registryActive() &&
(profiler->taskBlockEnabled() || tf->enabled())) {
ThreadFilter::SlotID slot_id = tf->ensureCurrentThreadSlot(current);
if (slot_id >= 0) {
current->setParkBlockToken(
tf->enterBlockedRun(slot_id, OSThreadState::CONDVAR_WAIT));
current->setParkBlockToken(tf->enterBlockedRun(
slot_id, OSThreadState::CONDVAR_WAIT, BlockRunOwner::JAVA));
}
}
return first_park ? JNI_TRUE : JNI_FALSE;
return JNI_TRUE;
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_parkExit0(
JNIEnv *env, jclass unused, jlong blocker, jlong unblockingSpanId) {
JNIEnv *env, jclass unused, jthread thread, jlong blocker,
jlong unblockingSpanId) {
if (!JVMSupport::isPlatformThread(env, thread)) {
return;
}
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
if (current == nullptr) {
return;
}

u64 start_ticks = 0;
u64 park_block_token = 0;
if (!current->parkExit(park_block_token) || park_block_token == 0) {
Context context{};
if (!current->parkExit(start_ticks, context, park_block_token) ||
park_block_token == 0) {
return;
}
ThreadFilter *tf = Profiler::instance()->threadFilter();
if (tf->registryActive()) {
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(park_block_token);
if (tf->activeSlotForId(current->filterSlotId(), current->tid()) != nullptr &&
current->filterSlotId() == slot_id) {
tf->exitBlockedRun(slot_id, ThreadFilter::tokenGeneration(park_block_token));
}
}
Profiler *profiler = Profiler::instance();
finishTaskBlockAtExit(
current, profiler->threadFilter(), thread, 1, park_block_token,
start_ticks, context, static_cast<u64>(blocker),
static_cast<u64>(unblockingSpanId));
}

static bool decodeJavaBlockState(jint state, OSThreadState &decoded) {
Expand All @@ -410,9 +417,10 @@ static bool decodeJavaBlockState(jint state, OSThreadState &decoded) {

extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_blockEnter0(
JNIEnv *env, jclass unused, jint state) {
JNIEnv *env, jclass unused, jthread thread, jint state) {
OSThreadState decoded;
if (!decodeJavaBlockState(state, decoded)) {
if (!decodeJavaBlockState(state, decoded) ||
!JVMSupport::isPlatformThread(env, thread)) {
return 0;
}
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
Expand All @@ -427,16 +435,16 @@ Java_com_datadoghq_profiler_JavaProfiler_blockEnter0(
if (!profiler->taskBlockEnabled() && !tf->enabled()) {
return 0;
}
ThreadFilter::SlotID slot_id = ensureCurrentThreadFilterSlot(tf, current);
ThreadFilter::SlotID slot_id = tf->ensureCurrentThreadSlot(current);
if (slot_id < 0) return 0;
return static_cast<jlong>(tf->enterBlockedRun(slot_id, decoded));
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_blockExit0(
JNIEnv *env, jclass unused, jlong token) {
JNIEnv *env, jclass unused, jthread thread, jlong token) {
u64 block_token = static_cast<u64>(token);
if (block_token == 0) {
if (block_token == 0 || !JVMSupport::isPlatformThread(env, thread)) {
return;
}
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
Expand Down Expand Up @@ -468,7 +476,7 @@ Java_com_datadoghq_profiler_JavaProfiler_beginTaskBlock0(
}
ThreadFilter *tf = profiler->threadFilter();
if (!tf->unfilteredWallTrackingActive()) return 0;
ThreadFilter::SlotID slot_id = ensureCurrentThreadFilterSlot(tf, current);
ThreadFilter::SlotID slot_id = tf->ensureCurrentThreadSlot(current);
if (slot_id < 0) return 0;

Context context = ContextApi::snapshot();
Expand Down
17 changes: 15 additions & 2 deletions ddprof-lib/src/main/cpp/jvmSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "jvmSupport.h"

#include "asyncSampleMutex.h"
#include "common.h"
#include "frames.h"
#include "os.h"
#include "profiler.h"
Expand All @@ -16,6 +17,8 @@

#include <jni.h>

#include <atomic>

using JniFunction = void (JNICALL*)();
using IsVirtualThreadFunction = jboolean (JNICALL*)(JNIEnv*, jobject);

Expand All @@ -41,11 +44,21 @@ bool JVMSupport::isPlatformThread(JNIEnv* jni, jthread thread) {

const JniFunction* functions =
reinterpret_cast<const JniFunction*>(jni->functions);
if (functions == nullptr) return false;
IsVirtualThreadFunction is_virtual_thread =
reinterpret_cast<IsVirtualThreadFunction>(
functions[IS_VIRTUAL_THREAD_INDEX]);
return is_virtual_thread != nullptr &&
is_virtual_thread(jni, thread) == JNI_FALSE;
if (is_virtual_thread == nullptr) {
static std::atomic<bool> warning_emitted{false};
bool expected = false;
if (warning_emitted.compare_exchange_strong(expected, true,
std::memory_order_relaxed)) {
LOG_WARN("JNI version 19 or later does not expose IsVirtualThread; "
"JVM producer callbacks will be ignored");
}
return false;
}
return is_virtual_thread(jni, thread) == JNI_FALSE;
}

bool JVMSupport::initialize() {
Expand Down
27 changes: 23 additions & 4 deletions ddprof-lib/src/main/cpp/profiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,26 @@ Error Profiler::init() {
return Error::OK;
}

void Profiler::setTaskBlockEnabled(bool enabled) {
if (enabled) {
// Keep callback admission closed until native setup has either completed
// or rolled back, so partial event enablement cannot create paired state.
bool monitor_events_enabled =
VM::nativeMonitorEventsAvailable() &&
VM::setNativeMonitorEventsEnabled(true);
_task_block_monitor_events_enabled.store(monitor_events_enabled,
std::memory_order_release);
_task_block_enabled.store(true, std::memory_order_release);
return;
}

_task_block_enabled.store(false, std::memory_order_release);
if (_task_block_monitor_events_enabled.exchange(
false, std::memory_order_acq_rel)) {
VM::setNativeMonitorEventsEnabled(false);
}
}

Error Profiler::start(Arguments &args, bool reset) {
MutexLocker ml(_state_lock);
Error error = checkState();
Expand Down Expand Up @@ -1743,9 +1763,8 @@ Error Profiler::start(Arguments &args, bool reset) {
// Paired with drainInflight() on the stop side.
_cpu_engine->enableEvents(true);

_task_block_enabled.store(
(activated & EM_WALL) && args._wall_precheck && track_unfiltered_wall,
std::memory_order_release);
setTaskBlockEnabled(
(activated & EM_WALL) && args._wall_precheck && track_unfiltered_wall);
_state.store(RUNNING, std::memory_order_release);
_start_time = time(NULL);
__atomic_add_fetch(&_epoch, 1, __ATOMIC_RELAXED);
Expand All @@ -1770,7 +1789,7 @@ Error Profiler::stop() {
if (state() != RUNNING) {
return Error("Profiler is not active");
}
_task_block_enabled.store(false, std::memory_order_release);
setTaskBlockEnabled(false);

// Order matters: disable engines first so the _enabled check inside signal
// handlers will fail for any new signal delivered from now on. drain() then
Expand Down
5 changes: 5 additions & 0 deletions ddprof-lib/src/main/cpp/profiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class alignas(alignof(SpinLock)) Profiler {
alignas(DEFAULT_CACHE_LINE_SIZE) u64 _failures[ASGCT_FAILURE_TYPES];
bool _wall_precheck = false;
std::atomic<bool> _task_block_enabled{false};
std::atomic<bool> _task_block_monitor_events_enabled{false};
std::atomic<bool> _task_block_rotation{false};
std::atomic<u64> _task_block_inflight{0};

Expand Down Expand Up @@ -181,6 +182,7 @@ class alignas(alignof(SpinLock)) Profiler {

void lockAll();
void unlockAll();
void setTaskBlockEnabled(bool enabled);
void beginTaskBlockRotation();
void endTaskBlockRotation();

Expand Down Expand Up @@ -471,6 +473,9 @@ class alignas(alignof(SpinLock)) Profiler {
bool taskBlockEnabled() const {
return _task_block_enabled.load(std::memory_order_acquire);
}
bool nativeMonitorTaskBlockEnabled() const {
return _task_block_monitor_events_enabled.load(std::memory_order_acquire);
}
void writeLog(LogLevel level, const char *message);
void writeLog(LogLevel level, const char *message, size_t len);
void writeDatadogProfilerSetting(int tid, int length, const char *name,
Expand Down
Loading
Loading