Skip to content

Commit 1f2c16a

Browse files
authored
Add a PoC for OTel process context support (#9472)
1 parent 4135f1d commit 1f2c16a

File tree

6 files changed

+204
-1
lines changed

6 files changed

+204
-1
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.datadog.profiling.agent;
2+
3+
import datadog.libs.ddprof.DdprofLibraryLoader;
4+
import datadog.trace.api.Config;
5+
import datadog.trace.api.config.ProfilingConfig;
6+
import datadog.trace.bootstrap.config.provider.ConfigProvider;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
public final class ProcessContext {
11+
private static final Logger log = LoggerFactory.getLogger(ProcessContext.class.getName());
12+
13+
public static void register(ConfigProvider configProvider) {
14+
if (configProvider.getBoolean(
15+
ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED,
16+
ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT)) {
17+
log.info("Registering process context for OTel profiler");
18+
DdprofLibraryLoader.OTelContextHolder holder = DdprofLibraryLoader.otelContext();
19+
Throwable err = holder.getReasonNotLoaded();
20+
if (err == null) {
21+
Config cfg = Config.get();
22+
holder
23+
.getComponent()
24+
.setProcessContext(
25+
cfg.getEnv(),
26+
cfg.getHostName(),
27+
cfg.getRuntimeId(),
28+
cfg.getServiceName(),
29+
cfg.getRuntimeVersion(),
30+
cfg.getVersion());
31+
} else {
32+
log.warn("Failed to register process context for OTel profiler", err);
33+
}
34+
}
35+
}
36+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public static synchronized boolean run(final boolean earlyStart, Instrumentation
9696
// Register the profiler flare before we start the profiling system, but early during the
9797
// profiler lifecycle
9898
ProfilerFlareReporter.register();
99+
ProcessContext.register(configProvider);
99100

100101
boolean startForceFirst =
101102
Platform.isNativeImage()
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.datadog.profiling.agent;
2+
3+
import static org.mockito.ArgumentMatchers.eq;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.mockStatic;
6+
import static org.mockito.Mockito.verify;
7+
import static org.mockito.Mockito.when;
8+
9+
import com.datadoghq.profiler.OTelContext;
10+
import datadog.libs.ddprof.DdprofLibraryLoader;
11+
import datadog.trace.api.Config;
12+
import datadog.trace.api.config.ProfilingConfig;
13+
import datadog.trace.bootstrap.config.provider.ConfigProvider;
14+
import org.junit.jupiter.api.Test;
15+
import org.mockito.MockedStatic;
16+
17+
class ProcessContextTest {
18+
19+
@Test
20+
void testRegisterSetsProcessContextValues() {
21+
ConfigProvider configProvider = mock(ConfigProvider.class);
22+
when(configProvider.getBoolean(
23+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED),
24+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT)))
25+
.thenReturn(true);
26+
27+
Config config = mock(Config.class);
28+
when(config.getEnv()).thenReturn("test-env");
29+
when(config.getHostName()).thenReturn("test-host");
30+
when(config.getRuntimeId()).thenReturn("test-runtime-id");
31+
when(config.getServiceName()).thenReturn("test-service");
32+
when(config.getRuntimeVersion()).thenReturn("test-runtime-version");
33+
when(config.getVersion()).thenReturn("test-version");
34+
35+
OTelContext otelContext = mock(OTelContext.class);
36+
DdprofLibraryLoader.OTelContextHolder holder =
37+
mock(DdprofLibraryLoader.OTelContextHolder.class);
38+
when(holder.getReasonNotLoaded()).thenReturn(null);
39+
when(holder.getComponent()).thenReturn(otelContext);
40+
41+
try (MockedStatic<Config> configMock = mockStatic(Config.class);
42+
MockedStatic<DdprofLibraryLoader> ddprofMock = mockStatic(DdprofLibraryLoader.class)) {
43+
44+
configMock.when(Config::get).thenReturn(config);
45+
ddprofMock.when(DdprofLibraryLoader::otelContext).thenReturn(holder);
46+
47+
ProcessContext.register(configProvider);
48+
49+
verify(otelContext)
50+
.setProcessContext(
51+
eq("test-env"),
52+
eq("test-host"),
53+
eq("test-runtime-id"),
54+
eq("test-service"),
55+
eq("test-runtime-version"),
56+
eq("test-version"));
57+
}
58+
}
59+
60+
@Test
61+
void testRegisterSkipsWhenDisabled() {
62+
ConfigProvider configProvider = mock(ConfigProvider.class);
63+
when(configProvider.getBoolean(
64+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED),
65+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT)))
66+
.thenReturn(false);
67+
68+
DdprofLibraryLoader.OTelContextHolder holder =
69+
mock(DdprofLibraryLoader.OTelContextHolder.class);
70+
71+
try (MockedStatic<DdprofLibraryLoader> ddprofMock = mockStatic(DdprofLibraryLoader.class)) {
72+
ddprofMock.when(DdprofLibraryLoader::otelContext).thenReturn(holder);
73+
74+
ProcessContext.register(configProvider);
75+
76+
verify(holder, org.mockito.Mockito.never()).getReasonNotLoaded();
77+
verify(holder, org.mockito.Mockito.never()).getComponent();
78+
}
79+
}
80+
81+
@Test
82+
void testRegisterSkipsByDefault() {
83+
ConfigProvider configProvider = mock(ConfigProvider.class);
84+
when(configProvider.getBoolean(
85+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED),
86+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT)))
87+
.thenReturn(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT);
88+
89+
DdprofLibraryLoader.OTelContextHolder holder =
90+
mock(DdprofLibraryLoader.OTelContextHolder.class);
91+
92+
try (MockedStatic<DdprofLibraryLoader> ddprofMock = mockStatic(DdprofLibraryLoader.class)) {
93+
ddprofMock.when(DdprofLibraryLoader::otelContext).thenReturn(holder);
94+
95+
ProcessContext.register(configProvider);
96+
97+
verify(holder, org.mockito.Mockito.never()).getReasonNotLoaded();
98+
verify(holder, org.mockito.Mockito.never()).getComponent();
99+
}
100+
}
101+
102+
@Test
103+
void testRegisterHandlesLibraryLoadFailure() {
104+
ConfigProvider configProvider = mock(ConfigProvider.class);
105+
when(configProvider.getBoolean(
106+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED),
107+
eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT)))
108+
.thenReturn(true);
109+
110+
Throwable loadError = new RuntimeException("Library load failed");
111+
DdprofLibraryLoader.OTelContextHolder holder =
112+
mock(DdprofLibraryLoader.OTelContextHolder.class);
113+
when(holder.getReasonNotLoaded()).thenReturn(loadError);
114+
115+
try (MockedStatic<DdprofLibraryLoader> ddprofMock = mockStatic(DdprofLibraryLoader.class)) {
116+
ddprofMock.when(DdprofLibraryLoader::otelContext).thenReturn(holder);
117+
118+
ProcessContext.register(configProvider);
119+
120+
verify(holder).getReasonNotLoaded();
121+
verify(holder, org.mockito.Mockito.never()).getComponent();
122+
}
123+
}
124+
}

