Skip to content

Commit 170912b

Browse files
committed
Simplify Hikari pool.waiting instrumentation with AsmVisitorWrapper to detect blocking
1 parent 82191a6 commit 170912b

File tree

4 files changed

+132
-101
lines changed

4 files changed

+132
-101
lines changed

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariBlockedTracker.java

Lines changed: 0 additions & 18 deletions
This file was deleted.

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariBlockedTrackingSynchronousQueue.java

Lines changed: 0 additions & 18 deletions
This file was deleted.

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariConcurrentBagInstrumentation.java

Lines changed: 132 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,30 @@
1717
import java.util.Map;
1818
import java.util.concurrent.TimeUnit;
1919
import net.bytebuddy.asm.Advice;
20+
import net.bytebuddy.asm.AsmVisitorWrapper;
21+
import net.bytebuddy.description.field.FieldDescription;
22+
import net.bytebuddy.description.field.FieldList;
23+
import net.bytebuddy.description.method.MethodList;
24+
import net.bytebuddy.description.type.TypeDescription;
25+
import net.bytebuddy.implementation.Implementation;
26+
import net.bytebuddy.jar.asm.ClassVisitor;
27+
import net.bytebuddy.jar.asm.ClassWriter;
28+
import net.bytebuddy.jar.asm.MethodVisitor;
29+
import net.bytebuddy.jar.asm.Opcodes;
30+
import net.bytebuddy.jar.asm.Type;
31+
import net.bytebuddy.pool.TypePool;
2032

