From d33670373a3782ac165cc8fabf067b9b4c63b2ef Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 21 Oct 2026 16:05:43 +0200 Subject: [PATCH 1/6] Add experimental Datadog profiler option overrides --- .../profiling/ddprof/DatadogProfiler.java | 68 +++++++++++++++- .../ddprof/DatadogProfilerConfig.java | 5 ++ .../ddprof/DatadogProfilerConfigTest.java | 79 +++++++++++++++++++ .../profiling/ddprof/DatadogProfilerTest.java | 22 ++++++ .../trace/api/config/ProfilingConfig.java | 3 + metadata/supported-configurations.json | 16 ++++ 6 files changed, 191 insertions(+), 2 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 c1305763464..69878f8e46e 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 @@ -9,6 +9,7 @@ import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getLogLevel; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getNativeMemoryInterval; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getNativeSocketInterval; +import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getProfilerOptions; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getSafeMode; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getSchedulingEvent; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.getSchedulingEventInterval; @@ -37,6 +38,7 @@ import static datadog.trace.api.config.ProfilingConfig.PROFILING_DETAILED_DEBUG_LOGGING_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_THRESHOLD_MILLIS; import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_THRESHOLD_MILLIS_DEFAULT; +import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; import com.datadog.profiling.controller.OngoingRecording; import com.datadog.profiling.utils.ProfilingMode; @@ -56,7 +58,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport; @@ -77,9 +81,15 @@ public final class DatadogProfiler { private static final String OPERATION = "_dd.trace.operation"; private static final String RESOURCE = "_dd.trace.resource"; + static final String PROFILER_OPTIONS_WARNING = + "The profiling.experimental.ddprof.options configuration directly modifies Datadog " + + "profiler options, overrides conflicting derived options, and may cause unexpected " + + "behavior."; + private static final int MAX_NUM_ENDPOINTS = 8192; private final boolean detailedDebugLogging; + private final String profilerOptions; /** * Creates a profiler API with default configuration, may result in loading the profiler native @@ -219,6 +229,8 @@ void reset() { private DatadogProfiler(ConfigProvider configProvider) { this.configProvider = configProvider; + this.profilerOptions = getProfilerOptions(configProvider); + warnIfProfilerOptionsConfigured(log, profilerOptions); this.profiler = DdprofLibraryLoader.javaProfiler().getComponent(); this.detailedDebugLogging = configProvider.getBoolean( @@ -398,6 +410,9 @@ void dump(Path path) { String cmdStartProfiling(Path file) throws IllegalStateException { // 'start' = start, 'jfr=7' = store in JFR format ready for concatenation + StringBuilder cmd = new StringBuilder("start,jfr"); + cmd.append(",file=").append(file.toAbsolutePath()); + int derivedOptionsStart = cmd.length(); int safemode = getSafeMode(configProvider); if (safemode != ProfilingConfig.PROFILING_DATADOG_PROFILER_SAFEMODE_DEFAULT) { // be very vocal about messing around with the profiler safemode as it may induce crashes @@ -406,8 +421,6 @@ String cmdStartProfiling(Path file) throws IllegalStateException { + "This is not recommended and may cause instability and crashes.", safemode); } - StringBuilder cmd = new StringBuilder("start,jfr"); - cmd.append(",file=").append(file.toAbsolutePath()); cmd.append(",loglevel=").append(getLogLevel(configProvider)); cmd.append(",jstackdepth=").append(getStackDepth(configProvider)); cmd.append(",cstack=").append(getCStack(configProvider)); @@ -487,11 +500,62 @@ String cmdStartProfiling(Path file) throws IllegalStateException { cmd.append('=').append(interval); } } + if (profilerOptions != null && !profilerOptions.isEmpty()) { + String mergedOptions = + mergeProfilerOptions(cmd.substring(derivedOptionsStart), profilerOptions); + cmd.setLength(derivedOptionsStart); + cmd.append(mergedOptions); + } String cmdString = cmd.toString(); log.debug("Datadog profiler command line: {}", cmdString); return cmdString; } + static String mergeProfilerOptions(String derivedOptions, String configuredOptions) { + Map configuredByName = new LinkedHashMap<>(); + for (String option : configuredOptions.split(",")) { + String trimmedOption = option.trim(); + if (!trimmedOption.isEmpty()) { + String name = profilerOptionName(trimmedOption); + if (isReservedProfilerOption(name)) { + continue; + } + // Reinsert duplicate keys to order them by their last occurrence. + configuredByName.remove(name); + configuredByName.put(name, trimmedOption); + } + } + + StringBuilder mergedOptions = new StringBuilder(); + for (String option : derivedOptions.split(",")) { + String trimmedOption = option.trim(); + if (!trimmedOption.isEmpty() + && !configuredByName.containsKey(profilerOptionName(trimmedOption))) { + mergedOptions.append(',').append(trimmedOption); + } + } + for (String option : configuredByName.values()) { + mergedOptions.append(',').append(option); + } + return mergedOptions.toString(); + } + + static void warnIfProfilerOptionsConfigured(Logger logger, String configuredOptions) { + if (configuredOptions != null && !configuredOptions.isEmpty()) { + logger.warn(SEND_TELEMETRY, PROFILER_OPTIONS_WARNING); + } + } + + private static String profilerOptionName(String option) { + int equals = option.indexOf('='); + return equals < 0 ? option : option.substring(0, equals).trim(); + } + + private static boolean isReservedProfilerOption(String name) { + // Keep the agent-controlled action, format, and recording path. + return "start".equals(name) || "jfr".equals(name) || "file".equals(name); + } + public void recordTraceRoot(long rootSpanId, String endpoint, String operation) { if (!profiler.recordTraceRoot(rootSpanId, endpoint, operation, MAX_NUM_ENDPOINTS)) { log.debug( diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java index b54b069fe55..3269c31756b 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java @@ -40,6 +40,7 @@ import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_NATIVESOCKET_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_NATIVESOCKET_ENABLED_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_NATIVESOCKET_INTERVAL; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_SAFEMODE; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_SAFEMODE_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_SCHEDULING_EVENT; @@ -90,6 +91,10 @@ public static String getLibPath() { return getLibPath(ConfigProvider.getInstance()); } + public static String getProfilerOptions(ConfigProvider configProvider) { + return getString(configProvider, PROFILING_DATADOG_PROFILER_OPTIONS); + } + public static boolean isCpuProfilerEnabled() { return isCpuProfilerEnabled(ConfigProvider.getInstance()); } diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java index ed4b4476606..8c2eadfcfe5 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java @@ -1,9 +1,14 @@ package com.datadog.profiling.ddprof; import static com.datadog.profiling.controller.ProfilingSupport.isOldObjectSampleAvailable; +import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import datadog.environment.JavaVirtualMachine; import datadog.trace.api.config.ProfilingConfig; @@ -16,6 +21,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; class DatadogProfilerConfigTest { private String originalVmName; @@ -97,6 +103,79 @@ void testGetCStackWithHotspot(String configuredStackwalker, String expectedStack + "'"); } + @Test + void profilerOptionsPreferDdprofKeyOverAsyncCompatibilityKey() { + Properties props = new Properties(); + props.put("profiling.experimental.async.options", "cpu=20m"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "cpu=10m"); + + assertEquals( + "cpu=10m", + DatadogProfilerConfig.getProfilerOptions(ConfigProvider.withPropertiesOverride(props))); + } + + @Test + void profilerOptionsUseAsyncCompatibilityKeyAsFallback() { + Properties props = new Properties(); + props.put("profiling.experimental.async.options", "cpu=20m"); + + assertEquals( + "cpu=20m", + DatadogProfilerConfig.getProfilerOptions(ConfigProvider.withPropertiesOverride(props))); + } + + @Test + void profilerOptionsAreNullWhenUnset() { + assertNull( + DatadogProfilerConfig.getProfilerOptions( + ConfigProvider.withPropertiesOverride(new Properties()))); + } + + @ParameterizedTest + @MethodSource("profilerOptionMergeCases") + void mergeProfilerOptions( + String derivedOptions, String configuredOptions, String expectedOptions) { + assertEquals( + expectedOptions, DatadogProfiler.mergeProfilerOptions(derivedOptions, configuredOptions)); + } + + @Test + void warningIsNotEmittedWithoutProfilerOptions() { + Logger logger = mock(Logger.class); + + DatadogProfiler.warnIfProfilerOptionsConfigured(logger, null); + DatadogProfiler.warnIfProfilerOptionsConfigured(logger, ""); + + verifyNoInteractions(logger); + } + + @Test + void warningIsMarkedForTelemetryWhenProfilerOptionsAreConfigured() { + Logger logger = mock(Logger.class); + + DatadogProfiler.warnIfProfilerOptionsConfigured(logger, "cpu=7m"); + + verify(logger).warn(SEND_TELEMETRY, DatadogProfiler.PROFILER_OPTIONS_WARNING); + } + + private static Stream profilerOptionMergeCases() { + return Stream.of( + Arguments.of( + ",cpu=10m,wall=50m,loglevel=INFO", + "cpu=7m,nativemem=131072", + ",wall=50m,loglevel=INFO,cpu=7m,nativemem=131072"), + Arguments.of( + ",loglevel=INFO,cpu=10m,loglevel=INFO", "loglevel=DEBUG", ",cpu=10m,loglevel=DEBUG"), + Arguments.of(",natsock=100us,wall=50m", "natsock", ",wall=50m,natsock"), + Arguments.of( + ",cpu=10m,wall=50m", + " , cpu=7m, , nativemem=131072, ", + ",wall=50m,cpu=7m,nativemem=131072"), + Arguments.of(",cpu=10m,wall=50m", " ", ",cpu=10m,wall=50m"), + Arguments.of(",cpu=10m,wall=50m", "cpu=5m,cpu=7m", ",wall=50m,cpu=7m"), + Arguments.of(",cpu=10m", "start,jfr,file=other.jfr,cpu=7m", ",cpu=7m")); + } + private static Stream j9StackwalkerTestCases() { return Stream.of( // Unsupported stackwalkers - should fall back to dwarf 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 c09e6ce8fb9..1efb00058cd 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 @@ -121,6 +121,28 @@ void testStartCmdEnableJMethodIDOptim() throws Exception { assertTrue(startCmd(profiler).contains(",fjmethodid=false")); } + @Test + void testStartCmdExperimentalOptionsExtendAndOverrideDerivedOptions() throws Exception { + assertDoesNotThrow( + () -> DdprofLibraryLoader.jvmAccess().getReasonNotLoaded(), "Profiler not available"); + + Properties props = new Properties(); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "cpu=7m,nativemem=131072"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_LOG_LEVEL, "DEBUG"); + DatadogProfiler profiler = + DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props)); + + Path targetFile = Paths.get("/tmp/target.jfr"); + String cmd = profiler.cmdStartProfiling(targetFile); + assertTrue(cmd.startsWith("start,jfr,file=" + targetFile.toAbsolutePath())); + int explicitCpuOption = cmd.lastIndexOf(",cpu=7m"); + assertTrue(explicitCpuOption >= 0); + assertEquals(explicitCpuOption, cmd.indexOf(",cpu=")); + assertTrue(cmd.contains(",wall=")); + assertTrue(cmd.contains(",loglevel=DEBUG")); + assertTrue(cmd.endsWith(",cpu=7m,nativemem=131072")); + } + @Test void testStartCmdNativeMemDisabledByDefault() throws Exception { assertDoesNotThrow( diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java index 37dc6c3c500..6bfae88186e 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java @@ -131,6 +131,9 @@ public final class ProfilingConfig { public static final String PROFILING_DATADOG_PROFILER_SCHEDULING_EVENT_INTERVAL = "profiling.experimental.ddprof.scheduling.event.interval"; + public static final String PROFILING_DATADOG_PROFILER_OPTIONS = + "profiling.experimental.ddprof.options"; + public static final String PROFILING_PROCESS_CONTEXT_ENABLED = "profiling.experimental.process_context.enabled"; public static final boolean PROFILING_PROCESS_CONTEXT_ENABLED_DEFAULT = true; diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 45e12e44c85..76664798195 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -3273,6 +3273,14 @@ "aliases": [] } ], + "DD_PROFILING_EXPERIMENTAL_ASYNC_OPTIONS": [ + { + "version": "A", + "type": "string", + "default": null, + "aliases": [] + } + ], "DD_PROFILING_EXPERIMENTAL_ASYNC_SCHEDULING_EVENT": [ { "version": "A", @@ -3337,6 +3345,14 @@ "aliases": [] } ], + "DD_PROFILING_EXPERIMENTAL_DDPROF_OPTIONS": [ + { + "version": "A", + "type": "string", + "default": null, + "aliases": [] + } + ], "DD_PROFILING_EXPERIMENTAL_DDPROF_SCHEDULING_EVENT": [ { "version": "A", From ba89aa7998750f94963b509ebcd565e44ad914bc Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 21 Oct 2026 16:38:21 +0200 Subject: [PATCH 2/6] Remove unintended async profiler option alias --- .../profiling/ddprof/DatadogProfilerConfig.java | 2 +- .../profiling/ddprof/DatadogProfilerConfigTest.java | 13 +------------ metadata/supported-configurations.json | 8 -------- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java index 3269c31756b..518c048993e 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java @@ -92,7 +92,7 @@ public static String getLibPath() { } public static String getProfilerOptions(ConfigProvider configProvider) { - return getString(configProvider, PROFILING_DATADOG_PROFILER_OPTIONS); + return configProvider.getString(PROFILING_DATADOG_PROFILER_OPTIONS); } public static boolean isCpuProfilerEnabled() { diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java index 8c2eadfcfe5..2fd0103feb1 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java @@ -104,9 +104,8 @@ void testGetCStackWithHotspot(String configuredStackwalker, String expectedStack } @Test - void profilerOptionsPreferDdprofKeyOverAsyncCompatibilityKey() { + void profilerOptionsReadDdprofKey() { Properties props = new Properties(); - props.put("profiling.experimental.async.options", "cpu=20m"); props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "cpu=10m"); assertEquals( @@ -114,16 +113,6 @@ void profilerOptionsPreferDdprofKeyOverAsyncCompatibilityKey() { DatadogProfilerConfig.getProfilerOptions(ConfigProvider.withPropertiesOverride(props))); } - @Test - void profilerOptionsUseAsyncCompatibilityKeyAsFallback() { - Properties props = new Properties(); - props.put("profiling.experimental.async.options", "cpu=20m"); - - assertEquals( - "cpu=20m", - DatadogProfilerConfig.getProfilerOptions(ConfigProvider.withPropertiesOverride(props))); - } - @Test void profilerOptionsAreNullWhenUnset() { assertNull( diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 76664798195..602133f28d9 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -3273,14 +3273,6 @@ "aliases": [] } ], - "DD_PROFILING_EXPERIMENTAL_ASYNC_OPTIONS": [ - { - "version": "A", - "type": "string", - "default": null, - "aliases": [] - } - ], "DD_PROFILING_EXPERIMENTAL_ASYNC_SCHEDULING_EVENT": [ { "version": "A", From 0e4f669f25c5cc6b16283fe5f3150460d0d2b551 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 21 Oct 2026 17:29:07 +0200 Subject: [PATCH 3/6] Harden experimental profiler option overrides --- .../profiling/ddprof/DatadogProfiler.java | 158 ++++++++++++++---- .../ddprof/DatadogProfilerConfigTest.java | 54 +++++- .../profiling/ddprof/DatadogProfilerTest.java | 30 ++++ 3 files changed, 212 insertions(+), 30 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 69878f8e46e..c7ee1061990 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 @@ -60,6 +60,7 @@ import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -119,6 +120,7 @@ public static DatadogProfiler newInstance(ConfigProvider configProvider) { private final AtomicBoolean recordingFlag = new AtomicBoolean(false); private final ConfigProvider configProvider; private final JavaProfiler profiler; + private final Set configuredProfilingModes = EnumSet.noneOf(ProfilingMode.class); private final Set profilingModes = EnumSet.noneOf(ProfilingMode.class); private final ContextSetter contextSetter; @@ -244,23 +246,25 @@ private DatadogProfiler(ConfigProvider configProvider) { // TODO enable/disable events by name (e.g. datadog.ExecutionSample), not flag, so configuration // can be consistent with JFR event control if (isAllocationProfilingEnabled(configProvider)) { - profilingModes.add(ALLOCATION); + configuredProfilingModes.add(ALLOCATION); } if (isMemoryLeakProfilingEnabled(configProvider)) { - profilingModes.add(MEMLEAK); + configuredProfilingModes.add(MEMLEAK); } if (isCpuProfilerEnabled(configProvider)) { - profilingModes.add(CPU); + configuredProfilingModes.add(CPU); } if (isWallClockProfilerEnabled(configProvider)) { - profilingModes.add(WALL); + configuredProfilingModes.add(WALL); } if (isNativeMemoryProfilingEnabled(configProvider)) { - profilingModes.add(NATIVEMEM); + configuredProfilingModes.add(NATIVEMEM); } if (isNativeSocketProfilingEnabled(configProvider)) { - profilingModes.add(NATIVESOCKET); + configuredProfilingModes.add(NATIVESOCKET); } + profilingModes.addAll(configuredProfilingModes); + applyProfilerOptionModes(profilingModes, profilerOptions); Set contextAttributes = getContextAttributes(configProvider); this.orderedContextAttributes = getOrderedContextAttributes(contextAttributes, configProvider); this.contextSetter = new ContextSetter(profiler, orderedContextAttributes); @@ -436,7 +440,7 @@ String cmdStartProfiling(Path file) throws IllegalStateException { cmd.append(",fjmethodid=false"); } - if (profilingModes.contains(CPU)) { + if (configuredProfilingModes.contains(CPU)) { // cpu profiling is enabled. String schedulingEvent = getSchedulingEvent(configProvider); if (schedulingEvent != null && !schedulingEvent.isEmpty()) { @@ -457,7 +461,7 @@ String cmdStartProfiling(Path file) throws IllegalStateException { cmd.append(",cpu=").append(interval).append('m'); } } - if (profilingModes.contains(WALL)) { + if (configuredProfilingModes.contains(WALL)) { // wall profiling is enabled. cmd.append(",wall="); if (getWallCollapsing(configProvider)) { @@ -475,24 +479,25 @@ String cmdStartProfiling(Path file) throws IllegalStateException { } } cmd.append(",loglevel=").append(getLogLevel(configProvider)); - if (profilingModes.contains(ALLOCATION) || profilingModes.contains(MEMLEAK)) { + if (configuredProfilingModes.contains(ALLOCATION) + || configuredProfilingModes.contains(MEMLEAK)) { // allocation profiling or live heap profiling is enabled cmd.append(",memory=").append(getAllocationInterval(configProvider)).append('b'); cmd.append(':'); - if (profilingModes.contains(ALLOCATION)) { + if (configuredProfilingModes.contains(ALLOCATION)) { cmd.append('a'); } - if (profilingModes.contains(MEMLEAK)) { + if (configuredProfilingModes.contains(MEMLEAK)) { cmd.append(isLiveHeapSizeTrackingEnabled(configProvider) ? 'L' : 'l'); cmd.append(':') .append(String.format("%.2f", getLiveHeapSamplePercent(configProvider) / 100.0d)); } } - if (profilingModes.contains(NATIVEMEM)) { + if (configuredProfilingModes.contains(NATIVEMEM)) { // native memory (malloc) profiling is enabled cmd.append(",nativemem=").append(getNativeMemoryInterval(configProvider)); } - if (profilingModes.contains(NATIVESOCKET)) { + if (configuredProfilingModes.contains(NATIVESOCKET)) { // native socket I/O profiling is enabled String interval = getNativeSocketInterval(configProvider); cmd.append(",natsock"); @@ -512,19 +517,7 @@ String cmdStartProfiling(Path file) throws IllegalStateException { } static String mergeProfilerOptions(String derivedOptions, String configuredOptions) { - Map configuredByName = new LinkedHashMap<>(); - for (String option : configuredOptions.split(",")) { - String trimmedOption = option.trim(); - if (!trimmedOption.isEmpty()) { - String name = profilerOptionName(trimmedOption); - if (isReservedProfilerOption(name)) { - continue; - } - // Reinsert duplicate keys to order them by their last occurrence. - configuredByName.remove(name); - configuredByName.put(name, trimmedOption); - } - } + Map configuredByName = configuredProfilerOptions(configuredOptions); StringBuilder mergedOptions = new StringBuilder(); for (String option : derivedOptions.split(",")) { @@ -540,6 +533,23 @@ static String mergeProfilerOptions(String derivedOptions, String configuredOptio return mergedOptions.toString(); } + private static Map configuredProfilerOptions(String configuredOptions) { + Map configuredByName = new LinkedHashMap<>(); + for (String option : configuredOptions.split(",")) { + String trimmedOption = option.trim(); + if (!trimmedOption.isEmpty()) { + String name = profilerOptionName(trimmedOption); + if (isReservedProfilerOption(name)) { + continue; + } + // Reinsert duplicate keys to order them by their last occurrence. + configuredByName.remove(name); + configuredByName.put(name, trimmedOption); + } + } + return configuredByName; + } + static void warnIfProfilerOptionsConfigured(Logger logger, String configuredOptions) { if (configuredOptions != null && !configuredOptions.isEmpty()) { logger.warn(SEND_TELEMETRY, PROFILER_OPTIONS_WARNING); @@ -548,12 +558,102 @@ static void warnIfProfilerOptionsConfigured(Logger logger, String configuredOpti private static String profilerOptionName(String option) { int equals = option.indexOf('='); - return equals < 0 ? option : option.substring(0, equals).trim(); + String name = equals < 0 ? option : option.substring(0, equals).trim(); + return name.toLowerCase(Locale.ROOT); } private static boolean isReservedProfilerOption(String name) { - // Keep the agent-controlled action, format, and recording path. - return "start".equals(name) || "jfr".equals(name) || "file".equals(name); + switch (name) { + case "start": + case "resume": + case "stop": + case "check": + case "status": + case "list": + case "version": + case "jfr": + case "file": + case "attributes": + return true; + default: + return false; + } + } + + static void applyProfilerOptionModes(Set modes, String configuredOptions) { + if (configuredOptions == null || configuredOptions.isEmpty()) { + return; + } + Map configuredByName = configuredProfilerOptions(configuredOptions); + for (Map.Entry option : configuredByName.entrySet()) { + String name = option.getKey(); + switch (name) { + case "cpu": + modes.add(CPU); + break; + case "event": + String event = profilerOptionValue(option.getValue()); + if (!event.isEmpty() && !"alloc".equals(event) && !"noop".equals(event)) { + modes.add(CPU); + } + break; + case "wall": + modes.add(WALL); + break; + case "memory": + applyMemoryProfilerModes(modes, profilerOptionValue(option.getValue())); + break; + case "nativemem": + modes.add(NATIVEMEM); + break; + case "natsock": + modes.add(NATIVESOCKET); + break; + default: + break; + } + } + String generations = configuredByName.get("generations"); + if (generations != null && profilerBooleanValue(generations)) { + modes.add(MEMLEAK); + } + } + + private static void applyMemoryProfilerModes(Set modes, String value) { + modes.remove(ALLOCATION); + modes.remove(MEMLEAK); + int separator = value.indexOf(':'); + if (separator < 0) { + modes.add(ALLOCATION); + modes.add(MEMLEAK); + return; + } + String flags = value.substring(separator + 1); + int nextSeparator = flags.indexOf(':'); + if (nextSeparator >= 0) { + flags = flags.substring(0, nextSeparator); + } + if (flags.indexOf('a') >= 0) { + modes.add(ALLOCATION); + } + if (flags.indexOf('l') >= 0 || flags.indexOf('L') >= 0) { + modes.add(MEMLEAK); + } + } + + private static String profilerOptionValue(String option) { + int equals = option.indexOf('='); + return equals < 0 ? "" : option.substring(equals + 1).trim(); + } + + private static boolean profilerBooleanValue(String option) { + String value = profilerOptionValue(option); + return value.isEmpty() + || "y".equals(value) + || "yes".equals(value) + || "t".equals(value) + || "true".equals(value) + || "1".equals(value); } public void recordTraceRoot(long rootSpanId, String endpoint, String operation) { diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java index 2fd0103feb1..e2ffee9e1b4 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java @@ -10,10 +10,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import com.datadog.profiling.utils.ProfilingMode; import datadog.environment.JavaVirtualMachine; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.bootstrap.config.provider.ConfigProvider; +import java.util.EnumSet; import java.util.Properties; +import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -162,7 +165,56 @@ private static Stream profilerOptionMergeCases() { ",wall=50m,cpu=7m,nativemem=131072"), Arguments.of(",cpu=10m,wall=50m", " ", ",cpu=10m,wall=50m"), Arguments.of(",cpu=10m,wall=50m", "cpu=5m,cpu=7m", ",wall=50m,cpu=7m"), - Arguments.of(",cpu=10m", "start,jfr,file=other.jfr,cpu=7m", ",cpu=7m")); + Arguments.of(",cpu=10m", "CPU=7m", ",CPU=7m"), + Arguments.of( + ",cpu=10m,attributes=foo", + "START,Resume,stop,CHECK,status,List,VERSION,JFR,File=other.jfr,ATTRIBUTES=bar,cpu=7m", + ",attributes=foo,cpu=7m")); + } + + @ParameterizedTest + @MethodSource("profilerOptionModeCases") + void applyProfilerOptionModes( + Set initialModes, String configuredOptions, Set expectedModes) { + DatadogProfiler.applyProfilerOptionModes(initialModes, configuredOptions); + + assertEquals(expectedModes, initialModes); + } + + private static Stream profilerOptionModeCases() { + return Stream.of( + Arguments.of( + EnumSet.noneOf(ProfilingMode.class), + "CPU=7m,event=cycles,WALL=50m,nativemem=131072,NATSOCK", + EnumSet.of( + ProfilingMode.CPU, + ProfilingMode.WALL, + ProfilingMode.NATIVEMEM, + ProfilingMode.NATIVESOCKET)), + Arguments.of( + EnumSet.of(ProfilingMode.ALLOCATION), + "memory=262144b:l:0.1", + EnumSet.of(ProfilingMode.MEMLEAK)), + Arguments.of( + EnumSet.of(ProfilingMode.MEMLEAK), + "memory=262144b:a", + EnumSet.of(ProfilingMode.ALLOCATION)), + Arguments.of( + EnumSet.of(ProfilingMode.ALLOCATION, ProfilingMode.MEMLEAK), + "memory=262144b:", + EnumSet.noneOf(ProfilingMode.class)), + Arguments.of( + EnumSet.noneOf(ProfilingMode.class), + "memory=262144b:aL:0.1", + EnumSet.of(ProfilingMode.ALLOCATION, ProfilingMode.MEMLEAK)), + Arguments.of( + EnumSet.noneOf(ProfilingMode.class), + "memory=262144b:a,memory=262144b:l", + EnumSet.of(ProfilingMode.MEMLEAK)), + Arguments.of( + EnumSet.noneOf(ProfilingMode.class), + "event=noop,memory=262144b:,generations=true", + EnumSet.of(ProfilingMode.MEMLEAK))); } private static Stream j9StackwalkerTestCases() { 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 1efb00058cd..d834ea8cf9c 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 @@ -141,6 +141,36 @@ void testStartCmdExperimentalOptionsExtendAndOverrideDerivedOptions() throws Exc assertTrue(cmd.contains(",wall=")); assertTrue(cmd.contains(",loglevel=DEBUG")); assertTrue(cmd.endsWith(",cpu=7m,nativemem=131072")); + assertTrue(profiler.enabledModes().contains(ProfilingMode.NATIVEMEM)); + } + + @Test + void testExperimentalMemoryOptionUpdatesEnabledModes() throws Exception { + assertDoesNotThrow( + () -> DdprofLibraryLoader.jvmAccess().getReasonNotLoaded(), "Profiler not available"); + + Properties props = new Properties(); + props.put(ProfilingConfig.PROFILING_ALLOCATION_ENABLED, "false"); + props.put(ProfilingConfig.PROFILING_HEAP_ENABLED, "false"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "memory=262144b:a"); + DatadogProfiler profiler = + DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props)); + + assertTrue(profiler.enabledModes().contains(ProfilingMode.ALLOCATION)); + assertFalse(profiler.enabledModes().contains(ProfilingMode.MEMLEAK)); + String cmd = startCmd(profiler); + assertTrue(cmd.endsWith(",memory=262144b:a"), cmd); + assertEquals(cmd.indexOf(",memory="), cmd.lastIndexOf(",memory="), cmd); + + props.put(ProfilingConfig.PROFILING_ALLOCATION_ENABLED, "true"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "memory=262144b:l:0.1"); + profiler = DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props)); + + assertFalse(profiler.enabledModes().contains(ProfilingMode.ALLOCATION)); + assertTrue(profiler.enabledModes().contains(ProfilingMode.MEMLEAK)); + cmd = startCmd(profiler); + assertTrue(cmd.endsWith(",memory=262144b:l:0.1"), cmd); + assertEquals(cmd.indexOf(",memory="), cmd.lastIndexOf(",memory="), cmd); } @Test From a47df9dcf3f027271968df8e520d925e99ab9c0a Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 21 Oct 2026 18:20:13 +0200 Subject: [PATCH 4/6] Avoid forbidden regex option parsing --- .../profiling/ddprof/DatadogProfiler.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 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 c7ee1061990..d39d53ea699 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 @@ -520,12 +520,20 @@ static String mergeProfilerOptions(String derivedOptions, String configuredOptio Map configuredByName = configuredProfilerOptions(configuredOptions); StringBuilder mergedOptions = new StringBuilder(); - for (String option : derivedOptions.split(",")) { - String trimmedOption = option.trim(); + for (int start = 0; start <= derivedOptions.length(); ) { + int end = derivedOptions.indexOf(',', start); + if (end < 0) { + end = derivedOptions.length(); + } + String trimmedOption = derivedOptions.substring(start, end).trim(); if (!trimmedOption.isEmpty() && !configuredByName.containsKey(profilerOptionName(trimmedOption))) { mergedOptions.append(',').append(trimmedOption); } + if (end == derivedOptions.length()) { + break; + } + start = end + 1; } for (String option : configuredByName.values()) { mergedOptions.append(',').append(option); @@ -535,17 +543,24 @@ static String mergeProfilerOptions(String derivedOptions, String configuredOptio private static Map configuredProfilerOptions(String configuredOptions) { Map configuredByName = new LinkedHashMap<>(); - for (String option : configuredOptions.split(",")) { - String trimmedOption = option.trim(); + for (int start = 0; start <= configuredOptions.length(); ) { + int end = configuredOptions.indexOf(',', start); + if (end < 0) { + end = configuredOptions.length(); + } + String trimmedOption = configuredOptions.substring(start, end).trim(); if (!trimmedOption.isEmpty()) { String name = profilerOptionName(trimmedOption); - if (isReservedProfilerOption(name)) { - continue; + if (!isReservedProfilerOption(name)) { + // Reinsert duplicate keys to order them by their last occurrence. + configuredByName.remove(name); + configuredByName.put(name, trimmedOption); } - // Reinsert duplicate keys to order them by their last occurrence. - configuredByName.remove(name); - configuredByName.put(name, trimmedOption); } + if (end == configuredOptions.length()) { + break; + } + start = end + 1; } return configuredByName; } From c374493be39692ee29938618e4f8880003bb74e7 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Thu, 22 Oct 2026 12:22:01 +0200 Subject: [PATCH 5/6] Handle profiler CPU option conflicts --- .../profiling/ddprof/DatadogProfiler.java | 31 +++++++++++++++-- .../ddprof/DatadogProfilerConfigTest.java | 23 +++++++++++-- .../profiling/ddprof/DatadogProfilerTest.java | 33 +++++++++++++++++++ 3 files changed, 83 insertions(+), 4 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 d39d53ea699..6360f809ac2 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 @@ -527,7 +527,7 @@ static String mergeProfilerOptions(String derivedOptions, String configuredOptio } String trimmedOption = derivedOptions.substring(start, end).trim(); if (!trimmedOption.isEmpty() - && !configuredByName.containsKey(profilerOptionName(trimmedOption))) { + && !overridesDerivedProfilerOption(configuredByName, profilerOptionName(trimmedOption))) { mergedOptions.append(',').append(trimmedOption); } if (end == derivedOptions.length()) { @@ -552,9 +552,13 @@ private static Map configuredProfilerOptions(String configuredOp if (!trimmedOption.isEmpty()) { String name = profilerOptionName(trimmedOption); if (!isReservedProfilerOption(name)) { + if (isCpuEngineOption(name)) { + configuredByName.remove("cpu"); + configuredByName.remove("event"); + } // Reinsert duplicate keys to order them by their last occurrence. configuredByName.remove(name); - configuredByName.put(name, trimmedOption); + configuredByName.put(name, normalizeProfilerOption(name, trimmedOption)); } } if (end == configuredOptions.length()) { @@ -565,6 +569,26 @@ private static Map configuredProfilerOptions(String configuredOp return configuredByName; } + private static boolean overridesDerivedProfilerOption( + Map configuredByName, String derivedName) { + if (configuredByName.containsKey(derivedName)) { + return true; + } + if (isCpuEngineOption(derivedName)) { + return configuredByName.containsKey("cpu") || configuredByName.containsKey("event"); + } + return "interval".equals(derivedName) && configuredByName.containsKey("cpu"); + } + + private static boolean isCpuEngineOption(String name) { + return "cpu".equals(name) || "event".equals(name); + } + + private static String normalizeProfilerOption(String name, String option) { + int equals = option.indexOf('='); + return equals < 0 ? name : name + '=' + option.substring(equals + 1).trim(); + } + static void warnIfProfilerOptionsConfigured(Logger logger, String configuredOptions) { if (configuredOptions != null && !configuredOptions.isEmpty()) { logger.warn(SEND_TELEMETRY, PROFILER_OPTIONS_WARNING); @@ -600,6 +624,9 @@ static void applyProfilerOptionModes(Set modes, String configured return; } Map configuredByName = configuredProfilerOptions(configuredOptions); + if (configuredByName.containsKey("cpu") || configuredByName.containsKey("event")) { + modes.remove(CPU); + } for (Map.Entry option : configuredByName.entrySet()) { String name = option.getKey(); switch (name) { diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java index e2ffee9e1b4..f140b67fa2a 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerConfigTest.java @@ -165,7 +165,15 @@ private static Stream profilerOptionMergeCases() { ",wall=50m,cpu=7m,nativemem=131072"), Arguments.of(",cpu=10m,wall=50m", " ", ",cpu=10m,wall=50m"), Arguments.of(",cpu=10m,wall=50m", "cpu=5m,cpu=7m", ",wall=50m,cpu=7m"), - Arguments.of(",cpu=10m", "CPU=7m", ",CPU=7m"), + Arguments.of(",cpu=10m", "CPU=7m", ",cpu=7m"), + Arguments.of(",cpu=10m,wall=50m", "cpu = 7m", ",wall=50m,cpu=7m"), + Arguments.of(",cpu=10m,wall=50m", "event=cycles", ",wall=50m,event=cycles"), + Arguments.of(",event=cycles,interval=1000000", "cpu=7m", ",cpu=7m"), + Arguments.of(",cpu=10m", "cpu=7m,event=cycles", ",event=cycles"), + Arguments.of( + ",event=cycles,interval=1000000", + "event=instructions", + ",interval=1000000,event=instructions"), Arguments.of( ",cpu=10m,attributes=foo", "START,Resume,stop,CHECK,status,List,VERSION,JFR,File=other.jfr,ATTRIBUTES=bar,cpu=7m", @@ -214,7 +222,18 @@ private static Stream profilerOptionModeCases() { Arguments.of( EnumSet.noneOf(ProfilingMode.class), "event=noop,memory=262144b:,generations=true", - EnumSet.of(ProfilingMode.MEMLEAK))); + EnumSet.of(ProfilingMode.MEMLEAK)), + Arguments.of( + EnumSet.of(ProfilingMode.CPU), "event=noop", EnumSet.noneOf(ProfilingMode.class)), + Arguments.of(EnumSet.of(ProfilingMode.CPU), "event=cycles", EnumSet.of(ProfilingMode.CPU)), + Arguments.of( + EnumSet.of(ProfilingMode.CPU), + "cpu=7m,event=noop", + EnumSet.noneOf(ProfilingMode.class)), + Arguments.of( + EnumSet.noneOf(ProfilingMode.class), + "event=noop,cpu=7m", + EnumSet.of(ProfilingMode.CPU))); } private static Stream j9StackwalkerTestCases() { 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 d834ea8cf9c..7f0b5d872ee 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 @@ -144,6 +144,39 @@ void testStartCmdExperimentalOptionsExtendAndOverrideDerivedOptions() throws Exc assertTrue(profiler.enabledModes().contains(ProfilingMode.NATIVEMEM)); } + @Test + void testExperimentalEventOptionOverridesDerivedCpuOption() throws Exception { + assertDoesNotThrow( + () -> DdprofLibraryLoader.jvmAccess().getReasonNotLoaded(), "Profiler not available"); + + Properties props = new Properties(); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "event=cycles"); + DatadogProfiler profiler = + DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props)); + + String cmd = startCmd(profiler); + assertFalse(cmd.contains(",cpu="), cmd); + assertTrue(cmd.endsWith(",event=cycles"), cmd); + assertTrue(profiler.enabledModes().contains(ProfilingMode.CPU)); + } + + @Test + void testExperimentalNoopEventDisablesCpuMode() throws Exception { + assertDoesNotThrow( + () -> DdprofLibraryLoader.jvmAccess().getReasonNotLoaded(), "Profiler not available"); + + Properties props = new Properties(); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_SCHEDULING_EVENT, "cycles"); + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "event=noop"); + DatadogProfiler profiler = + DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props)); + + String cmd = startCmd(profiler); + assertFalse(cmd.contains(",event=cycles"), cmd); + assertTrue(cmd.endsWith(",event=noop"), cmd); + assertFalse(profiler.enabledModes().contains(ProfilingMode.CPU)); + } + @Test void testExperimentalMemoryOptionUpdatesEnabledModes() throws Exception { assertDoesNotThrow( From 51efb8a23179a688fd1f7afc7f72debb868106f5 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Thu, 22 Oct 2026 18:05:24 +0200 Subject: [PATCH 6/6] Fix J9 wall default assumption in profiler options test --- .../java/com/datadog/profiling/ddprof/DatadogProfilerTest.java | 3 +++ 1 file changed, 3 insertions(+) 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 7f0b5d872ee..021f36b331d 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 @@ -128,6 +128,9 @@ void testStartCmdExperimentalOptionsExtendAndOverrideDerivedOptions() throws Exc Properties props = new Properties(); props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_OPTIONS, "cpu=7m,nativemem=131072"); + // wall clock is disabled by default on J9, so opt in explicitly to keep the derived-option + // assertions JVM-independent + props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_ENABLED, "true"); props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_LOG_LEVEL, "DEBUG"); DatadogProfiler profiler = DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props));