From 7cea2eff0d28f4ce2af484a60dbf0fbb550810bb Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Thu, 4 Jun 2026 11:17:49 +0000 Subject: [PATCH] [PROF-14883] Publish thread context attribute keys at process start **What does this PR do?** This PR adjusts the `ProcessContext` setup to match the change in https://github.com/DataDog/java-profiler/pull/576 to publish the context attributes at process start. **Motivation:** Publishing the context attributes at process start enables outside readers of thread context to immediately start reading this information; prior to this change they were impacted by the profiler start delay. **Additional Notes:** This PR relies on the API changes introduced in https://github.com/DataDog/java-profiler/pull/576 and will need to be merged together with an upgrade to the latest java-profiler. **How to test the change?** This change includes test coverage. --- .../profiling/ddprof/DatadogProfiler.java | 31 +++++++++++-------- .../profiling/ddprof/DatadogProfilerTest.java | 10 ++++-- .../profiling/agent/ProcessContext.java | 10 +++++- .../profiling/agent/ProcessContextTest.java | 9 +++++- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java index 9aedde9e49f..a9d29b627dd 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java @@ -38,7 +38,6 @@ import datadog.environment.JavaVirtualMachine; import datadog.libs.ddprof.DdprofLibraryLoader; import datadog.trace.api.config.ProfilingConfig; -import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.bootstrap.instrumentation.api.TaskWrapper; @@ -113,11 +112,6 @@ public static DatadogProfiler newInstance(ConfigProvider configProvider) { private final Path recordingsPath; private DatadogProfiler(ConfigProvider configProvider) { - this(configProvider, getContextAttributes(configProvider)); - } - - @VisibleForTesting - DatadogProfiler(ConfigProvider configProvider, Set contextAttributes) { this.configProvider = configProvider; this.profiler = DdprofLibraryLoader.javaProfiler().getComponent(); this.detailedDebugLogging = @@ -143,13 +137,7 @@ private DatadogProfiler(ConfigProvider configProvider) { if (isWallClockProfilerEnabled(configProvider)) { profilingModes.add(WALL); } - this.orderedContextAttributes = new ArrayList<>(contextAttributes); - if (isSpanNameContextAttributeEnabled(configProvider)) { - orderedContextAttributes.add(OPERATION); - } - if (isResourceNameContextAttributeEnabled(configProvider)) { - orderedContextAttributes.add(RESOURCE); - } + this.orderedContextAttributes = getOrderedContextAttributes(configProvider); this.contextSetter = new ContextSetter(profiler, orderedContextAttributes); this.queueTimeThresholdMillis = configProvider.getLong( @@ -170,6 +158,23 @@ private DatadogProfiler(ConfigProvider configProvider) { } } + /** + * Computes the ordered context-attribute list (base attributes from config, then the optional + * span-name and resource-name attributes) in the exact order used for the per-thread {@link + * ContextSetter} and the native profiler's {@code attributes=} argument. Exposed so the OTel + * process context can publish the same {@code attribute_key_map} before the profiler starts. + */ + public static List getOrderedContextAttributes(ConfigProvider configProvider) { + List ordered = new ArrayList<>(getContextAttributes(configProvider)); + if (isSpanNameContextAttributeEnabled(configProvider)) { + ordered.add(OPERATION); + } + if (isResourceNameContextAttributeEnabled(configProvider)) { + ordered.add(RESOURCE); + } + return ordered; + } + void addThread() { profiler.addThread(); } diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java index 55d39ba52a0..65da1163a3d 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java @@ -17,7 +17,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import java.util.HashSet; import java.util.Properties; import java.util.UUID; import java.util.stream.IntStream; @@ -169,9 +168,14 @@ public void testContextRegistration() { // so there is only one shot to test it here, 'foo,bar' need to be kept in the same // order whether in the list or the enum, and any other test which tries to register // context attributes will fail + Properties props = new Properties(); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_CPU_ENABLED, "true"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_ENABLED, "true"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_ALLOC_ENABLED, "true"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, "true"); + props.put(ProfilingConfig.PROFILING_CONTEXT_ATTRIBUTES, "foo,bar"); DatadogProfiler profiler = - new DatadogProfiler( - configProvider(true, true, true, true), new HashSet<>(Arrays.asList("foo", "bar"))); + DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props)); assertTrue(profiler.setContextValue("foo", "abc")); assertTrue(profiler.setContextValue("bar", "abc")); assertTrue(profiler.setContextValue("foo", "xyz")); diff --git a/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProcessContext.java b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProcessContext.java index 7eaf7f857e7..df3cde3cc4a 100644 --- a/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProcessContext.java +++ b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProcessContext.java @@ -1,9 +1,11 @@ package com.datadog.profiling.agent; +import com.datadog.profiling.ddprof.DatadogProfiler; import datadog.libs.ddprof.DdprofLibraryLoader; import datadog.trace.api.Config; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.bootstrap.config.provider.ConfigProvider; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +21,11 @@ public static void register(ConfigProvider configProvider) { Throwable err = holder.getReasonNotLoaded(); if (err == null) { Config cfg = Config.get(); + // Publish the thread-context attribute keys together with the process context so the + // very first published context already carries the attribute_key_map. The order must + // match what DatadogProfiler passes to the native attributes= argument and the + // per-thread ContextSetter, so external readers can decode the thread-local record. + List attributeKeys = DatadogProfiler.getOrderedContextAttributes(configProvider); holder .getComponent() .setProcessContext( @@ -27,7 +34,8 @@ public static void register(ConfigProvider configProvider) { cfg.getRuntimeId(), cfg.getServiceName(), cfg.getRuntimeVersion(), - cfg.getVersion()); + cfg.getVersion(), + attributeKeys.toArray(new String[0])); } else { log.warn("Failed to register process context for OTel profiler", err); } diff --git a/dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ProcessContextTest.java b/dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ProcessContextTest.java index 960a8da60bc..54346e94681 100644 --- a/dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ProcessContextTest.java +++ b/dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ProcessContextTest.java @@ -1,6 +1,8 @@ package com.datadog.profiling.agent; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; @@ -12,6 +14,8 @@ import datadog.trace.api.Config; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.bootstrap.config.provider.ConfigProvider; +import java.util.Arrays; +import java.util.LinkedHashSet; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; @@ -24,6 +28,8 @@ void testRegisterSetsProcessContextValues() { eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED), eq(ProfilingConfig.PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT))) .thenReturn(true); + when(configProvider.getSet(eq(ProfilingConfig.PROFILING_CONTEXT_ATTRIBUTES), any())) + .thenReturn(new LinkedHashSet<>(Arrays.asList("http.route", "db.system"))); Config config = mock(Config.class); when(config.getEnv()).thenReturn("test-env"); @@ -54,7 +60,8 @@ void testRegisterSetsProcessContextValues() { eq("test-runtime-id"), eq("test-service"), eq("test-runtime-version"), - eq("test-version")); + eq("test-version"), + aryEq(new String[] {"http.route", "db.system"})); } }