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 @@ -304,8 +304,14 @@ String cmdStartProfiling(Path file) throws IllegalStateException {
cmd.append('~');
}
cmd.append(getWallInterval(configProvider)).append('m');
// ddprof quirk: if filter parameter is omitted, it defaults to "0" (enabled),
// not empty string (disabled). When enabled without tracing, no threads are added
// to the filter, resulting in zero samples. We must explicitly pass filter= (empty)
// to disable filtering and sample all threads.
if (getWallContextFilter(configProvider)) {
cmd.append(",filter=0");
} else {
cmd.append(",filter=");
}
if (useJvmtiWallclockSampler(configProvider)) {
cmd.append(",wallsampler=jvmti");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,25 @@ public static boolean getWallCollapsing(ConfigProvider configProvider) {
PROFILING_DATADOG_PROFILER_WALL_COLLAPSING_DEFAULT);
}

/**
* Checks whether the wall-clock context filter should be used.<br>
* The context filter will make wall-clock profiler to pick candidate threads only from threads
* with attached tracing context.<br>
* If context filter is not used (this method returns {@literal false}) all threads will be
* considered for wallclock sampling.
*
* @param configProvider the associated config provider
* @return {@literal true} if the wallclock sampler should use context filtering, {@literal false}
* otherwise
*/
public static boolean getWallContextFilter(ConfigProvider configProvider) {
// Context filtering requires tracing to be enabled - without tracing,
// there are no span contexts to filter on, so threads would never be added
// to the filter, resulting in no walltime samples.
boolean isTracingEnabled = configProvider.getBoolean(TRACE_ENABLED, true);
if (!isTracingEnabled) {
return false;
}
return getBoolean(
configProvider,
PROFILING_DATADOG_PROFILER_WALL_CONTEXT_FILTER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import datadog.environment.OperatingSystem;
import datadog.libs.ddprof.DdprofLibraryLoader;
import datadog.trace.api.config.ProfilingConfig;
import datadog.trace.api.config.TraceInstrumentationConfig;
import datadog.trace.api.profiling.ProfilingScope;
import datadog.trace.api.profiling.RecordingData;
import datadog.trace.bootstrap.config.provider.ConfigProvider;
Expand Down Expand Up @@ -106,6 +107,63 @@ private static Stream<Arguments> profilingModes() {
Arguments.of((x & 0x1000) != 0, (x & 0x100) != 0, (x & 0x10) != 0, (x & 0x1) != 0));
}

@ParameterizedTest
@MethodSource("wallContextFilterModes")
void testWallContextFilter(boolean tracingEnabled, boolean contextFilterEnabled)
throws Exception {
// Skip test if profiler native library is not available (e.g., on macOS)
try {
Throwable reason = DdprofLibraryLoader.jvmAccess().getReasonNotLoaded();
if (reason != null) {
Assumptions.assumeTrue(false, "Profiler not available: " + reason.getMessage());
}
} catch (Throwable e) {
Assumptions.assumeTrue(false, "Profiler not available: " + e.getMessage());
}

Properties props = new Properties();
props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_ENABLED, "true");
props.put(TraceInstrumentationConfig.TRACE_ENABLED, Boolean.toString(tracingEnabled));
props.put(
ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_CONTEXT_FILTER,
Boolean.toString(contextFilterEnabled));

DatadogProfiler profiler =
DatadogProfiler.newInstance(ConfigProvider.withPropertiesOverride(props));

Path targetFile = Paths.get("/tmp/target.jfr");
String cmd = profiler.cmdStartProfiling(targetFile);

assertTrue(cmd.contains("wall="), "Command should contain wall profiling: " + cmd);

if (tracingEnabled && contextFilterEnabled) {
assertTrue(
cmd.contains(",filter=0"),
"Command should contain ',filter=0' when tracing and context filter are enabled: " + cmd);
} else {
assertTrue(
cmd.contains(",filter="),
"Command should contain ',filter=' when tracing is disabled or context filter is disabled: "
+ cmd);
if (cmd.contains(",filter=0")) {
throw new AssertionError(
"Command should not contain ',filter=0' when tracing is disabled or context filter is disabled: "
+ cmd);
}
}
}

private static Stream<Arguments> wallContextFilterModes() {
return Stream.of(
Arguments.of(true, true), // tracing enabled, context filter enabled -> filter=0
Arguments.of(true, false), // tracing enabled, context filter disabled -> filter=
Arguments.of(
false, true), // tracing disabled, context filter enabled -> filter= (tracing disabled
// overrides)
Arguments.of(false, false) // tracing disabled, context filter disabled -> filter=
);
}

