Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
2faed35
feat(profiling): Add OTLP profiles core infrastructure
jbachorik Dec 1, 2025
33b5639
feat(profiling): Initial implementation of JFR->OTLP/P format
jbachorik Dec 2, 2025
cfad251
feat(profiling): Add benchmarks for OTLP/P converter
jbachorik Dec 2, 2025
6898d9d
feat(profiling): Add OLTP/P format validation tests
jbachorik Dec 3, 2025
1316291
feat(profiling): Add JMH filtering and update benchmarks with results
jbachorik Dec 3, 2025
5f4952b
chore(profiling): revert dictionary optimization and add profiling su…
jbachorik Dec 4, 2025
600b19b
feat(profiling): Add stack trace caching optimization using JFR const…
jbachorik Dec 4, 2025
ed7710f
fix(profiling): Skip Docker-dependent validation tests gracefully whe…
jbachorik Dec 4, 2025
fa78e7a
feat(profiling): Add original_payload support to OTLP profiles converter
jbachorik Dec 4, 2025
4fee3be
feat(profiling): Add RecordingData reference counting and OTLP config…
jbachorik Dec 4, 2025
69af4c0
feat(profiling): Implement OtlpProfileUploader for OTLP format uploads
jbachorik Dec 4, 2025
47e5b66
feat(profiling): Integrate OtlpProfileUploader with explicit referenc…
jbachorik Dec 4, 2025
0be7e24
feat(profiling): Add profiling-otel to agent-profiling shadowJar
jbachorik Dec 4, 2025
ceb7b58
feat(profiling): Add CLI tool for converting JFR to OTLP format
jbachorik Dec 5, 2025
dc733f4
feat(profiling): Add optional pretty-printing for JSON output
jbachorik Dec 5, 2025
499464d
feat(profiling): Add profcheck integration for OTLP profile validation
jbachorik Dec 5, 2025
4285467
fix(profiling): Fix OTLP dictionary index 0 sentinel encoding
jbachorik Dec 5, 2025
b133f7d
feat(profiling): Add sample attributes support to OTLP profiles conve…
jbachorik Dec 5, 2025
91cbdfb
fix(profiling): Fix timestamp validation and make profcheck mandatory
jbachorik Dec 5, 2025
194d05b
feat(profiling): Add convenience script for JFR to OTLP conversion
jbachorik Dec 8, 2025
9a98613
feat(profiling): Add diagnostics mode to convert-jfr.sh script
jbachorik Dec 8, 2025
ee07484
feat(profiling): Add JFR to OTLP conversion convenience script with d…
jbachorik Dec 8, 2025
629d63a
feat(profiling): Convert JFR converter script to use fat jar for 31x …
jbachorik Dec 8, 2025
a239dd4
refactor(profiling): Consolidate converter script output for clarity
jbachorik Dec 8, 2025
198aabb
Properly handle `objectClass` attribute
jbachorik Dec 8, 2025
9d041a0
Adapt to master: fix API breaks from rebase
jbachorik Apr 22, 2026
c48e85d
fix(profiling): Address review findings in OTLP pipeline
jbachorik Apr 22, 2026
6a04374
build: bump jafar to 0.21.1
jbachorik Apr 22, 2026
a646b40
fix(profiling): fix StackTraceConversionBenchmark broken reflection
jbachorik Apr 22, 2026
1632a0d
perf(profiling): optimize OTLP converter hot paths
jbachorik Apr 22, 2026
94bba41
build: bump jafar to 0.21.3
jbachorik Apr 22, 2026
5e1d8a0
perf(profiling): eliminate allocations in OTLP encoder hot paths
jbachorik Apr 23, 2026
b0e3a56
build: bump jafar to 0.21.7
jbachorik Apr 24, 2026
32d8bc0
style: spotless
jbachorik Apr 24, 2026
1b62eb4
fix(profiling): Fix RecordingData reference counting — start refCount…
jbachorik Aug 14, 2026
4877596
fix(profiling): Address code review findings in OTLP pipeline
jbachorik Aug 14, 2026
75cd156
build: Bump jafar to 0.26.2 and fix profiling-otel build
jbachorik Aug 14, 2026
e9ba674
refactor: Extract OTLP sender infrastructure to communication/otlp-ex…
jbachorik Aug 17, 2026
7de364d
feat(profiling): Integrate OTLP profile uploader with tracer OTLP inf…
jbachorik Aug 17, 2026
99fcb84
feat(profiling): Add lightweight OTLP mode — raw JFR blob, no conversion
jbachorik Aug 17, 2026
8a5346a
feat(profiling): Default lightweight OTLP to true and register all OT…
jbachorik Aug 17, 2026
c664462
Fix build and spotless fallout from origin/master rebase
jbachorik Sep 1, 2026
6325bf1
Register DD_PROFILING_OTLP_URL and compression config keys
jbachorik Sep 2, 2026
6f4c0df
Replace OTLP profiling boolean flags with profiling.otlp.mode
jbachorik Sep 2, 2026
819b1dd
Remove dead profiling.otlp.compression.enabled config
jbachorik Sep 2, 2026
9b5b1bd
Add resource attributes to light OTLP profile mode
jbachorik Sep 2, 2026
d9c74ba
Emit resource attributes in full/converted OTLP profile modes
jbachorik Sep 2, 2026
1c95c12
Log JFR to OTLP conversion duration
jbachorik Sep 2, 2026
69e1b4d
Report JFR to OTLP conversion duration as telemetry gauges
jbachorik Sep 2, 2026
20a96e0
Remove profiling.otlp.url in favor of otlp.profiles.endpoint
jbachorik Sep 2, 2026
48dab03
Fix WriterFactoryTest table alignment for spotless
jbachorik Sep 2, 2026
7fbf017
Single-copy jafar classes in agent-profiling shadow jar
jbachorik Sep 2, 2026
95cc2dd
Revert JMC to 8.1.0 to fix Java 11 test compiles
jbachorik Sep 2, 2026
2814bca
Generate light profile ids via ThreadLocalRandom
jbachorik Sep 2, 2026
c0e5f07
Trim redundant comments and javadocs from OTLP profiling code
jbachorik Sep 2, 2026
cc25b24
Suppress forbidden System.out/err in converter CLI
jbachorik Sep 2, 2026
71df79d
Merge branch 'master' into jb/rnd_otlp_profile
jbachorik Sep 2, 2026
a69e2eb
Merge branch 'master' into jb/rnd_otlp_profile
jbachorik Sep 14, 2026
b0ea251
Gate OTLP validation tests behind -PrunOtlpValidation
jbachorik Sep 14, 2026
8535391
Remove Java 11 source compat from profiling-uploader
jbachorik Sep 14, 2026
5a41f26
Exclude OTLP sender classes and OtlpMode from coverage checks
jbachorik Sep 14, 2026
65363e4
Raise agent jar size budget to 33.6 MiB
jbachorik Sep 14, 2026
a9b0acc
Merge branch 'master' into jb/rnd_otlp_profile
jbachorik Sep 14, 2026
bf5ed74
Merge branch 'master' into jb/rnd_otlp_profile
jbachorik Sep 14, 2026
04da5d5
Fix spotbugs and coverage violations in profiling-otel/uploader
jbachorik Sep 14, 2026
bbed587
Merge branch 'master' into jb/rnd_otlp_profile
jbachorik Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,4 @@ mise*.local.toml

