#1183 - ENH: Add support for transaction profiling

This commit is contained in:
rob bygrave
2017-10-25 01:07:00 +13:00
parent 093f82a87f
commit 7ebef51859
46 changed files with 1063 additions and 68 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ ebean-profiling*.xml
/db
/mydb.db
!src/test/ddl-review/*.sql
profiling/
# Intellij project files
*.iml
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.2.4-SNAPSHOT</version>
<version>11.3.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -87,7 +87,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.1</version>
<version>3.2</version>
</dependency>
<dependency>
+11
View File
@@ -1331,6 +1331,17 @@ public interface Query<T> {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set an id to identify this query for profiling purposes.
* <p>
* The profileId is expected to be unique for a given bean type.
* </p>
* <p>
* Note that the profileId is treated as a short internally and has a MAX value of 32,767.
* </p>
*/
Query<T> setProfileId(int profileId);
/**
* Set to true if this query should execute against the doc store.
* <p>
+17
View File
@@ -25,6 +25,8 @@ import java.util.concurrent.Callable;
*/
public final class TxScope {
int profileId;
TxType type;
String serverName;
@@ -168,6 +170,21 @@ public final class TxScope {
return this;
}
/**
* Return the transaction profile id.
*/
public int getProfileId() {
return profileId;
}
/**
* Set the transaction profile id.
*/
public TxScope setProfileId(int profileId) {
this.profileId = profileId;
return this;
}
/**
* Return the batch mode.
*/
@@ -0,0 +1,126 @@
package io.ebean.config;
/**
* Configuration for transaction profiling.
*/
public class ProfilingConfig {
/**
* When true transaction profiling is enabled.
*/
private boolean transactionProfiling;
/**
* The minimum transaction execution time to be included in profiling.
*/
private long minimumTransactionMicros;
/**
* A specific set of profileIds to include in profiling.
*/
private int[] includeProfileIds = {};
/**
* The number of profiles to write per file.
*/
private long profilesPerFile = 1000;
private String directory = "profiling";
/**
* Return true if transaction profiling is enabled.
*/
public boolean isTransactionProfiling() {
return transactionProfiling;
}
/**
* Set to true to enable transaction profiling.
*/
public void setTransactionProfiling(boolean transactionProfiling) {
this.transactionProfiling = transactionProfiling;
}
/**
* Return the minimum transaction execution to be included in profiling.
*/
public long getMinimumTransactionMicros() {
return minimumTransactionMicros;
}
/**
* Set the minimum transaction execution to be included in profiling.
*/
public void setMinimumTransactionMicros(long minimumTransactionMicros) {
this.minimumTransactionMicros = minimumTransactionMicros;
}
/**
* Return the specific set of profileIds to include in profiling.
* When not set all transactions with profileIds are included.
*/
public int[] getIncludeProfileIds() {
return includeProfileIds;
}
/**
* Set a specific set of profileIds to include in profiling.
* When not set all transactions with profileIds are included.
*/
public void setIncludeProfileIds(int[] includeProfileIds) {
this.includeProfileIds = includeProfileIds;
}
/**
* Return the number of profiles to write to a single file.
*/
public long getProfilesPerFile() {
return profilesPerFile;
}
/**
* Set the number of profiles to write to a single file.
*/
public void setProfilesPerFile(long profilesPerFile) {
this.profilesPerFile = profilesPerFile;
}
/**
* Return the directory profiling files are put into.
*/
public String getDirectory() {
return directory;
}
/**
* Set the directory profiling files are put into.
*/
public void setDirectory(String directory) {
this.directory = directory;
}
/**
* Load setting from properties.
*/
public void loadSettings(PropertiesWrapper p, String name) {
transactionProfiling = p.getBoolean("profiling.transactionProfiling", transactionProfiling);
directory = p.get("profiling.directory", directory);
profilesPerFile = p.getLong("profiling.profilesPerFile", profilesPerFile);
minimumTransactionMicros = p.getLong("profiling.minimumTransactionMicros", minimumTransactionMicros);
String includeIds = p.get("profiling.includeProfileIds");
if (includeIds != null) {
includeProfileIds = parseIds(includeIds);
}
}
private int[] parseIds(String includeIds) {
String[] ids = includeIds.split(",");
int[] vals = new int[ids.length];
for (int i = 0; i < ids.length; i++) {
vals[i] = Integer.parseInt(ids[i]);
}
return vals;
}
}
@@ -450,6 +450,9 @@ public class ServerConfig {
*/
private SlowQueryListener slowQueryListener;
private ProfilingConfig profilingConfig = new ProfilingConfig();
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1021,6 +1024,20 @@ public class ServerConfig {
this.readAuditPrepare = readAuditPrepare;
}
/**
* Return the configuration for profiling.
*/
public ProfilingConfig getProfilingConfig() {
return profilingConfig;
}
/**
* Set the configuration for profiling.
*/
public void setProfilingConfig(ProfilingConfig profilingConfig) {
this.profilingConfig = profilingConfig;
}
/**
* Return the DB migration configuration.
*/
@@ -2546,6 +2563,7 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
profilingConfig.loadSettings(p, name);
migrationConfig.loadSettings(p, name);
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
@@ -20,6 +20,11 @@ public interface BeanType<T> {
*/
String getName();
/**
* Return the profileId of the bean type.
*/
short getProfileId();
/**
* Return the full name of the bean type.
*/
@@ -10,6 +10,7 @@ import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
@@ -79,6 +80,21 @@ public class ScopedTransaction implements SpiTransaction {
}
}
@Override
public int profileOffset() {
return transaction.profileOffset();
}
@Override
public void profileEvent(SpiProfileTransactionEvent event) {
transaction.profileEvent(event);
}
@Override
public ProfileStream profileStream() {
return transaction.profileStream();
}
@Override
public void setTenantId(Object tenantId) {
transaction.setTenantId(tenantId);
@@ -0,0 +1,30 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeaninternal.server.transaction.TransactionProfile;
/**
* Handle the logging or processing of transaction profiling information that is collected.
*/
public interface SpiProfileHandler {
/**
* Process the collected transaction profiling information.
* <p>
* Note that profileId and totalMicros are part of the profilingData but passed separately as the handler
* may filter what it processed based on this information (ignore short transactions, only process specific
* profileId transactions etc).
* </p>
*
* @param transactionProfile The transaction profile that has just been collected
*/
void collectTransactionProfile(TransactionProfile transactionProfile);
/**
* Create a profiling stream if we are profiling this transaction.
* Return null if we are not profiling this transaction.
*
* @param profileId The transaction profileId
*/
ProfileStream createProfileStream(int profileId);
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.api;
/**
* Event that adds to a profiling transaction.
*/
public interface SpiProfileTransactionEvent {
/**
* Add the event information to the profiling transaction.
*/
void profile();
}
@@ -29,7 +29,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T> {
public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -53,57 +53,67 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Find by Id or unique returning a single bean.
*/
BEAN,
/**
* Find iterate type query - findEach(), findIterate() etc.
*/
ITERATE,
BEAN(FIND_ONE),
/**
* Find returning a List.
*/
LIST,
LIST(FIND_MANY),
/**
* Find returning a Set.
*/
SET,
SET(FIND_MANY),
/**
* Find returning a Map.
*/
MAP,
MAP(FIND_MANY),
/**
* Find iterate type query - findEach(), findIterate() etc.
*/
ITERATE(FIND_ITERATE),
/**
* Find the Id's.
*/
ID_LIST,
ID_LIST(FIND_ID_LIST),
/**
* Find single attribute.
*/
ATTRIBUTE,
ATTRIBUTE(FIND_ATTRIBUTE),
/**
* Find rowCount.
*/
COUNT,
COUNT(FIND_COUNT),
/**
* A subquery used as part of a where clause.
*/
SUBQUERY,
SUBQUERY(FIND_SUBQUERY),
/**
* Delete query.
*/
DELETE,
DELETE(FIND_DELETE),
/**
* Update query.
*/
UPDATE,
UPDATE(FIND_UPDATE);
byte profileEventId;
Type(byte profileEventId) {
this.profileEventId = profileEventId;
}
public byte profileEventId() {
return profileEventId;
}
}
enum TemporalMode {
@@ -140,6 +150,16 @@ public interface SpiQuery<T> extends Query<T> {
}
}
/**
* Return the profile event id based on query mode and type.
*/
byte profileEventId();
/**
* Return the id used to identify a particular query for the given bean type.
*/
short getProfileId();
/**
* Check for a single "equal to" expression for the Id.
*/
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
@@ -282,4 +283,19 @@ public interface SpiTransaction extends Transaction {
* Return the current Tenant Id.
*/
Object getTenantId();
/**
* Return the offset time from the start of the transaction.
*/
int profileOffset();
/**
* Check if the event should be added to a profiling transaction.
*/
void profileEvent(SpiProfileTransactionEvent event);
/**
* Return the stream that profiling events are written to.
*/
ProfileStream profileStream();
}
@@ -0,0 +1,35 @@
package io.ebeaninternal.api;
/**
* Event codes used in transaction profiling.
*/
public interface TxnProfileEventCodes {
byte EVT_END = 0;
byte EVT_COMMIT = 1;
byte EVT_ROLLBACK = 2;
byte EVT_INSERT = 10;
byte EVT_UPDATE = 11;
byte EVT_DELETE = 12;
byte EVT_SOFT_DELETE = 13;
byte EVT_DELETE_PERMANENT = 14;
byte EVT_ORMUPDATE = 15;
byte FIND_UPDATE = 16;
byte FIND_DELETE = 17;
byte EVT_UPDATESQL = 20;
byte EVT_CALLABLESQL = 21;
byte FIND_ONE = 30;
byte FIND_MANY = 31;
byte FIND_ITERATE = 34;
byte FIND_ID_LIST = 35;
byte FIND_ATTRIBUTE = 36;
byte FIND_COUNT = 37;
byte FIND_SUBQUERY = 38;
byte FIND_ONE_LAZY = 40;
byte FIND_MANY_LAZY = 41;
}
@@ -659,7 +659,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction createTransaction() {
return transactionManager.createTransaction(true, -1);
return transactionManager.createTransaction(0,true, -1);
}
/**
@@ -671,7 +671,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction createTransaction(TxIsolation isolation) {
return transactionManager.createTransaction(true, isolation.getLevel());
return transactionManager.createTransaction(0,true, isolation.getLevel());
}
@Override
@@ -796,7 +796,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (isolation != null) {
isoLevel = isolation.getLevel();
}
t = transactionManager.createTransaction(true, isoLevel);
t = transactionManager.createTransaction(txScope.getProfileId(), true, isoLevel);
// note ScopeTrans.onFinally() restores the suspended transaction
transactionScopeManager.replace(t);
}
@@ -842,7 +842,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction beginTransaction(TxIsolation isolation) {
// start an explicit transaction
SpiTransaction t = transactionManager.createTransaction(true, isolation.getLevel());
SpiTransaction t = transactionManager.createTransaction(0,true, isolation.getLevel());
try {
transactionScopeManager.set(t);
} catch (PersistenceException existingTransactionError) {
@@ -2094,7 +2094,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public SpiTransaction beginServerTransaction() {
SpiTransaction t = transactionManager.createTransaction(false, -1);
SpiTransaction t = transactionManager.createTransaction(0,false, -1);
transactionScopeManager.set(t);
return t;
}
@@ -5,6 +5,7 @@ import io.ebean.ExpressionFactory;
import io.ebean.annotation.Platform;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ExternalTransactionManager;
import io.ebean.config.ProfilingConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbHistorySupport;
@@ -19,6 +20,7 @@ import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.autotune.service.AutoTuneServiceFactory;
@@ -49,11 +51,13 @@ import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import io.ebeaninternal.server.text.json.DJsonContext;
import io.ebeaninternal.server.transaction.AutoCommitTransactionManager;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import io.ebeaninternal.server.transaction.DefaultProfileHandler;
import io.ebeaninternal.server.transaction.DefaultTransactionScopeManager;
import io.ebeaninternal.server.transaction.DocStoreTransactionManager;
import io.ebeaninternal.server.transaction.ExplicitTransactionManager;
import io.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
import io.ebeaninternal.server.transaction.JtaTransactionManager;
import io.ebeaninternal.server.transaction.NoopProfileHandler;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.transaction.TransactionManagerOptions;
import io.ebeaninternal.server.transaction.TransactionScopeManager;
@@ -352,7 +356,7 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(localL2, serverConfig, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource());
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -366,6 +370,19 @@ public class InternalConfiguration {
return new TransactionManager(options);
}
private SpiProfileHandler profileHandler() {
ProfilingConfig profilingConfig = serverConfig.getProfilingConfig();
if (!profilingConfig.isTransactionProfiling()) {
return new NoopProfileHandler();
}
SpiProfileHandler handler = serverConfig.service(SpiProfileHandler.class);
if (handler == null) {
handler = new DefaultProfileHandler(profilingConfig);
}
return plugin(handler);
}
/**
* Return the DataSource supplier based on the tenancy mode.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchPostExecute;
import io.ebeaninternal.server.persist.BatchedSqlException;
@@ -10,10 +11,20 @@ import io.ebeaninternal.server.persist.PersistExecute;
/**
* Wraps all the objects used to persist a bean.
*/
public abstract class PersistRequest extends BeanRequest implements BatchPostExecute {
public abstract class PersistRequest extends BeanRequest implements BatchPostExecute, TxnProfileEventCodes {
public enum Type {
INSERT, UPDATE, DELETE, SOFT_DELETE, DELETE_PERMANENT, UPDATESQL, CALLABLESQL
INSERT(EVT_INSERT),
UPDATE(EVT_UPDATE),
DELETE(EVT_DELETE),
SOFT_DELETE(EVT_SOFT_DELETE),
DELETE_PERMANENT(EVT_DELETE_PERMANENT),
UPDATESQL(EVT_UPDATESQL),
CALLABLESQL(EVT_CALLABLESQL);
byte profileEventId;
Type(byte profileEventId) {
this.profileEventId = profileEventId;
}
}
boolean persistCascade;
@@ -43,6 +54,10 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
*/
public abstract int executeNow();
void profileBase(byte event, int offset, short beanTypeId, int beanCount) {
transaction.profileStream().addEvent(event, offset, beanTypeId, beanCount);
}
@Override
public boolean isLogSql() {
return transaction.isLogSql();
@@ -12,6 +12,7 @@ import io.ebean.event.changelog.BeanChange;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -40,7 +41,7 @@ import java.util.Set;
/**
* PersistRequest for insert update or delete of a bean.
*/
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, DocStoreUpdate, PreGetterCallback {
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, DocStoreUpdate, PreGetterCallback, SpiProfileTransactionEvent {
private final BeanManager<T> beanManager;
@@ -143,6 +144,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private long now;
private int profileOffset;
/**
* Flag set when request is added to JDBC batch registered as a "getter callback" to automatically flush batch.
*/
@@ -184,6 +187,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
initGeneratedProperties();
}
/**
* Add to profile as batched bean insert, update or delete.
*/
@Override
public void profile(int offset, int flushCount) {
profileBase(type.profileEventId, offset, beanDescriptor.getProfileId(), flushCount);
}
/**
* Return the document store event that should be used for this request.
* <p>
@@ -753,13 +764,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return control.executeOrQueue(this, true);
} else {
return executeNow();
return executeNoBatch();
}
} catch (BatchedSqlException e) {
throw transaction.translate(e.getMessage(), e.getCause());
}
}
private int executeNoBatch() {
profileOffset = transaction.profileOffset();
int result = executeNow();
transaction.profileEvent(this);
return result;
}
/**
* Set the generated key back to the bean. Only used for inserts with getGeneratedKeys.
*/
@@ -1184,4 +1202,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isStatelessUpdate() {
return statelessUpdate;
}
/**
* Add to profile as single bean insert, update or delete (not batched).
*/
@Override
public void profile() {
profileBase(type.profileEventId, profileOffset, beanDescriptor.getProfileId(), 1);
}
}
@@ -39,6 +39,11 @@ public final class PersistRequestCallableSql extends PersistRequest {
this.callableSql = (SpiCallableSql) cs;
}
@Override
public void profile(int offset, int flushCount) {
profileBase(EVT_CALLABLESQL, offset, (short)0, flushCount);
}
@Override
public int executeOrQueue() {
return executeStatement();
@@ -32,6 +32,11 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
this.ormUpdate = ormUpdate;
}
@Override
public void profile(int offset, int flushCount) {
profileBase(EVT_ORMUPDATE, offset, beanDescriptor.getProfileId(), flushCount);
}
public BeanDescriptor<?> getBeanDescriptor() {
return beanDescriptor;
}
@@ -38,6 +38,11 @@ public final class PersistRequestUpdateSql extends PersistRequest {
this.updateSql = (SpiSqlUpdate) updateSql;
}
@Override
public void profile(int offset, int flushCount) {
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
}
@Override
public int executeNow() {
return persistExecute.executeSqlUpdate(this);
@@ -114,6 +114,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final Map<String, String> namedQuery;
private final short profileBeanId;
public enum EntityType {
ORM, EMBEDDED, VIEW, SQL, DOC
}
@@ -406,7 +408,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.name = InternString.intern(deploy.getName());
this.baseTableAlias = "t0";
this.fullName = InternString.intern(deploy.getFullName());
this.profileBeanId = deploy.getProfileId();
this.beanType = deploy.getBeanType();
this.rootBeanType = PersistenceContextUtil.root(beanType);
this.prototypeEntityBean = createPrototypeEntityBean(beanType);
@@ -528,6 +530,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
}
/**
* Return the id used in profiling to identify the bean type.
*/
@Override
public short getProfileId() {
return profileBeanId;
}
/**
* Derive an array of property positions for properties that are initialised in the constructor.
* These properties need to be unloaded when populating beans for queries.
@@ -79,6 +79,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Creates BeanDescriptors.
@@ -325,6 +326,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
readEntityBeanTable();
readEntityDeploymentAssociations();
readInheritedIdGenerators();
setProfileIds();
// creates the BeanDescriptors
readEntityRelationships();
@@ -727,6 +729,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
/**
* Set profileIds based on descriptor full name order.
*/
private void setProfileIds() {
List<? extends DeployBeanDescriptor<?>> deployDescriptors = deployInfoMap.values().stream()
.map(DeployBeanInfo::getDescriptor)
.collect(Collectors.toList());
deployDescriptors.sort(Comparator.comparing(DeployBeanDescriptor::getFullName));
short id = 0;
for (DeployBeanDescriptor<?> desc : deployDescriptors) {
desc.setProfileId(++id);
}
}
/**
* Create the BeanTable from the deployment information gathered so far.
*/
@@ -1044,7 +1063,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
prop.setUnidirectional();
return;
}
if (!findMappedBy(prop)) {
makeUnidirectional(info, prop);
return;
@@ -204,6 +204,8 @@ public class DeployBeanDescriptor<T> {
private List<DeployBeanProperty> idProperties;
private short profileId;
/**
* Construct the BeanDescriptor.
*/
@@ -620,6 +622,20 @@ public class DeployBeanDescriptor<T> {
setBaseTable(new TableName(viewName), "", "");
}
/**
* Set the profileId to identity this bean type.
*/
public void setProfileId(short profileId) {
this.profileId = profileId;
}
/**
* Return the profileId to identify this bean type.
*/
public short getProfileId() {
return profileId;
}
/**
* Set the base table. Only properties mapped to the base table are by default persisted.
*/
@@ -30,4 +30,8 @@ public interface BatchPostExecute {
*/
void postExecute();
/**
* Add as event to the profiling.
*/
void profile(int offset, int batchSize);
}
@@ -1,5 +1,8 @@
package io.ebeaninternal.server.persist;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -12,7 +15,7 @@ import java.util.ArrayList;
* This can hold CallableStatements as well.
* </p>
*/
public class BatchedPstmt {
public class BatchedPstmt implements SpiProfileTransactionEvent {
/**
* The underlying statement.
@@ -31,13 +34,18 @@ public class BatchedPstmt {
private final String sql;
private final SpiTransaction transaction;
private int profileStart;
/**
* Create with a given statement.
*/
public BatchedPstmt(PreparedStatement pstmt, boolean isGenKeys, String sql) {
public BatchedPstmt(PreparedStatement pstmt, boolean isGenKeys, String sql, SpiTransaction transaction) {
this.pstmt = pstmt;
this.isGenKeys = isGenKeys;
this.sql = sql;
this.transaction = transaction;
}
/**
@@ -74,12 +82,20 @@ public class BatchedPstmt {
*/
public void executeBatch(boolean getGeneratedKeys) throws SQLException {
this.profileStart = transaction.profileOffset();
executeAndCheckRowCounts();
if (isGenKeys && getGeneratedKeys) {
getGeneratedKeys();
}
postExecute();
close();
transaction.profileEvent(this);
}
@Override
public void profile() {
// just use the first to add the event
list.get(0).profile(profileStart, list.size());
}
/**
@@ -93,8 +109,8 @@ public class BatchedPstmt {
}
private void postExecute() {
for (BatchPostExecute aList : list) {
aList.postExecute();
for (BatchPostExecute item : list) {
item.postExecute();
}
}
@@ -54,7 +54,7 @@ public class PstmtFactory {
Connection conn = t.getInternalConnection();
stmt = conn.prepareStatement(sql);
BatchedPstmt bs = new BatchedPstmt(stmt, false, sql);
BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, t);
batch.addStmt(bs, batchExe);
return stmt;
}
@@ -78,7 +78,7 @@ public class PstmtFactory {
Connection conn = t.getInternalConnection();
stmt = conn.prepareCall(sql);
BatchedPstmt bs = new BatchedPstmt(stmt, false, sql);
BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, t);
batch.addStmt(bs, batchExe);
return stmt;
}
@@ -283,7 +283,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
stmt = getPstmt(t, sql, genKeys);
BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql);
BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql, t);
batch.addStmt(bs, request);
return stmt;
}
@@ -13,6 +13,7 @@ import io.ebean.event.readaudit.ReadEvent;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Mode;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
@@ -50,7 +51,7 @@ import java.util.NoSuchElementException;
* the key object used in reading the flat resultSet back into Objects.
* </p>
*/
public class CQuery<T> implements DbReadContext, CancelableQuery {
public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTransactionEvent {
private static final Logger logger = LoggerFactory.getLogger(CQuery.class);
@@ -172,6 +173,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
private final Boolean readOnly;
private int profileOffset;
private long startNano;
private long executionTimeMicros;
@@ -318,6 +320,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
// prepare
SpiTransaction t = request.getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
if (query.isRawSql()) {
@@ -587,11 +590,19 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
profilingListener.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros);
}
queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode);
getTransaction().profileEvent(this);
} catch (Exception e) {
logger.error("Error updating execution statistics", e);
}
}
@Override
public void profile() {
getTransaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), loadedBeanCount, query.getProfileId());
}
QueryIterator<T> readIterate(int bufferSize, OrmQueryRequest<T> request) {
if (bufferSize > 0) {
@@ -253,6 +253,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private String nativeSql;
/**
* Identity the query for profiling purposes (expected to be unique for a bean type).
*/
private short profileId;
public DefaultOrmQuery(BeanDescriptor<T> desc, EbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.beanType = desc.getBeanType();
@@ -280,6 +285,26 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
}
@Override
public byte profileEventId() {
switch (mode) {
case LAZYLOAD_BEAN: return FIND_ONE_LAZY;
case LAZYLOAD_MANY: return FIND_MANY_LAZY;
default:
return type.profileEventId();
}
}
public short getProfileId() {
return profileId;
}
@Override
public Query<T> setProfileId(int profileId) {
this.profileId = (short)profileId;
return this;
}
@Override
public boolean isAutoTunable() {
return nativeSql == null && beanDescriptor.isAutoTunable();
@@ -19,7 +19,7 @@ public class AutoCommitTransactionManager extends TransactionManager {
* Create an autoCommit based Transaction.
*/
@Override
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
return new AutoCommitJdbcTransaction(prefix + id, explicit, c, this);
}
@@ -0,0 +1,247 @@
package io.ebeaninternal.server.transaction;
import io.ebean.config.ProfilingConfig;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiProfileHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
/**
* Default profile handler.
* <p>
* Uses ConcurrentLinkedQueue to minimise contention on threads calling collectTransactionProfile().
* </p>
* <p>
* Uses a sleep backoff on the single threaded consumer that reads the profiles and writes them to files.
* </p>
*/
public class DefaultProfileHandler implements SpiProfileHandler, Plugin {
private static final Logger log = LoggerFactory.getLogger(DefaultProfileHandler.class);
private static final DateTimeFormatter DTF;
static {
DTF = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue(YEAR, 4)
.appendValue(MONTH_OF_YEAR, 2)
.appendValue(DAY_OF_MONTH, 2)
.appendLiteral('-')
.appendValue(HOUR_OF_DAY, 2)
.appendValue(MINUTE_OF_HOUR, 2)
.appendValue(SECOND_OF_MINUTE, 2)
.toFormatter();
}
/**
* Low contention choice.
*/
private final Queue<TransactionProfile> queue = new ConcurrentLinkedQueue<>();
private final ExecutorService executor;
private final File dir;
private final long minMicros;
private final int[] includeIds;
private final long profilesPerFile;
private volatile boolean shutdown;
private long profileCounter;
/**
* Slow down polling of transaction profiling queue.
*/
private int sleepBackoff;
private FileOutputStream out;
public DefaultProfileHandler(ProfilingConfig config) {
this.minMicros = config.getMinimumTransactionMicros();
this.includeIds = config.getIncludeProfileIds();
this.profilesPerFile = config.getProfilesPerFile();
// dedicated single threaded executor for consuming the
// profiling and writing it to file(s)
this.executor = Executors.newSingleThreadExecutor();
this.dir = new File(config.getDirectory());
if (!dir.mkdirs()) {
log.error("failed to mkdirs " + dir.getAbsolutePath());
}
incrementFile();
}
/**
* Low contention adding the transaction profile to the queue.
* Minimise the impact to the normal transaction processing (threads).
*/
@Override
public void collectTransactionProfile(TransactionProfile transactionProfile) {
queue.add(transactionProfile);
}
/**
* Create and return a ProfileStream if we are profiling for the given transaction profileId.
*/
@Override
public ProfileStream createProfileStream(int profileId) {
if (profileId < 1) {
// not this transaction
return null;
}
if (includeIds.length == 0) {
return new ProfileStream(profileId);
}
// check if we are profiling this specific transaction profileId, just
// perform linear search as this is expected to be a small array
for (int includeId : includeIds) {
if (includeId == profileId) {
return new ProfileStream(profileId);
}
}
return null;
}
private void flushCurrentFile() {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
log.error("Failed to flush and close transaction profiling file ", e);
}
}
}
/**
* Move to the next file to write to.
*/
private void incrementFile() {
flushCurrentFile();
try {
String now = DTF.format(LocalDateTime.now());
File file = new File(dir, "txprofile-" + now + ".tprofile");
out = new FileOutputStream(file);
} catch (FileNotFoundException e) {
log.error("Not expected", e);
}
}
/**
* Main loop for polling the queue and processing profiling messages.
*/
private void collect() {
while (!shutdown) {
TransactionProfile profile = queue.poll();
if (profile == null) {
sleep();
} else if (include(profile)) {
write(profile);
}
}
}
/**
* Write the profile to the current file.
*/
private void write(TransactionProfile profile) {
try {
sleepBackoff = 0;
++profileCounter;
out.write(profile.getBytes());
if (profileCounter % profilesPerFile == 0) {
incrementFile();
log.debug("profiled {} transactions", profileCounter);
}
} catch (IOException e) {
log.warn("Error writing transaction profiling", e);
}
}
/**
* Return true if the profile should be included (or false for ignored).
*/
private boolean include(TransactionProfile profile) {
return profile.getTotalMicros() >= minMicros;
}
/**
* Sleep backing off towards 250 millis when there is no activity.
* This seems to be simple and decent for our queue consumer.
*/
private void sleep() {
try {
// backoff sleep when nothing is happening
int sleepFor = Math.min(++sleepBackoff, 250);
Thread.sleep(sleepFor);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void configure(SpiServer server) {
StringBuilder sb = new StringBuilder(200);
sb.append("Bean profile mapping - ");
for (BeanType<?> type : server.getBeanTypes()) {
sb.append("profileId:").append(type.getProfileId())
.append(" ").append(type.getName()).append(", ");
}
log.info(sb.toString());
}
@Override
public void online(boolean online) {
if (online) {
executor.submit(this::collect);
}
}
@Override
public void shutdown() {
shutdown = true;
log.trace("shutting down profiling consumer");
flushCurrentFile();
try {
executor.shutdown();
if (!executor.awaitTermination(4, TimeUnit.SECONDS)) {
log.info("Shut down timeout exceeded. Terminating profiling consumer thread.");
executor.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Interrupt on shutdown", e);
}
}
}
@@ -22,9 +22,9 @@ public class DocStoreTransactionManager extends TransactionManager {
}
@Override
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
long id = counter.incrementAndGet();
return createTransaction(explicit, null, id);
return createTransaction(profileId, explicit, null, id);
}
@Override
@@ -33,7 +33,7 @@ public class DocStoreTransactionManager extends TransactionManager {
}
@Override
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
return new DocStoreOnlyTransaction(prefix + id, explicit, this);
}
}
@@ -18,7 +18,7 @@ public class ExplicitTransactionManager extends TransactionManager {
* Create a ExplicitJdbcTransaction.
*/
@Override
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
return new ExplicitJdbcTransaction(prefix + id, explicit, c, this);
}
@@ -9,7 +9,9 @@ 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.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
@@ -34,7 +36,7 @@ import java.util.Map;
/**
* JDBC Connection based transaction.
*/
public class JdbcTransaction implements SpiTransaction {
public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
private static final Logger logger = LoggerFactory.getLogger(JdbcTransaction.class);
@@ -169,11 +171,21 @@ public class JdbcTransaction implements SpiTransaction {
protected DocStoreTransaction docStoreTxn;
private final ProfileStream profileStream;
/**
* Create without ProfileStream option (no profiling).
*/
public JdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) {
this(null, id, explicit, connection, manager);
}
/**
* Create a new JdbcTransaction.
*/
public JdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) {
public JdbcTransaction(ProfileStream profileStream, String id, boolean explicit, Connection connection, TransactionManager manager) {
try {
this.profileStream = profileStream;
this.active = true;
this.id = id;
this.logPrefix = deriveLogPrefix(id);
@@ -201,6 +213,23 @@ public class JdbcTransaction implements SpiTransaction {
}
}
@Override
public int profileOffset() {
return (profileStream == null) ? 0 : profileStream.offset();
}
@Override
public void profileEvent(SpiProfileTransactionEvent event) {
if (profileStream != null) {
event.profile();
}
}
@Override
public ProfileStream profileStream() {
return profileStream;
}
/**
* Overridden in AutoCommitJdbcTransaction as that expects to run/operate with autocommit true.
*/
@@ -884,6 +913,7 @@ public class JdbcTransaction implements SpiTransaction {
}
connection = null;
active = false;
profileEnd();
}
/**
@@ -918,14 +948,28 @@ public class JdbcTransaction implements SpiTransaction {
* Perform the actual rollback on the connection.
*/
protected void performRollback() throws SQLException {
int offset = profileOffset();
connection.rollback();
if (profileStream != null) {
profileStream.addEvent(EVT_ROLLBACK, offset);
}
}
/**
* Perform the actual commit on the connection.
*/
protected void performCommit() throws SQLException {
int offset = profileOffset();
connection.commit();
if (profileStream != null) {
profileStream.addEvent(EVT_COMMIT, offset);
}
}
private void profileEnd() {
if (profileStream != null) {
profileStream.end(manager);
}
}
/**
@@ -0,0 +1,20 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.SpiProfileHandler;
/**
* A do nothing SpiProfileHandler.
*/
public class NoopProfileHandler implements SpiProfileHandler {
@Override
public void collectTransactionProfile(TransactionProfile transactionProfile) {
// do nothing
}
@Override
public ProfileStream createProfileStream(int profileId) {
// always return null
return null;
}
}
@@ -0,0 +1,102 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.TxnProfileEventCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* A binary encoding of the transaction profiling events.
*/
public class ProfileStream implements TxnProfileEventCodes {
private static final Logger logger = LoggerFactory.getLogger(ProfileStream.class);
private final int profId;
private final long startNanos;
private final DataOutputStream out;
private final ByteArrayOutputStream profileBuffer;
public ProfileStream(int profId) {
this.profId = profId;
this.startNanos = System.nanoTime();
this.profileBuffer = new ByteArrayOutputStream(200);
this.out = new DataOutputStream(profileBuffer);
try {
out.writeLong(System.currentTimeMillis());
out.writeInt(profId);
} catch (IOException e) {
throw new RuntimeException("Unexpected error starting transaction profiling", e);
}
}
/**
* Return the time offset from the beginning of the transaction.
*/
public int offset() {
// int max of 2,147,483,648 as micros = 35 minutes
// use 10_000 to get 100th of millis to max at 357 minutes (almost 6 hours)
// not micros so we can reasonably use int rather than long
return (int)((System.nanoTime() - startNanos) / 10_000L);
}
/**
* Add the commit/rollback event.
*/
public void addEvent(byte event, int startOffset) {
try {
out.writeByte(event);
out.writeInt(startOffset);
out.writeInt(offset() - startOffset);
} catch (IOException e) {
logger.error("Error writing event to transaction profiling", e);
}
}
/**
* Add a query execution event.
*/
public void addQueryEvent(byte event, int offset, short beanTypeId, int beanCount, short queryId) {
add(event, offset, beanTypeId, beanCount, queryId);
}
/**
* Add a persist event.
*/
public void addEvent(byte event, int offset, short beanTypeId, int beanCount) {
add(event, offset, beanTypeId, beanCount, (short)0);
}
private void add(byte event, int offset, short beanTypeId, int beanCount, short queryId) {
try {
out.writeByte(event);
out.writeInt(offset);
out.writeInt(offset() - offset);
out.writeShort(beanTypeId);
out.writeInt(beanCount);
out.writeShort(queryId);
} catch (IOException e) {
logger.error("Error writing event to transaction profiling", e);
}
}
/**
* End the transaction profiling.
*/
public void end(TransactionManager manager) {
try {
long totalMicros = ((System.nanoTime() - startNanos) / 1_000L);
out.writeByte(EVT_END);
out.writeLong(totalMicros);
out.flush();
out.close();
manager.profileCollect(new TransactionProfile(profId, totalMicros, profileBuffer.toByteArray()));
} catch (IOException e) {
logger.error("Error flushing and collecting profiling", e);
}
}
}
@@ -34,7 +34,7 @@ abstract class TransactionFactory {
/**
* Return a new transaction.
*/
abstract SpiTransaction createTransaction(boolean explicit, int isolationLevel);
abstract SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel);
/**
* Set the Transaction Isolation level if required.
@@ -22,20 +22,20 @@ class TransactionFactoryBasic extends TransactionFactory {
@Override
public SpiTransaction createQueryTransaction(Object tenantId) {
return create(false);
return create(0,false);
}
@Override
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
SpiTransaction t = create(explicit);
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
SpiTransaction t = create(profileId, explicit);
return setIsolationLevel(t, explicit, isolationLevel);
}
private SpiTransaction create(boolean explicit) {
private SpiTransaction create(int profileId, boolean explicit) {
Connection c = null;
try {
c = dataSource.getConnection();
return manager.createTransaction(explicit, c, counter.incrementAndGet());
return manager.createTransaction(profileId, explicit, c, counter.incrementAndGet());
} catch (PersistenceException ex) {
JdbcClose.close(c);
@@ -25,17 +25,17 @@ class TransactionFactoryTenant extends TransactionFactory {
@Override
public SpiTransaction createQueryTransaction(Object tenantId) {
return create(false, tenantId);
return create(0,false, tenantId);
}
@Override
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
SpiTransaction t = create(explicit, null);
SpiTransaction t = create(profileId, explicit, null);
return setIsolationLevel(t, explicit, isolationLevel);
}
private SpiTransaction create(boolean explicit, Object tenantId) {
private SpiTransaction create(int profileId, boolean explicit, Object tenantId) {
Connection c = null;
try {
if (tenantId == null) {
@@ -43,7 +43,7 @@ class TransactionFactoryTenant extends TransactionFactory {
tenantId = tenantProvider.currentId();
}
c = dataSourceSupplier.getConnection(tenantId);
SpiTransaction transaction = manager.createTransaction(explicit, c, counter.incrementAndGet());
SpiTransaction transaction = manager.createTransaction(profileId, explicit, c, counter.incrementAndGet());
transaction.setTenantId(tenantId);
return transaction;
@@ -8,6 +8,7 @@ import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TransactionEventTable;
@@ -110,6 +111,8 @@ public class TransactionManager {
private final DatabasePlatform databasePlatform;
private final SpiProfileHandler profileHandler;
/**
* Create the TransactionManager
*/
@@ -131,6 +134,7 @@ public class TransactionManager {
this.dataSourceSupplier = options.dataSourceSupplier;
this.docStoreActive = options.config.getDocStoreConfig().isActive();
this.docStoreUpdateProcessor = options.docStoreUpdateProcessor;
this.profileHandler = options.profileHandler;
this.bulkEventListenerMap = new BulkEventListenerMap(options.config.getBulkTableEventListeners());
this.prefix = "";
this.externalTransPrefix = "e";
@@ -248,8 +252,8 @@ public class TransactionManager {
/**
* Create a new Transaction.
*/
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
return transactionFactory.createTransaction(explicit, isolationLevel);
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
return transactionFactory.createTransaction(profileId, explicit, isolationLevel);
}
public SpiTransaction createQueryTransaction(Object tenantId) {
@@ -259,8 +263,10 @@ public class TransactionManager {
/**
* Create a new transaction.
*/
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
return new JdbcTransaction(prefix + id, explicit, c, this);
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
ProfileStream profileStream = profileHandler.createProfileStream(profileId);
return new JdbcTransaction(profileStream,prefix + id, explicit, c, this);
}
/**
@@ -412,4 +418,10 @@ public class TransactionManager {
}
}
/**
* Process the collected transaction profiling information.
*/
public void profileCollect(TransactionProfile transactionProfile) {
profileHandler.collectTransactionProfile(transactionProfile);
}
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
@@ -19,10 +20,11 @@ public class TransactionManagerOptions {
final DocStoreUpdateProcessor docStoreUpdateProcessor;
final BeanDescriptorManager descMgr;
final DataSourceSupplier dataSourceSupplier;
final SpiProfileHandler profileHandler;
public TransactionManagerOptions(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier) {
public TransactionManagerOptions(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler) {
this.localL2Caching = localL2Caching;
this.config = config;
@@ -31,6 +33,7 @@ public class TransactionManagerOptions {
this.docStoreUpdateProcessor = docStoreUpdateProcessor;
this.descMgr = descMgr;
this.dataSourceSupplier = dataSourceSupplier;
this.profileHandler = profileHandler;
}
}
@@ -0,0 +1,52 @@
package io.ebeaninternal.server.transaction;
/**
* Profiling information for a single transaction that has completed.
*/
public class TransactionProfile {
/**
* The profileId of the transaction (On @Transactional explicitly or can be automatically set by enhancement).
*/
private final int profileId;
/**
* The total execution time of the transaction (for filtering out small/short transactions).
*/
private final long totalMicros;
/**
* The binary encoding of the transaction profiling events.
*/
private final byte[] bytes;
/**
* Create with profileId, total micros and encoded profile data.
*/
public TransactionProfile(int profileId, long totalMicros, byte[] bytes) {
this.profileId = profileId;
this.totalMicros = totalMicros;
this.bytes = bytes;
}
/**
* Return the transaction profileId.
*/
public int getProfileId() {
return profileId;
}
/**
* Return the total transaction execution time in micros.
*/
public long getTotalMicros() {
return totalMicros;
}
/**
* Return the profiling data in encoded form.
*/
public byte[] getBytes() {
return bytes;
}
}
@@ -0,0 +1 @@
api-version: 11.3
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.transaction;
import io.ebean.config.ProfilingConfig;
import org.junit.Test;
import static org.junit.Assert.*;
public class DefaultProfileHandlerTest {
@Test
public void createProfileStream() throws Exception {
DefaultProfileHandler handler = new DefaultProfileHandler(new ProfilingConfig());
assertNotNull(handler.createProfileStream(12));
assertNull(handler.createProfileStream(0));
}
@Test
public void createProfileStream_when_specificIncludeIds() throws Exception {
ProfilingConfig config = new ProfilingConfig();
config.setIncludeProfileIds(new int[]{100,101});
DefaultProfileHandler handler = new DefaultProfileHandler(config);
assertNotNull(handler.createProfileStream(100));
assertNotNull(handler.createProfileStream(101));
assertNull(handler.createProfileStream(0));
assertNull(handler.createProfileStream(12));
}
}
@@ -11,12 +11,12 @@ import java.util.ArrayList;
import java.util.List;
/**
* Helper that can collect the SQL that is logged via SLF4J.
* Helper that can collectTransactionProfile the SQL that is logged via SLF4J.
* <p>
* Used {@link #start()} and {@link #stop()} to collect the logged messages that contain the
* Used {@link #start()} and {@link #stop()} to collectTransactionProfile the logged messages that contain the
* executed SQL statements.
* <p>
* Internally this uses a Logback Appender to collect messages for org.avaje.ebean.SQL.
* Internally this uses a Logback Appender to collectTransactionProfile messages for org.avaje.ebean.SQL.
*/
public class LoggedSqlCollector {
@@ -33,7 +33,7 @@ public class TestPersistenceContextScopeUsingOrders extends BaseTestCase {
assertTrue(!orders.isEmpty());
// collect the customer instances
// collectTransactionProfile the customer instances
List<Customer> customers = new ArrayList<>();
Set<Integer> identities = new HashSet<>();