Skip to content

Commit 9c7e69a

Browse files
mccullsdeejgregor
andauthored
🪞 9251 - Add a span when waiting for an available database connection from a pool (#9636)
* HikariCP: Add a span when waiting on the pool * Apache DBCP2: Add a span when waiting on the pool * Disable pool.waiting by default, other tweaks * Use dd.trace.experimental.jdbc.pool.waiting.enabled=true to enable. * Change instrumentation name from jdbc-datasource to jdbc. * Record exceptions. * Use default instrumentation name (jdbc). * Set resource name to {dbcp2,hikari}.waiting * Docs: Update formatting settings for later versions of IntelliJ * Merge SaturatedPoolBlockingTest into JDBCInstrumentationTestBase * Add PoolWaitingDecorator. * Cleanup Hikari instrumentation * Comment on the overall structure and highlight related instrumentations. * Avoid using reflection and breakup HikariConcurrentBagInstrumentation.ConstructorAdvice into separate, simpler instrumentations: HikariPoolInstrumentation and HikariConcurrentBagHandoffQueueInstrumentation. * Move HikariBlockedTrackingSynchronousQueue into HikariConcurrentBagHandoffQueueInstrumentation. * Make sure the instrumentationName is consistently "jdbc". * Fix: injected bytecode shouldn't link back to instrumentation class * Add dbcp2/hikari aliases for pool waiting instrumentation * Avoid leaking thread-locals in HikariBlockedTracker --------- Co-authored-by: DJ Gregor <[email protected]> Co-authored-by: Stuart McCulloch <[email protected]>
1 parent 713a41a commit 9c7e69a

20 files changed

+998
-2
lines changed

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ The default JVM to build and run tests from the command line should be Java 8.
5252
* `Project Structure` -> `Project` -> `SDK` -> `Download JDK...` -> `Version: 1.8` -> `Download`
5353
* Configure Java and Groovy import formatting:
5454
* `Settings...` ->`Editor` > `Code Style` > `Java` > `Imports`
55+
* `Use single class import`: checked
5556
* `Class count to use import with '*'`: `9999` (some number sufficiently large that is unlikely to matter)
5657
* `Names count to use static import with '*'`: `9999`
5758
* Use the following import layout to ensure consistency with google-java-format:

‎dd-java-agent/instrumentation/jdbc/build.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
testImplementation group: 'com.h2database', name: 'h2', version: '[1.3.168,1.3.169]'// first jdk 1.6 compatible
3232
testImplementation group: 'org.apache.derby', name: 'derby', version: '10.6.1.0'
3333
testImplementation group: 'org.hsqldb', name: 'hsqldb', version: '2.0.0'
34+
testImplementation group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.10.0'
3435

3536
testImplementation group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '7.0.19'
3637
// tomcat needs this to run
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
5+
import static datadog.trace.instrumentation.jdbc.PoolWaitingDecorator.DECORATE;
6+
import static datadog.trace.instrumentation.jdbc.PoolWaitingDecorator.POOL_WAITING;
7+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
8+
9+
import com.google.auto.service.AutoService;
10+
import datadog.trace.agent.tooling.Instrumenter;
11+
import datadog.trace.agent.tooling.InstrumenterModule;
12+
import datadog.trace.api.InstrumenterConfig;
13+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
14+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
15+
import net.bytebuddy.asm.Advice;
16+
17+
@AutoService(InstrumenterModule.class)
18+
public final class Dbcp2LinkedBlockingDequeInstrumentation extends InstrumenterModule.Tracing
19+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
20+
21+
public Dbcp2LinkedBlockingDequeInstrumentation() {
22+
super("jdbc", "dbcp2");
23+
}
24+
25+
@Override
26+
protected boolean defaultEnabled() {
27+
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
28+
}
29+
30+
@Override
31+
public String[] knownMatchingTypes() {
32+
return new String[] {
33+
"org.apache.commons.pool2.impl.LinkedBlockingDeque", // standalone
34+
"org.apache.tomcat.dbcp.pool2.impl.LinkedBlockingDeque" // bundled with Tomcat
35+
};
36+
}
37+
38+
@Override
39+
public String[] helperClassNames() {
40+
return new String[] {packageName + ".PoolWaitingDecorator"};
41+
}
42+
43+
@Override
44+
public void methodAdvice(MethodTransformer transformer) {
45+
transformer.applyAdvice(
46+
named("pollFirst").and(takesArguments(1)),
47+
Dbcp2LinkedBlockingDequeInstrumentation.class.getName() + "$PollFirstAdvice");
48+
}
49+
50+
public static class PollFirstAdvice {
51+
@Advice.OnMethodEnter(suppress = Throwable.class)
52+
public static AgentSpan onEnter() {
53+
if (CallDepthThreadLocalMap.getCallDepth(PoolWaitingDecorator.class) > 0) {
54+
AgentSpan span = startSpan(POOL_WAITING);
55+
DECORATE.afterStart(span);
56+
span.setResourceName("dbcp2.waiting");
57+
return span;
58+
} else {
59+
return null;
60+
}
61+
}
62+
63+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
64+
public static void onExit(
65+
@Advice.Enter final AgentSpan span, @Advice.Thrown final Throwable throwable) {
66+
if (span != null) {
67+
DECORATE.onError(span, throwable);
68+
span.finish();
69+
}
70+
}
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import datadog.trace.api.InstrumenterConfig;
9+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
10+
import net.bytebuddy.asm.Advice;
11+
12+
@AutoService(InstrumenterModule.class)
13+
public final class Dbcp2ManagedConnectionInstrumentation extends InstrumenterModule.Tracing
14+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
15+
16+
public Dbcp2ManagedConnectionInstrumentation() {
17+
super("jdbc", "dbcp2");
18+
}
19+
20+
@Override
21+
protected boolean defaultEnabled() {
22+
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
23+
}
24+
25+
@Override
26+
public String[] knownMatchingTypes() {
27+
return new String[] {
28+
"org.apache.commons.dbcp2.managed.ManagedConnection", // standalone
29+
"org.apache.tomcat.dbcp.dbcp2.managed.ManagedConnection" // bundled with Tomcat
30+
};
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {packageName + ".PoolWaitingDecorator"};
36+
}
37+
38+
@Override
39+
public void methodAdvice(MethodTransformer transformer) {
40+
transformer.applyAdvice(
41+
named("updateTransactionStatus"),
42+
Dbcp2ManagedConnectionInstrumentation.class.getName() + "$UpdateTransactionStatusAdvice");
43+
}
44+
45+
public static class UpdateTransactionStatusAdvice {
46+
@Advice.OnMethodEnter(suppress = Throwable.class)
47+
public static void onEnter() {
48+
CallDepthThreadLocalMap.incrementCallDepth(PoolWaitingDecorator.class);
49+
}
50+
51+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
52+
public static void onExit() {
53+
CallDepthThreadLocalMap.decrementCallDepth(PoolWaitingDecorator.class);
54+
}
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import datadog.trace.api.InstrumenterConfig;
9+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
10+
import net.bytebuddy.asm.Advice;
11+
12+
@AutoService(InstrumenterModule.class)
13+
public final class Dbcp2PerUserPoolDataSourceInstrumentation extends InstrumenterModule.Tracing
14+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
15+
16+
public Dbcp2PerUserPoolDataSourceInstrumentation() {
17+
super("jdbc", "dbcp2");
18+
}
19+
20+
@Override
21+
protected boolean defaultEnabled() {
22+
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
23+
}
24+
25+
@Override
26+
public String[] knownMatchingTypes() {
27+
return new String[] {
28+
"org.apache.commons.dbcp2.datasources.PerUserPoolDataSource", // standalone
29+
"org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource" // bundled with Tomcat
30+
};
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {packageName + ".PoolWaitingDecorator"};
36+
}
37+
38+
@Override
39+
public void methodAdvice(MethodTransformer transformer) {
40+
transformer.applyAdvice(
41+
named("getPooledConnectionAndInfo"),
42+
Dbcp2PerUserPoolDataSourceInstrumentation.class.getName()
43+
+ "$GetPooledConnectionAndInfoAdvice");
44+
}
45+
46+
public static class GetPooledConnectionAndInfoAdvice {
47+
@Advice.OnMethodEnter(suppress = Throwable.class)
48+
public static void onEnter() {
49+
CallDepthThreadLocalMap.incrementCallDepth(PoolWaitingDecorator.class);
50+
}
51+
52+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
53+
public static void onExit() {
54+
CallDepthThreadLocalMap.decrementCallDepth(PoolWaitingDecorator.class);
55+
}
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import datadog.trace.api.InstrumenterConfig;
9+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
10+
import net.bytebuddy.asm.Advice;
11+
12+
@AutoService(InstrumenterModule.class)
13+
public final class Dbcp2PoolingDataSourceInstrumentation extends InstrumenterModule.Tracing
14+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
15+
16+
public Dbcp2PoolingDataSourceInstrumentation() {
17+
super("jdbc", "dbcp2");
18+
}
19+
20+
@Override
21+
protected boolean defaultEnabled() {
22+
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
23+
}
24+
25+
@Override
26+
public String[] knownMatchingTypes() {
27+
return new String[] {
28+
"org.apache.commons.dbcp2.PoolingDataSource", // standalone
29+
"org.apache.tomcat.dbcp.dbcp2.PoolingDataSource" // bundled with Tomcat
30+
};
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {packageName + ".PoolWaitingDecorator"};
36+
}
37+
38+
@Override
39+
public void methodAdvice(MethodTransformer transformer) {
40+
transformer.applyAdvice(
41+
named("getConnection"),
42+
Dbcp2PoolingDataSourceInstrumentation.class.getName() + "$GetConnectionAdvice");
43+
}
44+
45+
public static class GetConnectionAdvice {
46+
@Advice.OnMethodEnter(suppress = Throwable.class)
47+
public static void onEnter() {
48+
CallDepthThreadLocalMap.incrementCallDepth(PoolWaitingDecorator.class);
49+
}
50+
51+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
52+
public static void onExit() {
53+
CallDepthThreadLocalMap.decrementCallDepth(PoolWaitingDecorator.class);
54+
}
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import datadog.trace.api.InstrumenterConfig;
9+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
10+
import net.bytebuddy.asm.Advice;
11+
12+
@AutoService(InstrumenterModule.class)
13+
public final class Dbcp2PoolingDriverInstrumentation extends InstrumenterModule.Tracing
14+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
15+
16+
public Dbcp2PoolingDriverInstrumentation() {
17+
super("jdbc", "dbcp2");
18+
}
19+
20+
@Override
21+
protected boolean defaultEnabled() {
22+
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
23+
}
24+
25+
@Override
26+
public String[] knownMatchingTypes() {
27+
return new String[] {
28+
"org.apache.commons.dbcp2.PoolingDriver", // standalone
29+
"org.apache.tomcat.dbcp.dbcp2.PoolingDriver" // bundled with Tomcat
30+
};
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {packageName + ".PoolWaitingDecorator"};
36+
}
37+
38+
@Override
39+
public void methodAdvice(MethodTransformer transformer) {
40+
transformer.applyAdvice(
41+
named("connect"), Dbcp2PoolingDriverInstrumentation.class.getName() + "$ConnectAdvice");
42+
}
43+
44+
public static class ConnectAdvice {
45+
@Advice.OnMethodEnter(suppress = Throwable.class)
46+
public static void onEnter() {
47+
CallDepthThreadLocalMap.incrementCallDepth(PoolWaitingDecorator.class);
48+
}
49+
50+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
51+
public static void onExit() {
52+
CallDepthThreadLocalMap.decrementCallDepth(PoolWaitingDecorator.class);
53+
}
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import datadog.trace.api.InstrumenterConfig;
9+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
10+
import net.bytebuddy.asm.Advice;
11+
12+
@AutoService(InstrumenterModule.class)
13+
public final class Dbcp2SharedPoolDataSourceInstrumentation extends InstrumenterModule.Tracing
14+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
15+
16+
public Dbcp2SharedPoolDataSourceInstrumentation() {
17+
super("jdbc", "dbcp2");
18+
}
19+
20+
@Override
21+
protected boolean defaultEnabled() {
22+
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
23+
}
24+
25+
@Override
26+
public String[] knownMatchingTypes() {
27+
return new String[] {
28+
"org.apache.commons.dbcp2.datasources.SharePoolDataSource", // standalone
29+
"org.apache.tomcat.dbcp.dbcp2.datasources.SharedPoolPoolDataSource" // bundled with Tomcat
30+
};
31+
}
32+
33+
@Override
34+
public String[] helperClassNames() {
35+
return new String[] {packageName + ".PoolWaitingDecorator"};
36+
}
37+
38+
@Override
39+
public void methodAdvice(MethodTransformer transformer) {
40+
transformer.applyAdvice(
41+
named("getPooledConnectionAndInfo"),
42+
Dbcp2SharedPoolDataSourceInstrumentation.class.getName()
43+
+ "$GetPooledConnectionAndInfoAdvice");
44+
}
45+
46+
public static class GetPooledConnectionAndInfoAdvice {
47+
@Advice.OnMethodEnter(suppress = Throwable.class)
48+
public static void onEnter() {
49+
CallDepthThreadLocalMap.incrementCallDepth(PoolWaitingDecorator.class);
50+
}
51+
52+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
53+
public static void onExit() {
54+
CallDepthThreadLocalMap.decrementCallDepth(PoolWaitingDecorator.class);
55+
}
56+
}
57+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import static java.lang.Boolean.TRUE;
4+
5+
/** Shared blocked getConnection() tracking {@link ThreadLocal} for Hikari. */
6+
public class HikariBlockedTracker {
7+
private static final ThreadLocal<Boolean> tracker = new ThreadLocal<>();
8+
9+
public static void clearBlocked() {
10+
tracker.remove();
11+
}
12+
13+
public static void setBlocked() {
14+
tracker.set(TRUE);
15+
}
16+
17+
public static boolean wasBlocked() {
18+
return TRUE.equals(tracker.get());
19+
}
20+
}

0 commit comments

Comments
 (0)