Skip to content

Commit cfcf801

Browse files
committed
kamon-redis supports redisson client
1 parent 1212250 commit cfcf801

File tree

4 files changed

+151
-1
lines changed

4 files changed

+151
-1
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ lazy val `kamon-redis` = (project in file("instrumentation/kamon-redis"))
550550
"redis.clients" % "jedis" % "3.6.0" % "provided",
551551
"io.lettuce" % "lettuce-core" % "6.1.2.RELEASE" % "provided",
552552
"com.github.etaty" %% "rediscala" % "1.9.0" % "provided",
553+
"org.redisson" % "redisson" % "3.11.6" % "provided",
553554

554555
scalatest % "test",
555556
logbackClassic % "test",

instrumentation/kamon-redis/src/main/resources/reference.conf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
kanela.modules {
22
redis {
33
name = "Redis Instrumentation"
4-
description = "Provides tracing for Jedis, Lettuce and Rediscala libraries"
4+
description = "Provides tracing for Jedis, Lettuce, Rediscala and Redisson libraries"
55

66
instrumentations = [
77
"kamon.instrumentation.jedis.JedisInstrumentation",
88
"kamon.instrumentation.lettuce.LettuceInstrumentation",
99
"kamon.instrumentation.rediscala.RediscalaInstrumentation",
10+
"kamon.instrumentation.redisson.RedissonInstrumentation",
1011
]
1112

1213
within = [
1314
"redis.clients.jedis..*",
1415
"io.lettuce.core..*",
1516
"redis..*",
17+
"org.redisson..*",
1618
]
1719
}
1820
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package kamon.instrumentation.redisson
2+
3+
import io.netty.buffer.ByteBuf
4+
import io.netty.channel.{ChannelFuture, ChannelFutureListener}
5+
import kamon.Kamon
6+
import kamon.trace.Span
7+
import kanela.agent.api.instrumentation.InstrumentationBuilder
8+
import kanela.agent.libs.net.bytebuddy.asm.Advice
9+
import org.redisson.client.protocol.{CommandData, CommandsData}
10+
11+
import scala.collection.JavaConverters._
12+
13+
class RedissonInstrumentation extends InstrumentationBuilder {
14+
15+
onType("org.redisson.client.RedisConnection")
16+
.advise(method("send").and(takesArguments(1)), classOf[RedisConnectionInstrumentation])
17+
18+
}
19+
20+
class RedisConnectionInstrumentation
21+
22+
object RedisConnectionInstrumentation {
23+
@Advice.OnMethodEnter()
24+
def enter(@Advice.Argument(0) command: Any): Span = {
25+
26+
def parseCommand(command: CommandData[_, _]): String = {
27+
command.getParams.map {
28+
case _: ByteBuf => ""
29+
case bytes => String.valueOf(bytes)
30+
}.filter(_.nonEmpty).mkString(" ")
31+
}
32+
33+
val (commandName, statements) = command match {
34+
case commands: CommandsData =>
35+
val spanName = "redis.command.batch_execute"
36+
val statements = commands.getCommands.asScala.map(parseCommand).mkString(",")
37+
(spanName, statements)
38+
case command: CommandData[_, _] =>
39+
val spanName = s"redis.command.${command.getCommand.getName}"
40+
val statement = parseCommand(command)
41+
(spanName, statement)
42+
}
43+
44+
Kamon.clientSpanBuilder(commandName, "redisson")
45+
.tag("db.statement", statements)
46+
.tagMetrics("db.system", "redis")
47+
.start()
48+
}
49+
50+
@Advice.OnMethodExit(onThrowable = classOf[Throwable], suppress = classOf[Throwable])
51+
def exit(@Advice.Enter span: Span,
52+
@Advice.Thrown t: Throwable,
53+
@Advice.Return future: ChannelFuture) = {
54+
if (t != null) {
55+
span.fail(t)
56+
}
57+
future.addListener(new ChannelFutureListener() {
58+
override def operationComplete(future: ChannelFuture): Unit = {
59+
if (future.isSuccess) {
60+
span.finish()
61+
} else {
62+
span.fail(future.cause())
63+
span.finish()
64+
}
65+
}
66+
})
67+
}
68+
}

instrumentation/kamon-redis/src/test/scala/kamon/instrumentation/combined/RedisInstrumentationsSpec.scala

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import kamon.tag.Lookups
66
import kamon.tag.Lookups._
77
import kamon.testkit.{InitAndStopKamonAfterAll, MetricInspection, TestSpanReporter}
88
import kamon.trace.Span.Kind
9+
import org.redisson.Redisson
10+
import org.redisson.api.RBloomFilter
11+
import org.redisson.config.Config
912
import org.scalatest.concurrent.{Eventually, ScalaFutures}
1013
import org.scalatest.matchers.should.Matchers
1114
import org.scalatest.wordspec.AnyWordSpec
@@ -129,6 +132,82 @@ class RedisInstrumentationsSpec extends AnyWordSpec
129132
}
130133
}
131134

