Skip to content
Merged
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 @@ -192,10 +192,36 @@ public OpenJdkController(final ConfigProvider configProvider)

// Toggle settings from config

if (configProvider.getBoolean(
ProfilingConfig.PROFILING_HEAP_ENABLED, ProfilingConfig.PROFILING_HEAP_ENABLED_DEFAULT)) {
log.debug("Enabling OldObjectSample JFR event with the config.");
recordingSettings.put("jdk.OldObjectSample#enabled", "true");
// Unified live heap (profiling.heap.enabled): when explicitly disabled, turn off
// OldObjectSample. When enabled (or default), if ddprof is likely handling live heap,
// proactively disable OldObjectSample to avoid double collection.
// disableOverriddenEvents() at recording start is the definitive safety net,
// but we disable here too so the settings map is consistent from the start.
if (!configProvider.getBoolean(
ProfilingConfig.PROFILING_HEAP_ENABLED, isLiveHeapProfilingSafe())) {
disableEvent(recordingSettings, "jdk.OldObjectSample", "live heap profiling is disabled");
} else {
// ddprof live heap requires Java 11+ (JVMTI Allocation Sampler)
// isJmethodIDSafe() matches ddprof's own default for liveheap: it only enables
// MEMLEAK mode by default on versions where jmethodID is safe.
boolean ddprofLikelyActive =
isJavaVersionAtLeast(11)
&& configProvider.getBoolean(
ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, isJmethodIDSafe())
&& configProvider.getBoolean(
ProfilingConfig.PROFILING_DATADOG_PROFILER_ENABLED, true);
if (ddprofLikelyActive) {
disableEvent(
recordingSettings,
"jdk.OldObjectSample",
"ddprof live heap profiling is expected to handle live heap data");
} else if (isOldObjectSampleAvailable()) {
enableEvent(recordingSettings, "jdk.OldObjectSample", "heap profiling is enabled");
} else if (configProvider.getBoolean(ProfilingConfig.PROFILING_HEAP_ENABLED, false)) {
log.warn(
"Live heap profiling was explicitly requested but is not supported on this JVM version;"
+ " no heap profiling data will be collected.");
}
}

if (configProvider.getBoolean(
Expand Down Expand Up @@ -240,9 +266,8 @@ ProfilingConfig.PROFILING_ALLOCATION_ENABLED, isObjectAllocationSampleAvailable(

// Warn users for expensive events

if (!isOldObjectSampleAvailable()
&& isEventEnabled(recordingSettings, "jdk.OldObjectSample#enabled")) {
log.warn("Inexpensive heap profiling is not supported for this JDK but is enabled.");
if (!isOldObjectSampleAvailable() && isEventEnabled(recordingSettings, "jdk.OldObjectSample")) {
log.warn("JFR based live heap profiling is not supported for this JDK but is enabled.");
}

if (isEventEnabled(recordingSettings, "jdk.ObjectAllocationInNewTLAB")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

import com.datadog.profiling.controller.ControllerContext;
import com.datadog.profiling.controller.jfr.JfpUtilsTest;
import com.datadog.profiling.utils.ProfilingMode;
import datadog.environment.JavaVirtualMachine;
import datadog.trace.api.profiling.RecordingData;
import datadog.trace.bootstrap.config.provider.ConfigProvider;
import java.util.EnumSet;
import java.util.Properties;
import jdk.jfr.Recording;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -94,6 +96,8 @@ public void testHeapProfilerIsStillOverriddenOnUnsupportedVersion() throws Excep
public void testHeapProfilerIsStillOverriddenThroughConfig() throws Exception {
Properties props = getConfigProperties();
props.put(PROFILING_HEAP_ENABLED, "true");
// Disable ddprof so OldObjectSample is not proactively disabled
props.put(PROFILING_DATADOG_PROFILER_ENABLED, "false");

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);

Expand All @@ -102,10 +106,11 @@ public void testHeapProfilerIsStillOverriddenThroughConfig() throws Exception {
((OpenJdkRecordingData)
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop())
.getRecording()) {
if (!isOldObjectSampleAvailable()) {
assertEquals(
true, Boolean.parseBoolean(recording.getSettings().get("jdk.OldObjectSample#enabled")));
}
// On JVMs where OldObjectSample is not available (e.g. Java 8), explicitly enabling heap
// profiling has no effect — the event cannot be safely enabled.
assertEquals(
isOldObjectSampleAvailable(),
Boolean.parseBoolean(recording.getSettings().get("jdk.OldObjectSample#enabled")));
}
}

Expand Down Expand Up @@ -234,6 +239,43 @@ public void testNativeProfilerIsStillOverriddenOnUnsupportedVersion() throws Exc
}
}

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

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);

ControllerContext context = new ControllerContext();
context.setDatadogProfilerEnabled(true);
context.setDatadogProfilingModes(EnumSet.of(ProfilingMode.MEMLEAK));

OpenJdkController controller = new OpenJdkController(configProvider);
try (final Recording recording =
((OpenJdkRecordingData) controller.createRecording(TEST_NAME, context.snapshot()).stop())
.getRecording()) {
assertFalse(Boolean.parseBoolean(recording.getSettings().get("jdk.OldObjectSample#enabled")));
}
}

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

ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props);

