Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ The default JVM to build and run tests from the command line should be Java 8.
* `Project Structure` -> `Project` -> `SDK` -> `Download JDK...` -> `Version: 1.8` -> `Download`
* Configure Java and Groovy import formatting:
* `Settings...` ->`Editor` > `Code Style` > `Java` > `Imports`
* `Use single class import`: checked
* `Class count to use import with '*'`: `9999` (some number sufficiently large that is unlikely to matter)
* `Names count to use static import with '*'`: `9999`
* Use the following import layout to ensure consistency with google-java-format:
Expand Down
1 change: 1 addition & 0 deletions dd-java-agent/instrumentation/jdbc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
testImplementation group: 'com.h2database', name: 'h2', version: '[1.3.168,1.3.169]'// first jdk 1.6 compatible
testImplementation group: 'org.apache.derby', name: 'derby', version: '10.6.1.0'
testImplementation group: 'org.hsqldb', name: 'hsqldb', version: '2.0.0'
testImplementation group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.10.0'

testImplementation group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '7.0.19'
// tomcat needs this to run
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.jdbc.PoolWaitingDecorator.DECORATE;
import static datadog.trace.instrumentation.jdbc.PoolWaitingDecorator.POOL_WAITING;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class Dbcp2LinkedBlockingDequeInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {

public Dbcp2LinkedBlockingDequeInstrumentation() {
super("jdbc");
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.apache.commons.pool2.impl.LinkedBlockingDeque", // standalone
"org.apache.tomcat.dbcp.pool2.impl.LinkedBlockingDeque" // bundled with Tomcat
};
}

@Override
public String[] helperClassNames() {
return new String[] {packageName + ".PoolWaitingDecorator"};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("pollFirst").and(takesArguments(1)),
Dbcp2LinkedBlockingDequeInstrumentation.class.getName() + "$PollFirstAdvice");
}

public static class PollFirstAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentSpan onEnter() {
if (CallDepthThreadLocalMap.getCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class) > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this advice bytecode will be lifted out and injected into the DBCP code it should only refer to types that are:

  • on the boot-class-path (including those from agent-bootstrap)
  • from the JDK
  • from the library being instrumented
  • injected as helpers

while referring to the surrounding instrumentation class will compile, it will cause issues to have injected bytecode attempt to link back to the original class (for one it will stop the instrumentation class-loader from unloading its classes after everything has been setup)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, excellent. Thank you! Will fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dbcp2 instrumentations target dbcp2 classes in two different packages, so I was thinking of chosing one of the helper classes added in this PR for CallDepthThreadLocalMap: either PoolWaitingDecorator or HikariBlockedTracker (I'd rename it to be PoolWaitingTracker, which I should probably do anyway). Does this approach sound good and do you have a preference on which class to use?

AgentSpan span = startSpan(POOL_WAITING);
DECORATE.afterStart(span);
span.setResourceName("dbcp2.waiting");
return span;
} else {
return null;
}
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Enter final AgentSpan span, @Advice.Thrown final Throwable throwable) {
if (span != null) {
DECORATE.onError(span, throwable);
span.finish();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class Dbcp2ManagedConnectionInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {

public Dbcp2ManagedConnectionInstrumentation() {
super("jdbc");
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.apache.commons.dbcp2.managed.ManagedConnection", // standalone
"org.apache.tomcat.dbcp.dbcp2.managed.ManagedConnection" // bundled with Tomcat
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("updateTransactionStatus"),
Dbcp2ManagedConnectionInstrumentation.class.getName() + "$UpdateTransactionStatusAdvice");
}

public static class UpdateTransactionStatusAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter() {
CallDepthThreadLocalMap.incrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit() {
CallDepthThreadLocalMap.decrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class Dbcp2PerUserPoolDataSourceInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {

public Dbcp2PerUserPoolDataSourceInstrumentation() {
super("jdbc");
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.apache.commons.dbcp2.datasources.PerUserPoolDataSource", // standalone
"org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource" // bundled with Tomcat
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("getPooledConnectionAndInfo"),
Dbcp2PerUserPoolDataSourceInstrumentation.class.getName()
+ "$GetPooledConnectionAndInfoAdvice");
}

public static class GetPooledConnectionAndInfoAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter() {
CallDepthThreadLocalMap.incrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit() {
CallDepthThreadLocalMap.decrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class Dbcp2PoolingDataSourceInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {

public Dbcp2PoolingDataSourceInstrumentation() {
super("jdbc");
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.apache.commons.dbcp2.PoolingDataSource", // standalone
"org.apache.tomcat.dbcp.dbcp2.PoolingDataSource" // bundled with Tomcat
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("getConnection"),
Dbcp2PoolingDataSourceInstrumentation.class.getName() + "$GetConnectionAdvice");
}

public static class GetConnectionAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter() {
CallDepthThreadLocalMap.incrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit() {
CallDepthThreadLocalMap.decrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class Dbcp2PoolingDriverInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {

public Dbcp2PoolingDriverInstrumentation() {
super("jdbc");
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.apache.commons.dbcp2.PoolingDriver", // standalone
"org.apache.tomcat.dbcp.dbcp2.PoolingDriver" // bundled with Tomcat
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("connect"), Dbcp2PoolingDriverInstrumentation.class.getName() + "$ConnectAdvice");
}

public static class ConnectAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter() {
CallDepthThreadLocalMap.incrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit() {
CallDepthThreadLocalMap.decrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class Dbcp2SharedPoolDataSourceInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {

public Dbcp2SharedPoolDataSourceInstrumentation() {
super("jdbc");
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isJdbcPoolWaitingEnabled();
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"org.apache.commons.dbcp2.datasources.SharePoolDataSource", // standalone
"org.apache.tomcat.dbcp.dbcp2.datasources.SharedPoolPoolDataSource" // bundled with Tomcat
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("getPooledConnectionAndInfo"),
Dbcp2SharedPoolDataSourceInstrumentation.class.getName()
+ "$GetPooledConnectionAndInfoAdvice");
}

public static class GetPooledConnectionAndInfoAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter() {
CallDepthThreadLocalMap.incrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit() {
CallDepthThreadLocalMap.decrementCallDepth(Dbcp2LinkedBlockingDequeInstrumentation.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, advice bytecode should not refer to its surrounding instrumentation class

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package datadog.trace.instrumentation.jdbc;

/** Shared blocked getConnection() tracking ThreadLocking for Hikari. */
public class HikariBlockedTracker {
private static final ThreadLocal<Boolean> tracker = ThreadLocal.withInitial(() -> false);

public static void clearBlocked() {
tracker.set(false);
}

public static void setBlocked() {
tracker.set(true);
}

public static boolean wasBlocked() {
return tracker.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package datadog.trace.instrumentation.jdbc;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/** Blocked getConnection() tracking for Hikari starting with commit f0b3c520c. */
public class HikariBlockedTrackingSynchronousQueue<T> extends SynchronousQueue<T> {
public HikariBlockedTrackingSynchronousQueue() {
// This assumes the initialization of the SynchronousQueue in ConcurrentBag doesn't change
super(true);
}

@Override
public T poll(long timeout, TimeUnit unit) throws InterruptedException {
HikariBlockedTracker.setBlocked();
return super.poll(timeout, unit);
}
}
Loading