Add support for redisson instrumentation
ADD
instrumentation/redisson-3.0/redisson-3.0.gradle
+// Set properties before any plugins get loaded
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+}
+
+apply from: "$rootDir/gradle/instrumentation.gradle"
+
+muzzle {
+ pass {
+ group = "org.redisson"
+ module = "redisson"
+ versions = "[3.0.0,)"
+ }
+}
+
+dependencies {
+ library group: 'org.redisson', name: 'redisson', version: '3.0.0'
+ testImplementation group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6'
+}

ADD
instrumentation/redisson-3.0/src/main/java/io/opentelemetry/instrumentation/auto/redisson/RedissonClientTracer.java
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opentelemetry.instrumentation.auto.redisson;
+
+import io.netty.channel.Channel;
+import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
+import io.opentelemetry.instrumentation.auto.api.jdbc.DbSystem;
+import java.net.InetSocketAddress;
+import java.util.List;
+import org.redisson.client.RedisConnection;
+import org.redisson.client.protocol.CommandData;
+import org.redisson.client.protocol.CommandsData;
+
+public class RedissonClientTracer extends DatabaseClientTracer<RedisConnection, Object> {
+
+ public static final RedissonClientTracer TRACER = new RedissonClientTracer();
+
+ @Override
+ protected String getInstrumentationName() {
+ return "io.opentelemetry.auto.redisson-3.0";
+ }
+
+ @Override
+ protected String normalizeQuery(Object args) {
+ // get command
+ if (args instanceof CommandsData) {
+ List<CommandData<?, ?>> commands = ((CommandsData) args).getCommands();
+ StringBuilder commandStrings = new StringBuilder();
+ for (CommandData commandData : commands) {
+ commandStrings.append(commandData.getCommand().getName()).append(";");
+ }
+ return commandStrings.toString();
+ } else if (args instanceof CommandData) {
+ return ((CommandData) args).getCommand().getName();
+ }
+ return "Redis Command";
+ }
+
+ @Override
+ protected String dbSystem(RedisConnection connection) {
+ return DbSystem.REDIS;
+ }
+
+ @Override
+ protected String dbUser(RedisConnection o) {
+ return null;
+ }
+
+ @Override
+ protected String dbName(RedisConnection o) {
+ return null;
+ }
+
+ @Override
+ protected InetSocketAddress peerAddress(RedisConnection connection) {
+ Channel channel = connection.getChannel();
+ return (InetSocketAddress) channel.remoteAddress();
+ }
+
+ @Override
+ protected String dbConnectionString(RedisConnection connection) {
+ Channel channel = connection.getChannel();
+ InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
+ return remoteAddress.getHostString() + ":" + remoteAddress.getPort();
+ }
+}

ADD
instrumentation/redisson-3.0/src/main/java/io/opentelemetry/instrumentation/auto/redisson/RedissonInstrumentation.java
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opentelemetry.instrumentation.auto.redisson;
+
+import static io.opentelemetry.instrumentation.auto.redisson.RedissonClientTracer.TRACER;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.tooling.Instrumenter;
+import io.opentelemetry.trace.Span;
+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 org.redisson.client.RedisConnection;
+
+@AutoService(Instrumenter.class)
+public final class RedissonInstrumentation extends Instrumenter.Default {
+
+ public RedissonInstrumentation() {
+ super("redisson", "redis");
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return named("org.redisson.client.RedisConnection");
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {packageName + ".RedissonClientTracer"};
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod().and(named("send")), RedissonInstrumentation.class.getName() + "$RedissonAdvice");
+ }
+
+ public static class RedissonAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.This RedisConnection connection,
+ @Advice.Argument(0) Object arg,
+ @Advice.Local("otelSpan") Span span,
+ @Advice.Local("otelScope") Scope scope) {
+ span = TRACER.startSpan(connection, arg);
+ scope = TRACER.startScope(span);
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelSpan") Span span,
+ @Advice.Local("otelScope") Scope scope) {
+ scope.close();
+
+ if (throwable != null) {
+ TRACER.endExceptionally(span, throwable);
+ } else {
+ TRACER.end(span);
+ }
+ }
+ }
+}

ADD
instrumentation/redisson-3.0/src/test/groovy/RedissonAsyncClientTest.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import io.opentelemetry.auto.test.AgentTestRunner
+import io.opentelemetry.auto.test.utils.PortUtils
+import io.opentelemetry.trace.attributes.SemanticAttributes
+import org.redisson.Redisson
+import org.redisson.api.*
+import org.redisson.config.Config
+import redis.embedded.RedisServer
+import spock.lang.Shared
+
+import java.util.concurrent.TimeUnit
+import java.util.function.BiConsumer
+
+import static io.opentelemetry.trace.Span.Kind.CLIENT
+
+class RedissonAsyncClientTest 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
+ RedissonClient redisson
+
+ def setupSpec() {
+ println "Using redis: $redisServer.args"
+ redisServer.start()
+ }
+
+ def cleanupSpec() {
+ redisson.shutdown()
+ redisServer.stop()
+ }
+
+ def setup() {
+ Config config = new Config()
+ config.useSingleServer().setAddress("localhost:" + port)
+ redisson = Redisson.create(config)
+ TEST_WRITER.clear()
+ }
+
+ def "test future get"() {
+ when:
+ RBucket<String> keyObject = redisson.getBucket("foo")
+ RFuture future = keyObject.setAsync("bar")
+ future.get(3, TimeUnit.SECONDS)
+ then:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "SET"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "SET"
+ }
+ }
+ }
+ }
+ }
+
+ def "test future whenComplete"() {
+ when:
+ RSet<String> rSet = redisson.getSet("set1")
+ RFuture<Boolean> result = rSet.addAsync("s1")
+ result.whenComplete(new BiConsumer<Boolean, Throwable>() {
+ @Override
+ void accept(Boolean res, Throwable throwable) {
+ RList<String> strings = redisson.getList("list1")
+ strings.add("a")
+ }
+ })
+ then:
+ result.get(3, TimeUnit.SECONDS)
+ assertTraces(2) {
+ trace(0, 1) {
+ span(0) {
+ operationName "SADD"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "SADD"
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ operationName "RPUSH"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "RPUSH"
+ }
+ }
+ }
+ }
+ }
+
+}
+