@Test
public void testContextRegistration() {
// warning - the profiler is a process wide singleton and can't be reinitialised
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,80 @@ void testBogusApiKey(final TestInfo testInfo) throws Exception {
3);
}

@Test
@DisplayName("Test wallclock profiling without tracing")
public void testWallclockProfilingWithoutTracing(final TestInfo testInfo) throws Exception {
Assumptions.assumeTrue(OperatingSystem.isLinux());
testWithRetry(
() -> {
try {
targetProcess =
createProcessBuilder(
profilingServer.getPort(),
tracingServer.getPort(),
VALID_API_KEY,
0,
PROFILING_START_DELAY_SECONDS,
PROFILING_UPLOAD_PERIOD_SECONDS,
false,
true,
"on",
0,
logFilePath,
false)
.start();

Assumptions.assumeFalse(JavaVirtualMachine.isJ9());

final RecordedRequest request = retrieveRequest();
assertNotNull(request);

final List<FileItem> items =
FileUpload.parse(
request.getBody().readByteArray(), request.getHeader("Content-Type"));

FileItem rawJfr = items.get(1);
assertEquals("main.jfr", rawJfr.getName());

assertFalse(logHasErrors(logFilePath));
InputStream eventStream = new ByteArrayInputStream(rawJfr.get());
eventStream = decompressStream("on", eventStream);
IItemCollection events = JfrLoaderToolkit.loadEvents(eventStream);
assertTrue(events.hasItems());

IItemCollection wallclockSamples =
events.apply(ItemFilters.type("datadog.MethodSample"));
assertTrue(
wallclockSamples.hasItems(), "Expected wallclock samples when tracing is disabled");

// Verify span context is not present
for (IItemIterable event : wallclockSamples) {
IMemberAccessor<IQuantity, IItem> rootSpanIdAccessor =
LOCAL_ROOT_SPAN_ID.getAccessor(event.getType());
IMemberAccessor<IQuantity, IItem> spanIdAccessor =
SPAN_ID.getAccessor(event.getType());
for (IItem sample : event) {
assertEquals(
0,
rootSpanIdAccessor.getMember(sample).longValue(),
"rootSpanId should be 0 when tracing is disabled");
assertEquals(
0,
spanIdAccessor.getMember(sample).longValue(),
"spanId should be 0 when tracing is disabled");
}
}
} finally {
if (targetProcess != null) {
targetProcess.destroyForcibly();
}
targetProcess = null;
}
},
testInfo,
3);
}

@Test
@DisplayName("Test shutdown")
@Disabled("https://github.com/DataDog/dd-trace-java/pull/5213")
Expand Down Expand Up @@ -771,7 +845,8 @@ private ProcessBuilder createProcessBuilder(
asyncProfilerEnabled,
withCompression,
exitDelay,
logFilePath);
logFilePath,
true);
}

