Add instrumentation for Rediscala library
This commit is contained in:
parent
5bc7c14cd0
commit
99408e6ba4
|
@ -0,0 +1,41 @@
|
||||||
|
muzzle {
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'com.github.Ma27', name: 'rediscala_2.11', version: '1.8.1'
|
||||||
|
|
||||||
|
testCompile group: 'com.github.Ma27', name: 'rediscala_2.11', version: '1.8.1'
|
||||||
|
testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6'
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'com.github.Ma27', name: 'rediscala_2.11', version: '+'
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package datadog.trace.instrumentation.rediscala;
|
||||||
|
|
||||||
|
import datadog.trace.agent.decorator.DatabaseClientDecorator;
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import redis.RedisCommand;
|
||||||
|
|
||||||
|
public class RediscalaClientDecorator extends DatabaseClientDecorator<RedisCommand> {
|
||||||
|
public static final RediscalaClientDecorator DECORATE = new RediscalaClientDecorator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] instrumentationNames() {
|
||||||
|
return new String[] {"rediscala", "redis"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String service() {
|
||||||
|
return "redis";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String component() {
|
||||||
|
return "redis-command";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String spanType() {
|
||||||
|
return DDSpanTypes.REDIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbType() {
|
||||||
|
return "redis";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbUser(final RedisCommand session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbInstance(final RedisCommand session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package datadog.trace.instrumentation.rediscala;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
|
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
import static datadog.trace.instrumentation.api.AgentTracer.activeScope;
|
||||||
|
import static datadog.trace.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.context.TraceScope;
|
||||||
|
import datadog.trace.instrumentation.api.AgentScope;
|
||||||
|
import datadog.trace.instrumentation.api.AgentSpan;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private static final String SERVICE_NAME = "redis";
|
||||||
|
private static final String COMPONENT_NAME = SERVICE_NAME + "-command";
|
||||||
|
|
||||||
|
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.agent.decorator.BaseDecorator",
|
||||||
|
"datadog.trace.agent.decorator.ClientDecorator",
|
||||||
|
"datadog.trace.agent.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.Config
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.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 {
|
||||||
|
|
||||||
|
public static final int PORT = 6399
|
||||||
|
|
||||||
|
@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.DB_TYPE" "redis"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
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.DB_TYPE" "redis"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
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.DB_TYPE" "redis"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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:play-ws-2.1'
|
||||||
include ':dd-java-agent:instrumentation:rabbitmq-amqp-2.7'
|
include ':dd-java-agent:instrumentation:rabbitmq-amqp-2.7'
|
||||||
include ':dd-java-agent:instrumentation:ratpack-1.4'
|
include ':dd-java-agent:instrumentation:ratpack-1.4'
|
||||||
|
include ':dd-java-agent:instrumentation:rediscala'
|
||||||
include ':dd-java-agent:instrumentation:reactor-core-3.1'
|
include ':dd-java-agent:instrumentation:reactor-core-3.1'
|
||||||
include ':dd-java-agent:instrumentation:rmi'
|
include ':dd-java-agent:instrumentation:rmi'
|
||||||
include ':dd-java-agent:instrumentation:rxjava-1'
|
include ':dd-java-agent:instrumentation:rxjava-1'
|
||||||
|
|
Loading…
Reference in New Issue