ADD
instrumentation/redisson-3.0/src/test/groovy/RedissonClientTest.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import io.opentelemetry.auto.test.AgentTestRunner
+import io.opentelemetry.auto.test.utils.PortUtils
+import io.opentelemetry.trace.attributes.SemanticAttributes
+import org.redisson.Redisson
+import org.redisson.api.RAtomicLong
+import org.redisson.api.RBatch
+import org.redisson.api.RBucket
+import org.redisson.api.RList
+import org.redisson.api.RLock
+import org.redisson.api.RMap
+import org.redisson.api.RScoredSortedSet
+import org.redisson.api.RSet
+import org.redisson.api.RedissonClient
+import org.redisson.config.Config
+import redis.embedded.RedisServer
+import spock.lang.Shared
+
+import static io.opentelemetry.trace.Span.Kind.CLIENT
+
+class RedissonClientTest 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
+ RedissonClient redisson
+
+ def setupSpec() {
+ println "Using redis: $redisServer.args"
+ redisServer.start()
+ }
+
+ def cleanupSpec() {
+ redisson.shutdown()
+ redisServer.stop()
+ }
+
+ def setup() {
+ Config config = new Config()
+ config.useSingleServer().setAddress("localhost:" + port)
+ redisson = Redisson.create(config)
+ TEST_WRITER.clear()
+ }
+
+ def "test string command"() {
+ when:
+ RBucket<String> keyObject = redisson.getBucket("foo")
+ keyObject.set("bar")
+ keyObject.get()
+ then:
+ assertTraces(2) {
+ trace(0, 1) {
+ span(0) {
+ operationName "SET"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "SET"
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ operationName "GET"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "GET"
+ }
+ }
+ }
+ }
+ }
+
+ def "test batch command"() {
+ when:
+ RBatch batch = redisson.createBatch()
+ batch.getBucket("batch1").setAsync("v1")
+ batch.getBucket("batch2").setAsync("v2")
+ batch.execute()
+ then:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "SET;SET;"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "SET;SET;"
+ }
+ }
+ }
+ }
+ }
+
+ def "test list command"() {
+ when:
+ RList<String> strings = redisson.getList("list1")
+ strings.add("a")
+ then:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "RPUSH"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "RPUSH"
+ }
+ }
+ }
+ }
+ }
+
+ def "test hash command"() {
+ when:
+ RMap<String, String> rMap = redisson.getMap("map1")
+ rMap.put("key1", "value1")
+ rMap.get("key1")
+ then:
+ assertTraces(2) {
+ trace(0, 1) {
+ span(0) {
+ operationName "EVAL"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "EVAL"
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ operationName "HGET"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "HGET"
+ }
+ }
+ }
+ }
+ }
+
+ def "test set command"() {
+ when:
+ RSet<String> rSet = redisson.getSet("set1")
+ rSet.add("s1")
+ then:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "SADD"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "SADD"
+ }
+ }
+ }
+ }
+ }
+
+ def "test sorted set command"() {
+ when:
+ Map<String, Double> scores = new HashMap<>()
+ scores.put("u1", 1.0d)
+ scores.put("u2", 3.0d)
+ scores.put("u3", 0.0d)
+ RScoredSortedSet<String> sortSet = redisson.getScoredSortedSet("sort_set1")
+ sortSet.addAll(scores)
+ then:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "ZADD"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "ZADD"
+ }
+ }
+ }
+ }
+ }
+
+ def "test AtomicLong command"() {
+ when:
+ RAtomicLong atomicLong = redisson.getAtomicLong("AtomicLong")
+ atomicLong.incrementAndGet()
+ then:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "INCR"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "INCR"
+ }
+ }
+ }
+ }
+ }
+
+ def "test lock command"() {
+ when:
+ RLock lock = redisson.getLock("lock")
+ lock.lock()
+ lock.unlock()
+ then:
+ assertTraces(2) {
+ trace(0, 1) {
+ span(0) {
+ operationName "EVAL"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "EVAL"
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ operationName "EVAL"
+ spanKind CLIENT
+ attributes {
+ "${SemanticAttributes.DB_SYSTEM.key()}" "redis"
+ "${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
+ "${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
+ "${SemanticAttributes.DB_CONNECTION_STRING.key()}" "localhost:$port"
+ "${SemanticAttributes.NET_PEER_PORT.key()}" port
+ "${SemanticAttributes.DB_STATEMENT.key()}" "EVAL"
+ }
+ }
+ }
+ }
+ }
+}
+

MODIFY
settings.gradle
settings.gradle
@@ -145,6 +145,7 @@ include ':instrumentation:rabbitmq-2.7'
include ':instrumentation:ratpack-1.4'
include ':instrumentation:reactor-3.1'
include ':instrumentation:rediscala-1.8'
+include ':instrumentation:redisson-3.0'
include ':instrumentation:rmi'
include ':instrumentation:rxjava-1.0'
include ':instrumentation:servlet:glassfish-testing'

Add support for redisson instrumentation (#1132)