OpenJdkController controller = new OpenJdkController(configProvider);
RecordingData data =
controller.createRecording(TEST_NAME, new ControllerContext().snapshot()).stop();
assertTrue(data instanceof OpenJdkRecordingData);
try (final Recording recording = ((OpenJdkRecordingData) data).getRecording()) {
assertFalse(
Boolean.parseBoolean(recording.getSettings().get("jdk.OldObjectSample#enabled")),
"OldObjectSample should be disabled when unified live heap flag is false");
}
}

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 @@ -157,12 +157,19 @@ private String getProfilerConfig() {
ProfilingConfig.PROFILING_DIRECT_ALLOCATION_SAMPLE_LIMIT_DEFAULT);

sb.append("\n=== Heap Profiling ===\n");
boolean heapDefault = ProfilingSupport.isLiveHeapProfilingSafe();
appendConfig(
sb,
"Heap Profiling Enabled",
configProvider.getBoolean(
ProfilingConfig.PROFILING_HEAP_ENABLED, ProfilingConfig.PROFILING_HEAP_ENABLED_DEFAULT),
ProfilingConfig.PROFILING_HEAP_ENABLED_DEFAULT);
configProvider.getBoolean(ProfilingConfig.PROFILING_HEAP_ENABLED, heapDefault),
heapDefault);
appendConfig(
sb,
"DDProf Live Heap Enabled",
configProvider.getString(ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED),
null);
appendConfig(
sb, "JFR OldObjectSample Available", ProfilingSupport.isOldObjectSampleAvailable(), false);
appendConfig(
sb,
"Heap Histogram Enabled",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ protected ProfilerSettingsSupport(
ProfilingSupport.isObjectAllocationSampleAvailable());
heapProfilingEnabled =
configProvider.getBoolean(
ProfilingConfig.PROFILING_HEAP_ENABLED, ProfilingConfig.PROFILING_HEAP_ENABLED_DEFAULT);
ProfilingConfig.PROFILING_HEAP_ENABLED, ProfilingSupport.isLiveHeapProfilingSafe());
startForceFirst =
configProvider.getBoolean(
ProfilingConfig.PROFILING_START_FORCE_FIRST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,41 @@
import static datadog.environment.JavaVirtualMachine.isJavaVersionAtLeast;
import static datadog.environment.JavaVirtualMachine.isOracleJDK8;

import datadog.environment.JavaVirtualMachine;

public class ProfilingSupport {

/**
* Checks whether jmethodID handling is safe on the current JVM version. Unsafe versions can cause
* crashes due to <a href="https://bugs.openjdk.org/browse/JDK-8313816">JDK-8313816</a>.
*/
public static boolean isJmethodIDSafe() {
if (isJavaVersionAtLeast(22)) {
return true;
}
switch (JavaVirtualMachine.getLangVersion()) {
case "8":
return true;
case "11":
return isJavaVersionAtLeast(11, 0, 23);
case "17":
return isJavaVersionAtLeast(17, 0, 11);
case "21":
return isJavaVersionAtLeast(21, 0, 3);
default:
return false;
}
}

/**
* Checks whether any live heap profiling mechanism is safe to enable on the current platform.
* Returns true if at least one of ddprof native (jmethodID safe) or JFR OldObjectSample is
* available.
*/
public static boolean isLiveHeapProfilingSafe() {
return isJmethodIDSafe() || isOldObjectSampleAvailable();
}

public static boolean isOldObjectSampleAvailable() {
if (isOracleJDK8()) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.datadog.profiling.ddprof;

import static com.datadog.profiling.controller.ProfilingSupport.isOldObjectSampleAvailable;
import static datadog.environment.JavaVirtualMachine.isJ9;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ALLOCATION_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_CONTEXT_ATTRIBUTES;
Expand All @@ -20,7 +21,6 @@
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_CAPACITY;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_CAPACITY_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_INTERVAL;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_SAMPLE_PERCENT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_SAMPLE_PERCENT_DEFAULT;
Expand All @@ -43,6 +43,7 @@
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_INTERVAL;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_INTERVAL_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_TRACK_GENERATIONS;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_TRACK_GENERATIONS_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_ENABLED;
Expand All @@ -52,6 +53,7 @@
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ULTRA_MINIMAL;
import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_ENABLED;

import com.datadog.profiling.controller.ProfilingSupport;
import datadog.environment.JavaVirtualMachine;
import datadog.trace.api.config.ProfilingConfig;
import datadog.trace.bootstrap.config.provider.ConfigProvider;
Expand Down Expand Up @@ -165,26 +167,12 @@ public static boolean getWallContextFilter(ConfigProvider configProvider) {
PROFILING_DATADOG_PROFILER_WALL_CONTEXT_FILTER_DEFAULT);
}

private static boolean isJmethodIDSafe() {
// see https://bugs.openjdk.org/browse/JDK-8313816
if (JavaVirtualMachine.isJavaVersionAtLeast(22)) {
// any version after 22 should be safe
return true;
}
switch (JavaVirtualMachine.getLangVersion()) {
case "8":
// Java 8 is not affected by the jmethodID issue
return true;
case "11":
return JavaVirtualMachine.isJavaVersionAtLeast(11, 0, 23);
case "17":
return JavaVirtualMachine.isJavaVersionAtLeast(17, 0, 11);
case "21":
return JavaVirtualMachine.isJavaVersionAtLeast(21, 0, 3);
default:
// any other non-LTS version should be considered unsafe
return false;
}
static boolean isJmethodIDSafe() {
return ProfilingSupport.isJmethodIDSafe();
}

static boolean isMemoryLeakProfilingSafe() {
return ProfilingSupport.isLiveHeapProfilingSafe();
}

public static boolean isAllocationProfilingEnabled(ConfigProvider configProvider) {
Expand Down Expand Up @@ -223,16 +211,29 @@ public static int getAllocationInterval() {
}

public static boolean isMemoryLeakProfilingEnabled(ConfigProvider configProvider) {
boolean isSafe = isJmethodIDSafe();
boolean unifiedEnabled =
configProvider.getBoolean(PROFILING_HEAP_ENABLED, isMemoryLeakProfilingSafe());
if (!unifiedEnabled) {
return false;
}
// JVMTI Allocation Sampler is required for ddprof live heap and is available since Java 11.
// isJmethodIDSafe() alone is not sufficient — Java 8 is jmethodID-safe but lacks the sampler.
boolean isSafe = JavaVirtualMachine.isJavaVersionAtLeast(11) && isJmethodIDSafe();
boolean enableDdprofMemleak =
getBoolean(
configProvider,
PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED,
PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED_DEFAULT,
isSafe,
PROFILING_DATADOG_PROFILER_MEMLEAK_ENABLED);
if (!isSafe && enableDdprofMemleak) {
log.warn(
"Memory leak profiling was enabled although it is not considered stable on this JVM version.");
"Live heap profiling (ddprof) was enabled although it is not considered stable"
+ " on this JVM version.");
}
if (!enableDdprofMemleak && !isOldObjectSampleAvailable()) {
log.warn(
"ddprof live heap profiling is disabled and JFR OldObjectSample is not available"
+ " on this JVM. Live heap profiling will be inactive.");
}
return enableDdprofMemleak;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.datadog.profiling.ddprof;

import static com.datadog.profiling.controller.ProfilingSupport.isOldObjectSampleAvailable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import datadog.environment.JavaVirtualMachine;
import datadog.trace.api.config.ProfilingConfig;
import datadog.trace.bootstrap.config.provider.ConfigProvider;
import java.util.Properties;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
Expand Down Expand Up @@ -126,4 +131,70 @@ private static Stream<Arguments> hotspotStackwalkerTestCases() {
Arguments.of("lbr", "lbr"),
Arguments.of("no", "no"));
}

@Test
void isMemoryLeakProfilingSafeConsistentWithComponentChecks() {
boolean expected = DatadogProfilerConfig.isJmethodIDSafe() || isOldObjectSampleAvailable();
assertEquals(expected, DatadogProfilerConfig.isMemoryLeakProfilingSafe());
}

@Test
void unifiedFlagEnabledDdprofKeyDefaultReflectsSafety() {
Properties props = new Properties();
props.put(ProfilingConfig.PROFILING_HEAP_ENABLED, "true");
ConfigProvider config = ConfigProvider.withPropertiesOverride(props);
// ddprof live heap requires Java 11+ (JVMTI Allocation Sampler) AND jmethodID safety
boolean expectedDefault =
JavaVirtualMachine.isJavaVersionAtLeast(11) && DatadogProfilerConfig.isJmethodIDSafe();
assertEquals(expectedDefault, DatadogProfilerConfig.isMemoryLeakProfilingEnabled(config));
}

@Test
void unifiedFlagDisabledOverridesDdprof() {
Properties props = new Properties();
props.put(ProfilingConfig.PROFILING_HEAP_ENABLED, "false");
props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, "true");
ConfigProvider config = ConfigProvider.withPropertiesOverride(props);
assertFalse(DatadogProfilerConfig.isMemoryLeakProfilingEnabled(config));
}

@Test
void unifiedFlagEnabledDdprofKeyDisabledReturnsFalse() {
Properties props = new Properties();
props.put(ProfilingConfig.PROFILING_HEAP_ENABLED, "true");
props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, "false");
ConfigProvider config = ConfigProvider.withPropertiesOverride(props);
assertFalse(DatadogProfilerConfig.isMemoryLeakProfilingEnabled(config));
}

@Test
void unifiedFlagEnabledDdprofKeyEnabledReturnsTrue() {
Properties props = new Properties();
props.put(ProfilingConfig.PROFILING_HEAP_ENABLED, "true");
props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, "true");
ConfigProvider config = ConfigProvider.withPropertiesOverride(props);
assertTrue(DatadogProfilerConfig.isMemoryLeakProfilingEnabled(config));
}

@Test
void oldMemleakAliasStillWorks() {
Properties props = new Properties();
props.put(ProfilingConfig.PROFILING_HEAP_ENABLED, "true");
props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_MEMLEAK_ENABLED, "true");
ConfigProvider config = ConfigProvider.withPropertiesOverride(props);
assertTrue(DatadogProfilerConfig.isMemoryLeakProfilingEnabled(config));
}

@Test
void defaultBehaviorNoFlagsSetUsesAutoDetection() {
Properties props = new Properties();
ConfigProvider config = ConfigProvider.withPropertiesOverride(props);
// With no flags set:
// unified default = isMemoryLeakProfilingSafe()
// ddprof default = Java 11+ && isJmethodIDSafe()
// result = isMemoryLeakProfilingSafe() && (Java 11+ && isJmethodIDSafe())
boolean expectedDefault =
JavaVirtualMachine.isJavaVersionAtLeast(11) && DatadogProfilerConfig.isJmethodIDSafe();
assertEquals(expectedDefault, DatadogProfilerConfig.isMemoryLeakProfilingEnabled(config));
}
}
Loading
Loading