From 44c0f626cbda10599ec4dad9bce514047e879db2 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Fri, 27 Feb 2026 16:13:39 +0100 Subject: [PATCH 01/15] wip --- .../tracing/trace-annotation/build.gradle | 28 ++++ .../java-lang/java-lang-22.0/build.gradle | 38 +++++ .../java-lang/java-lang-22.0/gradle.lockfile | 125 +++++++++++++++ .../java/lang/jdk22/FFMApiModule.java | 38 +++++ .../java/lang/jdk21/JavaAsyncChild.java | 46 ++++++ .../VirtualThreadApiInstrumentationTest.java | 151 ++++++++++++++++++ .../datadog/trace/api/ConfigDefaults.java | 1 + .../config/TraceInstrumentationConfig.java | 1 + .../datadog/trace/api/InstrumenterConfig.java | 10 ++ settings.gradle.kts | 1 + 10 files changed, 439 insertions(+) create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/gradle.lockfile create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java diff --git a/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle b/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle index 7066c6cc174..f4626d8dcb3 100644 --- a/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle +++ b/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle @@ -1,8 +1,36 @@ apply from: "$rootDir/gradle/java.gradle" +sourceSets { + register("main_java25") { + java { + srcDirs = [file('src/main/java25')] + } + } + named("main_java25") { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } + named("test") { + compileClasspath += sourceSets.main_java25.output + runtimeClasspath += sourceSets.main_java25.output + } +} + dependencies { implementation project(':dd-java-agent:agent-debugger:debugger-bootstrap') testImplementation group: 'com.newrelic.agent.java', name: 'newrelic-api', version: '6.+' } + +tasks.named("jar") { + from(sourceSets.main_java25.output) +} + +tasks.named("compileMain_java25Java") { + configureCompiler( + it, + 25, + JavaVersion.VERSION_1_8, + "Java 25 sourceset for FFM native tracing advice.") +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle new file mode 100644 index 00000000000..698964e4990 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'idea' +} + +muzzle { + pass { + coreJdk('22') + } +} + +apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/slf4j-simple.gradle" + +tracerJava { + addSourceSetFor(JavaVersion.VERSION_25) +} + +testJvmConstraints { + minJavaVersion = JavaVersion.VERSION_22 +} + +idea { + module { + jdkName = '25' + } +} + + +tasks.named("compileMain_java25Java", JavaCompile) { + configureCompiler(it, 25, JavaVersion.VERSION_1_8) +} + +tasks.named("compileTestGroovy", GroovyCompile) { + configureCompiler(it, 25) +} +dependencies { + implementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation') +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/gradle.lockfile b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/gradle.lockfile new file mode 100644 index 00000000000..8d5e0f2e333 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/gradle.lockfile @@ -0,0 +1,125 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +cafe.cryptography:curve25519-elisabeth:0.1.0=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +cafe.cryptography:ed25519-elisabeth:0.1.0=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.13=testCompileClasspath +ch.qos.logback:logback-core:1.2.13=testCompileClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okhttp3:okhttp:3.12.15=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okio:okio:1.17.6=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:java-dogstatsd-client:4.4.3=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.datadoghq:sketches-java:0.8.3=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.javaparser:javaparser-core:3.25.6=codenarc +com.github.jnr:jffi:1.3.14=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-a64asm:1.0.0=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-constants:0.10.4=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-enxio:0.32.19=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-ffi:2.2.18=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-posix:3.1.21=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-unixsocket:0.38.24=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-x86asm:1.0.2=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs:4.9.8=spotbugs +com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs +com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,testAnnotationProcessor,testCompileClasspath +com.google.auto.service:auto-service:1.1.1=annotationProcessor,testAnnotationProcessor +com.google.auto:auto-common:1.2.1=annotationProcessor,testAnnotationProcessor +com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=spotbugs +com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.41.0=spotbugs +com.google.guava:failureaccess:1.0.1=annotationProcessor,testAnnotationProcessor +com.google.guava:guava:20.0=testCompileClasspath,testRuntimeClasspath +com.google.guava:guava:32.0.1-jre=annotationProcessor,testAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,testAnnotationProcessor +com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,testAnnotationProcessor +com.google.re2j:re2j:1.7=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.squareup.moshi:moshi:1.11.0=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:logging-interceptor:3.12.12=testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:3.12.12=testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:1.17.5=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.qdox:qdox:1.12.1=codenarc +commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.11.0=testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=spotbugs +de.thetaphi:forbiddenapis:3.10=compileClasspath +io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath +io.sqreen:libsqreen:17.3.0=testRuntimeClasspath +javax.servlet:javax.servlet-api:3.1.0=testCompileClasspath,testRuntimeClasspath +jaxen:jaxen:2.0.0=spotbugs +junit:junit:4.13.2=testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna-platform:5.8.0=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.8.0=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +net.sf.saxon:Saxon-HE:12.9=spotbugs +org.apache.ant:ant-antlr:1.10.14=codenarc +org.apache.ant:ant-junit:1.10.14=codenarc +org.apache.bcel:bcel:6.11.0=spotbugs +org.apache.commons:commons-lang3:3.19.0=spotbugs +org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.logging.log4j:log4j-api:2.25.2=spotbugs +org.apache.logging.log4j:log4j-core:2.25.2=spotbugs +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.checkerframework:checker-qual:3.33.0=annotationProcessor,testAnnotationProcessor +org.codehaus.groovy:groovy-ant:3.0.23=codenarc +org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc +org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy-templates:3.0.23=codenarc +org.codehaus.groovy:groovy-xml:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath +org.codenarc:CodeNarc:3.7.0=codenarc +org.dom4j:dom4j:2.2.0=spotbugs +org.gmetrics:GMetrics:2.1.0=codenarc +org.hamcrest:hamcrest-core:1.3=testRuntimeClasspath +org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.jctools:jctools-core-jdk11:4.0.6=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.jctools:jctools-core:4.0.6=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath +org.junit.platform:junit-platform-runner:1.14.1=testRuntimeClasspath +org.junit.platform:junit-platform-suite-api:1.14.1=testRuntimeClasspath +org.junit.platform:junit-platform-suite-commons:1.14.1=testRuntimeClasspath +org.junit:junit-bom:5.14.0=spotbugs +org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-core:4.4.0=testRuntimeClasspath +org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.7.1=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.9=spotbugs +org.ow2.asm:asm-commons:9.7.1=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath +org.ow2.asm:asm-commons:9.9=spotbugs +org.ow2.asm:asm-commons:9.9.1=testRuntimeClasspath +org.ow2.asm:asm-tree:9.7.1=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath +org.ow2.asm:asm-tree:9.9=spotbugs +org.ow2.asm:asm-tree:9.9.1=testRuntimeClasspath +org.ow2.asm:asm-util:9.7.1=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-util:9.9=spotbugs +org.ow2.asm:asm:9.7.1=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath +org.ow2.asm:asm:9.9=spotbugs +org.ow2.asm:asm:9.9.1=testRuntimeClasspath +org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath +org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.32=testCompileClasspath +org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j +org.slf4j:slf4j-simple:1.7.30=testRuntimeClasspath +org.slf4j:slf4j-simple:1.7.30=testRuntimeClasspath +org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j +org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath +org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath +org.xmlresolver:xmlresolver:5.3.3=spotbugs +empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java new file mode 100644 index 00000000000..8640c095a56 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java @@ -0,0 +1,38 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.InstrumenterConfig; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@AutoService(InstrumenterModule.class) +public class FFMApiModule extends InstrumenterModule.Tracing { + private final Map> tracedNativeMethods; + public FFMApiModule() { + super("java-lang-22"); + tracedNativeMethods = InstrumenterConfig.get().getTraceNativeMethods(); + } + + @Override + public Map contextStore() { + return super.contextStore(); + } + + @Override + public boolean isEnabled() { + return super.isEnabled() && !tracedNativeMethods.isEmpty(); + } + + @Override + public List typeInstrumentations() { + return super.typeInstrumentations(); + } + + @Override + public String[] helperClassNames() { + return super.helperClassNames(); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java new file mode 100644 index 00000000000..8de6b89839d --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java @@ -0,0 +1,46 @@ +package testdog.trace.instrumentation.java.lang.jdk21; + +import datadog.trace.api.Trace; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +public class JavaAsyncChild implements Runnable, Callable { + private final AtomicBoolean blockThread; + private final boolean doTraceableWork; + + public JavaAsyncChild() { + this(true, false); + } + + public JavaAsyncChild(final boolean doTraceableWork, final boolean blockThread) { + this.doTraceableWork = doTraceableWork; + this.blockThread = new AtomicBoolean(blockThread); + } + + public void unblock() { + blockThread.set(false); + } + + @Override + public void run() { + runImpl(); + } + + @Override + public Void call() { + runImpl(); + return null; + } + + private void runImpl() { + while (blockThread.get()) { + // busy-wait to block thread + } + if (doTraceableWork) { + asyncChild(); + } + } + + @Trace(operationName = "asyncChild") + private void asyncChild() {} +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java new file mode 100644 index 00000000000..e0bfd5b9088 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java @@ -0,0 +1,151 @@ +package testdog.trace.instrumentation.java.lang.jdk21; + +import static datadog.trace.agent.test.assertions.SpanMatcher.span; +import static datadog.trace.agent.test.assertions.TraceMatcher.SORT_BY_START_TIME; +import static datadog.trace.agent.test.assertions.TraceMatcher.trace; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.api.Trace; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class VirtualThreadApiInstrumentationTest extends AbstractInstrumentationTest { + + @DisplayName("test Thread.Builder.OfVirtual.start()") + @Test + void testBuilderOfVirtualStart() throws InterruptedException, TimeoutException { + Thread.Builder.OfVirtual threadBuilder = Thread.ofVirtual().name("builder - started"); + + new Runnable() { + @Override + @Trace(operationName = "parent") + public void run() { + // this child will have a span + threadBuilder.start(new JavaAsyncChild()); + // this child won't + threadBuilder.start(new JavaAsyncChild(false, false)); + blockUntilChildSpansFinished(1); + } + }.run(); + + assertConnectedTrace(); + } + + @DisplayName("test Thread.Builder.OfVirtual.unstarted()") + @Test + void testBuilderOfVirtualUnstarted() throws InterruptedException, TimeoutException { + Thread.Builder.OfVirtual threadBuilder = Thread.ofVirtual().name("builder - started"); + + new Runnable() { + @Override + @Trace(operationName = "parent") + public void run() { + // this child will have a span + threadBuilder.unstarted(new JavaAsyncChild()).start(); + // this child won't + threadBuilder.unstarted(new JavaAsyncChild(false, false)).start(); + blockUntilChildSpansFinished(1); + } + }.run(); + + assertConnectedTrace(); + } + + @DisplayName("test Thread.startVirtual()") + @Test + void testThreadStartVirtual() throws InterruptedException, TimeoutException { + new Runnable() { + @Override + @Trace(operationName = "parent") + public void run() { + // this child will have a span + Thread.startVirtualThread(new JavaAsyncChild()); + // this child won't + Thread.startVirtualThread(new JavaAsyncChild(false, false)); + blockUntilChildSpansFinished(1); + } + }.run(); + + assertConnectedTrace(); + } + + @DisplayName("test Thread.Builder.OfVirtual.factory()") + @Test + void testThreadOfVirtualFactory() throws InterruptedException, TimeoutException { + ThreadFactory factory = Thread.ofVirtual().factory(); + + new Runnable() { + @Override + @Trace(operationName = "parent") + public void run() { + // this child will have a span + factory.newThread(new JavaAsyncChild()).start(); + // this child won't + factory.newThread(new JavaAsyncChild(false, false)).start(); + blockUntilChildSpansFinished(1); + } + }.run(); + + assertConnectedTrace(); + } + + @DisplayName("test nested virtual threads") + @Test + void testNestedVirtualThreads() throws InterruptedException, TimeoutException { + Thread.Builder.OfVirtual threadBuilder = Thread.ofVirtual(); + CountDownLatch latch = new CountDownLatch(3); + + new Runnable() { + @Trace(operationName = "parent") + @Override + public void run() { + threadBuilder.start( + new Runnable() { + @Trace(operationName = "child") + @Override + public void run() { + threadBuilder.start( + new Runnable() { + @Trace(operationName = "great-child") + @Override + public void run() { + threadBuilder.start( + new Runnable() { + @Trace(operationName = "great-great-child") + @Override + public void run() { + System.out.println("complete"); + latch.countDown(); + } + }); + latch.countDown(); + } + }); + latch.countDown(); + } + }); + } + }.run(); + + latch.await(); + + assertTraces( + trace( + SORT_BY_START_TIME, + span().root().operationName("parent"), + span().childOfPrevious().operationName("child"), + span().childOfPrevious().operationName("great-child"), + span().childOfPrevious().operationName("great-great-child"))); + } + + /** Verifies the parent / child span relation. */ + void assertConnectedTrace() { + assertTraces( + trace( + span().root().operationName("parent"), + span().childOfPrevious().operationName("asyncChild"))); + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 465812e7f7b..79bf74aa54b 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -236,6 +236,7 @@ public final class ConfigDefaults { static final boolean DEFAULT_TRACE_ANNOTATION_ASYNC = false; static final boolean DEFAULT_TRACE_EXECUTORS_ALL = false; static final String DEFAULT_TRACE_METHODS = null; + static final String DEFAULT_TRACE_NATIVE_METHODS = null; static final String DEFAULT_MEASURE_METHODS = ""; static final boolean DEFAULT_TRACE_ANALYTICS_ENABLED = false; static final float DEFAULT_ANALYTICS_SAMPLE_RATE = 1.0f; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java index cf4fbb1d78f..b0c0ccc5c76 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java @@ -23,6 +23,7 @@ public final class TraceInstrumentationConfig { public static final String TRACE_EXECUTORS_ALL = "trace.executors.all"; public static final String TRACE_EXECUTORS = "trace.executors"; public static final String TRACE_METHODS = "trace.methods"; + public static final String TRACE_NATIVE_METHODS = "trace.native.methods"; /* format for measure.methods is the same as for trace.methods: https://docs.datadoghq.com/tracing/trace_collection/custom_instrumentation/java/ diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index 00dd43f7197..54aa6cf1fe4 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -21,6 +21,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_EXECUTORS_ALL; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_METHODS; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_NATIVE_METHODS; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_OTEL_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_USM_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_WEBSOCKET_MESSAGES_ENABLED; @@ -81,6 +82,7 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_EXECUTORS_ALL; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_EXTENSIONS_PATH; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_METHODS; +import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_NATIVE_METHODS; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_PEKKO_SCHEDULER_ENABLED; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_THREAD_POOL_EXECUTORS_EXCLUDE; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_WEBSOCKET_MESSAGES_ENABLED; @@ -200,6 +202,7 @@ public class InstrumenterConfig { private final String traceAnnotations; private final boolean traceAnnotationAsync; private final Map> traceMethods; + private final Map> traceNativeMethods; private final Map> measureMethods; private final boolean internalExitOnFailure; @@ -343,6 +346,9 @@ private InstrumenterConfig() { traceMethods = MethodFilterConfigParser.parse( configProvider.getString(TRACE_METHODS, DEFAULT_TRACE_METHODS)); + traceNativeMethods = + MethodFilterConfigParser.parse( + configProvider.getString(TRACE_NATIVE_METHODS, DEFAULT_TRACE_NATIVE_METHODS)); measureMethods = MethodFilterConfigParser.parse( configProvider.getString(MEASURE_METHODS, DEFAULT_MEASURE_METHODS)); @@ -647,6 +653,10 @@ public Map> getTraceMethods() { return traceMethods; } + public Map> getTraceNativeMethods() { + return traceNativeMethods; + } + public boolean isMethodMeasured(Method method) { if (this.measureMethods.isEmpty()) { return false; diff --git a/settings.gradle.kts b/settings.gradle.kts index dbe66b33670..8956cdb1ba1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -387,6 +387,7 @@ include( ":dd-java-agent:instrumentation:java:java-lang:java-lang-15.0", ":dd-java-agent:instrumentation:java:java-lang:java-lang-17.0", ":dd-java-agent:instrumentation:java:java-lang:java-lang-21.0", + ":dd-java-agent:instrumentation:java:java-lang:java-lang-22.0", ":dd-java-agent:instrumentation:java:java-lang:java-lang-9.0", ":dd-java-agent:instrumentation:java:java-lang:java-lang-classloading-1.8", ":dd-java-agent:instrumentation:java:java-net:java-net-1.8", From fdcbaafdb79794d030209c5c797a7ba2d22d5b6b Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Mon, 2 Mar 2026 14:39:38 +0100 Subject: [PATCH 02/15] wip2 --- .../java/lang/jdk22/FFMApiModule.java | 20 ++-- .../lang/jdk22/LinkerInstrumentation.java | 31 +++++ .../jdk22/SymbolLookupInstrumentation.java | 50 +++++++++ .../java/lang/jdk22/DownCallWrapAdvice.java | 18 +++ .../lang/jdk22/FFMNativeMethodDecorator.java | 106 ++++++++++++++++++ .../java/lang/jdk22/SymbolLookupAdvices.java | 71 ++++++++++++ 6 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java index 8640c095a56..5f3a74f3b58 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java @@ -1,38 +1,44 @@ package datadog.trace.instrumentation.java.lang.jdk22; +import static java.util.Arrays.asList; + import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.api.InstrumenterConfig; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; @AutoService(InstrumenterModule.class) public class FFMApiModule extends InstrumenterModule.Tracing { - private final Map> tracedNativeMethods; public FFMApiModule() { super("java-lang-22"); - tracedNativeMethods = InstrumenterConfig.get().getTraceNativeMethods(); } @Override public Map contextStore() { - return super.contextStore(); + final Map ret = new HashMap<>(); + ret.put("java.lang.foreign.SymbolLookup", "java.lang.String"); + ret.put("java.lang.foreign.MemorySegment", "java.lang.CharSequence"); + return ret; } @Override public boolean isEnabled() { - return super.isEnabled() && !tracedNativeMethods.isEmpty(); + return super.isEnabled() && !InstrumenterConfig.get().getTraceNativeMethods().isEmpty(); } @Override public List typeInstrumentations() { - return super.typeInstrumentations(); + return asList(new LinkerInstrumentation(), new SymbolLookupInstrumentation()); } @Override public String[] helperClassNames() { - return super.helperClassNames(); + return new String[] { + // this could be moved to the boostrap eventually + "datadog.trace.instrumentation.trace_annotation.TraceDecorator", + }; } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java new file mode 100644 index 00000000000..72c50e142d7 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; + +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class LinkerInstrumentation + implements Instrumenter.ForTypeHierarchy, + Instrumenter.ForBootstrap, + Instrumenter.HasMethodAdvice { + @Override + public String hierarchyMarkerType() { + return null; // bootstrap type + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named("java.lang.foreign.Linker")); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(named("downcallHandle")), + "datadog.trace.instrumentation.java.lang.jdk22.DownCallWrapAdvice"); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java new file mode 100644 index 00000000000..b81bb5ddcc0 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java @@ -0,0 +1,50 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SymbolLookupInstrumentation + implements Instrumenter.ForTypeHierarchy, + Instrumenter.ForBootstrap, + Instrumenter.HasMethodAdvice { + private static final String SYMBOL_LOOKUP = "java.lang.foreign.SymbolLookup"; + + @Override + public String hierarchyMarkerType() { + return null; // bootstrap type + } + + @Override + public ElementMatcher hierarchyMatcher() { + // instrument both interface and sub-implementations + return implementsInterface(named(SYMBOL_LOOKUP)).or(named(SYMBOL_LOOKUP)); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(isStatic()).and(named("defaultLookup")), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureDefaultLookup"); + transformer.applyAdvice( + isMethod() + .and(isStatic()) + .and(named("libraryLookup").and(takesArgument(0, named("java.lang.String")))), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryName"); + transformer.applyAdvice( + isMethod() + .and(isStatic()) + .and(named("libraryLookup").and(takesArgument(0, named("java.nio.Path")))), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryPath"); + transformer.applyAdvice( + isMethod().and(named("find").and(takesArgument(0, named("java.lang.String")))), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureMemorySegment"); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java new file mode 100644 index 00000000000..5bd2c3e6d92 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java @@ -0,0 +1,18 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + + +import datadog.trace.bootstrap.InstrumentationContext; +import net.bytebuddy.asm.Advice; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + +public class DownCallWrapAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Argument(0) final MemorySegment memorySegment, @Advice.Return(readOnly = false)MethodHandle handle) { + if (memorySegment == null || !Boolean.TRUE.equals(InstrumentationContext.get(MemorySegment.class, Boolean.class).get(memorySegment))) { + return; + } + MethodHandles. + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java new file mode 100644 index 00000000000..2cea08f773e --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java @@ -0,0 +1,106 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import datadog.context.Context; +import datadog.context.ContextScope; +import datadog.trace.api.DDSpanTypes; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.BaseDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Deque; + +public final class FFMNativeMethodDecorator extends BaseDecorator { + + private static final Logger LOGGER = LoggerFactory.getLogger(FFMNativeMethodDecorator.class); + private static final CharSequence TRACE_FFM = UTF8BytesString.create("trace-ffm"); + private static final CharSequence OPERATION_NAME = UTF8BytesString.create("trace.native"); + + private static final MethodHandle START_SPAN_MH = safeFindStatic("startSpan", MethodType.methodType(void.class, String.class)); + private static final MethodHandle END_SPAN_MH = safeFindStatic("endSpan", MethodType.methodType(void.class, String.class))) + + public static final FFMNativeMethodDecorator DECORATE = new FFMNativeMethodDecorator(); + + private static MethodHandle safeFindStatic(String name, MethodType methodType) { + try { + return MethodHandles.lookup().findStatic(FFMNativeMethodDecorator.class, name, methodType); + } catch (Throwable t) { + LOGGER.debug("Cannot find method {} in NativeMethodHandleWrapper", name, t); + return null; + } + } + + public static MethodHandle wrap(MethodHandle original, String operationName) { + if (START_SPAN_MH == null || END_SPAN_MH == null) { + return original; + } + MethodType originalType = original.type(); + boolean isVoid = originalType.returnType() == void.class; + + MethodHandle startSpanMH = START_SPAN_MH.bindTo(operationName); + /* + Return a methodHandle chain that + 1. first calls startspans + 2. than calls the original + 3. As a tryfinally calls the endSpan providing the return value of (1) as argument + 4. Eventually drops the return value if the original return was void in order to have a method handle wrapped that's transparent + */ + return null; + + } + + public static ContextScope startSpan(CharSequence resourceName) { + AgentSpan span = AgentTracer.startSpan(TRACE_FFM.toString(), OPERATION_NAME); + DECORATE.afterStart(span); + span.setResourceName(resourceName); + return AgentTracer.activateSpan(span); + } + + public static Object endSpan(Throwable t, ContextScope scope, Object result) { + try { + if (scope != null) { + final AgentSpan span = AgentSpan.fromContext(scope.context()); + scope.close(); + + if (span != null) { + if (t != null) { + DECORATE.onError(span, t); + span.addThrowable(t); + } + + span.finish(); + } + } + } catch (Throwable ignored) { + + } + return result; + } + + + @Override + protected String[] instrumentationNames() { + return new String[] {TRACE_FFM.toString()}; + } + + @Override + protected CharSequence spanType() { + return null; + } + + @Override + protected CharSequence component() { + return TRACE_FFM; + } + + +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java new file mode 100644 index 00000000000..b175644f271 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java @@ -0,0 +1,71 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import datadog.trace.api.InstrumenterConfig; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import net.bytebuddy.asm.Advice; + +public class SymbolLookupAdvices { + + public static class DefaultLookup { + public static class CaptureDefault { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Return final SymbolLookup symbolLookup) { + if (symbolLookup != null) { + InstrumentationContext.get(SymbolLookup.class, String.class).put(symbolLookup, "default"); + } + } + } + + public static class CaptureLibraryName { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return final SymbolLookup symbolLookup, + @Advice.Argument(0) final String libraryName) { + if (symbolLookup != null && libraryName != null) { + InstrumentationContext.get(SymbolLookup.class, String.class) + .put(symbolLookup, libraryName.toLowerCase(Locale.ROOT)); + } + } + } + } + + public static class CaptureLibraryPath { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return final SymbolLookup symbolLookup, + @Advice.Argument(0) final Path libraryPath) { + if (symbolLookup != null && libraryPath != null) { + InstrumentationContext.get(SymbolLookup.class, String.class) + .put(symbolLookup, libraryPath.getFileName().toString().toLowerCase(Locale.ROOT)); + } + } + } + + public static class CaptureMemorySegment { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This final SymbolLookup self, + @Advice.Argument(0) final String name, + @Advice.Return final Optional maybeSegment) { + if (name != null && maybeSegment != null && maybeSegment.isPresent()) { + final String libName = + InstrumentationContext.get(SymbolLookup.class, String.class).get(self); + if (libName != null) { + final Set tracedMethods = + InstrumenterConfig.get().getTraceNativeMethods().get(libName); + if (tracedMethods != null && tracedMethods.contains(name)) { + InstrumentationContext.get(MemorySegment.class, CharSequence.class) + .put(maybeSegment.get(), UTF8BytesString.create(libName + "." + name)); + } + } + } + } + } +} From f05234e9ea5e1c0efab71ce92d9350df12aa35c3 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Mon, 2 Mar 2026 17:18:06 +0100 Subject: [PATCH 03/15] Trace native methods calls using FFM APIs --- .../ffm/FFMNativeMethodDecorator.java | 217 ++++++++++++++++++ .../context/FieldBackedContextInjector.java | 2 +- .../FieldBackedContextRequestRewriter.java | 2 +- .../bytebuddy/matcher/ignored_class_name.trie | 2 + .../java-lang/java-lang-22.0/build.gradle | 2 +- .../java/lang/jdk22/FFMApiModule.java | 13 +- .../lang/jdk22/LinkerInstrumentation.java | 12 +- .../jdk22/SymbolLookupInstrumentation.java | 4 - .../java/lang/jdk22/DownCallWrapAdvice.java | 18 +- .../lang/jdk22/FFMNativeMethodDecorator.java | 106 --------- .../java/lang/jdk22/SymbolLookupAdvices.java | 46 ++-- .../test/groovy/FFMInstrumentationTest.groovy | 62 +++++ .../java/lang/jdk21/JavaAsyncChild.java | 46 ---- .../VirtualThreadApiInstrumentationTest.java | 151 ------------ .../test/java/util/NativeLibraryResolver.java | 75 ++++++ .../datadog/trace/api/InstrumenterConfig.java | 9 +- .../trace/api/MethodFilterConfigParser.java | 12 +- 17 files changed, 422 insertions(+), 357 deletions(-) create mode 100644 dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java new file mode 100644 index 00000000000..5bfeefaf705 --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java @@ -0,0 +1,217 @@ +package datadog.trace.bootstrap.instrumentation.ffm; + +import datadog.context.ContextScope; +import datadog.trace.api.InstrumenterConfig; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.BaseDecorator; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class FFMNativeMethodDecorator extends BaseDecorator { + private static final Logger LOGGER = LoggerFactory.getLogger(FFMNativeMethodDecorator.class); + private static final CharSequence TRACE_FFM = UTF8BytesString.create("trace-ffm"); + private static final CharSequence OPERATION_NAME = UTF8BytesString.create("trace.native"); + + private static final MethodHandle START_SPAN_MH = + safeFindStatic( + "startSpan", + MethodType.methodType(ContextScope.class, CharSequence.class, boolean.class)); + private static final MethodHandle END_SPAN_MH = + safeFindStatic( + "endSpan", + MethodType.methodType(Object.class, Throwable.class, ContextScope.class, Object.class)); + + public static final FFMNativeMethodDecorator DECORATE = new FFMNativeMethodDecorator(); + + private static MethodHandle safeFindStatic(String name, MethodType methodType) { + try { + return MethodHandles.lookup().findStatic(FFMNativeMethodDecorator.class, name, methodType); + } catch (Throwable t) { + LOGGER.debug("Cannot find method {} in NativeMethodHandleWrapper", name, t); + return null; + } + } + + public static MethodHandle wrap( + final MethodHandle original, final String libraryName, final String methodName) { + if (START_SPAN_MH == null || END_SPAN_MH == null) { + return original; + } + try { + MethodType originalType = original.type(); + boolean isVoid = originalType.returnType() == void.class; + + // We need the ContextScope to be visible to the finally block. + // Easiest way is to artificially prepend it to the target signature. + // The added parameter is ignored by the original handle. + // originalWithScope: (ContextScope, args...) -> R + MethodHandle originalWithScope = MethodHandles.dropArguments(original, 0, ContextScope.class); + + /* + * Build the cleanup handle used by MethodHandles.tryFinally. + * + * tryFinally has a strict calling convention: + * - void target -> cleanup(Throwable, ContextScope, args...) + * - non-void -> cleanup(Throwable, R, ContextScope, args...) + * + * END_SPAN_MH is (Throwable, ContextScope, Object) -> Object, + * so we need to reshape it to match what tryFinally expects. + */ + MethodHandle cleanup; + + if (isVoid) { + // No return value: bind `null` as the result argument. + MethodHandle endWithNull = MethodHandles.insertArguments(END_SPAN_MH, 2, (Object) null); + + // Make it accept the original arguments even though they are unused. + MethodHandle endDropped = + MethodHandles.dropArguments(endWithNull, 2, originalType.parameterList()); + + // tryFinally requires void return for void targets. + cleanup = endDropped.asType(endDropped.type().changeReturnType(void.class)); + + } else { + /* + * Non-void case: + * tryFinally will call cleanup as: + * (Throwable, returnValue, ContextScope, args...) + * + * END_SPAN_MH expects: + * (Throwable, ContextScope, result) + * + * So we first permute parameters to swap returnValue and ContextScope. + */ + MethodHandle endPermuted = + MethodHandles.permuteArguments( + END_SPAN_MH, + MethodType.methodType( + Object.class, Throwable.class, Object.class, ContextScope.class), + 0, + 2, + 1); + + // Accept original arguments (unused) after the required ones. + MethodHandle endDropped = + MethodHandles.dropArguments(endPermuted, 3, originalType.parameterList()); + + // Adapt return and result parameter types to match the original signature. + MethodType cleanupType = + endDropped + .type() + .changeParameterType(1, originalType.returnType()) + .changeReturnType(originalType.returnType()); + + cleanup = endDropped.asType(cleanupType); + } + + // Wrap the original in try/finally semantics. + // Resulting handle: + // (ContextScope, args...) -> R + MethodHandle withFinally = MethodHandles.tryFinally(originalWithScope, cleanup); + + // Precompute span metadata so we don't redo the lookup per invocation. + final CharSequence resourceName = resourceNameFor(libraryName, methodName); + final boolean methodMeasured = isMethodMeasured(libraryName, methodName); + + // Bind both arguments to startSpan. + // After binding: () -> ContextScope + MethodHandle boundStart = + MethodHandles.insertArguments(START_SPAN_MH, 0, resourceName, methodMeasured); + + // Make it look like it takes the same arguments as the original, + // even though they are ignored. + // (args...) -> ContextScope + MethodHandle startCombiner = + MethodHandles.dropArguments(boundStart, 0, originalType.parameterList()); + + /* + * foldArguments wires it all together: + * + * scope = startCombiner(args...) + * return withFinally(scope, args...) + * + * Final shape matches the original: + * (args...) -> R + */ + return MethodHandles.foldArguments(withFinally, startCombiner); + + } catch (Throwable t) { + LOGGER.debug( + "Cannot wrap method handle for library {} and method {}", libraryName, methodName, t); + return original; + } + } + + @SuppressWarnings("unused") + public static ContextScope startSpan(CharSequence resourceName, boolean methodMeasured) { + AgentSpan span = AgentTracer.startSpan(TRACE_FFM.toString(), OPERATION_NAME); + DECORATE.afterStart(span); + span.setResourceName(resourceName); + return AgentTracer.activateSpan(span); + } + + @SuppressWarnings("unused") + public static Object endSpan(Throwable t, ContextScope scope, Object result) { + try { + if (scope != null) { + final AgentSpan span = AgentSpan.fromContext(scope.context()); + scope.close(); + + if (span != null) { + if (t != null) { + DECORATE.onError(span, t); + span.addThrowable(t); + } + + span.finish(); + } + } + } catch (Throwable ignored) { + + } + return result; + } + + public static boolean isMethodTraced(final String library, final String method) { + return matches(InstrumenterConfig.get().getTraceNativeMethods().get(library), method); + } + + public static boolean isMethodMeasured(final String library, final String method) { + return matches(InstrumenterConfig.get().getMeasureMethods().get(library), method); + } + + public static CharSequence resourceNameFor(final String library, final String method) { + if (library == null || library.isEmpty()) { + return UTF8BytesString.create(method); + } + return UTF8BytesString.create(library + "." + method); + } + + private static boolean matches(final Set allows, final String method) { + if (allows == null) { + return false; + } + return allows.contains(method) || allows.contains("*"); + } + + @Override + protected String[] instrumentationNames() { + return new String[] {TRACE_FFM.toString()}; + } + + @Override + protected CharSequence spanType() { + return null; + } + + @Override + protected CharSequence component() { + return TRACE_FFM; + } +} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextInjector.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextInjector.java index 276ac6daf1b..1392a08fbfa 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextInjector.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextInjector.java @@ -106,7 +106,7 @@ public ClassVisitor wrap( final MethodList methods, final int writerFlags, final int readerFlags) { - return new ClassVisitor(Opcodes.ASM8, classVisitor) { + return new ClassVisitor(Opcodes.ASM9, classVisitor) { private final boolean frames = implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6); diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextRequestRewriter.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextRequestRewriter.java index fb0a6153a41..08d52f9bd57 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextRequestRewriter.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextRequestRewriter.java @@ -84,7 +84,7 @@ public ClassVisitor wrap( final MethodList methods, final int writerFlags, final int readerFlags) { - return new ClassVisitor(Opcodes.ASM8, classVisitor) { + return new ClassVisitor(Opcodes.ASM9, classVisitor) { @Override public MethodVisitor visitMethod( final int access, diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index eff0a75f69d..7db619d7488 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -44,6 +44,8 @@ 1 io.opentelemetry.javaagent.* 1 java.* 0 java.lang.ClassLoader +0 java.lang.foreign.* +0 jdk.internal.foreign.* # allow exception profiling instrumentation 0 java.lang.Exception 0 java.lang.Error diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle index 698964e4990..78476a2dd9d 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle @@ -16,7 +16,7 @@ tracerJava { } testJvmConstraints { - minJavaVersion = JavaVersion.VERSION_22 + minJavaVersion = JavaVersion.VERSION_25 } idea { diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java index 5f3a74f3b58..95250757aa7 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java @@ -6,6 +6,7 @@ import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.api.InstrumenterConfig; +import datadog.trace.api.Pair; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,8 +20,8 @@ public FFMApiModule() { @Override public Map contextStore() { final Map ret = new HashMap<>(); - ret.put("java.lang.foreign.SymbolLookup", "java.lang.String"); - ret.put("java.lang.foreign.MemorySegment", "java.lang.CharSequence"); + ret.put("java.lang.foreign.SymbolLookup", String.class.getName()); + ret.put("java.lang.foreign.MemorySegment", Pair.class.getName()); return ret; } @@ -33,12 +34,4 @@ public boolean isEnabled() { public List typeInstrumentations() { return asList(new LinkerInstrumentation(), new SymbolLookupInstrumentation()); } - - @Override - public String[] helperClassNames() { - return new String[] { - // this could be moved to the boostrap eventually - "datadog.trace.instrumentation.trace_annotation.TraceDecorator", - }; - } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java index 72c50e142d7..ec58693017a 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java @@ -1,8 +1,9 @@ package datadog.trace.instrumentation.java.lang.jdk22; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import datadog.trace.agent.tooling.Instrumenter; import net.bytebuddy.description.type.TypeDescription; @@ -19,13 +20,18 @@ public String hierarchyMarkerType() { @Override public ElementMatcher hierarchyMatcher() { - return implementsInterface(named("java.lang.foreign.Linker")); + return hasInterface(named("java.lang.foreign.Linker")); } @Override public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice( - isMethod().and(named("downcallHandle")), + isMethod().and(named("defaultLookup")), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureDefaultLookup"); + transformer.applyAdvice( + isMethod() + .and(named("downcallHandle")) + .and(takesArgument(0, named("java.lang.foreign.MemorySegment"))), "datadog.trace.instrumentation.java.lang.jdk22.DownCallWrapAdvice"); } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java index b81bb5ddcc0..63aceb24f7d 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java @@ -7,7 +7,6 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -30,9 +29,6 @@ public ElementMatcher hierarchyMatcher() { @Override public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod().and(isStatic()).and(named("defaultLookup")), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureDefaultLookup"); transformer.applyAdvice( isMethod() .and(isStatic()) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java index 5bd2c3e6d92..12227c2c904 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java @@ -1,18 +1,26 @@ package datadog.trace.instrumentation.java.lang.jdk22; +import static datadog.trace.bootstrap.instrumentation.ffm.FFMNativeMethodDecorator.wrap; +import datadog.trace.api.Pair; import datadog.trace.bootstrap.InstrumentationContext; -import net.bytebuddy.asm.Advice; import java.lang.foreign.MemorySegment; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; +import net.bytebuddy.asm.Advice; public class DownCallWrapAdvice { @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit(@Advice.Argument(0) final MemorySegment memorySegment, @Advice.Return(readOnly = false)MethodHandle handle) { - if (memorySegment == null || !Boolean.TRUE.equals(InstrumentationContext.get(MemorySegment.class, Boolean.class).get(memorySegment))) { + public static void onExit( + @Advice.Argument(0) final MemorySegment memorySegment, + @Advice.Return(readOnly = false) MethodHandle handle) { + if (memorySegment == null) { + return; + } + final Pair libAndMethod = + InstrumentationContext.get(MemorySegment.class, Pair.class).get(memorySegment); + if (libAndMethod == null) { return; } - MethodHandles. + handle = wrap(handle, libAndMethod.getLeft(), libAndMethod.getRight()); } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java deleted file mode 100644 index 2cea08f773e..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/FFMNativeMethodDecorator.java +++ /dev/null @@ -1,106 +0,0 @@ -package datadog.trace.instrumentation.java.lang.jdk22; - -import datadog.context.Context; -import datadog.context.ContextScope; -import datadog.trace.api.DDSpanTypes; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; -import datadog.trace.bootstrap.instrumentation.decorator.BaseDecorator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.Deque; - -public final class FFMNativeMethodDecorator extends BaseDecorator { - - private static final Logger LOGGER = LoggerFactory.getLogger(FFMNativeMethodDecorator.class); - private static final CharSequence TRACE_FFM = UTF8BytesString.create("trace-ffm"); - private static final CharSequence OPERATION_NAME = UTF8BytesString.create("trace.native"); - - private static final MethodHandle START_SPAN_MH = safeFindStatic("startSpan", MethodType.methodType(void.class, String.class)); - private static final MethodHandle END_SPAN_MH = safeFindStatic("endSpan", MethodType.methodType(void.class, String.class))) - - public static final FFMNativeMethodDecorator DECORATE = new FFMNativeMethodDecorator(); - - private static MethodHandle safeFindStatic(String name, MethodType methodType) { - try { - return MethodHandles.lookup().findStatic(FFMNativeMethodDecorator.class, name, methodType); - } catch (Throwable t) { - LOGGER.debug("Cannot find method {} in NativeMethodHandleWrapper", name, t); - return null; - } - } - - public static MethodHandle wrap(MethodHandle original, String operationName) { - if (START_SPAN_MH == null || END_SPAN_MH == null) { - return original; - } - MethodType originalType = original.type(); - boolean isVoid = originalType.returnType() == void.class; - - MethodHandle startSpanMH = START_SPAN_MH.bindTo(operationName); - /* - Return a methodHandle chain that - 1. first calls startspans - 2. than calls the original - 3. As a tryfinally calls the endSpan providing the return value of (1) as argument - 4. Eventually drops the return value if the original return was void in order to have a method handle wrapped that's transparent - */ - return null; - - } - - public static ContextScope startSpan(CharSequence resourceName) { - AgentSpan span = AgentTracer.startSpan(TRACE_FFM.toString(), OPERATION_NAME); - DECORATE.afterStart(span); - span.setResourceName(resourceName); - return AgentTracer.activateSpan(span); - } - - public static Object endSpan(Throwable t, ContextScope scope, Object result) { - try { - if (scope != null) { - final AgentSpan span = AgentSpan.fromContext(scope.context()); - scope.close(); - - if (span != null) { - if (t != null) { - DECORATE.onError(span, t); - span.addThrowable(t); - } - - span.finish(); - } - } - } catch (Throwable ignored) { - - } - return result; - } - - - @Override - protected String[] instrumentationNames() { - return new String[] {TRACE_FFM.toString()}; - } - - @Override - protected CharSequence spanType() { - return null; - } - - @Override - protected CharSequence component() { - return TRACE_FFM; - } - - -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java index b175644f271..c40aa2daa20 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java @@ -1,37 +1,34 @@ package datadog.trace.instrumentation.java.lang.jdk22; -import datadog.trace.api.InstrumenterConfig; +import static datadog.trace.bootstrap.instrumentation.ffm.FFMNativeMethodDecorator.isMethodTraced; + +import datadog.trace.api.Pair; import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; import java.nio.file.Path; import java.util.Locale; import java.util.Optional; -import java.util.Set; import net.bytebuddy.asm.Advice; public class SymbolLookupAdvices { - - public static class DefaultLookup { - public static class CaptureDefault { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit(@Advice.Return final SymbolLookup symbolLookup) { - if (symbolLookup != null) { - InstrumentationContext.get(SymbolLookup.class, String.class).put(symbolLookup, "default"); - } + public static class CaptureDefaultLookup { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Return final SymbolLookup symbolLookup) { + if (symbolLookup != null) { + InstrumentationContext.get(SymbolLookup.class, String.class).put(symbolLookup, ""); } } + } - public static class CaptureLibraryName { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return final SymbolLookup symbolLookup, - @Advice.Argument(0) final String libraryName) { - if (symbolLookup != null && libraryName != null) { - InstrumentationContext.get(SymbolLookup.class, String.class) - .put(symbolLookup, libraryName.toLowerCase(Locale.ROOT)); - } + public static class CaptureLibraryName { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return final SymbolLookup symbolLookup, + @Advice.Argument(0) final String libraryName) { + if (symbolLookup != null && libraryName != null) { + InstrumentationContext.get(SymbolLookup.class, String.class) + .put(symbolLookup, libraryName.toLowerCase(Locale.ROOT)); } } } @@ -58,12 +55,9 @@ public static void onExit( final String libName = InstrumentationContext.get(SymbolLookup.class, String.class).get(self); if (libName != null) { - final Set tracedMethods = - InstrumenterConfig.get().getTraceNativeMethods().get(libName); - if (tracedMethods != null && tracedMethods.contains(name)) { - InstrumentationContext.get(MemorySegment.class, CharSequence.class) - .put(maybeSegment.get(), UTF8BytesString.create(libName + "." + name)); - } + if (isMethodTraced(libName, name)) + InstrumentationContext.get(MemorySegment.class, Pair.class) + .put(maybeSegment.get(), Pair.of(libName, name)); } } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy new file mode 100644 index 00000000000..25d1f8d0c1d --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy @@ -0,0 +1,62 @@ +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.bootstrap.instrumentation.api.Tags +import java.lang.foreign.Arena +import java.lang.foreign.FunctionDescriptor +import java.lang.foreign.Linker +import java.lang.foreign.MemorySegment +import java.lang.foreign.ValueLayout +import java.lang.invoke.MethodHandle + +class FFMInstrumentationTest extends InstrumentationSpecification { + @Override + protected void configurePreAgent() { + super.configurePreAgent() + // only here to let the FFMApiModule kick in without forking each case then. + injectSysConfig("trace.native.methods", "unknown[*]") + } + + def "should trace ffm calls"() { + setup: + injectSysConfig("trace.native.methods", configured) + final MemorySegment strlenAddr = lookup.call().findOrThrow("strlen") + final MethodHandle strlenHandle = + Linker.nativeLinker().downcallHandle( + strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) + + when: + long len + try (final Arena arena = Arena.ofConfined()) { + len = (long) strlenHandle.invokeWithArguments(arena.allocateFrom("Hello world!")) + } + + then: + len == 12 + assertTraces(traceExpected ? 1 : 0) { + if (traceExpected) { + trace(1) { + span { + operationName "trace.native" + resourceName "$resource" + tags { + "$Tags.COMPONENT" "trace-ffm" + defaultTags() + } + } + } + } + } + + where: + configured | traceExpected | resource | lookup + "" | false | _ | { + Linker.nativeLinker().defaultLookup() + } + "[strlen]" | true | "strlen" | { + Linker.nativeLinker().defaultLookup() + } + "[*]" | true | "strlen" | { + Linker.nativeLinker().defaultLookup() + } + } +} + diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java deleted file mode 100644 index 8de6b89839d..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/JavaAsyncChild.java +++ /dev/null @@ -1,46 +0,0 @@ -package testdog.trace.instrumentation.java.lang.jdk21; - -import datadog.trace.api.Trace; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicBoolean; - -public class JavaAsyncChild implements Runnable, Callable { - private final AtomicBoolean blockThread; - private final boolean doTraceableWork; - - public JavaAsyncChild() { - this(true, false); - } - - public JavaAsyncChild(final boolean doTraceableWork, final boolean blockThread) { - this.doTraceableWork = doTraceableWork; - this.blockThread = new AtomicBoolean(blockThread); - } - - public void unblock() { - blockThread.set(false); - } - - @Override - public void run() { - runImpl(); - } - - @Override - public Void call() { - runImpl(); - return null; - } - - private void runImpl() { - while (blockThread.get()) { - // busy-wait to block thread - } - if (doTraceableWork) { - asyncChild(); - } - } - - @Trace(operationName = "asyncChild") - private void asyncChild() {} -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java deleted file mode 100644 index e0bfd5b9088..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/testdog/trace/instrumentation/java/lang/jdk21/VirtualThreadApiInstrumentationTest.java +++ /dev/null @@ -1,151 +0,0 @@ -package testdog.trace.instrumentation.java.lang.jdk21; - -import static datadog.trace.agent.test.assertions.SpanMatcher.span; -import static datadog.trace.agent.test.assertions.TraceMatcher.SORT_BY_START_TIME; -import static datadog.trace.agent.test.assertions.TraceMatcher.trace; - -import datadog.trace.agent.test.AbstractInstrumentationTest; -import datadog.trace.api.Trace; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class VirtualThreadApiInstrumentationTest extends AbstractInstrumentationTest { - - @DisplayName("test Thread.Builder.OfVirtual.start()") - @Test - void testBuilderOfVirtualStart() throws InterruptedException, TimeoutException { - Thread.Builder.OfVirtual threadBuilder = Thread.ofVirtual().name("builder - started"); - - new Runnable() { - @Override - @Trace(operationName = "parent") - public void run() { - // this child will have a span - threadBuilder.start(new JavaAsyncChild()); - // this child won't - threadBuilder.start(new JavaAsyncChild(false, false)); - blockUntilChildSpansFinished(1); - } - }.run(); - - assertConnectedTrace(); - } - - @DisplayName("test Thread.Builder.OfVirtual.unstarted()") - @Test - void testBuilderOfVirtualUnstarted() throws InterruptedException, TimeoutException { - Thread.Builder.OfVirtual threadBuilder = Thread.ofVirtual().name("builder - started"); - - new Runnable() { - @Override - @Trace(operationName = "parent") - public void run() { - // this child will have a span - threadBuilder.unstarted(new JavaAsyncChild()).start(); - // this child won't - threadBuilder.unstarted(new JavaAsyncChild(false, false)).start(); - blockUntilChildSpansFinished(1); - } - }.run(); - - assertConnectedTrace(); - } - - @DisplayName("test Thread.startVirtual()") - @Test - void testThreadStartVirtual() throws InterruptedException, TimeoutException { - new Runnable() { - @Override - @Trace(operationName = "parent") - public void run() { - // this child will have a span - Thread.startVirtualThread(new JavaAsyncChild()); - // this child won't - Thread.startVirtualThread(new JavaAsyncChild(false, false)); - blockUntilChildSpansFinished(1); - } - }.run(); - - assertConnectedTrace(); - } - - @DisplayName("test Thread.Builder.OfVirtual.factory()") - @Test - void testThreadOfVirtualFactory() throws InterruptedException, TimeoutException { - ThreadFactory factory = Thread.ofVirtual().factory(); - - new Runnable() { - @Override - @Trace(operationName = "parent") - public void run() { - // this child will have a span - factory.newThread(new JavaAsyncChild()).start(); - // this child won't - factory.newThread(new JavaAsyncChild(false, false)).start(); - blockUntilChildSpansFinished(1); - } - }.run(); - - assertConnectedTrace(); - } - - @DisplayName("test nested virtual threads") - @Test - void testNestedVirtualThreads() throws InterruptedException, TimeoutException { - Thread.Builder.OfVirtual threadBuilder = Thread.ofVirtual(); - CountDownLatch latch = new CountDownLatch(3); - - new Runnable() { - @Trace(operationName = "parent") - @Override - public void run() { - threadBuilder.start( - new Runnable() { - @Trace(operationName = "child") - @Override - public void run() { - threadBuilder.start( - new Runnable() { - @Trace(operationName = "great-child") - @Override - public void run() { - threadBuilder.start( - new Runnable() { - @Trace(operationName = "great-great-child") - @Override - public void run() { - System.out.println("complete"); - latch.countDown(); - } - }); - latch.countDown(); - } - }); - latch.countDown(); - } - }); - } - }.run(); - - latch.await(); - - assertTraces( - trace( - SORT_BY_START_TIME, - span().root().operationName("parent"), - span().childOfPrevious().operationName("child"), - span().childOfPrevious().operationName("great-child"), - span().childOfPrevious().operationName("great-great-child"))); - } - - /** Verifies the parent / child span relation. */ - void assertConnectedTrace() { - assertTraces( - trace( - span().root().operationName("parent"), - span().childOfPrevious().operationName("asyncChild"))); - } -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java new file mode 100644 index 00000000000..8668d6ad714 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java @@ -0,0 +1,75 @@ +package util; + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.nio.charset.StandardCharsets; + +public class NativeLibraryResolver { + private static final Linker LINKER = Linker.nativeLinker(); + private static final SymbolLookup LOOKUP = LINKER.defaultLookup(); + + // dladdr symbol + private static final MethodHandle DLADDR; + + // struct Dl_info + private static final GroupLayout DL_INFO_LAYOUT = + MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("dli_fname"), + ValueLayout.ADDRESS.withName("dli_fbase"), + ValueLayout.ADDRESS.withName("dli_sname"), + ValueLayout.ADDRESS.withName("dli_saddr")); + + private static final long DLI_FNAME_OFFSET = + DL_INFO_LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("dli_fname")); + + static { + try { + MemorySegment dladdrSymbol = LOOKUP.find("dladdr").orElseThrow(); + + DLADDR = + LINKER.downcallHandle( + dladdrSymbol, + FunctionDescriptor.of( + ValueLayout.JAVA_INT, // int return + ValueLayout.ADDRESS, // const void* addr + ValueLayout.ADDRESS // Dl_info* info + )); + } catch (Throwable t) { + throw new ExceptionInInitializerError(t); + } + } + + /** + * Returns the full native library path that defines the given symbol. Returns null if the symbol + * cannot be resolved. + */ + public static String findLibraryPath(MemorySegment symbolAddress) { + try (Arena arena = Arena.ofConfined()) { + + MemorySegment info = arena.allocate(DL_INFO_LAYOUT); + + int result = (int) DLADDR.invoke(symbolAddress, info); + if (result == 0) { + return null; // not found + } + + MemorySegment fnamePtr = info.get(ValueLayout.ADDRESS, DLI_FNAME_OFFSET); + + if (fnamePtr == MemorySegment.NULL) { + return null; + } + + return fnamePtr.getString(0, StandardCharsets.UTF_8); + + } catch (Throwable t) { + return null; + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index 54aa6cf1fe4..97df9cbb636 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -347,7 +347,7 @@ private InstrumenterConfig() { MethodFilterConfigParser.parse( configProvider.getString(TRACE_METHODS, DEFAULT_TRACE_METHODS)); traceNativeMethods = - MethodFilterConfigParser.parse( + MethodFilterConfigParser.parseForNativeMethods( configProvider.getString(TRACE_NATIVE_METHODS, DEFAULT_TRACE_NATIVE_METHODS)); measureMethods = MethodFilterConfigParser.parse( @@ -657,6 +657,10 @@ public Map> getTraceNativeMethods() { return traceNativeMethods; } + public Map> getMeasureMethods() { + return measureMethods; + } + public boolean isMethodMeasured(Method method) { if (this.measureMethods.isEmpty()) { return false; @@ -802,6 +806,9 @@ public String toString() { + ", traceMethods='" + traceMethods + '\'' + + ", traceNativeMethods='" + + traceNativeMethods + + '\'' + ", measureMethods= '" + measureMethods + '\'' diff --git a/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java b/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java index 00e55e4f2ff..d24b4e0aa2c 100644 --- a/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java +++ b/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java @@ -41,8 +41,16 @@ private static boolean isIllegalMethodName(String string) { return !string.equals("*") && hasIllegalCharacters(string); } - @SuppressForbidden public static Map> parse(String configString) { + return parse(configString, false); + } + + public static Map> parseForNativeMethods(String configString) { + return parse(configString, true); + } + + @SuppressForbidden + private static Map> parse(String configString, boolean allowEmptyClassNames) { Map> classMethodsToTrace; if (configString == null || configString.trim().isEmpty()) { classMethodsToTrace = Collections.emptyMap(); @@ -77,7 +85,7 @@ public static Map> parse(String configString) { break; } String className = configString.substring(start, methodsStart).trim(); - if (className.isEmpty() || isIllegalClassName(className)) { + if ((className.isEmpty() && !allowEmptyClassNames) || isIllegalClassName(className)) { toTrace = logWarn("with illegal class name", start, end, configString); break; } From f83396371fd1dda68e972ebec75258c807cc02b7 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 3 Mar 2026 10:10:29 +0100 Subject: [PATCH 04/15] Add more tests --- .../api/MethodFilterConfigParserTest.groovy | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy index 29d8adb4c3b..f9f71d8203f 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy @@ -46,6 +46,27 @@ class MethodFilterConfigParserTest extends DDSpecification { "asdfg;c1[m1]" | [:] } + def "test native method configuration \"#value\""() { + setup: + if (value) { + injectSysConfig("dd.trace.native.methods", value) + } else { + removeSysConfig("dd.trace.methods") + } + + expect: + InstrumenterConfig.get().traceNativeMethods == expected + + where: + value | expected + null | [:] + " " | [:] + "*" | [:] + "[ method , ]" | ["": ["method"].toSet()] + "lib[ method1 , method2]" | ["lib": ["method1", "method2"].toSet()] + "[*]" | ["": ["*"].toSet()] + } + def "survive very long list"() { setup: def mset = ["m1", "m2"].toSet() From 1e07926c8834484a52dea5893c52a04fa62ceb1d Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 3 Mar 2026 10:37:50 +0100 Subject: [PATCH 05/15] try to add linux tests --- .../test/groovy/FFMInstrumentationTest.groovy | 102 ++++++++++++++++-- 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy index 25d1f8d0c1d..36472266d7e 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy @@ -4,8 +4,12 @@ import java.lang.foreign.Arena import java.lang.foreign.FunctionDescriptor import java.lang.foreign.Linker import java.lang.foreign.MemorySegment +import java.lang.foreign.SymbolLookup import java.lang.foreign.ValueLayout import java.lang.invoke.MethodHandle +import java.nio.file.Path +import spock.lang.IgnoreIf +import util.NativeLibraryResolver class FFMInstrumentationTest extends InstrumentationSpecification { @Override @@ -15,10 +19,10 @@ class FFMInstrumentationTest extends InstrumentationSpecification { injectSysConfig("trace.native.methods", "unknown[*]") } - def "should trace ffm calls"() { + def "should trace ffm calls for #configured"() { setup: injectSysConfig("trace.native.methods", configured) - final MemorySegment strlenAddr = lookup.call().findOrThrow("strlen") + final MemorySegment strlenAddr = Linker.nativeLinker().defaultLookup().findOrThrow("strlen") final MethodHandle strlenHandle = Linker.nativeLinker().downcallHandle( strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) @@ -47,16 +51,94 @@ class FFMInstrumentationTest extends InstrumentationSpecification { } where: - configured | traceExpected | resource | lookup - "" | false | _ | { - Linker.nativeLinker().defaultLookup() + configured | traceExpected | resource + "" | false | _ + "[strlen]" | true | "strlen" + "[*]" | true | "strlen" + } + + @IgnoreIf({ !os.isLinux() }) + def "should trace ffm calls using libraryLookup by name for #configured"() { + setup: + injectSysConfig("trace.native.methods", configured) + + when: + long len + try (final Arena arena = Arena.ofConfined()) { + final SymbolLookup libLookup = SymbolLookup.libraryLookup("c", arena) + final MemorySegment strlenAddr = libLookup.findOrThrow("strlen") + final MethodHandle strlenHandle = + Linker.nativeLinker().downcallHandle( + strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) + len = (long) strlenHandle.invokeWithArguments(arena.allocateFrom("Hello world!")) } - "[strlen]" | true | "strlen" | { - Linker.nativeLinker().defaultLookup() + + then: + len == 12 + assertTraces(traceExpected ? 1 : 0) { + if (traceExpected) { + trace(1) { + span { + operationName "trace.native" + resourceName "$resource" + tags { + "$Tags.COMPONENT" "trace-ffm" + defaultTags() + } + } + } + } + } + + where: + configured | traceExpected | resource + "" | false | _ + "c[strlen]" | true | "c.strlen" + "c[*]" | true | "c.strlen" + "unknown_lib[*]" | false | _ + } + + @IgnoreIf({ !os.isLinux() }) + def "should trace ffm calls using libraryLookup by path for #configTemplate"() { + setup: + final MemorySegment strlenSym = Linker.nativeLinker().defaultLookup().findOrThrow("strlen") + final String libPath = NativeLibraryResolver.findLibraryPath(strlenSym) + final String libFileName = Path.of(libPath).getFileName().toString().toLowerCase(Locale.ROOT) + injectSysConfig("trace.native.methods", configTemplate.replace("{lib}", libFileName)) + + when: + long len + try (final Arena arena = Arena.ofConfined()) { + final SymbolLookup libLookup = SymbolLookup.libraryLookup(Path.of(libPath), arena) + final MemorySegment strlenAddr = libLookup.findOrThrow("strlen") + final MethodHandle strlenHandle = + Linker.nativeLinker().downcallHandle( + strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) + len = (long) strlenHandle.invokeWithArguments(arena.allocateFrom("Hello world!")) } - "[*]" | true | "strlen" | { - Linker.nativeLinker().defaultLookup() + + then: + len == 12 + assertTraces(traceExpected ? 1 : 0) { + if (traceExpected) { + trace(1) { + span { + operationName "trace.native" + resourceName "${libFileName}.strlen" + tags { + "$Tags.COMPONENT" "trace-ffm" + defaultTags() + } + } + } + } } + + where: + configTemplate | traceExpected + "" | false + "{lib}[strlen]" | true + "{lib}[*]" | true + "unknown_lib[*]" | false } } - From b2840101a2138ad31f3c32d1fb414ac8bc223f9f Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 3 Mar 2026 10:47:05 +0100 Subject: [PATCH 06/15] cleanup --- .../java/lang/jdk22/FFMApiModule.java | 5 ++- .../jdk22/SymbolLookupInstrumentation.java | 15 +-------- .../SymbolLookupStaticInstrumentation.java | 31 +++++++++++++++++++ .../test/java/util/NativeLibraryResolver.java | 3 ++ 4 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java index 95250757aa7..467ca9e5cfb 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java @@ -32,6 +32,9 @@ public boolean isEnabled() { @Override public List typeInstrumentations() { - return asList(new LinkerInstrumentation(), new SymbolLookupInstrumentation()); + return asList( + new LinkerInstrumentation(), + new SymbolLookupInstrumentation(), + new SymbolLookupStaticInstrumentation()); } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java index 63aceb24f7d..bb83bdf1421 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java @@ -3,7 +3,6 @@ import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import datadog.trace.agent.tooling.Instrumenter; @@ -14,8 +13,6 @@ public class SymbolLookupInstrumentation implements Instrumenter.ForTypeHierarchy, Instrumenter.ForBootstrap, Instrumenter.HasMethodAdvice { - private static final String SYMBOL_LOOKUP = "java.lang.foreign.SymbolLookup"; - @Override public String hierarchyMarkerType() { return null; // bootstrap type @@ -24,21 +21,11 @@ public String hierarchyMarkerType() { @Override public ElementMatcher hierarchyMatcher() { // instrument both interface and sub-implementations - return implementsInterface(named(SYMBOL_LOOKUP)).or(named(SYMBOL_LOOKUP)); + return implementsInterface(named("java.lang.foreign.SymbolLookup")); } @Override public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod() - .and(isStatic()) - .and(named("libraryLookup").and(takesArgument(0, named("java.lang.String")))), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryName"); - transformer.applyAdvice( - isMethod() - .and(isStatic()) - .and(named("libraryLookup").and(takesArgument(0, named("java.nio.Path")))), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryPath"); transformer.applyAdvice( isMethod().and(named("find").and(takesArgument(0, named("java.lang.String")))), "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureMemorySegment"); diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java new file mode 100644 index 00000000000..7a260dc0fca --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import datadog.trace.agent.tooling.Instrumenter; + +public class SymbolLookupStaticInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.ForBootstrap, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "java.lang.foreign.SymbolLookup"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(isStatic()) + .and(named("libraryLookup").and(takesArgument(0, named("java.lang.String")))), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryName"); + transformer.applyAdvice( + isMethod() + .and(isStatic()) + .and(named("libraryLookup").and(takesArgument(0, named("java.nio.Path")))), + "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryPath"); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java index 8668d6ad714..a7a44bb4738 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java @@ -49,6 +49,9 @@ public class NativeLibraryResolver { /** * Returns the full native library path that defines the given symbol. Returns null if the symbol * cannot be resolved. + * + * @param symbolAddress the address of the symbol to resolve. + * @return the library path or null if cannot be found. */ public static String findLibraryPath(MemorySegment symbolAddress) { try (Arena arena = Arena.ofConfined()) { From 3b21970237f181c78a4b328dadd5f3ccb6619aca Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 3 Mar 2026 10:52:52 +0100 Subject: [PATCH 07/15] Add config stuff --- metadata/supported-configurations.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 6b00677813f..6b3a1c4c9eb 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -7649,6 +7649,14 @@ "aliases": [] } ], + "DD_TRACE_NATIVE_METHODS": [ + { + "version": "A", + "type": "string", + "default": null, + "aliases": [] + } + ], "DD_TRACE_MICRONAUT_ANALYTICS_ENABLED": [ { "version": "A", From 24b9309288f809d55b450cea34420af1a2b5e131 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 3 Mar 2026 11:11:15 +0100 Subject: [PATCH 08/15] Add measured methods --- .../ffm/FFMNativeMethodDecorator.java | 5 ++- .../java-lang/java-lang-22.0/build.gradle | 2 +- .../test/groovy/FFMInstrumentationTest.groovy | 37 +++++++++++++++++++ .../datadog/trace/api/ConfigDefaults.java | 1 + .../config/TraceInstrumentationConfig.java | 1 + .../datadog/trace/api/InstrumenterConfig.java | 13 +++++++ metadata/supported-configurations.json | 8 ++++ 7 files changed, 65 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java index 5bfeefaf705..e2e0eb9261b 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java @@ -153,6 +153,9 @@ public static ContextScope startSpan(CharSequence resourceName, boolean methodMe AgentSpan span = AgentTracer.startSpan(TRACE_FFM.toString(), OPERATION_NAME); DECORATE.afterStart(span); span.setResourceName(resourceName); + if (methodMeasured) { + span.setMeasured(true); + } return AgentTracer.activateSpan(span); } @@ -183,7 +186,7 @@ public static boolean isMethodTraced(final String library, final String method) } public static boolean isMethodMeasured(final String library, final String method) { - return matches(InstrumenterConfig.get().getMeasureMethods().get(library), method); + return matches(InstrumenterConfig.get().getMeasureNativeMethods().get(library), method); } public static CharSequence resourceNameFor(final String library, final String method) { diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle index 78476a2dd9d..3fb856dd9c1 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/build.gradle @@ -4,7 +4,7 @@ plugins { muzzle { pass { - coreJdk('22') + coreJdk('25') } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy index 36472266d7e..ee96ae7add4 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy @@ -57,6 +57,43 @@ class FFMInstrumentationTest extends InstrumentationSpecification { "[*]" | true | "strlen" } + def "should measure methods for #configured"() { + setup: + injectSysConfig("trace.native.methods", "[strlen]") + injectSysConfig("measure.native.methods", configured) + final MemorySegment strlenAddr = Linker.nativeLinker().defaultLookup().findOrThrow("strlen") + final MethodHandle strlenHandle = + Linker.nativeLinker().downcallHandle( + strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) + + when: + long len + try (final Arena arena = Arena.ofConfined()) { + len = (long) strlenHandle.invokeWithArguments(arena.allocateFrom("Hello world!")) + } + + then: + len == 12 + assertTraces(1) { + trace(1) { + span { + operationName "trace.native" + resourceName "strlen" + measured expectMeasured + tags { + "$Tags.COMPONENT" "trace-ffm" + defaultTags() + } + } + } + } + + where: + configured | expectMeasured + "[strlen]" | true + "" | false + } + @IgnoreIf({ !os.isLinux() }) def "should trace ffm calls using libraryLookup by name for #configured"() { setup: diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 79bf74aa54b..31e5e36b9cf 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -238,6 +238,7 @@ public final class ConfigDefaults { static final String DEFAULT_TRACE_METHODS = null; static final String DEFAULT_TRACE_NATIVE_METHODS = null; static final String DEFAULT_MEASURE_METHODS = ""; + static final String DEFAULT_MEASURE_NATIVE_METHODS = ""; static final boolean DEFAULT_TRACE_ANALYTICS_ENABLED = false; static final float DEFAULT_ANALYTICS_SAMPLE_RATE = 1.0f; static final int DEFAULT_TRACE_RATE_LIMIT = 100; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java index b0c0ccc5c76..486d6398d17 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java @@ -29,6 +29,7 @@ public final class TraceInstrumentationConfig { https://docs.datadoghq.com/tracing/trace_collection/custom_instrumentation/java/ */ public static final String MEASURE_METHODS = "measure.methods"; + public static final String MEASURE_NATIVE_METHODS = "measure.native.methods"; public static final String TRACE_CLASSES_EXCLUDE = "trace.classes.exclude"; public static final String TRACE_CLASSES_EXCLUDE_FILE = "trace.classes.exclude.file"; public static final String TRACE_CLASSLOADERS_EXCLUDE = "trace.classloaders.exclude"; diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index 97df9cbb636..f16af0f0a18 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -10,6 +10,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_INTEGRATIONS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_LLM_OBS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_MEASURE_METHODS; +import static datadog.trace.api.ConfigDefaults.DEFAULT_MEASURE_NATIVE_METHODS; import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_RESOLVER_RESET_INTERVAL; import static datadog.trace.api.ConfigDefaults.DEFAULT_RUM_ENABLED; @@ -61,6 +62,7 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.JDBC_PREPARED_STATEMENT_CLASS_NAME; import static datadog.trace.api.config.TraceInstrumentationConfig.LEGACY_CONTEXT_MANAGER_ENABLED; import static datadog.trace.api.config.TraceInstrumentationConfig.MEASURE_METHODS; +import static datadog.trace.api.config.TraceInstrumentationConfig.MEASURE_NATIVE_METHODS; import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_CACHE_CONFIG; import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_CACHE_DIR; import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_NAMES_ARE_UNIQUE; @@ -204,6 +206,7 @@ public class InstrumenterConfig { private final Map> traceMethods; private final Map> traceNativeMethods; private final Map> measureMethods; + private final Map> measureNativeMethods; private final boolean internalExitOnFailure; @@ -352,6 +355,9 @@ private InstrumenterConfig() { measureMethods = MethodFilterConfigParser.parse( configProvider.getString(MEASURE_METHODS, DEFAULT_MEASURE_METHODS)); + measureNativeMethods = + MethodFilterConfigParser.parseForNativeMethods( + configProvider.getString(MEASURE_NATIVE_METHODS, DEFAULT_MEASURE_NATIVE_METHODS)); internalExitOnFailure = configProvider.getBoolean(INTERNAL_EXIT_ON_FAILURE, false); this.additionalJaxRsAnnotations = @@ -661,6 +667,10 @@ public Map> getMeasureMethods() { return measureMethods; } + public Map> getMeasureNativeMethods() { + return measureNativeMethods; + } + public boolean isMethodMeasured(Method method) { if (this.measureMethods.isEmpty()) { return false; @@ -812,6 +822,9 @@ public String toString() { + ", measureMethods= '" + measureMethods + '\'' + + ", measureNativeMethods= '" + + measureNativeMethods + + '\'' + ", internalExitOnFailure=" + internalExitOnFailure + ", additionalJaxRsAnnotations=" diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 6b3a1c4c9eb..5b7f7fde880 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -2233,6 +2233,14 @@ "aliases": [] } ], + "DD_MEASURE_NATIVE_METHODS": [ + { + "version": "A", + "type": "string", + "default": "", + "aliases": [] + } + ], "DD_MESSAGE_BROKER_SPLIT_BY_DESTINATION": [ { "version": "A", From 0a37077d28209bc9d372e77b56e196322ebfa016 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 4 Mar 2026 09:50:03 +0100 Subject: [PATCH 09/15] Improve the implementation --- .../ffm/FFMNativeMethodDecorator.java | 4 - .../ffm/NativeLibraryHelper.java | 26 ++++ .../bytebuddy/matcher/ignored_class_name.trie | 1 + .../java/lang/jdk22/FFMApiModule.java | 19 +-- .../lang/jdk22/LinkerInstrumentation.java | 3 - ...java => NativeLibraryInstrumentation.java} | 20 +-- .../SymbolLookupStaticInstrumentation.java | 31 ---- .../lang/jdk22/CaptureLibraryNameAdvice.java | 33 +++++ .../jdk22/CaptureSymbolAddressAdvice.java | 19 +++ .../java/lang/jdk22/DownCallWrapAdvice.java | 4 +- .../java/lang/jdk22/SymbolLookupAdvices.java | 65 --------- .../test/groovy/FFMInstrumentationTest.groovy | 134 ++++++++++-------- .../src/test/java/util/LoaderUtil.java | 20 +++ .../test/java/util/NativeLibraryResolver.java | 78 ---------- .../datadog/trace/api/InstrumenterConfig.java | 4 +- .../trace/api/MethodFilterConfigParser.java | 12 +- .../api/MethodFilterConfigParserTest.groovy | 21 --- 17 files changed, 198 insertions(+), 296 deletions(-) create mode 100644 dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java rename dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/{SymbolLookupInstrumentation.java => NativeLibraryInstrumentation.java} (54%) delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/LoaderUtil.java delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java index e2e0eb9261b..b7ce858c031 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/FFMNativeMethodDecorator.java @@ -171,7 +171,6 @@ public static Object endSpan(Throwable t, ContextScope scope, Object result) { DECORATE.onError(span, t); span.addThrowable(t); } - span.finish(); } } @@ -190,9 +189,6 @@ public static boolean isMethodMeasured(final String library, final String method } public static CharSequence resourceNameFor(final String library, final String method) { - if (library == null || library.isEmpty()) { - return UTF8BytesString.create(method); - } return UTF8BytesString.create(library + "." + method); } diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java new file mode 100644 index 00000000000..1adf22384fe --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java @@ -0,0 +1,26 @@ +package datadog.trace.bootstrap.instrumentation.ffm; + +import datadog.trace.api.Pair; +import java.util.concurrent.ConcurrentHashMap; + +public final class NativeLibraryHelper { + // this map is unlimited. However, the number of entries depends on the configured methods we want + // to trace. + private static final ConcurrentHashMap> SYMBOLS_MAP = + new ConcurrentHashMap<>(); + + private NativeLibraryHelper() {} + + public static void onSymbolLookup( + final String libraryName, final String symbol, final long address) { + if (libraryName != null && !libraryName.isEmpty()) { + if (FFMNativeMethodDecorator.isMethodTraced(libraryName, symbol)) { + SYMBOLS_MAP.put(address, Pair.of(libraryName, symbol)); + } + } + } + + public static Pair reverseResolveLibraryAndSymbol(long address) { + return SYMBOLS_MAP.get(address); + } +} diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index 7db619d7488..c2c958ed6c1 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -46,6 +46,7 @@ 0 java.lang.ClassLoader 0 java.lang.foreign.* 0 jdk.internal.foreign.* +0 jdk.internal.loader.* # allow exception profiling instrumentation 0 java.lang.Exception 0 java.lang.Error diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java index 467ca9e5cfb..85deb92f89c 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java @@ -6,8 +6,7 @@ import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.api.InstrumenterConfig; -import datadog.trace.api.Pair; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -18,23 +17,17 @@ public FFMApiModule() { } @Override - public Map contextStore() { - final Map ret = new HashMap<>(); - ret.put("java.lang.foreign.SymbolLookup", String.class.getName()); - ret.put("java.lang.foreign.MemorySegment", Pair.class.getName()); - return ret; + public boolean isEnabled() { + return super.isEnabled() && !InstrumenterConfig.get().getTraceNativeMethods().isEmpty(); } @Override - public boolean isEnabled() { - return super.isEnabled() && !InstrumenterConfig.get().getTraceNativeMethods().isEmpty(); + public Map contextStore() { + return Collections.singletonMap("jdk.internal.loader.NativeLibrary", "java.lang.String"); } @Override public List typeInstrumentations() { - return asList( - new LinkerInstrumentation(), - new SymbolLookupInstrumentation(), - new SymbolLookupStaticInstrumentation()); + return asList(new LinkerInstrumentation(), new NativeLibraryInstrumentation()); } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java index ec58693017a..4109d6f913f 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java @@ -25,9 +25,6 @@ public ElementMatcher hierarchyMatcher() { @Override public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod().and(named("defaultLookup")), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureDefaultLookup"); transformer.applyAdvice( isMethod() .and(named("downcallHandle")) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/NativeLibraryInstrumentation.java similarity index 54% rename from dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java rename to dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/NativeLibraryInstrumentation.java index bb83bdf1421..44b9bb56979 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/NativeLibraryInstrumentation.java @@ -1,18 +1,19 @@ package datadog.trace.instrumentation.java.lang.jdk22; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.returns; import datadog.trace.agent.tooling.Instrumenter; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -public class SymbolLookupInstrumentation +public class NativeLibraryInstrumentation implements Instrumenter.ForTypeHierarchy, - Instrumenter.ForBootstrap, - Instrumenter.HasMethodAdvice { + Instrumenter.HasMethodAdvice, + Instrumenter.ForBootstrap { @Override public String hierarchyMarkerType() { return null; // bootstrap type @@ -20,14 +21,15 @@ public String hierarchyMarkerType() { @Override public ElementMatcher hierarchyMatcher() { - // instrument both interface and sub-implementations - return implementsInterface(named("java.lang.foreign.SymbolLookup")); + return extendsClass(named("jdk.internal.loader.NativeLibrary")); } @Override public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice( - isMethod().and(named("find").and(takesArgument(0, named("java.lang.String")))), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureMemorySegment"); + isConstructor(), "datadog.trace.instrumentation.java.lang.jdk22.CaptureLibraryNameAdvice"); + transformer.applyAdvice( + isMethod().and(named("find")).and(returns(long.class)), + "datadog.trace.instrumentation.java.lang.jdk22.CaptureSymbolAddressAdvice"); } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java deleted file mode 100644 index 7a260dc0fca..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupStaticInstrumentation.java +++ /dev/null @@ -1,31 +0,0 @@ -package datadog.trace.instrumentation.java.lang.jdk22; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isStatic; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; - -import datadog.trace.agent.tooling.Instrumenter; - -public class SymbolLookupStaticInstrumentation - implements Instrumenter.ForSingleType, Instrumenter.ForBootstrap, Instrumenter.HasMethodAdvice { - - @Override - public String instrumentedType() { - return "java.lang.foreign.SymbolLookup"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod() - .and(isStatic()) - .and(named("libraryLookup").and(takesArgument(0, named("java.lang.String")))), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryName"); - transformer.applyAdvice( - isMethod() - .and(isStatic()) - .and(named("libraryLookup").and(takesArgument(0, named("java.nio.Path")))), - "datadog.trace.instrumentation.java.lang.jdk22.SymbolLookupAdvices$CaptureLibraryPath"); - } -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java new file mode 100644 index 00000000000..708d89bafd4 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java @@ -0,0 +1,33 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import datadog.trace.bootstrap.InstrumentationContext; +import java.io.File; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Locale; +import net.bytebuddy.asm.Advice; + +public class CaptureLibraryNameAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This final Object self) { + // this module is not opened by default hence we are inlining this code into the target to + // circumvent this limitation + try { + final MethodHandle mh = + MethodHandles.lookup() + .findVirtual(self.getClass(), "name", MethodType.methodType(String.class)); + String libraryName = (String) mh.invoke(self); + if (libraryName != null) { + libraryName = new File(libraryName).getName().toLowerCase(Locale.ROOT); + int dot = libraryName.lastIndexOf('.'); + libraryName = (dot > 0) ? libraryName.substring(0, dot) : libraryName; + } else { + libraryName = ""; + } + InstrumentationContext.get("jdk.internal.loader.NativeLibrary", "java.lang.String") + .put(self, libraryName); + } catch (Throwable ignored) { + } + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java new file mode 100644 index 00000000000..550cea252a8 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java @@ -0,0 +1,19 @@ +package datadog.trace.instrumentation.java.lang.jdk22; + +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper; +import net.bytebuddy.asm.Advice; + +public class CaptureSymbolAddressAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) final String symbol, + @Advice.This final Object self, + @Advice.Return final long address) { + final String libraryName = + (String) + InstrumentationContext.get("jdk.internal.loader.NativeLibrary", "java.lang.String") + .get(self); + NativeLibraryHelper.onSymbolLookup(libraryName, symbol, address); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java index 12227c2c904..ba0d31c239c 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java @@ -3,7 +3,7 @@ import static datadog.trace.bootstrap.instrumentation.ffm.FFMNativeMethodDecorator.wrap; import datadog.trace.api.Pair; -import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper; import java.lang.foreign.MemorySegment; import java.lang.invoke.MethodHandle; import net.bytebuddy.asm.Advice; @@ -17,7 +17,7 @@ public static void onExit( return; } final Pair libAndMethod = - InstrumentationContext.get(MemorySegment.class, Pair.class).get(memorySegment); + NativeLibraryHelper.reverseResolveLibraryAndSymbol(memorySegment.address()); if (libAndMethod == null) { return; } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java deleted file mode 100644 index c40aa2daa20..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/SymbolLookupAdvices.java +++ /dev/null @@ -1,65 +0,0 @@ -package datadog.trace.instrumentation.java.lang.jdk22; - -import static datadog.trace.bootstrap.instrumentation.ffm.FFMNativeMethodDecorator.isMethodTraced; - -import datadog.trace.api.Pair; -import datadog.trace.bootstrap.InstrumentationContext; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SymbolLookup; -import java.nio.file.Path; -import java.util.Locale; -import java.util.Optional; -import net.bytebuddy.asm.Advice; - -public class SymbolLookupAdvices { - public static class CaptureDefaultLookup { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit(@Advice.Return final SymbolLookup symbolLookup) { - if (symbolLookup != null) { - InstrumentationContext.get(SymbolLookup.class, String.class).put(symbolLookup, ""); - } - } - } - - public static class CaptureLibraryName { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return final SymbolLookup symbolLookup, - @Advice.Argument(0) final String libraryName) { - if (symbolLookup != null && libraryName != null) { - InstrumentationContext.get(SymbolLookup.class, String.class) - .put(symbolLookup, libraryName.toLowerCase(Locale.ROOT)); - } - } - } - - public static class CaptureLibraryPath { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return final SymbolLookup symbolLookup, - @Advice.Argument(0) final Path libraryPath) { - if (symbolLookup != null && libraryPath != null) { - InstrumentationContext.get(SymbolLookup.class, String.class) - .put(symbolLookup, libraryPath.getFileName().toString().toLowerCase(Locale.ROOT)); - } - } - } - - public static class CaptureMemorySegment { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This final SymbolLookup self, - @Advice.Argument(0) final String name, - @Advice.Return final Optional maybeSegment) { - if (name != null && maybeSegment != null && maybeSegment.isPresent()) { - final String libName = - InstrumentationContext.get(SymbolLookup.class, String.class).get(self); - if (libName != null) { - if (isMethodTraced(libName, name)) - InstrumentationContext.get(MemorySegment.class, Pair.class) - .put(maybeSegment.get(), Pair.of(libName, name)); - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy index ee96ae7add4..1a9870df166 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy @@ -8,8 +8,7 @@ import java.lang.foreign.SymbolLookup import java.lang.foreign.ValueLayout import java.lang.invoke.MethodHandle import java.nio.file.Path -import spock.lang.IgnoreIf -import util.NativeLibraryResolver +import util.LoaderUtil class FFMInstrumentationTest extends InstrumentationSpecification { @Override @@ -40,7 +39,7 @@ class FFMInstrumentationTest extends InstrumentationSpecification { trace(1) { span { operationName "trace.native" - resourceName "$resource" + resourceName "libsyslookup.strlen" tags { "$Tags.COMPONENT" "trace-ffm" defaultTags() @@ -51,15 +50,15 @@ class FFMInstrumentationTest extends InstrumentationSpecification { } where: - configured | traceExpected | resource - "" | false | _ - "[strlen]" | true | "strlen" - "[*]" | true | "strlen" + configured | traceExpected + "" | false + "libsyslookup[strlen]" | true + "libsyslookup[*]" | true } def "should measure methods for #configured"() { setup: - injectSysConfig("trace.native.methods", "[strlen]") + injectSysConfig("trace.native.methods", "libsyslookup[strlen]") injectSysConfig("measure.native.methods", configured) final MemorySegment strlenAddr = Linker.nativeLinker().defaultLookup().findOrThrow("strlen") final MethodHandle strlenHandle = @@ -78,7 +77,7 @@ class FFMInstrumentationTest extends InstrumentationSpecification { trace(1) { span { operationName "trace.native" - resourceName "strlen" + resourceName "libsyslookup.strlen" measured expectMeasured tags { "$Tags.COMPONENT" "trace-ffm" @@ -89,35 +88,37 @@ class FFMInstrumentationTest extends InstrumentationSpecification { } where: - configured | expectMeasured - "[strlen]" | true - "" | false + configured | expectMeasured + "libsyslookup[strlen]" | true + "" | false } - @IgnoreIf({ !os.isLinux() }) - def "should trace ffm calls using libraryLookup by name for #configured"() { + def "should trace ffm calls using libraryLookup of jdk library for #configured"() { setup: + // libzip ships with every JDK; System.mapLibraryName gives the correct platform filename + final String libName = System.mapLibraryName("zip") // "libzip.so" on Linux, "libzip.dylib" on macOS + final Path libzipPath = Path.of(System.getProperty("java.home"), "lib", libName) injectSysConfig("trace.native.methods", configured) when: - long len try (final Arena arena = Arena.ofConfined()) { - final SymbolLookup libLookup = SymbolLookup.libraryLookup("c", arena) - final MemorySegment strlenAddr = libLookup.findOrThrow("strlen") - final MethodHandle strlenHandle = + final SymbolLookup libLookup = SymbolLookup.libraryLookup(libzipPath, arena) + final MemorySegment zipOpenAddr = libLookup.findOrThrow("ZIP_Open") + final MethodHandle zipOpenHandle = Linker.nativeLinker().downcallHandle( - strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) - len = (long) strlenHandle.invokeWithArguments(arena.allocateFrom("Hello world!")) + zipOpenAddr, + FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)) + // open a nonexistent path — ZIP_Open guards on pmsg before deref, returns NULL safely + zipOpenHandle.invokeWithArguments(arena.allocateFrom("/nonexistent.zip"), MemorySegment.NULL) } then: - len == 12 assertTraces(traceExpected ? 1 : 0) { if (traceExpected) { trace(1) { span { operationName "trace.native" - resourceName "$resource" + resourceName "libzip.ZIP_Open" tags { "$Tags.COMPONENT" "trace-ffm" defaultTags() @@ -128,54 +129,71 @@ class FFMInstrumentationTest extends InstrumentationSpecification { } where: - configured | traceExpected | resource - "" | false | _ - "c[strlen]" | true | "c.strlen" - "c[*]" | true | "c.strlen" - "unknown_lib[*]" | false | _ + configured | traceExpected + "libzip[ZIP_Open]" | true + "libzip[*]" | true } - @IgnoreIf({ !os.isLinux() }) - def "should trace ffm calls using libraryLookup by path for #configTemplate"() { + def "should trace ffm calls using libraryLookup by path for library loaded with System.load"() { setup: - final MemorySegment strlenSym = Linker.nativeLinker().defaultLookup().findOrThrow("strlen") - final String libPath = NativeLibraryResolver.findLibraryPath(strlenSym) - final String libFileName = Path.of(libPath).getFileName().toString().toLowerCase(Locale.ROOT) - injectSysConfig("trace.native.methods", configTemplate.replace("{lib}", libFileName)) + injectSysConfig("trace.native.methods", "libjvm[*]") + final String libName = System.mapLibraryName("jvm") + final Path libPath = Path.of(System.getProperty("java.home"), "lib", "server", libName) when: - long len + LoaderUtil.loadLibrary(libPath) try (final Arena arena = Arena.ofConfined()) { - final SymbolLookup libLookup = SymbolLookup.libraryLookup(Path.of(libPath), arena) - final MemorySegment strlenAddr = libLookup.findOrThrow("strlen") - final MethodHandle strlenHandle = - Linker.nativeLinker().downcallHandle( - strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)) - len = (long) strlenHandle.invokeWithArguments(arena.allocateFrom("Hello world!")) + final SymbolLookup libLookup = LoaderUtil.loaderLookup() + FunctionDescriptor fd = FunctionDescriptor.of( + ValueLayout.JAVA_INT, // jint return + ValueLayout.ADDRESS, // JavaVM** + ValueLayout.JAVA_INT, // jsize bufLen + ValueLayout.ADDRESS // jsize* nVMs + ) + + // this is a quite stable symbol (it's in the public API) + MemorySegment sym = + libLookup.find("JNI_GetCreatedJavaVMs") + .orElseThrow() + + MethodHandle methodHandle = Linker.nativeLinker().downcallHandle(sym, fd) + + // Allocate space for JavaVM* (we only expect 1 VM) + MemorySegment vmBuf = arena.allocate(ValueLayout.ADDRESS) + + // Allocate space for jsize nVMs + MemorySegment nVMs = arena.allocate(ValueLayout.JAVA_INT) + + int result = (int) methodHandle.invokeWithArguments( + vmBuf, + 1, + nVMs + ) + + int count = nVMs.get(ValueLayout.JAVA_INT, 0) + + System.out.println("Return code: " + result) + System.out.println("Number of VMs: " + count) + + if (count > 0) { + MemorySegment vmPtr = vmBuf.get(ValueLayout.ADDRESS, 0) + System.out.println("JavaVM pointer: " + vmPtr) + } } + then: - len == 12 - assertTraces(traceExpected ? 1 : 0) { - if (traceExpected) { - trace(1) { - span { - operationName "trace.native" - resourceName "${libFileName}.strlen" - tags { - "$Tags.COMPONENT" "trace-ffm" - defaultTags() - } + assertTraces(1) { + trace(1) { + span { + operationName "trace.native" + resourceName "libjvm.JNI_GetCreatedJavaVMs" + tags { + "$Tags.COMPONENT" "trace-ffm" + defaultTags() } } } } - - where: - configTemplate | traceExpected - "" | false - "{lib}[strlen]" | true - "{lib}[*]" | true - "unknown_lib[*]" | false } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/LoaderUtil.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/LoaderUtil.java new file mode 100644 index 00000000000..d3eac010ea9 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/LoaderUtil.java @@ -0,0 +1,20 @@ +package util; + +import java.lang.foreign.SymbolLookup; +import java.nio.file.Path; + +/** + * Groovy is doing odd things and System.load needs to have a stable caller to pin to the right + * classloader + */ +public final class LoaderUtil { + private LoaderUtil() {} + + public static void loadLibrary(Path path) { + System.load(path.toString()); + } + + public static SymbolLookup loaderLookup() { + return SymbolLookup.loaderLookup(); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java deleted file mode 100644 index a7a44bb4738..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/java/util/NativeLibraryResolver.java +++ /dev/null @@ -1,78 +0,0 @@ -package util; - -import java.lang.foreign.Arena; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.Linker; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SymbolLookup; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; -import java.nio.charset.StandardCharsets; - -public class NativeLibraryResolver { - private static final Linker LINKER = Linker.nativeLinker(); - private static final SymbolLookup LOOKUP = LINKER.defaultLookup(); - - // dladdr symbol - private static final MethodHandle DLADDR; - - // struct Dl_info - private static final GroupLayout DL_INFO_LAYOUT = - MemoryLayout.structLayout( - ValueLayout.ADDRESS.withName("dli_fname"), - ValueLayout.ADDRESS.withName("dli_fbase"), - ValueLayout.ADDRESS.withName("dli_sname"), - ValueLayout.ADDRESS.withName("dli_saddr")); - - private static final long DLI_FNAME_OFFSET = - DL_INFO_LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("dli_fname")); - - static { - try { - MemorySegment dladdrSymbol = LOOKUP.find("dladdr").orElseThrow(); - - DLADDR = - LINKER.downcallHandle( - dladdrSymbol, - FunctionDescriptor.of( - ValueLayout.JAVA_INT, // int return - ValueLayout.ADDRESS, // const void* addr - ValueLayout.ADDRESS // Dl_info* info - )); - } catch (Throwable t) { - throw new ExceptionInInitializerError(t); - } - } - - /** - * Returns the full native library path that defines the given symbol. Returns null if the symbol - * cannot be resolved. - * - * @param symbolAddress the address of the symbol to resolve. - * @return the library path or null if cannot be found. - */ - public static String findLibraryPath(MemorySegment symbolAddress) { - try (Arena arena = Arena.ofConfined()) { - - MemorySegment info = arena.allocate(DL_INFO_LAYOUT); - - int result = (int) DLADDR.invoke(symbolAddress, info); - if (result == 0) { - return null; // not found - } - - MemorySegment fnamePtr = info.get(ValueLayout.ADDRESS, DLI_FNAME_OFFSET); - - if (fnamePtr == MemorySegment.NULL) { - return null; - } - - return fnamePtr.getString(0, StandardCharsets.UTF_8); - - } catch (Throwable t) { - return null; - } - } -} diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index f16af0f0a18..4fbf54eed7d 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -350,13 +350,13 @@ private InstrumenterConfig() { MethodFilterConfigParser.parse( configProvider.getString(TRACE_METHODS, DEFAULT_TRACE_METHODS)); traceNativeMethods = - MethodFilterConfigParser.parseForNativeMethods( + MethodFilterConfigParser.parse( configProvider.getString(TRACE_NATIVE_METHODS, DEFAULT_TRACE_NATIVE_METHODS)); measureMethods = MethodFilterConfigParser.parse( configProvider.getString(MEASURE_METHODS, DEFAULT_MEASURE_METHODS)); measureNativeMethods = - MethodFilterConfigParser.parseForNativeMethods( + MethodFilterConfigParser.parse( configProvider.getString(MEASURE_NATIVE_METHODS, DEFAULT_MEASURE_NATIVE_METHODS)); internalExitOnFailure = configProvider.getBoolean(INTERNAL_EXIT_ON_FAILURE, false); diff --git a/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java b/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java index d24b4e0aa2c..00e55e4f2ff 100644 --- a/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java +++ b/internal-api/src/main/java/datadog/trace/api/MethodFilterConfigParser.java @@ -41,16 +41,8 @@ private static boolean isIllegalMethodName(String string) { return !string.equals("*") && hasIllegalCharacters(string); } - public static Map> parse(String configString) { - return parse(configString, false); - } - - public static Map> parseForNativeMethods(String configString) { - return parse(configString, true); - } - @SuppressForbidden - private static Map> parse(String configString, boolean allowEmptyClassNames) { + public static Map> parse(String configString) { Map> classMethodsToTrace; if (configString == null || configString.trim().isEmpty()) { classMethodsToTrace = Collections.emptyMap(); @@ -85,7 +77,7 @@ private static Map> parse(String configString, boolean allow break; } String className = configString.substring(start, methodsStart).trim(); - if ((className.isEmpty() && !allowEmptyClassNames) || isIllegalClassName(className)) { + if (className.isEmpty() || isIllegalClassName(className)) { toTrace = logWarn("with illegal class name", start, end, configString); break; } diff --git a/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy index f9f71d8203f..29d8adb4c3b 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/MethodFilterConfigParserTest.groovy @@ -46,27 +46,6 @@ class MethodFilterConfigParserTest extends DDSpecification { "asdfg;c1[m1]" | [:] } - def "test native method configuration \"#value\""() { - setup: - if (value) { - injectSysConfig("dd.trace.native.methods", value) - } else { - removeSysConfig("dd.trace.methods") - } - - expect: - InstrumenterConfig.get().traceNativeMethods == expected - - where: - value | expected - null | [:] - " " | [:] - "*" | [:] - "[ method , ]" | ["": ["method"].toSet()] - "lib[ method1 , method2]" | ["lib": ["method1", "method2"].toSet()] - "[*]" | ["": ["*"].toSet()] - } - def "survive very long list"() { setup: def mset = ["m1", "m2"].toSet() From 6460d0319f26a3fcd96e2bf88a56610d99c08683 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 4 Mar 2026 13:06:25 +0100 Subject: [PATCH 10/15] refactorise some code --- .../instrumentation/ffm/NativeLibraryHelper.java | 9 +++++++++ .../java/lang/jdk22/CaptureLibraryNameAdvice.java | 8 +++----- .../java/lang/jdk22/CaptureSymbolAddressAdvice.java | 5 +++-- .../java/lang/jdk22/DownCallWrapAdvice.java | 4 ++-- ...onTest.groovy => FFMInstrumentationForkedTest.groovy} | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) rename dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/{FFMInstrumentationTest.groovy => FFMInstrumentationForkedTest.groovy} (98%) diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java index 1adf22384fe..15033da489b 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/ffm/NativeLibraryHelper.java @@ -1,6 +1,8 @@ package datadog.trace.bootstrap.instrumentation.ffm; import datadog.trace.api.Pair; +import java.io.File; +import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; public final class NativeLibraryHelper { @@ -23,4 +25,11 @@ public static void onSymbolLookup( public static Pair reverseResolveLibraryAndSymbol(long address) { return SYMBOLS_MAP.get(address); } + + public static String extractLibraryName(String fullPath) { + String libraryName = new File(fullPath).getName().toLowerCase(Locale.ROOT); + int dot = libraryName.lastIndexOf('.'); + libraryName = (dot > 0) ? libraryName.substring(0, dot) : libraryName; + return libraryName; + } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java index 708d89bafd4..c718b9acd28 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureLibraryNameAdvice.java @@ -1,11 +1,11 @@ package datadog.trace.instrumentation.java.lang.jdk22; +import static datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper.extractLibraryName; + import datadog.trace.bootstrap.InstrumentationContext; -import java.io.File; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.Locale; import net.bytebuddy.asm.Advice; public class CaptureLibraryNameAdvice { @@ -19,9 +19,7 @@ public static void onExit(@Advice.This final Object self) { .findVirtual(self.getClass(), "name", MethodType.methodType(String.class)); String libraryName = (String) mh.invoke(self); if (libraryName != null) { - libraryName = new File(libraryName).getName().toLowerCase(Locale.ROOT); - int dot = libraryName.lastIndexOf('.'); - libraryName = (dot > 0) ? libraryName.substring(0, dot) : libraryName; + libraryName = extractLibraryName(libraryName); } else { libraryName = ""; } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java index 550cea252a8..236a49a82b9 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/CaptureSymbolAddressAdvice.java @@ -1,7 +1,8 @@ package datadog.trace.instrumentation.java.lang.jdk22; +import static datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper.onSymbolLookup; + import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper; import net.bytebuddy.asm.Advice; public class CaptureSymbolAddressAdvice { @@ -14,6 +15,6 @@ public static void onExit( (String) InstrumentationContext.get("jdk.internal.loader.NativeLibrary", "java.lang.String") .get(self); - NativeLibraryHelper.onSymbolLookup(libraryName, symbol, address); + onSymbolLookup(libraryName, symbol, address); } } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java index ba0d31c239c..960f6a46212 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java25/datadog/trace/instrumentation/java/lang/jdk22/DownCallWrapAdvice.java @@ -1,9 +1,9 @@ package datadog.trace.instrumentation.java.lang.jdk22; import static datadog.trace.bootstrap.instrumentation.ffm.FFMNativeMethodDecorator.wrap; +import static datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper.reverseResolveLibraryAndSymbol; import datadog.trace.api.Pair; -import datadog.trace.bootstrap.instrumentation.ffm.NativeLibraryHelper; import java.lang.foreign.MemorySegment; import java.lang.invoke.MethodHandle; import net.bytebuddy.asm.Advice; @@ -17,7 +17,7 @@ public static void onExit( return; } final Pair libAndMethod = - NativeLibraryHelper.reverseResolveLibraryAndSymbol(memorySegment.address()); + reverseResolveLibraryAndSymbol(memorySegment.address()); if (libAndMethod == null) { return; } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationForkedTest.groovy similarity index 98% rename from dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy rename to dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationForkedTest.groovy index 1a9870df166..66d73cbe68f 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/test/groovy/FFMInstrumentationForkedTest.groovy @@ -10,7 +10,7 @@ import java.lang.invoke.MethodHandle import java.nio.file.Path import util.LoaderUtil -class FFMInstrumentationTest extends InstrumentationSpecification { +class FFMInstrumentationForkedTest extends InstrumentationSpecification { @Override protected void configurePreAgent() { super.configurePreAgent() From 5babc612e8c07edcf34beb86f6be9be9e42ce173 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 4 Mar 2026 13:21:33 +0100 Subject: [PATCH 11/15] leftover --- .../tracing/trace-annotation/build.gradle | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle b/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle index f4626d8dcb3..7066c6cc174 100644 --- a/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle +++ b/dd-java-agent/instrumentation/datadog/tracing/trace-annotation/build.gradle @@ -1,36 +1,8 @@ apply from: "$rootDir/gradle/java.gradle" -sourceSets { - register("main_java25") { - java { - srcDirs = [file('src/main/java25')] - } - } - named("main_java25") { - compileClasspath += sourceSets.main.output - runtimeClasspath += sourceSets.main.output - } - named("test") { - compileClasspath += sourceSets.main_java25.output - runtimeClasspath += sourceSets.main_java25.output - } -} - dependencies { implementation project(':dd-java-agent:agent-debugger:debugger-bootstrap') testImplementation group: 'com.newrelic.agent.java', name: 'newrelic-api', version: '6.+' } - -tasks.named("jar") { - from(sourceSets.main_java25.output) -} - -tasks.named("compileMain_java25Java") { - configureCompiler( - it, - 25, - JavaVersion.VERSION_1_8, - "Java 25 sourceset for FFM native tracing advice.") -} From c3eda9755779368a66563c8bad0e2bdcad4cc008 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Mon, 16 Mar 2026 10:48:44 +0100 Subject: [PATCH 12/15] Narrow allowed packages to reduce startup time --- .../bytebuddy/matcher/ignored_class_name.trie | 3 +-- .../java/lang/jdk22/LinkerInstrumentation.java | 16 +++------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index c2c958ed6c1..034ea48a1b7 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -44,8 +44,7 @@ 1 io.opentelemetry.javaagent.* 1 java.* 0 java.lang.ClassLoader -0 java.lang.foreign.* -0 jdk.internal.foreign.* +0 jdk.internal.foreign.abi.AbstractLinker 0 jdk.internal.loader.* # allow exception profiling instrumentation 0 java.lang.Exception diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java index 4109d6f913f..0d607adc4c4 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/LinkerInstrumentation.java @@ -1,26 +1,16 @@ package datadog.trace.instrumentation.java.lang.jdk22; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import datadog.trace.agent.tooling.Instrumenter; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; public class LinkerInstrumentation - implements Instrumenter.ForTypeHierarchy, - Instrumenter.ForBootstrap, - Instrumenter.HasMethodAdvice { + implements Instrumenter.ForSingleType, Instrumenter.ForBootstrap, Instrumenter.HasMethodAdvice { @Override - public String hierarchyMarkerType() { - return null; // bootstrap type - } - - @Override - public ElementMatcher hierarchyMatcher() { - return hasInterface(named("java.lang.foreign.Linker")); + public String instrumentedType() { + return "jdk.internal.foreign.abi.AbstractLinker"; } @Override From b30744c956fc551a4d13170093387a8e74d377f0 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 26 Mar 2026 15:57:23 +0100 Subject: [PATCH 13/15] Restrict allowed packages to jdk.internal.loader.NativeLibraries --- .../agent/tooling/bytebuddy/matcher/ignored_class_name.trie | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index 034ea48a1b7..3276f1081a7 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -45,7 +45,7 @@ 1 java.* 0 java.lang.ClassLoader 0 jdk.internal.foreign.abi.AbstractLinker -0 jdk.internal.loader.* +0 jdk.internal.loader.NativeLibraries$* # allow exception profiling instrumentation 0 java.lang.Exception 0 java.lang.Error From bfb885ab24eaa3b9be6c80231a86f4eda2490dc9 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 26 Mar 2026 17:41:54 +0100 Subject: [PATCH 14/15] better trie --- .../agent/tooling/bytebuddy/matcher/ignored_class_name.trie | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index 3276f1081a7..daae12566f1 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -45,6 +45,8 @@ 1 java.* 0 java.lang.ClassLoader 0 jdk.internal.foreign.abi.AbstractLinker +0 jdk.internal.loader.NativeLibrary +0 jdk.internal.loader.RawNativeLibraries$* 0 jdk.internal.loader.NativeLibraries$* # allow exception profiling instrumentation 0 java.lang.Exception From b36dc8a81222844bb154b4dfa9035007f0668321 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Mon, 30 Mar 2026 14:18:22 +0200 Subject: [PATCH 15/15] Better instrumentation name --- .../trace/instrumentation/java/lang/jdk22/FFMApiModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java index 85deb92f89c..f49f343a555 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-22.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk22/FFMApiModule.java @@ -13,7 +13,7 @@ @AutoService(InstrumenterModule.class) public class FFMApiModule extends InstrumenterModule.Tracing { public FFMApiModule() { - super("java-lang-22"); + super("ffm-native-tracing", "java-lang-22"); } @Override