Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,24 @@

import static com.datadog.profiling.controller.ProfilingSupport.*;
import static datadog.environment.JavaVirtualMachine.isJavaVersionAtLeast;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_CPU_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_CPU_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_EXCEPTION_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_EXCEPTION_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_IO_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_IO_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_LOCK_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_LOCK_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_ENABLED_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.config.ProfilingConfig.PROFILING_THREAD_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_THREAD_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ULTRA_MINIMAL;

import com.datadog.profiling.controller.ConfigurationException;
Expand Down Expand Up @@ -63,6 +73,13 @@ public final class OpenJdkController implements Controller {
private static final String EXPLICITLY_ENABLED = "explicitly enabled by user";
private static final String EXPENSIVE_ON_CURRENT_JVM =
"expensive on this version of the JVM (" + JavaVirtualMachine.getRuntimeVersion() + ")";

/**
* jdk.JavaMonitorWait, jdk.ThreadPark, and jdk.ThreadSleep are intentionally NOT gated by
* PROFILING_WALLTIME_ENABLED. They serve timeline and queueing-time purposes independently of
* wall-clock profiling. This constant makes the design decision machine-checkable.
*/
static final boolean GATE_WALLTIME_TIMELINE_EVENTS = false;
private static final String CPUTIME_SAMPLE_JDK25 = "Switching to CPUTimeSample on JDK 25+";

static final Duration RECORDING_MAX_AGE = Duration.ofMinutes(5);
Expand Down Expand Up @@ -264,6 +281,54 @@ ProfilingConfig.PROFILING_ALLOCATION_ENABLED, isObjectAllocationSampleAvailable(
"Aggregated smaps collection is disabled in the config");
}

// Feature gates — unified profiling.*.enabled keys.
// These gates are evaluated at recording creation time only; profiling does not use Remote
// Config, so events disabled here remain disabled for the lifetime of the recording.

if (!configProvider.getBoolean(PROFILING_CPU_ENABLED, PROFILING_CPU_ENABLED_DEFAULT)) {
disableEvent(recordingSettings, "jdk.ExecutionSample", "disabled by CPU profiling gate");
disableEvent(recordingSettings, "jdk.NativeMethodSample", "disabled by CPU profiling gate");
disableEvent(recordingSettings, "jdk.CPUTimeSample", "disabled by CPU profiling gate");
disableEvent(recordingSettings, "jdk.CPUTimeSamplesLost", "disabled by CPU profiling gate");
}
Comment thread
jbachorik marked this conversation as resolved.

// jdk.JavaMonitorWait, jdk.ThreadPark, and jdk.ThreadSleep are intentionally NOT gated by
// PROFILING_WALLTIME_ENABLED: they serve purposes beyond wall-clock profile construction
// (timeline blocking events, queueing time) and must remain enabled independently.

if (!configProvider.getBoolean(
PROFILING_EXCEPTION_ENABLED, PROFILING_EXCEPTION_ENABLED_DEFAULT)) {
disableEvent(
recordingSettings, "datadog.ExceptionSample", "disabled by exception profiling gate");
disableEvent(
recordingSettings, "datadog.ExceptionCount", "disabled by exception profiling gate");
}

if (!configProvider.getBoolean(PROFILING_IO_ENABLED, PROFILING_IO_ENABLED_DEFAULT)) {
disableEvent(recordingSettings, "jdk.FileRead", "disabled by I/O profiling gate");
disableEvent(recordingSettings, "jdk.FileWrite", "disabled by I/O profiling gate");
disableEvent(recordingSettings, "jdk.FileForce", "disabled by I/O profiling gate");
disableEvent(recordingSettings, "jdk.SocketRead", "disabled by I/O profiling gate");
disableEvent(recordingSettings, "jdk.SocketWrite", "disabled by I/O profiling gate");
}

if (!configProvider.getBoolean(PROFILING_LOCK_ENABLED, PROFILING_LOCK_ENABLED_DEFAULT)) {
disableEvent(recordingSettings, "jdk.JavaMonitorEnter", "disabled by lock profiling gate");
// BiasedLock events are no-ops on JDK 18+ (biased locking removed via JEP 374); disabling
// them here is harmless on modern JDKs.
disableEvent(
recordingSettings, "jdk.BiasedLockRevocation", "disabled by lock profiling gate");
disableEvent(
recordingSettings, "jdk.BiasedLockSelfRevocation", "disabled by lock profiling gate");
disableEvent(
recordingSettings, "jdk.BiasedLockClassRevocation", "disabled by lock profiling gate");
}

