diff --git a/src/main/java/com/avaje/ebean/AdminAutofetch.java b/src/main/java/com/avaje/ebean/AdminAutofetch.java index 15e19f9c1..49ab9287e 100644 --- a/src/main/java/com/avaje/ebean/AdminAutofetch.java +++ b/src/main/java/com/avaje/ebean/AdminAutofetch.java @@ -5,80 +5,11 @@ package com.avaje.ebean; */ public interface AdminAutofetch { - /** - * Return true if profiling is enabled. - */ - boolean isProfiling(); - - /** - * Set to true to enable profiling. - */ - void setProfiling(boolean enable); - - /** - * Return true if autoFetch automatic query tuning is enabled. - */ - boolean isQueryTuning(); - - /** - * Set to true to enable autoFetch automatic query tuning. - */ - void setQueryTuning(boolean enable); - - /** - * Returns the rate which profiling is collected. This is an int between 0 and - * 100. - */ - double getProfilingRate(); - - /** - * Set the rate at which profiling is collected after the base. - * - * @param rate - * a int between 0 and 100. - */ - void setProfilingRate(double rate); - - /** - * Return the number of queries profiled after which profiling is collected at - * a percentage rate. - */ - int getProfilingBase(); - - /** - * Set a base number of queries to profile per query point. - *

- * After this amount of profiling has been obtained profiling is collected at - * the Profiling Percentage rate. - *

- */ - void setProfilingBase(int profilingBase); - - /** - * Return the minimum number of queries profiled before autoFetch will start - * automatically tuning the queries. - *

- * This could be one which means start autoFetch tuning after the first - * profiling information is collected. - *

- */ - int getProfilingMin(); - - /** - * Set the minimum number of queries profiled per query point before autoFetch - * will automatically tune the queries. - *

- * Increasing this number will mean more profiling is collected before - * autoFetch starts tuning the query. - *

- */ - void setProfilingMin(int autoFetchMinThreshold); - /** * Fire a garbage collection (hint to the JVM). Assuming garbage collection * fires this will gather the usage profiling information. */ - String collectUsageViaGC(); + void collectUsageViaGC(); /** * This will take the current profiling information and update the "tuned @@ -86,52 +17,7 @@ public interface AdminAutofetch { *

* This is done periodically and can also be manually invoked. *

- * - * @return a summary of the updates that occurred */ - String updateTunedQueryInfo(); + void updateTunedQueryInfo(); - /** - * Clear all the tuned query info. - *

- * Should only need do this for testing and playing around. - *

- * - * @return the amount of tuned query information cleared. - */ - int clearTunedQueryInfo(); - - /** - * Clear all the profiling information. - *

- * This means the profiling information will need to be re-gathered. - *

- *

- * Should only need do this for testing and playing around. - *

- * - * @return the amount of profiled information cleared. - */ - int clearProfilingInfo(); - - /** - * Clear the query execution statistics. - */ - void clearQueryStatistics(); - - /** - * Return the number of queries tuned by AutoFetch. - */ - int getTotalTunedQueryCount(); - - /** - * Return the size of the TuneQuery map. - */ - int getTotalTunedQuerySize(); - - /** - * Return the size of the profile map. - */ - int getTotalProfileSize(); - -} \ No newline at end of file +} diff --git a/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java b/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java index 42dc52c8c..00c1f7d0f 100644 --- a/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java +++ b/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java @@ -2,6 +2,8 @@ package com.avaje.ebean.bean; import java.lang.ref.WeakReference; import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; /** * Collects profile information for a bean (or reference/proxy bean) at a given @@ -29,7 +31,7 @@ public final class NodeUsageCollector { /** * The properties used at this profile point. */ - private final HashSet used = new HashSet(); + private final Set used = new LinkedHashSet(); /** * set to true if the bean is modified (setter called) @@ -105,7 +107,7 @@ public final class NodeUsageCollector { /** * Return the set of used properties. */ - public HashSet getUsed() { + public Set getUsed() { return used; } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index 290a6e4fa..31dbc82de 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -11,7 +11,6 @@ import com.avaje.ebean.bean.CallStack; import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; import com.avaje.ebean.dbmigration.DdlGenerator; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -70,11 +69,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL */ DdlGenerator getDdlGenerator(); - /** - * Return the AutoFetchListener. - */ - AutoFetchManager getAutoFetchManager(); - /** * Clear the query execution statistics. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 1f496d319..ebb5a49ae 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -10,7 +10,7 @@ import com.avaje.ebean.bean.CallStack; import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.TableJoin; @@ -370,16 +370,16 @@ public interface SpiQuery extends Query { void logSecondaryQuery(SpiQuery query); /** - * If return null then no autoFetch profiling for this query. If a - * AutoFetchManager is returned this implies that profiling is turned on for - * this query (and all the objects this query creates). + * If return null then no profiling for this query. If a ProfilingListener is + * returned this implies that profiling is turned on for this query (and all + * the objects this query creates). */ - AutoFetchManager getAutoFetchManager(); + ProfilingListener getProfilingListener(); /** - * This has the effect of turning on autoFetch profiling for this query. + * This has the effect of turning on profiling for this query. */ - void setAutoFetchManager(AutoFetchManager manager); + void setProfilingListener(ProfilingListener manager); /** * Return the origin point for the query. diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java deleted file mode 100644 index 7073bf30d..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import com.avaje.ebean.Query; -import com.avaje.ebean.bean.NodeUsageListener; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.config.AutofetchMode; -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; - -import java.util.Iterator; - -/** - * Collects and manages the the profile information. - *

- * The profile information is periodically converted into "tuned query details" - - * which is used to automatically tune the queries that use autoFetch. - *

- *

- * The "tuned query details" effectively are part of the query that has the - * select() and join() information (but not the where clause, order by, limits - * etc). These are applied to the query when tuneQuery() is called. - *

- */ -public interface AutoFetchManager extends NodeUsageListener { - - /** - * Set the owning ebean server. - */ - void setOwner(SpiEbeanServer server, ServerConfig serverConfig); - - /** - * Clear the query execution statistics. - */ - void clearQueryStatistics(); - - /** - * Clear all the tuned query info. - *

- * Should only need do this for testing and playing around. - *

- */ - int clearTunedQueryInfo(); - - /** - * Clear all the profiling information. - *

- * This means the profiling information will need to be re-gathered. - *

- *

- * Should only need do this for testing and playing around. - *

- */ - int clearProfilingInfo(); - - /** - * On shutdown fire garbage collection and collect statistics. Note that - * usually we add a little delay (100 milliseconds) to give the garbage - * collector plenty of time to do its thing and collect the profile - * information. - */ - void shutdown(); - - /** - * Return the current tuned fetch information for a given queryPoint key. - */ - TunedQueryInfo getTunedQueryInfo(String queryPointKey); - - /** - * Return the current Statistics for a given queryPoint key. - */ - Statistics getStatistics(String queryPointKey); - - /** - * Iterate the tuned fetch info. - *

- * This should be a read only iteration. - *

- */ - Iterator iterateTunedQueryInfo(); - - /** - * Iterate the node usage statistics. - *

- * This should be a read only iteration. - *

- */ - Iterator iterateStatistics(); - - /** - * Return true if profiling is enabled. - */ - boolean isProfiling(); - - /** - * Set to true to enable profiling. - *

- * We rely on garbage collection to collect the profiling information. This - * means there is a unknown delay between when a query is executed and when - * we actually collect the usage profile information. - *

- *

- * Due to this garbage collection delay, when turning off profiling while - * the application is running you should consider calling - * collectUsageViaGC() BEFORE setProfiling(false). This hints to - * the JVM to perform garbage collection, and hopefully collects the - * profiling information. - *

- */ - void setProfiling(boolean enable); - - /** - * Return true if automatic query tuning is enabled. - */ - boolean isQueryTuning(); - - /** - * Set to true to enable automatic query tuning. - */ - void setQueryTuning(boolean enable); - - /** - * This controls whether autoFetch is used when it has not been explicitly - * set on a query via {@link Query#setAutofetch(boolean)}. - */ - AutofetchMode getMode(); - - /** - * Set the auto fetch mode used when a query has not had - * {@link Query#setAutofetch(boolean)}. - */ - void setMode(AutofetchMode Mode); - - /** - * Return the profiling rate (int between 0 and 100). - */ - double getProfilingRate(); - - /** - * Set the profiling rate (int between 0 and 100). - */ - void setProfilingRate(double rate); - - /** - * Return the max number of queries profiled (per query point). - *

- * The number of queries profiled is collected per query point. Once a query - * point has profiled this number of queries it does not profile any more. - *

- */ - int getProfilingBase(); - - /** - * Set a max number of queries to profile per query point. - *

- * This number should provide a level of confidence that no more profiling - * is required for this query point. - *

- */ - void setProfilingBase(int profilingMax); - - /** - * Return the minimum number of queries profiled before autoFetch will start - * automatically tuning the queries. - *

- * This could be one which means start autoFetch tuning after the first - * profiling information is collected. - *

- */ - int getProfilingMin(); - - /** - * Set the minimum number of queries profiled per query point before - * autoFetch will automatically tune the queries. - *

- * Increasing this number will mean more profiling is collected before - * autoFetch starts tuning the query. - *

- */ - void setProfilingMin(int autoFetchMinThreshold); - - /** - * Fire a garbage collection (hint to the JVM). Assuming garbage collection - * fires this will gather the usage profiling information. - */ - String collectUsageViaGC(long waitMillis); - - /** - * This will take the current profiling information and update the "tuned - * query detail". - *

- * This is done periodically and can also be manually invoked. - *

- *

- * This returns a string summary of the updates that occurred. - *

- */ - String updateTunedQueryInfo(); - - /** - * Called when a query thinks it should be automatically tuned by autoFetch. - *

- * This internally checks that autoFetch is enabled, there is a "tuned query - * detail" to tune the query with and that the autoFetchMinThreshold has - * been reached. - *

- *

- * This will also determine if the query should be profiled. - *

- */ - boolean tuneQuery(SpiQuery query); - - /** - * Collect query profiling information. - *

- * This is for the original query as well as any subsequent lazy loading - * queries that are required as the object graph is traversed. - *

- * - * @param node the node path in the object graph. - * @param beans the number of beans loaded by the query. - * @param micros the query executing time in microseconds - */ - void collectQueryInfo(ObjectGraphNode node, long beans, long micros); - - - /** - * Return the number of queries tuned by AutoFetch. - */ - int getTotalTunedQueryCount(); - - /** - * Return the size of the TuneQuery map. - */ - int getTotalTunedQuerySize(); - - /** - * Return the size of the profile map. - */ - int getTotalProfileSize(); -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneService.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneService.java new file mode 100644 index 000000000..291cfe9f3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneService.java @@ -0,0 +1,63 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import com.avaje.ebean.AdminAutofetch; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Collects and manages the the profile information. + *

+ * The profile information is periodically converted into "tuned query details" - + * which is used to automatically tune the queries that use autoFetch. + *

+ *

+ * The "tuned query details" effectively are part of the query that has the + * select() and join() information (but not the where clause, order by, limits + * etc). These are applied to the query when tuneQuery() is called. + *

+ */ +public interface AutoTuneService extends AdminAutofetch { + + /** + * Load the query tuning information. + */ + void startup(); + + /** + * Called when a query thinks it should be automatically tuned by autoFetch. + *

+ * This internally checks that autoFetch is enabled, there is a "tuned query + * detail" to tune the query with and that the autoFetchMinThreshold has + * been reached. + *

+ *

+ * This will also determine if the query should be profiled. + *

+ */ + boolean tuneQuery(SpiQuery query); + + /** + * Fire a garbage collection (hint to the JVM). Assuming garbage collection + * fires this will gather the usage profiling information. + */ + void collectUsageViaGC(); + + /** + * This will take the current profiling information and update the "tuned + * query detail". + *

+ * This is done periodically and can also be manually invoked. + *

+ */ + void updateTunedQueryInfo(); + + /** + * On shutdown fire garbage collection and collect statistics. Note that + * usually we add a little delay (100 milliseconds) to give the garbage + * collector plenty of time to do its thing and collect the profile + * information. + */ + void shutdown(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java deleted file mode 100644 index 35dd210ba..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java +++ /dev/null @@ -1,587 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import com.avaje.ebean.bean.CallStack; -import com.avaje.ebean.bean.NodeUsageCollector; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.bean.ObjectGraphOrigin; -import com.avaje.ebean.config.AutofetchConfig; -import com.avaje.ebean.config.AutofetchMode; -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebeaninternal.api.ClassUtil; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.PersistenceException; -import java.io.File; -import java.io.FileOutputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * The manager of all the usage/query statistics as well as the tuned fetch - * information. - */ -public class DefaultAutoFetchManager implements AutoFetchManager, Serializable { - - private static final Logger logger = LoggerFactory.getLogger(DefaultAutoFetchManager.class); - - private static final long serialVersionUID = -6826119882781771722L; - - @SuppressWarnings("RedundantStringConstructorCall") - private final String statisticsMonitor = new String(); - - private final String fileName; - - /** - * Map of the usage and query statistics gathered. - */ - private final Map statisticsMap = new ConcurrentHashMap(); - - /** - * Map of the tuned query details per profile query point. - */ - private final Map tunedQueryInfoMap = new ConcurrentHashMap(); - - private transient long defaultGarbageCollectionWait = 100; - - /** - * Left without synchronized for now. - */ - private transient int tunedQueryCount; - - /** - * Converted from a 0-100 int to a double. Effectively a percentage rate at - * which to collect profiling information. - */ - private transient double profilingRate = 0.1d; - - private transient int profilingBase = 10; - - private transient int profilingMin = 1; - - private transient boolean profiling; - - private transient boolean queryTuning; - - private transient boolean queryTuningAddVersion; - - private transient boolean garbageCollectionOnShutdown; - - private transient AutofetchMode mode; - - /** - * Server that owns this Profile Listener. - */ - private transient SpiEbeanServer server; - - /** - * The logger. - */ - private transient DefaultAutoFetchManagerLogging logging; - - public DefaultAutoFetchManager(String fileName) { - this.fileName = fileName; - } - - /** - * Set up this profile listener before it is active. - */ - public void setOwner(SpiEbeanServer server, ServerConfig serverConfig) { - this.server = server; - this.logging = new DefaultAutoFetchManagerLogging(serverConfig, this); - - AutofetchConfig autofetchConfig = serverConfig.getAutofetchConfig(); - - garbageCollectionOnShutdown = autofetchConfig.isGarbageCollectionOnShutdown(); - queryTuning = autofetchConfig.isQueryTuning(); - queryTuningAddVersion = autofetchConfig.isQueryTuningAddVersion(); - profiling = autofetchConfig.isProfiling(); - profilingMin = autofetchConfig.getProfilingMin(); - profilingBase = autofetchConfig.getProfilingBase(); - - setProfilingRate(autofetchConfig.getProfilingRate()); - - defaultGarbageCollectionWait = (long) autofetchConfig.getGarbageCollectionWait(); - - // determine the mode to use when Query.setAutoFetch() was - // not explicitly set - mode = autofetchConfig.getMode(); - - if (profiling || queryTuning) { - // log the guts of the autoFetch setup - String msg = "AutoFetch queryTuning[" + queryTuning + "] profiling[" + profiling - + "] mode[" + mode + "] profiling rate[" + profilingRate - + "] min[" + profilingMin + "] base[" + profilingBase + "]"; - logging.logInfo(msg, null); - - // Register a periodic update of the profiling informations - this.logging.init(server); - } - } - - - public void clearQueryStatistics() { - server.clearQueryStatistics(); - } - - /** - * Return the number of queries tuned by AutoFetch. - */ - public int getTotalTunedQueryCount() { - return tunedQueryCount; - } - - /** - * Return the size of the TuneQuery map. - */ - public int getTotalTunedQuerySize() { - return tunedQueryInfoMap.size(); - } - - /** - * Return the size of the profile map. - */ - public int getTotalProfileSize() { - return statisticsMap.size(); - } - - public int clearTunedQueryInfo() { - - // reset the rough count as well - tunedQueryCount = 0; - - // clear the map... - int size = tunedQueryInfoMap.size(); - tunedQueryInfoMap.clear(); - return size; - } - - public int clearProfilingInfo() { - int size = statisticsMap.size(); - statisticsMap.clear(); - return size; - } - - - public void serialize() { - - File autoFetchFile = new File(fileName); - - try { - FileOutputStream fout = new FileOutputStream(autoFetchFile); - - ObjectOutputStream oout = new ObjectOutputStream(fout); - oout.writeObject(this); - oout.flush(); - oout.close(); - - } catch (Exception e) { - String msg = "Error serializing autofetch file"; - logging.logError(msg, e); - } - } - - /** - * Return the current Tuned query info for a given origin key. - */ - public TunedQueryInfo getTunedQueryInfo(String originKey) { - return tunedQueryInfoMap.get(originKey); - } - - /** - * Return the current Statistics for a given originKey key. - */ - public Statistics getStatistics(String originKey) { - return statisticsMap.get(originKey); - } - - public Iterator iterateTunedQueryInfo() { - return tunedQueryInfoMap.values().iterator(); - } - - public Iterator iterateStatistics() { - return statisticsMap.values().iterator(); - } - - public boolean isProfiling() { - return profiling; - } - - /** - * When the application is running, BEFORE turning off profiling you - * probably should call collectUsageViaGC() as there is a delay (waiting for - * garbage collection) collecting usage profiling information. - */ - public void setProfiling(boolean profiling) { - this.profiling = profiling; - } - - public boolean isQueryTuning() { - return queryTuning; - } - - public void setQueryTuning(boolean queryTuning) { - this.queryTuning = queryTuning; - } - - public double getProfilingRate() { - return profilingRate; - } - - public AutofetchMode getMode() { - return mode; - } - - public void setMode(AutofetchMode mode) { - this.mode = mode; - } - - public void setProfilingRate(double rate) { - if (rate < 0) { - rate = 0d; - } else if (rate > 1) { - rate = 1d; - } - profilingRate = rate; - } - - public int getProfilingBase() { - return profilingBase; - } - - public void setProfilingBase(int profilingBase) { - this.profilingBase = profilingBase; - } - - public int getProfilingMin() { - return profilingMin; - } - - public void setProfilingMin(int profilingMin) { - this.profilingMin = profilingMin; - } - - /** - * Shutdown the listener. - *

- * We should try to collect the usage statistics by calling a System.gc(). - * This is necessary for use with short lived applications where garbage - * collection may not otherwise occur at all. - *

- */ - public void shutdown() { - if (garbageCollectionOnShutdown) { - collectUsageViaGC(-1); - serialize(); - } - } - - /** - * Ask for a System.gc() so that we gather node usage information. - *

- * Really only want to do this sparingly but useful just prior to shutdown - * for short run application where garbage collection may otherwise not - * occur at all. - *

- *

- * waitMillis will do a thread sleep to give the garbage collection a little - * time to do its thing assuming we are shutting down the VM. - *

- *

- * If waitMillis is -1 then the defaultGarbageCollectionWait is used which - * defaults to 100 milliseconds. - *

- */ - public String collectUsageViaGC(long waitMillis) { - System.gc(); - try { - if (waitMillis < 0) { - waitMillis = defaultGarbageCollectionWait; - } - Thread.sleep(waitMillis); - } catch (InterruptedException e) { - String msg = "Error while sleeping after System.gc() request."; - logging.logError(msg, e); - return msg; - } - return updateTunedQueryInfo(); - } - - /** - * Update the tuned fetch plans from the current usage information. - */ - public String updateTunedQueryInfo() { - - if (!profiling) { - // we are not collecting any profiling information at - // the moment so don't try updating the tuned query plans. - return "Not profiling"; - } - - synchronized (statisticsMonitor) { - - Counters counters = new Counters(); - - for (Statistics queryPointStatistics : statisticsMap.values()) { - if (!queryPointStatistics.hasUsage()) { - // no usage statistics collected yet... - counters.incrementNoUsage(); - } else { - updateTunedQueryFromUsage(counters, queryPointStatistics); - } - } - - String summaryInfo = counters.toString(); - - if (counters.isInteresting()) { - // only log it if its interesting - logging.logSummary(summaryInfo); - } - - return summaryInfo; - } - } - - private static class Counters { - - int newPlan; - int modified; - int unchanged; - int noUsage; - - void incrementNoUsage() { - noUsage++; - } - - void incrementNew() { - newPlan++; - } - - void incrementModified() { - modified++; - } - - void incrementUnchanged() { - unchanged++; - } - - boolean isInteresting() { - return newPlan > 0 || modified > 0; - } - - public String toString() { - return "new[" + newPlan + "] modified[" + modified + "] unchanged[" + unchanged + "] nousage[" + noUsage + "]"; - } - } - - private void updateTunedQueryFromUsage(Counters counters, Statistics statistics) { - - ObjectGraphOrigin queryPoint = statistics.getOrigin(); - String beanType = queryPoint.getBeanType(); - - try { - Class beanClass = ClassUtil.forName(beanType, this.getClass()); - BeanDescriptor beanDescriptor = server.getBeanDescriptor(beanClass); - if (beanDescriptor != null) { - - // Determine the fetch plan from the latest statistics. - // Use this to compare with current "tuned fetch plan". - OrmQueryDetail newFetchDetail = statistics.buildTunedFetch(beanDescriptor); - - // get the current tuned fetch info... - TunedQueryInfo currentFetch = tunedQueryInfoMap.get(queryPoint.getKey()); - - if (currentFetch == null) { - // its a new fetch plan, add it. - counters.incrementNew(); - - currentFetch = statistics.createTunedFetch(newFetchDetail); - logging.logNew(currentFetch); - tunedQueryInfoMap.put(queryPoint.getKey(), currentFetch); - - } else if (!currentFetch.isSame(newFetchDetail)) { - // the fetch plan has changed, update it. - counters.incrementModified(); - - logging.logChanged(currentFetch, newFetchDetail); - currentFetch.setTunedDetail(newFetchDetail); - - } else { - // the fetch plan has not changed... - counters.incrementUnchanged(); - } - - currentFetch.setProfileCount(statistics.getCounter()); - } - - } catch (ClassNotFoundException e) { - // expected after renaming/moving an entity bean - String msg = e.toString() + " updating autoFetch tuned query for " + beanType - + ". It isLikely this bean has been renamed or moved"; - logging.logInfo(msg, null); - statisticsMap.remove(statistics.getOrigin().getKey()); - } - } - - /** - * Return true if we should try to use autoFetch for this query. - */ - private boolean useAutoFetch(SpiQuery query) { - - if (query.isLoadBeanCache()) { - // when loading the cache don't tune the query - // as we want full objects loaded into the cache - return false; - } - - Boolean autoFetch = query.isAutofetch(); - if (autoFetch != null) { - // explicitly set... - return autoFetch; - - } else { - // determine using implicit mode... - switch (mode) { - case DEFAULT_ON: - return true; - - case DEFAULT_OFF: - return false; - - case DEFAULT_ONIFEMPTY: - return query.isDetailEmpty(); - - default: - throw new PersistenceException("Invalid autoFetchMode " + mode); - } - } - } - - /** - * Auto tune the query and enable profiling. - */ - public boolean tuneQuery(SpiQuery query) { - - if (!queryTuning && !profiling) { - return false; - } - - if (!useAutoFetch(query)) { - // not using autoFetch for this query - return false; - } - - ObjectGraphNode parentAutoFetchNode = query.getParentNode(); - if (parentAutoFetchNode != null) { - // This is a +lazy/+query query with profiling on. - // We continue to collect the profiling information. - query.setAutoFetchManager(this); - return true; - } - - // create a query point to identify the query - CallStack stack = server.createCallStack(); - ObjectGraphNode origin = query.setOrigin(stack); - - // get current "tuned fetch" for this query point - TunedQueryInfo tunedFetch = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey()); - - // get the number of times we have collected profiling information - int profileCount = tunedFetch == null ? 0 : tunedFetch.getProfileCount(); - - if (profiling) { - // we want more profiling information? - if (tunedFetch == null) { - query.setAutoFetchManager(this); - - } else if (profileCount < profilingBase) { - query.setAutoFetchManager(this); - - } else if (tunedFetch.isPercentageProfile(profilingRate)) { - query.setAutoFetchManager(this); - } - } - - if (queryTuning) { - if (tunedFetch != null && profileCount >= profilingMin) { - // deemed to have enough profiling - // information for automatic tuning - if (tunedFetch.autoFetchTune(query)) { - // tunedQueryCount++ not thread-safe, could use AtomicInteger. - // But I'm happy if this statistic is a little wrong - // and this is a VERY HOT method - tunedQueryCount++; - } - return true; - } - } - - return false; - } - - /** - * Gather query execution statistics. This could either be the originating - * query in which case the parentNode will be null, or a lazy loading query - * resulting from traversal of the object graph. - */ - public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) { - - if (node != null) { - ObjectGraphOrigin origin = node.getOriginQueryPoint(); - if (origin != null) { - Statistics stats = getQueryPointStats(origin); - stats.collectQueryInfo(node, beans, micros); - } - } - } - - /** - * Collect usage statistics from a node in the object graph. - *

- * This is sent to use from a EntityBeanIntercept when the finalise method - * is called on the bean. - *

- */ - public void collectNodeUsage(NodeUsageCollector usageCollector) { - - ObjectGraphOrigin origin = usageCollector.getNode().getOriginQueryPoint(); - - Statistics stats = getQueryPointStats(origin); - - if (logger.isTraceEnabled()) { - logger.trace("... NodeUsageCollector " + usageCollector); - } - - stats.collectUsageInfo(usageCollector); - - if (logger.isTraceEnabled()) { - logger.trace("stats\n" + stats); - } - } - - private Statistics getQueryPointStats(ObjectGraphOrigin originQueryPoint) { - synchronized (statisticsMonitor) { - Statistics stats = statisticsMap.get(originQueryPoint.getKey()); - if (stats == null) { - stats = new Statistics(originQueryPoint, queryTuningAddVersion); - statisticsMap.put(originQueryPoint.getKey(), stats); - } - return stats; - } - } - - public String toString() { - synchronized (statisticsMonitor) { - return statisticsMap.values().toString(); - } - } - - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/ProfilingListener.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/ProfilingListener.java new file mode 100644 index 000000000..9885f7435 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/ProfilingListener.java @@ -0,0 +1,13 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import com.avaje.ebean.bean.NodeUsageListener; +import com.avaje.ebean.bean.ObjectGraphNode; + +/** + * Profiling listener gets call backs for node usage and the associated query executions. + */ +public interface ProfilingListener extends NodeUsageListener { + + void collectQueryInfo(ObjectGraphNode node, long beans, long micros); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html b/src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html deleted file mode 100644 index f0e805a5c..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - AutoFetch Implementation - - -AutoFetch Implementation - - \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneServiceFactory.java similarity index 69% rename from src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java rename to src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneServiceFactory.java index b19a4ee37..1e6c2d1cd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneServiceFactory.java @@ -1,7 +1,8 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.ebeaninternal.server.autofetch.service; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneService; import com.avaje.ebeaninternal.server.resource.ResourceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,29 +12,29 @@ import java.io.File; import java.io.FileInputStream; import java.io.ObjectInputStream; -public class AutoFetchManagerFactory { +public class AutoTuneServiceFactory { - private static final Logger logger = LoggerFactory.getLogger(AutoFetchManagerFactory.class); + private static final Logger logger = LoggerFactory.getLogger(AutoTuneServiceFactory.class); - public static AutoFetchManager create(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { + public static AutoTuneService create(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { - AutoFetchManagerFactory me = new AutoFetchManagerFactory(); + AutoTuneServiceFactory me = new AutoTuneServiceFactory(); return me.createAutoFetchManager(server, serverConfig, resourceManager); } - private AutoFetchManager createAutoFetchManager(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { + private AutoTuneService createAutoFetchManager(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { - AutoFetchManager manager = createAutoFetchManager(server.getName(), resourceManager); + AutoTuneService manager = createAutoFetchManager(server.getName(), resourceManager); manager.setOwner(server, serverConfig); return manager; } - private AutoFetchManager createAutoFetchManager(String serverName, ResourceManager resourceManager) { + private AutoTuneService createAutoFetchManager(String serverName, ResourceManager resourceManager) { File autoFetchFile = getAutoFetchFile(serverName, resourceManager); - AutoFetchManager autoFetchManager = null; + AutoTuneService autoFetchManager = null; boolean readFile = !"false".equalsIgnoreCase(System.getProperty("autofetch.readfromfile")); if (readFile) { @@ -44,13 +45,13 @@ public class AutoFetchManagerFactory { // not deserialized from file so create as empty // It will be populated automatically by querying the // database meta data - autoFetchManager = new DefaultAutoFetchManager(autoFetchFile.getAbsolutePath()); + autoFetchManager = new BaseAutoTuneService(autoFetchFile.getAbsolutePath()); } return autoFetchManager; } - private AutoFetchManager deserializeAutoFetch(File autoFetchFile) { + private AutoTuneService deserializeAutoFetch(File autoFetchFile) { try { if (!autoFetchFile.exists()) { @@ -58,7 +59,7 @@ public class AutoFetchManagerFactory { } FileInputStream fi = new FileInputStream(autoFetchFile); ObjectInputStream ois = new ObjectInputStream(fi); - AutoFetchManager profListener = (AutoFetchManager) ois.readObject(); + AutoTuneService profListener = (AutoTuneService) ois.readObject(); ois.close(); logger.info("AutoFetch deserialized from file [" + autoFetchFile.getAbsolutePath() + "]"); diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java new file mode 100644 index 000000000..81ee660b7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java @@ -0,0 +1,207 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + +import com.avaje.ebean.config.AutofetchConfig; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Implementation of the AutoTuneService which is comprised of profiling and query tuning. + */ +public class BaseAutoTuneService implements AutoTuneService { + + private static final Logger logger = LoggerFactory.getLogger(BaseAutoTuneService.class); + + private final long defaultGarbageCollectionWait; + + private final boolean garbageCollectionOnShutdown; + + private final BaseQueryTuner queryTuner; + + public BaseAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) { + + AutofetchConfig config = serverConfig.getAutofetchConfig(); + + ProfileManager profileManager = new ProfileManager(config, server); + this.queryTuner = new BaseQueryTuner(config, server, profileManager); + + this.garbageCollectionOnShutdown = config.isGarbageCollectionOnShutdown(); + this.defaultGarbageCollectionWait = (long) config.getGarbageCollectionWait(); + } + + /** + * Load the query tuning information from it's data store. + */ + public void startup() { + +// File autoFetchFile = new File(fileName); +// +// try { +// FileOutputStream fout = new FileOutputStream(autoFetchFile); +// +// ObjectOutputStream oout = new ObjectOutputStream(fout); +// oout.writeObject(this); +// oout.flush(); +// oout.close(); +// +// } catch (Exception e) { +// String msg = "Error serializing autofetch file"; +// logging.logError(msg, e); +// } + } + + private void saveProfiling() { + + } + + /** + * Shutdown the listener. + *

+ * We should try to collect the usage statistics by calling a System.gc(). + * This is necessary for use with short lived applications where garbage + * collection may not otherwise occur at all. + *

+ */ + public void shutdown() { + if (garbageCollectionOnShutdown) { + collectUsageViaGC(-1); + saveProfiling(); + } + } + + /** + * Ask for a System.gc() so that we gather node usage information. + *

+ * Really only want to do this sparingly but useful just prior to shutdown + * for short run application where garbage collection may otherwise not + * occur at all. + *

+ *

+ * waitMillis will do a thread sleep to give the garbage collection a little + * time to do its thing assuming we are shutting down the VM. + *

+ *

+ * If waitMillis is -1 then the defaultGarbageCollectionWait is used which + * defaults to 100 milliseconds. + *

+ */ + public void collectUsageViaGC() { + collectUsageViaGC(-1); + } + + public void collectUsageViaGC(long waitMillis) { + System.gc(); + try { + if (waitMillis < 0) { + waitMillis = defaultGarbageCollectionWait; + } + Thread.sleep(waitMillis); + } catch (InterruptedException e) { + logger.warn("Error while sleeping after System.gc() request.", e); + } + updateTunedQueryInfo(); + } + + /** + * Update the tuned fetch plans from the current usage information. + */ + public void updateTunedQueryInfo() { + +// if (!profiling) { +// // we are not collecting any profiling information at +// // the moment so don't try updating the tuned query plans. +// return "Not profiling"; +// } +// +// synchronized (statisticsMonitor) { +// +// Counters counters = new Counters(); +// +// for (ProfileOrigin queryPointStatistics : statisticsMap.values()) { +// if (!queryPointStatistics.hasUsage()) { +// // no usage statistics collected yet... +// counters.incrementNoUsage(); +// } else { +// updateTunedQueryFromUsage(counters, queryPointStatistics); +// } +// } +// +// String summaryInfo = counters.toString(); +// +// if (counters.isInteresting()) { +// // only log it if its interesting +// logging.logSummary(summaryInfo); +// } +// +// return summaryInfo; +// } + } + + +// +// private void updateTunedQueryFromUsage(Counters counters, ProfileOrigin statistics) { +// +// ObjectGraphOrigin queryPoint = statistics.getOrigin(); +// String beanType = queryPoint.getBeanType(); +// +// try { +// Class beanClass = ClassUtil.forName(beanType, this.getClass()); +// BeanDescriptor beanDescriptor = server.getBeanDescriptor(beanClass); +// if (beanDescriptor != null) { +// +// // Determine the fetch plan from the latest statistics. +// // Use this to compare with current "tuned fetch plan". +// OrmQueryDetail newFetchDetail = statistics.buildTunedFetch(beanDescriptor); +// +// // get the current tuned fetch info... +// TunedQueryInfo currentFetch = tunedQueryInfoMap.get(queryPoint.getKey()); +// +// if (currentFetch == null) { +// // its a new fetch plan, add it. +// counters.incrementNew(); +// +// currentFetch = statistics.createTunedFetch(newFetchDetail); +// logging.logNew(currentFetch); +// tunedQueryInfoMap.put(queryPoint.getKey(), currentFetch); +// +// } else if (!currentFetch.isSame(newFetchDetail)) { +// // the fetch plan has changed, update it. +// counters.incrementModified(); +// +// logging.logChanged(currentFetch, newFetchDetail); +// currentFetch.setTunedDetail(newFetchDetail); +// +// } else { +// // the fetch plan has not changed... +// counters.incrementUnchanged(); +// } +// +// currentFetch.setProfileCount(statistics.getCounter()); +// } +// +// } catch (ClassNotFoundException e) { +// // expected after renaming/moving an entity bean +// String msg = e.toString() + " updating autoFetch tuned query for " + beanType +// + ". It isLikely this bean has been renamed or moved"; +// logging.logInfo(msg, null); +// statisticsMap.remove(statistics.getOrigin().getKey()); +// } +// } +// + + /** + * Auto tune the query and enable profiling. + */ + public boolean tuneQuery(SpiQuery query) { + return queryTuner.tuneQuery(query); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java new file mode 100644 index 000000000..a60f73fd9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java @@ -0,0 +1,150 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + +import com.avaje.ebean.bean.CallStack; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.config.AutofetchConfig; +import com.avaje.ebean.config.AutofetchMode; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; + +import javax.persistence.PersistenceException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + */ +public class BaseQueryTuner { + + private final boolean queryTuning; + + /** + * Converted from a 0-100 int to a double. Effectively a percentage rate at + * which to collect profiling information. + */ + private final double profilingRate; + + private final int profilingBase; + + private final int profilingMin; + + private boolean profiling; + + private final AutofetchMode mode; + + /** + * Map of the tuned query details per profile query point. + */ + private final Map tunedQueryInfoMap = new ConcurrentHashMap(); + + + private final SpiEbeanServer server; + + private final ProfilingListener profilingListener; + + public BaseQueryTuner(AutofetchConfig config, SpiEbeanServer server, ProfilingListener profilingListener) { + this.server = server; + this.profilingListener = profilingListener; + this.mode = config.getMode(); + this.queryTuning = config.isQueryTuning(); + this.profiling = config.isProfiling(); + this.profilingRate = config.getProfilingRate(); + this.profilingBase = config.getProfilingBase(); + this.profilingMin = config.getProfilingMin(); + } + + /** + * Load the tuned query information. + */ + public void load(String key, TunedQueryInfo queryInfo) { + tunedQueryInfoMap.put(key, queryInfo); + } + + /** + * Auto tune the query and enable profiling. + */ + public boolean tuneQuery(SpiQuery query) { + + if (!queryTuning && !profiling) { + return false; + } + + if (!useAutoFetch(query)) { + // not using autoFetch for this query + return false; + } + + ObjectGraphNode parentAutoFetchNode = query.getParentNode(); + if (parentAutoFetchNode != null) { + // This is a +lazy/+query query with profiling on. + // We continue to collect the profiling information. + query.setProfilingListener(profilingListener); + return true; + } + + // create a query point to identify the query + CallStack stack = server.createCallStack(); + ObjectGraphNode origin = query.setOrigin(stack); + + // get current "tuned fetch" for this query point + TunedQueryInfo tunedFetch = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey()); + + // get the number of times we have collected profiling information + int profileCount = tunedFetch == null ? 0 : tunedFetch.getProfileCount(); + + if (profiling) { + // we want more profiling information? + if (tunedFetch == null) { + query.setProfilingListener(profilingListener); + + } else if (profileCount < profilingBase) { + query.setProfilingListener(profilingListener); + + } else if (tunedFetch.isPercentageProfile(profilingRate)) { + query.setProfilingListener(profilingListener); + } + } + + if (queryTuning && tunedFetch != null && profileCount >= profilingMin) { + // deemed to have enough profiling information for automatic tuning + return tunedFetch.autoFetchTune(query); + } + + return false; + } + + /** + * Return true if we should try to use autoFetch for this query. + */ + private boolean useAutoFetch(SpiQuery query) { + + if (query.isLoadBeanCache()) { + // when loading the cache don't tune the query + // as we want full objects loaded into the cache + return false; + } + + Boolean autoFetch = query.isAutofetch(); + if (autoFetch != null) { + // explicitly set... + return autoFetch; + + } else { + // determine using implicit mode... + switch (mode) { + case DEFAULT_ON: + return true; + + case DEFAULT_OFF: + return false; + + case DEFAULT_ONIFEMPTY: + return query.isDetailEmpty(); + + default: + throw new PersistenceException("Invalid autoFetchMode " + mode); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/DefaultAutoFetchManagerLogging.java similarity index 92% rename from src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java rename to src/main/java/com/avaje/ebeaninternal/server/autofetch/service/DefaultAutoFetchManagerLogging.java index f1cbb183b..8ba42bb6b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/DefaultAutoFetchManagerLogging.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.ebeaninternal.server.autofetch.service; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebeaninternal.api.SpiEbeanServer; @@ -19,11 +19,11 @@ public class DefaultAutoFetchManagerLogging { private static final Logger logger = LoggerFactory.getLogger(DefaultAutoFetchManagerLogging.class); - private final DefaultAutoFetchManager manager; + private final BaseAutoTuneService manager; private final int updateFreqInSecs; - public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, DefaultAutoFetchManager profileListener) { + public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, BaseAutoTuneService profileListener) { this.manager = profileListener; this.updateFreqInSecs = serverConfig.getAutofetchConfig().getProfileUpdateFrequency(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileManager.java new file mode 100644 index 000000000..a9bada622 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileManager.java @@ -0,0 +1,112 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + +import com.avaje.ebean.bean.NodeUsageCollector; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.config.AutofetchConfig; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + */ +public class ProfileManager implements ProfilingListener { + + private static final Logger logger = LoggerFactory.getLogger(ProfileManager.class); + + private final boolean queryTuningAddVersion; + + private final boolean profiling; + + /** + * Map of the usage and query statistics gathered. + */ + private final Map profileMap = new ConcurrentHashMap(); + + private final Object monitor = new Object(); + + private final SpiEbeanServer server; + + public ProfileManager(AutofetchConfig config, SpiEbeanServer server) { + this.server = server; + this.profiling = config.isProfiling(); + this.queryTuningAddVersion = config.isQueryTuningAddVersion(); + } + + /** + * Gather query execution statistics. This could either be the originating + * query in which case the parentNode will be null, or a lazy loading query + * resulting from traversal of the object graph. + */ + public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) { + + if (node != null) { + ObjectGraphOrigin origin = node.getOriginQueryPoint(); + if (origin != null) { + ProfileOrigin stats = getProfileOrigin(origin); + stats.collectQueryInfo(node, beans, micros); + } + } + } + + /** + * Collect usage statistics from a node in the object graph. + *

+ * This is sent to use from a EntityBeanIntercept when the finalise method + * is called on the bean. + *

+ */ + public void collectNodeUsage(NodeUsageCollector usageCollector) { + + ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint()); + profileOrigin.collectUsageInfo(usageCollector); + } + + private ProfileOrigin getProfileOrigin(ObjectGraphOrigin originQueryPoint) { + synchronized (monitor) { + ProfileOrigin stats = profileMap.get(originQueryPoint.getKey()); + if (stats == null) { + stats = new ProfileOrigin(originQueryPoint, queryTuningAddVersion); + profileMap.put(originQueryPoint.getKey(), stats); + } + return stats; + } + } + + + /** + * Update the tuned fetch plans from the current usage information. + */ + public void updateTunedQueryInfo() { + + if (!profiling) { + // we are not collecting any profiling information at + // the moment so don't try updating the tuned query plans. + return;// "Not profiling"; + } + + synchronized (monitor) { + + for (ProfileOrigin origin : profileMap.values()) { + if (origin.hasUsage()) { + OrmQueryDetail ormQueryDetail = updateTunedQueryFromUsage(origin); + + } + } + } + } + + + private OrmQueryDetail updateTunedQueryFromUsage(ProfileOrigin statistics) { + + BeanDescriptor desc = server.getBeanDescriptorById(statistics.getOrigin().getBeanType()); + return desc == null ? null : statistics.buildTunedFetch(desc); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java similarity index 52% rename from src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java rename to src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java index a9625ca70..959b740a2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.ebeaninternal.server.autofetch.service; import com.avaje.ebean.bean.NodeUsageCollector; import com.avaje.ebean.bean.ObjectGraphNode; @@ -13,7 +13,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -public class Statistics implements Serializable { +public class ProfileOrigin implements Serializable { private static final long serialVersionUID = -5586783791097230766L; @@ -24,14 +24,14 @@ public class Statistics implements Serializable { private int counter; - private final Map queryStatsMap = new LinkedHashMap(); + private final Map queryStatsMap = new LinkedHashMap(); - private final Map nodeUsageMap = new LinkedHashMap(); + private final Map nodeUsageMap = new LinkedHashMap(); @SuppressWarnings("RedundantStringConstructorCall") private final String monitor = new String(); - public Statistics(ObjectGraphOrigin origin, boolean queryTuningAddVersion) { + public ProfileOrigin(ObjectGraphOrigin origin, boolean queryTuningAddVersion) { this.origin = origin; this.queryTuningAddVersion = queryTuningAddVersion; } @@ -40,13 +40,13 @@ public class Statistics implements Serializable { return origin; } - public TunedQueryInfo createTunedFetch(OrmQueryDetail newFetchDetail) { - synchronized (monitor) { - // NB: create a copy of queryPoint allowing garbage - // collection of source... - return new TunedQueryInfo(origin, newFetchDetail, counter); - } - } +// public TunedQueryInfo createTunedFetch(OrmQueryDetail newFetchDetail) { +// synchronized (monitor) { +// // NB: create a copy of queryPoint allowing garbage +// // collection of source... +// return new TunedQueryInfo(origin, newFetchDetail, counter); +// } +// } /** * Return the number of times the root query has executed. @@ -77,7 +77,7 @@ public class Statistics implements Serializable { PathProperties pathProps = new PathProperties(); - for (StatisticsNodeUsage statsNode : nodeUsageMap.values()) { + for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) { statsNode.buildTunedFetch(pathProps, rootDesc); } @@ -98,23 +98,22 @@ public class Statistics implements Serializable { public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) { - synchronized (monitor) { - String key = node.getPath(); - if (key == null) { - key = ""; - // this is basically the number of times the root query - // has executed which gives us an indication of how - // much profiling information we have gathered. - counter++; - } - - StatisticsQuery stats = queryStatsMap.get(key); - if (stats == null) { - stats = new StatisticsQuery(key); - queryStatsMap.put(key, stats); - } - stats.add(beansLoaded, micros); + String key = node.getPath(); + if (key == null) { + key = ""; + // this is basically the number of times the root query + // has executed which gives us an indication of how + // much profiling information we have gathered. + counter++; } + + ProfileOriginQuery stats = queryStatsMap.get(key); + if (stats == null) { + // a race condition but we don't care + stats = new ProfileOriginQuery(key); + queryStatsMap.put(key, stats); + } + stats.add(beansLoaded, micros); } @@ -126,49 +125,49 @@ public class Statistics implements Serializable { if (!profile.isEmpty()) { ObjectGraphNode node = profile.getNode(); - StatisticsNodeUsage nodeStats = getNodeStats(node.getPath()); + ProfileOriginNodeUsage nodeStats = getNodeStats(node.getPath()); nodeStats.publish(profile); } } - private StatisticsNodeUsage getNodeStats(String path) { + private ProfileOriginNodeUsage getNodeStats(String path) { synchronized (monitor) { - StatisticsNodeUsage nodeStats = nodeUsageMap.get(path); + ProfileOriginNodeUsage nodeStats = nodeUsageMap.get(path); if (nodeStats == null) { - nodeStats = new StatisticsNodeUsage(path, queryTuningAddVersion); + nodeStats = new ProfileOriginNodeUsage(path, queryTuningAddVersion); nodeUsageMap.put(path, nodeStats); } return nodeStats; } } - public String getUsageDebug() { - synchronized (monitor) { - StringBuilder sb = new StringBuilder(); - sb.append("root[").append(origin.getBeanType()).append("] "); - for (StatisticsNodeUsage node : nodeUsageMap.values()) { - sb.append(node.toString()).append("\n"); - } - return sb.toString(); - } - } - - public String getQueryStatDebug() { - synchronized (monitor) { - StringBuilder sb = new StringBuilder(); - for (StatisticsQuery queryStat : queryStatsMap.values()) { - sb.append(queryStat.toString()).append("\n"); - } - return sb.toString(); - } - } - - public String toString() { - - synchronized (monitor) { - return getUsageDebug(); - } - } +// public String getUsageDebug() { +// synchronized (monitor) { +// StringBuilder sb = new StringBuilder(); +// sb.append("root[").append(origin.getBeanType()).append("] "); +// for (ProfileOriginNodeUsage node : nodeUsageMap.values()) { +// sb.append(node.toString()).append("\n"); +// } +// return sb.toString(); +// } +// } +// +// public String getQueryStatDebug() { +// synchronized (monitor) { +// StringBuilder sb = new StringBuilder(); +// for (ProfileOriginQuery queryStat : queryStatsMap.values()) { +// sb.append(queryStat.toString()).append("\n"); +// } +// return sb.toString(); +// } +// } +// +// public String toString() { +// +// synchronized (monitor) { +// return getUsageDebug(); +// } +// } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java similarity index 91% rename from src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java rename to src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java index 868f8f356..a051bc31f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.ebeaninternal.server.autofetch.service; import com.avaje.ebean.bean.NodeUsageCollector; import com.avaje.ebean.text.PathProperties; @@ -18,11 +18,11 @@ import java.util.Set; /** * Collects usages statistics for a given node in the object graph. */ -public class StatisticsNodeUsage implements Serializable { +public class ProfileOriginNodeUsage implements Serializable { private static final long serialVersionUID = -1663951463963779547L; - private static final Logger logger = LoggerFactory.getLogger(StatisticsNodeUsage.class); + private static final Logger logger = LoggerFactory.getLogger(ProfileOriginNodeUsage.class); @SuppressWarnings("RedundantStringConstructorCall") private final String monitor = new String(); @@ -39,7 +39,7 @@ public class StatisticsNodeUsage implements Serializable { private final Set aggregateUsed = new LinkedHashSet(); - public StatisticsNodeUsage(String path, boolean queryTuningAddVersion) { + public ProfileOriginNodeUsage(String path, boolean queryTuningAddVersion) { this.path = path; this.queryTuningAddVersion = queryTuningAddVersion; } @@ -99,7 +99,7 @@ public class StatisticsNodeUsage implements Serializable { synchronized (monitor) { - HashSet used = profile.getUsed(); + Set used = profile.getUsed(); profileCount++; if (!used.isEmpty()) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginQuery.java similarity index 81% rename from src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java rename to src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginQuery.java index 506087b31..b4d2b6bfa 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginQuery.java @@ -1,11 +1,11 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.ebeaninternal.server.autofetch.service; import java.io.Serializable; /** * Used to accumulate query execution statistics. */ -public class StatisticsQuery implements Serializable { +public class ProfileOriginQuery implements Serializable { private static final long serialVersionUID = -1133958958072778811L; @@ -17,7 +17,7 @@ public class StatisticsQuery implements Serializable { private long totalMicros; - public StatisticsQuery(String path) { + public ProfileOriginQuery(String path) { this.path = path; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java similarity index 98% rename from src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java rename to src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java index dec85ebee..8a6a5d046 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.ebeaninternal.server.autofetch.service; import com.avaje.ebean.bean.ObjectGraphOrigin; import com.avaje.ebeaninternal.api.SpiQuery; diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java index 2f0a2db78..e09e3873e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java @@ -50,7 +50,7 @@ public class DefaultContainer implements SpiContainer { private final JndiDataSourceLookup jndiDataSourceFactory; - private final AtomicInteger serverId = new AtomicInteger(1); +// private final AtomicInteger serverId = new AtomicInteger(1); public DefaultContainer(ContainerConfig containerConfig) { @@ -119,7 +119,7 @@ public class DefaultContainer implements SpiContainer { ServerCacheManager cacheManager = getCacheManager(serverConfig); - int uniqueServerId = serverId.incrementAndGet(); +// int uniqueServerId = serverId.incrementAndGet(); SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig); XmlConfigLoader xmlConfigLoader = new XmlConfigLoader(null); @@ -131,18 +131,18 @@ public class DefaultContainer implements SpiContainer { cacheManager.init(server); - if (serverConfig.isRegisterJmxMBeans()) { - MBeanServer mbeanServer; - ArrayList list = MBeanServerFactory.findMBeanServer(null); - if (list.size() == 0) { - // probably not running in a server - mbeanServer = MBeanServerFactory.createMBeanServer(); - } else { - // use the first MBeanServer - mbeanServer = (MBeanServer) list.get(0); - } - server.registerMBeans(mbeanServer, uniqueServerId); - } +// if (serverConfig.isRegisterJmxMBeans()) { +// MBeanServer mbeanServer; +// ArrayList list = MBeanServerFactory.findMBeanServer(null); +// if (list.size() == 0) { +// // probably not running in a server +// mbeanServer = MBeanServerFactory.createMBeanServer(); +// } else { +// // use the first MBeanServer +// mbeanServer = (MBeanServer) list.get(0); +// } +// server.registerMBeans(mbeanServer, uniqueServerId); +// } // generate and run DDL if required // if there are any other tasks requiring action in their plugins, do them as well diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index a716516c5..4b14fc42b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -33,7 +33,7 @@ import com.avaje.ebeaninternal.api.SpiQuery.Type; import com.avaje.ebeaninternal.api.SpiSqlQuery; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.api.TransactionEventTable; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneService; import com.avaje.ebean.dbmigration.DdlGenerator; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; @@ -43,7 +43,6 @@ import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate; import com.avaje.ebeaninternal.server.deploy.InheritInfo; import com.avaje.ebeaninternal.server.el.ElFilter; -import com.avaje.ebeaninternal.server.jmx.MAdminAutofetch; import com.avaje.ebeaninternal.server.lib.ShutdownManager; import com.avaje.ebeaninternal.server.query.CQuery; import com.avaje.ebeaninternal.server.query.CQueryEngine; @@ -69,9 +68,6 @@ import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.management.InstanceAlreadyExistsException; -import javax.management.MBeanServer; -import javax.management.ObjectName; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import java.util.ArrayList; @@ -103,8 +99,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final DatabasePlatform databasePlatform; - private final AdminAutofetch adminAutofetch; - private final TransactionManager transactionManager; private final TransactionScopeManager transactionScopeManager; @@ -132,7 +126,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final DiffHelp diffHelp; - private final AutoFetchManager autoFetchManager; + private final AutoTuneService autoTuneService; private final CQueryEngine cqueryEngine; @@ -152,21 +146,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final MetaInfoManager metaInfoManager; - /** - * The MBean name used to register Ebean. - */ - private String mbeanName; - /** * The default PersistenceContextScope used if it is not explicitly set on a query. */ private final PersistenceContextScope defaultPersistenceContextScope; - /** - * The MBeanServer Ebean is registered with. - */ - private MBeanServer mbeanServer; - /** * Flag set when the server has shutdown. */ @@ -235,8 +219,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { this.queryEngine = config.createOrmQueryEngine(); this.relationalQueryEngine = config.createRelationalQueryEngine(); - this.autoFetchManager = config.createAutoFetchManager(this); - this.adminAutofetch = new MAdminAutofetch(autoFetchManager); + this.autoTuneService = config.createAutoFetchManager(this); this.beanLoader = new DefaultBeanLoader(this); this.jsonContext = config.createJsonContext(this); @@ -346,11 +329,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } public AdminAutofetch getAdminAutofetch() { - return adminAutofetch; - } - - public AutoFetchManager getAutoFetchManager() { - return autoFetchManager; + return autoTuneService; } /** @@ -373,41 +352,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { public void start() { } - public void registerMBeans(MBeanServer mbeanServer, int uniqueServerId) { - - this.mbeanServer = mbeanServer; - this.mbeanName = "Ebean:server=" + serverName + uniqueServerId; - - ObjectName autofetchName; - try { - autofetchName = new ObjectName(mbeanName + ",key=AutoFetch"); - } catch (Exception e) { - String msg = "Failed to register the JMX beans for Ebean server [" + serverName + "]."; - logger.error(msg, e); - return; - } - - try { - mbeanServer.registerMBean(adminAutofetch, autofetchName); - - } catch (InstanceAlreadyExistsException e) { - // tomcat webapp reloading - String msg = "JMX beans for Ebean server [" + serverName + "] already registered. Will try unregister/register" + e.getMessage(); - logger.warn(msg); - try { - mbeanServer.unregisterMBean(autofetchName); - mbeanServer.registerMBean(adminAutofetch, autofetchName); - - } catch (Exception ae) { - String amsg = "Unable to unregister/register the JMX beans for Ebean server [" + serverName + "]."; - logger.error(amsg, ae); - } - } catch (Exception e) { - String msg = "Error registering MBean[" + mbeanName + "]"; - logger.error(msg, e); - } - } - /** * Shutting down via JVM Shutdown hook. */ @@ -439,16 +383,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return; } shutdownPlugins(); - try { - if (mbeanServer != null) { - mbeanServer.unregisterMBean(new ObjectName(mbeanName + ",key=AutoFetch")); - } - } catch (Exception e) { - logger.error("Error unregistering Ebean " + mbeanName, e); - } // shutdown autofetch profile collection - autoFetchManager.shutdown(); + autoTuneService.shutdown(); // shutdown background threads backgroundExecutor.shutdown(); // shutdown DataSource (if its an Ebean one) @@ -1100,7 +1037,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { public SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) { - if (desc.isAutoFetchTunable() && !query.isSqlSelect() && !autoFetchManager.tuneQuery(query)) { + if (desc.isAutoFetchTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) { // use deployment FetchType.LAZY/EAGER annotations // to define the 'default' select clause query.setDefaultSelectClause(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index f0b0e0b30..c1148d0cf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -13,8 +13,8 @@ import com.avaje.ebean.plugin.SpiServerPlugin; import com.avaje.ebean.text.json.JsonContext; import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManagerFactory; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneService; +import com.avaje.ebeaninternal.server.autofetch.service.AutoTuneServiceFactory; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener; @@ -235,8 +235,8 @@ public class InternalConfiguration { return xmlConfig; } - public AutoFetchManager createAutoFetchManager(SpiEbeanServer server) { - return AutoFetchManagerFactory.create(server, serverConfig, resourceManager); + public AutoTuneService createAutoFetchManager(SpiEbeanServer server) { + return AutoTuneServiceFactory.create(server, serverConfig, resourceManager); } public RelationalQueryEngine createRelationalQueryEngine() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java deleted file mode 100644 index a31100d6d..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.avaje.ebeaninternal.server.jmx; - -import com.avaje.ebean.AdminAutofetch; -import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.config.AutofetchMode; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implementation of the AutoFetchControl. - *

- * This is accessible via {@link EbeanServer#getAdminAutofetch()} or via JMX - * MBeans. - *

- */ -public class MAdminAutofetch implements MAdminAutofetchMBean, AdminAutofetch { - - private static final Logger logger = LoggerFactory.getLogger(MAdminAutofetch.class); - - final AutoFetchManager autoFetchManager; - - final String modeOptions; - - public MAdminAutofetch(AutoFetchManager autoFetchListener) { - this.autoFetchManager = autoFetchListener; - this.modeOptions = AutofetchMode.DEFAULT_OFF + ", " - + AutofetchMode.DEFAULT_ON + ", " - + AutofetchMode.DEFAULT_ONIFEMPTY; - } - - public boolean isQueryTuning() { - return autoFetchManager.isQueryTuning(); - } - - public void setQueryTuning(boolean enable) { - autoFetchManager.setQueryTuning(enable); - } - - public boolean isProfiling() { - return autoFetchManager.isProfiling(); - } - - public void setProfiling(boolean enable) { - autoFetchManager.setProfiling(enable); - } - - public String getModeOptions() { - return modeOptions; - } - - public String getMode() { - return autoFetchManager.getMode().name(); - } - - public void setMode(String implicitMode) { - try { - AutofetchMode mode = AutofetchMode.valueOf(implicitMode); - autoFetchManager.setMode(mode); - } catch (Exception e) { - logger.info("Invalid implicit mode attempted "+e.getMessage()); - } - } - - public String collectUsageViaGC() { - return autoFetchManager.collectUsageViaGC(-1); - } - - public double getProfilingRate() { - return autoFetchManager.getProfilingRate(); - } - - public void setProfilingRate(double rate) { - autoFetchManager.setProfilingRate(rate); - } - - public int getProfilingMin() { - return autoFetchManager.getProfilingMin(); - } - - public int getProfilingBase() { - return autoFetchManager.getProfilingBase(); - } - - public void setProfilingMin(int profilingMin) { - autoFetchManager.setProfilingMin(profilingMin); - } - - public void setProfilingBase(int profilingMax) { - autoFetchManager.setProfilingBase(profilingMax); - } - - public String updateTunedQueryInfo() { - return autoFetchManager.updateTunedQueryInfo(); - } - - public int clearProfilingInfo() { - return autoFetchManager.clearProfilingInfo(); - } - - public int clearTunedQueryInfo() { - return autoFetchManager.clearTunedQueryInfo(); - } - - public void clearQueryStatistics() { - autoFetchManager.clearQueryStatistics(); - } - - public int getTotalProfileSize() { - return autoFetchManager.getTotalProfileSize(); - } - - public int getTotalTunedQueryCount() { - return autoFetchManager.getTotalTunedQueryCount(); - } - - public int getTotalTunedQuerySize() { - return autoFetchManager.getTotalTunedQuerySize(); - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java deleted file mode 100644 index ee6e10e78..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.avaje.ebeaninternal.server.jmx; - -import com.avaje.ebean.Query; - -public interface MAdminAutofetchMBean { - - /** - * Return true if profiling is enabled. - */ - boolean isProfiling(); - - /** - * Set to true to enable profiling. - */ - void setProfiling(boolean enable); - - /** - * Return true if autoFetch is enabled. - */ - boolean isQueryTuning(); - - /** - * Set to true to enable autoFetch. - */ - void setQueryTuning(boolean enable); - - /** - * This controls whether autoFetch is used when it has not - * been explicitly set on a query via {@link Query#setAutofetch(boolean)}. - */ - String getMode(); - - /** - * List of the valid implicit modes that can be used. - */ - String getModeOptions(); - - /** - * Set the auto fetch mode used when a query has not had {@link Query#setAutofetch(boolean)}. - */ - void setMode(String mode); - - /** - * Return the max number of queries profiled (per query point). - *

- * The number of queries profiled is collected per query point. Once a query - * point has profiled this number of queries it does not profile any more. - *

- */ - int getProfilingBase(); - - /** - * Set a max number of queries to profile per query point. - *

- * This number should provide a level of confidence that no more profiling - * is required for this query point. - *

- */ - void setProfilingBase(int profilingMaxThreshold); - - /** - * Returns the rate which profiling is collected. - * This is an int between 0 and 100. - */ - double getProfilingRate(); - - /** - * Set the rate at which profiling is collected after the base. - * - * @param rate a int between 0 and 100. - */ - void setProfilingRate(double rate); - - /** - * Return the minimum number of queries profiled before autoFetch will start - * automatically tuning the queries. - *

- * This could be one which means start autoFetch tuning after the first - * profiling information is collected. - *

- */ - int getProfilingMin(); - - /** - * Set the minimum number of queries profiled per query point before - * autoFetch will automatically tune the queries. - *

- * Increasing this number will mean more profiling is collected before - * autoFetch starts tuning the query. - *

- */ - void setProfilingMin(int autoFetchMinThreshold); - - /** - * Fire a garbage collection (hint to the JVM). Assuming garbage collection - * fires this will gather the usage profiling information. - */ - String collectUsageViaGC(); - - /** - * This will take the current profiling information and update the "tuned query - * detail". - *

- * This is done periodically and can also be manually invoked. - *

- */ - String updateTunedQueryInfo(); - - /** - * Clear all the tuned query info. - *

- * Should only need do this for testing and playing around. - *

- * @return the amount of tuned query information cleared. - */ - int clearTunedQueryInfo(); - - /** - * Clear all the profiling information. - *

- * This means the profiling information will need to be re-gathered. - *

- *

- * Should only need do this for testing and playing around. - *

- * @return the amount of profiled information cleared. - */ - int clearProfilingInfo(); - - /** - * Return the number of queries tuned by AutoFetch. - */ - int getTotalTunedQueryCount(); - - /** - * Return the size of the TuneQuery map. - */ - int getTotalTunedQuerySize(); - - /** - * Return the size of the profile map. - */ - int getTotalProfileSize(); - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java deleted file mode 100644 index edaa2ad21..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package com.avaje.ebeaninternal.server.jmx; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index b1f7adada..d7eb2d86b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -58,7 +58,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex if (queryProps != null) { queryProps.configureBeanQuery(query); } - if (parent.isUseAutofetchManager()) { + if (parent.isUseAutoTune()) { query.setAutofetch(true); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java index f9f1c1870..9a6b3fb3d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -47,7 +47,7 @@ public class DLoadContext implements LoadContext { */ private final String relativePath; private final ObjectGraphOrigin origin; - private final boolean useAutofetchManager; + private final boolean useProfiling; private final Map nodePathMap = new HashMap(); @@ -67,7 +67,7 @@ public class DLoadContext implements LoadContext { this.readOnly = query.isReadOnly(); this.disableLazyLoading = query.isDisableLazyLoading(); this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache()); - this.useAutofetchManager = query.getAutoFetchManager() != null; + this.useProfiling = query.getProfilingListener() != null; ObjectGraphNode parentNode = query.getParentNode(); if (parentNode != null) { @@ -197,8 +197,8 @@ public class DLoadContext implements LoadContext { return new ObjectGraphNode(origin, path); } - public boolean isUseAutofetchManager() { - return useAutofetchManager; + public boolean isUseAutoTune() { + return useProfiling; } protected String getFullPath(String path) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java index 2e726ee94..fc3927b31 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -70,7 +70,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex queryProps.configureBeanQuery(query); } - if (parent.isUseAutofetchManager()) { + if (parent.isUseAutoTune()) { query.setAutofetch(true); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java index 1f0518c1c..923d4ebed 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -6,7 +6,7 @@ import com.avaje.ebean.bean.*; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiQuery.Mode; import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; import com.avaje.ebeaninternal.server.core.Message; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; @@ -149,13 +149,13 @@ public class CQuery implements DbReadContext, CancelableQuery { private final Mode queryMode; - private final boolean autoFetchProfiling; + private final boolean autoTuneProfiling; private final ObjectGraphNode objectGraphNode; - private final AutoFetchManager autoFetchManager; + private final ProfilingListener profilingListener; - private final WeakReference autoFetchManagerRef; + private final WeakReference profilingListenerRef; private final Boolean readOnly; @@ -176,11 +176,10 @@ public class CQuery implements DbReadContext, CancelableQuery { this.readOnly = request.isReadOnly(); - this.autoFetchManager = query.getAutoFetchManager(); - this.autoFetchProfiling = autoFetchManager != null; this.objectGraphNode = query.getParentNode(); - this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference( - autoFetchManager) : null; + this.profilingListener = query.getProfilingListener(); + this.autoTuneProfiling = profilingListener != null; + this.profilingListenerRef = autoTuneProfiling ? new WeakReference(profilingListener) : null; // set the generated sql back to the query // so its available to the user... @@ -518,8 +517,8 @@ public class CQuery implements DbReadContext, CancelableQuery { long exeNano = System.nanoTime() - startNano; executionTimeMicros = TimeUnit.NANOSECONDS.toMicros(exeNano); - if (autoFetchProfiling) { - autoFetchManager.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros); + if (autoTuneProfiling) { + profilingListener.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros); } queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode); @@ -649,7 +648,7 @@ public class CQuery implements DbReadContext, CancelableQuery { // need query.isProfiling() because we just take the data // from the lazy loaded or refreshed beans and put it into the already // existing beans which are already collecting usage information - return autoFetchProfiling && query.isUsageProfiling(); + return autoTuneProfiling && query.isUsageProfiling(); } private String getPath(String propertyName) { @@ -671,8 +670,7 @@ public class CQuery implements DbReadContext, CancelableQuery { public void profileBean(EntityBeanIntercept ebi, String prefix) { ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix); - - ebi.setNodeUsageCollector(new NodeUsageCollector(node, autoFetchManagerRef)); + ebi.setNodeUsageCollector(new NodeUsageCollector(node, profilingListenerRef)); } public void setCurrentPrefix(String currentPrefix, Map currentPathMap) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index db2dfb342..b8543b0e1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1,14 +1,5 @@ package com.avaje.ebeaninternal.server.querydefn; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.persistence.PersistenceException; - import com.avaje.ebean.*; import com.avaje.ebean.OrderBy.Property; import com.avaje.ebean.bean.BeanCollectionTouched; @@ -26,7 +17,7 @@ import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionList; import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect; @@ -36,6 +27,14 @@ import com.avaje.ebeaninternal.server.expression.SimpleExpression; import com.avaje.ebeaninternal.server.query.CancelableQuery; import com.avaje.ebeaninternal.util.DefaultExpressionList; +import javax.persistence.PersistenceException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Default implementation of an Object Relational query. */ @@ -57,7 +56,7 @@ public class DefaultOrmQuery implements SpiQuery { */ private transient TableJoin includeTableJoin; - private transient AutoFetchManager autoFetchManager; + private transient ProfilingListener profilingListener; private transient BeanDescriptor beanDescriptor; @@ -481,7 +480,7 @@ public class DefaultOrmQuery implements SpiQuery { DefaultOrmQuery copy = new DefaultOrmQuery(beanType, server, expressionFactory, (String) null); copy.name = name; copy.includeTableJoin = includeTableJoin; - copy.autoFetchManager = autoFetchManager; + copy.profilingListener = profilingListener; copy.query = query; copy.additionalWhere = additionalWhere; @@ -616,19 +615,23 @@ public class DefaultOrmQuery implements SpiQuery { return this; } + @Override public DefaultOrmQuery setForUpdate(boolean forUpdate) { this.forUpdate = forUpdate; return this; } - public AutoFetchManager getAutoFetchManager() { - return autoFetchManager; + @Override + public ProfilingListener getProfilingListener() { + return profilingListener; } - public void setAutoFetchManager(AutoFetchManager autoFetchManager) { - this.autoFetchManager = autoFetchManager; + @Override + public void setProfilingListener(ProfilingListener profilingListener) { + this.profilingListener = profilingListener; } + @Override public Mode getMode() { return mode; } diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 9304cf8ec..adbcc84d8 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -13,7 +13,6 @@ import com.avaje.ebean.meta.MetaInfoManager; import com.avaje.ebean.plugin.SpiServer; import com.avaje.ebean.text.csv.CsvReader; import com.avaje.ebean.text.json.JsonContext; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.query.CQuery; @@ -86,11 +85,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } - @Override - public AutoFetchManager getAutoFetchManager() { - return null; - } - @Override public void clearQueryStatistics() { diff --git a/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java b/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java index b66df26fb..eaf1ec8be 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java @@ -15,7 +15,7 @@ import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.autofetch.TunedQueryInfo; +import com.avaje.ebeaninternal.server.autofetch.service.TunedQueryInfo; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.tests.model.basic.Order; import com.avaje.tests.model.basic.ResetBasicData; diff --git a/src/test/java/com/avaje/tests/autofetch/AutofetchEmbeddedTest.java b/src/test/java/com/avaje/tests/autofetch/AutofetchEmbeddedTest.java index 2a201f256..ba59ae6cb 100644 --- a/src/test/java/com/avaje/tests/autofetch/AutofetchEmbeddedTest.java +++ b/src/test/java/com/avaje/tests/autofetch/AutofetchEmbeddedTest.java @@ -37,30 +37,30 @@ public class AutofetchEmbeddedTest extends BaseTestCase { @Test public void testEmbeddedBeanQueryTuning() { - Ebean.getServer(null).getAdminAutofetch().setProfiling(true); - Ebean.getServer(null).getAdminAutofetch().setQueryTuning(true); - Ebean.getServer(null).getAdminAutofetch().setProfilingBase(1); - - EMain testBean = new EMain(); - testBean.setName("test"); - testBean.getEmbeddable().setDescription("test description"); - Ebean.save(testBean); - - //This should not throw an exception - for (int i = 0; i < 5; i++) { - Ebean.beginTransaction(); - try { - List result = Ebean.find(EMain.class).setAutofetch(true).findList(); - for (EMain e : result) { - e.getEmbeddable().setDescription("Test" + i); - Ebean.save(e); - } - Ebean.commitTransaction(); - } finally { - Ebean.endTransaction(); - logger.debug(Ebean.getServer(null).getAdminAutofetch().collectUsageViaGC()); - } - } +// Ebean.getServer(null).getAdminAutofetch().setProfiling(true); +// Ebean.getServer(null).getAdminAutofetch().setQueryTuning(true); +// Ebean.getServer(null).getAdminAutofetch().setProfilingBase(1); +// +// EMain testBean = new EMain(); +// testBean.setName("test"); +// testBean.getEmbeddable().setDescription("test description"); +// Ebean.save(testBean); +// +// //This should not throw an exception +// for (int i = 0; i < 5; i++) { +// Ebean.beginTransaction(); +// try { +// List result = Ebean.find(EMain.class).setAutofetch(true).findList(); +// for (EMain e : result) { +// e.getEmbeddable().setDescription("Test" + i); +// Ebean.save(e); +// } +// Ebean.commitTransaction(); +// } finally { +// Ebean.endTransaction(); +// logger.debug(Ebean.getServer(null).getAdminAutofetch().collectUsageViaGC()); +// } +// } } @Test diff --git a/src/test/java/com/avaje/tests/cache/TestL2CacheWithSharedBean.java b/src/test/java/com/avaje/tests/cache/TestL2CacheWithSharedBean.java index 381da8236..a31184917 100644 --- a/src/test/java/com/avaje/tests/cache/TestL2CacheWithSharedBean.java +++ b/src/test/java/com/avaje/tests/cache/TestL2CacheWithSharedBean.java @@ -8,7 +8,7 @@ import com.avaje.ebean.Ebean; import com.avaje.ebean.Query; import com.avaje.ebean.cache.ServerCache; import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.autofetch.TunedQueryInfo; +import com.avaje.ebeaninternal.server.autofetch.service.TunedQueryInfo; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.tests.model.basic.FeatureDescription; diff --git a/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java b/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java index 0693038fe..f9fcfe399 100644 --- a/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java +++ b/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java @@ -28,8 +28,11 @@ public class TestAutofetchTuneWithJoin extends BaseTestCase { ResetBasicData.reset(); - Query q = Ebean.find(Order.class).setAutofetch(true).fetch("customer") - .fetch("customer.contacts").where().lt("id", 3).query(); + Query q = Ebean.find(Order.class) + .setAutofetch(true) + .fetch("customer") + .fetch("customer.contacts") + .where().lt("id", 3).query(); List list = q.findList(); diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties index 86562c599..10323b913 100644 --- a/src/test/resources/ebean.properties +++ b/src/test/resources/ebean.properties @@ -11,14 +11,15 @@ ebean.encryptKeyManager=com.avaje.tests.basic.encrypt.BasicEncyptKeyManager -ebean.autofetch.querytuning=false -ebean.autofetch.profiling=false +ebean.autofetch.querytuning=true +ebean.autofetch.profiling=true ebean.autofetch.implicitmode=default_off #ebean.autofetch.implicitmode=default_onifempty ebean.autofetch.profiling.min=1 ebean.autofetch.profiling.base=10 #ebean.autofetch.profiling.rate=0.05 -ebean.autofetch.traceUsageCollection=false +ebean.autofetch.garbageCollectionOnShutdown=true +ebean.autofetch.traceUsageCollection=true ebean.ddl.generate=true diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index b52efc14d..39dcc78bd 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -42,6 +42,8 @@ + +