diff --git a/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp b/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp index 024605c4ab1..f6a683361a9 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp +++ b/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp @@ -47,6 +47,12 @@ jdk.ExecutionSample#enabled=true # Note: we use 9 ms sampling rate in a hope to avoid 'lockstep' sampling. # Ideally JFR should provide random jitter in sampling to ensure this doesn't happen. jdk.ExecutionSample#period=9 ms +# In JDK 25+ we want to use CPUTimeSample instead +# Disabling the ExecutionSample will be done in OpenJDKController impl +jdk.CPUTimeSample#enabled=true +jdk.CPUTimeSample#throttle=9ms +jdk.CPUTimeSamplesLost#enabled=true +# --- jdk.NativeMethodSample#enabled=false jdk.SafepointBegin#enabled=true jdk.SafepointBegin#threshold=0 ms diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java index 69c31c6847a..b8e775d4fb1 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java @@ -34,6 +34,7 @@ import com.datadog.profiling.controller.jfr.JfpUtils; import com.datadog.profiling.controller.openjdk.events.AvailableProcessorCoresEvent; import datadog.environment.JavaVirtualMachine; +import datadog.environment.OperatingSystem; import datadog.trace.api.Config; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.bootstrap.config.provider.ConfigProvider; @@ -62,6 +63,8 @@ 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() + ")"; + private static final String CPUTIME_SAMPLE_JDK25 = "Switching to CPUTimeSample on JDK 25+"; + static final Duration RECORDING_MAX_AGE = Duration.ofMinutes(5); private final ConfigProvider configProvider; @@ -164,6 +167,13 @@ public OpenJdkController(final ConfigProvider configProvider) throw new ConfigurationException(e); } + // switch to CPUTimeSample event on JDK 25 and Linux + if (JavaVirtualMachine.isJavaVersionAtLeast(25) && OperatingSystem.isLinux()) { + disableEvent(recordingSettings, "jdk.ExecutionSample", CPUTIME_SAMPLE_JDK25); + enableEvent(recordingSettings, "jdk.CPUTimeSample", CPUTIME_SAMPLE_JDK25); + enableEvent(recordingSettings, "jdk.CPUTimeSamplesLost", CPUTIME_SAMPLE_JDK25); + } + // Toggle settings from override args String disabledEventsArgs = configProvider.getString(ProfilingConfig.PROFILING_DISABLED_EVENTS); diff --git a/dd-smoke-tests/profiling-integration-tests/src/test/java/datadog/smoketest/JFRBasedProfilingIntegrationTest.java b/dd-smoke-tests/profiling-integration-tests/src/test/java/datadog/smoketest/JFRBasedProfilingIntegrationTest.java index 9e6ae68a8d6..1ce46c94947 100644 --- a/dd-smoke-tests/profiling-integration-tests/src/test/java/datadog/smoketest/JFRBasedProfilingIntegrationTest.java +++ b/dd-smoke-tests/profiling-integration-tests/src/test/java/datadog/smoketest/JFRBasedProfilingIntegrationTest.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Multimap; import datadog.environment.JavaVirtualMachine; +import datadog.environment.OperatingSystem; +import datadog.environment.SystemProperties; import datadog.trace.api.Pair; import datadog.trace.api.config.ProfilingConfig; import delight.fileupload.FileUpload; @@ -49,8 +51,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openjdk.jmc.common.IMCStackTrace; import org.openjdk.jmc.common.item.Aggregators; import org.openjdk.jmc.common.item.IAttribute; @@ -68,12 +71,8 @@ import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import spock.util.environment.OperatingSystem; @DisabledIfSystemProperty(named = "java.vm.name", matches = ".*J9.*") -@DisabledIf( - value = "isJavaVersionAtLeast24", - disabledReason = "Failing on Java 24. Skip until we have a fix.") class JFRBasedProfilingIntegrationTest { private static final Logger log = LoggerFactory.getLogger(JFRBasedProfilingIntegrationTest.class); private static final Duration ONE_NANO = Duration.ofNanos(1); @@ -155,47 +154,57 @@ void teardown() throws Exception { } } - @Test + @ParameterizedTest + @ValueSource(strings = {"jfr", "ddprof"}) @DisplayName("Test continuous recording - no jmx delay, default compression") - public void testContinuousRecording_no_jmx_delay(final TestInfo testInfo) throws Exception { + public void testContinuousRecording_no_jmx_delay(String profiler, final TestInfo testInfo) + throws Exception { + Assumptions.assumeTrue("jfr".equals(profiler) || OperatingSystem.isLinux()); testWithRetry( () -> testContinuousRecording( - 0, ENDPOINT_COLLECTION_ENABLED, OperatingSystem.getCurrent().isLinux(), false), + 0, ENDPOINT_COLLECTION_ENABLED, "ddprof".equals(profiler), false), testInfo, 5); } - @Test + @ParameterizedTest + @ValueSource(strings = {"jfr", "ddprof"}) @DisplayName("Test continuous recording - no jmx delay, zstd compression") - public void testContinuousRecording_no_jmx_delay_jmethodid_cache(final TestInfo testInfo) + public void testContinuousRecording_no_jmx_delay_zstd(String profiler, final TestInfo testInfo) throws Exception { + Assumptions.assumeTrue("jfr".equals(profiler) || OperatingSystem.isLinux()); testWithRetry( () -> testContinuousRecording( - 0, ENDPOINT_COLLECTION_ENABLED, OperatingSystem.getCurrent().isLinux(), true), + 0, ENDPOINT_COLLECTION_ENABLED, "ddprof".equals(profiler), true), testInfo, 5); } - @Test + @ParameterizedTest + @ValueSource(strings = {"jfr", "ddprof"}) @DisplayName("Test continuous recording - 1 sec jmx delay, default compression") - public void testContinuousRecording(final TestInfo testInfo) throws Exception { + public void testContinuousRecording(String profiler, final TestInfo testInfo) throws Exception { + Assumptions.assumeTrue("jfr".equals(profiler) || OperatingSystem.isLinux()); testWithRetry( () -> testContinuousRecording( - 1, ENDPOINT_COLLECTION_ENABLED, OperatingSystem.getCurrent().isLinux(), false), + 1, ENDPOINT_COLLECTION_ENABLED, "ddprof".equals(profiler), false), testInfo, 5); } - @Test + @ParameterizedTest + @ValueSource(strings = {"jfr", "ddprof"}) @DisplayName("Test continuous recording - 1 sec jmx delay, zstd compression") - public void testContinuousRecording_zstd(final TestInfo testInfo) throws Exception { + public void testContinuousRecording_zstd(String profiler, final TestInfo testInfo) + throws Exception { + Assumptions.assumeTrue("jfr".equals(profiler) || OperatingSystem.isLinux()); testWithRetry( () -> testContinuousRecording( - 1, ENDPOINT_COLLECTION_ENABLED, OperatingSystem.getCurrent().isLinux(), true), + 1, ENDPOINT_COLLECTION_ENABLED, "ddprof".equals(profiler), true), testInfo, 5); } @@ -368,6 +377,15 @@ private static void verifyJdkEventsDisabled(IItemCollection events) { assertFalse(events.apply(ItemFilters.type("jdk.ThreadPark")).hasItems()); } + private static void verifyJdkEvents(IItemCollection events) { + String cpuSampleType = "jdk.ExecutionSample"; + if (JavaVirtualMachine.isJavaVersionAtLeast(25) && OperatingSystem.isLinux()) { + // for Java 25+ we are defaulting to 'jdk.CPUTimeSample' on Linux + cpuSampleType = "jdk.CPUTimeSample"; + } + assertTrue(events.apply(ItemFilters.type(cpuSampleType)).hasItems()); + } + private static void verifyDatadogEventsNotCorrupt(IItemCollection events) { // if we emit any of these events during the test they mustn't have corrupted context for (String eventName : @@ -621,6 +639,7 @@ private void assertRecordingEvents( // TODO ddprof (async) profiler seems to be having some issues with stack depth limit and // native frames } else { + verifyJdkEvents(events); // make sure the stack depth limit is respected for (IItemIterable lane : events.apply(ItemFilters.type(JdkTypeIDs.EXECUTION_SAMPLE))) { IMemberAccessor stackTraceAccessor = @@ -838,8 +857,7 @@ private static ProcessBuilder createProcessBuilder( } private static String javaPath() { - final String separator = System.getProperty("file.separator"); - return System.getProperty("java.home") + separator + "bin" + separator + "java"; + return Paths.get(SystemProperties.getOrDefault("java.home", ""), "bin", "java").toString(); } private static String buildDirectory() {