mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Add MetaObjectGraphNodeStats and MetaQueryPlanOriginCount etc
This commit is contained in:
@@ -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();
|
||||
|
||||
}
|
||||
+17
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user