mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1237 - ENH: Add ProfileLocation - support for relating performance metrics for queries and transactions back to source line of code
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* A location for profiling transactions and queries.
|
||||
* <p>
|
||||
* Typically represents a class method in the form of class file and line of code that started
|
||||
* the transaction or invoked the query.
|
||||
* </p>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -1369,6 +1369,12 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> 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<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
|
||||
@@ -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 {
|
||||
* </p>
|
||||
*/
|
||||
static RawSqlBuilder parse(String sql) {
|
||||
return XServiceProvider.get().parsed(sql);
|
||||
return XServiceProvider.rawSql().parsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,6 +52,8 @@ public final class TxScope {
|
||||
|
||||
private ArrayList<Class<? extends Throwable>> 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.
|
||||
*/
|
||||
|
||||
@@ -148,6 +148,12 @@ public interface UpdateQuery<T> {
|
||||
*/
|
||||
UpdateQuery<T> 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<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Return the query expression list to add predicates to.
|
||||
*/
|
||||
|
||||
@@ -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<SpiRawSqlService> 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<SpiProfileLocationFactory> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
short getProfileId();
|
||||
|
||||
/**
|
||||
* Return the profile location for this query.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Check for a single "equal to" expression for the Id.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<T> implements SpiQuery<T> {
|
||||
*/
|
||||
private short profileId;
|
||||
|
||||
private ProfileLocation profileLocation;
|
||||
|
||||
public DefaultOrmQuery(BeanDescriptor<T> desc, EbeanServer server, ExpressionFactory expressionFactory) {
|
||||
this.beanDescriptor = desc;
|
||||
this.beanType = desc.getBeanType();
|
||||
@@ -304,6 +307,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> setProfileLocation(ProfileLocation profileLocation) {
|
||||
this.profileLocation = profileLocation;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoTunable() {
|
||||
return nativeSql == null && beanDescriptor.isAutoTunable();
|
||||
@@ -1732,4 +1741,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
public OrmUpdateProperties getUpdateProperties() {
|
||||
return updateProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileLocation getProfileLocation() {
|
||||
return profileLocation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> implements UpdateQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation) {
|
||||
query.setProfileLocation(profileLocation);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> where() {
|
||||
return query.where();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user