Skip to content

Commit 5ce4223

Browse files
committed
eval: add generated jedis 1.4 instrumentation (blind test, config v1)
Generated by apm-instrumentation-toolkit using add-apm-integrations skill v1. This is a blind test run - jedis was deleted from the repo before generation. Agent metrics: 423.2s, 96 turns, $3.29 Layer 1 checks all pass: - compileJava, spotlessCheck, codenarcTest, muzzle, test, latestDepTest
1 parent 4215789 commit 5ce4223

4 files changed

Lines changed: 383 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
muzzle {
2+
pass {
3+
group = "redis.clients"
4+
module = "jedis"
5+
versions = "[1.4.0,3.0.0)"
6+
assertInverse = true
7+
}
8+
}
9+
10+
apply from: "$rootDir/gradle/java.gradle"
11+
12+
addTestSuiteForDir('latestDepTest', 'test')
13+
14+
dependencies {
15+
compileOnly group: 'redis.clients', name: 'jedis', version: '1.4.0'
16+
testImplementation group: 'redis.clients', name: 'jedis', version: '1.4.0'
17+
18+
testImplementation (group: 'com.github.codemonstur', name: 'embedded-redis', version: '1.4.3') {
19+
// Excluding redis client to avoid conflicts in instrumentation code.
20+
exclude group: 'redis.clients', module: 'jedis'
21+
}
22+
23+
// Jedis 3.0 has API changes that prevent instrumentation from applying
24+
latestDepTestImplementation group: 'redis.clients', name: 'jedis', version: '2.+'
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package datadog.trace.instrumentation.jedis;
2+
3+
import datadog.trace.api.naming.SpanNaming;
4+
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
5+
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
6+
import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator;
7+
import redis.clients.jedis.Connection;
8+
9+
public class JedisClientDecorator extends DBTypeProcessingDatabaseClientDecorator<Connection> {
10+
private static final String REDIS = "redis";
11+
public static final CharSequence COMPONENT_NAME = UTF8BytesString.create("redis-command");
12+
public static final CharSequence OPERATION_NAME =
13+
UTF8BytesString.create(SpanNaming.instance().namingSchema().cache().operation(REDIS));
14+
private static final String SERVICE_NAME =
15+
SpanNaming.instance().namingSchema().cache().service(REDIS);
16+
public static final JedisClientDecorator DECORATE = new JedisClientDecorator();
17+
18+
@Override
19+
protected String[] instrumentationNames() {
20+
return new String[] {"jedis", REDIS};
21+
}
22+
23+
@Override
24+
protected String service() {
25+
return SERVICE_NAME;
26+
}
27+
28+
@Override
29+
protected CharSequence component() {
30+
return COMPONENT_NAME;
31+
}
32+
33+
@Override
34+
protected CharSequence spanType() {
35+
return InternalSpanTypes.REDIS;
36+
}
37+
38+
@Override
39+
protected String dbType() {
40+
return REDIS;
41+
}
42+
43+
@Override
44+
protected String dbUser(final Connection connection) {
45+
return null;
46+
}
47+
48+
@Override
49+
protected String dbInstance(final Connection connection) {
50+
return null;
51+
}
52+
53+
@Override
54+
protected String dbHostname(Connection connection) {
55+
return connection.getHost();
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package datadog.trace.instrumentation.jedis;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
5+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
6+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
7+
import static datadog.trace.instrumentation.jedis.JedisClientDecorator.DECORATE;
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.not;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import com.google.auto.service.AutoService;
13+
import datadog.trace.agent.tooling.Instrumenter;
14+
import datadog.trace.agent.tooling.InstrumenterModule;
15+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
16+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
17+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
18+
import net.bytebuddy.asm.Advice;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
import redis.clients.jedis.Connection;
21+
import redis.clients.jedis.Protocol.Command;
22+
23+
@AutoService(InstrumenterModule.class)
24+
public final class JedisInstrumentation extends InstrumenterModule.Tracing
25+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
26+
27+
public JedisInstrumentation() {
28+
super("jedis", "redis");
29+
}
30+
31+
@Override
32+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
33+
// Avoid matching Jedis 3+ which has its own instrumentation.
34+
return not(hasClassNamed("redis.clients.jedis.commands.ProtocolCommand"));
35+
}
36+
37+
@Override
38+
public String instrumentedType() {
39+
return "redis.clients.jedis.Connection";
40+
}
41+
42+
@Override
43+
public String[] helperClassNames() {
44+
return new String[] {
45+
packageName + ".JedisClientDecorator",
46+
};
47+
}
48+
49+
@Override
50+
public void methodAdvice(MethodTransformer transformer) {
51+
transformer.applyAdvice(
52+
isMethod()
53+
.and(named("sendCommand"))
54+
.and(takesArgument(0, named("redis.clients.jedis.Protocol$Command"))),
55+
JedisInstrumentation.class.getName() + "$JedisAdvice");
56+
}
57+
58+
public static class JedisAdvice {
59+
60+
@Advice.OnMethodEnter(suppress = Throwable.class)
61+
public static AgentScope onEnter(
62+
@Advice.Argument(0) final Command command, @Advice.This final Connection thiz) {
63+
if (CallDepthThreadLocalMap.incrementCallDepth(Connection.class) > 0) {
64+
return null;
65+
}
66+
final AgentSpan span = startSpan(JedisClientDecorator.OPERATION_NAME);
67+
DECORATE.afterStart(span);
68+
DECORATE.onConnection(span, thiz);
69+
DECORATE.onStatement(span, command.name());
70+
return activateSpan(span);
71+
}
72+
73+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
74+
public static void stopSpan(
75+
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
76+
if (scope == null) {
77+
return;
78+
}
79+
CallDepthThreadLocalMap.reset(Connection.class);
80+
DECORATE.onError(scope.span(), throwable);
81+
DECORATE.beforeFinish(scope.span());
82+
scope.close();
83+
scope.span().finish();
84+
}
85+
}
86+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import datadog.trace.agent.test.naming.VersionedNamingTestBase
2+
import datadog.trace.agent.test.utils.PortUtils
3+
import datadog.trace.api.Config
4+
import datadog.trace.api.DDSpanTypes
5+
import datadog.trace.bootstrap.instrumentation.api.Tags
6+
import redis.clients.jedis.Jedis
7+
import redis.embedded.RedisServer
8+
import spock.lang.Shared
9+
10+
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
11+
import static datadog.trace.api.config.TraceInstrumentationConfig.DB_CLIENT_HOST_SPLIT_BY_INSTANCE
12+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan
13+
14+
abstract class JedisClientTest extends VersionedNamingTestBase {
15+
16+
@Shared
17+
int port = PortUtils.randomOpenPort()
18+
19+
@Shared
20+
RedisServer redisServer = RedisServer.newRedisServer()
21+
// bind to localhost to avoid firewall popup
22+
.setting("bind 127.0.0.1")
23+
// set max memory to avoid problems in CI
24+
.setting("maxmemory 128M")
25+
.port(port).build()
26+
27+
@Shared
28+
Jedis jedis = new Jedis("localhost", port)
29+
30+
@Override
31+
void configurePreAgent() {
32+
super.configurePreAgent()
33+
34+
// This setting should have no effect since decorator returns null for the instance.
35+
injectSysConfig(DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true")
36+
}
37+
38+
def setupSpec() {
39+
println "Using redis: $redisServer.@args"
40+
redisServer.start()
41+
}
42+
43+
def cleanupSpec() {
44+
redisServer.stop()
45+
// jedis.close() // not available in the early version we're using.
46+
}
47+
48+
def setup() {
49+
def cleanupSpan = runUnderTrace("cleanup") {
50+
jedis.flushAll()
51+
activeSpan()
52+
}
53+
TEST_WRITER.waitUntilReported(cleanupSpan)
54+
TEST_WRITER.start()
55+
}
56+
57+
def "set command"() {
58+
when:
59+
jedis.set("foo", "bar")
60+
61+
then:
62+
assertTraces(1) {
63+
trace(1) {
64+
span {
65+
serviceName service()
66+
operationName operation()
67+
resourceName "SET"
68+
spanType DDSpanTypes.REDIS
69+
measured true
70+
tags {
71+
"$Tags.COMPONENT" "redis-command"
72+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
73+
"$Tags.DB_TYPE" "redis"
74+
"$Tags.PEER_HOSTNAME" "localhost"
75+
defaultTags()
76+
}
77+
}
78+
}
79+
}
80+
}
81+
82+
def "get command"() {
83+
when:
84+
jedis.set("foo", "bar")
85+
def value = jedis.get("foo")
86+
87+
then:
88+
value == "bar"
89+
90+
assertTraces(2) {
91+
trace(1) {
92+
span {
93+
serviceName service()
94+
operationName operation()
95+
resourceName "SET"
96+
spanType DDSpanTypes.REDIS
97+
tags {
98+
"$Tags.COMPONENT" "redis-command"
99+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
100+
"$Tags.DB_TYPE" "redis"
101+
"$Tags.PEER_HOSTNAME" "localhost"
102+
peerServiceFrom(Tags.PEER_HOSTNAME)
103+
defaultTags()
104+
}
105+
}
106+
}
107+
trace(1) {
108+
span {
109+
serviceName service()
110+
operationName operation()
111+
resourceName "GET"
112+
spanType DDSpanTypes.REDIS
113+
tags {
114+
"$Tags.COMPONENT" "redis-command"
115+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
116+
"$Tags.DB_TYPE" "redis"
117+
"$Tags.PEER_HOSTNAME" "localhost"
118+
peerServiceFrom(Tags.PEER_HOSTNAME)
119+
defaultTags()
120+
}
121+
}
122+
}
123+
}
124+
}
125+
126+
def "command with no arguments"() {
127+
when:
128+
jedis.set("foo", "bar")
129+
def value = jedis.randomKey()
130+
131+
then:
132+
value == "foo"
133+
134+
assertTraces(2) {
135+
trace(1) {
136+
span {
137+
serviceName service()
138+
operationName operation()
139+
resourceName "SET"
140+
spanType DDSpanTypes.REDIS
141+
tags {
142+
"$Tags.COMPONENT" "redis-command"
143+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
144+
"$Tags.DB_TYPE" "redis"
145+
"$Tags.PEER_HOSTNAME" "localhost"
146+
peerServiceFrom(Tags.PEER_HOSTNAME)
147+
defaultTags()
148+
}
149+
}
150+
}
151+
trace(1) {
152+
span {
153+
serviceName service()
154+
operationName operation()
155+
resourceName "RANDOMKEY"
156+
spanType DDSpanTypes.REDIS
157+
tags {
158+
"$Tags.COMPONENT" "redis-command"
159+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
160+
"$Tags.DB_TYPE" "redis"
161+
"$Tags.PEER_HOSTNAME" "localhost"
162+
peerServiceFrom(Tags.PEER_HOSTNAME)
163+
defaultTags()
164+
}
165+
}
166+
}
167+
}
168+
}
169+
170+
@Override
171+
String operation() {
172+
return "redis.query"
173+
}
174+
175+
@Override
176+
String service() {
177+
return Config.get().getServiceName()
178+
}
179+
}
180+
181+
class JedisClientV0Test extends JedisClientTest {
182+
183+
@Override
184+
int version() {
185+
return 0
186+
}
187+
188+
@Override
189+
String service() {
190+
return "redis"
191+
}
192+
193+
@Override
194+
String operation() {
195+
return "redis.query"
196+
}
197+
}
198+
199+
class JedisClientV1ForkedTest extends JedisClientTest {
200+
201+
@Override
202+
int version() {
203+
return 1
204+
}
205+
206+
@Override
207+
String service() {
208+
return Config.get().getServiceName()
209+
}
210+
211+
@Override
212+
String operation() {
213+
return "redis.query"
214+
}
215+
}

0 commit comments

Comments
 (0)