Add MetaObjectGraphNodeStats and MetaQueryPlanOriginCount etc

This commit is contained in:
Rob Bygrave
2014-01-22 01:05:22 +13:00
parent 470b27efe1
commit acca803a80
24 changed files with 609 additions and 74 deletions
@@ -65,7 +65,7 @@ public final class ObjectGraphNode implements Serializable {
}
public String toString() {
return "origin:" + originQueryPoint + " " + ":" + path + ":" + path;
return "origin:" + originQueryPoint + " path[" + path+"]";
}
public int hashCode() {
@@ -58,7 +58,7 @@ public final class ObjectGraphOrigin implements Serializable {
}
public String toString() {
return key + " " + beanType + " " + callStack.getFirstStackTraceElement();
return "key["+ key + "] type[" + beanType + "] " + callStack.getFirstStackTraceElement()+" ";
}
public int hashCode() {
@@ -18,6 +18,7 @@ import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BulkTableEventListener;
import com.avaje.ebean.event.ServerConfigStartup;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.util.ClassUtil;
/**
@@ -191,6 +192,10 @@ public class ServerConfig {
private ServerCacheManager serverCacheManager;
private boolean collectQueryStatsByNode;
private boolean collectQueryOrigins;
/**
* Construct a Server Configuration for programmatically creating an
* EbeanServer.
@@ -926,6 +931,51 @@ public class ServerConfig {
public void setUpdateChangesOnly(boolean updateChangesOnly) {
this.updateChangesOnly = updateChangesOnly;
}
/**
* Return true if the ebeanServer should collection query statistics by ObjectGraphNode.
*/
public boolean isCollectQueryStatsByNode() {
return collectQueryStatsByNode;
}
/**
* Set to true to collection query execution statistics by ObjectGraphNode.
* <p>
* These statistics can be used to highlight code/query 'origin points' that result in lots of lazy loading.
* </p>
* <p>
* It is considered safe/fine to have this set to true for production.
* </p>
* <p>
* This information can be later retrieved via {@link MetaInfoManager}.
* </p>
* @see MetaInfoManager
*/
public void setCollectQueryStatsByNode(boolean collectQueryStatsByNode) {
this.collectQueryStatsByNode = collectQueryStatsByNode;
}
/**
* Return true if query plans should also collect their 'origins'. This means for a given query plan you
* can identify the code/origin points where this query resulted from including lazy loading origins.
*/
public boolean isCollectQueryOrigins() {
return collectQueryOrigins;
}
/**
* Set to true if query plans should collect their 'origin' points. This means for a given query plan you
* can identify the code/origin points where this query resulted from including lazy loading origins.
* <p>
* This information can be later retrieved via {@link MetaInfoManager}.
* </p>
* @see MetaInfoManager
*/
public void setCollectQueryOrigins(boolean collectQueryOrigins) {
this.collectQueryOrigins = collectQueryOrigins;
}
/**
* Returns the resource directory.
@@ -1183,6 +1233,9 @@ public class ServerConfig {
packages = getSearchJarsPackages(packagesProp);
}
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", true);
collectQueryOrigins = p.getBoolean("collectQueryOrigins", true);
updateChangesOnly = p.getBoolean("updateChangesOnly", true);
boolean batchMode = p.getBoolean("batch.mode", false);
@@ -7,11 +7,11 @@ public interface MetaBeanInfo {
/**
* Collect the current query plan statistics return the non-empty statistics.
*/
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
/**
* Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions).
*/
public List<MetaBeanQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset);
public List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset);
}
@@ -21,6 +21,19 @@ public interface MetaInfoManager {
* executions (since the last collection with reset).
* </p>
*/
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
/**
* Collect and return the ObjectGraphNode statistics.
* <p>
* These show query executions for based on an origin point and paths. This is
* used to look at the amount of lazy loading occurring for a given query
* origin point.
* </p>
*
* @param reset
* Set to true to reset the underlying statistics after collection.
*/
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset);
}
@@ -0,0 +1,41 @@
package com.avaje.ebean.meta;
import com.avaje.ebean.bean.ObjectGraphNode;
/**
* Statistics for query execution based on object graph origin and paths.
* <p>
* These statistics can be used to identify origin queries that result in lots
* of lazy loading.
* </p>
*
* @see MetaInfoManager#collectNodeStatistics(boolean)
*/
public interface MetaObjectGraphNodeStats {
/**
* Return the ObjectGraphNode which has the origin point and relative path.
*/
public ObjectGraphNode getNode();
/**
* Return the startTime of statistics collection.
*/
public long getStartTime();
/**
* Return the total count of queries executed for this node.
*/
public long getCount();
/**
* Return the total time of queries executed for this node.
*/
public long getTotalTime();
/**
* Return the total beans loaded by queries for this node.
*/
public long getTotalBeans();
}
@@ -0,0 +1,33 @@
package com.avaje.ebean.meta;
import com.avaje.ebean.bean.ObjectGraphNode;
/**
* Holds a query 'origin' point and count for the number of queries executed for
* this 'origin'.
* <p>
* This basically points to the bit of original code and query that results in
* this query directly or via lazy loading.
* </p>
*
* @see MetaQueryPlanStatistic
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
*/
public interface MetaQueryPlanOriginCount {
/**
* The 'origin' and path which this query belongs to.
* <p>
* For lazy loading queries this points to the original query and associated
* navigation path that resulted in this query being executed.
* </p>
*/
public ObjectGraphNode getObjectGraphNode();
/**
* The number of times a query was fired for this node since the counter was
* last reset.
*/
public long getCount();
}
@@ -1,15 +1,19 @@
package com.avaje.ebean.meta;
import java.util.List;
/**
* Query execution statistics Meta data.
*
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
*/
public interface MetaBeanQueryPlanStatistic {
public interface MetaQueryPlanStatistic {
/**
* Return the bean type this query plan is for.
*/
public Class<?> getBeanType();
/**
* Return true if this query plan was tuned by Autofetch.
*/
@@ -47,7 +51,7 @@ public interface MetaBeanQueryPlanStatistic {
* Return the max execution time for this query.
*/
public long getMaxTimeMicros();
/**
* Return the time collection started (or was last reset).
*/
@@ -74,4 +78,14 @@ public interface MetaBeanQueryPlanStatistic {
*/
public long getAvgLoadedBeans();
/**
* Return the 'origin' points and paths that resulted in the query being
* executed and the associated number of times the query was executed via that
* path.
* <p>
* This includes direct and lazy loading paths.
* </p>
*/
public List<MetaQueryPlanOriginCount> getOrigins();
}
@@ -9,6 +9,7 @@ import com.avaje.ebean.TxScope;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.BeanLoader;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.core.PstmtBatch;
@@ -29,6 +30,8 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
public void shutdownManaged();
public boolean isCollectQueryOrigins();
/**
* Return true if DeleteMissingChildren defaults to true for stateless
* updates.
@@ -187,4 +190,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
public boolean isSupportedType(java.lang.reflect.Type genericType);
/**
* Collect query statistics by ObjectGraphNode. Used for Lazy loading reporting.
*/
public void collectQueryStats(ObjectGraphNode objectGraphNode, long loadedBeanCount, long timeMicros);
}
@@ -96,6 +96,10 @@ public abstract class BeanRequest {
return ebeanServer;
}
public SpiEbeanServer getServer() {
return ebeanServer;
}
/**
* Return the Transaction associated with this request.
*/
@@ -0,0 +1,89 @@
package com.avaje.ebeaninternal.server.core;
import java.util.concurrent.atomic.AtomicLong;
import com.avaje.ebean.meta.*;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebeaninternal.server.util.LongAdder;
/**
* Helper to collect the query execution statistics for a given node.
*/
public class CObjectGraphNodeStatistics {
private final ObjectGraphNode node;
private final LongAdder count = new LongAdder();
private final LongAdder totalTime = new LongAdder();
private final LongAdder totalBeans = new LongAdder();
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
public CObjectGraphNodeStatistics(ObjectGraphNode node) {
this.node = node;
}
public void add(long beanCount, long exeMicros) {
count.increment();
totalTime.add(exeMicros);
totalBeans.add(beanCount);
}
public MetaObjectGraphNodeStats get(boolean reset) {
if (reset) {
return new Snapshot(node, startTime.getAndSet(System.currentTimeMillis()), count.sumThenReset(),
totalTime.sumThenReset(), totalBeans.sumThenReset());
} else {
return new Snapshot(node, startTime.get(), count.sum(), totalTime.sum(), totalBeans.sum());
}
}
private static class Snapshot implements MetaObjectGraphNodeStats {
private final ObjectGraphNode node;
private final long startTime;
private final long count;
private final long totalTime;
private final long totalBeans;
public Snapshot(ObjectGraphNode node, long startTime, long count, long totalTime, long totalBeans) {
this.node = node;
this.startTime = startTime;
this.count = count;
this.totalTime = totalTime;
this.totalBeans = totalBeans;
}
public String toString() {
return node + " count[" + count + "] time[" + totalTime + "] beans[" + totalBeans + "]";
}
@Override
public ObjectGraphNode getNode() {
return node;
}
@Override
public long getStartTime() {
return startTime;
}
@Override
public long getCount() {
return count;
}
@Override
public long getTotalTime() {
return totalTime;
}
@Override
public long getTotalBeans() {
return totalBeans;
}
}
}
@@ -4,8 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.meta.MetaObjectGraphNodeStats;
/**
* DefaultServer based implementation of MetaInfoManager.
@@ -30,9 +31,9 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
}
@Override
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
List<MetaBeanQueryPlanStatistic> list = new ArrayList<MetaBeanQueryPlanStatistic>();
List<MetaQueryPlanStatistic> list = new ArrayList<MetaQueryPlanStatistic>();
for (MetaBeanInfo metaBeanInfo : getMetaBeanInfoList()) {
list.addAll(metaBeanInfo.collectQueryPlanStatistics(reset));
@@ -41,4 +42,18 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
return list;
}
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset) {
List<MetaObjectGraphNodeStats> list = new ArrayList<MetaObjectGraphNodeStats>();
for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) {
MetaObjectGraphNodeStats nodeStats = nodeStatistics.get(reset);
if (nodeStats.getCount() > 0) {
// Only collection non-empty statistics
list.add(nodeStats);
}
}
return list;
}
}
@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
import javax.management.InstanceAlreadyExistsException;
@@ -49,11 +50,13 @@ import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.bean.PersistenceContext.WithOption;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
@@ -207,26 +210,41 @@ public final class DefaultServer implements SpiEbeanServer {
*/
private List<SpiEbeanPlugin> ebeanPlugins;
private final boolean collectQueryOrigins;
private final boolean collectQueryStatsByNode;
/**
* Cache used to collect statistics based on ObjectGraphNode (used to highlight lazy loading origin points).
*/
protected final ConcurrentHashMap<ObjectGraphNode, CObjectGraphNodeStatistics> objectGraphStats;
/**
* Create the DefaultServer.
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
ServerConfig serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<ObjectGraphNode, CObjectGraphNodeStatistics>();
this.metaInfoManager = new DefaultMetaInfoManager(this);
this.serverCacheManager = cache;
this.pstmtBatch = config.getPstmtBatch();
this.databasePlatform = config.getDatabasePlatform();
this.backgroundExecutor = config.getBackgroundExecutor();
this.serverName = config.getServerConfig().getName();
this.lazyLoadBatchSize = config.getServerConfig().getLazyLoadBatchSize();
this.queryBatchSize = config.getServerConfig().getQueryBatchSize();
this.serverName = serverConfig.getName();
this.lazyLoadBatchSize = serverConfig.getLazyLoadBatchSize();
this.queryBatchSize = serverConfig.getQueryBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = config.getServerConfig().getEncryptKeyManager();
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.beanDescriptorManager = config.getBeanDescriptorManager();
beanDescriptorManager.setEbeanServer(this);
this.collectQueryOrigins = serverConfig.isCollectQueryOrigins();
this.collectQueryStatsByNode = serverConfig.isCollectQueryStatsByNode();
this.maxCallStack = GlobalProperties.getInt("ebean.maxCallStack", 5);
this.defaultUpdateNullProperties = "true"
@@ -284,6 +302,11 @@ public final class DefaultServer implements SpiEbeanServer {
}
}
@Override
public boolean isCollectQueryOrigins() {
return collectQueryOrigins;
}
public boolean isDefaultDeleteMissingChildren() {
return defaultDeleteMissingChildren;
}
@@ -2093,4 +2116,20 @@ public final class DefaultServer implements SpiEbeanServer {
return jsonContext;
}
@Override
public void collectQueryStats(ObjectGraphNode node, long loadedBeanCount, long timeMicros) {
if (collectQueryStatsByNode) {
CObjectGraphNodeStatistics nodeStatistics = objectGraphStats.get(node);
if (nodeStatistics == null) {
// race condition here but I actually don't care too much if we miss a
// few early statistics - especially when the server is warming up etc
nodeStatistics = new CObjectGraphNodeStatistics(node);
objectGraphStats.put(node, nodeStatistics);
}
nodeStatistics.add(loadedBeanCount, timeMicros);
}
}
}
@@ -37,7 +37,7 @@ import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriteBeanVisitor;
import com.avaje.ebeaninternal.api.HashQueryPlan;
@@ -1154,17 +1154,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
}
@Override
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
return collectQueryPlanStatisticsInternal(reset, false);
}
@Override
public List<MetaBeanQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset) {
public List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset) {
return collectQueryPlanStatisticsInternal(reset, false);
}
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) {
List<MetaBeanQueryPlanStatistic> list = new ArrayList<MetaBeanQueryPlanStatistic>(queryPlanCache.size());
public List<MetaQueryPlanStatistic> collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) {
List<MetaQueryPlanStatistic> list = new ArrayList<MetaQueryPlanStatistic>(queryPlanCache.size());
for (CQueryPlan queryPlan : queryPlanCache.values()) {
Snapshot snapshot = queryPlan.getSnapshot(reset);
if (collectAll || snapshot.getExecutionCount() > 0) {
@@ -64,7 +64,6 @@ public class DLoadContext implements LoadContext {
this.ebeanServer = ebeanServer;
this.defaultBatchSize = ebeanServer.getLazyLoadBatchSize();
this.rootDescriptor = rootDescriptor;
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
this.readOnly = readOnly;
this.excludeBeanCache = excludeBeanCache;
this.useAutofetchManager = useAutofetchManager;
@@ -76,6 +75,9 @@ public class DLoadContext implements LoadContext {
this.origin = null;
this.relativePath = null;
}
// initialise rootBeanContext after origin and relativePath have been set
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
}
protected boolean isExcludeBeanCache() {
@@ -10,6 +10,9 @@ import java.util.concurrent.TimeUnit;
import javax.persistence.PersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.QueryListener;
import com.avaje.ebean.bean.BeanCollection;
@@ -21,6 +24,7 @@ import com.avaje.ebean.bean.NodeUsageListener;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.LoadContext;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
@@ -41,9 +45,6 @@ import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.DataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An object that represents a SqlSelect statement.
* <p>
@@ -210,7 +211,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
private final boolean autoFetchProfiling;
private final ObjectGraphNode autoFetchParentNode;
private final ObjectGraphNode objectGraphNode;
private final AutoFetchManager autoFetchManager;
@@ -238,7 +239,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
this.autoFetchManager = query.getAutoFetchManager();
this.autoFetchProfiling = autoFetchManager != null;
this.autoFetchParentNode = autoFetchProfiling ? query.getParentNode() : null;
this.objectGraphNode = query.getParentNode();
this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference<NodeUsageListener>(
autoFetchManager) : null;
@@ -645,9 +646,9 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
if (autoFetchProfiling) {
autoFetchManager
.collectQueryInfo(autoFetchParentNode, loadedBeanCount, executionTimeMicros);
.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros);
}
queryPlan.executionTime(loadedBeanCount, executionTimeMicros);
queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode);
} catch (Exception e) {
logger.error(null, e);
@@ -115,7 +115,7 @@ public class CQueryBuilder implements Constants {
String sql = s.getSql();
// cache the query plan
queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryFetchIds(request, predicates, sql, backgroundExecutor);
@@ -171,7 +171,7 @@ public class CQueryBuilder implements Constants {
}
// cache the query plan
queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryRowCount(request, predicates, sql);
@@ -216,7 +216,7 @@ public class CQueryBuilder implements Constants {
queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql());
} else {
queryPlan = new CQueryPlan(request, res, sqlTree, rawSql, predicates.getLogWhereSql(), null);
queryPlan = new CQueryPlan(request, res, sqlTree, rawSql, predicates.getLogWhereSql());
}
// cache the query plan because we can reuse it and also
@@ -3,9 +3,11 @@ package com.avaje.ebeaninternal.server.query;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.dbplatform.SqlLimitResponse;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
@@ -34,6 +36,8 @@ import com.avaje.ebeaninternal.server.type.RsetDataReader;
*/
public class CQueryPlan {
private final SpiEbeanServer server;
private final boolean autofetchTuned;
private final HashQueryPlan hash;
@@ -60,34 +64,35 @@ public class CQueryPlan {
/**
* Create a query plan based on a OrmQueryRequest.
*/
public CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree,
boolean rawSql, String logWhereSql, String luceneQueryDescription) {
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this);
this.hash = request.getQueryPlanHash();
this.autofetchTuned = request.getQuery().isAutofetchTuned();
if (sqlRes != null){
this.sql = sqlRes.getSql();
this.rowNumberIncluded = sqlRes.isIncludesRowNumberColumn();
} else {
this.sql = luceneQueryDescription;
this.rowNumberIncluded = false;
}
this.sqlTree = sqlTree;
this.rawSql = rawSql;
this.logWhereSql = logWhereSql;
this.encryptedProps = sqlTree.getEncryptedProps();
}
public CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) {
this.server = request.getServer();
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.hash = request.getQueryPlanHash();
this.autofetchTuned = request.getQuery().isAutofetchTuned();
if (sqlRes != null) {
this.sql = sqlRes.getSql();
this.rowNumberIncluded = sqlRes.isIncludesRowNumberColumn();
} else {
this.sql = null;
this.rowNumberIncluded = false;
}
this.sqlTree = sqlTree;
this.rawSql = rawSql;
this.logWhereSql = logWhereSql;
this.encryptedProps = sqlTree.getEncryptedProps();
}
/**
* Create a query plan for a raw sql query.
*/
public CQueryPlan(Class<?> beanType, String sql, SqlTree sqlTree,
public CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTree sqlTree,
boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
this.beanType = beanType;
this.stats = new CQueryPlanStats(this);
this.server = request.getServer();
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.hash = buildHash(sql, rawSql, rowNumberIncluded, logWhereSql);
this.autofetchTuned = false;
this.sql = sql;
@@ -97,8 +102,9 @@ public class CQueryPlan {
this.logWhereSql = logWhereSql;
this.encryptedProps = sqlTree.getEncryptedProps();
}
private HashQueryPlan buildHash(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
private HashQueryPlan buildHash(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
builder.add(sql).add(rawSql).add(rowNumberIncluded).add(logWhereSql);
builder.addRawSql(sql);
@@ -165,9 +171,13 @@ public class CQueryPlan {
/**
* Register an execution time against this query plan;
*/
public void executionTime(long loadedBeanCount, long timeMicros) {
public void executionTime(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
stats.add(loadedBeanCount, timeMicros);
stats.add(loadedBeanCount, timeMicros, objectGraphNode);
if (objectGraphNode != null) {
// collect stats based on objectGraphNode for lazy loading reporting
server.collectQueryStats(objectGraphNode, loadedBeanCount, timeMicros);
}
}
public Snapshot getSnapshot(boolean reset) {
@@ -15,7 +15,7 @@ public class CQueryPlanRawSql extends CQueryPlan {
public CQueryPlanRawSql(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) {
super(request, sqlRes, sqlTree, true, logWhereSql, null);
super(request, sqlRes, sqlTree, true, logWhereSql);
this.rsetIndexPositions = createIndexPositions(request, sqlTree);
}
@@ -1,9 +1,17 @@
package com.avaje.ebeaninternal.server.query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebean.meta.MetaQueryPlanOriginCount;
import com.avaje.ebeaninternal.server.util.LongAdder;
/**
@@ -25,12 +33,14 @@ public final class CQueryPlanStats {
private long lastQueryTime;
public CQueryPlanStats(CQueryPlan queryPlan) {
private final ConcurrentHashMap<ObjectGraphNode, LongAdder> origins;
public CQueryPlanStats(CQueryPlan queryPlan, boolean collectQueryOrigins) {
this.queryPlan = queryPlan;
this.origins = !collectQueryOrigins ? null : new ConcurrentHashMap<ObjectGraphNode, LongAdder>();
}
public void add(long loadedBeanCount, long timeMicros) {
public void add(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
count.increment();
totalBeans.add(loadedBeanCount);
totalTime.add(timeMicros);
@@ -38,15 +48,37 @@ public final class CQueryPlanStats {
// effectively a high water mark
maxTime.set(timeMicros);
}
// not safe but should be atomic
lastQueryTime = System.currentTimeMillis();
if (origins != null && objectGraphNode != null) {
// Maintain the origin points this query fires from
// with a simple counter
LongAdder counter = origins.get(objectGraphNode);
if (counter == null) {
// race condition - we can miss counters here but going
// to live with that. Don't want to lock/synchronize etc
counter = new LongAdder();
origins.put(objectGraphNode, counter);
}
counter.increment();
}
}
public void reset() {
// Racey but near enough for our purposes as we don't want locks
count.reset();
totalBeans.reset();
totalTime.reset();
maxTime.set(0);
startTime.set(System.currentTimeMillis());
if (origins != null) {
for (LongAdder counter : origins.values()) {
counter.reset();
}
}
}
public long getLastQueryTime() {
@@ -54,17 +86,68 @@ public final class CQueryPlanStats {
}
public Snapshot getSnapshot(boolean reset) {
// not guaranteed to be consistent - time gaps between getting each value
List<MetaQueryPlanOriginCount> origins = getOrigins(reset);
// not guaranteed to be consistent due to time gaps between getting each value out of LongAdders but can live with that
// relative to the cost of making sure count and totalTime etc are all guaranteed to be consistent
if (reset) {
return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getAndSet(0), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime);
return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getAndSet(0), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime, origins);
}
return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime);
return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime, origins);
}
/**
* Return the list/snapshot of the origins and their counter value.
*/
private List<MetaQueryPlanOriginCount> getOrigins(boolean reset) {
if (origins == null) {
return Collections.emptyList();
}
List<MetaQueryPlanOriginCount> list = new ArrayList<MetaQueryPlanOriginCount>();
for (Entry<ObjectGraphNode, LongAdder> entry : origins.entrySet()) {
if (reset) {
list.add(new OriginSnapshot(entry.getKey(), entry.getValue().sumThenReset()));
} else {
list.add(new OriginSnapshot(entry.getKey(), entry.getValue().sum()));
}
}
return list;
}
/**
* Snapshot of the origin ObjectGraphNode and counter value.
*/
private static class OriginSnapshot implements MetaQueryPlanOriginCount {
private final ObjectGraphNode objectGraphNode;
private final long count;
public OriginSnapshot(ObjectGraphNode objectGraphNode, long count) {
this.objectGraphNode = objectGraphNode;
this.count = count;
}
public String toString() {
return "node["+objectGraphNode+"] count["+count+"]";
}
@Override
public ObjectGraphNode getObjectGraphNode() {
return objectGraphNode;
}
@Override
public long getCount() {
return count;
}
}
/**
* A snapshot of the current statistics for a query plan.
*/
public static class Snapshot implements MetaBeanQueryPlanStatistic {
public static class Snapshot implements MetaQueryPlanStatistic {
private final CQueryPlan queryPlan;
private final long count;
@@ -73,9 +156,11 @@ public final class CQueryPlanStats {
private final long maxTime;
private final long startTime;
private final long lastQueryTime;
public Snapshot(CQueryPlan queryPlan, long count, long totalTime, long totalBeans, long maxTime, long startTime, long lastQueryTime) {
super();
private final List<MetaQueryPlanOriginCount> origins;
public Snapshot(CQueryPlan queryPlan, long count, long totalTime, long totalBeans, long maxTime, long startTime, long lastQueryTime,
List<MetaQueryPlanOriginCount> origins) {
this.queryPlan = queryPlan;
this.count = count;
this.totalTime = totalTime;
@@ -83,11 +168,13 @@ public final class CQueryPlanStats {
this.maxTime = maxTime;
this.startTime = startTime;
this.lastQueryTime = lastQueryTime;
this.origins = origins;
}
public String toString() {
return queryPlan+" count:"+count+" time:"+totalTime+" maxTime:"+maxTime+" beans:"+totalBeans+" start:"+startTime+" lastQuery:"+lastQueryTime;
}
public String toString() {
return queryPlan + " count:" + count + " time:" + totalTime + " maxTime:" + maxTime + " beans:" + totalBeans
+ " start:" + startTime + " lastQuery:" + lastQueryTime + " origins:" + origins;
}
@Override
public Class<?> getBeanType() {
@@ -149,6 +236,12 @@ public final class CQueryPlanStats {
public long getAvgLoadedBeans() {
return count < 1 ? 0 : totalBeans / count;
}
@Override
public List<MetaQueryPlanOriginCount> getOrigins() {
return origins;
}
}
}
@@ -83,7 +83,7 @@ public class RawSqlSelectClauseBuilder {
SqlTree sqlTree = sqlSelect.getSqlTree();
CQueryPlan queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, true, includeRowNumColumn, "");
CQueryPlan queryPlan = new CQueryPlan(request, sql, sqlTree, true, includeRowNumColumn, "");
CQuery<T> compiledQuery = new CQuery<T>(request, predicates, queryPlan);
return compiledQuery;