2133
/**
2234
* Instrument Hikari's ConcurrentBag class to detect when blocking occurs trying to get an entry
2335
* from the connection pool.
2436
*/
2537
@AutoService(InstrumenterModule.class)
2638
public final class HikariConcurrentBagInstrumentation extends InstrumenterModule.Tracing
27-
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
39+
implements Instrumenter.ForSingleType,
40+
Instrumenter.HasTypeAdvice,
41+
Instrumenter.HasMethodAdvice {
42+
private static final String INSTRUMENTATION_NAME = "hikari";
43+
private static final String POOL_WAITING = "pool.waiting";
2844

2945
public HikariConcurrentBagInstrumentation() {
3046
super("jdbc-datasource");
@@ -35,19 +51,17 @@ public String instrumentedType() {
3551
return "com.zaxxer.hikari.util.ConcurrentBag";
3652
}
3753

38-
@Override
39-
public String[] helperClassNames() {
40-
return new String[] {
41-
packageName + ".HikariBlockedTrackingSynchronousQueue", packageName + ".HikariBlockedTracker"
42-
};
43-
}
44-
4554
@Override
4655
public Map<String, String> contextStore() {
4756
// For getting the poolName
4857
return singletonMap("com.zaxxer.hikari.util.ConcurrentBag", String.class.getName());
4958
}
5059

60+
@Override
61+
public void typeAdvice(TypeTransformer transformer) {
62+
transformer.applyAdvice(new ConcurrentBagVisitorWrapper());
63+
}
64+
5165
@Override
5266
public void methodAdvice(MethodTransformer transformer) {
5367
transformer.applyAdvice(
@@ -58,19 +72,11 @@ public void methodAdvice(MethodTransformer transformer) {
5872

5973
public static class ConstructorAdvice {
6074
@Advice.OnMethodExit(suppress = Throwable.class)
61-
static void after(@Advice.This ConcurrentBag<?> thiz)
75+
static void after(
76+
@Advice.This ConcurrentBag<?> thiz,
77+
@Advice.FieldValue("listener") ConcurrentBag.IBagStateListener listener)
6278
throws IllegalAccessException, NoSuchFieldException {
63-
try {
64-
Field handoffQueueField = thiz.getClass().getDeclaredField("handoffQueue");
65-
handoffQueueField.setAccessible(true);
66-
handoffQueueField.set(thiz, new HikariBlockedTrackingSynchronousQueue<>());
67-
} catch (NoSuchFieldException e) {
68-
// ignore -- see HikariQueuedSequenceSynchronizerInstrumentation for older Hikari versions
69-
}
70-
71-
Field hikariPoolField = thiz.getClass().getDeclaredField("listener");
72-
hikariPoolField.setAccessible(true);
73-
HikariPool hikariPool = (HikariPool) hikariPoolField.get(thiz);
79+
HikariPool hikariPool = (HikariPool) listener;
7480

7581
/*
7682
* In earlier versions of Hikari, poolName is directly inside HikariPool, and
@@ -91,11 +97,9 @@ static void after(@Advice.This ConcurrentBag<?> thiz)
9197
}
9298

9399
public static class BorrowAdvice {
94-
private static final String POOL_WAITING = "pool.waiting";
95-
96100
@Advice.OnMethodEnter(suppress = Throwable.class)
97101
public static Long onEnter() {
98-
HikariBlockedTracker.clearBlocked();
102+
HikariWaitingTracker.clearWaiting();
99103
return System.currentTimeMillis();
100104
}
101105

@@ -104,9 +108,12 @@ public static void stopSpan(
104108
@Advice.This ConcurrentBag thiz,
105109
@Advice.Enter final Long startTimeMillis,
106110
@Advice.Thrown final Throwable throwable) {
107-
if (HikariBlockedTracker.wasBlocked()) {
111+
if (HikariWaitingTracker.wasWaiting()) {
108112
final AgentSpan span =
109-
startSpan("hikari", POOL_WAITING, TimeUnit.MILLISECONDS.toMicros(startTimeMillis));
113+
startSpan(
114+
INSTRUMENTATION_NAME,
115+
POOL_WAITING,
116+
TimeUnit.MILLISECONDS.toMicros(startTimeMillis));
110117
final String poolName =
111118
InstrumentationContext.get(ConcurrentBag.class, String.class).get(thiz);
112119
if (poolName != null) {
@@ -115,7 +122,106 @@ public static void stopSpan(
115122
// XXX should we do anything with the throwable?
116123
span.finish();
117124
}
118-
HikariBlockedTracker.clearBlocked();
125+
HikariWaitingTracker.clearWaiting();
126+
}
127+
}
128+
129+
private class ConcurrentBagVisitorWrapper implements AsmVisitorWrapper {
130+
@Override
131+
public int mergeWriter(int flags) {
132+
return flags | ClassWriter.COMPUTE_MAXS;
133+
}
134+
135+
@Override
136+
public int mergeReader(int flags) {
137+
return flags;
138+
}
139+
140+
@Override
141+
public ClassVisitor wrap(
142+
TypeDescription instrumentedType,
143+
ClassVisitor classVisitor,
144+
Implementation.Context implementationContext,
145+
TypePool typePool,
146+
FieldList<FieldDescription.InDefinedShape> fields,
147+
MethodList<?> methods,
148+
int writerFlags,
149+
int readerFlags) {
150+
return new ConcurrentBagClassVisitor(Opcodes.ASM8, classVisitor);
151+
}
152+
}
153+
154+
public static class ConcurrentBagClassVisitor extends ClassVisitor {
155+
public ConcurrentBagClassVisitor(int api, ClassVisitor cv) {
156+
super(api, cv);
157+
}
158+
159+
@Override
160+
public MethodVisitor visitMethod(
161+
int access, String name, String descriptor, String signature, String[] exceptions) {
162+
MethodVisitor superMv = super.visitMethod(access, name, descriptor, signature, exceptions);
163+
if ("borrow".equals(name)
164+
&& "(JLjava/util/concurrent/TimeUnit;)Lcom/zaxxer/hikari/util/ConcurrentBag$IConcurrentBagEntry;"
165+
.equals(descriptor)) {
166+
return new BorrowMethodVisitor(api, superMv);
167+
} else {
168+
return superMv;
169+
}
170+
}
171+
}
172+
173+
public static class BorrowMethodVisitor extends MethodVisitor {
174+
public BorrowMethodVisitor(int api, MethodVisitor superMv) {
175+
super(api, superMv);
176+
}
177+
178+
179+
/**
180+
* Adds a call to HikariWaitingTracker.setWaiting whenever Hikari is blocking waiting on a connection from the pool
181+
* to be available whenever either of these method calls happen (which one depends on Hikari version):
182+
* <br/>
183+
* <code>synchronizer.waitUntilSequenceExceeded(startSeq, timeout)</code>
184+
* -- <a href=" https://github.com/brettwooldridge/HikariCP/blob/5adf46c148dfa095886c7c754f365b0644dc04cb/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java#L159">prior to 2.6.0</a>
185+
* <br/>
186+
* <code>handoffQueue.poll(timeout, NANOSECONDS)</code>
187+
* -- <a href="https://github.com/brettwooldridge/HikariCP/blob/22cc9bde6c0fb54c8ac009122a20d2f579e1a54a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java#L162">2.6.0 and later</a>
188+
*/
189+
@Override
190+
public void visitMethodInsn(
191+
int opcode, String owner, String name, String descriptor, boolean isInterface) {
192+
if ((opcode == Opcodes.INVOKEVIRTUAL
193+
&& owner.equals("com/zaxxer/hikari/util/QueuedSequenceSynchronizer")
194+
&& name.equals("waitUntilSequenceExceeded")
195+
&& descriptor.equals("(JJ)Z"))
196+
|| (opcode == Opcodes.INVOKEVIRTUAL
197+
&& owner.equals("java/util/concurrent/SynchronousQueue")
198+
&& name.equals("poll")
199+
&& descriptor.equals("(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;"))) {
200+
super.visitMethodInsn(
201+
Opcodes.INVOKESTATIC,
202+
Type.getInternalName(HikariWaitingTracker.class),
203+
"setWaiting",
204+
"()V",
205+
false);
206+
// original stack
207+
}
208+
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
209+
}
210+
}
211+
212+
public static class HikariWaitingTracker {
213+
private static final ThreadLocal<Boolean> tracker = ThreadLocal.withInitial(() -> false);
214+
215+
public static void clearWaiting() {
216+
tracker.set(false);
217+
}
218+
219+
public static void setWaiting() {
220+
tracker.set(true);
221+
}
222+
223+
public static boolean wasWaiting() {
224+
return tracker.get();
119225
}
120226
}
121227
}

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariQueuedSequenceSynchronizerInstrumentation.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)