dd-java-agent/ddprof-lib/src/main/java/datadog/libs/ddprof/DdprofLibraryLoader.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.datadoghq.profiler.JVMAccess;
44
import com.datadoghq.profiler.JavaProfiler;
5+
import com.datadoghq.profiler.OTelContext;
56
import datadog.trace.api.config.ProfilingConfig;
67
import datadog.trace.bootstrap.config.provider.ConfigProvider;
78
import datadog.trace.util.TempLocationManager;
@@ -91,12 +92,25 @@ public JVMAccessHolder(Supplier<? extends ComponentHolder<JVMAccess>> initialize
9192
}
9293
}
9394

95+
public static final class OTelContextHolder extends ComponentHolder<OTelContext> {
96+
public OTelContextHolder(Supplier<? extends ComponentHolder<OTelContext>> initializer) {
97+
super(initializer);
98+
}
99+
100+
OTelContextHolder(OTelContext component, Throwable reasonNotLoaded) {
101+
super(component, reasonNotLoaded);
102+
}
103+
}
104+
94105
private static final JavaProfilerHolder PROFILER_HOLDER =
95106
new JavaProfilerHolder(DdprofLibraryLoader::initJavaProfiler);
96107

97108
private static final JVMAccessHolder JVM_ACCESS_HOLDER =
98109
new JVMAccessHolder(DdprofLibraryLoader::initJVMAccess);
99110

