Skip to content

Commit 887508d

Browse files
jbachorikivoanjoclaude
authored
Initial implementation of OTel process context support (#266)
* Initial implementation of OTel process context support * Update ddprof-lib/src/main/java/com/datadoghq/profiler/OTelContext.java Co-authored-by: Ivo Anjo <[email protected]> * Use the upstream ebpf-context lib sources * Fix OpenTelemetry process context implementation and build system - Add comprehensive gradle patching for otel_process_ctx.c to .cpp conversion - Add Linux preprocessor guards and C++ explicit casts for compilation - Fix gradle task dependencies and caching for proper file handling - Implement proper publish/update API usage in JNI setProcessCtx0 - Add native read functionality through JNI wrapper - Update ProcessContextTest for test resilience and native read testing - Resolve all compilation failures in gtest tasks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * CI can't access the ebpf-context repo * Make OTelContext actually thread safe * Remove unused repo locks from build * Typo * Simplify ProcessContextTest.java * [PROF-12377] Update process context support with latest version of reference library (#267) * Optimize locking --------- Co-authored-by: Ivo Anjo <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent d0c8781 commit 887508d

File tree

10 files changed

+1106
-21
lines changed

10 files changed

+1106
-21
lines changed

.github/workflows/test_workflow.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
uses: actions/cache@v4
5858
with:
5959
path: ddprof-lib/build/async-profiler
60-
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/ap-lock.properties') }}
60+
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/lock.properties') }}
6161
enableCrossOsArchive: true
6262
restore-keys: |
6363
async-profiler-${{ runner.os }}-
@@ -156,7 +156,7 @@ jobs:
156156
uses: actions/cache@v4
157157
with:
158158
path: ddprof-lib/build/async-profiler
159-
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/ap-lock.properties') }}
159+
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/lock.properties') }}
160160
enableCrossOsArchive: true
161161
restore-keys: |
162162
async-profiler-${{ runner.os }}-
@@ -276,7 +276,7 @@ jobs:
276276
uses: actions/cache@v4
277277
with:
278278
path: ddprof-lib/build/async-profiler
279-
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/ap-lock.properties') }}
279+
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/lock.properties') }}
280280
enableCrossOsArchive: true
281281
restore-keys: |
282282
async-profiler-${{ runner.os }}-
@@ -372,7 +372,7 @@ jobs:
372372
uses: actions/cache@v4
373373
with:
374374
path: ddprof-lib/build/async-profiler
375-
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/ap-lock.properties') }}
375+
key: async-profiler-${{ runner.os }}-${{ hashFiles('gradle/lock.properties') }}
376376
enableCrossOsArchive: true
377377
restore-keys: |
378378
async-profiler-${{ runner.os }}-

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The resulting artifact will be in `ddprof-lib/build/libs/ddprof-<version>.jar`
4040
To smoothen the absorption of the upstream changes, we are using parts of the upstream codebase in (mostly) vanilla form.
4141

4242
For this, we have four new gradle tasks in [ddprof-lib/build.gradle](ddprof-lib/build.gradle):
43-
- `cloneAsyncProfiler` - clones the [DataDog/async-profiler](https://github.com/DataDog/async-profiler) repository into `ddprof-lib/build/async-profiler` using the commit lock specified in [gradle/ap-lock.properties](gradle/ap-lock.properties)
43+
- `cloneAsyncProfiler` - clones the [DataDog/async-profiler](https://github.com/DataDog/async-profiler) repository into `ddprof-lib/build/async-profiler` using the commit lock specified in [gradle/ap-lock.properties](gradle/lock.properties)
4444
- in that repository, we are maintainin a branch called `dd/master` where we keep the upstream code in sync with the 'safe' changes from the upstream `master` branch
4545
- cherry-picks into that branch should be rare and only done for critical fixes that are needed in the project
4646
- otherwise, we should wait for the next upstream release to avoid conflicts

ddprof-lib/build.gradle

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,12 @@ description = "Datadog Java Profiler Library"
181181
def component_version = project.hasProperty("ddprof_version") ? project.ddprof_version : project.version
182182

183183
def props = new Properties()
184-
file("${rootDir}/gradle/ap-lock.properties").withInputStream { stream ->
184+
file("${rootDir}/gradle/lock.properties").withInputStream { stream ->
185185
props.load(stream)
186186
}
187187

188-
def branch_lock = props.getProperty("branch")
189-
def commit_lock = props.getProperty("commit")
188+
def ap_branch_lock = props.getProperty("ap.branch")
189+
def ap_commit_lock = props.getProperty("ap.commit")
190190

191191
// this feels weird but it is the only way invoking `./gradlew :ddprof-lib:*` tasks will work
192192
if (rootDir.toString().endsWith("ddprof-lib")) {
@@ -267,7 +267,7 @@ tasks.register('copyExternalLibs', Copy) {
267267

268268
def cloneAPTask = tasks.register('cloneAsyncProfiler') {
269269
description = 'Clones async-profiler repo if directory is missing or updates it if commit hash differs'
270-
inputs.file("${rootDir}/gradle/ap-lock.properties")
270+
inputs.file("${rootDir}/gradle/lock.properties")
271271
outputs.dir("${projectDir}/build/async-profiler")
272272
outputs.upToDateWhen {
273273
def targetDir = file("${projectDir}/build/async-profiler")
@@ -284,7 +284,7 @@ def cloneAPTask = tasks.register('cloneAsyncProfiler') {
284284
}
285285
currentCommit = os.toString().trim()
286286
}
287-
return currentCommit == commit_lock
287+
return currentCommit == ap_commit_lock
288288
} catch (Exception e) {
289289
return false
290290
}
@@ -300,11 +300,11 @@ def cloneAPTask = tasks.register('cloneAsyncProfiler') {
300300
if (!targetDir.exists()) {
301301
println "Cloning missing async-profiler git subdirectory..."
302302
exec {
303-
commandLine 'git', 'clone', '--branch', branch_lock, 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
303+
commandLine 'git', 'clone', '--branch', ap_branch_lock, 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
304304
}
305305
exec {
306306
workingDir targetDir.absolutePath
307-
commandLine 'git', 'checkout', commit_lock
307+
commandLine 'git', 'checkout', ap_commit_lock
308308
}
309309
} else {
310310
// Also fix git ownership for existing directory
@@ -324,18 +324,18 @@ def cloneAPTask = tasks.register('cloneAsyncProfiler') {
324324
currentCommit = os.toString().trim()
325325
}
326326

327-
if (currentCommit != commit_lock) {
328-
println "async-profiler commit hash differs (current: ${currentCommit}, expected: ${commit_lock}), updating..."
327+
if (currentCommit != ap_commit_lock) {
328+
println "async-profiler commit hash differs (current: ${currentCommit}, expected: ${ap_commit_lock}), updating..."
329329
exec {
330330
workingDir targetDir.absolutePath
331331
commandLine 'rm', '-rf', targetDir.absolutePath
332332
}
333333
exec {
334-
commandLine 'git', 'clone', '--branch', branch_lock, 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
334+
commandLine 'git', 'clone', '--branch', ap_branch_lock, 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
335335
}
336336
exec {
337337
workingDir targetDir.absolutePath
338-
commandLine 'git', 'checkout', commit_lock
338+
commandLine 'git', 'checkout', ap_commit_lock
339339
}
340340
} else {
341341
println "async-profiler git subdirectory present with correct commit hash."
@@ -373,7 +373,7 @@ def patchStackFrame = tasks.register("patchStackFrame") {
373373
configure {
374374
dependsOn copyUpstreamFiles
375375
}
376-
inputs.files copyUpstreamFiles
376+
inputs.file("${projectDir}/src/main/cpp-external/stackFrame_x64.cpp")
377377
outputs.file("${projectDir}/src/main/cpp-external/stackFrame_x64.cpp")
378378

379379
doLast {
@@ -428,7 +428,7 @@ def patchStackWalker = tasks.register("patchStackWalker") {
428428
configure {
429429
dependsOn copyUpstreamFiles, patchStackFrame
430430
}
431-
inputs.files copyUpstreamFiles
431+
inputs.file("${projectDir}/src/main/cpp-external/stackWalker.cpp")
432432
outputs.file("${projectDir}/src/main/cpp-external/stackWalker.cpp")
433433

434434
doLast {

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

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "engine.h"
2222
#include "incbin.h"
2323
#include "os.h"
24+
#include "otel_process_ctx.h"
2425
#include "profiler.h"
2526
#include "thread.h"
2627
#include "tsc.h"
@@ -110,7 +111,7 @@ Java_com_datadoghq_profiler_JavaProfiler_execute0(JNIEnv *env, jobject unused,
110111
}
111112

112113
extern "C" DLLEXPORT jstring JNICALL
113-
Java_com_datadoghq_profiler_JavaProfiler_getStatus0(JNIEnv* env,
114+
Java_com_datadoghq_profiler_JavaProfiler_getStatus0(JNIEnv* env,
114115
jobject unused) {
115116
char msg[2048];
116117
int ret = Profiler::instance()->status((char*)msg, sizeof(msg) - 1);
@@ -427,3 +428,93 @@ Java_com_datadoghq_profiler_ActiveBitmap_getActiveCountAddr0(JNIEnv *env,
427428
jclass unused) {
428429
return (jlong)Profiler::instance()->threadFilter()->addressOfSize();
429430
}
431+
432+
// Static variable to track the current published context
433+
static otel_process_ctx_result* current_published_context = nullptr;
434+
435+
extern "C" DLLEXPORT void JNICALL
436+
Java_com_datadoghq_profiler_OTelContext_setProcessCtx0(JNIEnv *env,
437+
jclass unused,
438+
jstring env_data,
439+
jstring hostname,
440+
jstring runtime_id,
441+
jstring service,
442+
jstring version,
443+
jstring tracer_version
444+
) {
445+
JniString env_str(env, env_data);
446+
JniString hostname_str(env, hostname);
447+
JniString runtime_id_str(env, runtime_id);
448+
JniString service_str(env, service);
449+
JniString version_str(env, version);
450+
JniString tracer_version_str(env, tracer_version);
451+
452+
otel_process_ctx_data data = {
453+
.deployment_environment_name = const_cast<char*>(env_str.c_str()),
454+
.host_name = const_cast<char*>(hostname_str.c_str()),
455+
.service_instance_id = const_cast<char*>(runtime_id_str.c_str()),
456+
.service_name = const_cast<char*>(service_str.c_str()),
457+
.service_version = const_cast<char*>(version_str.c_str()),
458+
.telemetry_sdk_language = const_cast<char*>("java"),
459+
.telemetry_sdk_version = const_cast<char*>(tracer_version_str.c_str()),
460+
.telemetry_sdk_name = const_cast<char*>("dd-trace-java"),
461+
.resources = NULL // TODO: Arbitrary tags not supported yet for Java
462+
};
463+
464+
otel_process_ctx_result result = otel_process_ctx_publish(&data);
465+
}
466+
467+
extern "C" DLLEXPORT jobject JNICALL
468+
Java_com_datadoghq_profiler_OTelContext_readProcessCtx0(JNIEnv *env, jclass unused) {
469+
#ifndef OTEL_PROCESS_CTX_NO_READ
470+
otel_process_ctx_read_result result = otel_process_ctx_read();
471+
472+
if (!result.success) {
473+
// Return null if reading failed
474+
return nullptr;
475+
}
476+
477+
// Convert C strings to Java strings
478+
jstring jDeploymentEnvironmentName = result.data.deployment_environment_name ?
479+
env->NewStringUTF(result.data.deployment_environment_name) : nullptr;
480+
jstring jHostName = result.data.host_name ?
481+
env->NewStringUTF(result.data.host_name) : nullptr;
482+
jstring jServiceInstanceId = result.data.service_instance_id ?
483+
env->NewStringUTF(result.data.service_instance_id) : nullptr;
484+
jstring jServiceName = result.data.service_name ?
485+
env->NewStringUTF(result.data.service_name) : nullptr;
486+
jstring jServiceVersion = result.data.service_version ?
487+
env->NewStringUTF(result.data.service_version) : nullptr;
488+
jstring jTelemetrySdkLanguage = result.data.telemetry_sdk_language ?
489+
env->NewStringUTF(result.data.telemetry_sdk_language) : nullptr;
490+
jstring jTelemetrySdkVersion = result.data.telemetry_sdk_version ?
491+
env->NewStringUTF(result.data.telemetry_sdk_version) : nullptr;
492+
jstring jTelemetrySdkName = result.data.telemetry_sdk_name ?
493+
env->NewStringUTF(result.data.telemetry_sdk_name) : nullptr;
494+
// TODO: result.data.resources not supported yet for Java
495+
496+
otel_process_ctx_read_drop(&result);
497+
498+
// Find the ProcessContext class
499+
jclass processContextClass = env->FindClass("com/datadoghq/profiler/OTelContext$ProcessContext");
500+
if (!processContextClass) {
501+
return nullptr;
502+
}
503+
504+
// Find the constructor
505+
jmethodID constructor = env->GetMethodID(processContextClass, "<init>",
506+
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
507+
if (!constructor) {
508+
return nullptr;
509+
}
510+
511+
// Create the ProcessContext object
512+
jobject processContext = env->NewObject(processContextClass, constructor,
513+
jDeploymentEnvironmentName, jHostName, jServiceInstanceId, jServiceName, jServiceVersion, jTelemetrySdkLanguage, jTelemetrySdkVersion, jTelemetrySdkName);
514+
515+
return processContext;
516+
#else
517+
// If OTEL_PROCESS_CTX_NO_READ is defined, return null
518+
return nullptr;
519+
#endif
520+
}

0 commit comments

Comments
 (0)