Skip to content

Commit 6588b97

Browse files
jbachorikclaude
andcommitted
feat(profiling): Add RecordingData reference counting and OTLP configuration constants
Implement foundation for parallel OTLP profile uploads alongside JFR format. **Step 1: RecordingData Reference Counting** Add thread-safe reference counting to support multiple listeners accessing the same RecordingData: - Add AtomicInteger refCount and volatile boolean released flag - Add retain() method to increment reference count before passing to additional listeners - Make release() final with automatic reference counting (decrements and calls doRelease at 0) - Add protected doRelease() for actual cleanup (called when refcount reaches 0) - Update all implementations: OpenJdkRecordingData, DatadogProfilerRecordingData, OracleJdkRecordingData, CompositeRecordingData Reference counting pattern enables multiple uploaders (JFR + OTLP) to safely share RecordingData without double-release or resource leaks. Each listener calls retain() before use and release() when done. Actual cleanup happens only when refcount reaches zero. **Step 2: OTLP Configuration Constants** Add configuration property keys to ProfilingConfig for OTLP profile format support: - profiling.otlp.enabled (default: false) - Enable parallel OTLP upload - profiling.otlp.include.original.payload (default: false) - Embed source JFR in OTLP - profiling.otlp.url (default: "") - OTLP endpoint URL (empty = derive from agent URL) - profiling.otlp.compression (default: "gzip") - Compression type for OTLP upload Configuration will be read directly from ConfigProvider in OtlpProfileUploader for testability. Next steps: - Step 3: Implement OtlpProfileUploader class (reads config from ConfigProvider) - Step 4: Integrate with ProfilingAgent - Step 5: Add tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 52b1dde commit 6588b97

File tree

6 files changed

+66
-6
lines changed

6 files changed

+66
-6
lines changed

dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public RecordingInputStream getStream() throws IOException {
4444
}
4545

4646
@Override
47-
public void release() {
47+
protected void doRelease() {
4848
recording.close();
4949
}
5050

dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public RecordingInputStream getStream() throws IOException {
5151
}
5252

5353
@Override
54-
public void release() {
54+
protected void doRelease() {
5555
// noop
5656
}
5757

dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public RecordingInputStream getStream() throws IOException {
2323
}
2424

2525
@Override
26-
public void release() {
26+
protected void doRelease() {
2727
try {
2828
Files.deleteIfExists(recordingFile);
2929
} catch (IOException e) {

dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/CompositeController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public RecordingInputStream getStream() throws IOException {
117117
}
118118

119119
@Override
120-
public void release() {
120+
protected void doRelease() {
121121
for (RecordingData data : data) {
122122
data.release();
123123
}

dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,5 +263,19 @@ public final class ProfilingConfig {
263263
public static final String PROFILING_DETAILED_DEBUG_LOGGING = "profiling.detailed.debug.logging";
264264
public static final boolean PROFILING_DETAILED_DEBUG_LOGGING_DEFAULT = false;
265265

266+
// OTLP Profiles Format Support
267+
public static final String PROFILING_OTLP_ENABLED = "profiling.otlp.enabled";
268+
public static final boolean PROFILING_OTLP_ENABLED_DEFAULT = false;
269+
270+
public static final String PROFILING_OTLP_INCLUDE_ORIGINAL_PAYLOAD =
271+
"profiling.otlp.include.original.payload";
272+
public static final boolean PROFILING_OTLP_INCLUDE_ORIGINAL_PAYLOAD_DEFAULT = false;
273+
274+
public static final String PROFILING_OTLP_URL = "profiling.otlp.url";
275+
public static final String PROFILING_OTLP_URL_DEFAULT = ""; // Empty = derive from agent URL
276+
277+
public static final String PROFILING_OTLP_COMPRESSION = "profiling.otlp.compression";
278+
public static final String PROFILING_OTLP_COMPRESSION_DEFAULT = "gzip";
279+
266280
private ProfilingConfig() {}
267281
}

internal-api/src/main/java/datadog/trace/api/profiling/RecordingData.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919
import java.nio.file.Path;
2020
import java.time.Instant;
21+
import java.util.concurrent.atomic.AtomicInteger;
2122
import javax.annotation.Nonnull;
2223
import javax.annotation.Nullable;
2324

@@ -27,6 +28,10 @@ public abstract class RecordingData implements ProfilingSnapshot {
2728
protected final Instant end;
2829
protected final Kind kind;
2930

31+
// Reference counting for multiple listeners
32+
private final AtomicInteger refCount = new AtomicInteger(1); // Start at 1
33+
private volatile boolean released = false;
34+
3035
public RecordingData(final Instant start, final Instant end, Kind kind) {
3136
this.start = start;
3237
this.end = end;
@@ -40,10 +45,30 @@ public RecordingData(final Instant start, final Instant end, Kind kind) {
4045
@Nonnull
4146
public abstract RecordingInputStream getStream() throws IOException;
4247

48+
/**
49+
* Increment reference count. Must be called before passing RecordingData to additional listeners
50+
* beyond the first.
51+
*
52+
* @return this instance for chaining
53+
* @throws IllegalStateException if the recording has already been released
54+
*/
55+
@Nonnull
56+
public final RecordingData retain() {
57+
if (released) {
58+
throw new IllegalStateException("Cannot retain released RecordingData");
59+
}
60+
refCount.incrementAndGet();
61+
return this;
62+
}
63+
4364
/**
4465
* Releases the resources associated with the recording, for example the underlying file.
4566
*
46-
* <p>Forgetting to releasing this when done streaming, will lead to one or more of the following:
67+
* <p>This method uses reference counting to support multiple listeners. Each call to {@link
68+
* #retain()} must be matched with a call to {@code release()}. The actual resource cleanup
69+
* happens when the reference count reaches zero.
70+
*
71+
* <p>Forgetting to release this when done streaming will lead to one or more of the following:
4772
*
4873
* <ul>
4974
* <li>Memory leak
@@ -52,7 +77,28 @@ public RecordingData(final Instant start, final Instant end, Kind kind) {
5277
*
5378
* <p>Please don't forget to call release when done streaming...
5479
*/
55-
public abstract void release();
80+
public final void release() {
81+
if (released) {
82+
return; // Already released, no-op
83+
}
84+
85+
int remaining = refCount.decrementAndGet();
86+
if (remaining == 0) {
87+
released = true;
88+
doRelease();
89+
} else if (remaining < 0) {
90+
// Should never happen, but guard against it
91+
throw new IllegalStateException("RecordingData over-released");
92+
}
93+
}
94+
95+
/**
96+
* Actual resource cleanup implementation. Subclasses must override this method instead of {@link
97+
* #release()}.
98+
*
99+
* <p>This method is called exactly once when the reference count reaches zero.
100+
*/
101+
protected abstract void doRelease();
56102

57103
/**
58104
* Returns the name of the recording from which the data is originating.

0 commit comments

Comments
 (0)