111+
private static final OTelContextHolder OTEL_CONTEXT_HOLDER =
112+
new OTelContextHolder(DdprofLibraryLoader::initOtelContext);
113+
100114
public static JavaProfilerHolder javaProfiler() {
101115
return PROFILER_HOLDER;
102116
}
@@ -105,6 +119,10 @@ public static JVMAccessHolder jvmAccess() {
105119
return JVM_ACCESS_HOLDER;
106120
}
107121

122+
public static OTelContextHolder otelContext() {
123+
return OTEL_CONTEXT_HOLDER;
124+
}
125+
108126
private static JavaProfilerHolder initJavaProfiler() {
109127
JavaProfiler profiler;
110128
Throwable reasonNotLoaded = null;
@@ -144,6 +162,26 @@ private static JVMAccessHolder initJVMAccess() {
144162
return new JVMAccessHolder(jvmAccess, reasonNotLoaded.get());
145163
}
146164

165+
private static OTelContextHolder initOtelContext() {
166+
ConfigProvider configProvider = ConfigProvider.getInstance();
167+
AtomicReference<Throwable> reasonNotLoaded = new AtomicReference<>();
168+
OTelContext otelContext = null;
169+
try {
170+
String scratchDir = getScratchDir(configProvider);
171+
otelContext = new OTelContext(null, scratchDir, reasonNotLoaded::set);
172+
} catch (Throwable t) {
173+
if (reasonNotLoaded.get() == null) {
174+
reasonNotLoaded.set(t);
175+
} else {
176+
// if we already have a reason, don't overwrite it
177+
// this can happen if the OTelContext constructor throws an exception
178+
// and then the execute method throws another one
179+
}
180+
otelContext = null;
181+
}
182+
return new OTelContextHolder(otelContext, reasonNotLoaded.get());
183+
}
184+
147185
private static String getScratchDir(ConfigProvider configProvider) throws IOException {
148186
String scratch = configProvider.getString(ProfilingConfig.PROFILING_DATADOG_PROFILER_SCRATCH);
149187
if (scratch == null) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ public final class ProfilingConfig {
129129
public static final String PROFILING_DATADOG_PROFILER_SCHEDULING_EVENT_INTERVAL =
130130
"profiling.experimental.ddprof.scheduling.event.interval";
131131

132+
public static final String PROFILING_PROCESS_CONTEXT_ENABLED =
133+
"profiling.experimental.process_context.enabled";
134+
public static final boolean PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT = false;
135+
132136
public static final String PROFILING_DATADOG_PROFILER_LOG_LEVEL = "profiling.ddprof.loglevel";
133137

134138
public static final String PROFILING_DATADOG_PROFILER_LOG_LEVEL_DEFAULT = "NONE";

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ moshi = '1.11.0'
3131
testcontainers = '1.20.1'
3232
jmc = "8.1.0"
3333
autoservice = "1.1.1"
34-
ddprof = "1.29.0"
34+
ddprof = "1.31.0"
3535
asm = "9.8"
3636
cafe_crypto = "0.1.0"
3737
lz4 = "1.7.1"

0 commit comments

Comments
 (0)