if (!configProvider.getBoolean(PROFILING_THREAD_ENABLED, PROFILING_THREAD_ENABLED_DEFAULT)) {
disableEvent(recordingSettings, "jdk.ThreadStart", "disabled by thread profiling gate");
disableEvent(recordingSettings, "jdk.ThreadEnd", "disabled by thread profiling gate");
}

// Warn users for expensive events

if (!isOldObjectSampleAvailable() && isEventEnabled(recordingSettings, "jdk.OldObjectSample")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ALLOCATION_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_AUXILIARY_TYPE;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_AUXILIARY_TYPE_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_CPU_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_EXCEPTION_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_IO_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_LOCK_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_TEMPLATE_OVERRIDE_FILE;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_THREAD_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_WALLTIME_ENABLED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -276,6 +282,144 @@ public void testUnifiedFlagDisabledTurnsOffOldObjectSample() throws Exception {
}
}

@Test
public void testCpuGateDisablesCpuEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_CPU_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.ExecutionSample#enabled")),
"ExecutionSample must be disabled when CPU profiling is disabled");
}
}

@Test
public void testExceptionGateDisablesExceptionEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_EXCEPTION_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("datadog.ExceptionSample#enabled")),
"ExceptionSample must be disabled when exception profiling is disabled");
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("datadog.ExceptionCount#enabled")),
"ExceptionCount must be disabled when exception profiling is disabled");
}
}

@Test
public void testIoGateDisablesIoEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_IO_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.FileRead#enabled")),
"FileRead must be disabled when I/O profiling is disabled");
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.SocketRead#enabled")),
"SocketRead must be disabled when I/O profiling is disabled");
}
}

@Test
public void testLockGateDisablesLockEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_LOCK_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.JavaMonitorEnter#enabled")),
"JavaMonitorEnter must be disabled when lock profiling is disabled");
}
}

@Test
public void testThreadGateDisablesThreadEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_THREAD_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.ThreadStart#enabled")),
"ThreadStart must be disabled when thread profiling is disabled");
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.ThreadEnd#enabled")),
"ThreadEnd must be disabled when thread profiling is disabled");
}
}

@Test
public void testWallGateDoesNotDisableTimelineEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_WALLTIME_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
// JavaMonitorWait and ThreadPark serve timeline purposes beyond wall-clock profiling and must
// NOT be disabled when only wall profiling is disabled. (ThreadSleep is disabled by the
// dd.jfp template by default and is therefore not checked here.)
assertTrue(
Boolean.parseBoolean(recording.getSettings().get("jdk.JavaMonitorWait#enabled")),
"JavaMonitorWait must remain enabled when only wall profiling is disabled");
assertTrue(
Boolean.parseBoolean(recording.getSettings().get("jdk.ThreadPark#enabled")),
"ThreadPark must remain enabled when only wall profiling is disabled");
}
}

@Test
public void testWallGateEnabledDoesNotDisableTimelineEvents() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_WALLTIME_ENABLED, "true");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);
OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
// When wall profiling is enabled, timeline blocking events must still be enabled.
assertTrue(
Boolean.parseBoolean(recording.getSettings().get("jdk.JavaMonitorWait#enabled")),
"JavaMonitorWait must remain enabled when wall profiling is enabled");
assertTrue(
Boolean.parseBoolean(recording.getSettings().get("jdk.ThreadPark#enabled")),
"ThreadPark must remain enabled when wall profiling is enabled");
}
}

