From 7228babbc8804477e3f049344276e23a2c169764 Mon Sep 17 00:00:00 2001 From: Jordan Wong Date: Wed, 2 Sep 2026 15:53:23 -0400 Subject: [PATCH 1/3] workflow(grpc): blind_setup --- .../instrumentation/grpc-1.5/build.gradle | 52 -- .../instrumentation/grpc-1.5/gradle.lockfile | 181 ----- .../grpc/QueuedCommandInstrumentation.java | 92 --- .../AbstractClientStreamInstrumentation.java | 54 -- .../client/ClientCallImplInstrumentation.java | 183 ----- ...ientStreamListenerImplInstrumentation.java | 117 --- .../grpc/client/GrpcClientDecorator.java | 131 --- .../grpc/client/GrpcClientModule.java | 47 -- .../grpc/client/GrpcInjectAdapter.java | 28 - .../MessagesAvailableInstrumentation.java | 75 -- .../grpc/server/GrpcExtractAdapter.java | 27 - .../GrpcServerBuilderInstrumentation.java | 80 -- .../grpc/server/GrpcServerDecorator.java | 125 --- .../grpc/server/GrpcServerModule.java | 49 -- .../server/MethodHandlersInstrumentation.java | 73 -- .../grpc/server/TracingServerInterceptor.java | 369 --------- .../test/groovy/GrpcInjectAdapterTest.groovy | 47 -- .../src/test/groovy/GrpcStreamingTest.groovy | 288 ------- .../grpc-1.5/src/test/groovy/GrpcTest.groovy | 745 ------------------ .../HierarchyMatcherGrpcStreamingTest.groovy | 22 - .../groovy/HierarchyMatcherGrpcTest.groovy | 22 - .../groovy/UnwrapGrpcContextForkedTest.groovy | 26 - .../grpc-1.5/src/test/proto/helloworld.proto | 25 - 23 files changed, 2858 deletions(-) delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/build.gradle delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/gradle.lockfile delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcInjectAdapterTest.groovy delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcStreamingTest.groovy delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcStreamingTest.groovy delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcTest.groovy delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/UnwrapGrpcContextForkedTest.groovy delete mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto diff --git a/dd-java-agent/instrumentation/grpc-1.5/build.gradle b/dd-java-agent/instrumentation/grpc-1.5/build.gradle deleted file mode 100644 index c3f3600f9c4..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/build.gradle +++ /dev/null @@ -1,52 +0,0 @@ -plugins { - id 'com.google.protobuf' version '0.10.0' - id 'dd-trace-java.module.instrumentation' - id 'idea' -} - -muzzle { - pass { - group = "io.grpc" - module = "grpc-core" - versions = "[1.5.0,)" - } -} - -// First version with Mac M1 support -def grpcVersion = '1.42.2' -protobuf { - protoc { - // Download compiler rather than using locally installed version: - // First version with Mac M1 support - artifact = 'com.google.protobuf:protoc:3.17.3' - } - plugins { - // First version with aarch support - grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } - } - generateProtoTasks { - ofSourceSet("test").configureEach { - plugins { grpc {} } - } - } -} - -addTestSuiteForDir('latestDepTest', 'test') - -dependencies { - compileOnly group: 'io.grpc', name: 'grpc-core', version: grpcVersion - - testImplementation group: 'io.grpc', name: 'grpc-netty', version: grpcVersion - testImplementation group: 'io.grpc', name: 'grpc-protobuf', version: grpcVersion - testImplementation group: 'io.grpc', name: 'grpc-stub', version: grpcVersion - testImplementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2' - testImplementation project(':dd-java-agent:agent-debugger:debugger-bootstrap') - - latestDepTestImplementation sourceSets.test.output // include the protobuf generated classes - latestDepTestCompileOnly group: 'io.grpc', name: 'grpc-core', version: '1.+' - - latestDepTestImplementation group: 'io.grpc', name: 'grpc-inprocess', version: '1.+' - latestDepTestImplementation group: 'io.grpc', name: 'grpc-netty', version: '1.+' - latestDepTestImplementation group: 'io.grpc', name: 'grpc-protobuf', version: '1.+' - latestDepTestImplementation group: 'io.grpc', name: 'grpc-stub', version: '1.+' -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/gradle.lockfile b/dd-java-agent/instrumentation/grpc-1.5/gradle.lockfile deleted file mode 100644 index 2e29e9b84cf..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/gradle.lockfile +++ /dev/null @@ -1,181 +0,0 @@ -# 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. -# To regenerate this file, run: ./gradlew :dd-java-agent:instrumentation:grpc-1.5:dependencies --write-locks -cafe.cryptography:curve25519-elisabeth:0.1.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -cafe.cryptography:ed25519-elisabeth:0.1.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -ch.qos.logback:logback-classic:1.2.13=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -ch.qos.logback:logback-core:1.2.13=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.datadoghq.okhttp3:okhttp:3.12.15=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.datadoghq.okio:okio:1.17.6=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.4=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.datadoghq:java-dogstatsd-client:4.4.5=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.datadoghq:sketches-java:0.8.3=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.javaparser:javaparser-core:3.25.6=codenarc -com.github.jnr:jffi:1.3.15=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-a64asm:1.0.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-constants:0.10.4=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-enxio:0.32.20=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-ffi:2.2.19=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-posix:3.1.22=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-unixsocket:0.38.25=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.jnr:jnr-x86asm:1.0.2=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.github.spotbugs:spotbugs-annotations:4.10.3=compileClasspath,compileProtoPath,spotbugs -com.github.spotbugs:spotbugs:4.10.3=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.android:annotations:4.1.1.4=compileProtoPath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.api.grpc:proto-google-common-protos:2.0.1=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.api.grpc:proto-google-common-protos:2.64.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,compileProtoPath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,testAnnotationProcessor,testCompileClasspath,testCompileProtoPath -com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,compileProtoPath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.code.gson:gson:2.14.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,spotbugs -com.google.code.gson:gson:2.8.6=compileProtoPath,testCompileProtoPath,testRuntimeClasspath -com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.47.0=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.errorprone:error_prone_annotations:2.48.0=spotbugs -com.google.errorprone:error_prone_annotations:2.50.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -com.google.errorprone:error_prone_annotations:2.9.0=compileProtoPath -com.google.guava:failureaccess:1.0.1=annotationProcessor,compileProtoPath,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.guava:failureaccess:1.0.3=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.guava:guava:30.1.1-android=compileProtoPath -com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.guava:guava:33.6.0-jre=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,compileProtoPath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.j2objc:j2objc-annotations:1.3=compileProtoPath -com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.j2objc:j2objc-annotations:3.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.protobuf:protobuf-java:3.18.2=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.google.protobuf:protobuf-java:3.25.9=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -com.google.protobuf:protoc:3.17.3=protobufToolsLocator_protoc -com.google.re2j:re2j:1.8=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -com.squareup.moshi:moshi:1.11.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.squareup.okhttp3:okhttp:3.12.12=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.squareup.okio:okio:1.17.5=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -com.thoughtworks.qdox:qdox:1.12.1=codenarc -commons-fileupload:commons-fileupload:1.5=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -commons-io:commons-io:2.11.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -commons-io:commons-io:2.21.0=spotbugs -de.thetaphi:forbiddenapis:3.10=compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-api:1.42.2=compileClasspath,compileProtoPath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-api:1.83.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-context:1.42.2=compileClasspath,compileProtoPath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-context:1.83.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-core:1.42.2=compileClasspath,compileProtoPath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-core:1.83.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-inprocess:1.83.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-netty:1.42.2=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-netty:1.83.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-protobuf-lite:1.42.2=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-protobuf-lite:1.83.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-protobuf:1.42.2=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-protobuf:1.83.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-stub:1.42.2=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.grpc:grpc-stub:1.83.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:grpc-util:1.83.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.grpc:protoc-gen-grpc-java:1.42.2=protobufToolsLocator_grpc -io.leangen.geantyref:geantyref:1.3.16=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-buffer:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-buffer:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-codec-base:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-codec-compression:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-codec-http2:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-codec-http2:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-codec-http:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-codec-http:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-codec-socks:4.1.63.Final=testCompileProtoPath,testRuntimeClasspath -io.netty:netty-codec-socks:4.2.15.Final=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-codec:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-common:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-common:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-handler-proxy:4.1.63.Final=testCompileProtoPath,testRuntimeClasspath -io.netty:netty-handler-proxy:4.2.15.Final=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-handler:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-handler:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-resolver:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-resolver:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.netty:netty-transport:4.1.63.Final=testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -io.netty:netty-transport:4.2.15.Final=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.perfmark:perfmark-api:0.23.0=compileProtoPath,testCompileProtoPath,testRuntimeClasspath -io.perfmark:perfmark-api:0.27.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -io.sqreen:libsqreen:17.5.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -javax.annotation:javax.annotation-api:1.3.2=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -javax.servlet:javax.servlet-api:3.1.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -jaxen:jaxen:2.0.6=spotbugs -junit:junit:4.13.2=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.10=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.10=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -net.java.dev.jna:jna-platform:5.8.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -net.java.dev.jna:jna:5.8.0=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -net.sf.saxon:Saxon-HE:12.10=spotbugs -org.apache.ant:ant-antlr:1.10.14=codenarc -org.apache.ant:ant-junit:1.10.14=codenarc -org.apache.bcel:bcel:6.12.0=spotbugs -org.apache.commons:commons-lang3:3.20.0=spotbugs -org.apache.commons:commons-text:1.15.0=spotbugs -org.apache.logging.log4j:log4j-api:2.26.1=spotbugs -org.apache.logging.log4j:log4j-core:2.26.1=spotbugs -org.apiguardian:apiguardian-api:1.1.2=latestDepTestCompileClasspath,testCompileClasspath -org.checkerframework:checker-compat-qual:2.5.5=compileProtoPath -org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepTestAnnotationProcessor,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=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,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=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.codehaus.mojo:animal-sniffer-annotations:1.19=compileProtoPath,testCompileProtoPath,testRuntimeClasspath -org.codehaus.mojo:animal-sniffer-annotations:1.27=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath -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=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.hamcrest:hamcrest:3.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.jctools:jctools-core-jdk11:4.0.6=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.jctools:jctools-core:4.0.6=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.jspecify:jspecify:1.0.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.14.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.14.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.14.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.platform:junit-platform-launcher:1.14.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.platform:junit-platform-runner:1.14.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.platform:junit-platform-suite-api:1.14.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit:junit-bom:5.14.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.junit:junit-bom:6.1.2=compileProtoPath,spotbugs -org.mockito:mockito-core:4.4.0=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.objenesis:objenesis:3.3=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.10.1=spotbugs -org.ow2.asm:asm-analysis:9.7.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.ow2.asm:asm-commons:9.10.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,spotbugs,testCompileProtoPath,testRuntimeClasspath -org.ow2.asm:asm-tree:9.10.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,spotbugs,testCompileProtoPath,testRuntimeClasspath -org.ow2.asm:asm-util:9.10.1=spotbugs -org.ow2.asm:asm-util:9.7.1=latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.ow2.asm:asm:9.10.1=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,spotbugs,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.slf4j:jcl-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.slf4j:jul-to-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.slf4j:log4j-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,compileProtoPath,muzzleBootstrap,muzzleTooling,runtimeClasspath -org.slf4j:slf4j-api:1.7.32=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.17=spotbugsSlf4j -org.slf4j:slf4j-api:2.0.18=spotbugs -org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,compileProtoPath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileProtoPath,testRuntimeClasspath -org.spockframework:spock-bom:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.spockframework:spock-core:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.tabletest:tabletest-junit:1.2.2=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.tabletest:tabletest-parser:1.2.1=latestDepTestCompileClasspath,latestDepTestCompileProtoPath,latestDepTestRuntimeClasspath,testCompileClasspath,testCompileProtoPath,testRuntimeClasspath -org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=latestDepTestProtobuf,protobuf,spotbugsPlugins,testProtobuf diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java deleted file mode 100644 index d44454e6c1d..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java +++ /dev/null @@ -1,92 +0,0 @@ -package datadog.trace.instrumentation.grpc; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.capture; -import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.endTaskScope; -import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.startTaskScope; -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.takesArguments; - -import com.google.auto.service.AutoService; -import datadog.context.ContextScope; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; -import datadog.trace.bootstrap.ContextStore; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.java.concurrent.QueueTimerHelper; -import datadog.trace.bootstrap.instrumentation.java.concurrent.State; -import java.nio.channels.Channel; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import net.bytebuddy.asm.Advice; - -@AutoService(InstrumenterModule.class) -public final class QueuedCommandInstrumentation extends InstrumenterModule.Profiling - implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice { - - private static final String QUEUED_COMMAND = "io.grpc.netty.WriteQueue$QueuedCommand"; - private static final String STATE = - "datadog.trace.bootstrap.instrumentation.java.concurrent.State"; - - public QueuedCommandInstrumentation() { - super("grpc", "grpc-netty"); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice(isConstructor(), getClass().getName() + "$Construct"); - transformer.applyAdvice( - isMethod() - .and( - named("run") - .and( - takesArguments(1) - .and(takesArgument(0, named("io.netty.channel.Channel"))))), - getClass().getName() + "$Run"); - } - - @Override - public Map contextStore() { - return Collections.singletonMap(QUEUED_COMMAND, STATE); - } - - @Override - public String[] knownMatchingTypes() { - return new String[] { - "io.grpc.netty.WriteQueue$AbstractQueuedCommand", - "io.grpc.netty.WriteQueue$RunnableCommand", - "io.grpc.netty.SendGrpcFrameCommand" - }; - } - - public static final class Construct { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void after(@Advice.This Object command) { - ContextStore contextStore = InstrumentationContext.get(QUEUED_COMMAND, STATE); - capture(contextStore, command); - // FIXME hard to handle both the lifecyle and get access to the queue instance in the same - // frame within the WriteQueue class. - // This means we can't get the queue length. A (bad) alternative would be to instrument - // ConcurrentLinkedQueue broadly, - // or we could write more brittle instrumentation targeting code patterns in different gRPC - // versions. - QueueTimerHelper.startQueuingTimer( - contextStore, Channel.class, ConcurrentLinkedQueue.class, 0, command); - } - } - - public static final class Run { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static ContextScope before(@Advice.This Object command) { - return startTaskScope(InstrumentationContext.get(QUEUED_COMMAND, STATE), command); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter ContextScope scope) { - endTaskScope(scope); - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java deleted file mode 100644 index 6caaa52af1b..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java +++ /dev/null @@ -1,54 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import io.grpc.internal.ClientStreamListener; -import net.bytebuddy.asm.Advice; - -public final class AbstractClientStreamInstrumentation - implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - - @Override - public String instrumentedType() { - return "io.grpc.internal.AbstractClientStream"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - named("start") - .and( - isMethod() - .and( - takesArgument(0, named("io.grpc.internal.ClientStreamListener")) - .and(takesArguments(1)))), - getClass().getName() + "$ActivateSpan"); - } - - public static final class ActivateSpan { - @Advice.OnMethodEnter - public static AgentScope before(@Advice.Argument(0) ClientStreamListener listener) { - AgentSpan span = - InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); - if (null != span) { - return activateSpan(span); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter AgentScope scope) { - if (null != scope) { - scope.close(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java deleted file mode 100644 index fcb8615aaa2..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java +++ /dev/null @@ -1,183 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; -import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.DECORATE; -import static datadog.trace.instrumentation.grpc.client.GrpcInjectAdapter.SETTER; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.annotation.AppliesOn; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import io.grpc.ClientCall; -import io.grpc.Grpc; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import io.grpc.StatusException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import net.bytebuddy.asm.Advice; - -public final class ClientCallImplInstrumentation - implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - - @Override - public String instrumentedType() { - return "io.grpc.internal.ClientCallImpl"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice(isConstructor(), getClass().getName() + "$Capture"); - transformer.applyAdvices( - named("start").and(isMethod()), - getClass().getName() + "$Start", - getClass().getName() + "$StartContextPropagationAdvice"); - transformer.applyAdvice(named("cancel").and(isMethod()), getClass().getName() + "$Cancel"); - transformer.applyAdvice( - named("request") - .and(isMethod()) - .and(takesArguments(int.class)) - .or(isMethod().and(named("halfClose").and(takesArguments(0)))), - getClass().getName() + "$ActivateSpan"); - transformer.applyAdvice( - named("sendMessage").and(isMethod()), getClass().getName() + "$SendMessage"); - transformer.applyAdvice( - named("closeObserver").and(takesArguments(3)), getClass().getName() + "$CloseObserver"); - } - - public static final class Capture { - @Advice.OnMethodExit - public static void capture( - @Advice.This io.grpc.ClientCall call, - @Advice.Argument(0) MethodDescriptor method) { - AgentSpan span = DECORATE.startCall(method); - if (null != span) { - InstrumentationContext.get(ClientCall.class, AgentSpan.class).put(call, span); - } - } - } - - public static final class Start { - @Advice.OnMethodEnter - public static AgentScope before( - @Advice.This ClientCall call, - @Advice.Argument(1) Metadata headers, - @Advice.Local("$$ddSpan") AgentSpan span) { - span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).get(call); - if (null != span) { - return activateSpan(span); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after( - @Advice.Enter AgentScope scope, - @Advice.Thrown Throwable error, - @Advice.Local("$$ddSpan") AgentSpan span) - throws Throwable { - if (null != scope) { - scope.close(); - } - if (null != error && null != span) { - DECORATE.onError(span, error); - DECORATE.beforeFinish(span); - span.finish(); - throw error; - } - } - } - - @AppliesOn(CONTEXT_TRACKING) - public static final class StartContextPropagationAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void before(@Advice.Argument(1) Metadata headers) { - AgentSpan span = activeSpan(); - if (span == null) { - return; - } - DECORATE.injectContext(span, headers, SETTER); - } - } - - public static final class ActivateSpan { - @Advice.OnMethodEnter - public static AgentScope before(@Advice.This ClientCall call) { - AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).get(call); - if (null != span) { - return activateSpan(span); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter AgentScope scope) { - if (null != scope) { - scope.close(); - } - } - } - - public static final class SendMessage { - @Advice.OnMethodEnter - public static AgentScope before(@Advice.This ClientCall call) { - // could create a message span here for the request - AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).get(call); - if (span != null) { - return activateSpan(span); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter AgentScope scope) { - if (null != scope) { - scope.close(); - } - } - } - - public static final class Cancel { - @Advice.OnMethodEnter - public static void before( - @Advice.This ClientCall call, @Advice.Argument(1) Throwable cause) { - AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).remove(call); - if (null != span) { - final SocketAddress socketAddress = - call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); - if (socketAddress instanceof InetSocketAddress) { - DECORATE.onPeerConnection(span, (InetSocketAddress) socketAddress); - } - if (cause instanceof StatusException) { - DECORATE.onClose(span, ((StatusException) cause).getStatus()); - } - span.finish(); - } - } - } - - public static final class CloseObserver { - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void closeObserver( - @Advice.This ClientCall call, @Advice.Argument(1) Status status) { - AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).remove(call); - if (null != span) { - final SocketAddress socketAddress = - call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); - if (socketAddress instanceof InetSocketAddress) { - DECORATE.onPeerConnection(span, (InetSocketAddress) socketAddress); - } - DECORATE.onClose(span, status); - span.finish(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java deleted file mode 100644 index 953a21be327..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java +++ /dev/null @@ -1,117 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; -import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.DECORATE; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import io.grpc.Status; -import io.grpc.internal.ClientStreamListener; -import net.bytebuddy.asm.Advice; - -public class ClientStreamListenerImplInstrumentation - implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - - @Override - public String instrumentedType() { - return "io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice(isConstructor(), getClass().getName() + "$Construct"); - transformer.applyAdvice( - named("exceptionThrown") - .and(takesArgument(0, named("io.grpc.Status"))) - .and(takesArguments(1)), - getClass().getName() + "$ExceptionThrown"); - transformer.applyAdvice( - namedOneOf("messageRead", "messagesAvailable"), getClass().getName() + "$RecordActivity"); - transformer.applyAdvice(named("headersRead"), getClass().getName() + "$RecordHeaders"); - } - - public static final class Construct { - @Advice.OnMethodExit - public static void capture(@Advice.This ClientStreamListener listener) { - // instrumentation of ClientCallImpl::start ensures this scope is present and valid - AgentSpan span = activeSpan(); - if (null != span) { - InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).put(listener, span); - } - } - } - - public static final class ExceptionThrown { - @Advice.OnMethodEnter - public static void exceptionThrown( - @Advice.This ClientStreamListener listener, @Advice.Argument(0) Status status) { - if (null != status) { - AgentSpan span = - InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); - if (null != span) { - DECORATE.onError(span, status.getCause()); - DECORATE.beforeFinish(span); - span.finish(); - } - } - } - } - - public static final class RecordActivity { - - @Advice.OnMethodEnter - public static AgentScope before(@Advice.This ClientStreamListener listener) { - // activate the span so serialisation work is accounted for, whichever thread the work is done - // on - AgentSpan span = - InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); - if (span != null) { - return activateSpan(span); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter AgentScope scope) { - if (null != scope) { - scope.close(); - } - } - } - - /* - A call to 'headersAvailable' is optional - meaning that it may not appear at all but if it appears - it will be followed by a call to `messageRead`. In order to properly cooperate with the `messageRead` instrumentation - we must make sure that when this method is finished the associated span is 'migrated' - such that `messageRead` - instrumentation can correctly 'resume' the span. - */ - public static final class RecordHeaders { - - @Advice.OnMethodEnter - public static AgentScope before(@Advice.This ClientStreamListener listener) { - // activate the span so serialisation work is accounted for, whichever thread the work is done - // on - AgentSpan span = - InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); - if (span != null) { - return activateSpan(span); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter AgentScope scope) { - if (null != scope) { - scope.close(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java deleted file mode 100644 index bcf6a5461b5..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java +++ /dev/null @@ -1,131 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -import static datadog.context.propagation.Propagators.defaultPropagator; -import static datadog.trace.api.datastreams.DataStreamsContext.fromTags; -import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND; -import static datadog.trace.api.datastreams.DataStreamsTags.create; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.traceConfig; - -import datadog.context.Context; -import datadog.context.propagation.CarrierSetter; -import datadog.trace.api.Config; -import datadog.trace.api.GenericClassValue; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.api.datastreams.DataStreamsContext; -import datadog.trace.api.naming.SpanNaming; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; -import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; -import datadog.trace.bootstrap.instrumentation.api.Tags; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; -import datadog.trace.bootstrap.instrumentation.decorator.ClientDecorator; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import java.util.BitSet; -import java.util.Set; -import java.util.function.Function; - -public class GrpcClientDecorator extends ClientDecorator { - public static final CharSequence OPERATION_NAME = - UTF8BytesString.create( - SpanNaming.instance().namingSchema().client().operationForProtocol("grpc")); - public static final CharSequence COMPONENT_NAME = UTF8BytesString.create("grpc-client"); - public static final CharSequence GRPC_MESSAGE = UTF8BytesString.create("grpc.message"); - - private static DataStreamsContext createDsmContext() { - return fromTags(create("grpc", OUTBOUND)); - } - - public static final GrpcClientDecorator DECORATE = new GrpcClientDecorator(); - - private static final Set IGNORED_METHODS = Config.get().getGrpcIgnoredOutboundMethods(); - private static final BitSet CLIENT_ERROR_STATUSES = Config.get().getGrpcClientErrorStatuses(); - - private static final ClassValue MESSAGE_TYPES = - GenericClassValue.of( - // Uses inner class for predictable name for Instrumenter.Default.helperClassNames() - new Function, UTF8BytesString>() { - @Override - public UTF8BytesString apply(Class input) { - return UTF8BytesString.create(input.getName()); - } - }); - - private static final DDCache RPC_SERVICE_CACHE = DDCaches.newFixedSizeCache(64); - - public UTF8BytesString requestMessageType(MethodDescriptor method) { - return messageType(method.getRequestMarshaller()); - } - - public UTF8BytesString responseMessageType(MethodDescriptor method) { - return messageType(method.getResponseMarshaller()); - } - - private UTF8BytesString messageType(MethodDescriptor.Marshaller marshaller) { - return marshaller instanceof MethodDescriptor.ReflectableMarshaller - ? MESSAGE_TYPES.get( - ((MethodDescriptor.ReflectableMarshaller) marshaller).getMessageClass()) - : null; - } - - @Override - protected String[] instrumentationNames() { - return new String[] {"grpc", "grpc-client"}; - } - - @Override - protected CharSequence component() { - return COMPONENT_NAME; - } - - @Override - protected CharSequence spanType() { - return InternalSpanTypes.RPC; - } - - @Override - protected String service() { - return null; - } - - public AgentSpan startCall(MethodDescriptor method) { - if (IGNORED_METHODS.contains(method.getFullMethodName())) { - // if the method is ignored we want to preserve the old behaviour and not make injection - // happen - return AgentTracer.blackholeSpan(); - } - AgentSpan span = - startSpan(COMPONENT_NAME.toString(), OPERATION_NAME) - .setTag("request.type", requestMessageType(method)) - .setTag("response.type", responseMessageType(method)) - // method.getServiceName() may not be available on some grpc versions - .setTag( - Tags.RPC_SERVICE, - RPC_SERVICE_CACHE.computeIfAbsent( - method.getFullMethodName(), MethodDescriptor::extractFullServiceName)); - span.setResourceName(method.getFullMethodName()); - afterStart(span); - return span; - } - - public void injectContext(Context context, final C request, CarrierSetter setter) { - if (traceConfig().isDataStreamsEnabled()) { - context = context.with(createDsmContext()); - } - defaultPropagator().inject(context, request, setter); - } - - public void onClose(final AgentSpan span, final Status status) { - span.setTag("status.code", status.getCode().name()); - span.setTag("grpc.status.code", status.getCode().name()); - span.setTag(InstrumentationTags.GRPC_STATUS_CODE, status.getCode().value()); - span.setTag("status.description", status.getDescription()); - - // TODO why is there a mismatch between client / server for calling the onError method? - onError(span, status.getCause()); - span.setError(CLIENT_ERROR_STATUSES.get(status.getCode().value())); - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java deleted file mode 100644 index 4322fa3d674..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java +++ /dev/null @@ -1,47 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -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.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.java.concurrent.State; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@AutoService(InstrumenterModule.class) -public class GrpcClientModule extends InstrumenterModule.Tracing { - - public GrpcClientModule() { - super("grpc", "grpc-client", "grpc-message"); - } - - @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".GrpcClientDecorator", - packageName + ".GrpcClientDecorator$1", - packageName + ".GrpcInjectAdapter" - }; - } - - @Override - public Map contextStore() { - Map contextStores = new HashMap<>(); - contextStores.put("io.grpc.ClientCall", AgentSpan.class.getName()); - contextStores.put("io.grpc.internal.ClientStreamListener", AgentSpan.class.getName()); - contextStores.put(Runnable.class.getName(), State.class.getName()); - return contextStores; - } - - @Override - public List typeInstrumentations() { - return asList( - new ClientCallImplInstrumentation(), - new AbstractClientStreamInstrumentation(), - new ClientStreamListenerImplInstrumentation(), - new MessagesAvailableInstrumentation()); - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java deleted file mode 100644 index 24a6c7df1aa..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -import datadog.context.propagation.CarrierSetter; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import io.grpc.Metadata; -import java.util.function.Function; -import javax.annotation.ParametersAreNonnullByDefault; - -@ParametersAreNonnullByDefault -public final class GrpcInjectAdapter implements CarrierSetter { - private static final Function> KEY_MAKER = - key -> Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); - private static final DDCache> KEY_CACHE = - DDCaches.newFixedSizeCache(64); - - public static final GrpcInjectAdapter SETTER = new GrpcInjectAdapter(); - - @Override - public void set(final Metadata carrier, final String key, final String value) { - Metadata.Key metadataKey = KEY_CACHE.computeIfAbsent(key, KEY_MAKER); - if (carrier.containsKey(metadataKey)) { - carrier.removeAll( - metadataKey); // Remove existing to ensure identical behavior with other carriers - } - carrier.put(metadataKey, value); - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java deleted file mode 100644 index e72ef5d5431..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java +++ /dev/null @@ -1,75 +0,0 @@ -package datadog.trace.instrumentation.grpc.client; - -import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.COMPONENT_NAME; -import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.DECORATE; -import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.GRPC_MESSAGE; -import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.OPERATION_NAME; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.annotation.AppliesOn; -import datadog.trace.api.InstrumenterConfig; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils; -import datadog.trace.bootstrap.instrumentation.java.concurrent.State; -import java.util.Collections; -import net.bytebuddy.asm.Advice; - -public final class MessagesAvailableInstrumentation - implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice { - - @Override - public String[] knownMatchingTypes() { - return new String[] { - "io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable", - "io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessageRead" - }; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice(isConstructor(), getClass().getName() + "$Capture"); - if (InstrumenterConfig.get() - .isIntegrationEnabled(Collections.singleton("grpc-message"), false)) { - transformer.applyAdvice(named("runInContext"), getClass().getName() + "$ReceiveMessages"); - } - } - - @AppliesOn(CONTEXT_TRACKING) - public static final class Capture { - @Advice.OnMethodExit - public static void capture(@Advice.This Runnable task) { - AdviceUtils.capture(InstrumentationContext.get(Runnable.class, State.class), task); - } - } - - public static final class ReceiveMessages { - @Advice.OnMethodEnter - public static AgentScope before() { - AgentSpan clientSpan = activeSpan(); - if (clientSpan != null && OPERATION_NAME.equals(clientSpan.getOperationName())) { - AgentSpan messageSpan = - startSpan(COMPONENT_NAME.toString(), GRPC_MESSAGE) - .setTag("message.type", clientSpan.getTag("response.type")); - DECORATE.afterStart(messageSpan); - return activateSpan(messageSpan); - } - return null; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void after(@Advice.Enter AgentScope scope) { - if (null != scope) { - scope.close(); - scope.span().finish(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java deleted file mode 100644 index 27a8804651c..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -package datadog.trace.instrumentation.grpc.server; - -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; -import io.grpc.Metadata; -import java.util.function.Function; - -public final class GrpcExtractAdapter implements AgentPropagation.ContextVisitor { - private static final Function> KEY_MAKER = - key -> Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); - private static final DDCache> KEY_CACHE = - DDCaches.newFixedSizeCache(64); - - public static final GrpcExtractAdapter GETTER = new GrpcExtractAdapter(); - - @Override - public void forEachKey(Metadata carrier, AgentPropagation.KeyClassifier classifier) { - for (String key : carrier.keys()) { - if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - if (!classifier.accept(key, carrier.get(KEY_CACHE.computeIfAbsent(key, KEY_MAKER)))) { - return; - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java deleted file mode 100644 index 4e982e6f031..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java +++ /dev/null @@ -1,80 +0,0 @@ -package datadog.trace.instrumentation.grpc.server; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.CallDepthThreadLocalMap.incrementCallDepth; -import static java.util.Arrays.asList; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.api.InstrumenterConfig; -import datadog.trace.bootstrap.CallDepthThreadLocalMap; -import datadog.trace.bootstrap.ContextStore; -import datadog.trace.bootstrap.InstrumentationContext; -import io.grpc.ServerBuilder; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class GrpcServerBuilderInstrumentation - implements Instrumenter.CanShortcutTypeMatching, Instrumenter.HasMethodAdvice { - - @Override - public boolean onlyMatchKnownTypes() { - return InstrumenterConfig.get() - .isIntegrationShortcutMatchingEnabled(asList("grpc", "grpc-server"), true); - } - - @Override - public String[] knownMatchingTypes() { - return new String[] { - "io.grpc.internal.AbstractServerImplBuilder", - "io.grpc.alts.AltsServerBuilder", - "io.grpc.ForwardingServerBuilder", - "io.grpc.inprocess.InProcessServerBuilder", - "io.grpc.netty.NettyServerBuilder", - "io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder", - "io.grpc.internal.ServerImplBuilder" - }; - } - - @Override - public String hierarchyMarkerType() { - return "io.grpc.ServerBuilder"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return extendsClass(named(hierarchyMarkerType())); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod().and(isPublic()).and(named("build")).and(takesArguments(0)), - GrpcServerBuilderInstrumentation.class.getName() + "$BuildAdvice"); - } - - public static class BuildAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter(@Advice.This ServerBuilder serverBuilder) { - int callDepth = incrementCallDepth(ServerBuilder.class); - if (callDepth == 0) { - ContextStore interceptedStore = - InstrumentationContext.get(ServerBuilder.class, Boolean.class); - if (interceptedStore.get(serverBuilder) == null) { - interceptedStore.put(serverBuilder, Boolean.TRUE); - serverBuilder.intercept(TracingServerInterceptor.INSTANCE); - } - } - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit() { - CallDepthThreadLocalMap.decrementCallDepth(ServerBuilder.class); - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java deleted file mode 100644 index 0126ae15ad4..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java +++ /dev/null @@ -1,125 +0,0 @@ -package datadog.trace.instrumentation.grpc.server; - -import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; -import static datadog.trace.api.datastreams.DataStreamsTags.create; - -import datadog.trace.api.Config; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.api.datastreams.DataStreamsTags; -import datadog.trace.api.naming.SpanNaming; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; -import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; -import datadog.trace.bootstrap.instrumentation.decorator.ServerDecorator; -import io.grpc.ServerCall; -import io.grpc.Status; -import io.grpc.StatusException; -import io.grpc.StatusRuntimeException; -import java.util.BitSet; -import java.util.function.Function; -import javax.annotation.Nonnull; - -public class GrpcServerDecorator extends ServerDecorator { - - private static final boolean TRIM_RESOURCE_PACKAGE_NAME = - Config.get().isGrpcServerTrimPackageResource(); - private static final BitSet SERVER_ERROR_STATUSES = Config.get().getGrpcServerErrorStatuses(); - - public static final CharSequence GRPC_SERVER = - UTF8BytesString.create( - SpanNaming.instance().namingSchema().server().operationForProtocol("grpc")); - public static final CharSequence COMPONENT_NAME = UTF8BytesString.create("grpc-server"); - public static final CharSequence GRPC_MESSAGE = UTF8BytesString.create("grpc.message"); - - private static DataStreamsTags createServerPathwaySortedTags() { - return create("grpc", INBOUND); - } - - public static final DataStreamsTags SERVER_PATHWAY_EDGE_TAGS = createServerPathwaySortedTags(); - public static final GrpcServerDecorator DECORATE = new GrpcServerDecorator(); - - private static final Function NORMALIZE = - // Uses inner class for predictable name for Instrumenter.Default.helperClassNames() - new Function() { - @Override - public String apply(String fullName) { - int index = fullName.lastIndexOf("."); - if (index > 0) { - return fullName.substring(index + 1); - } else { - return fullName; - } - } - }; - - private final DDCache cachedResourceNames; - - public GrpcServerDecorator() { - if (TRIM_RESOURCE_PACKAGE_NAME) { - cachedResourceNames = DDCaches.newFixedSizeCache(512); - } else { - cachedResourceNames = null; - } - } - - @Override - protected String[] instrumentationNames() { - return new String[] {"grpc", "grpc-server"}; - } - - @Override - protected CharSequence spanType() { - return InternalSpanTypes.RPC; - } - - @Override - protected CharSequence component() { - return COMPONENT_NAME; - } - - @Override - protected void doAfterStart(@Nonnull final AgentSpan span) { - span.setMeasured(true); - super.doAfterStart(span); - } - - public void onCall(final AgentSpan span, ServerCall call) { - if (TRIM_RESOURCE_PACKAGE_NAME) { - span.setResourceName( - cachedResourceNames.computeIfAbsent( - call.getMethodDescriptor().getFullMethodName(), NORMALIZE)); - } else { - span.setResourceName(call.getMethodDescriptor().getFullMethodName()); - } - } - - public void onStatus(final AgentSpan span, final Status status) { - span.setTag("status.code", status.getCode().name()); - span.setTag("grpc.status.code", status.getCode().name()); - span.setTag(InstrumentationTags.GRPC_STATUS_CODE, status.getCode().value()); - span.setTag("status.description", status.getDescription()); - span.setError( - SERVER_ERROR_STATUSES.get(status.getCode().value()), ErrorPriorities.HTTP_SERVER_DECORATOR); - } - - public void onClose(final AgentSpan span, final Status status) { - if (status.getCause() != null) { - onError(span, status.getCause()); - } - onStatus(span, status); - } - - @Override - protected void doOnError( - @Nonnull AgentSpan span, @Nonnull Throwable throwable, byte errorPriority) { - super.doOnError(span, throwable, ErrorPriorities.HTTP_SERVER_DECORATOR); - if (throwable instanceof StatusRuntimeException) { - onStatus(span, ((StatusRuntimeException) throwable).getStatus()); - } else if (throwable instanceof StatusException) { - onStatus(span, ((StatusException) throwable).getStatus()); - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java deleted file mode 100644 index b25894bb35d..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java +++ /dev/null @@ -1,49 +0,0 @@ -package datadog.trace.instrumentation.grpc.server; - -import static java.util.Collections.singleton; -import static java.util.Collections.singletonMap; - -import com.google.auto.service.AutoService; -import datadog.environment.JavaVirtualMachine; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; -import datadog.trace.api.InstrumenterConfig; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -@AutoService(InstrumenterModule.class) -public class GrpcServerModule extends InstrumenterModule.Tracing { - public GrpcServerModule() { - super("grpc", "grpc-server"); - } - - @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".GrpcServerDecorator", - packageName + ".GrpcServerDecorator$1", - packageName + ".GrpcExtractAdapter", - packageName + ".TracingServerInterceptor", - packageName + ".TracingServerInterceptor$TracingServerCall", - packageName + ".TracingServerInterceptor$TracingServerCallListener", - }; - } - - @Override - public Map contextStore() { - return singletonMap("io.grpc.ServerBuilder", Boolean.class.getName()); - } - - @Override - public List typeInstrumentations() { - final List ret = new ArrayList<>(2); - ret.add(new GrpcServerBuilderInstrumentation()); - if (!JavaVirtualMachine.isGraalVM() - && InstrumenterConfig.get() - .isIntegrationEnabled(singleton("grpc-server-code-origin"), true)) { - ret.add(new MethodHandlersInstrumentation()); - } - return ret; - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java deleted file mode 100644 index 18afa651be1..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java +++ /dev/null @@ -1,73 +0,0 @@ -package datadog.trace.instrumentation.grpc.server; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameEndsWith; -import static datadog.trace.bootstrap.debugger.DebuggerContext.captureCodeOrigin; -import static datadog.trace.bootstrap.debugger.DebuggerContext.marker; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import java.lang.reflect.Method; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class MethodHandlersInstrumentation - implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - private static final ElementMatcher METHOD_HANDLERS = - nameEndsWith("$MethodHandlers"); - - @Override - public String hierarchyMarkerType() { - return "io.grpc.MethodDescriptor"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return METHOD_HANDLERS; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isConstructor().and(takesArguments(2)), - "datadog.trace.instrumentation.grpc.server.MethodHandlersInstrumentation$BuildAdvice"); - } - - public static class BuildAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter(@Advice.Argument(0) Object serviceImpl) { - try { - Class serviceClass = serviceImpl.getClass(); - Class superClass = serviceClass.getSuperclass(); - while (superClass != null && !superClass.getSimpleName().endsWith("ImplBase")) { - superClass = superClass.getSuperclass(); - } - if (superClass != null) { - // bindService() would be the only method in this case and it's irrelevant - if (superClass.getDeclaredMethods().length == 1) { - for (Class i : superClass.getInterfaces()) { - if (i.getSimpleName().equals("AsyncService")) { - superClass = i; - break; - } - } - } - for (Method method : superClass.getDeclaredMethods()) { - try { - marker(); - captureCodeOrigin( - serviceClass.getDeclaredMethod(method.getName(), method.getParameterTypes()), - true); - } catch (Throwable e) { - // service method not overridden on the impl. skipping instrumentation. - } - } - } - } catch (Throwable e) { - // this should be logged somehow - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java deleted file mode 100644 index 7b45d3b1577..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java +++ /dev/null @@ -1,369 +0,0 @@ -package datadog.trace.instrumentation.grpc.server; - -import static datadog.trace.api.datastreams.DataStreamsContext.fromTags; -import static datadog.trace.api.gateway.Events.EVENTS; -import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.instrumentation.grpc.server.GrpcExtractAdapter.GETTER; -import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.COMPONENT_NAME; -import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.DECORATE; -import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.GRPC_MESSAGE; -import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.GRPC_SERVER; -import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.SERVER_PATHWAY_EDGE_TAGS; - -import datadog.context.ContextScope; -import datadog.trace.api.Config; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.api.function.TriConsumer; -import datadog.trace.api.function.TriFunction; -import datadog.trace.api.gateway.CallbackProvider; -import datadog.trace.api.gateway.Flow; -import datadog.trace.api.gateway.IGSpanInfo; -import datadog.trace.api.gateway.RequestContext; -import datadog.trace.api.gateway.RequestContextSlot; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.api.TagContext; -import io.grpc.ForwardingServerCall; -import io.grpc.ForwardingServerCallListener; -import io.grpc.Grpc; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.ServerCall; -import io.grpc.ServerCallHandler; -import io.grpc.ServerInterceptor; -import io.grpc.Status; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; -import javax.annotation.Nonnull; - -public class TracingServerInterceptor implements ServerInterceptor { - private static final Function> KEY_MAKER = - key -> Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); - private static final DDCache> KEY_CACHE = - DDCaches.newFixedSizeCache(64); - - public static final TracingServerInterceptor INSTANCE = new TracingServerInterceptor(); - private static final Set IGNORED_METHODS = Config.get().getGrpcIgnoredInboundMethods(); - - private TracingServerInterceptor() {} - - protected static AgentTracer.TracerAPI tracer() { - return AgentTracer.get(); - } - - @Override - public ServerCall.Listener interceptCall( - final ServerCall call, - final Metadata headers, - final ServerCallHandler next) { - if (IGNORED_METHODS.contains(call.getMethodDescriptor().getFullMethodName())) { - return next.startCall(call, headers); - } - - AgentSpanContext spanContext = extractContextAndGetSpanContext(headers, GETTER); - AgentTracer.TracerAPI tracer = tracer(); - spanContext = callIGCallbackRequestStarted(tracer, spanContext); - - CallbackProvider cbp = tracer.getCallbackProvider(RequestContextSlot.APPSEC); - final AgentSpan span = - startSpan(COMPONENT_NAME.toString(), GRPC_SERVER, spanContext).setMeasured(true); - - AgentTracer.get() - .getDataStreamsMonitoring() - .setCheckpoint(span, fromTags(SERVER_PATHWAY_EDGE_TAGS)); - - RequestContext reqContext = span.getRequestContext(); - if (reqContext != null) { - callIGCallbackClientAddress(cbp, reqContext, call); - callIGCallbackHeaders(cbp, reqContext, headers); - callIGCallbackGrpcServerMethod(cbp, reqContext, call.getMethodDescriptor()); - } - - DECORATE.afterStart(span); - DECORATE.onCall(span, call); - - final ServerCall.Listener result; - try (ContextScope scope = activateSpan(span)) { - // Wrap the server call so that we can decorate the span - // with the resulting status - final TracingServerCall tracingServerCall = new TracingServerCall<>(span, call); - // call other interceptors - result = next.startCall(tracingServerCall, headers); - } catch (final Throwable e) { - if (span.phasedFinish()) { - DECORATE.onError(span, e); - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - throw e; - } - - // This ensures the server implementation can see the span in scope - return new TracingServerCallListener<>(span, result); - } - - static final class TracingServerCall - extends ForwardingServerCall.SimpleForwardingServerCall { - final AgentSpan span; - - TracingServerCall(final AgentSpan span, final ServerCall delegate) { - super(delegate); - this.span = span; - } - - @Override - public void close(final Status status, final Metadata trailers) { - DECORATE.onClose(span, status); - try (final ContextScope scope = activateSpan(span)) { - delegate().close(status, trailers); - } catch (final Throwable e) { - DECORATE.onError(span, e); - throw e; - } finally { - if (span.phasedFinish()) { - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - } - } - } - - static final class TracingServerCallListener - extends ForwardingServerCallListener.SimpleForwardingServerCallListener { - private final AgentSpan span; - - TracingServerCallListener(final AgentSpan span, final ServerCall.Listener delegate) { - super(delegate); - this.span = span; - } - - @Override - public void onMessage(final ReqT message) { - final AgentSpan msgSpan = - startSpan(COMPONENT_NAME.toString(), GRPC_MESSAGE, this.span.spanContext()) - .setTag("message.type", message.getClass().getName()); - DECORATE.afterStart(msgSpan); - try (ContextScope scope = activateSpan(msgSpan)) { - callIGCallbackGrpcMessage(msgSpan, message); - delegate().onMessage(message); - } catch (final Throwable e) { - // I'm not convinced we should actually be finishing the span here... - if (span.phasedFinish()) { - DECORATE.onError(msgSpan, e); - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - throw e; - } finally { - DECORATE.beforeFinish(msgSpan); - msgSpan.finish(); - } - } - - @Override - public void onHalfClose() { - try (final ContextScope scope = activateSpan(span)) { - delegate().onHalfClose(); - } catch (final Throwable e) { - if (span.phasedFinish()) { - DECORATE.onError(span, e); - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - throw e; - } - } - - @Override - public void onCancel() { - // Finishes span. - try (final ContextScope scope = activateSpan(span)) { - delegate().onCancel(); - span.setTag("canceled", true); - } catch (CancellationException e) { - // No need to report an exception or mark as error that it was canceled. - throw e; - } catch (final Throwable e) { - DECORATE.onError(span, e); - throw e; - } finally { - if (span.phasedFinish()) { - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - } - } - - @Override - public void onComplete() { - // Finishes span. - try (final ContextScope scope = activateSpan(span)) { - delegate().onComplete(); - } catch (final Throwable e) { - DECORATE.onError(span, e); - throw e; - } finally { - /** - * grpc has quite a few states that can finish the span. rather than track down the correct - * combination of them to exclusively finish the span, use phasedFinish. - */ - if (span.phasedFinish()) { - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - } - } - - @Override - public void onReady() { - try (final ContextScope scope = activateSpan(span)) { - delegate().onReady(); - } catch (final Throwable e) { - if (span.phasedFinish()) { - DECORATE.onError(span, e); - DECORATE.beforeFinish(span); - callIGCallbackRequestEnded(span); - span.publish(); - } - throw e; - } - } - } - - // IG helpers follow - - private static AgentSpanContext callIGCallbackRequestStarted( - AgentTracer.TracerAPI cbp, AgentSpanContext context) { - Supplier> startedCbAppSec = - cbp.getCallbackProvider(RequestContextSlot.APPSEC).getCallback(EVENTS.requestStarted()); - Supplier> startedCbIast = - cbp.getCallbackProvider(RequestContextSlot.IAST).getCallback(EVENTS.requestStarted()); - - if (startedCbAppSec == null && startedCbIast == null) { - return context; - } - - TagContext tagContext = null; - if (context == null) { - tagContext = new TagContext(); - } else if (context instanceof TagContext) { - tagContext = (TagContext) context; - } - if (tagContext != null) { - if (startedCbAppSec != null) { - Flow flowAppSec = startedCbAppSec.get(); - tagContext.withRequestContextDataAppSec(flowAppSec.getResult()); - } - if (startedCbIast != null) { - Flow flowIast = startedCbIast.get(); - tagContext.withRequestContextDataIast(flowIast.getResult()); - } - return tagContext; - } - - return context; - } - - private static void callIGCallbackClientAddress( - CallbackProvider cbp, RequestContext ctx, ServerCall call) { - SocketAddress socketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); - TriFunction> cb = - cbp.getCallback(EVENTS.requestClientSocketAddress()); - if (socketAddress == null || !(socketAddress instanceof InetSocketAddress) || cb == null) { - return; - } - - InetSocketAddress inetSockAddr = (InetSocketAddress) socketAddress; - cb.apply(ctx, inetSockAddr.getHostString(), inetSockAddr.getPort()); - } - - private static void callIGCallbackHeaders( - CallbackProvider cbp, RequestContext reqCtx, Metadata metadata) { - TriConsumer headerCb = cbp.getCallback(EVENTS.requestHeader()); - Function> headerEndCb = cbp.getCallback(EVENTS.requestHeaderDone()); - if (headerCb == null || headerEndCb == null) { - return; - } - for (String key : metadata.keys()) { - if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - Metadata.Key mdKey = KEY_CACHE.computeIfAbsent(key, KEY_MAKER); - for (String value : metadata.getAll(mdKey)) { - headerCb.accept(reqCtx, key, value); - } - } - } - - headerEndCb.apply(reqCtx); - } - - private static void callIGCallbackRequestEnded(@Nonnull final AgentSpan span) { - CallbackProvider cbp = tracer().getUniversalCallbackProvider(); - if (cbp == null) { - return; - } - RequestContext requestContext = span.getRequestContext(); - if (requestContext != null) { - BiFunction> callback = - cbp.getCallback(EVENTS.requestEnded()); - if (callback != null) { - callback.apply(requestContext, span); - } - } - } - - private static void callIGCallbackGrpcServerMethod( - CallbackProvider cbp, RequestContext ctx, MethodDescriptor methodDescriptor) { - String method = methodDescriptor.getFullMethodName(); - BiFunction> cb = cbp.getCallback(EVENTS.grpcServerMethod()); - if (method == null || cb == null) { - return; - } - cb.apply(ctx, method); - } - - private static void callIGCallbackGrpcMessage(@Nonnull final AgentSpan span, Object obj) { - if (obj == null) { - return; - } - CallbackProvider cbpAppsec = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); - if (cbpAppsec == null && cbpIast == null) { - return; - } - RequestContext requestContext = span.getRequestContext(); - if (requestContext == null) { - return; - } - - if (cbpAppsec != null) { - BiFunction> callback = - cbpAppsec.getCallback(EVENTS.grpcServerRequestMessage()); - if (callback != null) { - callback.apply(requestContext, obj); - } - } - - if (cbpIast != null) { - BiFunction> callback = - cbpIast.getCallback(EVENTS.grpcServerRequestMessage()); - if (callback != null) { - callback.apply(requestContext, obj); - } - } - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcInjectAdapterTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcInjectAdapterTest.groovy deleted file mode 100644 index abb6ad7db8c..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcInjectAdapterTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import io.grpc.Metadata -import static datadog.trace.instrumentation.grpc.client.GrpcInjectAdapter.SETTER - -class GrpcInjectAdapterTest extends VersionedNamingTestBase { - def "carrier overrides values for duplicate keys"() { - setup: - def carrier = new Metadata() - - def baggage = [ - ["ot-baggage-foo", "v1"], - ["ot-baggage-foo", "v2"], - ["ot-baggage-bar", "v3"] - ] - - when: - baggage.each { pair -> - def (key, value) = pair - SETTER.set(carrier, key, value) - } - - then: - carrier.headerCount() == 2 - carrier.get(getKey("ot-baggage-foo")) == "v2" // overridden value wins - carrier.get(getKey("ot-baggage-bar")) == "v3" - } - - Metadata.Key getKey(String key){ - Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER) - } - - - @Override - int version() { - return 0 - } - - @Override - String service() { - return null - } - - @Override - String operation() { - return null - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcStreamingTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcStreamingTest.groovy deleted file mode 100644 index 6d28463e9d7..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcStreamingTest.groovy +++ /dev/null @@ -1,288 +0,0 @@ -import com.google.common.util.concurrent.MoreExecutors -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.api.DDSpanTypes -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import example.GreeterGrpc -import example.Helloworld -import io.grpc.BindableService -import io.grpc.ManagedChannel -import io.grpc.Server -import io.grpc.inprocess.InProcessChannelBuilder -import io.grpc.inprocess.InProcessServerBuilder -import io.grpc.stub.StreamObserver - -import java.util.concurrent.CopyOnWriteArrayList -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicReference - -abstract class GrpcStreamingTest extends VersionedNamingTestBase { - - @Override - final String service() { - return null - } - - @Override - final String operation() { - return null - } - - protected abstract String clientOperation() - - protected abstract String serverOperation() - - protected boolean hasClientMessageSpans() { - false - } - - @Override - protected void configurePreAgent() { - super.configurePreAgent() - injectSysConfig("dd.trace.grpc.ignored.inbound.methods", "example.Greeter/IgnoreInbound") - injectSysConfig("dd.trace.grpc.ignored.outbound.methods", "example.Greeter/Ignore") - if (hasClientMessageSpans()) { - injectSysConfig("integration.grpc-message.enabled", "true") - } - // here to trigger wrapping to record scheduling time - the logic is trivial so it's enough to verify - // that ClassCastExceptions do not arise from the wrapping - injectSysConfig("dd.profiling.enabled", "true") - } - - def "test conversation #name"() { - setup: - - def msgCount = serverMessageCount - def serverReceived = new CopyOnWriteArrayList<>() - def clientReceived = new CopyOnWriteArrayList<>() - def error = new AtomicReference() - - BindableService greeter = new GreeterGrpc.GreeterImplBase() { - @Override - StreamObserver conversation(StreamObserver observer) { - return new StreamObserver() { - @Override - void onNext(Helloworld.Response value) { - - serverReceived << value.message - - (1..msgCount).each { - if (TEST_TRACER.isAsyncPropagationEnabled()) { - observer.onNext(value) - } else { - observer.onError(new IllegalStateException("not async propagating!")) - } - } - } - - @Override - void onError(Throwable t) { - if (TEST_TRACER.isAsyncPropagationEnabled()) { - error.set(t) - observer.onError(t) - } else { - observer.onError(new IllegalStateException("not async propagating!")) - } - } - - @Override - void onCompleted() { - if (TEST_TRACER.isAsyncPropagationEnabled()) { - observer.onCompleted() - } else { - observer.onError(new IllegalStateException("not async propagating!")) - } - } - } - } - } - Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter) - .executor(directExecutor ? MoreExecutors.directExecutor() : Executors.newCachedThreadPool()) - .build().start() - - ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build() - GreeterGrpc.GreeterStub client = GreeterGrpc.newStub(channel).withWaitForReady() - - when: - def streamObserver = client.conversation(new StreamObserver() { - @Override - void onNext(Helloworld.Response value) { - if (TEST_TRACER.isAsyncPropagationEnabled()) { - clientReceived << value.message - } else { - error.set(new IllegalStateException("not async propagating!")) - } - } - - @Override - void onError(Throwable t) { - if (TEST_TRACER.isAsyncPropagationEnabled()) { - error.set(t) - } else { - error.set(new IllegalStateException("not async propagating!")) - } - } - - @Override - void onCompleted() { - if (!TEST_TRACER.isAsyncPropagationEnabled()) { - error.set(new IllegalStateException("not async propagating!")) - } - } - }) - - clientRange.each { - def message = Helloworld.Response.newBuilder().setMessage("call $it").build() - streamObserver.onNext(message) - } - streamObserver.onCompleted() - - then: - error.get() == null - TEST_WRITER.waitForTraces(2) - error.get() == null - serverReceived == clientRange.collect { "call $it" } - clientReceived == serverRange.collect { - clientRange.collect { - "call $it" - } - }.flatten().sort() - - assertTraces(2) { - trace((hasClientMessageSpans() ? clientMessageCount * serverMessageCount : 0) + 1) { - span { - operationName clientOperation() - resourceName "example.Greeter/Conversation" - spanType DDSpanTypes.RPC - parent() - errored false - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.RPC_SERVICE" "example.Greeter" - "status.code" "OK" - "grpc.status.code" "OK" - "$InstrumentationTags.GRPC_STATUS_CODE" 0 - "request.type" "example.Helloworld\$Response" - "response.type" "example.Helloworld\$Response" - peerServiceFrom(Tags.RPC_SERVICE) - defaultTags() - } - } - if (hasClientMessageSpans()) { - (1..(clientMessageCount * serverMessageCount)).each { - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "message.type" "example.Helloworld\$Response" - defaultTagsNoPeerService() - } - } - } - } - } - trace(clientMessageCount + 1) { - span { - operationName serverOperation() - resourceName "example.Greeter/Conversation" - spanType DDSpanTypes.RPC - childOf trace(0).get(0) - errored false - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "status.code" "OK" - "grpc.status.code" "OK" - "$InstrumentationTags.GRPC_STATUS_CODE" 0 - defaultTags(true) - } - } - clientRange.each { - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "message.type" "example.Helloworld\$Response" - defaultTags() - } - } - } - } - } - - cleanup: - channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS) - server?.shutdownNow()?.awaitTermination() - - where: - name | clientMessageCount | serverMessageCount | directExecutor - "A" | 1 | 1 | false - "B" | 2 | 1 | false - "C" | 1 | 2 | false - "D" | 2 | 2 | false - "E" | 3 | 3 | false - "A" | 1 | 1 | true - "B" | 2 | 1 | true - "C" | 1 | 2 | true - "D" | 2 | 2 | true - "E" | 3 | 3 | true - - clientRange = 1..clientMessageCount - serverRange = 1..serverMessageCount - } -} - -class GrpcStreamingV0Test extends GrpcStreamingTest { - - @Override - int version() { - return 0 - } - - @Override - protected String clientOperation() { - return "grpc.client" - } - - @Override - protected String serverOperation() { - return "grpc.server" - } -} - -class GrpcStreamingV1ForkedTest extends GrpcStreamingTest { - - @Override - int version() { - return 1 - } - - @Override - protected String clientOperation() { - return "grpc.client.request" - } - - @Override - protected String serverOperation() { - return "grpc.server.request" - } -} - -class GrpcStreamingClientMessageEnabledTest extends GrpcStreamingV0Test { - @Override - protected boolean hasClientMessageSpans() { - true - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy deleted file mode 100644 index 4f29f063841..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy +++ /dev/null @@ -1,745 +0,0 @@ - -import static datadog.trace.agent.test.asserts.TagsAssert.codeOriginTags -import static datadog.trace.api.config.TraceInstrumentationConfig.GRPC_SERVER_ERROR_STATUSES - -import com.google.common.util.concurrent.MoreExecutors -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.api.DDSpanId -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.DDTags -import datadog.trace.api.function.TriConsumer -import datadog.trace.api.gateway.Flow -import datadog.trace.api.gateway.RequestContext -import datadog.trace.api.gateway.RequestContextSlot -import datadog.trace.bootstrap.instrumentation.api.AgentPropagation -import datadog.trace.bootstrap.instrumentation.api.AgentTracer -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.datastreams.StatsGroup -import datadog.trace.instrumentation.grpc.server.GrpcExtractAdapter -import example.GreeterGrpc -import example.Helloworld -import io.grpc.BindableService -import io.grpc.ManagedChannel -import io.grpc.Metadata -import io.grpc.Server -import io.grpc.Status -import io.grpc.StatusRuntimeException -import io.grpc.inprocess.InProcessChannelBuilder -import io.grpc.inprocess.InProcessServerBuilder -import io.grpc.netty.NettyChannelBuilder -import io.grpc.netty.NettyServerBuilder -import io.grpc.stub.StreamObserver -import spock.lang.Shared - -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.ForkJoinPool -import java.util.concurrent.TimeUnit -import java.util.function.BiFunction -import java.util.function.Function -import java.util.function.Supplier - -import static datadog.trace.agent.test.utils.TraceUtils.basicSpan -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace -import static datadog.trace.api.gateway.Events.EVENTS - -abstract class GrpcTest extends VersionedNamingTestBase { - - @Shared - def ig - - def collectedAppSecHeaders = [:] - boolean appSecHeaderDone = false - def collectedAppSecReqMsgs = [] - def collectedAppSecServerMethods = [] - - @Override - final String service() { - return null - } - - @Override - final String operation() { - return null - } - - protected abstract String clientOperation() - - protected abstract String serverOperation() - - protected boolean hasClientMessageSpans() { - false - } - - @Override - protected void configurePreAgent() { - super.configurePreAgent() - codeOriginSetup() - injectSysConfig("dd.trace.grpc.ignored.inbound.methods", "example.Greeter/IgnoreInbound") - injectSysConfig("dd.trace.grpc.ignored.outbound.methods", "example.Greeter/Ignore") - if (hasClientMessageSpans()) { - injectSysConfig("integration.grpc-message.enabled", "true") - } - // here to trigger wrapping to record scheduling time - the logic is trivial so it's enough to verify - // that ClassCastExceptions do not arise from the wrapping - injectSysConfig("dd.profiling.enabled", "true") - injectSysConfig(GRPC_SERVER_ERROR_STATUSES, "2-14", true) - } - - def setupSpec() { - ig = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC) - } - - def setup() { - ig.registerCallback(EVENTS.requestStarted(), { -> new Flow.ResultFlow(new Object()) } as Supplier) - ig.registerCallback(EVENTS.requestHeader(), { reqCtx, name, value -> - collectedAppSecHeaders[name] = value - } as TriConsumer) - ig.registerCallback(EVENTS.requestHeaderDone(), { - appSecHeaderDone = true - Flow.ResultFlow.empty() - } as Function>) - ig.registerCallback(EVENTS.grpcServerRequestMessage(), { reqCtx, obj -> - collectedAppSecReqMsgs << obj - Flow.ResultFlow.empty() - } as BiFunction>) - ig.registerCallback(EVENTS.grpcServerMethod(), { reqCtx, method -> - collectedAppSecServerMethods << method - Flow.ResultFlow.empty() - } as BiFunction>) - } - - def cleanup() { - ig.reset() - } - - def "test request-response #name #executor.class.simpleName"() { - setup: - - ExecutorService responseExecutor = Executors.newSingleThreadExecutor() - BindableService greeter = new GreeterGrpc.GreeterImplBase() { - @Override - void sayHello( - final Helloworld.Request req, final StreamObserver responseObserver) { - final Helloworld.Response reply = Helloworld.Response.newBuilder().setMessage("Hello $req.name").build() - responseExecutor.execute { - if (TEST_TRACER.activeSpan() == null) { - responseObserver.onError(new IllegalStateException("no active span")) - } else { - responseObserver.onNext(reply) - responseObserver.onCompleted() - } - } - } - } - def builder = NettyServerBuilder.forPort(0).addService(greeter).executor(executor) - (0..extraBuildCalls).each { builder.build() } - Server server = builder.build().start() - - - ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build() - GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) - - when: - def response = runUnderTrace("parent") { - def resp = client.sayHello(Helloworld.Request.newBuilder().setName(name).build()) - return resp - } - // wait here to make checkpoint asserts deterministic - TEST_WRITER.waitForTraces(2) - if (isDataStreamsEnabled()) { - TEST_DATA_STREAMS_WRITER.waitForGroups(2) - } - - then: - response.message == "Hello $name" - codeOriginTags(TEST_WRITER) - assertTraces(2) { - trace(hasClientMessageSpans() ? 3 : 2) { - basicSpan(it, "parent") - span { - operationName clientOperation() - resourceName "example.Greeter/SayHello" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.RPC_SERVICE" "example.Greeter" - "$Tags.PEER_HOSTNAME" "localhost" - "$Tags.PEER_HOST_IPV4" "127.0.0.1" - "$Tags.PEER_PORT" server.port - "status.code" "OK" - "grpc.status.code" "OK" - "$InstrumentationTags.GRPC_STATUS_CODE" 0 - "request.type" "example.Helloworld\$Request" - "response.type" "example.Helloworld\$Response" - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - peerServiceFrom(Tags.RPC_SERVICE) - defaultTags() - } - } - if (hasClientMessageSpans()) { - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(1) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "message.type" "example.Helloworld\$Response" - defaultTagsNoPeerService() - } - } - } - } - trace(2) { - span { - operationName serverOperation() - resourceName "example.Greeter/SayHello" - spanType DDSpanTypes.RPC - childOf trace(0).get(1) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "status.code" "OK" - "grpc.status.code" "OK" - "$InstrumentationTags.GRPC_STATUS_CODE" 0 - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - defaultTags(true) - } - } - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "message.type" "example.Helloworld\$Request" - defaultTags() - } - } - } - } - - and: - def traceId = TEST_WRITER[0].traceId.first() - traceId.toLong() as String == collectedAppSecHeaders['x-datadog-trace-id'] - collectedAppSecReqMsgs.size() == 1 - collectedAppSecReqMsgs.first().name == name - collectedAppSecServerMethods.size() == 1 - collectedAppSecServerMethods.first() == 'example.Greeter/SayHello' - - and: - if (isDataStreamsEnabled()) { - StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } - verifyAll(first) { - tags.hasAllTags("direction:out", "type:grpc") - } - - StatsGroup second = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == first.hash } - verifyAll(second) { - tags.hasAllTags("direction:in", "type:grpc") - } - } - - cleanup: - channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS) - server?.shutdownNow()?.awaitTermination() - if (executor instanceof ExecutorService) { - (executor as ExecutorService).shutdownNow() - } - - where: - name | executor | extraBuildCalls - "some name" | MoreExecutors.directExecutor() | 0 - "some other name" | MoreExecutors.directExecutor() | 0 - "some name" | newWorkStealingPool() | 0 - "some other name" | newWorkStealingPool() | 0 - "some name" | Executors.newSingleThreadExecutor() | 0 - "some other name" | Executors.newSingleThreadExecutor() | 0 - "some name" | MoreExecutors.directExecutor() | 1 - "some other name" | MoreExecutors.directExecutor() | 1 - "some name" | newWorkStealingPool() | 1 - "some other name" | newWorkStealingPool() | 1 - "some name" | Executors.newSingleThreadExecutor() | 1 - "some other name" | Executors.newSingleThreadExecutor() | 1 - } - - def "test error - #name"() { - setup: - def error = status.asException() - BindableService greeter = new GreeterGrpc.GreeterImplBase() { - @Override - void sayHello( - final Helloworld.Request req, final StreamObserver responseObserver) { - responseObserver.onError(error) - } - } - Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start() - - ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build() - GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) - - when: - client.sayHello(Helloworld.Request.newBuilder().setName(name).build()) - // wait here to make checkpoint asserts deterministic - TEST_WRITER.waitForTraces(2) - - then: - thrown StatusRuntimeException - - assertTraces(2) { - trace(1) { - span { - operationName clientOperation() - resourceName "example.Greeter/SayHello" - spanType DDSpanTypes.RPC - parent() - errored true - measured true - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.RPC_SERVICE" "example.Greeter" - "status.code" "${status.code.name()}" - "grpc.status.code" "${status.code.name()}" - "$InstrumentationTags.GRPC_STATUS_CODE" status.code.value() - "status.description" description - "request.type" "example.Helloworld\$Request" - "response.type" "example.Helloworld\$Response" - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - peerServiceFrom(Tags.RPC_SERVICE) - defaultTags() - } - } - } - trace(2) { - span { - operationName serverOperation() - resourceName "example.Greeter/SayHello" - spanType DDSpanTypes.RPC - childOf trace(0).get(0) - errored true - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "status.code" "${status.code.name()}" - "grpc.status.code" "${status.code.name()}" - "$InstrumentationTags.GRPC_STATUS_CODE" status.code.value() - "status.description" description - if (status.cause != null) { - errorTags status.cause.class, status.cause.message - } - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - defaultTags(true) - } - } - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "message.type" "example.Helloworld\$Request" - defaultTags() - } - } - } - } - - cleanup: - channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS) - server?.shutdownNow()?.awaitTermination() - - where: - name | status | description - "Runtime - cause" | Status.UNKNOWN.withCause(new RuntimeException("some error")) | null - "Status - cause" | Status.PERMISSION_DENIED.withCause(new RuntimeException("some error")) | null - "StatusRuntime - cause" | Status.UNIMPLEMENTED.withCause(new RuntimeException("some error")) | null - "Runtime - description" | Status.UNKNOWN.withDescription("some description") | "some description" - "Status - description" | Status.PERMISSION_DENIED.withDescription("some description") | "some description" - "StatusRuntime - description" | Status.UNIMPLEMENTED.withDescription("some description") | "some description" - } - - def "test error thrown - #name"() { - setup: - - def error = status.asRuntimeException() - BindableService greeter = new GreeterGrpc.GreeterImplBase() { - @Override - void sayHello( - final Helloworld.Request req, final StreamObserver responseObserver) { - throw error - } - } - Server server = NettyServerBuilder.forPort(0).addService(greeter).directExecutor().build().start() - - ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build() - GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) - - when: - client.sayHello(Helloworld.Request.newBuilder().setName(name).build()) - // wait here to make checkpoint asserts deterministic - TEST_WRITER.waitForTraces(2) - - then: - thrown StatusRuntimeException - - assertTraces(2) { - trace(1) { - span { - operationName clientOperation() - resourceName "example.Greeter/SayHello" - spanType DDSpanTypes.RPC - parent() - errored true - measured true - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.RPC_SERVICE" "example.Greeter" - "$Tags.PEER_HOSTNAME" "localhost" - "$Tags.PEER_HOST_IPV4" "127.0.0.1" - "$Tags.PEER_PORT" server.port - "status.code" "UNKNOWN" - "grpc.status.code" "UNKNOWN" - "$InstrumentationTags.GRPC_STATUS_CODE" 2 - "request.type" "example.Helloworld\$Request" - "response.type" "example.Helloworld\$Response" - "status.description" { it == null || String} - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - peerServiceFrom(Tags.RPC_SERVICE) - defaultTags() - } - } - } - trace(2) { - span { - operationName serverOperation() - resourceName "example.Greeter/SayHello" - spanType DDSpanTypes.RPC - childOf trace(0).get(0) - errored errorFlag - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "status.code" "${status.code.name()}" - "grpc.status.code" "${status.code.name()}" - "$InstrumentationTags.GRPC_STATUS_CODE" status.code.value() - "status.description" { it == null || String} - errorTags error.class, error.message - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - defaultTags(true) - } - } - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "message.type" "example.Helloworld\$Request" - defaultTags() - } - } - } - } - - cleanup: - channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS) - server?.shutdownNow()?.awaitTermination() - - where: - name | status | errorFlag - "Runtime - cause" | Status.UNKNOWN.withCause(new RuntimeException("some error")) | true - "Status - cause" | Status.PERMISSION_DENIED.withCause(new RuntimeException("some error")) | true - "StatusRuntime - cause" | Status.UNIMPLEMENTED.withCause(new RuntimeException("some error")) | true - "Runtime - description" | Status.UNKNOWN.withDescription("some description") | true - "Status - description" | Status.PERMISSION_DENIED.withDescription("some description") | true - "StatusRuntime - description" | Status.UNIMPLEMENTED.withDescription("some description") | true - "StatusRuntime - Not errored no cause" | Status.fromCodeValue(15).withDescription("some description") | false - "StatusRuntime - Not errored with cause" | Status.fromCodeValue(15).withCause(new RuntimeException("some error")) | false - } - - def "skip binary headers"() { - setup: - def meta = new Metadata() - meta.put(Metadata.Key. of("test", Metadata.ASCII_STRING_MARSHALLER), "val") - meta.put(Metadata.Key. of("test-bin", Metadata.BINARY_BYTE_MARSHALLER), "bin-val".bytes) - - when: - def keys = new ArrayList() - GrpcExtractAdapter.GETTER.forEachKey(meta, new AgentPropagation.KeyClassifier() { - - @Override - boolean accept(String key, String value) { - keys.add(key.toLowerCase()) - return true - } - }) - - then: - keys == ["test"] - } - - def "test ignore ignored methods"() { - setup: - - ExecutorService responseExecutor = Executors.newSingleThreadExecutor() - BindableService greeter = new GreeterGrpc.GreeterImplBase() { - @Override - void ignore( - final Helloworld.Request req, final StreamObserver responseObserver) { - final Helloworld.Response reply = Helloworld.Response.newBuilder().setMessage("Hello $req.name").build() - responseExecutor.execute { - responseObserver.onNext(reply) - responseObserver.onCompleted() - } - } - } - Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start() - ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build() - GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) - - when: - def response = runUnderTrace("parent") { - def resp = client.ignore(Helloworld.Request.newBuilder().setName("whatever").build()) - return resp - } - - then: - response.message == "Hello whatever" - assertTraces(2) { - trace(1) { - basicSpan(it, "parent") - } - trace(2) { - span { - operationName serverOperation() - resourceName "example.Greeter/Ignore" - spanType DDSpanTypes.RPC - parentSpanId DDSpanId.ZERO - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "status.code" "OK" - "grpc.status.code" "OK" - "$InstrumentationTags.GRPC_STATUS_CODE" 0 - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - defaultTags(true) - } - } - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-server" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER - "message.type" "example.Helloworld\$Request" - defaultTags() - } - } - } - } - - cleanup: - channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS) - server?.shutdownNow()?.awaitTermination() - } - - def "test ignore ignored inbound methods"() { - setup: - - ExecutorService responseExecutor = Executors.newSingleThreadExecutor() - BindableService greeter = new GreeterGrpc.GreeterImplBase() { - @Override - void ignoreInbound( - final Helloworld.Request req, final StreamObserver responseObserver) { - final Helloworld.Response reply = Helloworld.Response.newBuilder().setMessage("Hello $req.name").build() - responseExecutor.execute { - responseObserver.onNext(reply) - responseObserver.onCompleted() - } - } - } - Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start() - ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build() - GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) - - when: - def response = client.ignoreInbound(Helloworld.Request.newBuilder().setName("whatever").build()) - - then: - response.message == "Hello whatever" - assertTraces(1) { - trace(hasClientMessageSpans() ? 2 : 1) { - span { - operationName clientOperation() - resourceName "example.Greeter/IgnoreInbound" - spanType DDSpanTypes.RPC - parent() - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.RPC_SERVICE" "example.Greeter" - "status.code" "OK" - "grpc.status.code" "OK" - "$InstrumentationTags.GRPC_STATUS_CODE" 0 - "request.type" "example.Helloworld\$Request" - "response.type" "example.Helloworld\$Response" - if ({ isDataStreamsEnabled() }) { - "$DDTags.PATHWAY_HASH" { String } - } - peerServiceFrom(Tags.RPC_SERVICE) - defaultTags() - } - } - if (hasClientMessageSpans()) { - span { - operationName "grpc.message" - resourceName "grpc.message" - spanType DDSpanTypes.RPC - childOf span(0) - errored false - measured true - tags { - "$Tags.COMPONENT" "grpc-client" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "message.type" "example.Helloworld\$Response" - defaultTagsNoPeerService() - } - } - } - } - } - - cleanup: - channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS) - server?.shutdownNow()?.awaitTermination() - } - - def newWorkStealingPool() { - // Executors.newWorkStealingPool() not available in JDK7 - return new ForkJoinPool - (Runtime.getRuntime().availableProcessors(), - ForkJoinPool.defaultForkJoinWorkerThreadFactory, - null, true) - } -} - -abstract class GrpcDataStreamsEnabledForkedTest extends GrpcTest { - @Override - protected boolean isDataStreamsEnabled() { - return true - } -} - -class GrpcDataStreamsEnabledV0Test extends GrpcDataStreamsEnabledForkedTest { - - @Override - int version() { - return 0 - } - - @Override - protected String clientOperation() { - return "grpc.client" - } - - @Override - protected String serverOperation() { - return "grpc.server" - } -} - -class GrpcDataStreamsEnabledV1ForkedTest extends GrpcDataStreamsEnabledForkedTest { - - @Override - int version() { - return 1 - } - - @Override - protected String clientOperation() { - return "grpc.client.request" - } - - @Override - protected String serverOperation() { - return "grpc.server.request" - } -} - -class GrpcDataStreamsDisabledForkedTest extends GrpcTest { - @Override - protected boolean isDataStreamsEnabled() { - return false - } - - @Override - int version() { - return 0 - } - - @Override - protected String clientOperation() { - return "grpc.client" - } - - @Override - protected String serverOperation() { - return "grpc.server" - } -} - -class GrpcClientMessagesEnabledTest extends GrpcDataStreamsEnabledV0Test { - @Override - protected boolean hasClientMessageSpans() { - true - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcStreamingTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcStreamingTest.groovy deleted file mode 100644 index cbb5d858c99..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcStreamingTest.groovy +++ /dev/null @@ -1,22 +0,0 @@ -class HierarchyMatcherGrpcStreamingTest extends GrpcStreamingTest { - @Override - protected void configurePreAgent() { - super.configurePreAgent() - injectSysConfig("dd.integration.grpc.matching.shortcut.enabled", "false") - } - - @Override - int version() { - return 0 - } - - @Override - String clientOperation() { - return "grpc.client" - } - - @Override - String serverOperation() { - return "grpc.server" - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcTest.groovy deleted file mode 100644 index efe773cd840..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/HierarchyMatcherGrpcTest.groovy +++ /dev/null @@ -1,22 +0,0 @@ - -class HierarchyMatcherGrpcTest extends GrpcTest { - @Override - protected void configurePreAgent() { - super.configurePreAgent() - injectSysConfig("dd.integration.grpc.matching.shortcut.enabled", "false") - } - @Override - int version() { - return 0 - } - - @Override - String clientOperation() { - return "grpc.client" - } - - @Override - String serverOperation() { - return "grpc.server" - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/UnwrapGrpcContextForkedTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/UnwrapGrpcContextForkedTest.groovy deleted file mode 100644 index e688155f27c..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/UnwrapGrpcContextForkedTest.groovy +++ /dev/null @@ -1,26 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.bootstrap.instrumentation.api.TaskWrapper -import io.grpc.Context - -class UnwrapGrpcContextForkedTest extends InstrumentationSpecification { - @Override - protected void configurePreAgent() { - super.configurePreAgent() - injectSysConfig("dd.profiling.enabled", "true") - } - - def "test unwrap gRPC context wrappers"() { - // relies on TaskUnwrappingInstrumentation (java-concurrent) targeting grpc context - setup: - def runnable = {} - def callable = {"result"} - when: - def unwrappedRunnableType = TaskWrapper.getUnwrappedType(Context.ROOT.wrap(runnable)) - then: - unwrappedRunnableType == runnable.getClass() - when: - def unwrappedCallableType = TaskWrapper.getUnwrappedType(Context.ROOT.wrap(callable)) - then: - unwrappedCallableType == callable.getClass() - } -} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto b/dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto deleted file mode 100644 index 26d9919e53e..00000000000 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto +++ /dev/null @@ -1,25 +0,0 @@ -syntax = "proto3"; - -package example; - -service Greeter { - rpc SayHello (Request) returns (Response) { - } - - rpc Conversation (stream Response) returns (stream Response) { - } - - rpc Ignore (Request) returns (Response) { - } - - rpc IgnoreInbound (Request) returns (Response) { - } -} - -message Request { - string name = 1; -} - -message Response { - string message = 1; -} From 6b5d9b2769a53c36f0d6e0c55265670c32affb74 Mon Sep 17 00:00:00 2001 From: Jordan Wong Date: Wed, 2 Sep 2026 16:15:52 -0400 Subject: [PATCH 2/3] workflow(grpc): test_scaffold --- .../instrumentation/grpc-1.5/build.gradle | 56 +++ .../instrumentation/grpc/GrpcErrorTest.java | 342 ++++++++++++++++ .../grpc/GrpcStreamingTest.java | 364 ++++++++++++++++++ .../trace/instrumentation/grpc/GrpcTest.java | 219 +++++++++++ .../grpc-1.5/src/test/proto/helloworld.proto | 21 + 5 files changed, 1002 insertions(+) create mode 100644 dd-java-agent/instrumentation/grpc-1.5/build.gradle create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcErrorTest.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcStreamingTest.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcTest.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto diff --git a/dd-java-agent/instrumentation/grpc-1.5/build.gradle b/dd-java-agent/instrumentation/grpc-1.5/build.gradle new file mode 100644 index 00000000000..f0510e3be39 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/build.gradle @@ -0,0 +1,56 @@ +plugins { + id 'com.google.protobuf' version '0.10.0' +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +def grpcVersion = '1.42.2' + +muzzle { + pass { + group = 'io.grpc' + module = 'grpc-core' + versions = '[1.5.0,)' + assertInverse = true + } +} + +dependencies { + compileOnly group: 'io.grpc', name: 'grpc-core', version: grpcVersion + + testImplementation group: 'io.grpc', name: 'grpc-core', version: grpcVersion + testImplementation group: 'io.grpc', name: 'grpc-netty', version: grpcVersion + testImplementation group: 'io.grpc', name: 'grpc-protobuf', version: grpcVersion + testImplementation group: 'io.grpc', name: 'grpc-stub', version: grpcVersion + testImplementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2' + + latestDepTestImplementation group: 'io.grpc', name: 'grpc-core', version: '1.+' + latestDepTestImplementation group: 'io.grpc', name: 'grpc-netty', version: '1.+' + latestDepTestImplementation group: 'io.grpc', name: 'grpc-protobuf', version: '1.+' + latestDepTestImplementation group: 'io.grpc', name: 'grpc-stub', version: '1.+' +} + +def protocVersion = '3.17.3' +// aarch_64 support was added in 3.17.3 +def protocArch = System.getProperty("os.arch") == "aarch64" ? ":osx-x86_64" : "" +def grpcArch = System.getProperty("os.arch") == "aarch64" ? ":osx-x86_64" : "" + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protocVersion}${protocArch}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}${grpcArch}" + } + } + generateProtoTasks { + ofSourceSet("test").configureEach { + plugins { + grpc {} + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcErrorTest.java b/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcErrorTest.java new file mode 100644 index 00000000000..9a9ef5f6bdf --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcErrorTest.java @@ -0,0 +1,342 @@ +package datadog.trace.instrumentation.grpc; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import example.GreeterGrpc; +import example.HelloworldProto.Request; +import example.HelloworldProto.Response; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Tests gRPC instrumentation error handling. Verifies that error tags (error.type, error.message, + * error.stack) and status codes are correctly set on spans when RPCs fail with various gRPC status + * codes and server-side exceptions. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class GrpcErrorTest extends AbstractInstrumentationTest { + + private static final int PORT = 18883; + + private Server grpcServer; + private ManagedChannel grpcChannel; + private GreeterGrpc.GreeterBlockingStub blockingStub; + private GreeterGrpc.GreeterStub asyncStub; + + @BeforeAll + void setupGrpc() throws IOException { + grpcServer = + ServerBuilder.forPort(PORT) + .addService(new ErrorGreeterImpl()) + .build() + .start(); + + grpcChannel = + ManagedChannelBuilder.forAddress("localhost", PORT).usePlaintext().build(); + blockingStub = GreeterGrpc.newBlockingStub(grpcChannel); + asyncStub = GreeterGrpc.newStub(grpcChannel); + } + + @AfterAll + void tearDownGrpc() throws InterruptedException { + if (grpcChannel != null) { + grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + if (grpcServer != null) { + grpcServer.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + @Test + void unaryErrorSetsErrorTagsOnSpans() throws InterruptedException, TimeoutException { + StatusRuntimeException thrown = + assertThrows( + StatusRuntimeException.class, + () -> + blockingStub.sayHello( + Request.newBuilder().setName("UNIMPLEMENTED").build())); + + assertEquals( + Status.UNIMPLEMENTED.getCode(), + thrown.getStatus().getCode(), + "Exception should carry UNIMPLEMENTED status"); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span on error path"); + assertEquals( + "example.Greeter/SayHello", + clientSpan.getResourceName().toString(), + "Resource name should still reflect the called method"); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("client", String.valueOf(clientSpan.getTag("span.kind"))); + assertEquals("rpc", clientSpan.getSpanType()); + assertEquals("grpc", String.valueOf(clientSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(clientSpan.getTag("rpc.service"))); + assertTrue(clientSpan.isError(), "Client span should be marked as errored"); + assertEquals( + String.valueOf(Status.UNIMPLEMENTED.getCode().value()), + String.valueOf(clientSpan.getTag("status.code")), + "Client status code should be UNIMPLEMENTED"); + assertNotNull( + clientSpan.getTag("error.message"), + "Client span should have error.message tag"); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span on error path"); + assertEquals( + "example.Greeter/SayHello", + serverSpan.getResourceName().toString(), + "Server resource name should match the called method"); + assertEquals("grpc", String.valueOf(serverSpan.getTag("component"))); + assertEquals("server", String.valueOf(serverSpan.getTag("span.kind"))); + assertEquals("rpc", serverSpan.getSpanType()); + assertEquals("grpc", String.valueOf(serverSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(serverSpan.getTag("rpc.service"))); + assertEquals( + String.valueOf(Status.UNIMPLEMENTED.getCode().value()), + String.valueOf(serverSpan.getTag("status.code")), + "Server status code should be UNIMPLEMENTED"); + } + + @Test + void serverExceptionSetsErrorTagsWithDetails() throws InterruptedException, TimeoutException { + StatusRuntimeException thrown = + assertThrows( + StatusRuntimeException.class, + () -> + blockingStub.sayHello( + Request.newBuilder().setName("INTERNAL_ERROR").build())); + + assertEquals( + Status.INTERNAL.getCode(), + thrown.getStatus().getCode(), + "Exception should carry INTERNAL status"); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span for internal error"); + assertTrue(clientSpan.isError(), "Client span should be errored on INTERNAL status"); + assertEquals( + String.valueOf(Status.INTERNAL.getCode().value()), + String.valueOf(clientSpan.getTag("status.code")), + "Client status code should be INTERNAL"); + assertNotNull( + clientSpan.getTag("error.message"), + "Client span should have error.message for INTERNAL error"); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span for internal error"); + assertEquals( + "example.Greeter/SayHello", + serverSpan.getResourceName().toString()); + assertEquals( + String.valueOf(Status.INTERNAL.getCode().value()), + String.valueOf(serverSpan.getTag("status.code")), + "Server status code should be INTERNAL"); + assertNotNull( + serverSpan.getTag("error.message"), + "Server span should have error.message for INTERNAL error"); + } + + @Test + void notFoundStatusSetsCorrectStatusCode() throws InterruptedException, TimeoutException { + StatusRuntimeException thrown = + assertThrows( + StatusRuntimeException.class, + () -> + blockingStub.sayHello( + Request.newBuilder().setName("NOT_FOUND").build())); + + assertEquals( + Status.NOT_FOUND.getCode(), + thrown.getStatus().getCode(), + "Exception should carry NOT_FOUND status"); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span for NOT_FOUND"); + assertEquals( + "example.Greeter/SayHello", + clientSpan.getResourceName().toString()); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("rpc", clientSpan.getSpanType()); + assertEquals( + String.valueOf(Status.NOT_FOUND.getCode().value()), + String.valueOf(clientSpan.getTag("status.code")), + "Client status code should be NOT_FOUND"); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span for NOT_FOUND"); + assertEquals( + String.valueOf(Status.NOT_FOUND.getCode().value()), + String.valueOf(serverSpan.getTag("status.code")), + "Server status code should be NOT_FOUND"); + } + + @Test + void streamingErrorSetsErrorTagsOnSpans() throws InterruptedException, TimeoutException { + CountDownLatch latch = new CountDownLatch(1); + CopyOnWriteArrayList errors = new CopyOnWriteArrayList<>(); + + StreamObserver requestObserver = + asyncStub.clientStreamHello( + new StreamObserver() { + @Override + public void onNext(Response value) {} + + @Override + public void onError(Throwable t) { + errors.add(t); + latch.countDown(); + } + + @Override + public void onCompleted() { + latch.countDown(); + } + }); + + requestObserver.onNext(Request.newBuilder().setName("STREAM_ERROR").build()); + requestObserver.onCompleted(); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Error callback should arrive within timeout"); + assertEquals(1, errors.size(), "Expected one error from streaming call"); + assertTrue( + errors.get(0) instanceof StatusRuntimeException, + "Error should be StatusRuntimeException"); + assertEquals( + Status.ABORTED.getCode(), + ((StatusRuntimeException) errors.get(0)).getStatus().getCode(), + "Error status should be ABORTED"); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span for streaming error"); + assertEquals( + "example.Greeter/ClientStreamHello", + clientSpan.getResourceName().toString(), + "Resource should be the streaming method path"); + assertTrue(clientSpan.isError(), "Client span should be errored on streaming failure"); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("rpc", clientSpan.getSpanType()); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span for streaming error"); + assertEquals( + "example.Greeter/ClientStreamHello", + serverSpan.getResourceName().toString()); + assertEquals( + String.valueOf(Status.ABORTED.getCode().value()), + String.valueOf(serverSpan.getTag("status.code")), + "Server status code should be ABORTED"); + } + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpan(List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + static class ErrorGreeterImpl extends GreeterGrpc.GreeterImplBase { + @Override + public void sayHello(Request request, StreamObserver responseObserver) { + String name = request.getName(); + switch (name) { + case "UNIMPLEMENTED": + responseObserver.onError( + Status.UNIMPLEMENTED + .withDescription("Method not implemented") + .asRuntimeException()); + break; + case "INTERNAL_ERROR": + responseObserver.onError( + Status.INTERNAL + .withDescription("Internal server error") + .asRuntimeException()); + break; + case "NOT_FOUND": + responseObserver.onError( + Status.NOT_FOUND + .withDescription("Resource not found") + .asRuntimeException()); + break; + default: + responseObserver.onNext( + Response.newBuilder().setMessage("Hello " + name).build()); + responseObserver.onCompleted(); + break; + } + } + + @Override + public StreamObserver clientStreamHello(StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(Request value) { + if ("STREAM_ERROR".equals(value.getName())) { + responseObserver.onError( + Status.ABORTED + .withDescription("Stream aborted by server") + .asRuntimeException()); + } + } + + @Override + public void onError(Throwable t) { + responseObserver.onError(t); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcStreamingTest.java b/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcStreamingTest.java new file mode 100644 index 00000000000..d27c19b14fb --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcStreamingTest.java @@ -0,0 +1,364 @@ +package datadog.trace.instrumentation.grpc; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import example.GreeterGrpc; +import example.HelloworldProto.Request; +import example.HelloworldProto.Response; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Tests gRPC instrumentation for streaming RPC patterns: server streaming, client streaming, and + * bidirectional streaming. Verifies span creation, tags, and parent-child relationships across + * streaming calls. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class GrpcStreamingTest extends AbstractInstrumentationTest { + + private static final int PORT = 18882; + + private Server grpcServer; + private ManagedChannel grpcChannel; + private GreeterGrpc.GreeterBlockingStub blockingStub; + private GreeterGrpc.GreeterStub asyncStub; + + @BeforeAll + void setupGrpc() throws IOException { + grpcServer = + ServerBuilder.forPort(PORT) + .addService(new StreamingGreeterImpl()) + .build() + .start(); + + grpcChannel = + ManagedChannelBuilder.forAddress("localhost", PORT).usePlaintext().build(); + blockingStub = GreeterGrpc.newBlockingStub(grpcChannel); + asyncStub = GreeterGrpc.newStub(grpcChannel); + } + + @AfterAll + void tearDownGrpc() throws InterruptedException { + if (grpcChannel != null) { + grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + if (grpcServer != null) { + grpcServer.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + @Test + void serverStreamingCreatesClientAndServerSpans() + throws InterruptedException, TimeoutException { + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + List responses = new ArrayList<>(); + try { + Iterator iterator = + blockingStub.serverStreamHello(Request.newBuilder().setName("stream").build()); + while (iterator.hasNext()) { + responses.add(iterator.next()); + } + } finally { + parentScope.close(); + parentSpan.finish(); + } + + assertEquals(3, responses.size(), "Expected 3 streamed responses"); + assertEquals("Hello stream 0", responses.get(0).getMessage()); + assertEquals("Hello stream 1", responses.get(1).getMessage()); + assertEquals("Hello stream 2", responses.get(2).getMessage()); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span for server streaming"); + assertEquals( + "example.Greeter/ServerStreamHello", + clientSpan.getResourceName().toString(), + "Resource name should be the server streaming method path"); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("client", String.valueOf(clientSpan.getTag("span.kind"))); + assertEquals("rpc", clientSpan.getSpanType()); + assertEquals("grpc", String.valueOf(clientSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(clientSpan.getTag("rpc.service"))); + assertFalse(clientSpan.isError()); + assertEquals( + parentSpan.getSpanId(), + clientSpan.getParentId(), + "Client span should be child of parent"); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span for server streaming"); + assertEquals( + "example.Greeter/ServerStreamHello", + serverSpan.getResourceName().toString(), + "Server resource name should match the streaming method path"); + assertEquals("grpc", String.valueOf(serverSpan.getTag("component"))); + assertEquals("server", String.valueOf(serverSpan.getTag("span.kind"))); + assertEquals("rpc", serverSpan.getSpanType()); + assertEquals("grpc", String.valueOf(serverSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(serverSpan.getTag("rpc.service"))); + assertFalse(serverSpan.isError()); + assertEquals( + clientSpan.getSpanId(), + serverSpan.getParentId(), + "Server span should be child of client span"); + } + + @Test + void clientStreamingCreatesClientAndServerSpans() + throws InterruptedException, TimeoutException { + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + + CountDownLatch latch = new CountDownLatch(1); + CopyOnWriteArrayList responses = new CopyOnWriteArrayList<>(); + + StreamObserver requestObserver; + try { + requestObserver = + asyncStub.clientStreamHello( + new StreamObserver() { + @Override + public void onNext(Response value) { + responses.add(value); + } + + @Override + public void onError(Throwable t) { + latch.countDown(); + } + + @Override + public void onCompleted() { + latch.countDown(); + } + }); + + requestObserver.onNext(Request.newBuilder().setName("msg1").build()); + requestObserver.onNext(Request.newBuilder().setName("msg2").build()); + requestObserver.onNext(Request.newBuilder().setName("msg3").build()); + requestObserver.onCompleted(); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Response should arrive within timeout"); + assertEquals(1, responses.size(), "Client streaming should produce a single response"); + assertEquals( + "Hello msg1, msg2, msg3", + responses.get(0).getMessage(), + "Response should aggregate all client messages"); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span for client streaming"); + assertEquals( + "example.Greeter/ClientStreamHello", + clientSpan.getResourceName().toString(), + "Resource name should be the client streaming method path"); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("rpc", clientSpan.getSpanType()); + assertEquals("grpc", String.valueOf(clientSpan.getTag("rpc.system"))); + assertFalse(clientSpan.isError()); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span for client streaming"); + assertEquals( + "example.Greeter/ClientStreamHello", + serverSpan.getResourceName().toString(), + "Server resource name should match the client streaming method path"); + assertEquals("grpc", String.valueOf(serverSpan.getTag("component"))); + assertEquals("rpc", serverSpan.getSpanType()); + assertEquals("grpc", String.valueOf(serverSpan.getTag("rpc.system"))); + assertFalse(serverSpan.isError()); + assertEquals( + clientSpan.getSpanId(), + serverSpan.getParentId(), + "Server span should be child of client span"); + } + + @Test + void bidiStreamingCreatesClientAndServerSpans() + throws InterruptedException, TimeoutException { + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + + CountDownLatch latch = new CountDownLatch(1); + CopyOnWriteArrayList responses = new CopyOnWriteArrayList<>(); + + StreamObserver requestObserver; + try { + requestObserver = + asyncStub.bidiStreamHello( + new StreamObserver() { + @Override + public void onNext(Response value) { + responses.add(value); + } + + @Override + public void onError(Throwable t) { + latch.countDown(); + } + + @Override + public void onCompleted() { + latch.countDown(); + } + }); + + requestObserver.onNext(Request.newBuilder().setName("bidi1").build()); + requestObserver.onNext(Request.newBuilder().setName("bidi2").build()); + requestObserver.onCompleted(); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Response should arrive within timeout"); + assertEquals(2, responses.size(), "Bidi streaming should echo each request as a response"); + assertEquals("Hello bidi1", responses.get(0).getMessage()); + assertEquals("Hello bidi2", responses.get(1).getMessage()); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span for bidi streaming"); + assertEquals( + "example.Greeter/BidiStreamHello", + clientSpan.getResourceName().toString(), + "Resource name should be the bidi streaming method path"); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("client", String.valueOf(clientSpan.getTag("span.kind"))); + assertEquals("rpc", clientSpan.getSpanType()); + assertEquals("grpc", String.valueOf(clientSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(clientSpan.getTag("rpc.service"))); + assertFalse(clientSpan.isError()); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span for bidi streaming"); + assertEquals( + "example.Greeter/BidiStreamHello", + serverSpan.getResourceName().toString(), + "Server resource name should match the bidi streaming method path"); + assertEquals("grpc", String.valueOf(serverSpan.getTag("component"))); + assertEquals("server", String.valueOf(serverSpan.getTag("span.kind"))); + assertEquals("rpc", serverSpan.getSpanType()); + assertEquals("grpc", String.valueOf(serverSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(serverSpan.getTag("rpc.service"))); + assertFalse(serverSpan.isError()); + assertEquals( + clientSpan.getSpanId(), + serverSpan.getParentId(), + "Server span should be child of client span"); + } + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpan(List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + static class StreamingGreeterImpl extends GreeterGrpc.GreeterImplBase { + @Override + public void serverStreamHello(Request request, StreamObserver responseObserver) { + for (int i = 0; i < 3; i++) { + responseObserver.onNext( + Response.newBuilder() + .setMessage("Hello " + request.getName() + " " + i) + .build()); + } + responseObserver.onCompleted(); + } + + @Override + public StreamObserver clientStreamHello(StreamObserver responseObserver) { + List names = new ArrayList<>(); + return new StreamObserver() { + @Override + public void onNext(Request value) { + names.add(value.getName()); + } + + @Override + public void onError(Throwable t) { + responseObserver.onError(t); + } + + @Override + public void onCompleted() { + responseObserver.onNext( + Response.newBuilder() + .setMessage("Hello " + String.join(", ", names)) + .build()); + responseObserver.onCompleted(); + } + }; + } + + @Override + public StreamObserver bidiStreamHello(StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(Request value) { + responseObserver.onNext( + Response.newBuilder().setMessage("Hello " + value.getName()).build()); + } + + @Override + public void onError(Throwable t) { + responseObserver.onError(t); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcTest.java b/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcTest.java new file mode 100644 index 00000000000..337c5ccce10 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/test/java/datadog/trace/instrumentation/grpc/GrpcTest.java @@ -0,0 +1,219 @@ +package datadog.trace.instrumentation.grpc; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import example.GreeterGrpc; +import example.HelloworldProto.Request; +import example.HelloworldProto.Response; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Tests gRPC client and server instrumentation for unary RPC calls. Verifies that spans are created + * with correct operation names, resource names, tags, and parent-child relationships for both client + * and server sides. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class GrpcTest extends AbstractInstrumentationTest { + + private static final int PORT = 18881; + + private Server grpcServer; + private ManagedChannel grpcChannel; + private GreeterGrpc.GreeterBlockingStub blockingStub; + + @BeforeAll + void setupGrpc() throws IOException { + grpcServer = + ServerBuilder.forPort(PORT) + .addService(new GreeterImpl()) + .build() + .start(); + + grpcChannel = + ManagedChannelBuilder.forAddress("localhost", PORT).usePlaintext().build(); + blockingStub = GreeterGrpc.newBlockingStub(grpcChannel); + } + + @AfterAll + void tearDownGrpc() throws InterruptedException { + if (grpcChannel != null) { + grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + if (grpcServer != null) { + grpcServer.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + @Test + void unaryCallCreatesClientAndServerSpans() throws InterruptedException, TimeoutException { + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + Response response; + try { + response = blockingStub.sayHello(Request.newBuilder().setName("World").build()); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + assertEquals("Hello World", response.getMessage()); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + assertNotNull(clientSpan, "Expected grpc.client span"); + assertEquals( + "example.Greeter/SayHello", + clientSpan.getResourceName().toString(), + "Resource name should be the full gRPC method path"); + assertEquals("grpc", String.valueOf(clientSpan.getTag("component"))); + assertEquals("client", String.valueOf(clientSpan.getTag("span.kind"))); + assertEquals("rpc", clientSpan.getSpanType()); + assertEquals( + "example.Greeter/SayHello", String.valueOf(clientSpan.getTag("rpc.method"))); + assertEquals("grpc", String.valueOf(clientSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(clientSpan.getTag("rpc.service"))); + assertEquals( + parentSpan.getSpanId(), + clientSpan.getParentId(), + "Client span should be child of parent"); + assertFalse(clientSpan.isError(), "Client span should not be errored on success"); + assertEquals( + String.valueOf(Status.OK.getCode().value()), + String.valueOf(clientSpan.getTag("status.code")), + "Status code should be OK (0)"); + + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + assertNotNull(serverSpan, "Expected grpc.server span"); + assertEquals( + "example.Greeter/SayHello", + serverSpan.getResourceName().toString(), + "Server resource name should match the gRPC method path"); + assertEquals("grpc", String.valueOf(serverSpan.getTag("component"))); + assertEquals("server", String.valueOf(serverSpan.getTag("span.kind"))); + assertEquals("rpc", serverSpan.getSpanType()); + assertEquals( + "example.Greeter/SayHello", String.valueOf(serverSpan.getTag("rpc.method"))); + assertEquals("grpc", String.valueOf(serverSpan.getTag("rpc.system"))); + assertEquals("example.Greeter", String.valueOf(serverSpan.getTag("rpc.service"))); + assertEquals( + clientSpan.getSpanId(), + serverSpan.getParentId(), + "Server span should be child of client span (distributed trace)"); + assertFalse(serverSpan.isError(), "Server span should not be errored on success"); + assertEquals( + String.valueOf(Status.OK.getCode().value()), + String.valueOf(serverSpan.getTag("status.code")), + "Server status code should be OK (0)"); + } + + @Test + void traceContextPropagatesFromClientToServer() throws InterruptedException, TimeoutException { + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + blockingStub.sayHello(Request.newBuilder().setName("propagation-test").build()); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + DDSpan clientSpan = findSpan(allSpans, "grpc.client", "client"); + DDSpan serverSpan = findSpan(allSpans, "grpc.server", "server"); + + assertNotNull(clientSpan, "Expected grpc.client span"); + assertNotNull(serverSpan, "Expected grpc.server span"); + + assertEquals( + parentSpan.getTraceId(), + clientSpan.getTraceId(), + "Client span should share trace ID with parent"); + assertEquals( + clientSpan.getTraceId(), + serverSpan.getTraceId(), + "Server span should share trace ID with client (distributed trace)"); + assertEquals( + clientSpan.getSpanId(), + serverSpan.getParentId(), + "Server span parent should be the client span"); + } + + @Test + void multipleUnaryCallsProduceSeparateTraces() throws InterruptedException, TimeoutException { + blockingStub.sayHello(Request.newBuilder().setName("first").build()); + blockingStub.sayHello(Request.newBuilder().setName("second").build()); + + writer.waitForTraces(4); + + int clientSpanCount = 0; + int serverSpanCount = 0; + for (List trace : writer) { + for (DDSpan span : trace) { + if ("grpc.client".equals(span.getOperationName().toString())) { + clientSpanCount++; + } + if ("grpc.server".equals(span.getOperationName().toString())) { + serverSpanCount++; + } + } + } + + assertEquals(2, clientSpanCount, "Expected 2 client spans for 2 RPC calls"); + assertEquals(2, serverSpanCount, "Expected 2 server spans for 2 RPC calls"); + } + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpan(List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + @Override + public void sayHello(Request request, StreamObserver responseObserver) { + responseObserver.onNext( + Response.newBuilder().setMessage("Hello " + request.getName()).build()); + responseObserver.onCompleted(); + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto b/dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto new file mode 100644 index 00000000000..0ed794969d1 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/test/proto/helloworld.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package example; + +option java_package = "example"; +option java_outer_classname = "HelloworldProto"; + +service Greeter { + rpc SayHello (Request) returns (Response) {} + rpc ServerStreamHello (Request) returns (stream Response) {} + rpc ClientStreamHello (stream Request) returns (Response) {} + rpc BidiStreamHello (stream Request) returns (stream Response) {} +} + +message Request { + string name = 1; +} + +message Response { + string message = 1; +} From ff5a133f45baedfb53ea1b23c825b3c6b0d04044 Mon Sep 17 00:00:00 2001 From: Jordan Wong Date: Wed, 2 Sep 2026 16:26:44 -0400 Subject: [PATCH 3/3] workflow(grpc): integration_code --- .../grpc/QueuedCommandInstrumentation.java | 86 ++++ .../AbstractClientStreamInstrumentation.java | 54 +++ .../client/ClientCallImplInstrumentation.java | 182 +++++++++ ...ientStreamListenerImplInstrumentation.java | 104 +++++ .../grpc/client/GrpcClientDecorator.java | 126 ++++++ .../grpc/client/GrpcClientModule.java | 47 +++ .../grpc/client/GrpcInjectAdapter.java | 27 ++ .../MessagesAvailableInstrumentation.java | 75 ++++ .../grpc/server/GrpcExtractAdapter.java | 27 ++ .../GrpcServerBuilderInstrumentation.java | 80 ++++ .../grpc/server/GrpcServerDecorator.java | 125 ++++++ .../grpc/server/GrpcServerModule.java | 49 +++ .../server/MethodHandlersInstrumentation.java | 73 ++++ .../grpc/server/TracingServerInterceptor.java | 369 ++++++++++++++++++ 14 files changed, 1424 insertions(+) create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java create mode 100644 dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java new file mode 100644 index 00000000000..34d75ef9049 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/QueuedCommandInstrumentation.java @@ -0,0 +1,86 @@ +package datadog.trace.instrumentation.grpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.capture; +import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.endTaskScope; +import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.startTaskScope; +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.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.context.ContextScope; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.java.concurrent.QueueTimerHelper; +import datadog.trace.bootstrap.instrumentation.java.concurrent.State; +import java.nio.channels.Channel; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumenterModule.class) +public final class QueuedCommandInstrumentation extends InstrumenterModule.Profiling + implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice { + + private static final String QUEUED_COMMAND = "io.grpc.netty.WriteQueue$QueuedCommand"; + private static final String STATE = + "datadog.trace.bootstrap.instrumentation.java.concurrent.State"; + + public QueuedCommandInstrumentation() { + super("grpc", "grpc-netty"); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(isConstructor(), getClass().getName() + "$Construct"); + transformer.applyAdvice( + isMethod() + .and( + named("run") + .and( + takesArguments(1) + .and(takesArgument(0, named("io.netty.channel.Channel"))))), + getClass().getName() + "$Run"); + } + + @Override + public Map contextStore() { + return Collections.singletonMap(QUEUED_COMMAND, STATE); + } + + @Override + public String[] knownMatchingTypes() { + return new String[] { + "io.grpc.netty.WriteQueue$AbstractQueuedCommand", + "io.grpc.netty.WriteQueue$RunnableCommand", + "io.grpc.netty.SendGrpcFrameCommand" + }; + } + + public static final class Construct { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void after(@Advice.This Object command) { + ContextStore contextStore = InstrumentationContext.get(QUEUED_COMMAND, STATE); + capture(contextStore, command); + QueueTimerHelper.startQueuingTimer( + contextStore, Channel.class, ConcurrentLinkedQueue.class, 0, command); + } + } + + public static final class Run { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static ContextScope before(@Advice.This Object command) { + return startTaskScope(InstrumentationContext.get(QUEUED_COMMAND, STATE), command); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter ContextScope scope) { + endTaskScope(scope); + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java new file mode 100644 index 00000000000..6caaa52af1b --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/AbstractClientStreamInstrumentation.java @@ -0,0 +1,54 @@ +package datadog.trace.instrumentation.grpc.client; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import io.grpc.internal.ClientStreamListener; +import net.bytebuddy.asm.Advice; + +public final class AbstractClientStreamInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "io.grpc.internal.AbstractClientStream"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("start") + .and( + isMethod() + .and( + takesArgument(0, named("io.grpc.internal.ClientStreamListener")) + .and(takesArguments(1)))), + getClass().getName() + "$ActivateSpan"); + } + + public static final class ActivateSpan { + @Advice.OnMethodEnter + public static AgentScope before(@Advice.Argument(0) ClientStreamListener listener) { + AgentSpan span = + InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); + if (null != span) { + return activateSpan(span); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter AgentScope scope) { + if (null != scope) { + scope.close(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java new file mode 100644 index 00000000000..8db041f8695 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java @@ -0,0 +1,182 @@ +package datadog.trace.instrumentation.grpc.client; + +import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.DECORATE; +import static datadog.trace.instrumentation.grpc.client.GrpcInjectAdapter.SETTER; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import io.grpc.ClientCall; +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.grpc.StatusException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import net.bytebuddy.asm.Advice; + +public final class ClientCallImplInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "io.grpc.internal.ClientCallImpl"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(isConstructor(), getClass().getName() + "$Capture"); + transformer.applyAdvices( + named("start").and(isMethod()), + getClass().getName() + "$Start", + getClass().getName() + "$StartContextPropagationAdvice"); + transformer.applyAdvice(named("cancel").and(isMethod()), getClass().getName() + "$Cancel"); + transformer.applyAdvice( + named("request") + .and(isMethod()) + .and(takesArguments(int.class)) + .or(isMethod().and(named("halfClose").and(takesArguments(0)))), + getClass().getName() + "$ActivateSpan"); + transformer.applyAdvice( + named("sendMessage").and(isMethod()), getClass().getName() + "$SendMessage"); + transformer.applyAdvice( + named("closeObserver").and(takesArguments(3)), getClass().getName() + "$CloseObserver"); + } + + public static final class Capture { + @Advice.OnMethodExit + public static void capture( + @Advice.This io.grpc.ClientCall call, + @Advice.Argument(0) MethodDescriptor method) { + AgentSpan span = DECORATE.startCall(method); + if (null != span) { + InstrumentationContext.get(ClientCall.class, AgentSpan.class).put(call, span); + } + } + } + + public static final class Start { + @Advice.OnMethodEnter + public static AgentScope before( + @Advice.This ClientCall call, + @Advice.Argument(1) Metadata headers, + @Advice.Local("$$ddSpan") AgentSpan span) { + span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).get(call); + if (null != span) { + return activateSpan(span); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after( + @Advice.Enter AgentScope scope, + @Advice.Thrown Throwable error, + @Advice.Local("$$ddSpan") AgentSpan span) + throws Throwable { + if (null != scope) { + scope.close(); + } + if (null != error && null != span) { + DECORATE.onError(span, error); + DECORATE.beforeFinish(span); + span.finish(); + throw error; + } + } + } + + @AppliesOn(CONTEXT_TRACKING) + public static final class StartContextPropagationAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void before(@Advice.Argument(1) Metadata headers) { + AgentSpan span = activeSpan(); + if (span == null) { + return; + } + DECORATE.injectContext(span, headers, SETTER); + } + } + + public static final class ActivateSpan { + @Advice.OnMethodEnter + public static AgentScope before(@Advice.This ClientCall call) { + AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).get(call); + if (null != span) { + return activateSpan(span); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter AgentScope scope) { + if (null != scope) { + scope.close(); + } + } + } + + public static final class SendMessage { + @Advice.OnMethodEnter + public static AgentScope before(@Advice.This ClientCall call) { + AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).get(call); + if (span != null) { + return activateSpan(span); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter AgentScope scope) { + if (null != scope) { + scope.close(); + } + } + } + + public static final class Cancel { + @Advice.OnMethodEnter + public static void before( + @Advice.This ClientCall call, @Advice.Argument(1) Throwable cause) { + AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).remove(call); + if (null != span) { + final SocketAddress socketAddress = + call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + if (socketAddress instanceof InetSocketAddress) { + DECORATE.onPeerConnection(span, (InetSocketAddress) socketAddress); + } + if (cause instanceof StatusException) { + DECORATE.onClose(span, ((StatusException) cause).getStatus()); + } + span.finish(); + } + } + } + + public static final class CloseObserver { + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void closeObserver( + @Advice.This ClientCall call, @Advice.Argument(1) Status status) { + AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).remove(call); + if (null != span) { + final SocketAddress socketAddress = + call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + if (socketAddress instanceof InetSocketAddress) { + DECORATE.onPeerConnection(span, (InetSocketAddress) socketAddress); + } + DECORATE.onClose(span, status); + span.finish(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java new file mode 100644 index 00000000000..657d3477b49 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientStreamListenerImplInstrumentation.java @@ -0,0 +1,104 @@ +package datadog.trace.instrumentation.grpc.client; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.DECORATE; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import io.grpc.Status; +import io.grpc.internal.ClientStreamListener; +import net.bytebuddy.asm.Advice; + +public class ClientStreamListenerImplInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(isConstructor(), getClass().getName() + "$Construct"); + transformer.applyAdvice( + named("exceptionThrown") + .and(takesArgument(0, named("io.grpc.Status"))) + .and(takesArguments(1)), + getClass().getName() + "$ExceptionThrown"); + transformer.applyAdvice( + namedOneOf("messageRead", "messagesAvailable"), getClass().getName() + "$RecordActivity"); + transformer.applyAdvice(named("headersRead"), getClass().getName() + "$RecordHeaders"); + } + + public static final class Construct { + @Advice.OnMethodExit + public static void capture(@Advice.This ClientStreamListener listener) { + AgentSpan span = activeSpan(); + if (null != span) { + InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).put(listener, span); + } + } + } + + public static final class ExceptionThrown { + @Advice.OnMethodEnter + public static void exceptionThrown( + @Advice.This ClientStreamListener listener, @Advice.Argument(0) Status status) { + if (null != status) { + AgentSpan span = + InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); + if (null != span) { + DECORATE.onError(span, status.getCause()); + DECORATE.beforeFinish(span); + span.finish(); + } + } + } + } + + public static final class RecordActivity { + @Advice.OnMethodEnter + public static AgentScope before(@Advice.This ClientStreamListener listener) { + AgentSpan span = + InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); + if (span != null) { + return activateSpan(span); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter AgentScope scope) { + if (null != scope) { + scope.close(); + } + } + } + + public static final class RecordHeaders { + @Advice.OnMethodEnter + public static AgentScope before(@Advice.This ClientStreamListener listener) { + AgentSpan span = + InstrumentationContext.get(ClientStreamListener.class, AgentSpan.class).get(listener); + if (span != null) { + return activateSpan(span); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter AgentScope scope) { + if (null != scope) { + scope.close(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java new file mode 100644 index 00000000000..6ceda7d1efa --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientDecorator.java @@ -0,0 +1,126 @@ +package datadog.trace.instrumentation.grpc.client; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.api.datastreams.DataStreamsContext.fromTags; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.traceConfig; + +import datadog.context.Context; +import datadog.context.propagation.CarrierSetter; +import datadog.trace.api.Config; +import datadog.trace.api.GenericClassValue; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.ClientDecorator; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import java.util.BitSet; +import java.util.Set; +import java.util.function.Function; + +public class GrpcClientDecorator extends ClientDecorator { + public static final CharSequence OPERATION_NAME = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().client().operationForProtocol("grpc")); + public static final CharSequence COMPONENT_NAME = UTF8BytesString.create("grpc-client"); + public static final CharSequence GRPC_MESSAGE = UTF8BytesString.create("grpc.message"); + + private static DataStreamsContext createDsmContext() { + return fromTags(create("grpc", OUTBOUND)); + } + + public static final GrpcClientDecorator DECORATE = new GrpcClientDecorator(); + + private static final Set IGNORED_METHODS = Config.get().getGrpcIgnoredOutboundMethods(); + private static final BitSet CLIENT_ERROR_STATUSES = Config.get().getGrpcClientErrorStatuses(); + + private static final ClassValue MESSAGE_TYPES = + GenericClassValue.of( + new Function, UTF8BytesString>() { + @Override + public UTF8BytesString apply(Class input) { + return UTF8BytesString.create(input.getName()); + } + }); + + private static final DDCache RPC_SERVICE_CACHE = DDCaches.newFixedSizeCache(64); + + public UTF8BytesString requestMessageType(MethodDescriptor method) { + return messageType(method.getRequestMarshaller()); + } + + public UTF8BytesString responseMessageType(MethodDescriptor method) { + return messageType(method.getResponseMarshaller()); + } + + private UTF8BytesString messageType(MethodDescriptor.Marshaller marshaller) { + return marshaller instanceof MethodDescriptor.ReflectableMarshaller + ? MESSAGE_TYPES.get( + ((MethodDescriptor.ReflectableMarshaller) marshaller).getMessageClass()) + : null; + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"grpc", "grpc-client"}; + } + + @Override + protected CharSequence component() { + return COMPONENT_NAME; + } + + @Override + protected CharSequence spanType() { + return InternalSpanTypes.RPC; + } + + @Override + protected String service() { + return null; + } + + public AgentSpan startCall(MethodDescriptor method) { + if (IGNORED_METHODS.contains(method.getFullMethodName())) { + return AgentTracer.blackholeSpan(); + } + AgentSpan span = + startSpan(COMPONENT_NAME.toString(), OPERATION_NAME) + .setTag("request.type", requestMessageType(method)) + .setTag("response.type", responseMessageType(method)) + .setTag( + Tags.RPC_SERVICE, + RPC_SERVICE_CACHE.computeIfAbsent( + method.getFullMethodName(), MethodDescriptor::extractFullServiceName)); + span.setResourceName(method.getFullMethodName()); + afterStart(span); + return span; + } + + public void injectContext(Context context, final C request, CarrierSetter setter) { + if (traceConfig().isDataStreamsEnabled()) { + context = context.with(createDsmContext()); + } + defaultPropagator().inject(context, request, setter); + } + + public void onClose(final AgentSpan span, final Status status) { + span.setTag("status.code", status.getCode().name()); + span.setTag("grpc.status.code", status.getCode().name()); + span.setTag(InstrumentationTags.GRPC_STATUS_CODE, status.getCode().value()); + span.setTag("status.description", status.getDescription()); + + onError(span, status.getCause()); + span.setError(CLIENT_ERROR_STATUSES.get(status.getCode().value())); + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java new file mode 100644 index 00000000000..4322fa3d674 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcClientModule.java @@ -0,0 +1,47 @@ +package datadog.trace.instrumentation.grpc.client; + +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.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.java.concurrent.State; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@AutoService(InstrumenterModule.class) +public class GrpcClientModule extends InstrumenterModule.Tracing { + + public GrpcClientModule() { + super("grpc", "grpc-client", "grpc-message"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".GrpcClientDecorator", + packageName + ".GrpcClientDecorator$1", + packageName + ".GrpcInjectAdapter" + }; + } + + @Override + public Map contextStore() { + Map contextStores = new HashMap<>(); + contextStores.put("io.grpc.ClientCall", AgentSpan.class.getName()); + contextStores.put("io.grpc.internal.ClientStreamListener", AgentSpan.class.getName()); + contextStores.put(Runnable.class.getName(), State.class.getName()); + return contextStores; + } + + @Override + public List typeInstrumentations() { + return asList( + new ClientCallImplInstrumentation(), + new AbstractClientStreamInstrumentation(), + new ClientStreamListenerImplInstrumentation(), + new MessagesAvailableInstrumentation()); + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java new file mode 100644 index 00000000000..d896ba4e26d --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/GrpcInjectAdapter.java @@ -0,0 +1,27 @@ +package datadog.trace.instrumentation.grpc.client; + +import datadog.context.propagation.CarrierSetter; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import io.grpc.Metadata; +import java.util.function.Function; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +public final class GrpcInjectAdapter implements CarrierSetter { + private static final Function> KEY_MAKER = + key -> Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + private static final DDCache> KEY_CACHE = + DDCaches.newFixedSizeCache(64); + + public static final GrpcInjectAdapter SETTER = new GrpcInjectAdapter(); + + @Override + public void set(final Metadata carrier, final String key, final String value) { + Metadata.Key metadataKey = KEY_CACHE.computeIfAbsent(key, KEY_MAKER); + if (carrier.containsKey(metadataKey)) { + carrier.removeAll(metadataKey); + } + carrier.put(metadataKey, value); + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java new file mode 100644 index 00000000000..e72ef5d5431 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/MessagesAvailableInstrumentation.java @@ -0,0 +1,75 @@ +package datadog.trace.instrumentation.grpc.client; + +import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.COMPONENT_NAME; +import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.DECORATE; +import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.GRPC_MESSAGE; +import static datadog.trace.instrumentation.grpc.client.GrpcClientDecorator.OPERATION_NAME; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.api.InstrumenterConfig; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils; +import datadog.trace.bootstrap.instrumentation.java.concurrent.State; +import java.util.Collections; +import net.bytebuddy.asm.Advice; + +public final class MessagesAvailableInstrumentation + implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice { + + @Override + public String[] knownMatchingTypes() { + return new String[] { + "io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable", + "io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessageRead" + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(isConstructor(), getClass().getName() + "$Capture"); + if (InstrumenterConfig.get() + .isIntegrationEnabled(Collections.singleton("grpc-message"), false)) { + transformer.applyAdvice(named("runInContext"), getClass().getName() + "$ReceiveMessages"); + } + } + + @AppliesOn(CONTEXT_TRACKING) + public static final class Capture { + @Advice.OnMethodExit + public static void capture(@Advice.This Runnable task) { + AdviceUtils.capture(InstrumentationContext.get(Runnable.class, State.class), task); + } + } + + public static final class ReceiveMessages { + @Advice.OnMethodEnter + public static AgentScope before() { + AgentSpan clientSpan = activeSpan(); + if (clientSpan != null && OPERATION_NAME.equals(clientSpan.getOperationName())) { + AgentSpan messageSpan = + startSpan(COMPONENT_NAME.toString(), GRPC_MESSAGE) + .setTag("message.type", clientSpan.getTag("response.type")); + DECORATE.afterStart(messageSpan); + return activateSpan(messageSpan); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void after(@Advice.Enter AgentScope scope) { + if (null != scope) { + scope.close(); + scope.span().finish(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java new file mode 100644 index 00000000000..27a8804651c --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcExtractAdapter.java @@ -0,0 +1,27 @@ +package datadog.trace.instrumentation.grpc.server; + +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import io.grpc.Metadata; +import java.util.function.Function; + +public final class GrpcExtractAdapter implements AgentPropagation.ContextVisitor { + private static final Function> KEY_MAKER = + key -> Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + private static final DDCache> KEY_CACHE = + DDCaches.newFixedSizeCache(64); + + public static final GrpcExtractAdapter GETTER = new GrpcExtractAdapter(); + + @Override + public void forEachKey(Metadata carrier, AgentPropagation.KeyClassifier classifier) { + for (String key : carrier.keys()) { + if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + if (!classifier.accept(key, carrier.get(KEY_CACHE.computeIfAbsent(key, KEY_MAKER)))) { + return; + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java new file mode 100644 index 00000000000..4e982e6f031 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerBuilderInstrumentation.java @@ -0,0 +1,80 @@ +package datadog.trace.instrumentation.grpc.server; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.CallDepthThreadLocalMap.incrementCallDepth; +import static java.util.Arrays.asList; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.InstrumenterConfig; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import io.grpc.ServerBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class GrpcServerBuilderInstrumentation + implements Instrumenter.CanShortcutTypeMatching, Instrumenter.HasMethodAdvice { + + @Override + public boolean onlyMatchKnownTypes() { + return InstrumenterConfig.get() + .isIntegrationShortcutMatchingEnabled(asList("grpc", "grpc-server"), true); + } + + @Override + public String[] knownMatchingTypes() { + return new String[] { + "io.grpc.internal.AbstractServerImplBuilder", + "io.grpc.alts.AltsServerBuilder", + "io.grpc.ForwardingServerBuilder", + "io.grpc.inprocess.InProcessServerBuilder", + "io.grpc.netty.NettyServerBuilder", + "io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder", + "io.grpc.internal.ServerImplBuilder" + }; + } + + @Override + public String hierarchyMarkerType() { + return "io.grpc.ServerBuilder"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return extendsClass(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(isPublic()).and(named("build")).and(takesArguments(0)), + GrpcServerBuilderInstrumentation.class.getName() + "$BuildAdvice"); + } + + public static class BuildAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.This ServerBuilder serverBuilder) { + int callDepth = incrementCallDepth(ServerBuilder.class); + if (callDepth == 0) { + ContextStore interceptedStore = + InstrumentationContext.get(ServerBuilder.class, Boolean.class); + if (interceptedStore.get(serverBuilder) == null) { + interceptedStore.put(serverBuilder, Boolean.TRUE); + serverBuilder.intercept(TracingServerInterceptor.INSTANCE); + } + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit() { + CallDepthThreadLocalMap.decrementCallDepth(ServerBuilder.class); + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java new file mode 100644 index 00000000000..0126ae15ad4 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerDecorator.java @@ -0,0 +1,125 @@ +package datadog.trace.instrumentation.grpc.server; + +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; + +import datadog.trace.api.Config; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.ServerDecorator; +import io.grpc.ServerCall; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; +import java.util.BitSet; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class GrpcServerDecorator extends ServerDecorator { + + private static final boolean TRIM_RESOURCE_PACKAGE_NAME = + Config.get().isGrpcServerTrimPackageResource(); + private static final BitSet SERVER_ERROR_STATUSES = Config.get().getGrpcServerErrorStatuses(); + + public static final CharSequence GRPC_SERVER = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().server().operationForProtocol("grpc")); + public static final CharSequence COMPONENT_NAME = UTF8BytesString.create("grpc-server"); + public static final CharSequence GRPC_MESSAGE = UTF8BytesString.create("grpc.message"); + + private static DataStreamsTags createServerPathwaySortedTags() { + return create("grpc", INBOUND); + } + + public static final DataStreamsTags SERVER_PATHWAY_EDGE_TAGS = createServerPathwaySortedTags(); + public static final GrpcServerDecorator DECORATE = new GrpcServerDecorator(); + + private static final Function NORMALIZE = + // Uses inner class for predictable name for Instrumenter.Default.helperClassNames() + new Function() { + @Override + public String apply(String fullName) { + int index = fullName.lastIndexOf("."); + if (index > 0) { + return fullName.substring(index + 1); + } else { + return fullName; + } + } + }; + + private final DDCache cachedResourceNames; + + public GrpcServerDecorator() { + if (TRIM_RESOURCE_PACKAGE_NAME) { + cachedResourceNames = DDCaches.newFixedSizeCache(512); + } else { + cachedResourceNames = null; + } + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"grpc", "grpc-server"}; + } + + @Override + protected CharSequence spanType() { + return InternalSpanTypes.RPC; + } + + @Override + protected CharSequence component() { + return COMPONENT_NAME; + } + + @Override + protected void doAfterStart(@Nonnull final AgentSpan span) { + span.setMeasured(true); + super.doAfterStart(span); + } + + public void onCall(final AgentSpan span, ServerCall call) { + if (TRIM_RESOURCE_PACKAGE_NAME) { + span.setResourceName( + cachedResourceNames.computeIfAbsent( + call.getMethodDescriptor().getFullMethodName(), NORMALIZE)); + } else { + span.setResourceName(call.getMethodDescriptor().getFullMethodName()); + } + } + + public void onStatus(final AgentSpan span, final Status status) { + span.setTag("status.code", status.getCode().name()); + span.setTag("grpc.status.code", status.getCode().name()); + span.setTag(InstrumentationTags.GRPC_STATUS_CODE, status.getCode().value()); + span.setTag("status.description", status.getDescription()); + span.setError( + SERVER_ERROR_STATUSES.get(status.getCode().value()), ErrorPriorities.HTTP_SERVER_DECORATOR); + } + + public void onClose(final AgentSpan span, final Status status) { + if (status.getCause() != null) { + onError(span, status.getCause()); + } + onStatus(span, status); + } + + @Override + protected void doOnError( + @Nonnull AgentSpan span, @Nonnull Throwable throwable, byte errorPriority) { + super.doOnError(span, throwable, ErrorPriorities.HTTP_SERVER_DECORATOR); + if (throwable instanceof StatusRuntimeException) { + onStatus(span, ((StatusRuntimeException) throwable).getStatus()); + } else if (throwable instanceof StatusException) { + onStatus(span, ((StatusException) throwable).getStatus()); + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java new file mode 100644 index 00000000000..b25894bb35d --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/GrpcServerModule.java @@ -0,0 +1,49 @@ +package datadog.trace.instrumentation.grpc.server; + +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; + +import com.google.auto.service.AutoService; +import datadog.environment.JavaVirtualMachine; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.InstrumenterConfig; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@AutoService(InstrumenterModule.class) +public class GrpcServerModule extends InstrumenterModule.Tracing { + public GrpcServerModule() { + super("grpc", "grpc-server"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".GrpcServerDecorator", + packageName + ".GrpcServerDecorator$1", + packageName + ".GrpcExtractAdapter", + packageName + ".TracingServerInterceptor", + packageName + ".TracingServerInterceptor$TracingServerCall", + packageName + ".TracingServerInterceptor$TracingServerCallListener", + }; + } + + @Override + public Map contextStore() { + return singletonMap("io.grpc.ServerBuilder", Boolean.class.getName()); + } + + @Override + public List typeInstrumentations() { + final List ret = new ArrayList<>(2); + ret.add(new GrpcServerBuilderInstrumentation()); + if (!JavaVirtualMachine.isGraalVM() + && InstrumenterConfig.get() + .isIntegrationEnabled(singleton("grpc-server-code-origin"), true)) { + ret.add(new MethodHandlersInstrumentation()); + } + return ret; + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java new file mode 100644 index 00000000000..18afa651be1 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/MethodHandlersInstrumentation.java @@ -0,0 +1,73 @@ +package datadog.trace.instrumentation.grpc.server; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameEndsWith; +import static datadog.trace.bootstrap.debugger.DebuggerContext.captureCodeOrigin; +import static datadog.trace.bootstrap.debugger.DebuggerContext.marker; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import java.lang.reflect.Method; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class MethodHandlersInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private static final ElementMatcher METHOD_HANDLERS = + nameEndsWith("$MethodHandlers"); + + @Override + public String hierarchyMarkerType() { + return "io.grpc.MethodDescriptor"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return METHOD_HANDLERS; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isConstructor().and(takesArguments(2)), + "datadog.trace.instrumentation.grpc.server.MethodHandlersInstrumentation$BuildAdvice"); + } + + public static class BuildAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.Argument(0) Object serviceImpl) { + try { + Class serviceClass = serviceImpl.getClass(); + Class superClass = serviceClass.getSuperclass(); + while (superClass != null && !superClass.getSimpleName().endsWith("ImplBase")) { + superClass = superClass.getSuperclass(); + } + if (superClass != null) { + // bindService() would be the only method in this case and it's irrelevant + if (superClass.getDeclaredMethods().length == 1) { + for (Class i : superClass.getInterfaces()) { + if (i.getSimpleName().equals("AsyncService")) { + superClass = i; + break; + } + } + } + for (Method method : superClass.getDeclaredMethods()) { + try { + marker(); + captureCodeOrigin( + serviceClass.getDeclaredMethod(method.getName(), method.getParameterTypes()), + true); + } catch (Throwable e) { + // service method not overridden on the impl. skipping instrumentation. + } + } + } + } catch (Throwable e) { + // this should be logged somehow + } + } + } +} diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java new file mode 100644 index 00000000000..7b45d3b1577 --- /dev/null +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/server/TracingServerInterceptor.java @@ -0,0 +1,369 @@ +package datadog.trace.instrumentation.grpc.server; + +import static datadog.trace.api.datastreams.DataStreamsContext.fromTags; +import static datadog.trace.api.gateway.Events.EVENTS; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.grpc.server.GrpcExtractAdapter.GETTER; +import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.COMPONENT_NAME; +import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.DECORATE; +import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.GRPC_MESSAGE; +import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.GRPC_SERVER; +import static datadog.trace.instrumentation.grpc.server.GrpcServerDecorator.SERVER_PATHWAY_EDGE_TAGS; + +import datadog.context.ContextScope; +import datadog.trace.api.Config; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.api.function.TriConsumer; +import datadog.trace.api.function.TriFunction; +import datadog.trace.api.gateway.CallbackProvider; +import datadog.trace.api.gateway.Flow; +import datadog.trace.api.gateway.IGSpanInfo; +import datadog.trace.api.gateway.RequestContext; +import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.TagContext; +import io.grpc.ForwardingServerCall; +import io.grpc.ForwardingServerCallListener; +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.Status; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class TracingServerInterceptor implements ServerInterceptor { + private static final Function> KEY_MAKER = + key -> Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + private static final DDCache> KEY_CACHE = + DDCaches.newFixedSizeCache(64); + + public static final TracingServerInterceptor INSTANCE = new TracingServerInterceptor(); + private static final Set IGNORED_METHODS = Config.get().getGrpcIgnoredInboundMethods(); + + private TracingServerInterceptor() {} + + protected static AgentTracer.TracerAPI tracer() { + return AgentTracer.get(); + } + + @Override + public ServerCall.Listener interceptCall( + final ServerCall call, + final Metadata headers, + final ServerCallHandler next) { + if (IGNORED_METHODS.contains(call.getMethodDescriptor().getFullMethodName())) { + return next.startCall(call, headers); + } + + AgentSpanContext spanContext = extractContextAndGetSpanContext(headers, GETTER); + AgentTracer.TracerAPI tracer = tracer(); + spanContext = callIGCallbackRequestStarted(tracer, spanContext); + + CallbackProvider cbp = tracer.getCallbackProvider(RequestContextSlot.APPSEC); + final AgentSpan span = + startSpan(COMPONENT_NAME.toString(), GRPC_SERVER, spanContext).setMeasured(true); + + AgentTracer.get() + .getDataStreamsMonitoring() + .setCheckpoint(span, fromTags(SERVER_PATHWAY_EDGE_TAGS)); + + RequestContext reqContext = span.getRequestContext(); + if (reqContext != null) { + callIGCallbackClientAddress(cbp, reqContext, call); + callIGCallbackHeaders(cbp, reqContext, headers); + callIGCallbackGrpcServerMethod(cbp, reqContext, call.getMethodDescriptor()); + } + + DECORATE.afterStart(span); + DECORATE.onCall(span, call); + + final ServerCall.Listener result; + try (ContextScope scope = activateSpan(span)) { + // Wrap the server call so that we can decorate the span + // with the resulting status + final TracingServerCall tracingServerCall = new TracingServerCall<>(span, call); + // call other interceptors + result = next.startCall(tracingServerCall, headers); + } catch (final Throwable e) { + if (span.phasedFinish()) { + DECORATE.onError(span, e); + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + throw e; + } + + // This ensures the server implementation can see the span in scope + return new TracingServerCallListener<>(span, result); + } + + static final class TracingServerCall + extends ForwardingServerCall.SimpleForwardingServerCall { + final AgentSpan span; + + TracingServerCall(final AgentSpan span, final ServerCall delegate) { + super(delegate); + this.span = span; + } + + @Override + public void close(final Status status, final Metadata trailers) { + DECORATE.onClose(span, status); + try (final ContextScope scope = activateSpan(span)) { + delegate().close(status, trailers); + } catch (final Throwable e) { + DECORATE.onError(span, e); + throw e; + } finally { + if (span.phasedFinish()) { + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + } + } + } + + static final class TracingServerCallListener + extends ForwardingServerCallListener.SimpleForwardingServerCallListener { + private final AgentSpan span; + + TracingServerCallListener(final AgentSpan span, final ServerCall.Listener delegate) { + super(delegate); + this.span = span; + } + + @Override + public void onMessage(final ReqT message) { + final AgentSpan msgSpan = + startSpan(COMPONENT_NAME.toString(), GRPC_MESSAGE, this.span.spanContext()) + .setTag("message.type", message.getClass().getName()); + DECORATE.afterStart(msgSpan); + try (ContextScope scope = activateSpan(msgSpan)) { + callIGCallbackGrpcMessage(msgSpan, message); + delegate().onMessage(message); + } catch (final Throwable e) { + // I'm not convinced we should actually be finishing the span here... + if (span.phasedFinish()) { + DECORATE.onError(msgSpan, e); + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + throw e; + } finally { + DECORATE.beforeFinish(msgSpan); + msgSpan.finish(); + } + } + + @Override + public void onHalfClose() { + try (final ContextScope scope = activateSpan(span)) { + delegate().onHalfClose(); + } catch (final Throwable e) { + if (span.phasedFinish()) { + DECORATE.onError(span, e); + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + throw e; + } + } + + @Override + public void onCancel() { + // Finishes span. + try (final ContextScope scope = activateSpan(span)) { + delegate().onCancel(); + span.setTag("canceled", true); + } catch (CancellationException e) { + // No need to report an exception or mark as error that it was canceled. + throw e; + } catch (final Throwable e) { + DECORATE.onError(span, e); + throw e; + } finally { + if (span.phasedFinish()) { + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + } + } + + @Override + public void onComplete() { + // Finishes span. + try (final ContextScope scope = activateSpan(span)) { + delegate().onComplete(); + } catch (final Throwable e) { + DECORATE.onError(span, e); + throw e; + } finally { + /** + * grpc has quite a few states that can finish the span. rather than track down the correct + * combination of them to exclusively finish the span, use phasedFinish. + */ + if (span.phasedFinish()) { + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + } + } + + @Override + public void onReady() { + try (final ContextScope scope = activateSpan(span)) { + delegate().onReady(); + } catch (final Throwable e) { + if (span.phasedFinish()) { + DECORATE.onError(span, e); + DECORATE.beforeFinish(span); + callIGCallbackRequestEnded(span); + span.publish(); + } + throw e; + } + } + } + + // IG helpers follow + + private static AgentSpanContext callIGCallbackRequestStarted( + AgentTracer.TracerAPI cbp, AgentSpanContext context) { + Supplier> startedCbAppSec = + cbp.getCallbackProvider(RequestContextSlot.APPSEC).getCallback(EVENTS.requestStarted()); + Supplier> startedCbIast = + cbp.getCallbackProvider(RequestContextSlot.IAST).getCallback(EVENTS.requestStarted()); + + if (startedCbAppSec == null && startedCbIast == null) { + return context; + } + + TagContext tagContext = null; + if (context == null) { + tagContext = new TagContext(); + } else if (context instanceof TagContext) { + tagContext = (TagContext) context; + } + if (tagContext != null) { + if (startedCbAppSec != null) { + Flow flowAppSec = startedCbAppSec.get(); + tagContext.withRequestContextDataAppSec(flowAppSec.getResult()); + } + if (startedCbIast != null) { + Flow flowIast = startedCbIast.get(); + tagContext.withRequestContextDataIast(flowIast.getResult()); + } + return tagContext; + } + + return context; + } + + private static void callIGCallbackClientAddress( + CallbackProvider cbp, RequestContext ctx, ServerCall call) { + SocketAddress socketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + TriFunction> cb = + cbp.getCallback(EVENTS.requestClientSocketAddress()); + if (socketAddress == null || !(socketAddress instanceof InetSocketAddress) || cb == null) { + return; + } + + InetSocketAddress inetSockAddr = (InetSocketAddress) socketAddress; + cb.apply(ctx, inetSockAddr.getHostString(), inetSockAddr.getPort()); + } + + private static void callIGCallbackHeaders( + CallbackProvider cbp, RequestContext reqCtx, Metadata metadata) { + TriConsumer headerCb = cbp.getCallback(EVENTS.requestHeader()); + Function> headerEndCb = cbp.getCallback(EVENTS.requestHeaderDone()); + if (headerCb == null || headerEndCb == null) { + return; + } + for (String key : metadata.keys()) { + if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + Metadata.Key mdKey = KEY_CACHE.computeIfAbsent(key, KEY_MAKER); + for (String value : metadata.getAll(mdKey)) { + headerCb.accept(reqCtx, key, value); + } + } + } + + headerEndCb.apply(reqCtx); + } + + private static void callIGCallbackRequestEnded(@Nonnull final AgentSpan span) { + CallbackProvider cbp = tracer().getUniversalCallbackProvider(); + if (cbp == null) { + return; + } + RequestContext requestContext = span.getRequestContext(); + if (requestContext != null) { + BiFunction> callback = + cbp.getCallback(EVENTS.requestEnded()); + if (callback != null) { + callback.apply(requestContext, span); + } + } + } + + private static void callIGCallbackGrpcServerMethod( + CallbackProvider cbp, RequestContext ctx, MethodDescriptor methodDescriptor) { + String method = methodDescriptor.getFullMethodName(); + BiFunction> cb = cbp.getCallback(EVENTS.grpcServerMethod()); + if (method == null || cb == null) { + return; + } + cb.apply(ctx, method); + } + + private static void callIGCallbackGrpcMessage(@Nonnull final AgentSpan span, Object obj) { + if (obj == null) { + return; + } + CallbackProvider cbpAppsec = tracer().getCallbackProvider(RequestContextSlot.APPSEC); + CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpAppsec == null && cbpIast == null) { + return; + } + RequestContext requestContext = span.getRequestContext(); + if (requestContext == null) { + return; + } + + if (cbpAppsec != null) { + BiFunction> callback = + cbpAppsec.getCallback(EVENTS.grpcServerRequestMessage()); + if (callback != null) { + callback.apply(requestContext, obj); + } + } + + if (cbpIast != null) { + BiFunction> callback = + cbpIast.getCallback(EVENTS.grpcServerRequestMessage()); + if (callback != null) { + callback.apply(requestContext, obj); + } + } + } +}