Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
933d2ee
feat(profiling): Add OTLP profiles core infrastructure
jbachorik Dec 1, 2025
35b3ab3
feat(profiling): Initial implementation of JFR->OTLP/P format
jbachorik Dec 2, 2025
0569606
feat(profiling): Add benchmarks for OTLP/P converter
jbachorik Dec 2, 2025
84ec6f0
feat(profiling): Add OLTP/P format validation tests
jbachorik Dec 3, 2025
4368ee4
feat(profiling): Add JMH filtering and update benchmarks with results
jbachorik Dec 3, 2025
8087006
chore(profiling): revert dictionary optimization and add profiling su…
jbachorik Dec 4, 2025
9214ea1
feat(profiling): Add stack trace caching optimization using JFR const…
jbachorik Dec 4, 2025
ec41c02
fix(profiling): Skip Docker-dependent validation tests gracefully whe…
jbachorik Dec 4, 2025
381f0db
feat(profiling): Add original_payload support to OTLP profiles converter
jbachorik Dec 4, 2025
e2ac6b5
feat(profiling): Add RecordingData reference counting and OTLP config…
jbachorik Dec 4, 2025
ebb7d02
feat(profiling): Implement OtlpProfileUploader for OTLP format uploads
jbachorik Dec 4, 2025
d497650
feat(profiling): Integrate OtlpProfileUploader with explicit referenc…
jbachorik Dec 4, 2025
826c717
feat(profiling): Add profiling-otel to agent-profiling shadowJar
jbachorik Dec 4, 2025
75f077f
feat(profiling): Add CLI tool for converting JFR to OTLP format
jbachorik Dec 5, 2025
1c9fceb
feat(profiling): Add optional pretty-printing for JSON output
jbachorik Dec 5, 2025
ed30489
feat(profiling): Add profcheck integration for OTLP profile validation
jbachorik Dec 5, 2025
d175a42
fix(profiling): Fix OTLP dictionary index 0 sentinel encoding
jbachorik Dec 5, 2025
d5c78c2
feat(profiling): Add sample attributes support to OTLP profiles conve…
jbachorik Dec 5, 2025
a5197b9
fix(profiling): Fix timestamp validation and make profcheck mandatory
jbachorik Dec 5, 2025
7b9529a
feat(profiling): Add convenience script for JFR to OTLP conversion
jbachorik Dec 8, 2025
1b30ff2
feat(profiling): Add diagnostics mode to convert-jfr.sh script
jbachorik Dec 8, 2025
f3439fc
feat(profiling): Add JFR to OTLP conversion convenience script with d…
jbachorik Dec 8, 2025
7a11c57
feat(profiling): Convert JFR converter script to use fat jar for 31x …
jbachorik Dec 8, 2025
d021da6
refactor(profiling): Consolidate converter script output for clarity
jbachorik Dec 8, 2025
fd74efe
Properly handle `objectClass` attribute
jbachorik Dec 8, 2025
52c579e
Spotless
jbachorik Dec 8, 2025
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 dd-java-agent/agent-profiling/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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')
api project(':dd-java-agent:agent-profiling:profiling-controller-jfr')
api project(':dd-java-agent:agent-profiling:profiling-controller-jfr:implementation')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2025 Datadog
*
* 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.datadog.profiling.controller;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import datadog.trace.api.profiling.ProfilingSnapshot;
import datadog.trace.api.profiling.RecordingData;
import datadog.trace.api.profiling.RecordingInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.junit.jupiter.api.Test;

