Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
}

muzzle {
pass {
group = "com.github.etaty"
module = "rediscala_2.11"
versions = "[1.5.0,)"
assertInverse = true
}

pass {
group = "com.github.etaty"
module = "rediscala_2.12"
versions = "[1.8.0,)"
assertInverse = true
}

pass {
group = "com.github.etaty"
module = "rediscala_2.13"
versions = "[1.9.0,)"
assertInverse = true
}

pass {
group = "com.github.Ma27"
module = "rediscala_2.11"
versions = "[1.8.1,)"
assertInverse = true
}

pass {
group = "com.github.Ma27"
module = "rediscala_2.12"
versions = "[1.8.1,)"
assertInverse = true
}

pass {
group = "com.github.Ma27"
module = "rediscala_2.13"
versions = "[1.9.0,)"
assertInverse = true
}
}

apply from: "${rootDir}/gradle/java.gradle"

apply plugin: 'org.unbroken-dome.test-sets'

testSets {
latestDepTest {
dirName = 'test'
}
}
Comment thread
richardstartin marked this conversation as resolved.

dependencies {
compileOnly group: 'com.github.etaty', name: 'rediscala_2.11', version: '1.8.0'

testCompile group: 'com.github.etaty', name: 'rediscala_2.11', version: '1.8.0'
testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6'

latestDepTestCompile group: 'com.github.etaty', name: 'rediscala_2.11', version: '+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package datadog.trace.instrumentation.rediscala;

import datadog.trace.api.DDSpanTypes;
import datadog.trace.bootstrap.instrumentation.decorator.DatabaseClientDecorator;
import redis.RedisCommand;
import redis.protocol.RedisReply;

public class RediscalaClientDecorator
extends DatabaseClientDecorator<RedisCommand<? extends RedisReply, ?>> {

private static final String SERVICE_NAME = "redis";
private static final String COMPONENT_NAME = SERVICE_NAME + "-command";

public static final RediscalaClientDecorator DECORATE = new RediscalaClientDecorator();

@Override
protected String[] instrumentationNames() {
return new String[] {"rediscala", "redis"};
}

@Override
protected String service() {
return SERVICE_NAME;
}

@Override
protected String component() {
return COMPONENT_NAME;
}

@Override
protected String spanType() {
return DDSpanTypes.REDIS;
}

@Override
protected String dbType() {
return "redis";
}

@Override
protected String dbUser(final RedisCommand<? extends RedisReply, ?> session) {
return null;
}

@Override
protected String dbInstance(final RedisCommand<? extends RedisReply, ?> session) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package datadog.trace.instrumentation.rediscala;

import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.safeHasSuperType;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.rediscala.RediscalaClientDecorator.DECORATE;
import static java.util.Collections.singletonMap;
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.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.context.TraceScope;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import redis.RedisCommand;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;
import scala.runtime.AbstractFunction1;
import scala.util.Try;

@AutoService(Instrumenter.class)
public final class RediscalaInstrumentation extends Instrumenter.Default {

public RediscalaInstrumentation() {
super("rediscala", "redis");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return safeHasSuperType(named("redis.ActorRequest"))
.or(safeHasSuperType(named("redis.Request")))
.or(safeHasSuperType(named("redis.BufferedRequest")))
.or(safeHasSuperType(named("redis.RoundRobinPoolRequest")));
}

@Override
public String[] helperClassNames() {
return new String[] {
RediscalaInstrumentation.class.getName() + "$OnCompleteHandler",
"datadog.trace.bootstrap.instrumentation.decorator.BaseDecorator",
"datadog.trace.bootstrap.instrumentation.decorator.ClientDecorator",
"datadog.trace.bootstrap.instrumentation.decorator.DatabaseClientDecorator",
packageName + ".RediscalaClientDecorator",
};
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(isPublic())
.and(named("send"))
.and(takesArgument(0, named("redis.RedisCommand")))
.and(returns(named("scala.concurrent.Future"))),
RediscalaInstrumentation.class.getName() + "$RediscalaAdvice");
}

public static class RediscalaAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope onEnter(@Advice.Argument(0) final RedisCommand cmd) {
final AgentSpan span = startSpan("redis.command");
DECORATE.afterStart(span);
DECORATE.onStatement(span, cmd.getClass().getName());
return activateSpan(span, true);
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope,
@Advice.Thrown final Throwable throwable,
@Advice.FieldValue("executionContext") final ExecutionContext ctx,
@Advice.Return(readOnly = false) final Future<Object> responseFuture) {

final AgentSpan span = scope.span();

if (throwable == null) {
responseFuture.onComplete(new OnCompleteHandler(span), ctx);
} else {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
span.finish();
}
scope.close();
}
}

public static class OnCompleteHandler extends AbstractFunction1<Try<Object>, Void> {
private final AgentSpan span;

public OnCompleteHandler(final AgentSpan span) {
this.span = span;
}

@Override
public Void apply(final Try<Object> result) {
try {
if (result.isFailure()) {
DECORATE.onError(span, result.failed().get());
}
DECORATE.beforeFinish(span);
final TraceScope scope = activeScope();
if (scope != null) {
scope.setAsyncPropagation(false);
}
} finally {
span.finish();
}
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import akka.actor.ActorSystem
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.Config
import datadog.trace.api.DDSpanTypes
import datadog.trace.bootstrap.instrumentation.api.Tags
import redis.ByteStringSerializerLowPriority
import redis.ByteStringDeserializerDefault
import redis.RedisClient
import redis.RedisDispatcher
import redis.embedded.RedisServer
import scala.Option
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import spock.lang.Shared

class RediscalaClientTest extends AgentTestRunner {

@Shared
int port = PortUtils.randomOpenPort()

@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
ActorSystem system

@Shared
RedisClient redisClient

def setupSpec() {
system = ActorSystem.create()
redisClient = new RedisClient("localhost",
port,
Option.apply(null),
Option.apply(null),
"RedisClient",
Option.apply(null),
system,
new RedisDispatcher("rediscala.rediscala-client-worker-dispatcher"))

println "Using redis: $redisServer.args"
redisServer.start()

// This setting should have no effect since decorator returns null for the instance.
System.setProperty(Config.PREFIX + Config.DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true")
}

def cleanupSpec() {
redisServer.stop()
system?.terminate()
System.clearProperty(Config.PREFIX + Config.DB_CLIENT_HOST_SPLIT_BY_INSTANCE)
}

def setup() {
TEST_WRITER.start()
}

def "set command"() {
when:
def value = redisClient.set("foo",
"bar",
Option.apply(null),
Option.apply(null),
false,
false,
new ByteStringSerializerLowPriority.String$())


then:
Await.result(value, Duration.apply("3 second")) == true
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "redis"
operationName "redis.query"
resourceName "redis.api.strings.Set"
spanType DDSpanTypes.REDIS
tags {
"$Tags.COMPONENT" "redis-command"
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.DB_TYPE" "redis"
defaultTags()
}
}
}
}
}

def "get command"() {
when:
def write = redisClient.set("bar",
"baz",
Option.apply(null),
Option.apply(null),
false,
false,
new ByteStringSerializerLowPriority.String$())
def value = redisClient.get("bar", new ByteStringDeserializerDefault.String$())

then:
Await.result(write, Duration.apply("3 second")) == true
Await.result(value, Duration.apply("3 second")) == Option.apply("baz")
assertTraces(2) {
trace(0, 1) {
span(0) {
serviceName "redis"
operationName "redis.query"
resourceName "redis.api.strings.Set"
spanType DDSpanTypes.REDIS
tags {
"$Tags.COMPONENT" "redis-command"
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.DB_TYPE" "redis"
defaultTags()
}
}
}
trace(1, 1) {
span(0) {
serviceName "redis"
operationName "redis.query"
resourceName "redis.api.strings.Get"
spanType DDSpanTypes.REDIS
tags {
"$Tags.COMPONENT" "redis-command"
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.DB_TYPE" "redis"
defaultTags()
}
}
}
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ include ':dd-java-agent:instrumentation:play-ws-2'
include ':dd-java-agent:instrumentation:play-ws-2.1'
include ':dd-java-agent:instrumentation:rabbitmq-amqp-2.7'
include ':dd-java-agent:instrumentation:ratpack-1.4'
include ':dd-java-agent:instrumentation:rediscala-1.8.0'
include ':dd-java-agent:instrumentation:reactor-core-3.1'
include ':dd-java-agent:instrumentation:rmi'
include ':dd-java-agent:instrumentation:rxjava-1'
Expand Down