#1237 - ENH: Add ProfileLocation - support for relating performance metrics for queries and transactions back to source line of code

This commit is contained in:
Rob Bygrave
2018-01-18 18:10:03 +13:00
parent 82f5c21d26
commit b9ae76d465
23 changed files with 359 additions and 11 deletions
@@ -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();
}
+6
View File
@@ -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>
+3 -3
View File
@@ -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);
}
/**
+17
View File
@@ -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.
*/
+6
View File
@@ -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.
*/
+24 -4
View File
@@ -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.
*/