private static ProcessBuilder createProcessBuilder(
Expand All @@ -785,50 +860,57 @@ private static ProcessBuilder createProcessBuilder(
final boolean asyncProfilerEnabled,
final String withCompression,
final int exitDelay,
final Path logFilePath) {
final Path logFilePath,
final boolean tracingEnabled) {
final String templateOverride =
JFRBasedProfilingIntegrationTest.class
.getClassLoader()
.getResource("overrides.jfp")
.getFile();

final List<String> command =
Arrays.asList(
javaPath(),
"-Xmx" + System.getProperty("datadog.forkedMaxHeapSize", "1024M"),
"-Xms" + System.getProperty("datadog.forkedMinHeapSize", "64M"),
"-javaagent:" + agentShadowJar(),
"-XX:ErrorFile=/tmp/hs_err_pid%p.log",
"-Ddd.trace.agent.port=" + tracerPort,
"-Ddd.service.name=smoke-test-java-app",
"-Ddd.env=smoketest",
"-Ddd.version=99",
"-Ddd.profiling.enabled=true",
"-Ddd.profiling.stackdepth=" + STACK_DEPTH_LIMIT,
"-Ddd.profiling.ddprof.enabled=" + asyncProfilerEnabled,
"-Ddd.profiling.ddprof.alloc.enabled=" + asyncProfilerEnabled,
"-Ddd.profiling.agentless=" + (apiKey != null),
"-Ddd.profiling.start-delay=" + profilingStartDelaySecs,
"-Ddd.profiling.upload.period=" + profilingUploadPeriodSecs,
"-Ddd.profiling.url=http://localhost:" + profilerPort,
"-Ddd.profiling.hotspots.enabled=true",
"-Ddd.profiling.endpoint.collection.enabled=" + endpointCollectionEnabled,
"-Ddd.profiling.upload.timeout=" + PROFILING_UPLOAD_TIMEOUT_SECONDS,
"-Ddd.profiling.debug.dump_path=/tmp/dd-profiler",
"-Ddd.profiling.queueing.time.enabled=true",
"-Ddd.profiling.queueing.time.threshold.millis=0",
"-Ddd.profiling.debug.upload.compression=" + withCompression,
"-Ddatadog.slf4j.simpleLogger.defaultLogLevel=debug",
"-Ddd.profiling.context.attributes=foo,bar",
"-Dorg.slf4j.simpleLogger.defaultLogLevel=debug",
"-XX:+IgnoreUnrecognizedVMOptions",
"-XX:+UnlockCommercialFeatures",
"-XX:+FlightRecorder",
"-Ddd." + ProfilingConfig.PROFILING_TEMPLATE_OVERRIDE_FILE + "=" + templateOverride,
"-Ddd.jmxfetch.start-delay=" + jmxFetchDelaySecs,
"-jar",
profilingShadowJar(),
Integer.toString(exitDelay));
final List<String> command = new java.util.ArrayList<>();
command.add(javaPath());
command.add("-Xmx" + System.getProperty("datadog.forkedMaxHeapSize", "1024M"));
command.add("-Xms" + System.getProperty("datadog.forkedMinHeapSize", "64M"));
command.add("-javaagent:" + agentShadowJar());
command.add("-XX:ErrorFile=/tmp/hs_err_pid%p.log");
command.add("-Ddd.trace.agent.port=" + tracerPort);
command.add("-Ddd.service.name=smoke-test-java-app");
command.add("-Ddd.env=smoketest");
command.add("-Ddd.version=99");
command.add("-Ddd.trace.enabled=" + tracingEnabled);
command.add("-Ddd.profiling.enabled=true");
command.add("-Ddd.profiling.stackdepth=" + STACK_DEPTH_LIMIT);
command.add("-Ddd.profiling.ddprof.enabled=" + asyncProfilerEnabled);
command.add("-Ddd.profiling.ddprof.alloc.enabled=" + asyncProfilerEnabled);
if (!tracingEnabled && asyncProfilerEnabled) {
command.add("-Ddd.profiling.ddprof.wall.enabled=true");
}
command.add("-Ddd.profiling.agentless=" + (apiKey != null));
command.add("-Ddd.profiling.start-delay=" + profilingStartDelaySecs);
command.add("-Ddd.profiling.upload.period=" + profilingUploadPeriodSecs);
command.add("-Ddd.profiling.url=http://localhost:" + profilerPort);
command.add("-Ddd.profiling.hotspots.enabled=true");
command.add("-Ddd.profiling.endpoint.collection.enabled=" + endpointCollectionEnabled);
command.add("-Ddd.profiling.upload.timeout=" + PROFILING_UPLOAD_TIMEOUT_SECONDS);
command.add("-Ddd.profiling.debug.dump_path=/tmp/dd-profiler");
if (tracingEnabled) {
command.add("-Ddd.profiling.queueing.time.enabled=true");
command.add("-Ddd.profiling.queueing.time.threshold.millis=0");
command.add("-Ddd.profiling.context.attributes=foo,bar");
}
command.add("-Ddd.profiling.debug.upload.compression=" + withCompression);
command.add("-Ddatadog.slf4j.simpleLogger.defaultLogLevel=debug");
command.add("-Dorg.slf4j.simpleLogger.defaultLogLevel=debug");
command.add("-XX:+IgnoreUnrecognizedVMOptions");
command.add("-XX:+UnlockCommercialFeatures");
command.add("-XX:+FlightRecorder");
command.add(
"-Ddd." + ProfilingConfig.PROFILING_TEMPLATE_OVERRIDE_FILE + "=" + templateOverride);
command.add("-Ddd.jmxfetch.start-delay=" + jmxFetchDelaySecs);
command.add("-jar");
command.add(profilingShadowJar());
command.add(Integer.toString(exitDelay));
final ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.directory(new File(buildDirectory()));

Expand Down