diff --git a/dd-java-agent/instrumentation/hibernate/core-3.5/core-3.5.gradle b/dd-java-agent/instrumentation/hibernate/core-3.5/core-3.5.gradle new file mode 100644 index 0000000000..8429e233cb --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/core-3.5.gradle @@ -0,0 +1,62 @@ +/* + * Integration for Hibernate between 3.5 and 4. + * Has the same logic as the Hibernate 4+ integration, but is copied rather than sharing a codebase. This is because + * the root interface for Session/StatelessSession - SharedSessionContract - isn't present before version 4. So the + * instrumentation isn't able to reference it. + */ + +// Set properties before any plugins get loaded +project.ext { + // Execute tests on all JVMs, even rare and outdated ones + coreJavaInstrumentation = true + minJavaVersionForTests = JavaVersion.VERSION_1_7 +} + +muzzle { + pass { + group = "org.hibernate" + module = "hibernate-core" + versions = "[3.5.0-Final,4.0.0.Final)" + } +} + +apply from: "${rootDir}/gradle/java.gradle" + +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + compileOnly group: 'org.hibernate', name: 'hibernate-core', version: '3.5.0-Final' + + compile project(':dd-java-agent:agent-tooling') + compile project(':dd-java-agent:instrumentation:hibernate') + + compile deps.bytebuddy + compile deps.opentracing + annotationProcessor deps.autoservice + implementation deps.autoservice + + testCompile project(':dd-java-agent:testing') + testCompile project(':dd-java-agent:instrumentation:jdbc') + + testCompile group: 'org.hibernate', name: 'hibernate-core', version: '3.5.0-Final' + testCompile group: 'org.hibernate', name: 'hibernate-annotations', version: '+' + testCompile group: 'javassist', name: 'javassist', version: '+' + testCompile group: 'com.h2database', name: 'h2', version: '1.4.197' + testCompile "javax.xml.bind:jaxb-api:2.2.11" + testCompile "com.sun.xml.bind:jaxb-core:2.2.11" + testCompile "com.sun.xml.bind:jaxb-impl:2.2.11" + testCompile "javax.activation:activation:1.1.1" + + latestDepTestCompile group: 'org.hibernate', name: 'hibernate-core', version: '3.+' +} + +configurations { + // Needed for test, but for latestDepTest this would otherwise bundle a second incompatible version of hibernate-core. + latestDepTestCompile.exclude group: 'org.hibernate', module: 'hibernate-annotations' +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/CriteriaInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/CriteriaInstrumentation.java similarity index 88% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/CriteriaInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/CriteriaInstrumentation.java index c3f66eaaca..65585b52f3 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/CriteriaInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/CriteriaInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate.core.v3_5; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static java.util.Collections.singletonMap; @@ -11,6 +11,8 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -34,13 +36,13 @@ public class CriteriaInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - packageName + ".SessionMethodUtils", - packageName + ".SessionState", + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - packageName + ".HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/QueryInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/QueryInstrumentation.java similarity index 88% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/QueryInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/QueryInstrumentation.java index 07a6228fac..90815aa31a 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/QueryInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/QueryInstrumentation.java @@ -1,7 +1,7 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate.core.v3_5; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; -import static datadog.trace.instrumentation.hibernate.v4_0.HibernateDecorator.DECORATOR; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isMethod; @@ -12,6 +12,8 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -36,13 +38,13 @@ public class QueryInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - packageName + ".SessionMethodUtils", - packageName + ".SessionState", + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - packageName + ".HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/SessionFactoryInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/SessionFactoryInstrumentation.java new file mode 100644 index 0000000000..97a3538a4f --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/SessionFactoryInstrumentation.java @@ -0,0 +1,96 @@ +package datadog.trace.instrumentation.hibernate.core.v3_5; + +import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionState; +import io.opentracing.Span; +import io.opentracing.util.GlobalTracer; +import java.util.HashMap; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.Session; +import org.hibernate.StatelessSession; + +@AutoService(Instrumenter.class) +public class SessionFactoryInstrumentation extends Instrumenter.Default { + + public SessionFactoryInstrumentation() { + super("hibernate", "hibernate-core"); + } + + @Override + public Map contextStore() { + final Map stores = new HashMap<>(); + stores.put("org.hibernate.Session", SessionState.class.getName()); + stores.put("org.hibernate.StatelessSession", SessionState.class.getName()); + stores.put("org.hibernate.SharedSessionContract", SessionState.class.getName()); + return stores; + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.instrumentation.hibernate.SessionState", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.DatabaseClientDecorator", + "datadog.trace.agent.decorator.OrmClientDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", + }; + } + + @Override + public ElementMatcher typeMatcher() { + return not(isInterface()).and(safeHasSuperType(named("org.hibernate.SessionFactory"))); + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod() + .and(named("openSession").or(named("openStatelessSession"))) + .and(takesArguments(0)) + .and( + returns( + named("org.hibernate.Session") + .or(named("org.hibernate.StatelessSession")) + .or(safeHasSuperType(named("org.hibernate.Session"))))), + SessionFactoryAdvice.class.getName()); + } + + public static class SessionFactoryAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void openSession(@Advice.Return final Object session) { + + final Span span = GlobalTracer.get().buildSpan("hibernate.session").start(); + DECORATOR.afterStart(span); + DECORATOR.onConnection(span, session); + + if (session instanceof Session) { + final ContextStore contextStore = + InstrumentationContext.get(Session.class, SessionState.class); + contextStore.putIfAbsent((Session) session, new SessionState(span)); + } else if (session instanceof StatelessSession) { + final ContextStore contextStore = + InstrumentationContext.get(StatelessSession.class, SessionState.class); + contextStore.putIfAbsent((StatelessSession) session, new SessionState(span)); + } + } + } +} diff --git a/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/SessionInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/SessionInstrumentation.java new file mode 100644 index 0000000000..5c90ffbb68 --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/SessionInstrumentation.java @@ -0,0 +1,257 @@ +package datadog.trace.instrumentation.hibernate.core.v3_5; + +import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; +import static datadog.trace.instrumentation.hibernate.SessionMethodUtils.SCOPE_ONLY_METHODS; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; +import io.opentracing.Span; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.Criteria; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +import org.hibernate.Transaction; + +@AutoService(Instrumenter.class) +public class SessionInstrumentation extends Instrumenter.Default { + + public SessionInstrumentation() { + super("hibernate", "hibernate-core"); + } + + @Override + public Map contextStore() { + final Map map = new HashMap<>(); + map.put("org.hibernate.Session", SessionState.class.getName()); + map.put("org.hibernate.StatelessSession", SessionState.class.getName()); + map.put("org.hibernate.Query", SessionState.class.getName()); + map.put("org.hibernate.Transaction", SessionState.class.getName()); + map.put("org.hibernate.Criteria", SessionState.class.getName()); + return Collections.unmodifiableMap(map); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.DatabaseClientDecorator", + "datadog.trace.agent.decorator.OrmClientDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", + }; + } + + @Override + public ElementMatcher typeMatcher() { + return not(isInterface()) + .and( + safeHasSuperType( + named("org.hibernate.Session").or(named("org.hibernate.StatelessSession")))); + } + + @Override + public Map, String> transformers() { + final Map, String> transformers = new HashMap<>(); + transformers.put( + isMethod().and(named("close")).and(takesArguments(0)), SessionCloseAdvice.class.getName()); + + // Session synchronous methods we want to instrument. + transformers.put( + isMethod() + .and( + named("save") + .or(named("replicate")) + .or(named("saveOrUpdate")) + .or(named("update")) + .or(named("merge")) + .or(named("persist")) + .or(named("lock")) + .or(named("refresh")) + .or(named("insert")) + .or(named("delete")) + // Iterator methods. + .or(named("iterate")) + // Lazy-load methods. + .or(named("immediateLoad")) + .or(named("internalLoad"))), + SessionMethodAdvice.class.getName()); + // Handle the non-generic 'get' separately. + transformers.put( + isMethod() + .and(named("get")) + .and(returns(named("java.lang.Object"))) + .and(takesArgument(0, named("java.lang.String"))), + SessionMethodAdvice.class.getName()); + + // These methods return some object that we want to instrument, and so the Advice will pin the + // current Span to the returned object using a ContextStore. + transformers.put( + isMethod() + .and(named("beginTransaction").or(named("getTransaction"))) + .and(returns(named("org.hibernate.Transaction"))), + GetTransactionAdvice.class.getName()); + + transformers.put( + isMethod().and(returns(safeHasSuperType(named("org.hibernate.Query")))), + GetQueryAdvice.class.getName()); + + transformers.put( + isMethod().and(returns(safeHasSuperType(named("org.hibernate.Criteria")))), + GetCriteriaAdvice.class.getName()); + + return transformers; + } + + public static class SessionCloseAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void closeSession( + @Advice.This final Object session, @Advice.Thrown final Throwable throwable) { + + SessionState state = null; + if (session instanceof Session) { + final ContextStore contextStore = + InstrumentationContext.get(Session.class, SessionState.class); + state = contextStore.get((Session) session); + } else if (session instanceof StatelessSession) { + final ContextStore contextStore = + InstrumentationContext.get(StatelessSession.class, SessionState.class); + state = contextStore.get((StatelessSession) session); + } + + if (state == null || state.getSessionSpan() == null) { + return; + } + if (state.getMethodScope() != null) { + state.getMethodScope().close(); + } + + final Span span = state.getSessionSpan(); + DECORATOR.onError(span, throwable); + DECORATOR.beforeFinish(span); + span.finish(); + } + } + + public static class SessionMethodAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SessionState startMethod( + @Advice.This final Object session, + @Advice.Origin("#m") final String name, + @Advice.Argument(0) final Object entity) { + + final boolean startSpan = !SCOPE_ONLY_METHODS.contains(name); + if (session instanceof Session) { + final ContextStore contextStore = + InstrumentationContext.get(Session.class, SessionState.class); + return SessionMethodUtils.startScopeFrom( + contextStore, (Session) session, "hibernate." + name, entity, startSpan); + } else if (session instanceof StatelessSession) { + final ContextStore contextStore = + InstrumentationContext.get(StatelessSession.class, SessionState.class); + return SessionMethodUtils.startScopeFrom( + contextStore, (StatelessSession) session, "hibernate." + name, entity, startSpan); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endMethod( + @Advice.Enter final SessionState sessionState, + @Advice.Thrown final Throwable throwable, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) final Object returned) { + + SessionMethodUtils.closeScope(sessionState, throwable, returned); + } + } + + public static class GetQueryAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getQuery( + @Advice.This final Object session, @Advice.Return final Query query) { + + final ContextStore queryContextStore = + InstrumentationContext.get(Query.class, SessionState.class); + if (session instanceof Session) { + final ContextStore sessionContextStore = + InstrumentationContext.get(Session.class, SessionState.class); + SessionMethodUtils.attachSpanFromStore( + sessionContextStore, (Session) session, queryContextStore, query); + } else if (session instanceof StatelessSession) { + final ContextStore sessionContextStore = + InstrumentationContext.get(StatelessSession.class, SessionState.class); + SessionMethodUtils.attachSpanFromStore( + sessionContextStore, (StatelessSession) session, queryContextStore, query); + } + } + } + + public static class GetTransactionAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getTransaction( + @Advice.This final Object session, @Advice.Return final Transaction transaction) { + + final ContextStore transactionContextStore = + InstrumentationContext.get(Transaction.class, SessionState.class); + + if (session instanceof Session) { + final ContextStore sessionContextStore = + InstrumentationContext.get(Session.class, SessionState.class); + SessionMethodUtils.attachSpanFromStore( + sessionContextStore, (Session) session, transactionContextStore, transaction); + } else if (session instanceof StatelessSession) { + final ContextStore sessionContextStore = + InstrumentationContext.get(StatelessSession.class, SessionState.class); + SessionMethodUtils.attachSpanFromStore( + sessionContextStore, (StatelessSession) session, transactionContextStore, transaction); + } + } + } + + public static class GetCriteriaAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getCriteria( + @Advice.This final Object session, @Advice.Return final Criteria criteria) { + + final ContextStore criteriaContextStore = + InstrumentationContext.get(Criteria.class, SessionState.class); + if (session instanceof Session) { + final ContextStore sessionContextStore = + InstrumentationContext.get(Session.class, SessionState.class); + SessionMethodUtils.attachSpanFromStore( + sessionContextStore, (Session) session, criteriaContextStore, criteria); + } else if (session instanceof StatelessSession) { + final ContextStore sessionContextStore = + InstrumentationContext.get(StatelessSession.class, SessionState.class); + SessionMethodUtils.attachSpanFromStore( + sessionContextStore, (StatelessSession) session, criteriaContextStore, criteria); + } + } + } +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/TransactionInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/TransactionInstrumentation.java similarity index 87% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/TransactionInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/TransactionInstrumentation.java index b2928f4540..cab6ee8e2d 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/TransactionInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/main/java/datadog/trace/instrumentation/hibernate/core/v3_5/TransactionInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate.core.v3_5; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static java.util.Collections.singletonMap; @@ -12,6 +12,8 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -34,13 +36,13 @@ public class TransactionInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - packageName + ".SessionMethodUtils", - packageName + ".SessionState", + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - packageName + ".HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/AbstractHibernateTest.groovy b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/AbstractHibernateTest.groovy new file mode 100644 index 0000000000..3a2f945d9e --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/AbstractHibernateTest.groovy @@ -0,0 +1,35 @@ +import datadog.trace.agent.test.AgentTestRunner +import org.hibernate.Session +import org.hibernate.SessionFactory +import org.hibernate.cfg.AnnotationConfiguration +import spock.lang.Shared + +abstract class AbstractHibernateTest extends AgentTestRunner { + + @Shared + protected SessionFactory sessionFactory + + @Shared + protected List prepopulated + + def setupSpec() { + sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory() + + // Pre-populate the DB, so delete/update can be tested. + Session writer = sessionFactory.openSession() + writer.beginTransaction() + prepopulated = new ArrayList<>() + for (int i = 0; i < 2; i++) { + prepopulated.add(new Value("Hello :) " + i)) + writer.save(prepopulated.get(i)) + } + writer.getTransaction().commit() + writer.close() + } + + def cleanupSpec() { + if (sessionFactory != null) { + sessionFactory.close() + } + } +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/CriteriaTest.groovy b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/CriteriaTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/CriteriaTest.groovy rename to dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/CriteriaTest.groovy diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/QueryTest.groovy b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/QueryTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/QueryTest.groovy rename to dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/QueryTest.groovy diff --git a/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/SessionTest.groovy b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/SessionTest.groovy new file mode 100644 index 0000000000..01d538f9c6 --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/groovy/SessionTest.groovy @@ -0,0 +1,627 @@ +import datadog.trace.api.DDSpanTypes +import io.opentracing.Scope +import io.opentracing.Tracer +import io.opentracing.tag.Tags +import io.opentracing.util.GlobalTracer +import org.hibernate.* +import spock.lang.Shared + +class SessionTest extends AbstractHibernateTest { + + @Shared + private Closure sessionBuilder = { return sessionFactory.openSession() } + @Shared + private Closure statelessSessionBuilder = { return sessionFactory.openStatelessSession() } + + + def "test hibernate action #testName"() { + setup: + + // Test for each implementation of Session. + for (def buildSession : sessionImplementations) { + def session = buildSession() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + } + + expect: + assertTraces(sessionImplementations.size()) { + for (int i = 0; i < sessionImplementations.size(); i++) { + trace(i, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName resource + operationName "hibernate.$methodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(3) { + serviceName "h2" + spanType "sql" + childOf span(2) + } + } + } + } + + where: + testName | methodName | resource | sessionImplementations | sessionMethodTest + "lock" | "lock" | "Value" | [sessionBuilder] | { sesh, val -> + sesh.lock(val, LockMode.READ) + } + "refresh" | "refresh" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val -> + sesh.refresh(val) + } + "get" | "get" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val -> + sesh.get("Value", val.getId()) + } + } + + def "test hibernate statless action #testName"() { + setup: + + // Test for each implementation of Session. + def session = statelessSessionBuilder() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "h2" + spanType "sql" + childOf span(1) + } + span(3) { + serviceName "hibernate" + resourceName resource + operationName "hibernate.$methodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } + } + + where: + testName | methodName | resource | sessionMethodTest + "insert" | "insert" | "Value" | { sesh, val -> + sesh.insert("Value", new Value("insert me")) + } + "update" | "update" | "Value" | { sesh, val -> + val.setName("New name") + sesh.update(val) + } + "update by entityName" | "update" | "Value" | { sesh, val -> + val.setName("New name") + sesh.update("Value", val) + } + "delete" | "delete" | "Value" | { sesh, val -> + sesh.delete(val) + } + } + + def "test hibernate replicate: #testName"() { + setup: + + // Test for each implementation of Session. + def session = sessionFactory.openSession() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 5) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "h2" + spanType "sql" + childOf span(1) + } + span(3) { + serviceName "hibernate" + resourceName resource + operationName "hibernate.$methodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(4) { + serviceName "h2" + spanType "sql" + childOf span(3) + } + } + + } + + where: + testName | methodName | resource | sessionMethodTest + "replicate" | "replicate" | "Value" | { sesh, val -> + Value replicated = new Value(val.getName() + " replicated") + replicated.setId(val.getId()) + sesh.replicate(replicated, ReplicationMode.OVERWRITE) + } + "replicate by entityName" | "replicate" | "Value" | { sesh, val -> + Value replicated = new Value(val.getName() + " replicated") + replicated.setId(val.getId()) + sesh.replicate("Value", replicated, ReplicationMode.OVERWRITE) + } + } + + def "test hibernate failed replicate"() { + setup: + + // Test for each implementation of Session. + def session = sessionFactory.openSession() + session.beginTransaction() + + try { + session.replicate(new Long(123) /* Not a valid entity */, ReplicationMode.OVERWRITE) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 3) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName "hibernate.replicate" + operationName "hibernate.replicate" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + errored(true) + tags { + errorTags(MappingException, "Unknown entity: java.lang.Long") + + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } + + } + } + + + def "test hibernate commit action #testName"() { + setup: + + def session = sessionBuilder() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "h2" + spanType "sql" + childOf span(1) + } + span(3) { + serviceName "hibernate" + resourceName resource + operationName "hibernate.$methodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + + } + } + + where: + testName | methodName | resource | sessionMethodTest + "save" | "save" | "Value" | { sesh, val -> + sesh.save(new Value("Another value")) + } + "saveOrUpdate save" | "saveOrUpdate" | "Value" | { sesh, val -> + sesh.saveOrUpdate(new Value("Value")) + } + "saveOrUpdate update" | "saveOrUpdate" | "Value" | { sesh, val -> + val.setName("New name") + sesh.saveOrUpdate(val) + } + "merge" | "merge" | "Value" | { sesh, val -> + sesh.merge(new Value("merge me in")) + } + "persist" | "persist" | "Value" | { sesh, val -> + sesh.persist(new Value("merge me in")) + } + "update (Session)" | "update" | "Value" | { sesh, val -> + val.setName("New name") + sesh.update(val) + } + "update by entityName (Session)" | "update" | "Value" | { sesh, val -> + val.setName("New name") + sesh.update("Value", val) + } + "delete (Session)" | "delete" | "Value" | { sesh, val -> + sesh.delete(val) + } + } + + + def "test attaches State to query created via #queryMethodName"() { + setup: + Session session = sessionFactory.openSession() + session.beginTransaction() + Query query = queryBuildMethod(session) + query.list() + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName "$resource" + operationName "hibernate.query.list" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(3) { + serviceName "h2" + spanType "sql" + childOf span(2) + } + } + } + + where: + queryMethodName | resource | queryBuildMethod + "createQuery" | "Value" | { sess -> sess.createQuery("from Value") } + "getNamedQuery" | "Value" | { sess -> sess.getNamedQuery("TestNamedQuery") } + "createSQLQuery" | "SELECT * FROM Value" | { sess -> sess.createSQLQuery("SELECT * FROM Value") } + } + + + def "test hibernate overlapping Sessions"() { + setup: + + Tracer tracer = GlobalTracer.get() + + Scope scope = tracer.buildSpan("overlapping Sessions").startActive(true) + + def session1 = sessionFactory.openSession() + session1.beginTransaction() + def session2 = sessionFactory.openStatelessSession() + def session3 = sessionFactory.openSession() + + def value1 = new Value("Value 1") + session1.save(value1) + session2.insert(new Value("Value 2")) + session3.save(new Value("Value 3")) + session1.delete(value1) + + session2.close() + session1.getTransaction().commit() + session1.close() + session3.close() + + scope.close() + + + expect: + assertTraces(1) { + trace(0, 11) { + span(0) { + serviceName "unnamed-java-app" + operationName "overlapping Sessions" + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(3) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(2) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(4) { + serviceName "h2" + spanType "sql" + childOf span(3) + } + span(5) { + serviceName "h2" + spanType "sql" + childOf span(3) + } + span(6) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(7) { + serviceName "hibernate" + resourceName "Value" + operationName "hibernate.delete" + spanType DDSpanTypes.HIBERNATE + childOf span(2) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(8) { + serviceName "hibernate" + resourceName "Value" + operationName "hibernate.save" + spanType DDSpanTypes.HIBERNATE + childOf span(1) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(9) { + serviceName "hibernate" + resourceName "Value" + operationName "hibernate.insert" + spanType DDSpanTypes.HIBERNATE + childOf span(6) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(10) { + serviceName "hibernate" + resourceName "Value" + operationName "hibernate.save" + spanType DDSpanTypes.HIBERNATE + childOf span(2) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } + } + } +} + diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/test/java/Value.java b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/java/Value.java similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.0/src/test/java/Value.java rename to dd-java-agent/instrumentation/hibernate/core-3.5/src/test/java/Value.java diff --git a/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/resources/hibernate.cfg.xml b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/resources/hibernate.cfg.xml new file mode 100644 index 0000000000..2f423e052b --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-3.5/src/test/resources/hibernate.cfg.xml @@ -0,0 +1,31 @@ + + + + + + + + false + + + org.h2.Driver + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE + sa + + org.hibernate.dialect.H2Dialect + + 3 + org.hibernate.cache.internal.NoCacheProvider + true + + + create + + + + + + + diff --git a/dd-java-agent/instrumentation/hibernate-4.0/hibernate-4.0.gradle b/dd-java-agent/instrumentation/hibernate/core-4.0/core-4.0.gradle similarity index 94% rename from dd-java-agent/instrumentation/hibernate-4.0/hibernate-4.0.gradle rename to dd-java-agent/instrumentation/hibernate/core-4.0/core-4.0.gradle index aa27bb6875..9cec52d893 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/hibernate-4.0.gradle +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/core-4.0.gradle @@ -27,6 +27,7 @@ dependencies { compileOnly group: 'org.hibernate', name: 'hibernate-core', version: '4.0.0.Final' compile project(':dd-java-agent:agent-tooling') + compile project(':dd-java-agent:instrumentation:hibernate') compile deps.bytebuddy compile deps.opentracing @@ -47,5 +48,5 @@ dependencies { latestDepTestCompile group: 'org.hibernate', name: 'hibernate-core', version: '+' latestDepTestCompile group: 'com.h2database', name: 'h2', version: '1.4.197' // Test that the incremental instrumentation for hibernate 4.3 doesn't cause issues. - latestDepTestCompile project(':dd-java-agent:instrumentation:hibernate-4.3') + latestDepTestCompile project(':dd-java-agent:instrumentation:hibernate:core-4.3') } diff --git a/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/CriteriaInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/CriteriaInstrumentation.java new file mode 100644 index 0000000000..4206d25aea --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/CriteriaInstrumentation.java @@ -0,0 +1,83 @@ +package datadog.trace.instrumentation.hibernate.core.v4_0; + +import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.Criteria; + +@AutoService(Instrumenter.class) +public class CriteriaInstrumentation extends Instrumenter.Default { + + public CriteriaInstrumentation() { + super("hibernate", "hibernate-core"); + } + + @Override + public Map contextStore() { + return singletonMap("org.hibernate.Criteria", SessionState.class.getName()); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.DatabaseClientDecorator", + "datadog.trace.agent.decorator.OrmClientDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", + }; + } + + @Override + public ElementMatcher typeMatcher() { + return not(isInterface()).and(safeHasSuperType(named("org.hibernate.Criteria"))); + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod().and(named("list").or(named("uniqueResult")).or(named("scroll"))), + CriteriaMethodAdvice.class.getName()); + } + + public static class CriteriaMethodAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SessionState startMethod( + @Advice.This final Criteria criteria, @Advice.Origin("#m") final String name) { + + final ContextStore contextStore = + InstrumentationContext.get(Criteria.class, SessionState.class); + + return SessionMethodUtils.startScopeFrom( + contextStore, criteria, "hibernate.criteria." + name, null, true); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endMethod( + @Advice.Enter final SessionState state, + @Advice.Thrown final Throwable throwable, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) final Object entity) { + + SessionMethodUtils.closeScope(state, throwable, entity); + } + } +} diff --git a/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/QueryInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/QueryInstrumentation.java new file mode 100644 index 0000000000..afa3c89c28 --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/QueryInstrumentation.java @@ -0,0 +1,102 @@ +package datadog.trace.instrumentation.hibernate.core.v4_0; + +import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.Query; +import org.hibernate.SQLQuery; + +@AutoService(Instrumenter.class) +public class QueryInstrumentation extends Instrumenter.Default { + + public QueryInstrumentation() { + super("hibernate", "hibernate-core"); + } + + @Override + public Map contextStore() { + return singletonMap("org.hibernate.Query", SessionState.class.getName()); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.DatabaseClientDecorator", + "datadog.trace.agent.decorator.OrmClientDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", + }; + } + + @Override + public ElementMatcher typeMatcher() { + return not(isInterface()).and(safeHasSuperType(named("org.hibernate.Query"))); + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod() + .and( + named("list") + .or(named("executeUpdate")) + .or(named("uniqueResult")) + .or(named("scroll"))), + QueryMethodAdvice.class.getName()); + } + + public static class QueryMethodAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SessionState startMethod( + @Advice.This final Query query, @Advice.Origin("#m") final String name) { + + final ContextStore contextStore = + InstrumentationContext.get(Query.class, SessionState.class); + + // Note: We don't know what the entity is until the method is returning. + final SessionState state = + SessionMethodUtils.startScopeFrom( + contextStore, query, "hibernate.query." + name, null, true); + DECORATOR.onStatement(state.getMethodScope().span(), query.getQueryString()); + return state; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endMethod( + @Advice.This final Query query, + @Advice.Enter final SessionState state, + @Advice.Thrown final Throwable throwable, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) final Object returned) { + + Object entity = returned; + if (returned == null || query instanceof SQLQuery) { + // Not a method that returns results, or the query returns a table rather than an ORM + // object. + entity = query.getQueryString(); + } + + SessionMethodUtils.closeScope(state, throwable, entity); + } + } +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/SessionFactoryInstrumentation.java similarity index 89% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/SessionFactoryInstrumentation.java index 7ffb45ee2f..ce069f5365 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/SessionFactoryInstrumentation.java @@ -1,7 +1,7 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate.core.v4_0; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; -import static datadog.trace.instrumentation.hibernate.v4_0.HibernateDecorator.DECORATOR; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isMethod; @@ -14,6 +14,7 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionState; import io.opentracing.Span; import io.opentracing.util.GlobalTracer; import java.util.Map; @@ -38,12 +39,12 @@ public class SessionFactoryInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - packageName + ".SessionState", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - packageName + ".HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/SessionInstrumentation.java similarity index 93% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/SessionInstrumentation.java index 88cbf2af6f..3e7759a80c 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/SessionInstrumentation.java @@ -1,8 +1,8 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate.core.v4_0; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; -import static datadog.trace.instrumentation.hibernate.v4_0.HibernateDecorator.DECORATOR; -import static datadog.trace.instrumentation.hibernate.v4_0.SessionMethodUtils.SCOPE_ONLY_METHODS; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; +import static datadog.trace.instrumentation.hibernate.SessionMethodUtils.SCOPE_ONLY_METHODS; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -15,6 +15,8 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; import io.opentracing.Span; import java.util.Collections; import java.util.HashMap; @@ -49,13 +51,13 @@ public class SessionInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - packageName + ".SessionMethodUtils", - packageName + ".SessionState", + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - packageName + ".HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/TransactionInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/TransactionInstrumentation.java new file mode 100644 index 0000000000..c775018436 --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_0/TransactionInstrumentation.java @@ -0,0 +1,82 @@ +package datadog.trace.instrumentation.hibernate.core.v4_0; + +import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.Transaction; + +@AutoService(Instrumenter.class) +public class TransactionInstrumentation extends Instrumenter.Default { + + public TransactionInstrumentation() { + super("hibernate", "hibernate-core"); + } + + @Override + public Map contextStore() { + return singletonMap("org.hibernate.Transaction", SessionState.class.getName()); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.DatabaseClientDecorator", + "datadog.trace.agent.decorator.OrmClientDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", + }; + } + + @Override + public ElementMatcher typeMatcher() { + return not(isInterface()).and(safeHasSuperType(named("org.hibernate.Transaction"))); + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod().and(named("commit")).and(takesArguments(0)), + TransactionCommitAdvice.class.getName()); + } + + public static class TransactionCommitAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SessionState startCommit(@Advice.This final Transaction transaction) { + + final ContextStore contextStore = + InstrumentationContext.get(Transaction.class, SessionState.class); + + return SessionMethodUtils.startScopeFrom( + contextStore, transaction, "hibernate.transaction.commit", null, true); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endCommit( + @Advice.This final Transaction transaction, + @Advice.Enter final SessionState state, + @Advice.Thrown final Throwable throwable) { + + SessionMethodUtils.closeScope(state, throwable, null); + } + } +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/AbstractHibernateTest.groovy b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/AbstractHibernateTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/AbstractHibernateTest.groovy rename to dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/AbstractHibernateTest.groovy diff --git a/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/CriteriaTest.groovy b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/CriteriaTest.groovy new file mode 100644 index 0000000000..3f8f47b5b2 --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/CriteriaTest.groovy @@ -0,0 +1,74 @@ +import datadog.trace.api.DDSpanTypes +import io.opentracing.tag.Tags +import org.hibernate.Criteria +import org.hibernate.Session +import org.hibernate.criterion.Order +import org.hibernate.criterion.Restrictions + +class CriteriaTest extends AbstractHibernateTest { + + def "test criteria.#methodName"() { + setup: + Session session = sessionFactory.openSession() + session.beginTransaction() + Criteria criteria = session.createCriteria(Value) + .add(Restrictions.like("name", "Hello")) + .addOrder(Order.desc("name")) + interaction.call(criteria) + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName "hibernate.criteria.$methodName" + operationName "hibernate.criteria.$methodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(3) { + serviceName "h2" + spanType "sql" + childOf span(2) + } + } + } + + where: + methodName | interaction + "list" | { c -> c.list() } + "uniqueResult" | { c -> c.uniqueResult() } + "scroll" | { c -> c.scroll() } + } +} diff --git a/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/QueryTest.groovy b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/QueryTest.groovy new file mode 100644 index 0000000000..763d531d73 --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/QueryTest.groovy @@ -0,0 +1,192 @@ +import datadog.trace.api.DDSpanTypes +import io.opentracing.tag.Tags +import org.hibernate.Query +import org.hibernate.Session + +class QueryTest extends AbstractHibernateTest { + + def "test hibernate query.#queryMethodName single call"() { + setup: + + // With Transaction + Session session = sessionFactory.openSession() + session.beginTransaction() + queryInteraction(session) + session.getTransaction().commit() + session.close() + + // Without Transaction + if (!requiresTransaction) { + session = sessionFactory.openSession() + queryInteraction(session) + session.close() + } + + expect: + assertTraces(requiresTransaction ? 1 : 2) { + // With Transaction + trace(0, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName "$resource" + operationName "hibernate.$queryMethodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(3) { + serviceName "h2" + spanType "sql" + childOf span(2) + } + } + if (!requiresTransaction) { + // Without Transaction + trace(1, 3) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "$resource" + operationName "hibernate.$queryMethodName" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "h2" + spanType "sql" + childOf span(1) + } + } + } + } + + where: + queryMethodName | resource | requiresTransaction | queryInteraction + "query.list" | "Value" | false | { sess -> + Query q = sess.createQuery("from Value") + q.list() + } + "query.executeUpdate" | "update Value set name = 'alyx'" | true | { sess -> + Query q = sess.createQuery("update Value set name = 'alyx'") + q.executeUpdate() + } + "query.uniqueResult" | "Value" | false | { sess -> + Query q = sess.createQuery("from Value where id = 1") + q.uniqueResult() + } + "iterate" | "from Value" | false | { sess -> + Query q = sess.createQuery("from Value") + q.iterate() + } + "query.scroll" | "from Value" | false | { sess -> + Query q = sess.createQuery("from Value") + q.scroll() + } + } + + def "test hibernate query.iterate"() { + setup: + + Session session = sessionFactory.openSession() + session.beginTransaction() + Query q = session.createQuery("from Value") + Iterator it = q.iterate() + while (it.hasNext()) { + it.next() + } + session.getTransaction().commit() + session.close() + + expect: + assertTraces(1) { + trace(0, 4) { + span(0) { + serviceName "hibernate" + resourceName "hibernate.session" + operationName "hibernate.session" + spanType DDSpanTypes.HIBERNATE + parent() + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(1) { + serviceName "hibernate" + resourceName "hibernate.transaction.commit" + operationName "hibernate.transaction.commit" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(2) { + serviceName "hibernate" + resourceName "from Value" + operationName "hibernate.iterate" + spanType DDSpanTypes.HIBERNATE + childOf span(0) + tags { + "$Tags.COMPONENT.key" "java-hibernate" + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + span(3) { + serviceName "h2" + spanType "sql" + childOf span(2) + } + } + } + } + +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/SessionTest.groovy b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/SessionTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.0/src/test/groovy/SessionTest.groovy rename to dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/SessionTest.groovy diff --git a/dd-java-agent/instrumentation/hibernate-4.3/src/test/java/Value.java b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/java/Value.java similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.3/src/test/java/Value.java rename to dd-java-agent/instrumentation/hibernate/core-4.0/src/test/java/Value.java diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/test/resources/hibernate.cfg.xml b/dd-java-agent/instrumentation/hibernate/core-4.0/src/test/resources/hibernate.cfg.xml similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.0/src/test/resources/hibernate.cfg.xml rename to dd-java-agent/instrumentation/hibernate/core-4.0/src/test/resources/hibernate.cfg.xml diff --git a/dd-java-agent/instrumentation/hibernate-4.3/hibernate-4.3.gradle b/dd-java-agent/instrumentation/hibernate/core-4.3/core-4.3.gradle similarity index 94% rename from dd-java-agent/instrumentation/hibernate-4.3/hibernate-4.3.gradle rename to dd-java-agent/instrumentation/hibernate/core-4.3/core-4.3.gradle index 041a3f3b8b..0b0a2f0862 100644 --- a/dd-java-agent/instrumentation/hibernate-4.3/hibernate-4.3.gradle +++ b/dd-java-agent/instrumentation/hibernate/core-4.3/core-4.3.gradle @@ -27,7 +27,7 @@ dependencies { compileOnly group: 'org.hibernate', name: 'hibernate-core', version: '4.3.0.Final' compile project(':dd-java-agent:agent-tooling') - compile project(':dd-java-agent:instrumentation:hibernate-4.0') + compile project(':dd-java-agent:instrumentation:hibernate:core-4.0') compile deps.bytebuddy compile deps.opentracing diff --git a/dd-java-agent/instrumentation/hibernate-4.3/src/main/java/datadog/trace/instrumentation/hibernate/v4_3/ProcedureCallInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.3/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_3/ProcedureCallInstrumentation.java similarity index 87% rename from dd-java-agent/instrumentation/hibernate-4.3/src/main/java/datadog/trace/instrumentation/hibernate/v4_3/ProcedureCallInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-4.3/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_3/ProcedureCallInstrumentation.java index 5d7bbc7237..a1ed3f2ee0 100644 --- a/dd-java-agent/instrumentation/hibernate-4.3/src/main/java/datadog/trace/instrumentation/hibernate/v4_3/ProcedureCallInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-4.3/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_3/ProcedureCallInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.hibernate.v4_3; +package datadog.trace.instrumentation.hibernate.core.v4_3; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static java.util.Collections.singletonMap; @@ -11,8 +11,8 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.instrumentation.hibernate.v4_0.SessionMethodUtils; -import datadog.trace.instrumentation.hibernate.v4_0.SessionState; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -35,13 +35,13 @@ public class ProcedureCallInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - "datadog.trace.instrumentation.hibernate.v4_0.SessionMethodUtils", - "datadog.trace.instrumentation.hibernate.v4_0.SessionState", + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - "datadog.trace.instrumentation.hibernate.v4_0.HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate-4.3/src/main/java/datadog/trace/instrumentation/hibernate/v4_3/SessionInstrumentation.java b/dd-java-agent/instrumentation/hibernate/core-4.3/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_3/SessionInstrumentation.java similarity index 88% rename from dd-java-agent/instrumentation/hibernate-4.3/src/main/java/datadog/trace/instrumentation/hibernate/v4_3/SessionInstrumentation.java rename to dd-java-agent/instrumentation/hibernate/core-4.3/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_3/SessionInstrumentation.java index a31181ef32..17fb2ef2b0 100644 --- a/dd-java-agent/instrumentation/hibernate-4.3/src/main/java/datadog/trace/instrumentation/hibernate/v4_3/SessionInstrumentation.java +++ b/dd-java-agent/instrumentation/hibernate/core-4.3/src/main/java/datadog/trace/instrumentation/hibernate/core/v4_3/SessionInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.hibernate.v4_3; +package datadog.trace.instrumentation.hibernate.core.v4_3; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; @@ -11,8 +11,8 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.instrumentation.hibernate.v4_0.SessionMethodUtils; -import datadog.trace.instrumentation.hibernate.v4_0.SessionState; +import datadog.trace.instrumentation.hibernate.SessionMethodUtils; +import datadog.trace.instrumentation.hibernate.SessionState; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -41,13 +41,13 @@ public class SessionInstrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - "datadog.trace.instrumentation.hibernate.v4_0.SessionMethodUtils", - "datadog.trace.instrumentation.hibernate.v4_0.SessionState", + "datadog.trace.instrumentation.hibernate.SessionMethodUtils", + "datadog.trace.instrumentation.hibernate.SessionState", "datadog.trace.agent.decorator.BaseDecorator", "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.DatabaseClientDecorator", "datadog.trace.agent.decorator.OrmClientDecorator", - "datadog.trace.instrumentation.hibernate.v4_0.HibernateDecorator", + "datadog.trace.instrumentation.hibernate.HibernateDecorator", }; } diff --git a/dd-java-agent/instrumentation/hibernate-4.3/src/test/groovy/ProcedureCallTest.groovy b/dd-java-agent/instrumentation/hibernate/core-4.3/src/test/groovy/ProcedureCallTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.3/src/test/groovy/ProcedureCallTest.groovy rename to dd-java-agent/instrumentation/hibernate/core-4.3/src/test/groovy/ProcedureCallTest.groovy diff --git a/dd-java-agent/instrumentation/hibernate/core-4.3/src/test/java/Value.java b/dd-java-agent/instrumentation/hibernate/core-4.3/src/test/java/Value.java new file mode 100644 index 0000000000..b8f4235cce --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/core-4.3/src/test/java/Value.java @@ -0,0 +1,42 @@ +import datadog.trace.api.Trace; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.NamedQuery; + +@Entity +@Table +@NamedQuery(name = "TestNamedQuery", query = "from Value") +public class Value { + + private Long id; + private String name; + + public Value() {} + + public Value(final String name) { + this.name = name; + } + + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + public Long getId() { + return id; + } + + private void setId(final Long id) { + this.id = id; + } + + @Trace + public String getName() { + return name; + } + + public void setName(final String title) { + this.name = title; + } +} diff --git a/dd-java-agent/instrumentation/hibernate-4.3/src/test/resources/hibernate.cfg.xml b/dd-java-agent/instrumentation/hibernate/core-4.3/src/test/resources/hibernate.cfg.xml similarity index 100% rename from dd-java-agent/instrumentation/hibernate-4.3/src/test/resources/hibernate.cfg.xml rename to dd-java-agent/instrumentation/hibernate/core-4.3/src/test/resources/hibernate.cfg.xml diff --git a/dd-java-agent/instrumentation/hibernate/hibernate.gradle b/dd-java-agent/instrumentation/hibernate/hibernate.gradle new file mode 100644 index 0000000000..e6b7aeff0b --- /dev/null +++ b/dd-java-agent/instrumentation/hibernate/hibernate.gradle @@ -0,0 +1,14 @@ +/* + * Classes that are common to all versions of the Hibernate instrumentation. + */ + +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + compile project(':dd-java-agent:agent-tooling') + + compile deps.bytebuddy + compile deps.opentracing + annotationProcessor deps.autoservice + implementation deps.autoservice +} diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/HibernateDecorator.java b/dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/HibernateDecorator.java similarity index 76% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/HibernateDecorator.java rename to dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/HibernateDecorator.java index 3ebfbe1805..b9cb30a859 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/HibernateDecorator.java +++ b/dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/HibernateDecorator.java @@ -1,9 +1,11 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate; import datadog.trace.agent.decorator.OrmClientDecorator; import datadog.trace.api.DDSpanTypes; +import java.lang.annotation.Annotation; +import java.util.HashSet; import java.util.List; -import javax.persistence.Entity; +import java.util.Set; public class HibernateDecorator extends OrmClientDecorator { public static final HibernateDecorator DECORATOR = new HibernateDecorator(); @@ -46,16 +48,22 @@ public class HibernateDecorator extends OrmClientDecorator { @Override public String entityName(final Object entity) { String name = null; + final Set annotations = new HashSet<>(); + for (final Annotation annotation : entity.getClass().getDeclaredAnnotations()) { + annotations.add(annotation.annotationType().getName()); + } + if (entity instanceof String) { // We were given an entity name, not the entity itself. name = (String) entity; - } else if (entity.getClass().isAnnotationPresent(Entity.class)) { + } else if (annotations.contains("javax.persistence.Entity")) { // We were given an instance of an entity. name = entity.getClass().getName(); } else if (entity instanceof List && ((List) entity).size() > 0) { // We have a list of entities. name = entityName(((List) entity).get(0)); } + return name; } } diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionMethodUtils.java b/dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/SessionMethodUtils.java similarity index 95% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionMethodUtils.java rename to dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/SessionMethodUtils.java index 656d688f53..65dfcbacf8 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionMethodUtils.java +++ b/dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/SessionMethodUtils.java @@ -1,6 +1,6 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate; -import static datadog.trace.instrumentation.hibernate.v4_0.HibernateDecorator.DECORATOR; +import static datadog.trace.instrumentation.hibernate.HibernateDecorator.DECORATOR; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.ContextStore; diff --git a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionState.java b/dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/SessionState.java similarity index 81% rename from dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionState.java rename to dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/SessionState.java index 051bcc8d0d..e320780b4f 100644 --- a/dd-java-agent/instrumentation/hibernate-4.0/src/main/java/datadog/trace/instrumentation/hibernate/v4_0/SessionState.java +++ b/dd-java-agent/instrumentation/hibernate/src/main/java/datadog/trace/instrumentation/hibernate/SessionState.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.hibernate.v4_0; +package datadog.trace.instrumentation.hibernate; import io.opentracing.Scope; import io.opentracing.Span; diff --git a/settings.gradle b/settings.gradle index 802fe72954..b57a5f08bf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -40,8 +40,10 @@ include ':dd-java-agent:instrumentation:elasticsearch:transport-5' include ':dd-java-agent:instrumentation:elasticsearch:transport-5.3' include ':dd-java-agent:instrumentation:elasticsearch:transport-6' include ':dd-java-agent:instrumentation:grpc-1.5' -include ':dd-java-agent:instrumentation:hibernate-4.0' -include ':dd-java-agent:instrumentation:hibernate-4.3' +include ':dd-java-agent:instrumentation:hibernate' +include ':dd-java-agent:instrumentation:hibernate:core-3.5' +include ':dd-java-agent:instrumentation:hibernate:core-4.0' +include ':dd-java-agent:instrumentation:hibernate:core-4.3' include ':dd-java-agent:instrumentation:http-url-connection' include ':dd-java-agent:instrumentation:hystrix-1.4' include ':dd-java-agent:instrumentation:jax-rs-annotations'