135+
"the Redisson instrumentation" should {
136+
"generate a client span for set operation" in {
137+
val config: Config = new Config()
138+
config.useSingleServer().setAddress(s"redis://${container.getHost}:${container.getFirstMappedPort}")
139+
val redisson = Redisson.create(config)
140+
141+
val mySet = redisson.getSet[String]("redisson:foo")
142+
mySet.add("bar")
143+
144+
eventually(timeout(30.seconds)) {
145+
val span = testSpanReporter().nextSpan().get
146+
span.kind shouldBe Kind.Client
147+
span.operationName shouldBe "redis.command.SADD"
148+
span.metricTags.get(plain("db.system")) shouldBe "redis"
149+
testSpanReporter().spans() shouldBe empty
150+
testSpanReporter().clear()
151+
}
152+
redisson.shutdown()
153+
}
154+
155+
"generate a client span for blocking queue operation" in {
156+
val config: Config = new Config()
157+
config.useSingleServer().setAddress(s"redis://${container.getHost}:${container.getFirstMappedPort}")
158+
val redisson = Redisson.create(config)
159+
160+
val queue = redisson.getBlockingQueue[String]("myQueue")
161+
queue.add("1")
162+
queue.add("2")
163+
queue.add("3")
164+
queue.add("4")
165+
166+
queue.contains("1")
167+
queue.peek()
168+
queue.poll()
169+
queue.element()
170+
171+
eventually(timeout(30.seconds)) {
172+
val span = testSpanReporter().nextSpan().get
173+
span.kind shouldBe Kind.Client
174+
span.metricTags.get(plain("db.system")) shouldBe "redis"
175+
testSpanReporter().spans() shouldBe empty
176+
testSpanReporter().clear()
177+
}
178+
redisson.shutdown()
179+
}
180+
181+
"generate a client span for bloom filter operation" in {
182+
val config: Config = new Config()
183+
config.useSingleServer().setAddress(s"redis://${container.getHost}:${container.getFirstMappedPort}")
184+
val redisson = Redisson.create(config)
185+
186+
val bloomFilter: RBloomFilter[String] = redisson.getBloomFilter("bloomFilter")
187+
bloomFilter.tryInit(100000000, 0.03)
188+
189+
bloomFilter.add("a")
190+
bloomFilter.add("b")
191+
bloomFilter.add("c")
192+
bloomFilter.add("d")
193+
194+
bloomFilter.getExpectedInsertions
195+
bloomFilter.getFalseProbability
196+
bloomFilter.getHashIterations
197+
198+
bloomFilter.contains("a")
199+
200+
eventually(timeout(30.seconds)) {
201+
val span = testSpanReporter().nextSpan().get
202+
span.kind shouldBe Kind.Client
203+
span.metricTags.get(plain("db.system")) shouldBe "redis"
204+
testSpanReporter().spans() shouldBe empty
205+
testSpanReporter().clear()
206+
}
207+
redisson.shutdown()
208+
}
209+
}
210+
132211
"the Rediscala instrumentation" should {
133212
implicit val akkaSystem = akka.actor.ActorSystem()
134213
"generate only one client span for commands" in {

0 commit comments

Comments
 (0)