Skip to content

Commit dc2c4f6

Browse files
tjiumingdao-jun
andauthored
[enhance][pulsar] add apache-pulsar client support (#5926)
Fix: #2107 Motivation: Support apache pulsar client from version 2.8.0 to lastest. --------- Co-authored-by: daojun <[email protected]>
1 parent deafc5f commit dc2c4f6

File tree

16 files changed

+1459
-0
lines changed

16 files changed

+1459
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("org.apache.pulsar")
8+
module.set("pulsar-client")
9+
versions.set("[2.8.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
library("org.apache.pulsar:pulsar-client:2.8.0")
16+
17+
testImplementation("javax.annotation:javax.annotation-api:1.3.2")
18+
testImplementation("org.testcontainers:pulsar:1.17.1")
19+
testImplementation("org.apache.pulsar:pulsar-client-admin:2.8.0")
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.pulsar.v28;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.pulsar.v28.telemetry.PulsarSingletons.startAndEndConsumerReceive;
9+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
10+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
11+
import static net.bytebuddy.matcher.ElementMatchers.isProtected;
12+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
13+
import static net.bytebuddy.matcher.ElementMatchers.named;
14+
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
15+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
16+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
17+
18+
import io.opentelemetry.context.Context;
19+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
20+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
21+
import java.util.concurrent.CompletableFuture;
22+
import net.bytebuddy.asm.Advice;
23+
import net.bytebuddy.description.type.TypeDescription;
24+
import net.bytebuddy.matcher.ElementMatcher;
25+
import org.apache.pulsar.client.api.Consumer;
26+
import org.apache.pulsar.client.api.Message;
27+
import org.apache.pulsar.client.api.PulsarClient;
28+
import org.apache.pulsar.client.impl.PulsarClientImpl;
29+
30+
public class ConsumerImplInstrumentation implements TypeInstrumentation {
31+
32+
@Override
33+
public ElementMatcher<TypeDescription> typeMatcher() {
34+
return namedOneOf(
35+
"org.apache.pulsar.client.impl.ConsumerImpl",
36+
"org.apache.pulsar.client.impl.MultiTopicsConsumerImpl");
37+
}
38+
39+
@Override
40+
public void transform(TypeTransformer transformer) {
41+
String klassName = ConsumerImplInstrumentation.class.getName();
42+
43+
transformer.applyAdviceToMethod(isConstructor(), klassName + "$ConsumerConstructorAdviser");
44+
45+
// internalReceive will apply to Consumer#receive(long,TimeUnit)
46+
// and called before MessageListener#receive.
47+
transformer.applyAdviceToMethod(
48+
isMethod()
49+
.and(isProtected())
50+
.and(named("internalReceive"))
51+
.and(takesArguments(2))
52+
.and(takesArgument(1, named("java.util.concurrent.TimeUnit"))),
53+
klassName + "$ConsumerInternalReceiveAdviser");
54+
// receive/batchReceive will apply to Consumer#receive()/Consumer#batchReceive()
55+
transformer.applyAdviceToMethod(
56+
isMethod()
57+
.and(isPublic())
58+
.and(namedOneOf("receive", "batchReceive"))
59+
.and(takesArguments(0)),
60+
klassName + "$ConsumerSyncReceiveAdviser");
61+
// receiveAsync/batchReceiveAsync will apply to
62+
// Consumer#receiveAsync()/Consumer#batchReceiveAsync()
63+
transformer.applyAdviceToMethod(
64+
isMethod()
65+
.and(isPublic())
66+
.and(namedOneOf("receiveAsync", "batchReceiveAsync"))
67+
.and(takesArguments(0)),
68+
klassName + "$ConsumerAsyncReceiveAdviser");
69+
}
70+
71+
@SuppressWarnings("unused")
72+
public static class ConsumerConstructorAdviser {
73+
private ConsumerConstructorAdviser() {}
74+
75+
@Advice.OnMethodExit(suppress = Throwable.class)
76+
public static void after(
77+
@Advice.This Consumer<?> consumer, @Advice.Argument(value = 0) PulsarClient client) {
78+
79+
PulsarClientImpl pulsarClient = (PulsarClientImpl) client;
80+
String url = pulsarClient.getLookup().getServiceUrl();
81+
VirtualFieldStore.inject(consumer, url);
82+
}
83+
}
84+
85+
@SuppressWarnings("unused")
86+
public static class ConsumerInternalReceiveAdviser {
87+
private ConsumerInternalReceiveAdviser() {}
88+
89+
@Advice.OnMethodEnter
90+
public static void before(@Advice.Local(value = "startTime") long startTime) {
91+
startTime = System.currentTimeMillis();
92+
}
93+
94+
@Advice.OnMethodExit(onThrowable = Throwable.class)
95+
public static void after(
96+
@Advice.This Consumer<?> consumer,
97+
@Advice.Return Message<?> message,
98+
@Advice.Thrown Throwable t,
99+
@Advice.Local(value = "startTime") long startTime) {
100+
if (t != null) {
101+
return;
102+
}
103+
104+
Context parent = Context.current();
105+
Context current = startAndEndConsumerReceive(parent, message, startTime, consumer);
106+
if (current != null) {
107+
// ConsumerBase#internalReceive(long,TimeUnit) will be called before
108+
// ConsumerListener#receive(Consumer,Message), so, need to inject Context into Message.
109+
VirtualFieldStore.inject(message, current);
110+
}
111+
}
112+
}
113+
114+
@SuppressWarnings("unused")
115+
public static class ConsumerSyncReceiveAdviser {
116+
private ConsumerSyncReceiveAdviser() {}
117+
118+
@Advice.OnMethodEnter
119+
public static void before(@Advice.Local(value = "startTime") long startTime) {
120+
startTime = System.currentTimeMillis();
121+
}
122+
123+
@Advice.OnMethodExit(onThrowable = Throwable.class)
124+
public static void after(
125+
@Advice.This Consumer<?> consumer,
126+
@Advice.Return Message<?> message,
127+
@Advice.Thrown Throwable t,
128+
@Advice.Local(value = "startTime") long startTime) {
129+
if (t != null) {
130+
return;
131+
}
132+
133+
Context parent = Context.current();
134+
startAndEndConsumerReceive(parent, message, startTime, consumer);
135+
// No need to inject context to message.
136+
}
137+
}
138+
139+
@SuppressWarnings("unused")
140+
public static class ConsumerAsyncReceiveAdviser {
141+
private ConsumerAsyncReceiveAdviser() {}
142+
143+
@Advice.OnMethodEnter
144+
public static void before(@Advice.Local(value = "startTime") long startTime) {
145+
startTime = System.currentTimeMillis();
146+
}
147+
148+
@Advice.OnMethodExit(onThrowable = Throwable.class)
149+
public static void after(
150+
@Advice.This Consumer<?> consumer,
151+
@Advice.Return CompletableFuture<Message<?>> future,
152+
@Advice.Local(value = "startTime") long startTime) {
153+
future.whenComplete(
154+
(message, t) -> {
155+
if (t != null) {
156+
return;
157+
}
158+
159+
Context parent = Context.current();
160+
startAndEndConsumerReceive(parent, message, startTime, consumer);
161+
// No need to inject context to message.
162+
});
163+
}
164+
}
165+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.pulsar.v28;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
10+
import static net.bytebuddy.matcher.ElementMatchers.named;
11+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
12+
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import net.bytebuddy.asm.Advice;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
import net.bytebuddy.matcher.ElementMatcher;
18+
import org.apache.pulsar.client.api.Message;
19+
20+
public class MessageInstrumentation implements TypeInstrumentation {
21+
@Override
22+
public ElementMatcher<TypeDescription> typeMatcher() {
23+
return named("org.apache.pulsar.client.impl.MessageImpl");
24+
}
25+
26+
@Override
27+
public void transform(TypeTransformer transformer) {
28+
transformer.applyAdviceToMethod(
29+
isMethod().and(isPublic()).and(named("recycle")).and(takesArguments(0)),
30+
MessageInstrumentation.class.getName() + "$MessageRecycleAdvice");
31+
}
32+
33+
@SuppressWarnings("unused")
34+
public static class MessageRecycleAdvice {
35+
private MessageRecycleAdvice() {}
36+
37+
@Advice.OnMethodExit
38+
public static void after(@Advice.This Message<?> message) {
39+
// Clean context to prevent memory leak.
40+
VirtualFieldStore.inject(message, null);
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.pulsar.v28;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
10+
import static net.bytebuddy.matcher.ElementMatchers.named;
11+
12+
import io.opentelemetry.api.common.Attributes;
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.context.Scope;
15+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
18+
import io.opentelemetry.javaagent.instrumentation.pulsar.v28.telemetry.PulsarSingletons;
19+
import net.bytebuddy.asm.Advice;
20+
import net.bytebuddy.description.type.TypeDescription;
21+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import org.apache.pulsar.client.api.Consumer;
24+
import org.apache.pulsar.client.api.Message;
25+
import org.apache.pulsar.client.api.MessageListener;
26+
import org.apache.pulsar.client.impl.conf.ConsumerConfigurationData;
27+
28+
public class MessageListenerInstrumentation implements TypeInstrumentation {
29+
30+
@Override
31+
public ElementMatcher<TypeDescription> typeMatcher() {
32+
// return hasSuperType(named("org.apache.pulsar.client.api.MessageListener"));
33+
// can't enhance MessageListener here like above due to jvm can't enhance lambda.
34+
return named("org.apache.pulsar.client.impl.conf.ConsumerConfigurationData");
35+
}
36+
37+
@Override
38+
public void transform(TypeTransformer transformer) {
39+
transformer.applyAdviceToMethod(
40+
isMethod().and(isPublic()).and(named("getMessageListener")),
41+
MessageListenerInstrumentation.class.getName() + "$ConsumerConfigurationDataMethodAdviser");
42+
}
43+
44+
@SuppressWarnings("unused")
45+
public static class ConsumerConfigurationDataMethodAdviser {
46+
private ConsumerConfigurationDataMethodAdviser() {}
47+
48+
@Advice.OnMethodExit
49+
public static void after(
50+
@Advice.This ConsumerConfigurationData<?> data,
51+
@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC)
52+
MessageListener<?> listener) {
53+
if (listener == null) {
54+
return;
55+
}
56+
57+
listener = new MessageListenerWrapper<>(listener);
58+
}
59+
}
60+
61+
public static class MessageListenerWrapper<T> implements MessageListener<T> {
62+
private static final long serialVersionUID = 1L;
63+
64+
private final MessageListener<T> delegator;
65+
66+
public MessageListenerWrapper(MessageListener<T> messageListener) {
67+
this.delegator = messageListener;
68+
}
69+
70+
@Override
71+
public void received(Consumer<T> consumer, Message<T> msg) {
72+
Context parent = VirtualFieldStore.extract(msg);
73+
74+
Instrumenter<Message<?>, Attributes> instrumenter =
75+
PulsarSingletons.consumerListenerInstrumenter();
76+
if (!instrumenter.shouldStart(parent, msg)) {
77+
this.delegator.received(consumer, msg);
78+
return;
79+
}
80+
81+
Context current = instrumenter.start(parent, msg);
82+
try (Scope scope = current.makeCurrent()) {
83+
this.delegator.received(consumer, msg);
84+
instrumenter.end(current, msg, null, null);
85+
} catch (Throwable t) {
86+
instrumenter.end(current, msg, null, t);
87+
throw t;
88+
}
89+
}
90+
91+
@Override
92+
public void reachedEndOfTopic(Consumer<T> consumer) {
93+
this.delegator.reachedEndOfTopic(consumer);
94+
}
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.pulsar.v28;
7+
8+
public class ProducerData {
9+
public final String url;
10+
public final String topic;
11+
12+
private ProducerData(String url, String topic) {
13+
this.url = url;
14+
this.topic = topic;
15+
}
16+
17+
public static ProducerData create(String url, String topic) {
18+
return new ProducerData(url, topic);
19+
}
20+
}

0 commit comments

Comments
 (0)