diff --git a/src/main/java/io/ebean/ProfileLocation.java b/src/main/java/io/ebean/ProfileLocation.java new file mode 100644 index 000000000..c7015ec7c --- /dev/null +++ b/src/main/java/io/ebean/ProfileLocation.java @@ -0,0 +1,37 @@ +package io.ebean; + +/** + * A location for profiling transactions and queries. + *

+ * Typically represents a class method in the form of class file and line of code that started + * the transaction or invoked the query. + *

+ */ +public interface ProfileLocation { + + /** + * Create and return a new ProfileLocation. + */ + static ProfileLocation create() { + return XServiceProvider.profileLocationFactory().create(); + } + + /** + * Create and return a new ProfileLocation with a given lineNumber. + */ + static ProfileLocation create(int lineNumber) { + return XServiceProvider.profileLocationFactory().create(lineNumber); + } + + /** + * Create and return a new ProfileLocation with a given location. + */ + static ProfileLocation create(String location) { + return XServiceProvider.profileLocationFactory().create(location); + } + + /** + * Obtain the location description. + */ + String obtain(); +} diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java index 0ffb4503f..53499f9c6 100644 --- a/src/main/java/io/ebean/Query.java +++ b/src/main/java/io/ebean/Query.java @@ -1369,6 +1369,12 @@ public interface Query { */ Query setProfileId(int profileId); + /** + * Set the profile location of this query. This is used to relate query execution metrics + * back to a location like a specific line of code. + */ + Query setProfileLocation(ProfileLocation profileLocation); + /** * Set to true if this query should execute against the doc store. *

diff --git a/src/main/java/io/ebean/RawSqlBuilder.java b/src/main/java/io/ebean/RawSqlBuilder.java index 4cebe5832..3d1e4f4ca 100644 --- a/src/main/java/io/ebean/RawSqlBuilder.java +++ b/src/main/java/io/ebean/RawSqlBuilder.java @@ -21,7 +21,7 @@ public interface RawSqlBuilder { * resultSet. */ static RawSql resultSet(ResultSet resultSet, String... propertyNames) { - return XServiceProvider.get().resultSet(resultSet, propertyNames); + return XServiceProvider.rawSql().resultSet(resultSet, propertyNames); } /** @@ -30,7 +30,7 @@ public interface RawSqlBuilder { * this query. */ static RawSqlBuilder unparsed(String sql) { - return XServiceProvider.get().unparsed(sql); + return XServiceProvider.rawSql().unparsed(sql); } /** @@ -47,7 +47,7 @@ public interface RawSqlBuilder { *

*/ static RawSqlBuilder parse(String sql) { - return XServiceProvider.get().parsed(sql); + return XServiceProvider.rawSql().parsed(sql); } /** diff --git a/src/main/java/io/ebean/TxScope.java b/src/main/java/io/ebean/TxScope.java index 76bb55582..bebe6a31d 100644 --- a/src/main/java/io/ebean/TxScope.java +++ b/src/main/java/io/ebean/TxScope.java @@ -52,6 +52,8 @@ public final class TxScope { private ArrayList> noRollbackFor; + private ProfileLocation profileLocation; + /** * Helper method to create a TxScope with REQUIRES. */ @@ -185,6 +187,21 @@ public final class TxScope { return this; } + /** + * Return the profile location. + */ + public ProfileLocation getProfileLocation() { + return profileLocation; + } + + /** + * Set the profile location. + */ + public TxScope setProfileLocation(ProfileLocation profileLocation) { + this.profileLocation = profileLocation; + return this; + } + /** * Return the batch mode. */ diff --git a/src/main/java/io/ebean/UpdateQuery.java b/src/main/java/io/ebean/UpdateQuery.java index 98bb7b610..b4633a462 100644 --- a/src/main/java/io/ebean/UpdateQuery.java +++ b/src/main/java/io/ebean/UpdateQuery.java @@ -148,6 +148,12 @@ public interface UpdateQuery { */ UpdateQuery setRaw(String propertyExpression, Object... values); + /** + * Set the profile location of this update query. This is used to relate query execution metrics + * back to a location like a specific line of code. + */ + UpdateQuery setProfileLocation(ProfileLocation profileLocation); + /** * Return the query expression list to add predicates to. */ diff --git a/src/main/java/io/ebean/XServiceProvider.java b/src/main/java/io/ebean/XServiceProvider.java index 4de4be54c..518e1b3af 100644 --- a/src/main/java/io/ebean/XServiceProvider.java +++ b/src/main/java/io/ebean/XServiceProvider.java @@ -1,5 +1,6 @@ package io.ebean; +import io.ebean.service.SpiProfileLocationFactory; import io.ebean.service.SpiRawSqlService; import java.util.Iterator; @@ -10,9 +11,11 @@ import java.util.ServiceLoader; */ class XServiceProvider { - private static SpiRawSqlService builder = init(); + private static SpiRawSqlService rawSqlService = initRawSql(); - private static SpiRawSqlService init() { + private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation(); + + private static SpiRawSqlService initRawSql() { Iterator loader = ServiceLoader.load(SpiRawSqlService.class).iterator(); if (loader.hasNext()) { @@ -21,10 +24,27 @@ class XServiceProvider { throw new IllegalStateException("No service implementation found for SpiRawSqlService?"); } + private static SpiProfileLocationFactory initProfileLocation() { + + Iterator loader = ServiceLoader.load(SpiProfileLocationFactory.class).iterator(); + if (loader.hasNext()) { + return loader.next(); + } + throw new IllegalStateException("No service implementation found for SpiProfileLocationFactory?"); + } + /** * Return the RawSqlService implementation. */ - static SpiRawSqlService get() { - return builder; + static SpiRawSqlService rawSql() { + return rawSqlService; } + + /** + * Return the RawSqlService implementation. + */ + static SpiProfileLocationFactory profileLocationFactory() { + return profileLocationFactory; + } + } diff --git a/src/main/java/io/ebean/service/SpiProfileLocationFactory.java b/src/main/java/io/ebean/service/SpiProfileLocationFactory.java new file mode 100644 index 000000000..79d9b8f5a --- /dev/null +++ b/src/main/java/io/ebean/service/SpiProfileLocationFactory.java @@ -0,0 +1,24 @@ +package io.ebean.service; + +import io.ebean.ProfileLocation; + +/** + * Factory for creating profile locations. + */ +public interface SpiProfileLocationFactory { + + /** + * Create a profile location. + */ + ProfileLocation create(); + + /** + * Create a profile location with a line number. + */ + ProfileLocation create(int lineNumber); + + /** + * Create a known location. + */ + ProfileLocation create(String location); +} diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java index d257a4056..ea5ef91bd 100644 --- a/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -5,6 +5,7 @@ import io.ebean.EbeanServer; import io.ebean.ExpressionList; import io.ebean.OrderBy; import io.ebean.PersistenceContextScope; +import io.ebean.ProfileLocation; import io.ebean.Query; import io.ebean.bean.CallStack; import io.ebean.bean.ObjectGraphNode; @@ -173,6 +174,11 @@ public interface SpiQuery extends Query, TxnProfileEventCodes { */ short getProfileId(); + /** + * Return the profile location for this query. + */ + ProfileLocation getProfileLocation(); + /** * Check for a single "equal to" expression for the Id. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/src/main/java/io/ebeaninternal/api/SpiTransaction.java index 66d7f1fc9..64d261f88 100644 --- a/src/main/java/io/ebeaninternal/api/SpiTransaction.java +++ b/src/main/java/io/ebeaninternal/api/SpiTransaction.java @@ -1,5 +1,6 @@ package io.ebeaninternal.api; +import io.ebean.ProfileLocation; import io.ebean.Transaction; import io.ebean.annotation.DocStoreMode; import io.ebean.bean.PersistenceContext; @@ -298,4 +299,14 @@ public interface SpiTransaction extends Transaction { * Return the stream that profiling events are written to. */ ProfileStream profileStream(); + + /** + * Set the profile location for this transaction. + */ + void setProfileLocation(ProfileLocation profileLocation); + + /** + * Return the profile location for this transaction. + */ + ProfileLocation getProfileLocation(); } diff --git a/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java index a534898ce..5e0f7d415 100644 --- a/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java +++ b/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java @@ -1,5 +1,6 @@ package io.ebeaninternal.api; +import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; import io.ebean.annotation.PersistBatch; @@ -54,6 +55,16 @@ abstract class SpiTransactionProxy implements SpiTransaction { return transaction.profileStream(); } + @Override + public void setProfileLocation(ProfileLocation profileLocation) { + transaction.setProfileLocation(profileLocation); + } + + @Override + public ProfileLocation getProfileLocation() { + return transaction.getProfileLocation(); + } + @Override public void setTenantId(Object tenantId) { transaction.setTenantId(tenantId); diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index d838c233e..30a722a45 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -12,6 +12,7 @@ import io.ebean.FutureList; import io.ebean.FutureRowCount; import io.ebean.PagedList; import io.ebean.PersistenceContextScope; +import io.ebean.ProfileLocation; import io.ebean.Query; import io.ebean.QueryIterator; import io.ebean.SqlQuery; @@ -834,6 +835,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { break; default: transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel()); + initNewTransaction(transaction, txScope); } } @@ -844,6 +846,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return txnContainer; } + private void initNewTransaction(SpiTransaction transaction, TxScope txScope) { + + ProfileLocation profileLocation = txScope.getProfileLocation(); + if (profileLocation != null) { + transaction.setProfileLocation(profileLocation); + } + } + private TxScope initTxScope(TxScope txScope) { if (txScope == null) { return new TxScope(); diff --git a/src/main/java/io/ebeaninternal/server/core/NoTransaction.java b/src/main/java/io/ebeaninternal/server/core/NoTransaction.java index 58a2574d4..5b826035b 100644 --- a/src/main/java/io/ebeaninternal/server/core/NoTransaction.java +++ b/src/main/java/io/ebeaninternal/server/core/NoTransaction.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.core; +import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; import io.ebean.annotation.PersistBatch; @@ -416,4 +417,14 @@ class NoTransaction implements SpiTransaction { public ProfileStream profileStream() { return null; } + + @Override + public void setProfileLocation(ProfileLocation profileLocation) { + + } + + @Override + public ProfileLocation getProfileLocation() { + return null; + } } diff --git a/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java b/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java new file mode 100644 index 000000000..543f99c91 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java @@ -0,0 +1,24 @@ +package io.ebeaninternal.server.profile; + +import io.ebean.ProfileLocation; + +/** + * Fixed / given location. Used internally for find by id and find all. + */ +class BasicProfileLocation implements ProfileLocation { + + private final String location; + + BasicProfileLocation(String location) { + this.location = location; + } + + public String toString() { + return "location: " + location; + } + + public String obtain() { + return location; + } + +} diff --git a/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java b/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java new file mode 100644 index 000000000..b64129b86 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java @@ -0,0 +1,55 @@ +package io.ebeaninternal.server.profile; + +import io.ebean.ProfileLocation; + +/** + * Default profile location that uses stack trace. + */ +class DProfileLocation implements ProfileLocation { + + private static final String IO_EBEAN = "io.ebean"; + + private static final String UNKNOWN = "unknown"; + + private String location; + + private final int lineNumber; + + DProfileLocation() { + this.lineNumber = 0; + } + + DProfileLocation(int lineNumber) { + this.lineNumber = lineNumber; + } + + public String toString() { + return "location: " + location; + } + + public String obtain() { + // atomic assignment so happy with this + if (location == null) { + location = create(); + } + return location; + } + + private String create() { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + for (int i = 3; i < trace.length; i++) { + if (!trace[i].getClassName().startsWith(IO_EBEAN)) { + return withLineNumber(trace[i].toString()); + } + } + return UNKNOWN; + } + + private String withLineNumber(String traceLine) { + if (lineNumber == 0 || traceLine.contains(":")) { + return traceLine; + } else { + return traceLine.substring(0, traceLine.length() - 1) + ":" + lineNumber + ")"; + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/profile/DProfileLocationFactory.java b/src/main/java/io/ebeaninternal/server/profile/DProfileLocationFactory.java new file mode 100644 index 000000000..8d4e592d0 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/profile/DProfileLocationFactory.java @@ -0,0 +1,25 @@ +package io.ebeaninternal.server.profile; + +import io.ebean.ProfileLocation; +import io.ebean.service.SpiProfileLocationFactory; + +/** + * Default implementation of the profile location factory. + */ +public class DProfileLocationFactory implements SpiProfileLocationFactory { + + @Override + public ProfileLocation create() { + return new DProfileLocation(); + } + + @Override + public ProfileLocation create(int lineNumber) { + return new DProfileLocation(lineNumber); + } + + @Override + public ProfileLocation create(String location) { + return new BasicProfileLocation(location); + } +} diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 562ee9d9b..29c7409ae 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -14,6 +14,7 @@ import io.ebean.OrderBy; import io.ebean.OrderBy.Property; import io.ebean.PagedList; import io.ebean.PersistenceContextScope; +import io.ebean.ProfileLocation; import io.ebean.Query; import io.ebean.QueryIterator; import io.ebean.RawSql; @@ -257,6 +258,8 @@ public class DefaultOrmQuery implements SpiQuery { */ private short profileId; + private ProfileLocation profileLocation; + public DefaultOrmQuery(BeanDescriptor desc, EbeanServer server, ExpressionFactory expressionFactory) { this.beanDescriptor = desc; this.beanType = desc.getBeanType(); @@ -304,6 +307,12 @@ public class DefaultOrmQuery implements SpiQuery { return this; } + @Override + public Query setProfileLocation(ProfileLocation profileLocation) { + this.profileLocation = profileLocation; + return this; + } + @Override public boolean isAutoTunable() { return nativeSql == null && beanDescriptor.isAutoTunable(); @@ -1732,4 +1741,9 @@ public class DefaultOrmQuery implements SpiQuery { public OrmUpdateProperties getUpdateProperties() { return updateProperties; } + + @Override + public ProfileLocation getProfileLocation() { + return profileLocation; + } } diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultUpdateQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultUpdateQuery.java index b99250331..f9189e88b 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultUpdateQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultUpdateQuery.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.ExpressionList; +import io.ebean.ProfileLocation; import io.ebean.UpdateQuery; /** @@ -41,6 +42,12 @@ public class DefaultUpdateQuery implements UpdateQuery { return this; } + @Override + public UpdateQuery setProfileLocation(ProfileLocation profileLocation) { + query.setProfileLocation(profileLocation); + return this; + } + @Override public ExpressionList where() { return query.where(); diff --git a/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java index e17444252..3699eb575 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java +++ b/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.transaction; +import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; import io.ebean.annotation.PersistBatch; @@ -92,6 +93,16 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode return null; } + @Override + public void setProfileLocation(ProfileLocation profileLocation) { + throw new IllegalStateException(notExpectedMessage); + } + + @Override + public ProfileLocation getProfileLocation() { + return null; + } + @Override public boolean isSkipCache() { return false; diff --git a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index 8dfc4abf1..f9b4034bb 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -1,15 +1,16 @@ package io.ebeaninternal.server.transaction; +import io.ebean.ProfileLocation; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; import io.ebean.annotation.PersistBatch; +import io.ebean.bean.PersistenceContext; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; -import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.SpiProfileTransactionEvent; +import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.TransactionEvent; import io.ebeaninternal.api.TxnProfileEventCodes; import io.ebeaninternal.server.core.PersistDeferredRelationship; @@ -173,6 +174,8 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { private final ProfileStream profileStream; + protected ProfileLocation profileLocation; + /** * Create without ProfileStream option (no profiling). */ @@ -230,6 +233,16 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { return profileStream; } + @Override + public void setProfileLocation(ProfileLocation profileLocation) { + this.profileLocation = profileLocation; + } + + @Override + public ProfileLocation getProfileLocation() { + return profileLocation; + } + /** * Overridden in AutoCommitJdbcTransaction as that expects to run/operate with autocommit true. */ diff --git a/src/main/resources/META-INF/services/io.ebean.service.SpiProfileLocationFactory b/src/main/resources/META-INF/services/io.ebean.service.SpiProfileLocationFactory new file mode 100644 index 000000000..b370147ab --- /dev/null +++ b/src/main/resources/META-INF/services/io.ebean.service.SpiProfileLocationFactory @@ -0,0 +1 @@ +io.ebeaninternal.server.profile.DProfileLocationFactory diff --git a/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java b/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java new file mode 100644 index 000000000..14c3b9b22 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java @@ -0,0 +1,17 @@ +package io.ebeaninternal.server.profile; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BasicProfileLocationTest { + + @Test + public void obtain() { + + DProfileLocation loc = new DProfileLocation(12); + String obtain = loc.obtain(); + + assertThat(obtain).endsWith(":12)"); + } +} diff --git a/src/test/java/org/tests/model/basic/finder/CustomerFinder.java b/src/test/java/org/tests/model/basic/finder/CustomerFinder.java index 7a7cb0352..63bedcfff 100644 --- a/src/test/java/org/tests/model/basic/finder/CustomerFinder.java +++ b/src/test/java/org/tests/model/basic/finder/CustomerFinder.java @@ -35,8 +35,9 @@ public class CustomerFinder extends Finder { * Find customer by unique name. */ public Customer byName(String name) { - - return query().where().eq("name", name).findOne(); + return query().where() + .eq("name", name) + .findOne(); } public List byNameStatus(String nameStartsWith, Customer.Status status) { diff --git a/src/test/java/org/tests/profile/ProfileLocationTest.java b/src/test/java/org/tests/profile/ProfileLocationTest.java new file mode 100644 index 000000000..49499f8b5 --- /dev/null +++ b/src/test/java/org/tests/profile/ProfileLocationTest.java @@ -0,0 +1,21 @@ +package org.tests.profile; + +import io.ebean.ProfileLocation; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProfileLocationTest { + + private static ProfileLocation loc = ProfileLocation.create(12); + + @Test + public void test() { + + assertThat(doIt()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:19)"); + } + + private String doIt() { + return loc.obtain(); + } +}