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 @@ -206,5 +206,17 @@
"fields": [
{"name": "consumerIndex", "allowUnsafeAccess": true}
]
},
{
"name": "com.datadoghq.profiler.BufferWriter8",
"methods": [
{"name": "<init>", "parameterTypes": []}
]
},
{
"name": "com.datadoghq.profiler.BufferWriter9",
"methods": [
{"name": "<init>", "parameterTypes": []}
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.sun.management.HotSpotDiagnosticMXBean;
import datadog.environment.OperatingSystem;
import datadog.libs.ddprof.DdprofLibraryLoader;
import datadog.trace.api.Platform;
import datadog.trace.util.TempLocationManager;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -76,7 +77,8 @@ public boolean setValue(String flagName, String value) {
public static boolean initialize(boolean forceJmx) {
try {
FlagAccess access = null;
if (forceJmx) {
// Native images don't support the native ddprof library, use JMX instead
if (forceJmx || Platform.isNativeImage()) {
access =
new JMXFlagAccess(ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[
+ "com.datadog.profiling.controller.openjdk.events.SmapEntryEvent:build_time,"
+ "com.datadog.profiling.controller.openjdk.events.SmapEntryFactory$SmapParseErrorEvent:build_time,"
+ "com.datadog.profiling.ddprof.JavaProfilerLoader:run_time,"
+ "com.datadoghq.profiler.ThreadContext:run_time,"
+ "com.datadoghq.profiler.BufferWriter:run_time,"
+ "com.datadoghq.profiler.BufferWriter8:run_time,"
+ "com.datadoghq.profiler.BufferWriter9:run_time,"
+ "datadog.environment.JavaVirtualMachine:rerun,"
+ "datadog.environment.OperatingSystem:rerun,"
+ "datadog.environment.OperatingSystem$Architecture:rerun,"
Expand Down
5 changes: 4 additions & 1 deletion dd-smoke-tests/quarkus-native/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ apply from: "$rootDir/gradle/java.gradle"

dependencies {
testImplementation project(':dd-smoke-tests')
testImplementation libs.bundles.jmc
}
// Check 'testJvm' gradle command parameter is GraalVM (e.g., -PtestJvm=graalvm21),
// if not nothing is done
Expand Down Expand Up @@ -40,7 +41,8 @@ if (testGraalvmVersion >= 17) {
"-PappBuildDir=$appBuildDir",
"-PapiJar=${project(':dd-trace-api').tasks.jar.archiveFile.get()}",
"-Dquarkus.native.additional-build-args=-J-javaagent:${project(':dd-java-agent').tasks.shadowJar.archiveFile.get()}," +
"-J-Ddatadog.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd'T'HH:mm:ss.SSS'Z [dd.trace]',-march=native"
"-J-Ddatadog.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd'T'HH:mm:ss.SSS'Z [dd.trace]'," +
"-J-Ddd.profiling.enabled=true,-march=native"
)
outputs.cacheIf { true }
outputs.dir(appBuildDir)
Expand Down Expand Up @@ -68,6 +70,7 @@ if (testGraalvmVersion >= 17) {

tasks.withType(Test).configureEach {
jvmArgs "-Ddatadog.smoketest.quarkus.native.executable=$appBuildDir/quarkus-native-smoketest--runner"
jvmArgs "-Ddd.profiling.enabled=true"
}
} else {
tasks.withType(Test).configureEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package datadog.smoketest

import okhttp3.Request
import org.openjdk.jmc.common.item.ItemFilters
import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit
import org.openjdk.jmc.flightrecorder.internal.InvalidJfrFileException
import spock.lang.Shared
import spock.lang.TempDir
import spock.util.concurrent.PollingConditions

import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.atomic.AtomicInteger

/**
* Smoke test for profiling in Quarkus native images.
*
* This test validates that profiling works correctly when the Datadog agent is attached
* during GraalVM native-image compilation. The build will fail if any profiler classes
* have incorrect native-image configuration (e.g., missing reflection metadata or
* improper class initialization timing).
*
* Test validates:
* 1. Native image builds successfully with profiling enabled
* 2. Application starts without agent initialization errors
* 3. Profiler produces JFR files at runtime
*/
class QuarkusNativeProfilingSmokeTest extends QuarkusSlf4jSmokeTest {

@Shared
@TempDir
Path testJfrDir

@Override
protected List<String> additionalArguments() {
return [
"-Ddd.profiling.upload.period=1",
"-Ddd.profiling.start-force-first=true",
"-Ddd.profiling.debug.dump_path=${testJfrDir}".toString(),
// TODO: Remove this arg after JFR initialization is fixed on GraalVM 25.
// https://datadoghq.atlassian.net/browse/PROF-12742
"-XX:StartFlightRecording=filename=${testJfrDir}/recording.jfr".toString(),
]
}

def "profiling works in native image"() {
setup:
String url = "http://localhost:${httpPort}/${endpointName}?id=1"
def conditions = new PollingConditions(initialDelay: 2, timeout: 10)

when:
// Make a request to generate some activity for the profiler
def response = client.newCall(new Request.Builder().url(url).get().build()).execute()

then:
response.code() == 200
response.body().string() == "Hello 1!"

// Wait for JFR files with execution samples to be generated
conditions.eventually {
assert countJfrsWithExecutionSamples() > 0
}
}

int countJfrsWithExecutionSamples() {
AtomicInteger jfrCount = new AtomicInteger(0)
Files.walkFileTree(testJfrDir, new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".jfr")) {
try {
def events = JfrLoaderToolkit.loadEvents(file.toFile())
if (events.apply(ItemFilters.type("jdk.ExecutionSample")).hasItems()) {
jfrCount.incrementAndGet()
return FileVisitResult.SKIP_SIBLINGS
}
} catch (InvalidJfrFileException ignored) {
// The recording captured at process exit might be incomplete
}
}
return FileVisitResult.CONTINUE
}
})
return jfrCount.get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ abstract class QuarkusNativeSmokeTest extends AbstractServerSmokeTest {
"-Ddd.app.customlogmanager=true",
"-Dquarkus.http.port=${httpPort}"
])
command.addAll(additionalArguments())
ProcessBuilder processBuilder = new ProcessBuilder(command)
processBuilder.directory(new File(buildDirectory))
}

protected List<String> additionalArguments() {
return Collections.emptyList()
}

@Override
File createTemporaryFile() {
return new File("${buildDirectory}/tmp/trace-structure-quarkus-native.out")
Expand Down