Skip to content

Commit fae71f9

Browse files
authored
Cache JMS resource names (DataDog#3044)
1 parent 87c7ca7 commit fae71f9

6 files changed

Lines changed: 212 additions & 128 deletions

File tree

‎dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jms/MessageConsumerState.java‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package datadog.trace.bootstrap.instrumentation.jms;
22

33
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
4-
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
54

65
/** Tracks message scopes and spans when consuming messages with {@code receive}. */
76
public final class MessageConsumerState {
87

98
private final SessionState sessionState;
10-
private final UTF8BytesString resourceName;
9+
private final CharSequence resourceName;
1110
private final boolean propagationDisabled;
1211

1312
public MessageConsumerState(
14-
SessionState sessionState, UTF8BytesString resourceName, boolean propagationDisabled) {
13+
SessionState sessionState, CharSequence resourceName, boolean propagationDisabled) {
1514
this.sessionState = sessionState;
1615
this.resourceName = resourceName;
1716
this.propagationDisabled = propagationDisabled;
@@ -21,7 +20,7 @@ public SessionState getSessionState() {
2120
return sessionState;
2221
}
2322

24-
public UTF8BytesString getResourceName() {
23+
public CharSequence getResourceName() {
2524
return resourceName;
2625
}
2726

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package datadog.trace.bootstrap.instrumentation.jms;
2+
3+
public final class MessageProducerState {
4+
5+
private final SessionState sessionState;
6+
private final CharSequence resourceName;
7+
private final boolean propagationDisabled;
8+
9+
public MessageProducerState(
10+
SessionState sessionState, CharSequence resourceName, boolean propagationDisabled) {
11+
this.sessionState = sessionState;
12+
this.resourceName = resourceName;
13+
this.propagationDisabled = propagationDisabled;
14+
}
15+
16+
public SessionState getSessionState() {
17+
return sessionState;
18+
}
19+
20+
public CharSequence getResourceName() {
21+
return resourceName;
22+
}
23+
24+
public boolean isPropagationDisabled() {
25+
return propagationDisabled;
26+
}
27+
}

‎dd-java-agent/instrumentation/jms/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java‎

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.RECORD_QUEUE_TIME_MS;
44

55
import datadog.trace.api.DDSpanTypes;
6+
import datadog.trace.api.Function;
7+
import datadog.trace.api.Functions.Join;
8+
import datadog.trace.api.Functions.PrefixJoin;
9+
import datadog.trace.api.cache.DDCache;
10+
import datadog.trace.api.cache.DDCaches;
611
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
712
import datadog.trace.bootstrap.instrumentation.api.Tags;
813
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
@@ -21,15 +26,38 @@ public final class JMSDecorator extends ClientDecorator {
2126
public static final CharSequence JMS_CONSUME = UTF8BytesString.create("jms.consume");
2227
public static final CharSequence JMS_PRODUCE = UTF8BytesString.create("jms.produce");
2328

29+
private static final Join QUEUE_JOINER = PrefixJoin.of("Queue ");
30+
private static final Join TOPIC_JOINER = PrefixJoin.of("Topic ");
31+
32+
private final DDCache<CharSequence, CharSequence> resourceNameCache =
33+
DDCaches.newFixedSizeCache(32);
34+
35+
private final String resourcePrefix;
36+
37+
private final UTF8BytesString queueTempResourceName;
38+
private final UTF8BytesString topicTempResourceName;
39+
40+
private final Function<CharSequence, CharSequence> queueResourceJoiner;
41+
private final Function<CharSequence, CharSequence> topicResourceJoiner;
42+
2443
private final String spanKind;
2544
private final String spanType;
45+
2646
public static final JMSDecorator PRODUCER_DECORATE =
27-
new JMSDecorator(Tags.SPAN_KIND_PRODUCER, DDSpanTypes.MESSAGE_PRODUCER);
47+
new JMSDecorator("Produced for ", Tags.SPAN_KIND_PRODUCER, DDSpanTypes.MESSAGE_PRODUCER);
2848

2949
public static final JMSDecorator CONSUMER_DECORATE =
30-
new JMSDecorator(Tags.SPAN_KIND_CONSUMER, DDSpanTypes.MESSAGE_CONSUMER);
50+
new JMSDecorator("Consumed from ", Tags.SPAN_KIND_CONSUMER, DDSpanTypes.MESSAGE_CONSUMER);
51+
52+
public JMSDecorator(String resourcePrefix, String spanKind, String spanType) {
53+
this.resourcePrefix = resourcePrefix;
54+
55+
this.queueTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Queue");
56+
this.topicTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Topic");
57+
58+
this.queueResourceJoiner = QUEUE_JOINER.curry(resourcePrefix);
59+
this.topicResourceJoiner = TOPIC_JOINER.curry(resourcePrefix);
3160

32-
public JMSDecorator(String spanKind, String spanType) {
3361
this.spanKind = spanKind;
3462
this.spanType = spanType;
3563
}
@@ -59,7 +87,7 @@ protected String spanKind() {
5987
return spanKind;
6088
}
6189

62-
public void onConsume(final AgentSpan span, final Message message, UTF8BytesString resourceName) {
90+
public void onConsume(AgentSpan span, Message message, CharSequence resourceName) {
6391
if (null != resourceName) {
6492
span.setResourceName(resourceName);
6593
}
@@ -74,41 +102,45 @@ public void onConsume(final AgentSpan span, final Message message, UTF8BytesStri
74102
}
75103
}
76104

77-
public void onProduce(
78-
final AgentSpan span, final Message message, final Destination destination) {
79-
span.setResourceName("Produced for " + toResourceName(message, destination));
105+
public void onProduce(AgentSpan span, CharSequence resourceName) {
106+
if (null != resourceName) {
107+
span.setResourceName(resourceName);
108+
}
80109
}
81110

82111
private static final String TIBCO_TMP_PREFIX = "$TMP$";
83112

84-
public static String toResourceName(final Message message, final Destination destination) {
85-
Destination jmsDestination = null;
86-
try {
87-
jmsDestination = message.getJMSDestination();
88-
} catch (Exception ignored) {
113+
public CharSequence toResourceName(String destinationName, boolean isQueue) {
114+
if (null == destinationName) {
115+
return isQueue ? queueTempResourceName : topicTempResourceName;
89116
}
90-
if (jmsDestination == null) {
91-
jmsDestination = destination;
117+
Function<CharSequence, CharSequence> joiner =
118+
isQueue ? queueResourceJoiner : topicResourceJoiner;
119+
// some systems may have queues and topics with the same name - since we won't know which was
120+
// cached first we check the character after the initial prefix to see if it's Q (for Queue) -
121+
// if that's what we expect we can use the cached value, otherwise generate the correct name
122+
CharSequence resourceName = resourceNameCache.computeIfAbsent(destinationName, joiner);
123+
if ((resourceName.charAt(resourcePrefix.length()) == 'Q') == isQueue) {
124+
return resourceName;
92125
}
126+
return joiner.apply(destinationName);
127+
}
128+
129+
public String getDestinationName(Destination destination) {
130+
String name = null;
93131
try {
94-
if (jmsDestination instanceof Queue) {
95-
final String queueName = ((Queue) jmsDestination).getQueueName();
96-
if (jmsDestination instanceof TemporaryQueue || queueName.startsWith(TIBCO_TMP_PREFIX)) {
97-
return "Temporary Queue";
98-
} else {
99-
return "Queue " + queueName;
132+
if (destination instanceof Queue) {
133+
if (!(destination instanceof TemporaryQueue)) {
134+
name = ((Queue) destination).getQueueName();
100135
}
101136
}
102-
if (jmsDestination instanceof Topic) {
103-
final String topicName = ((Topic) jmsDestination).getTopicName();
104-
if (jmsDestination instanceof TemporaryTopic || topicName.startsWith(TIBCO_TMP_PREFIX)) {
105-
return "Temporary Topic";
106-
} else {
107-
return "Topic " + topicName;
137+
if (destination instanceof Topic) {
138+
if (!(destination instanceof TemporaryTopic)) {
139+
name = ((Topic) destination).getTopicName();
108140
}
109141
}
110142
} catch (Exception ignored) {
111143
}
112-
return "Destination";
144+
return null != name && !name.startsWith(TIBCO_TMP_PREFIX) ? name : null;
113145
}
114146
}

‎dd-java-agent/instrumentation/jms/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java‎

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,31 +111,33 @@ public static void afterReceive(
111111
MessageConsumerState consumerState =
112112
InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class)
113113
.get(consumer);
114-
if (null != consumerState) {
115-
AgentSpan.Context propagatedContext = null;
116-
if (!consumerState.isPropagationDisabled()) {
117-
propagatedContext = propagate().extract(message, GETTER);
118-
}
119-
AgentSpan span = startSpan(JMS_CONSUME, propagatedContext);
120-
// this scope is intentionally not closed here
121-
// it stays open until the next call to get a
122-
// message, or the consumer is closed
123-
AgentScope scope = activateSpan(span);
124-
consumerState.closeOnIteration(scope);
125-
CONSUMER_DECORATE.afterStart(span);
126-
CONSUMER_DECORATE.onConsume(span, message, consumerState.getResourceName());
127-
CONSUMER_DECORATE.onError(span, throwable);
128-
SessionState sessionState = consumerState.getSessionState();
129-
if (sessionState.isClientAcknowledge()) {
130-
// consumed spans will be finished by a call to Message.acknowledge
131-
sessionState.finishOnAcknowledge(span);
132-
InstrumentationContext.get(Message.class, SessionState.class).put(message, sessionState);
133-
} else if (sessionState.isTransactedSession()) {
134-
// span will be finished by Session.commit/rollback/close
135-
sessionState.finishOnCommit(span);
136-
}
137-
// for AUTO_ACKNOWLEDGE, span is not finished until next call to receive, or close
114+
if (null == consumerState) {
115+
return;
116+
}
117+
118+
AgentSpan.Context propagatedContext = null;
119+
if (!consumerState.isPropagationDisabled()) {
120+
propagatedContext = propagate().extract(message, GETTER);
121+
}
122+
AgentSpan span = startSpan(JMS_CONSUME, propagatedContext);
123+
// this scope is intentionally not closed here
124+
// it stays open until the next call to get a
125+
// message, or the consumer is closed
126+
AgentScope scope = activateSpan(span);
127+
consumerState.closeOnIteration(scope);
128+
CONSUMER_DECORATE.afterStart(span);
129+
CONSUMER_DECORATE.onConsume(span, message, consumerState.getResourceName());
130+
CONSUMER_DECORATE.onError(span, throwable);
131+
SessionState sessionState = consumerState.getSessionState();
132+
if (sessionState.isClientAcknowledge()) {
133+
// consumed spans will be finished by a call to Message.acknowledge
134+
sessionState.finishOnAcknowledge(span);
135+
InstrumentationContext.get(Message.class, SessionState.class).put(message, sessionState);
136+
} else if (sessionState.isTransactedSession()) {
137+
// span will be finished by Session.commit/rollback/close
138+
sessionState.finishOnCommit(span);
138139
}
140+
// for AUTO_ACKNOWLEDGE, span is not finished until next call to receive, or close
139141
}
140142
}
141143

‎dd-java-agent/instrumentation/jms/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java‎

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@
99
import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCE;
1010
import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE;
1111
import static datadog.trace.instrumentation.jms.MessageInjectAdapter.SETTER;
12+
import static java.util.Collections.singletonMap;
1213
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
1314
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
1415

1516
import com.google.auto.service.AutoService;
1617
import datadog.trace.agent.tooling.Instrumenter;
1718
import datadog.trace.api.Config;
1819
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
20+
import datadog.trace.bootstrap.InstrumentationContext;
1921
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
2022
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
23+
import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState;
24+
import java.util.Map;
2125
import javax.jms.Destination;
2226
import javax.jms.Message;
2327
import javax.jms.MessageProducer;
2428
import javax.jms.Queue;
25-
import javax.jms.Topic;
2629
import net.bytebuddy.asm.Advice;
2730
import net.bytebuddy.description.type.TypeDescription;
2831
import net.bytebuddy.matcher.ElementMatcher;
@@ -55,6 +58,11 @@ public String[] helperClassNames() {
5558
};
5659
}
5760

61+
@Override
62+
public Map<String, String> contextStore() {
63+
return singletonMap("javax.jms.MessageProducer", MessageProducerState.class.getName());
64+
}
65+
5866
@Override
5967
public void adviceTransformations(AdviceTransformation transformation) {
6068
transformation.applyAdvice(
@@ -78,25 +86,17 @@ public static AgentScope beforeSend(
7886
return null;
7987
}
8088

81-
Destination defaultDestination;
82-
String destinationName = null;
83-
try {
84-
defaultDestination = producer.getDestination();
85-
if (defaultDestination instanceof Queue) {
86-
destinationName = ((Queue) defaultDestination).getQueueName();
87-
} else if (defaultDestination instanceof Topic) {
88-
destinationName = ((Topic) defaultDestination).getTopicName();
89-
}
90-
} catch (Exception ignored) {
91-
defaultDestination = null;
89+
MessageProducerState producerState =
90+
InstrumentationContext.get(MessageProducer.class, MessageProducerState.class)
91+
.get(producer);
92+
if (null == producerState) {
93+
return null;
9294
}
9395

9496
final AgentSpan span = startSpan(JMS_PRODUCE);
9597
PRODUCER_DECORATE.afterStart(span);
96-
PRODUCER_DECORATE.onProduce(span, message, defaultDestination);
97-
98-
if (Config.get().isJMSPropagationEnabled()
99-
&& !Config.get().isJMSPropagationDisabledForDestination(destinationName)) {
98+
PRODUCER_DECORATE.onProduce(span, producerState.getResourceName());
99+
if (Config.get().isJMSPropagationEnabled() && !producerState.isPropagationDisabled()) {
100100
propagate().inject(span, message, SETTER);
101101
}
102102
return activateSpan(span);
@@ -121,27 +121,19 @@ public static class ProducerWithDestinationAdvice {
121121
@Advice.OnMethodEnter(suppress = Throwable.class)
122122
public static AgentScope beforeSend(
123123
@Advice.Argument(0) final Destination destination,
124-
@Advice.Argument(1) final Message message,
125-
@Advice.This final MessageProducer producer) {
124+
@Advice.Argument(1) final Message message) {
126125
final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class);
127126
if (callDepth > 0) {
128127
return null;
129128
}
130129

131-
String destinationName = null;
132-
try {
133-
if (destination instanceof Queue) {
134-
destinationName = ((Queue) destination).getQueueName();
135-
} else if (destination instanceof Topic) {
136-
destinationName = ((Topic) destination).getTopicName();
137-
}
138-
} catch (Exception ignored) {
139-
}
130+
String destinationName = PRODUCER_DECORATE.getDestinationName(destination);
131+
CharSequence resourceName =
132+
PRODUCER_DECORATE.toResourceName(destinationName, destination instanceof Queue);
140133

141134
final AgentSpan span = startSpan(JMS_PRODUCE);
142135
PRODUCER_DECORATE.afterStart(span);
143-
PRODUCER_DECORATE.onProduce(span, message, destination);
144-
136+
PRODUCER_DECORATE.onProduce(span, resourceName);
145137
if (Config.get().isJMSPropagationEnabled()
146138
&& !Config.get().isJMSPropagationDisabledForDestination(destinationName)) {
147139
propagate().inject(span, message, SETTER);

0 commit comments

Comments
 (0)