17
17
import java .util .Map ;
18
18
import java .util .concurrent .TimeUnit ;
19
19
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 ;
20
32
21
33
/**
22
34
* Instrument Hikari's ConcurrentBag class to detect when blocking occurs trying to get an entry
23
35
* from the connection pool.
24
36
*/
25
37
@ AutoService (InstrumenterModule .class )
26
38
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" ;
28
44
29
45
public HikariConcurrentBagInstrumentation () {
30
46
super ("jdbc-datasource" );
@@ -35,19 +51,17 @@ public String instrumentedType() {
35
51
return "com.zaxxer.hikari.util.ConcurrentBag" ;
36
52
}
37
53
38
- @ Override
39
- public String [] helperClassNames () {
40
- return new String [] {
41
- packageName + ".HikariBlockedTrackingSynchronousQueue" , packageName + ".HikariBlockedTracker"
42
- };
43
- }
44
-
45
54
@ Override
46
55
public Map <String , String > contextStore () {
47
56
// For getting the poolName
48
57
return singletonMap ("com.zaxxer.hikari.util.ConcurrentBag" , String .class .getName ());
49
58
}
50
59
60
+ @ Override
61
+ public void typeAdvice (TypeTransformer transformer ) {
62
+ transformer .applyAdvice (new ConcurrentBagVisitorWrapper ());
63
+ }
64
+
51
65
@ Override
52
66
public void methodAdvice (MethodTransformer transformer ) {
53
67
transformer .applyAdvice (
@@ -58,19 +72,11 @@ public void methodAdvice(MethodTransformer transformer) {
58
72
59
73
public static class ConstructorAdvice {
60
74
@ 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 )
62
78
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 ;
74
80
75
81
/*
76
82
* In earlier versions of Hikari, poolName is directly inside HikariPool, and
@@ -91,11 +97,9 @@ static void after(@Advice.This ConcurrentBag<?> thiz)
91
97
}
92
98
93
99
public static class BorrowAdvice {
94
- private static final String POOL_WAITING = "pool.waiting" ;
95
-
96
100
@ Advice .OnMethodEnter (suppress = Throwable .class )
97
101
public static Long onEnter () {
98
- HikariBlockedTracker . clearBlocked ();
102
+ HikariWaitingTracker . clearWaiting ();
99
103
return System .currentTimeMillis ();
100
104
}
101
105
@@ -104,9 +108,12 @@ public static void stopSpan(
104
108
@ Advice .This ConcurrentBag thiz ,
105
109
@ Advice .Enter final Long startTimeMillis ,
106
110
@ Advice .Thrown final Throwable throwable ) {
107
- if (HikariBlockedTracker . wasBlocked ()) {
111
+ if (HikariWaitingTracker . wasWaiting ()) {
108
112
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 ));
110
117
final String poolName =
111
118
InstrumentationContext .get (ConcurrentBag .class , String .class ).get (thiz );
112
119
if (poolName != null ) {
@@ -115,7 +122,106 @@ public static void stopSpan(
115
122
// XXX should we do anything with the throwable?
116
123
span .finish ();
117
124
}
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 ();
119
225
}
120
226
}
121
227
}
0 commit comments