# Exclude kotlin build files
.kotlin
.claude/state/
26 changes: 26 additions & 0 deletions communication/otlp-exporter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
`java-library`
id("dd-trace-java.module.internal-library")
}

description = "otlp-exporter"

// Network/IO classes are hard to unit-test without a real OTLP endpoint; they are exercised
// via integration tests and the profiling-otel smoke tests instead.
extra["excludedClassesCoverage"] = listOf(
"datadog.communication.otlp.OtlpGrpcSender",
"datadog.communication.otlp.OtlpHttpSender",
"datadog.communication.otlp.OtlpResponse",
"datadog.communication.otlp.OtlpSenderSupport"
)

dependencies {
api(project(":dd-trace-api"))
api(project(":communication"))
implementation(project(":utils:logging-utils"))
implementation(libs.slf4j)

testImplementation(libs.bundles.junit5)
testImplementation(libs.bundles.mockito)
testImplementation(libs.okhttp3.mockwebserver)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import java.io.IOException;
import javax.annotation.Nonnull;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import static datadog.communication.http.OkHttpUtils.buildHttp2Client;
import static datadog.communication.http.OkHttpUtils.isPlainHttp;

import datadog.communication.http.HttpRetryPolicy;
import datadog.logging.RatelimitedLogger;
import datadog.trace.api.config.OtlpConfig.Compression;
import datadog.trace.common.writer.RemoteApi;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.HttpUrl;
Expand Down Expand Up @@ -53,7 +52,7 @@ public OtlpGrpcSender(
}

@Override
public RemoteApi.Response send(OtlpPayload payload) {
public OtlpResponse send(OtlpPayload payload) {
return OtlpSenderSupport.send(client, retryPolicy, makeRequest(payload), RATELIMITED_LOGGER);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import java.io.IOException;
import javax.annotation.Nonnull;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import static datadog.communication.http.OkHttpUtils.buildHttpClient;
import static datadog.communication.http.OkHttpUtils.isPlainHttp;

import datadog.communication.http.HttpRetryPolicy;
import datadog.logging.RatelimitedLogger;
import datadog.trace.api.config.OtlpConfig.Compression;
import datadog.trace.common.writer.RemoteApi;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.HttpUrl;
Expand Down Expand Up @@ -57,7 +56,7 @@ public HttpUrl url() {
}

@Override
public RemoteApi.Response send(OtlpPayload payload) {
public OtlpResponse send(OtlpPayload payload) {
return OtlpSenderSupport.send(client, retryPolicy, makeRequest(payload), RATELIMITED_LOGGER);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import java.nio.ByteBuffer;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package datadog.communication.otlp;

import java.util.Optional;
import java.util.OptionalInt;

/**
* Encapsulates the result of an OTLP export attempt.
*
* <p>If communication fails or times out, the response will NOT be successful and will lack a
* status code, but will have an exception.
*
* <p>If communication succeeds, the response will have a status code and will be marked as success
* or failure in accordance with the code.
*
* <p>NOTE: A successful communication may still contain an exception if there was a problem parsing
* the response.
*/
public final class OtlpResponse {
/** Factory method for a successful request with a trivial response body */
public static OtlpResponse success(final int status) {
return new OtlpResponse(true, status, null, null);
}

/** Factory method for a successful request with a response body */
public static OtlpResponse success(final int status, String response) {
return new OtlpResponse(true, status, null, response);
}

/** Factory method for a successful request with a malformed response body */
public static OtlpResponse success(final int status, final Throwable exception) {
return new OtlpResponse(true, status, exception, null);
}

/** Factory method for a request that received an error status in response */
public static OtlpResponse failed(final int status) {
return new OtlpResponse(false, status, null, null);
}

/** Factory method for a request that received an error status and a response body */
public static OtlpResponse failed(final int status, String response) {
return new OtlpResponse(false, status, null, response);
}

/** Factory method for a failed communication attempt */
public static OtlpResponse failed(final Throwable exception) {
return new OtlpResponse(false, null, exception, null);
}

private final boolean success;
private final Integer status;
private final Throwable exception;
private final String response;

private OtlpResponse(
final boolean success, final Integer status, final Throwable exception, String response) {
this.success = success;
this.status = status;
this.exception = exception;
this.response = response;
}

public boolean success() {
return success;
}

public OptionalInt status() {
return status == null ? OptionalInt.empty() : OptionalInt.of(status);
}

public Optional<Throwable> exception() {
return Optional.ofNullable(exception);
}

public String response() {
return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package datadog.communication.otlp;

/** Sends chunks of OTLP data. */
public interface OtlpSender {
OtlpResponse send(OtlpPayload payload);

void shutdown();
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import static datadog.communication.http.OkHttpUtils.sendWithRetries;
import static datadog.trace.common.writer.RemoteApi.Response.failed;
import static datadog.trace.common.writer.RemoteApi.Response.success;
import static datadog.communication.otlp.OtlpResponse.failed;
import static datadog.communication.otlp.OtlpResponse.success;

import datadog.communication.http.HttpRetryPolicy;
import datadog.logging.RatelimitedLogger;
import datadog.trace.common.writer.RemoteApi;
import java.io.IOException;
import okhttp3.OkHttpClient;

Expand All @@ -15,7 +14,7 @@ final class OtlpSenderSupport {
private OtlpSenderSupport() {}

/** Executes the given request with retries, logging failures via the rate-limited logger. */
static RemoteApi.Response send(
static OtlpResponse send(
OkHttpClient client,
HttpRetryPolicy.Factory retryPolicy,
okhttp3.Request request,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package datadog.trace.core.otlp.common;
package datadog.communication.otlp;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand All @@ -12,7 +12,6 @@
import datadog.communication.http.HttpRetryPolicy;
import datadog.communication.http.OkHttpUtils;
import datadog.logging.RatelimitedLogger;
import datadog.trace.common.writer.RemoteApi;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
Expand All @@ -39,8 +38,7 @@ void successfulResponseIsReturnedWithoutLogging() throws IOException {
.when(() -> OkHttpUtils.sendWithRetries(client, retryPolicy, request))
.thenReturn(response);

RemoteApi.Response result =
OtlpSenderSupport.send(client, retryPolicy, request, ratelimitedLogger);
OtlpResponse result = OtlpSenderSupport.send(client, retryPolicy, request, ratelimitedLogger);

assertTrue(result.success());
assertEquals(200, result.status().getAsInt());
Expand All @@ -56,8 +54,7 @@ void unsuccessfulResponseIsReturnedAndLogged() throws IOException {
.when(() -> OkHttpUtils.sendWithRetries(client, retryPolicy, request))
.thenReturn(response);

RemoteApi.Response result =
OtlpSenderSupport.send(client, retryPolicy, request, ratelimitedLogger);
OtlpResponse result = OtlpSenderSupport.send(client, retryPolicy, request, ratelimitedLogger);

assertFalse(result.success());
assertEquals(500, result.status().getAsInt());
Expand All @@ -73,8 +70,7 @@ void ioExceptionIsReturnedAsFailureAndLogged() throws IOException {
.when(() -> OkHttpUtils.sendWithRetries(client, retryPolicy, request))
.thenThrow(exception);

RemoteApi.Response result =
OtlpSenderSupport.send(client, retryPolicy, request, ratelimitedLogger);
OtlpResponse result = OtlpSenderSupport.send(client, retryPolicy, request, ratelimitedLogger);

assertFalse(result.success());
assertTrue(result.exception().isPresent());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mock-maker-inline
23 changes: 23 additions & 0 deletions dd-java-agent/agent-profiling/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {

api project(':dd-java-agent:agent-profiling:profiling-ddprof')
api project(':dd-java-agent:agent-profiling:profiling-uploader')
api project(':dd-java-agent:agent-profiling:profiling-otel')
api project(':dd-java-agent:agent-profiling:profiling-controller')
implementation project(':dd-java-agent:agent-profiling:profiling-scrubber')
api project(':dd-java-agent:agent-profiling:profiling-controller-jfr')
Expand All @@ -42,9 +43,31 @@ configurations {
}
}

// jafar-tools is a fat jar bundling (an incomplete copy of) the jafar-parser classes; the agent
// bundles jafar-parser (via profiling-otel) instead and only needs the io.jafar.tools classes
// (used by profiling-scrubber) from the fat jar
configurations {
jafarTools
}
dependencies {
jafarTools("io.btrace:jafar-tools:${libs.versions.jafar.get()}") {
transitive = false
}
}
def jafarToolsJar = configurations.jafarTools

tasks.named("shadowJar", ShadowJar) {
dependencies deps.excludeShared

// drop the jafar-tools fat jar (parser classes come from jafar-parser via profiling-otel) and
// re-add only its unique io/jafar/tools classes to keep a single copy of every class
dependencies {
exclude(dependency('io.btrace:jafar-tools:.*'))
}
from({ zipTree(jafarToolsJar.singleFile) }) {
include 'io/jafar/tools/**'
}

// Exclude multi-release versioned classes from jafar-parser.
// These are duplicates of base classes for newer Java APIs and confuse
// the GraalVM native-image builder when the profiling jar is embedded in the agent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public RecordingInputStream getStream() throws IOException {
}

@Override
public void release() {
protected void doRelease() {
recording.close();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public RecordingInputStream getStream() throws IOException {
}

@Override
public void release() {
protected void doRelease() {
// noop
}

Expand Down
Loading