diff --git a/dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy b/dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy index 7b63c72264e..12ac772118f 100644 --- a/dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy @@ -61,6 +61,7 @@ class AWSClientTest extends AgentTestRunner { false | 1 } + @Timeout(10) def "send request with mocked back end"() { setup: def receivedHeaders = new AtomicReference() diff --git a/dd-java-agent/instrumentation/jedis-1.4/jedis-1.4.gradle b/dd-java-agent/instrumentation/jedis-1.4/jedis-1.4.gradle new file mode 100644 index 00000000000..8213199b91a --- /dev/null +++ b/dd-java-agent/instrumentation/jedis-1.4/jedis-1.4.gradle @@ -0,0 +1,28 @@ +apply plugin: 'version-scan' + +versionScan { + group = "redis.clients" + module = "jedis" + versions = "[1.4.0,)" + legacyModule = "jms-api" + verifyPresent = [ + 'redis.clients.jedis.Protocol$Command': null, + ] +} + +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + compileOnly group: 'redis.clients', name: 'jedis', version: '1.4.0' + + compile project(':dd-java-agent:agent-tooling') + + compile deps.bytebuddy + compile deps.opentracing + compile deps.autoservice + + testCompile project(':dd-java-agent:testing') + testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' + testCompile group: 'redis.clients', name: 'jedis', version: '1.4.0' + +} diff --git a/dd-java-agent/instrumentation/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisInstrumentation.java b/dd-java-agent/instrumentation/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisInstrumentation.java new file mode 100644 index 00000000000..bdcd63f0546 --- /dev/null +++ b/dd-java-agent/instrumentation/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisInstrumentation.java @@ -0,0 +1,84 @@ +package datadog.trace.instrumentation.jedis; + +import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.DDAdvice; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.DDTags; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.tag.Tags; +import io.opentracing.util.GlobalTracer; +import java.util.Collections; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import redis.clients.jedis.Protocol.Command; + +@AutoService(Instrumenter.class) +public final class JedisInstrumentation extends Instrumenter.Configurable { + + private static final String SERVICE_NAME = "redis"; + private static final String COMPONENT_NAME = SERVICE_NAME + "-command"; + + public JedisInstrumentation() { + super(SERVICE_NAME); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public AgentBuilder apply(final AgentBuilder agentBuilder) { + return agentBuilder + .type( + named("redis.clients.jedis.Protocol"), + classLoaderHasClasses("redis.clients.jedis.Protocol$Command")) + .transform( + DDAdvice.create() + .advice( + isMethod() + .and(isPublic()) + .and(named("sendCommand")) + .and(takesArgument(1, named("redis.clients.jedis.Protocol$Command"))), + JedisAdvice.class.getName())) + .asDecorator(); + } + + public static class JedisAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope startSpan(@Advice.Argument(1) final Command command) { + + final Scope scope = GlobalTracer.get().buildSpan("redis.command").startActive(true); + + final Span span = scope.span(); + Tags.DB_TYPE.set(span, SERVICE_NAME); + Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); + Tags.COMPONENT.set(span, COMPONENT_NAME); + + span.setTag(DDTags.RESOURCE_NAME, command.name()); + span.setTag(DDTags.SERVICE_NAME, SERVICE_NAME); + span.setTag(DDTags.SPAN_TYPE, SERVICE_NAME); + + return scope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) { + if (throwable != null) { + final Span span = scope.span(); + Tags.ERROR.set(span, true); + span.log(Collections.singletonMap("error.object", throwable)); + } + scope.close(); + } + } +} diff --git a/dd-java-agent/instrumentation/jedis-1.4/src/test/groovy/JedisClientTest.groovy b/dd-java-agent/instrumentation/jedis-1.4/src/test/groovy/JedisClientTest.groovy new file mode 100644 index 00000000000..e0c30ea80e0 --- /dev/null +++ b/dd-java-agent/instrumentation/jedis-1.4/src/test/groovy/JedisClientTest.groovy @@ -0,0 +1,99 @@ +import datadog.opentracing.DDSpan +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.DDTags +import datadog.trace.instrumentation.jedis.JedisInstrumentation +import io.opentracing.tag.Tags +import redis.clients.jedis.Jedis +import redis.embedded.RedisServer +import spock.lang.Shared +import spock.lang.Timeout + +@Timeout(5) +class JedisClientTest extends AgentTestRunner { + + public static final int PORT = 6399 + + static { + System.setProperty("dd.integration.redis.enabled", "true") + } + + @Shared + RedisServer redisServer = RedisServer.builder() + // bind to localhost to avoid firewall popup + .setting("bind 127.0.0.1") + // set max memory to avoid problems in CI + .setting("maxmemory 128M") + .port(PORT).build() + @Shared + Jedis jedis = new Jedis("localhost", PORT) + + def setupSpec() { + println "Using redis: $redisServer.args" + redisServer.start() + } + + def cleanupSpec() { + redisServer.stop() +// jedis.close() // not available in the early version we're using. + } + + def setup() { + jedis.flushAll() + TEST_WRITER.start() + } + + def "set command"() { + jedis.set("foo", "bar") + + expect: + TEST_WRITER.size() == 1 + def trace = TEST_WRITER.firstTrace() + trace.size() == 1 + final DDSpan setTrace = trace.get(0) + setTrace.getServiceName() == JedisInstrumentation.SERVICE_NAME + setTrace.getOperationName() == "redis.query" + setTrace.getResourceName() == "SET" + setTrace.getTags().get(Tags.COMPONENT.getKey()) == JedisInstrumentation.COMPONENT_NAME + setTrace.getTags().get(Tags.DB_TYPE.getKey()) == JedisInstrumentation.SERVICE_NAME + setTrace.getTags().get(Tags.SPAN_KIND.getKey()) == Tags.SPAN_KIND_CLIENT + setTrace.getTags().get(DDTags.SPAN_TYPE) == JedisInstrumentation.SERVICE_NAME + } + + def "get command"() { + jedis.set("foo", "bar") + def value = jedis.get("foo") + + expect: + value == "bar" + TEST_WRITER.size() == 2 + def trace = TEST_WRITER.get(1) + trace.size() == 1 + final DDSpan getSpan = trace.get(0) + getSpan.getServiceName() == JedisInstrumentation.SERVICE_NAME + getSpan.getOperationName() == "redis.query" + getSpan.getResourceName() == "GET" + getSpan.getTags().get(Tags.COMPONENT.getKey()) == JedisInstrumentation.COMPONENT_NAME + getSpan.getTags().get(Tags.DB_TYPE.getKey()) == JedisInstrumentation.SERVICE_NAME + getSpan.getTags().get(Tags.SPAN_KIND.getKey()) == Tags.SPAN_KIND_CLIENT + getSpan.getTags().get(DDTags.SPAN_TYPE) == JedisInstrumentation.SERVICE_NAME + } + + def "command with no arguments"() { + jedis.set("foo", "bar") + def value = jedis.randomKey() + + expect: + value == "foo" + TEST_WRITER.size() == 2 + def trace = TEST_WRITER.get(1) + trace.size() == 1 + final DDSpan randomKeySpan = trace.get(0) + randomKeySpan.getServiceName() == JedisInstrumentation.SERVICE_NAME + randomKeySpan.getOperationName() == "redis.query" + randomKeySpan.getResourceName() == "RANDOMKEY" + randomKeySpan.getTags().get(Tags.COMPONENT.getKey()) == JedisInstrumentation.COMPONENT_NAME + randomKeySpan.getTags().get(Tags.DB_TYPE.getKey()) == JedisInstrumentation.SERVICE_NAME + randomKeySpan.getTags().get(Tags.SPAN_KIND.getKey()) == Tags.SPAN_KIND_CLIENT + randomKeySpan.getTags().get(DDTags.SPAN_TYPE) == JedisInstrumentation.SERVICE_NAME + } +} diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/WriterQueueTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/WriterQueueTest.groovy index 7632cca6d79..1402f4d85b1 100644 --- a/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/WriterQueueTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/WriterQueueTest.groovy @@ -7,7 +7,7 @@ import spock.lang.Timeout import java.util.concurrent.Phaser import java.util.concurrent.atomic.AtomicInteger -@Timeout(1) +@Timeout(5) class WriterQueueTest extends Specification { def "instantiate a empty queue throws an exception"() { @@ -120,7 +120,6 @@ class WriterQueueTest extends Specification { capacity = 100 numberThreads << [1, 10, 100] numberInsertionsPerThread = 100 - } @@ -180,7 +179,5 @@ class WriterQueueTest extends Specification { numberThreadsReads << [1, 5, 10] numberInsertionsPerThread = 100 numberGetsPerThread = 5 - } - } diff --git a/examples/README.md b/examples/README.md index 872a3592fe2..0a7348ddfda 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,7 +7,7 @@ using the OpenTracing API and the DD Tracer. Here are the examples * [Dropwizard (Jax-Rs) + Mongo database + HTTP Client](dropwizard-mongo-client/README.md) -* [Spring-boot + MySQL JDBC database](spring-boot-jdbc/README.md) +* [Spring-boot + MySQL JDBC database + Redis (Jedis client)](spring-boot-jdbc-redis/README.md) * [Instrumenting using a Java Agent](javaagent/README.md) diff --git a/examples/spring-boot-jdbc/README.md b/examples/spring-boot-jdbc-redis/README.md similarity index 82% rename from examples/spring-boot-jdbc/README.md rename to examples/spring-boot-jdbc-redis/README.md index 425e153b2a4..065f1cc18a5 100644 --- a/examples/spring-boot-jdbc/README.md +++ b/examples/spring-boot-jdbc-redis/README.md @@ -15,9 +15,9 @@ all libraries and examples launching from the ``dd-trace-java`` root folder: ./gradlew clean shadowJar bootRepackage ``` -Then you can launch the Datadog agent as follows: +Then you can launch the Datadog agent and a Redis instance as follows: ```bash -cd examples/spring-boot-jdbc +cd examples/spring-boot-jdbc-redis DD_API_KEY= docker-compose up -d ``` @@ -31,12 +31,12 @@ To launch the application, just: ``` *Note: The ``bootRun`` Gradle command appends automatically the ``-javaagent`` argument, so that you don't need to specify -the path of the Java Agent. Gradle executes the ``:examples:spring-boot-jdbc:bootRun`` task until you +the path of the Java Agent. Gradle executes the ``:examples:spring-boot-jdbc-redis:bootRun`` task until you stop it.* Or as an executable jar: ```bash -java -javaagent:../../dd-java-agent/build/libs/dd-java-agent-{version}.jar -Ddd.service.name=spring-boot-jdbc -jar build/libs/spring-boot-jdbc-demo.jar +java -javaagent:../../dd-java-agent/build/libs/dd-java-agent-{version}.jar -Ddd.service.name=spring-boot-jdbc-redis -jar build/libs/spring-boot-jdbc-redis-demo.jar ``` ### Generate traces @@ -45,6 +45,7 @@ Once the Gradle task is running. Go to the following urls: * [http://localhost:8080/user/add?name=foo&email=bar](http://localhost:8080/user/add?name=foo&email=bar) * [http://localhost:8080/user/all](http://localhost:8080/user/all) +* [http://localhost:8080/user/all](http://localhost:8080/user/random) Then get back to Datadog and wait a bit to see a trace coming. @@ -55,5 +56,6 @@ instruments: - The java servlet filters - The JDBC driver +- The Jedis Redis client The Java Agent embeds the [OpenTracing Java Agent](https://github.com/opentracing-contrib/java-agent). diff --git a/examples/spring-boot-jdbc/docker-compose.yml b/examples/spring-boot-jdbc-redis/docker-compose.yml similarity index 70% rename from examples/spring-boot-jdbc/docker-compose.yml rename to examples/spring-boot-jdbc-redis/docker-compose.yml index fb4e6e3fd6f..2ef28b663ce 100644 --- a/examples/spring-boot-jdbc/docker-compose.yml +++ b/examples/spring-boot-jdbc-redis/docker-compose.yml @@ -5,3 +5,7 @@ ddagent: - DD_API_KEY ports: - "127.0.0.1:8126:8126" +redis: + image: redis + ports: + - "127.0.0.1:6379:6379" diff --git a/examples/spring-boot-jdbc/gradle/wrapper/gradle-wrapper.jar b/examples/spring-boot-jdbc-redis/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from examples/spring-boot-jdbc/gradle/wrapper/gradle-wrapper.jar rename to examples/spring-boot-jdbc-redis/gradle/wrapper/gradle-wrapper.jar diff --git a/examples/spring-boot-jdbc/gradle/wrapper/gradle-wrapper.properties b/examples/spring-boot-jdbc-redis/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from examples/spring-boot-jdbc/gradle/wrapper/gradle-wrapper.properties rename to examples/spring-boot-jdbc-redis/gradle/wrapper/gradle-wrapper.properties diff --git a/examples/spring-boot-jdbc/gradlew b/examples/spring-boot-jdbc-redis/gradlew similarity index 100% rename from examples/spring-boot-jdbc/gradlew rename to examples/spring-boot-jdbc-redis/gradlew diff --git a/examples/spring-boot-jdbc/gradlew.bat b/examples/spring-boot-jdbc-redis/gradlew.bat similarity index 100% rename from examples/spring-boot-jdbc/gradlew.bat rename to examples/spring-boot-jdbc-redis/gradlew.bat diff --git a/examples/spring-boot-jdbc/spring-boot-jdbc.gradle b/examples/spring-boot-jdbc-redis/spring-boot-jdbc-redis.gradle similarity index 70% rename from examples/spring-boot-jdbc/spring-boot-jdbc.gradle rename to examples/spring-boot-jdbc-redis/spring-boot-jdbc-redis.gradle index 94339af63ae..487dc27600f 100644 --- a/examples/spring-boot-jdbc/spring-boot-jdbc.gradle +++ b/examples/spring-boot-jdbc-redis/spring-boot-jdbc-redis.gradle @@ -1,19 +1,21 @@ plugins { - id 'org.springframework.boot' version '1.5.4.RELEASE' + id 'org.springframework.boot' version '1.5.10.RELEASE' } apply from: "${rootDir}/gradle/java.gradle" apply from: "${rootDir}/gradle/jacoco.gradle" version = 'demo' -description = 'spring-boot-jdbc' +description = 'spring-boot-jdbc-redis' dependencies { compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3' compile group: 'com.h2database', name: 'h2', version: '1.4.196' - compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.4.RELEASE' - compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.4.RELEASE' + compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.10.RELEASE' + compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.10.RELEASE' + compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '1.5.10.RELEASE' + } bootRepackage { @@ -22,9 +24,9 @@ bootRepackage { bootRun { if (project.hasProperty('javaagent')) { - jvmArgs = ["-javaagent:$javaagent", "-Ddd.service.name=spring-boot-jdbc"] + jvmArgs = ["-javaagent:$javaagent", "-Ddd.service.name=spring-boot-jdbc-redis"] } else { - jvmArgs = ["-javaagent:${project(':dd-java-agent').tasks.shadowJar.outputs.files.getFiles().iterator().next()}", "-Ddd.service.name=spring-boot-jdbc"] + jvmArgs = ["-javaagent:${project(':dd-java-agent').tasks.shadowJar.outputs.files.getFiles().iterator().next()}", "-Ddd.service.name=spring-boot-jdbc-redis"] } } diff --git a/examples/spring-boot-jdbc/src/main/java/datadog/examples/Application.java b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/Application.java similarity index 100% rename from examples/spring-boot-jdbc/src/main/java/datadog/examples/Application.java rename to examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/Application.java diff --git a/examples/spring-boot-jdbc/src/main/java/datadog/examples/ApplicationConfig.java b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/ApplicationConfig.java similarity index 100% rename from examples/spring-boot-jdbc/src/main/java/datadog/examples/ApplicationConfig.java rename to examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/ApplicationConfig.java diff --git a/examples/spring-boot-jdbc/src/main/java/datadog/examples/entities/User.java b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/entities/User.java similarity index 100% rename from examples/spring-boot-jdbc/src/main/java/datadog/examples/entities/User.java rename to examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/entities/User.java diff --git a/examples/spring-boot-jdbc/src/main/java/datadog/examples/entities/UserRepository.java b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/entities/UserRepository.java similarity index 100% rename from examples/spring-boot-jdbc/src/main/java/datadog/examples/entities/UserRepository.java rename to examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/entities/UserRepository.java diff --git a/examples/spring-boot-jdbc/src/main/java/datadog/examples/resources/DBResource.java b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/resources/DBResource.java similarity index 70% rename from examples/spring-boot-jdbc/src/main/java/datadog/examples/resources/DBResource.java rename to examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/resources/DBResource.java index 5b5503b46a0..93c4cc07594 100644 --- a/examples/spring-boot-jdbc/src/main/java/datadog/examples/resources/DBResource.java +++ b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/resources/DBResource.java @@ -3,6 +3,8 @@ import datadog.examples.entities.User; import datadog.examples.entities.UserRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,9 +19,16 @@ public class DBResource { // Which is auto-generated by Spring, we will use it to handle the data private UserRepository userRepository; + private ValueOperations ops; + + public DBResource(@Autowired StringRedisTemplate template) { + this.ops = template.opsForValue(); + } + @GetMapping(path = "/add") // Map ONLY GET Requests public @ResponseBody String addNewUser( @RequestParam final String name, @RequestParam final String email) { + // @ResponseBody means the returned String is the response, not a view name // @RequestParam means it is a parameter from the GET or POST request @@ -27,6 +36,10 @@ public class DBResource { n.setName(name); n.setEmail(email); userRepository.save(n); + + // Also save to redis as key/value + ops.set(name, email); + return "Saved"; } @@ -38,7 +51,17 @@ public class DBResource { @GetMapping(path = "/get") public @ResponseBody User getUser(@RequestParam final int id) { - // This returns a JSON or XML with the users + // This returns a JSON or XML with the user return userRepository.findOne(id); } + + @GetMapping(path = "/getredis") + public @ResponseBody String getUserRedis(@RequestParam final String name) { + return ops.get(name); + } + + @GetMapping(path = "/random") + public @ResponseBody String flushRedis() { + return ops.getOperations().randomKey(); + } } diff --git a/examples/spring-boot-jdbc/src/main/java/datadog/examples/resources/HomeResource.java b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/resources/HomeResource.java similarity index 69% rename from examples/spring-boot-jdbc/src/main/java/datadog/examples/resources/HomeResource.java rename to examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/resources/HomeResource.java index 893603c3f8c..c4fca29ccb0 100644 --- a/examples/spring-boot-jdbc/src/main/java/datadog/examples/resources/HomeResource.java +++ b/examples/spring-boot-jdbc-redis/src/main/java/datadog/examples/resources/HomeResource.java @@ -15,9 +15,13 @@ public String test() { template.append("Demo links"); template.append(""); return template.toString(); diff --git a/examples/spring-boot-jdbc/out/production/resources/application.properties b/examples/spring-boot-jdbc-redis/src/main/resources/application.properties similarity index 100% rename from examples/spring-boot-jdbc/out/production/resources/application.properties rename to examples/spring-boot-jdbc-redis/src/main/resources/application.properties diff --git a/examples/spring-boot-jdbc/src/main/resources/application.properties b/examples/spring-boot-jdbc/src/main/resources/application.properties deleted file mode 100644 index 57104fffacd..00000000000 --- a/examples/spring-boot-jdbc/src/main/resources/application.properties +++ /dev/null @@ -1,7 +0,0 @@ -# you must set the following so that OpenTracing traced driver is used -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:spring-test;DB_CLOSE_ON_EXIT=FALSE - -# set the logging level -logging.level.root=INFO -logging.level.datadog.trace=DEBUG diff --git a/settings.gradle b/settings.gradle index c070473de9e..89ae5570b68 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include ':dd-java-agent:instrumentation:datastax-cassandra-3.2' include ':dd-java-agent:instrumentation:jax-rs' include ':dd-java-agent:instrumentation:jboss-classloading' include ':dd-java-agent:instrumentation:jdbc' +include ':dd-java-agent:instrumentation:jedis-1.4' include ':dd-java-agent:instrumentation:jms-1' include ':dd-java-agent:instrumentation:jms-2' include ':dd-java-agent:instrumentation:kafka-clients-0.11' @@ -38,7 +39,7 @@ if (JavaVersion.current().isJava8Compatible()) { // examples include ':examples:dropwizard-mongo-client' - include ':examples:spring-boot-jdbc' + include ':examples:spring-boot-jdbc-redis' include ':examples:rest-spark' }