private static Properties getConfigProperties() {
Properties props = new Properties();
// make sure the async profiler is not force-enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,56 @@ private String getProfilerConfig() {
.append(proxyPassword != null ? "[REDACTED]" : null)
.append(" (default: null)\n");

sb.append("\n=== Feature Gates ===\n");
appendConfig(
sb,
"CPU Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_CPU_ENABLED, ProfilingConfig.PROFILING_CPU_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_CPU_ENABLED_DEFAULT);
appendConfig(
sb,
"Wall Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_WALLTIME_ENABLED,
ProfilingConfig.PROFILING_WALLTIME_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_WALLTIME_ENABLED_DEFAULT);
appendConfig(
sb,
"Exception Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_EXCEPTION_ENABLED,
ProfilingConfig.PROFILING_EXCEPTION_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_EXCEPTION_ENABLED_DEFAULT);
appendConfig(
sb,
"I/O Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_IO_ENABLED, ProfilingConfig.PROFILING_IO_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_IO_ENABLED_DEFAULT);
appendConfig(
sb,
"Lock Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_LOCK_ENABLED, ProfilingConfig.PROFILING_LOCK_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_LOCK_ENABLED_DEFAULT);
appendConfig(
sb,
"Thread Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_THREAD_ENABLED,
ProfilingConfig.PROFILING_THREAD_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_THREAD_ENABLED_DEFAULT);
appendConfig(
sb,
"Direct Memory Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_DIRECT_MEMORY_ENABLED,
configProvider.getBoolean(
ProfilingConfig.PROFILING_DIRECT_ALLOCATION_ENABLED,
ProfilingConfig.PROFILING_DIRECT_MEMORY_ENABLED_DEFAULT)),
ProfilingConfig.PROFILING_DIRECT_MEMORY_ENABLED_DEFAULT);

sb.append("\n=== Allocation Profiling ===\n");
boolean allocDefault = ProfilingSupport.isObjectAllocationSampleAvailable();
boolean allocEnabled =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ public String toString() {
protected final String uploadCompression;
protected final boolean allocationProfilingEnabled;
protected final boolean heapProfilingEnabled;
protected final boolean cpuProfilingEnabled;
protected final boolean wallProfilingEnabled;
protected final boolean exceptionProfilingEnabled;
protected final boolean ioProfilingEnabled;
protected final boolean lockProfilingEnabled;
protected final boolean threadProfilingEnabled;
protected final boolean directMemoryProfilingEnabled;
protected final boolean startForceFirst;
protected final String templateOverride;
protected final int exceptionSampleLimit;
Expand Down Expand Up @@ -141,6 +148,33 @@ protected ProfilerSettingsSupport(
heapProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_HEAP_ENABLED, ProfilingSupport.isLiveHeapProfilingSafe());
cpuProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_CPU_ENABLED, ProfilingConfig.PROFILING_CPU_ENABLED_DEFAULT);
wallProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_WALLTIME_ENABLED,
ProfilingConfig.PROFILING_WALLTIME_ENABLED_DEFAULT);
exceptionProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_EXCEPTION_ENABLED,
ProfilingConfig.PROFILING_EXCEPTION_ENABLED_DEFAULT);
ioProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_IO_ENABLED, ProfilingConfig.PROFILING_IO_ENABLED_DEFAULT);
lockProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_LOCK_ENABLED, ProfilingConfig.PROFILING_LOCK_ENABLED_DEFAULT);
threadProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_THREAD_ENABLED,
ProfilingConfig.PROFILING_THREAD_ENABLED_DEFAULT);
directMemoryProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_DIRECT_MEMORY_ENABLED,
configProvider.getBoolean(
ProfilingConfig.PROFILING_DIRECT_ALLOCATION_ENABLED,
ProfilingConfig.PROFILING_DIRECT_MEMORY_ENABLED_DEFAULT));
startForceFirst =
configProvider.getBoolean(
ProfilingConfig.PROFILING_START_FORCE_FIRST,
Expand Down Expand Up @@ -293,6 +327,13 @@ public String toString() {
+ ", uploadCompression='" + uploadCompression + '\''
+ ", allocationProfilingEnabled=" + allocationProfilingEnabled
+ ", heapProfilingEnabled=" + heapProfilingEnabled
+ ", cpuProfilingEnabled=" + cpuProfilingEnabled
+ ", wallProfilingEnabled=" + wallProfilingEnabled
+ ", exceptionProfilingEnabled=" + exceptionProfilingEnabled
+ ", ioProfilingEnabled=" + ioProfilingEnabled
+ ", lockProfilingEnabled=" + lockProfilingEnabled
+ ", threadProfilingEnabled=" + threadProfilingEnabled
+ ", directMemoryProfilingEnabled=" + directMemoryProfilingEnabled
+ ", startForceFirst=" + startForceFirst
+ ", templateOverride='" + templateOverride + '\''
+ ", exceptionSampleLimit=" + exceptionSampleLimit
Expand Down
Loading
Loading