/** Tests for RecordingData reference counting with multiple handlers. */
public class RecordingDataRefCountingTest {

/** Test RecordingData implementation that tracks release calls. */
private static class TestRecordingData extends RecordingData {
private final AtomicInteger releaseCount = new AtomicInteger(0);
private final CountDownLatch releaseLatch = new CountDownLatch(1);

public TestRecordingData() {
super(Instant.now(), Instant.now(), ProfilingSnapshot.Kind.PERIODIC);
}

@Nonnull
@Override
public RecordingInputStream getStream() throws IOException {
return new RecordingInputStream(new ByteArrayInputStream(new byte[0]));
}

@Override
protected void doRelease() {
releaseCount.incrementAndGet();
releaseLatch.countDown();
}

@Nullable
@Override
public Path getFile() {
return null;
}

@Override
public String getName() {
return "test-recording";
}

public int getReleaseCount() {
return releaseCount.get();
}

public boolean awaitRelease(long timeout, TimeUnit unit) throws InterruptedException {
return releaseLatch.await(timeout, unit);
}
}

@Test
public void testSingleHandler() throws InterruptedException {
TestRecordingData data = new TestRecordingData();

// Single handler: retain once, release once
data.retain();
assertEquals(0, data.getReleaseCount(), "Should not be released yet");

data.release();

assertTrue(data.awaitRelease(1, TimeUnit.SECONDS), "Release should be called");
assertEquals(1, data.getReleaseCount(), "doRelease() should be called exactly once");
}

@Test
public void testTwoHandlers() throws InterruptedException {
TestRecordingData data = new TestRecordingData();

// Two handlers (e.g., JFR + OTLP): retain twice
data.retain(); // Handler 1
data.retain(); // Handler 2
assertEquals(0, data.getReleaseCount(), "Should not be released yet");

// First handler releases
data.release();
assertEquals(0, data.getReleaseCount(), "Should not be released after first release");

// Second handler releases
data.release();

assertTrue(data.awaitRelease(1, TimeUnit.SECONDS), "Release should be called");
assertEquals(1, data.getReleaseCount(), "doRelease() should be called exactly once");
}

@Test
public void testThreeHandlers() throws InterruptedException {
TestRecordingData data = new TestRecordingData();

// Three handlers (e.g., dumper + JFR + OTLP): retain three times
data.retain(); // Handler 1
data.retain(); // Handler 2
data.retain(); // Handler 3
assertEquals(0, data.getReleaseCount(), "Should not be released yet");

// First two handlers release
data.release();
data.release();
assertEquals(0, data.getReleaseCount(), "Should not be released after two releases");

// Third handler releases
data.release();

assertTrue(data.awaitRelease(1, TimeUnit.SECONDS), "Release should be called");
assertEquals(1, data.getReleaseCount(), "doRelease() should be called exactly once");
}

@Test
public void testReleaseBeforeRetain() {
TestRecordingData data = new TestRecordingData();

// Cannot release before any retain
assertThrows(
IllegalStateException.class,
data::release,
"Should throw when releasing with refcount=0");
}

@Test
public void testRetainAfterFullRelease() throws InterruptedException {
TestRecordingData data = new TestRecordingData();

data.retain();
data.release();
assertTrue(data.awaitRelease(1, TimeUnit.SECONDS), "Release should be called");

// Cannot retain after full release
assertThrows(
IllegalStateException.class,
data::retain,
"Should throw when retaining after release");
}

@Test
public void testMultipleReleaseIdempotent() throws InterruptedException {
TestRecordingData data = new TestRecordingData();

data.retain();
data.release();
assertTrue(data.awaitRelease(1, TimeUnit.SECONDS), "Release should be called");

// Additional release calls should be no-op
data.release();
data.release();

assertEquals(1, data.getReleaseCount(), "doRelease() should still be called exactly once");
}

@Test
public void testConcurrentHandlers() throws InterruptedException {
TestRecordingData data = new TestRecordingData();
int numHandlers = 10;

// Retain for all handlers
for (int i = 0; i < numHandlers; i++) {
data.retain();
}

// Simulate concurrent release from multiple threads
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(numHandlers);

for (int i = 0; i < numHandlers; i++) {
new Thread(
() -> {
try {
startLatch.await();
data.release();
doneLatch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
})
.start();
}

// Start all threads
startLatch.countDown();

// Wait for all threads to complete
assertTrue(doneLatch.await(5, TimeUnit.SECONDS), "All threads should complete");
assertTrue(data.awaitRelease(1, TimeUnit.SECONDS), "Release should be called");
assertEquals(1, data.getReleaseCount(), "doRelease() should be called exactly once");
}

@Test
public void testRetainChaining() {
TestRecordingData data = new TestRecordingData();

// retain() should return this for chaining
RecordingData result = data.retain();
assertEquals(data, result, "retain() should return the same instance");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public RecordingInputStream getStream() throws IOException {
}

@Override
public void release() {
protected void doRelease() {
try {
Files.deleteIfExists(recordingFile);
} catch (IOException e) {
Expand All @@ -36,4 +36,9 @@ public void release() {
public String getName() {
return "ddprof";
}

@Override
public Path getFile() {
return recordingFile;
}
}
Loading
Loading