Skip to content
Open
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 @@ -264,6 +274,50 @@ 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", "CPU profiling is disabled");
disableEvent(recordingSettings, "jdk.NativeMethodSample", "CPU profiling is disabled");
disableEvent(recordingSettings, "jdk.CPUTimeSample", "CPU profiling is disabled");
disableEvent(recordingSettings, "jdk.CPUTimeSamplesLost", "CPU profiling is disabled");
}

// jdk.JavaMonitorWait, jdk.ThreadPark, and jdk.ThreadSleep are intentionally NOT gated by
// PROFILING_WALL_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", "exception profiling is disabled");
disableEvent(recordingSettings, "datadog.ExceptionCount", "exception profiling is disabled");
}

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

if (!configProvider.getBoolean(PROFILING_LOCK_ENABLED, PROFILING_LOCK_ENABLED_DEFAULT)) {
disableEvent(recordingSettings, "jdk.JavaMonitorEnter", "lock profiling is disabled");
// 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", "lock profiling is disabled");
disableEvent(recordingSettings, "jdk.BiasedLockSelfRevocation", "lock profiling is disabled");
disableEvent(
recordingSettings, "jdk.BiasedLockClassRevocation", "lock profiling is disabled");
}

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

// 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_WALL_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,123 @@ 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_WALL_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");
}
}

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,55 @@ 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_WALL_ENABLED, ProfilingConfig.PROFILING_WALL_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_WALL_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,32 @@ 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_WALL_ENABLED, ProfilingConfig.PROFILING_WALL_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 +326,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