diff --git a/.gitignore b/.gitignore index bb406be3c..51330a2eb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ target/ logs/ log/ +ebean-profiling*.xml /db /mydb.db !src/test/ddl-review/*.sql diff --git a/ebean-autotune.xml b/ebean-autotune.xml new file mode 100644 index 000000000..53be9c98e --- /dev/null +++ b/ebean-autotune.xml @@ -0,0 +1,37 @@ + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:41) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useLots(TestAutoTuneProfiling.java:58) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:33) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:22) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:41) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useOrderDateCustomerName(TestAutoTuneProfiling.java:51) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:32) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:22) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:41) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useLotUntuned(TestAutoTuneProfiling.java:72) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:34) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:22) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:41) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useOrderDate(TestAutoTuneProfiling.java:45) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:31) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:22) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + diff --git a/src/main/java/com/avaje/ebean/AdminAutofetch.java b/src/main/java/com/avaje/ebean/AdminAutofetch.java deleted file mode 100644 index 15e19f9c1..000000000 --- a/src/main/java/com/avaje/ebean/AdminAutofetch.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.avaje.ebean; - -/** - * Administrative control of Autofetch during runtime. - */ -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(); - - /** - * This will take the current profiling information and update the "tuned - * query detail". - *

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

- * - * @return a summary of the updates that occurred - */ - 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(); - - /** - * 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/AutoTune.java b/src/main/java/com/avaje/ebean/AutoTune.java new file mode 100644 index 000000000..c5d17ac55 --- /dev/null +++ b/src/main/java/com/avaje/ebean/AutoTune.java @@ -0,0 +1,14 @@ +package com.avaje.ebean; + +/** + * Administrative control of AutoTune during runtime. + */ +public interface AutoTune { + + /** + * Fire a garbage collection (hint to the JVM). Assuming garbage collection + * fires this will gather remaining usage profiling information. + */ + void collectProfiling(); + +} diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index 3f19306c8..327d89275 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -106,10 +106,9 @@ public interface EbeanServer { void shutdown(boolean shutdownDataSource, boolean deregisterDriver); /** - * Return the AdminAutofetch which is used to control and configure the - * Autofetch service at runtime. + * Return AutoTune which is used to control the AutoTune service at runtime. */ - AdminAutofetch getAdminAutofetch(); + AutoTune getAutoTune(); /** * Return the name. This is used with {@link Ebean#getServer(String)} to get a diff --git a/src/main/java/com/avaje/ebean/bean/CallStack.java b/src/main/java/com/avaje/ebean/bean/CallStack.java index 0f4b78f0c..f283ea591 100644 --- a/src/main/java/com/avaje/ebean/bean/CallStack.java +++ b/src/main/java/com/avaje/ebean/bean/CallStack.java @@ -87,6 +87,17 @@ public final class CallStack implements Serializable { return zeroHash + ":" + pathHash + ":" + callStack[0]; } + /** + * Return the call stack lines appended with the given newLine string. + */ + public String description(String newLine) { + StringBuilder sb = new StringBuilder(400); + for (int i = 0; i < callStack.length; i++) { + sb.append(callStack[i].toString()).append(newLine); + } + return sb.toString(); + } + public String getOriginKey(int queryHash) { return zeroHash + "." + enc(queryHash) + "." + pathHash; } diff --git a/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java b/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java index 42dc52c8c..594f38b98 100644 --- a/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java +++ b/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java @@ -1,7 +1,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 +30,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 +106,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/ebean/config/AutoTuneConfig.java b/src/main/java/com/avaje/ebean/config/AutoTuneConfig.java new file mode 100644 index 000000000..6eab25e8c --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/AutoTuneConfig.java @@ -0,0 +1,218 @@ +package com.avaje.ebean.config; + +/** + * Defines the AutoTune behaviour for a EbeanServer. + */ +public class AutoTuneConfig { + + private AutoTuneMode mode = AutoTuneMode.DEFAULT_ON; + + private String queryTuningFile = "ebean-autotune.xml"; + + private boolean queryTuning; + + private boolean queryTuningAddVersion; + + private boolean profiling; + + private String profilingFile = "ebean-profiling"; + + private int profilingBase = 5; + + private double profilingRate = 0.01; + + private int garbageCollectionWait = 100; + + private boolean skipCollectionOnShutdown; + + public AutoTuneConfig() { + } + + /** + * Return the name of the file that holds the query tuning information. + */ + public String getQueryTuningFile() { + return queryTuningFile; + } + + /** + * Set the name of the file that holds the query tuning information. + */ + public void setQueryTuningFile(String queryTuningFile) { + this.queryTuningFile = queryTuningFile; + } + + /** + * Return the name of the file that profiling information is written to. + */ + public String getProfilingFile() { + return profilingFile; + } + + /** + * Set the name of the file that profiling information is written to. + */ + public void setProfilingFile(String profilingFile) { + this.profilingFile = profilingFile; + } + + /** + * Return the mode used when autoTune has not been explicit defined on a + * query. + */ + public AutoTuneMode getMode() { + return mode; + } + + /** + * Set the mode used when autoTune has not been explicit defined on a query. + */ + public void setMode(AutoTuneMode mode) { + this.mode = mode; + } + + /** + * Return true if the queries are being tuned. + */ + public boolean isQueryTuning() { + return queryTuning; + } + + /** + * Set to true if the queries should be tuned by autofetch. + */ + public void setQueryTuning(boolean queryTuning) { + this.queryTuning = queryTuning; + } + + /** + * Return true if the version property should be added when the query is + * tuned. + *

+ * If this is false then the version property will be added when profiling + * detects that the bean is possibly going to be modified. + *

+ */ + public boolean isQueryTuningAddVersion() { + return queryTuningAddVersion; + } + + /** + * Set to true to force the version property to be always added by the query + * tuning. + *

+ * If this is false then the version property will be added when profiling + * detects that the bean is possibly going to be modified. + *

+ *

+ * Generally this is not expected to be turned on. + *

+ */ + public void setQueryTuningAddVersion(boolean queryTuningAddVersion) { + this.queryTuningAddVersion = queryTuningAddVersion; + } + + /** + * Return true if profiling information should be collected. + */ + public boolean isProfiling() { + return profiling; + } + + /** + * Set to true if profiling information should be collected. + *

+ * The profiling information is collected and then used to generate the tuned + * queries for autofetch. + *

+ */ + public void setProfiling(boolean profiling) { + this.profiling = profiling; + } + + /** + * Return the base number of queries to profile before changing to profile + * only a percentage of following queries (profileRate). + */ + public int getProfilingBase() { + return profilingBase; + } + + /** + * Set the based number of queries to profile. + */ + public void setProfilingBase(int profilingBase) { + this.profilingBase = profilingBase; + } + + /** + * Return the rate (%) of queries to be profiled after the 'base' amount of + * profiling. + */ + public double getProfilingRate() { + return profilingRate; + } + + /** + * Set the rate (%) of queries to be profiled after the 'base' amount of + * profiling. + */ + public void setProfilingRate(double profilingRate) { + this.profilingRate = profilingRate; + } + + /** + * Return the time in millis to wait after a system gc to collect profiling + * information. + *

+ * The profiling information is collected on object finalise. As such we + * generally don't want to trigger GC (let the JVM do its thing) but on + * shutdown the autoTune manager will trigger System.gc() and then wait + * (default 100 millis) to hopefully collect profiling information - + * especially for short run unit tests. + *

+ */ + public int getGarbageCollectionWait() { + return garbageCollectionWait; + } + + /** + * Set the time in millis to wait after a System.gc() to collect profiling information. + */ + public void setGarbageCollectionWait(int garbageCollectionWait) { + this.garbageCollectionWait = garbageCollectionWait; + } + + /** + * Return true if profiling collection should be skipped on shutdown. + */ + public boolean isSkipCollectionOnShutdown() { + return skipCollectionOnShutdown; + } + + /** + * Set to true if profiling collection should be skipped on shutdown. + */ + public void setSkipCollectionOnShutdown(boolean skipCollectionOnShutdown) { + this.skipCollectionOnShutdown = skipCollectionOnShutdown; + } + + /** + * Load the settings from the properties file. + */ + public void loadSettings(PropertiesWrapper p) { + + queryTuning = p.getBoolean("autoTune.queryTuning", queryTuning); + queryTuningAddVersion = p.getBoolean("autoTune.queryTuningAddVersion", queryTuningAddVersion); + queryTuningFile = p.get("autoTune.queryTuningFile", queryTuningFile); + + skipCollectionOnShutdown = p.getBoolean("autoTune.skipCollectionOnShutdown", skipCollectionOnShutdown); + + mode = p.getEnum(AutoTuneMode.class, "autoTune.mode", mode); + + profiling = p.getBoolean("autoTune.profiling", profiling); + profilingBase = p.getInt("autoTune.profilingBase", profilingBase); + profilingRate = p.getDouble("autoTune.profilingRate", profilingRate); + profilingFile = p.get("autoTune.profilingFile", profilingFile); + } +} diff --git a/src/main/java/com/avaje/ebean/config/AutofetchMode.java b/src/main/java/com/avaje/ebean/config/AutoTuneMode.java similarity index 96% rename from src/main/java/com/avaje/ebean/config/AutofetchMode.java rename to src/main/java/com/avaje/ebean/config/AutoTuneMode.java index 86dcf95db..b255421ff 100644 --- a/src/main/java/com/avaje/ebean/config/AutofetchMode.java +++ b/src/main/java/com/avaje/ebean/config/AutoTuneMode.java @@ -11,7 +11,7 @@ import com.avaje.ebean.Query; * query. *

*/ -public enum AutofetchMode { +public enum AutoTuneMode { /** * Don't implicitly use Autofetch. Must explicitly turn it on. diff --git a/src/main/java/com/avaje/ebean/config/AutofetchConfig.java b/src/main/java/com/avaje/ebean/config/AutofetchConfig.java deleted file mode 100644 index 02dbdda38..000000000 --- a/src/main/java/com/avaje/ebean/config/AutofetchConfig.java +++ /dev/null @@ -1,245 +0,0 @@ -package com.avaje.ebean.config; - -/** - * Defines the Autofetch behaviour for a EbeanServer. - */ -public class AutofetchConfig { - - private AutofetchMode mode = AutofetchMode.DEFAULT_ONIFEMPTY; - - private boolean queryTuning = false; - - private boolean queryTuningAddVersion = false; - - private boolean profiling = false; - - private int profilingMin = 1; - - private int profilingBase = 10; - - private double profilingRate = 0.05; - - private String logDirectory; - - private int profileUpdateFrequency = 60; - - private int garbageCollectionWait = 100; - - private boolean garbageCollectionOnShutdown; - - public AutofetchConfig() { - } - - /** - * Return the mode used when autofetch has not been explicit defined on a - * query. - */ - public AutofetchMode getMode() { - return mode; - } - - /** - * Set the mode used when autofetch has not been explicit defined on a query. - */ - public void setMode(AutofetchMode mode) { - this.mode = mode; - } - - /** - * Return true if the queries are being tuned. - */ - public boolean isQueryTuning() { - return queryTuning; - } - - /** - * Set to true if the queries should be tuned by autofetch. - */ - public void setQueryTuning(boolean queryTuning) { - this.queryTuning = queryTuning; - } - - /** - * Return true if the version property should be added when the query is - * tuned. - *

- * If this is false then the version property will be added when profiling - * detects that the bean is possibly going to be modified. - *

- */ - public boolean isQueryTuningAddVersion() { - return queryTuningAddVersion; - } - - /** - * Set to true to force the version property to be always added by the query - * tuning. - *

- * If this is false then the version property will be added when profiling - * detects that the bean is possibly going to be modified. - *

- */ - public void setQueryTuningAddVersion(boolean queryTuningAddVersion) { - this.queryTuningAddVersion = queryTuningAddVersion; - } - - /** - * Return true if profiling information should be collected. - */ - public boolean isProfiling() { - return profiling; - } - - /** - * Set to true if profiling information should be collected. - *

- * The profiling information is collected and then used to generate the tuned - * queries for autofetch. - *

- */ - public void setProfiling(boolean profiling) { - this.profiling = profiling; - } - - /** - * Return the minimum number of queries to profile before autofetch will start - * tuning the queries. - */ - public int getProfilingMin() { - return profilingMin; - } - - /** - * Set the minimum number of queries to profile before autofetch will start - * tuning the queries. - */ - public void setProfilingMin(int profilingMin) { - this.profilingMin = profilingMin; - } - - /** - * Return the base number of queries to profile before changing to profile - * only a percentage of following queries (profileRate). - */ - public int getProfilingBase() { - return profilingBase; - } - - /** - * Set the based number of queries to profile. - */ - public void setProfilingBase(int profilingBase) { - this.profilingBase = profilingBase; - } - - /** - * Return the rate (%) of queries to be profiled after the 'base' amount of - * profiling. - */ - public double getProfilingRate() { - return profilingRate; - } - - /** - * Set the rate (%) of queries to be profiled after the 'base' amount of - * profiling. - */ - public void setProfilingRate(double profilingRate) { - this.profilingRate = profilingRate; - } - - /** - * Return the log directory to put the autofetch log. - */ - public String getLogDirectory() { - return logDirectory; - } - - /** - * Set the directory to put the autofetch log in. - */ - public void setLogDirectory(String logDirectory) { - this.logDirectory = logDirectory; - } - - /** - * Return the frequency in seconds to update the autofetch tuned queries from - * the profiled information. - */ - public int getProfileUpdateFrequency() { - return profileUpdateFrequency; - } - - /** - * Set the frequency in seconds to update the autofetch tuned queries from the - * profiled information. - */ - public void setProfileUpdateFrequency(int profileUpdateFrequency) { - this.profileUpdateFrequency = profileUpdateFrequency; - } - - /** - * Return the time in millis to wait after a system gc to collect profiling - * information. - *

- * The profiling information is collected on object finalise. As such we - * generally don't want to trigger GC (let the JVM do its thing) but on - * shutdown the autofetch manager will trigger System.gc() and then wait - * (default 100 millis) to hopefully collect profiling information - - * especially for short run unit tests. - *

- */ - public int getGarbageCollectionWait() { - return garbageCollectionWait; - } - - /** - * Set the time in millis to wait after a System.gc() to collect profiling - * information. - */ - public void setGarbageCollectionWait(int garbageCollectionWait) { - this.garbageCollectionWait = garbageCollectionWait; - } - - - /** - * Return true if GC should be trigger on shutdown. - *

- * Autofetch profiling information is collected as part of garbage collection. - *

- */ - public boolean isGarbageCollectionOnShutdown() { - return garbageCollectionOnShutdown; - } - - /** - * Set to true if you want GC to trigger on shutdown. - *

- * This would be done if you want to try and collect Autofetch profiling information - * on shutdown. - *

- */ - public void setGarbageCollectionOnShutdown(boolean garbageCollectionOnShutdown) { - this.garbageCollectionOnShutdown = garbageCollectionOnShutdown; - } - - /** - * Load the settings from the properties file. - */ - public void loadSettings(PropertiesWrapper p) { - - logDirectory = p.get("autofetch.logDirectory", logDirectory); - queryTuning = p.getBoolean("autofetch.querytuning", queryTuning); - queryTuningAddVersion = p.getBoolean("autofetch.queryTuningAddVersion", queryTuningAddVersion); - garbageCollectionOnShutdown = p.getBoolean("autofetch.garbageCollectionOnShutdown", garbageCollectionOnShutdown); - - profiling = p.getBoolean("autofetch.profiling", profiling); - mode = p.getEnum(AutofetchMode.class, "autofetch.implicitmode", mode); - - profilingMin = p.getInt("autofetch.profiling.min", profilingMin); - profilingBase = p.getInt("autofetch.profiling.base", profilingBase); - - profilingRate = p.getDouble("autofetch.profiling.rate", profilingRate); - profileUpdateFrequency = p.getInt("autofetch.profiling.updatefrequency", profileUpdateFrequency); - } -} diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index 7a89b00f2..927caa954 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -132,7 +132,7 @@ public class ServerConfig { /** * Config controlling the autofetch behaviour. */ - private AutofetchConfig autofetchConfig = new AutofetchConfig(); + private AutoTuneConfig autoTuneConfig = new AutoTuneConfig(); /** * The JSON format used for DateTime types. Default to millis. @@ -1144,17 +1144,17 @@ public class ServerConfig { } /** - * Return the configuration for the Autofetch feature. + * Return the configuration for AutoTune. */ - public AutofetchConfig getAutofetchConfig() { - return autofetchConfig; + public AutoTuneConfig getAutoTuneConfig() { + return autoTuneConfig; } /** - * Set the configuration for the Autofetch feature. + * Set the configuration for AutoTune. */ - public void setAutofetchConfig(AutofetchConfig autofetchConfig) { - this.autofetchConfig = autofetchConfig; + public void setAutoTuneConfig(AutoTuneConfig autoTuneConfig) { + this.autoTuneConfig = autoTuneConfig; } /** @@ -2042,8 +2042,8 @@ public class ServerConfig { /** * This is broken out for the same reason as above - preserve existing behaviour but let it be overridden. */ - protected void loadAutofetchSettings(PropertiesWrapper p) { - autofetchConfig.loadSettings(p); + protected void loadAutoTuneSettings(PropertiesWrapper p) { + autoTuneConfig.loadSettings(p); } /** @@ -2057,10 +2057,10 @@ public class ServerConfig { if (namingConvention != null) { namingConvention.loadFromProperties(p); } - if (autofetchConfig == null) { - autofetchConfig = new AutofetchConfig(); + if (autoTuneConfig == null) { + autoTuneConfig = new AutoTuneConfig(); } - loadAutofetchSettings(p); + loadAutoTuneSettings(p); if (dataSourceConfig == null) { dataSourceConfig = new DataSourceConfig(); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index f987355ec..fc85650f9 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -13,7 +13,6 @@ import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebean.event.readaudit.ReadAuditLogger; import com.avaje.ebean.event.readaudit.ReadAuditPrepare; -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; @@ -72,11 +71,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 a1b52d7bc..ea9f0fccb 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -11,7 +11,7 @@ import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebean.event.readaudit.ReadEvent; -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; @@ -371,16 +371,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/AutoFetchManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java deleted file mode 100644 index b19a4ee37..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.server.resource.ResourceManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.PersistenceException; -import java.io.File; -import java.io.FileInputStream; -import java.io.ObjectInputStream; - -public class AutoFetchManagerFactory { - - private static final Logger logger = LoggerFactory.getLogger(AutoFetchManagerFactory.class); - - public static AutoFetchManager create(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { - - AutoFetchManagerFactory me = new AutoFetchManagerFactory(); - return me.createAutoFetchManager(server, serverConfig, resourceManager); - } - - private AutoFetchManager createAutoFetchManager(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { - - AutoFetchManager manager = createAutoFetchManager(server.getName(), resourceManager); - manager.setOwner(server, serverConfig); - - return manager; - } - - private AutoFetchManager createAutoFetchManager(String serverName, ResourceManager resourceManager) { - - File autoFetchFile = getAutoFetchFile(serverName, resourceManager); - - AutoFetchManager autoFetchManager = null; - - boolean readFile = !"false".equalsIgnoreCase(System.getProperty("autofetch.readfromfile")); - if (readFile) { - autoFetchManager = deserializeAutoFetch(autoFetchFile); - } - - if (autoFetchManager == null) { - // not deserialized from file so create as empty - // It will be populated automatically by querying the - // database meta data - autoFetchManager = new DefaultAutoFetchManager(autoFetchFile.getAbsolutePath()); - } - - return autoFetchManager; - } - - private AutoFetchManager deserializeAutoFetch(File autoFetchFile) { - try { - - if (!autoFetchFile.exists()) { - return null; - } - FileInputStream fi = new FileInputStream(autoFetchFile); - ObjectInputStream ois = new ObjectInputStream(fi); - AutoFetchManager profListener = (AutoFetchManager) ois.readObject(); - ois.close(); - - logger.info("AutoFetch deserialized from file [" + autoFetchFile.getAbsolutePath() + "]"); - - return profListener; - - } catch (Exception ex) { - logger.error("Error loading autofetch file " + autoFetchFile.getAbsolutePath(), ex); - return null; - } - } - - /** - * Return the file name of the autoFetch meta data. - */ - private File getAutoFetchFile(String serverName, ResourceManager resourceManager) { - - String fileName = ".ebean." + serverName + ".autofetch"; - - File dir = resourceManager.getAutofetchDirectory(); - - if (!dir.exists()) { - // automatically create the directory if it does not exist. - // this is probably a fairly reasonable thing to do - if (!dir.mkdirs()) { - String m = "Unable to create directory [" + dir + "] for autofetch file [" + fileName + "]"; - throw new PersistenceException(m); - } - } - - return new File(dir, fileName); - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java new file mode 100644 index 000000000..98285d9d3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java @@ -0,0 +1,115 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +import java.util.ArrayList; +import java.util.List; + +/** + * Profiling information collected. + */ +public class AutoTuneCollection { + + List entries = new ArrayList(); + + public Entry add(ObjectGraphOrigin origin, OrmQueryDetail detail, String sourceQuery) { + Entry entry = new Entry(origin, detail, sourceQuery); + entries.add(entry); + return entry; + } + + public List getEntries() { + return entries; + } + + /** + * Profiling entry at a given origin point. + */ + public static class Entry { + + /** + * Profiling origin point. + */ + private final ObjectGraphOrigin origin; + + /** + * The tuned query detail. + */ + private final OrmQueryDetail detail; + + /** + * The original/existing query detail. + */ + private final String originalQuery; + + /** + * Summary execution statistics for queries related to this origin point. + */ + private final List queries = new ArrayList(); + + public Entry(ObjectGraphOrigin origin, OrmQueryDetail detail, String originalQuery) { + this.origin = origin; + this.detail = detail; + this.originalQuery = originalQuery; + } + + public void addQuery(EntryQuery entryQuery) { + queries.add(entryQuery); + } + + public ObjectGraphOrigin getOrigin() { + return origin; + } + + public OrmQueryDetail getDetail() { + return detail; + } + + public String getOriginalQuery() { + return originalQuery; + } + + public List getQueries() { + return queries; + } + + } + + /** + * Summary query execution statistics for the origin point. + */ + public static class EntryQuery { + + final String path; + final long exeCount; + final long totalBeanLoaded; + final long totalMicros; + + public EntryQuery(String path, long exeCount, long totalBeanLoaded, long totalMicros) { + this.path = path; + this.exeCount = exeCount; + this.totalBeanLoaded = totalBeanLoaded; + this.totalMicros = totalMicros; + } + + /** + * Return the relative path with empty string for the origin query. + */ + public String getPath() { + return path; + } + + public long getExeCount() { + return exeCount; + } + + public long getTotalBeanLoaded() { + return totalBeanLoaded; + } + + public long getTotalMicros() { + return totalMicros; + } + } +} 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..2e8258697 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneService.java @@ -0,0 +1,52 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import com.avaje.ebean.AutoTune; +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 AutoTune { + + /** + * 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 collectProfiling(); + + /** + * 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/AutoTuneStorage.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneStorage.java new file mode 100644 index 000000000..831cdeee2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneStorage.java @@ -0,0 +1,17 @@ +package com.avaje.ebeaninternal.server.autofetch; + +/** + * + */ +public interface AutoTuneStorage { + + /** + * Load and return the tuning information. + */ + AutoTuneCollection load(); + + /** + * Store the collected profiling information. + */ + void store(AutoTuneCollection profiling); +} 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/DefaultAutoFetchManagerLogging.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java deleted file mode 100644 index f1cbb183b..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.TimeUnit; - -/** - * Handles the logging aspects for the DefaultAutoFetchListener. - *

- * Note that java util logging loggers generally should not be serialised and - * that is one of the main reasons for pulling out the logging to this class. - *

- */ -public class DefaultAutoFetchManagerLogging { - - private static final Logger logger = LoggerFactory.getLogger(DefaultAutoFetchManagerLogging.class); - - private final DefaultAutoFetchManager manager; - - private final int updateFreqInSecs; - - public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, DefaultAutoFetchManager profileListener) { - - this.manager = profileListener; - this.updateFreqInSecs = serverConfig.getAutofetchConfig().getProfileUpdateFrequency(); - } - - public void init(SpiEbeanServer ebeanServer) { - ebeanServer.getBackgroundExecutor().executePeriodically(new UpdateProfile(), updateFreqInSecs, TimeUnit.SECONDS); - } - - private final class UpdateProfile implements Runnable { - public void run() { - manager.updateTunedQueryInfo(); - } - } - - public void logInfo(String msg, Throwable e) { - logger.info(msg, e); - } - - public void logError(String msg, Throwable e) { - logger.error(msg, e); - } - - public void logSummary(String summaryInfo) { - - String msg = "\"Summary\",\"" + summaryInfo + "\",,,,"; - logger.debug(msg); - } - - public void logChanged(TunedQueryInfo tunedFetch, OrmQueryDetail newQueryDetail) { - - String msg = tunedFetch.getLogOutput(newQueryDetail); - logger.debug(msg); - } - - public void logNew(TunedQueryInfo tunedFetch) { - - String msg = tunedFetch.getLogOutput(null); - logger.debug(msg); - } -} 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..f24300b91 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/ProfilingListener.java @@ -0,0 +1,26 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import com.avaje.ebean.bean.NodeUsageListener; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Profiling listener gets call backs for node usage and the associated query executions. + */ +public interface ProfilingListener extends NodeUsageListener { + + /** + * Collect summary statistics for a query executed for the given node. + * + * @param node the node relative to the origin point + * @param beans the number of beans loaded by the query + * @param micros the query execution in microseconds + */ + void collectQueryInfo(ObjectGraphNode node, long beans, long micros); + + /** + * Return true if this request should be profiled (based on the + * profiling ratio and collection count for this origin). + */ + boolean isProfileRequest(ObjectGraphNode origin, SpiQuery query); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java deleted file mode 100644 index a9625ca70..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import com.avaje.ebean.bean.NodeUsageCollector; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.bean.ObjectGraphOrigin; -import com.avaje.ebean.text.PathProperties; -import com.avaje.ebean.text.PathProperties.Props; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; - -import java.io.Serializable; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -public class Statistics implements Serializable { - - - private static final long serialVersionUID = -5586783791097230766L; - - private final ObjectGraphOrigin origin; - - private final boolean queryTuningAddVersion; - - private int counter; - - private final Map queryStatsMap = new LinkedHashMap(); - - private final Map nodeUsageMap = new LinkedHashMap(); - - @SuppressWarnings("RedundantStringConstructorCall") - private final String monitor = new String(); - - public Statistics(ObjectGraphOrigin origin, boolean queryTuningAddVersion) { - this.origin = origin; - this.queryTuningAddVersion = queryTuningAddVersion; - } - - public ObjectGraphOrigin getOrigin() { - 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); - } - } - - /** - * Return the number of times the root query has executed. - *

- * This tells us how much profiling we have done for this query. - * For example, after 100 times we may stop collecting more profiling info. - *

- */ - public int getCounter() { - return counter; - } - - /** - * Return true if this has usage statistics. - */ - public boolean hasUsage() { - synchronized (monitor) { - return !nodeUsageMap.isEmpty(); - } - } - - public OrmQueryDetail buildTunedFetch(BeanDescriptor rootDesc) { - - synchronized (monitor) { - if (nodeUsageMap.isEmpty()) { - return null; - } - - PathProperties pathProps = new PathProperties(); - - for (StatisticsNodeUsage statsNode : nodeUsageMap.values()) { - statsNode.buildTunedFetch(pathProps, rootDesc); - } - - OrmQueryDetail detail = new OrmQueryDetail(); - - Collection pathProperties = pathProps.getPathProps(); - for (Props props : pathProperties) { - if (!props.isEmpty()) { - detail.addFetch(props.getPath(), props.getPropertiesAsString(), null); - } - } - - detail.sortFetchPaths(rootDesc); - return detail; - } - } - - - 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); - } - } - - - /** - * Collect the usage information for from a instance for this node. - */ - public void collectUsageInfo(NodeUsageCollector profile) { - - if (!profile.isEmpty()) { - ObjectGraphNode node = profile.getNode(); - - StatisticsNodeUsage nodeStats = getNodeStats(node.getPath()); - nodeStats.publish(profile); - } - } - - private StatisticsNodeUsage getNodeStats(String path) { - - synchronized (monitor) { - StatisticsNodeUsage nodeStats = nodeUsageMap.get(path); - if (nodeStats == null) { - nodeStats = new StatisticsNodeUsage(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(); - } - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java deleted file mode 100644 index 506087b31..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import java.io.Serializable; - -/** - * Used to accumulate query execution statistics. - */ -public class StatisticsQuery implements Serializable { - - private static final long serialVersionUID = -1133958958072778811L; - - private final String path; - - private long exeCount; - - private long totalBeanLoaded; - - private long totalMicros; - - public StatisticsQuery(String path) { - this.path = path; - } - - public void add(long beansLoaded, long micros) { - exeCount++; - totalBeanLoaded += beansLoaded; - totalMicros += micros; - } - - public String toString() { - long avgMicros = exeCount == 0 ? 0 : totalMicros / exeCount; - - return "queryExe path[" + path + "] count[" + exeCount + "] totalBeansLoaded[" + totalBeanLoaded + "] avgMicros[" - + avgMicros + "] totalMicros[" + totalMicros + "]"; - } -} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java deleted file mode 100644 index dec85ebee..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.avaje.ebeaninternal.server.autofetch; - -import com.avaje.ebean.bean.ObjectGraphOrigin; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; - -import java.io.Serializable; - -/** - * Holds tuned query information. Is immutable so this represents the tuning at - * a given point in time. - */ -public class TunedQueryInfo implements Serializable { - - private static final long serialVersionUID = 7381493228797997282L; - - private final ObjectGraphOrigin origin; - - /** - * The tuned query details with joins and properties. - */ - private OrmQueryDetail tunedDetail; - - /** - * The number of times profiling has been collected for this query point. - */ - private int profileCount; - - private Long lastTuneTime = (long) 0; - - @SuppressWarnings("RedundantStringConstructorCall") - private final String rateMonitor = new String(); - - /** - * The number of queries tuned by this object. - * Could use AtomicInteger perhaps. - */ - private transient int tunedCount; - - private transient int rateTotal; - - private transient int rateHits; - - private transient double lastRate; - - public TunedQueryInfo(ObjectGraphOrigin queryPoint, OrmQueryDetail tunedDetail, int profileCount) { - this.origin = queryPoint; - this.tunedDetail = tunedDetail; - this.profileCount = profileCount; - } - - /** - * Return true if this query should be profiled based on a percentage rate. - */ - public boolean isPercentageProfile(double rate) { - - synchronized (rateMonitor) { - - if (lastRate != rate) { - // the rate has changed so resetting - lastRate = rate; - rateTotal = 0; - rateHits = 0; - } - - rateTotal++; - if (rate > (double) rateHits / rateTotal) { - rateHits++; - return true; - } else { - return false; - } - } - } - - /** - * Set the number of times profiling has been collected for this query - * point. - */ - public void setProfileCount(int profileCount) { - // int assignment is atomic - this.profileCount = profileCount; - } - - /** - * Set the tuned query detail. - */ - public void setTunedDetail(OrmQueryDetail tunedDetail) { - // assignment is atomic - this.tunedDetail = tunedDetail; - this.lastTuneTime = System.currentTimeMillis(); - } - - /** - * Return true if the fetches are essentially the same. - */ - public boolean isSame(OrmQueryDetail newQueryDetail) { - return tunedDetail != null && tunedDetail.isAutoFetchEqual(newQueryDetail); - } - - /** - * Tune the query by replacing its OrmQueryDetail with a tuned one. - * - * @return true if the query was tuned, otherwise false. - */ - public boolean autoFetchTune(SpiQuery query) { - if (tunedDetail == null) { - return false; - } - - boolean tuned; - //Note: tunedDetail is immutable by convention - if (query.isDetailEmpty()) { - tuned = true; - // tune by 'replacement' - query.setDetail(tunedDetail.copy()); - } else { - // tune by 'addition' - tuned = query.tuneFetchProperties(tunedDetail); - } - if (tuned) { - query.setAutoFetchTuned(true); - // a case for AtomicInteger but good enough for statistics - tunedCount++; - } - return tuned; - } - - /** - * Return the time of the last tune. - */ - public Long getLastTuneTime() { - return lastTuneTime; - } - - /** - * Return the number of queries tuned by this object. - */ - public int getTunedCount() { - return tunedCount; - } - - /** - * Return the number of times profiling has been collected for this query - * point. - */ - public int getProfileCount() { - return profileCount; - } - - public OrmQueryDetail getTunedDetail() { - return tunedDetail; - } - - public ObjectGraphOrigin getOrigin() { - return origin; - } - - public String getLogOutput(OrmQueryDetail newQueryDetail) { - - boolean changed = newQueryDetail != null; - - StringBuilder sb = new StringBuilder(150); - sb.append(changed ? "\"Changed\"," : "\"New\","); - sb.append("\"").append(origin.getBeanType()).append("\","); - sb.append("\"").append(origin.getKey()).append("\","); - if (changed) { - sb.append("\"to: ").append(newQueryDetail.toString()).append("\","); - sb.append("\"from: ").append(tunedDetail.toString()).append("\","); - } else { - sb.append("\"to: ").append(tunedDetail.toString()).append("\","); - sb.append("\"\","); - } - sb.append("\"").append(origin.getFirstStackElement()).append("\""); - - return sb.toString(); - } - - public String toString() { - return origin.getBeanType() + " " + origin.getKey() + " " + tunedDetail; - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Autotune.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Autotune.java new file mode 100644 index 000000000..4be03eb68 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Autotune.java @@ -0,0 +1,123 @@ + +package com.avaje.ebeaninternal.server.autofetch.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileDiff" minOccurs="0"/>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileNew" minOccurs="0"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "origin", + "profileDiff", + "profileNew" +}) +@XmlRootElement(name = "autotune") +public class Autotune { + + protected List origin; + protected ProfileDiff profileDiff; + protected ProfileNew profileNew; + + /** + * Gets the value of the origin property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the origin property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getOrigin().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Origin } + * + * + */ + public List getOrigin() { + if (origin == null) { + origin = new ArrayList(); + } + return this.origin; + } + + /** + * Gets the value of the profileDiff property. + * + * @return + * possible object is + * {@link ProfileDiff } + * + */ + public ProfileDiff getProfileDiff() { + return profileDiff; + } + + /** + * Sets the value of the profileDiff property. + * + * @param value + * allowed object is + * {@link ProfileDiff } + * + */ + public void setProfileDiff(ProfileDiff value) { + this.profileDiff = value; + } + + /** + * Gets the value of the profileNew property. + * + * @return + * possible object is + * {@link ProfileNew } + * + */ + public ProfileNew getProfileNew() { + return profileNew; + } + + /** + * Sets the value of the profileNew property. + * + * @param value + * allowed object is + * {@link ProfileNew } + * + */ + public void setProfileNew(ProfileNew value) { + this.profileNew = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ObjectFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ObjectFactory.java new file mode 100644 index 000000000..a6fc83ab8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ObjectFactory.java @@ -0,0 +1,64 @@ + +package com.avaje.ebeaninternal.server.autofetch.model; + +import javax.xml.bind.annotation.XmlRegistry; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the com.avaje.ebeaninternal.server.autofetch.model package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.server.autofetch.model + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link ProfileNew } + * + */ + public ProfileNew createProfileNew() { + return new ProfileNew(); + } + + /** + * Create an instance of {@link Origin } + * + */ + public Origin createOrigin() { + return new Origin(); + } + + /** + * Create an instance of {@link Autotune } + * + */ + public Autotune createAutotune() { + return new Autotune(); + } + + /** + * Create an instance of {@link ProfileDiff } + * + */ + public ProfileDiff createProfileDiff() { + return new ProfileDiff(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java new file mode 100644 index 000000000..eb2203c92 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java @@ -0,0 +1,171 @@ + +package com.avaje.ebeaninternal.server.autofetch.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *       </sequence>
+ *       <attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *       <attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *       <attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *       <attribute name="original" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "callStack" +}) +@XmlRootElement(name = "origin") +public class Origin { + + protected String callStack; + @XmlAttribute(name = "key", required = true) + protected String key; + @XmlAttribute(name = "beanType") + protected String beanType; + @XmlAttribute(name = "detail") + protected String detail; + @XmlAttribute(name = "original") + protected String original; + + /** + * Gets the value of the callStack property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getCallStack() { + return callStack; + } + + /** + * Sets the value of the callStack property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setCallStack(String value) { + this.callStack = value; + } + + /** + * Gets the value of the key property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getKey() { + return key; + } + + /** + * Sets the value of the key property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setKey(String value) { + this.key = value; + } + + /** + * Gets the value of the beanType property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getBeanType() { + return beanType; + } + + /** + * Sets the value of the beanType property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setBeanType(String value) { + this.beanType = value; + } + + /** + * Gets the value of the detail property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getDetail() { + return detail; + } + + /** + * Sets the value of the detail property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setDetail(String value) { + this.detail = value; + } + + /** + * Gets the value of the original property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getOriginal() { + return original; + } + + /** + * Sets the value of the original property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setOriginal(String value) { + this.original = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileDiff.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileDiff.java new file mode 100644 index 000000000..a1bc1c91b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileDiff.java @@ -0,0 +1,69 @@ + +package com.avaje.ebeaninternal.server.autofetch.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "origin" +}) +@XmlRootElement(name = "profileDiff") +public class ProfileDiff { + + protected List origin; + + /** + * Gets the value of the origin property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the origin property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getOrigin().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Origin } + * + * + */ + public List getOrigin() { + if (origin == null) { + origin = new ArrayList(); + } + return this.origin; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileNew.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileNew.java new file mode 100644 index 000000000..0a058fb29 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileNew.java @@ -0,0 +1,69 @@ + +package com.avaje.ebeaninternal.server.autofetch.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "origin" +}) +@XmlRootElement(name = "profileNew") +public class ProfileNew { + + protected List origin; + + /** + * Gets the value of the origin property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the origin property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getOrigin().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Origin } + * + * + */ + public List getOrigin() { + if (origin == null) { + origin = new ArrayList(); + } + return this.origin; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/package-info.java new file mode 100644 index 000000000..7204153e4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/package-info.java @@ -0,0 +1,2 @@ +@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/autotune", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) +package com.avaje.ebeaninternal.server.autofetch.model; 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/service/AutoTuneServiceFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneServiceFactory.java new file mode 100644 index 000000000..f730a3b31 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneServiceFactory.java @@ -0,0 +1,14 @@ +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; + +public class AutoTuneServiceFactory { + + public static AutoTuneService create(SpiEbeanServer server, ServerConfig serverConfig) { + + return new DefaultAutoTuneService(server, serverConfig); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlReader.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlReader.java new file mode 100644 index 000000000..8ff62af16 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlReader.java @@ -0,0 +1,57 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + + +import com.avaje.ebeaninternal.server.autofetch.model.Autotune; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Reads a profiling xml document. + */ +public class AutoTuneXmlReader { + + /** + * Read and return a Profiling from an xml file. + */ + public Autotune read(File file) { + + try { + return readFile(file); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + protected Autotune readFile(File file) throws IOException { + if (!file.exists()) { + return new Autotune(); + } + FileInputStream is = new FileInputStream(file); + try { + return read(is); + } finally { + is.close(); + } + } + + /** + * Read and return a Profiling from an xml document. + */ + public Autotune read(InputStream is) { + + try { + JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + return (Autotune) unmarshaller.unmarshal(is); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlWriter.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlWriter.java new file mode 100644 index 000000000..7770bafb1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlWriter.java @@ -0,0 +1,32 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + + +import com.avaje.ebeaninternal.server.autofetch.model.Autotune; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import java.io.File; + +/** + * Simple writer for output of the AutoTune Profiling as an XML document. + */ +public class AutoTuneXmlWriter { + + /** + * Write Profiling to a file as xml. + */ + public void write(Autotune profiling, File file) { + + try { + JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class); + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.marshal(profiling, file); + + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + +} 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..f3614d4f8 --- /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.AutoTuneConfig; +import com.avaje.ebean.config.AutoTuneMode; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +import javax.persistence.PersistenceException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + */ +public class BaseQueryTuner { + + private final boolean queryTuning; + + private boolean profiling; + + private final AutoTuneMode 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; + + boolean fullProfiling = true; + + public BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) { + this.server = server; + this.profilingListener = profilingListener; + this.mode = config.getMode(); + this.queryTuning = config.isQueryTuning(); + this.profiling = config.isProfiling(); + } + + /** + * Load the tuned query information. + */ + public void load(String key, TunedQueryInfo queryInfo) { + tunedQueryInfoMap.put(key, queryInfo); + } + + /** + * Return the detail currently used for tuning. + * This returns null if there is currently no matching tuning. + */ + public OrmQueryDetail get(String key) { + TunedQueryInfo info = tunedQueryInfoMap.get(key); + return (info == null) ? null : info.getTunedDetail(); + } + + /** + * Auto tune the query and enable profiling. + */ + public boolean tuneQuery(SpiQuery query) { + + if (!queryTuning && !profiling) { + return false; + } + + if (!useAutoTune(query)) { + // not tuning this query but maybe profiling + if (fullProfiling) { + CallStack stack = server.createCallStack(); + profiling(query, stack); + } + return false; + } + + if (query.getParentNode() != 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); + + if (profiling) { + if (profilingListener.isProfileRequest(origin, query)) { + // collect more profiling based on profiling rate etc + query.setProfilingListener(profilingListener); + } + } + + if (queryTuning) { + // get current "tuned fetch" for this query point + TunedQueryInfo tuneInfo = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey()); + return tuneInfo != null && tuneInfo.tuneQuery(query); + } + return false; + } + + private void profiling(SpiQuery query, CallStack stack) { + + // create a query point to identify the query + ObjectGraphNode origin = query.setOrigin(stack); + if (profilingListener.isProfileRequest(origin, query)) { + // collect more profiling based on profiling rate etc + query.setProfilingListener(profilingListener); + } + } + + /** + * Return true if we should try to tune this query. + */ + private boolean useAutoTune(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/service/DefaultAutoTuneService.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/DefaultAutoTuneService.java new file mode 100644 index 000000000..12600ad6d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/DefaultAutoTuneService.java @@ -0,0 +1,265 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.config.AutoTuneConfig; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneCollection; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneService; +import com.avaje.ebeaninternal.server.autofetch.model.Autotune; +import com.avaje.ebeaninternal.server.autofetch.model.Origin; +import com.avaje.ebeaninternal.server.autofetch.model.ProfileDiff; +import com.avaje.ebeaninternal.server.autofetch.model.ProfileNew; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetailParser; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Implementation of the AutoTuneService which is comprised of profiling and query tuning. + */ +public class DefaultAutoTuneService implements AutoTuneService { + + private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class); + + private final long defaultGarbageCollectionWait; + + private final boolean skipCollectionOnShutdown; + + private final BaseQueryTuner queryTuner; + + private final ProfileManager profileManager; + + private final boolean profiling; + + private final boolean queryTuning; + + private final String tuningFile; + + private final String profilingFile; + + private final String serverName; + + public DefaultAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) { + + AutoTuneConfig config = serverConfig.getAutoTuneConfig(); + + this.queryTuning = config.isQueryTuning(); + this.profiling = config.isProfiling(); + this.tuningFile = config.getQueryTuningFile(); + this.profilingFile = config.getProfilingFile(); + this.serverName = server.getName(); + this.profileManager = new ProfileManager(config, server); + this.queryTuner = new BaseQueryTuner(config, server, profileManager); + this.skipCollectionOnShutdown = config.isSkipCollectionOnShutdown(); + this.defaultGarbageCollectionWait = (long) config.getGarbageCollectionWait(); + } + + /** + * Load the query tuning information from it's data store. + */ + @Override + public void startup() { + + if (queryTuning) { + File file = new File(tuningFile); + if (!file.exists()) { + logger.warn("AutoTune file {} not found - no automatic tuning will be applied", file.getAbsolutePath()); + + } else { + AutoTuneXmlReader reader = new AutoTuneXmlReader(); + Autotune profiling = reader.read(file); + logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size()); + for (Origin origin : profiling.getOrigin()) { + queryTuner.load(origin.getKey(), createTunedQueryInfo(origin)); + } + } + } + } + + @NotNull + private TunedQueryInfo createTunedQueryInfo(Origin origin) { + OrmQueryDetail detail = new OrmQueryDetailParser(origin.getDetail()).parse(); + return new TunedQueryInfo(detail); + } + + private void saveProfiling(boolean reset) { + + Autotune document = new Autotune(); + + AutoTuneCollection autoTuneCollection = profileManager.profilingCollection(reset); + + List entries = autoTuneCollection.getEntries(); + + // count "new" and "diff" profiling entries + AtomicInteger newCounter = new AtomicInteger(); + AtomicInteger diffCounter = new AtomicInteger(); + + for (AutoTuneCollection.Entry entry : entries) { + saveProfilingEntry(document, entry, newCounter, diffCounter); + } + + int totalNew = newCounter.get(); + int totalDiff = diffCounter.get(); + if (totalNew == 0 && totalDiff == 0) { + logger.debug("No new or diff entries for profiling server:{}", serverName); + + } else { + sortDocument(document); + + SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss"); + String now = df.format(new Date()); + + // write the file with serverName and now suffix as we can output the profiling many times + File file = new File(profilingFile + "-" + serverName + "-" + now + ".xml"); + AutoTuneXmlWriter writer = new AutoTuneXmlWriter(); + writer.write(document, file); + + logger.debug("writing new:{} diff:{} profiling entries for server:{}", totalNew, totalDiff, serverName); + } + } + + /** + * Set the diff and new entries by bean type followed by key. + */ + private void sortDocument(Autotune document) { + + ProfileDiff profileDiff = document.getProfileDiff(); + if (profileDiff != null) { + Collections.sort(profileDiff.getOrigin(), new OriginNameKeySort()); + } + ProfileNew profileNew = document.getProfileNew(); + if (profileNew != null) { + Collections.sort(profileNew.getOrigin(), new OriginNameKeySort()); + } + } + + /** + * Comparator sort by bean type then key. + */ + class OriginNameKeySort implements Comparator { + + @Override + public int compare(Origin o1, Origin o2) { + int comp = o1.getBeanType().compareTo(o2.getBeanType()); + if (comp == 0) { + comp = o1.getKey().compareTo(o2.getKey()); + } + return comp; + } + } + + private void saveProfilingEntry(Autotune document, AutoTuneCollection.Entry entry, AtomicInteger newCount, AtomicInteger diffCount) { + + ObjectGraphOrigin point = entry.getOrigin(); + OrmQueryDetail profileDetail = entry.getDetail(); + + // compare with the existing query tuning entry + OrmQueryDetail tuneDetail = queryTuner.get(point.getKey()); + if (tuneDetail == null) { + // New entry + newCount.incrementAndGet(); + ProfileNew profileNew = document.getProfileNew(); + if (profileNew == null) { + profileNew = new ProfileNew(); + document.setProfileNew(profileNew); + } + Origin origin = createOrigin(entry, point); + origin.setOriginal(entry.getOriginalQuery()); + profileNew.getOrigin().add(origin); + + } else if (!tuneDetail.isAutoTuneEqual(profileDetail)) { + // Diff entry + diffCount.incrementAndGet(); + Origin origin = createOrigin(entry, point); + origin.setOriginal(tuneDetail.toString()); + ProfileDiff diff = document.getProfileDiff(); + if (diff == null) { + diff = new ProfileDiff(); + document.setProfileDiff(diff); + } + diff.getOrigin().add(origin); + } + } + + /** + * Create the XML Origin bean for the given entry and ObjectGraphOrigin. + */ + @NotNull + private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) { + Origin origin = new Origin(); + origin.setKey(point.getKey()); + origin.setBeanType(point.getBeanType()); + origin.setDetail(entry.getDetail().toString()); + origin.setCallStack(point.getCallStack().description("\n")); + return origin; + } + + /** + * 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. + *

+ */ + @Override + public void shutdown() { + if (profiling && !skipCollectionOnShutdown) { + collectProfiling(-1); + saveProfiling(false); + } + } + + /** + * 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. + *

+ */ + @Override + public void collectProfiling() { + collectProfiling(-1); + } + + public void collectProfiling(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); + } + } + + /** + * Auto tune the query and enable profiling. + */ + @Override + public boolean tuneQuery(SpiQuery query) { + return queryTuner.tuneQuery(query); + } + +} 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..4ef3b464a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileManager.java @@ -0,0 +1,123 @@ +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.AutoTuneConfig; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneCollection; +import com.avaje.ebeaninternal.server.autofetch.ProfilingListener; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +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; + + /** + * 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; + + /** + * 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(AutoTuneConfig config, SpiEbeanServer server) { + this.server = server; + this.profilingRate = config.getProfilingRate(); + this.profilingBase = config.getProfilingBase(); + this.queryTuningAddVersion = config.isQueryTuningAddVersion(); + } + + @Override + public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery query) { + + ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey()); + if (profileOrigin == null) { + profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate); + profileOrigin.setOriginalQuery(query.getDetail().toString()); + profileMap.put(origin.getOriginQueryPoint().getKey(), profileOrigin); + return true; + } else { + return profileOrigin.isProfile(); + } + } + + /** + * 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, profilingBase, profilingRate); + profileMap.put(originQueryPoint.getKey(), stats); + } + return stats; + } + } + + /** + * Collect all the profiling information. + */ + public AutoTuneCollection profilingCollection(boolean reset) { + + AutoTuneCollection req = new AutoTuneCollection(); + + for (ProfileOrigin origin : profileMap.values()) { + + BeanDescriptor desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType()); + if (desc != null) { + origin.profilingCollection(desc, req, reset); + } + } + + return req; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java new file mode 100644 index 000000000..d83c163d9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java @@ -0,0 +1,179 @@ +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.text.PathProperties; +import com.avaje.ebean.text.PathProperties.Props; +import com.avaje.ebeaninternal.server.autofetch.AutoTuneCollection; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +public class ProfileOrigin { + + //private static final Logger logger = LoggerFactory.getLogger(ProfileOrigin.class); + + private static final long RESET_COUNT = -1000000000L; + + private final ObjectGraphOrigin origin; + + private final boolean queryTuningAddVersion; + + private final int profilingBase; + + private final double profilingRate; + + private final Map queryStatsMap = new ConcurrentHashMap(); + + private final Map nodeUsageMap = new ConcurrentHashMap(); + + private final Object monitor = new Object(); + + private final AtomicLong requestCount = new AtomicLong(); + + private final AtomicLong profileCount = new AtomicLong(); + + private String originalQuery; + + public ProfileOrigin(ObjectGraphOrigin origin, boolean queryTuningAddVersion, int profilingBase, double profilingRate) { + this.origin = origin; + this.queryTuningAddVersion = queryTuningAddVersion; + this.profilingBase = profilingBase; + this.profilingRate = profilingRate; + } + + public String getOriginalQuery() { + return originalQuery; + } + + public void setOriginalQuery(String originalQuery) { + this.originalQuery = originalQuery; + } + + /** + * Return true if this query should be profiled based on a percentage rate. + */ + public boolean isProfile() { + + long count = requestCount.incrementAndGet(); + if (count < profilingBase) { + return true; + } + long hits = profileCount.get(); + if (profilingRate > (double) hits / count) { + profileCount.incrementAndGet(); + return true; + } else { + return false; + } + } + + /** + * Collect profiling information with the option to reset the underlying profiling detail. + */ + public void profilingCollection(BeanDescriptor rootDesc, AutoTuneCollection req, boolean reset) { + + synchronized (monitor) { + if (nodeUsageMap.isEmpty()) { + return; + } + + OrmQueryDetail detail = buildDetail(rootDesc); + AutoTuneCollection.Entry entry = req.add(origin, detail, originalQuery); + + Collection values = queryStatsMap.values(); + for (ProfileOriginQuery queryEntry : values) { + entry.addQuery(queryEntry.createEntryQuery(reset)); + } + if (reset) { + nodeUsageMap.clear(); + if (requestCount.get() > RESET_COUNT) { + requestCount.set(profilingBase); + profileCount.set(0); + } + } + } + } + + private OrmQueryDetail buildDetail(BeanDescriptor rootDesc) { + + PathProperties pathProps = new PathProperties(); + for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) { + statsNode.buildTunedFetch(pathProps, rootDesc, queryTuningAddVersion); + } + + OrmQueryDetail detail = new OrmQueryDetail(); + + Collection pathProperties = pathProps.getPathProps(); + for (Props props : pathProperties) { + if (!props.isEmpty()) { + detail.addFetch(props.getPath(), props.getPropertiesAsString(), null); + } + } + + detail.sortFetchPaths(rootDesc); + return detail; + } + + /** + * Return the origin. + */ + public ObjectGraphOrigin getOrigin() { + return origin; + } + + /** + * Collect query execution summary statistics. + *

+ * This can give us a quick overview into bad lazy loading areas etc. + *

+ */ + public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) { + + String key = node.getPath(); + if (key == null) { + key = ""; + } + + 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); + } + + /** + * Collect the usage information for from a instance for this node. + */ + public void collectUsageInfo(NodeUsageCollector profile) { + + //logger.info("COLLECT USAGE {}", profile.toString()); + + if (!profile.isEmpty()) { + ProfileOriginNodeUsage nodeStats = getNodeStats(profile.getNode().getPath()); + nodeStats.collectUsageInfo(profile); + } + } + + private ProfileOriginNodeUsage getNodeStats(String path) { + + synchronized (monitor) { + // handle null paths as using ConcurrentHashMap + path = (path == null) ? "" : path; + ProfileOriginNodeUsage nodeStats = nodeUsageMap.get(path); + if (nodeStats == null) { + nodeStats = new ProfileOriginNodeUsage(path); + nodeUsageMap.put(path, nodeStats); + } + return nodeStats; + } + } + +} 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 71% 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..4ce069ae0 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; @@ -10,27 +10,20 @@ import com.avaje.ebeaninternal.server.query.SplitName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; /** * Collects usages statistics for a given node in the object graph. */ -public class StatisticsNodeUsage implements Serializable { +public class ProfileOriginNodeUsage { - private static final long serialVersionUID = -1663951463963779547L; + private static final Logger logger = LoggerFactory.getLogger(ProfileOriginNodeUsage.class); - private static final Logger logger = LoggerFactory.getLogger(StatisticsNodeUsage.class); - - @SuppressWarnings("RedundantStringConstructorCall") - private final String monitor = new String(); + private final Object monitor = new Object(); private final String path; - private final boolean queryTuningAddVersion; - private int profileCount; private int profileUsedCount; @@ -39,12 +32,12 @@ public class StatisticsNodeUsage implements Serializable { private final Set aggregateUsed = new LinkedHashSet(); - public StatisticsNodeUsage(String path, boolean queryTuningAddVersion) { - this.path = path; - this.queryTuningAddVersion = queryTuningAddVersion; + public ProfileOriginNodeUsage(String path) { + // handle null paths as using ConcurrentHashMap + this.path = "".equals(path) ? null : path; } - public void buildTunedFetch(PathProperties pathProps, BeanDescriptor rootDesc) { + protected void buildTunedFetch(PathProperties pathProps, BeanDescriptor rootDesc, boolean addVersionProperty) { synchronized (monitor) { @@ -53,7 +46,7 @@ public class StatisticsNodeUsage implements Serializable { ElPropertyValue elGetValue = rootDesc.getElGetValue(path); if (elGetValue == null) { desc = null; - logger.warn("Autofetch: Can't find join for path[" + path + "] for " + rootDesc.getName()); + logger.warn("AutoTune: Can't find join for path[" + path + "] for " + rootDesc.getName()); } else { BeanProperty beanProperty = elGetValue.getBeanProperty(); @@ -66,7 +59,7 @@ public class StatisticsNodeUsage implements Serializable { for (String propName : aggregateUsed) { BeanProperty beanProp = desc.getBeanPropertyFromPath(propName); if (beanProp == null) { - logger.warn("Autofetch: Can't find property[" + propName + "] for " + desc.getName()); + logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName()); } else { if (beanProp instanceof BeanPropertyAssoc) { @@ -86,7 +79,7 @@ public class StatisticsNodeUsage implements Serializable { } } - if ((modified || queryTuningAddVersion) && desc != null) { + if ((modified || addVersionProperty) && desc != null) { BeanProperty versionProp = desc.getVersionProperty(); if (versionProp != null) { pathProps.addToPath(path, versionProp.getName()); @@ -95,11 +88,14 @@ public class StatisticsNodeUsage implements Serializable { } } - public void publish(NodeUsageCollector profile) { + /** + * Collect usage from a node. + */ + protected void collectUsageInfo(NodeUsageCollector profile) { 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/service/ProfileOriginQuery.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginQuery.java new file mode 100644 index 000000000..344d040dc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginQuery.java @@ -0,0 +1,43 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + +import com.avaje.ebeaninternal.server.autofetch.AutoTuneCollection; +import com.avaje.ebeaninternal.server.util.LongAdder; + +import java.io.Serializable; + +/** + * Used to accumulate query execution statistics for paths relative to the origin query. + */ +public class ProfileOriginQuery implements Serializable { + + private static final long serialVersionUID = -1133958958072778811L; + + private final String path; + + private final LongAdder exeCount = new LongAdder(); + + private final LongAdder totalBeanLoaded = new LongAdder(); + + private final LongAdder totalMicros = new LongAdder(); + + public ProfileOriginQuery(String path) { + this.path = path; + } + + public void add(long beansLoaded, long micros) { + exeCount.increment(); + totalBeanLoaded.add(beansLoaded); + totalMicros.add(micros); + } + + public AutoTuneCollection.EntryQuery createEntryQuery(boolean reset){ + + if (reset) { + return new AutoTuneCollection.EntryQuery(path, exeCount.sumThenReset(), totalBeanLoaded.sumThenReset(), totalMicros.sumThenReset()); + + } else { + return new AutoTuneCollection.EntryQuery(path, exeCount.sum(), totalBeanLoaded.sum(), totalMicros.sum()); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java new file mode 100644 index 000000000..7f8a06853 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.autofetch.service; + +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +import java.io.Serializable; + +/** + * Holds tuned query information. Is immutable so this represents the tuning at + * a given point in time. + */ +public class TunedQueryInfo implements Serializable { + + private final OrmQueryDetail tunedDetail; + + public TunedQueryInfo(OrmQueryDetail tunedDetail) { + this.tunedDetail = tunedDetail; + } + + /** + * Return the tuned detail (for comparison with profiling information). + */ + public OrmQueryDetail getTunedDetail() { + return tunedDetail; + } + + /** + * Tune the query by replacing its OrmQueryDetail with a tuned one. + * + * @return true if the query was tuned, otherwise false. + */ + public boolean tuneQuery(SpiQuery query) { + if (tunedDetail == null) { + return false; + } + + boolean tuned; + if (query.isDetailEmpty()) { + tuned = true; + // tune by 'replacement' + query.setDetail(tunedDetail.copy()); + } else { + // tune by 'addition' + tuned = query.tuneFetchProperties(tunedDetail); + } + if (tuned) { + query.setAutoFetchTuned(true); + } + return tuned; + } + + public String toString() { + return tunedDetail.toString(); + } + +} 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..9ce4609d3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java @@ -24,20 +24,16 @@ import com.avaje.ebeaninternal.server.lib.sql.SimpleDataSourceAlert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.management.MBeanServer; -import javax.management.MBeanServerFactory; import javax.persistence.PersistenceException; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.ServiceLoader; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.atomic.AtomicInteger; /** * Default Server side implementation of ServerFactory. @@ -50,7 +46,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 +115,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 +127,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 ab197fac3..ca2fbe9a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -36,7 +36,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.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; import com.avaje.ebeaninternal.server.deploy.BeanProperty; @@ -45,7 +45,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; @@ -71,9 +70,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; @@ -105,8 +101,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 BeanDescriptorManager beanDescriptorManager; - private final AutoFetchManager autoFetchManager; + private final AutoTuneService autoTuneService; private final ReadAuditPrepare readAuditPrepare; @@ -156,21 +150,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. */ @@ -238,8 +222,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.readAuditPrepare = config.getReadAuditPrepare(); this.readAuditLogger = config.getReadAuditLogger(); @@ -258,6 +241,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private void configureServerPlugins() { + autoTuneService.startup(); + for (SpiServerPlugin plugin : serverPlugins) { plugin.configure(this); } @@ -350,12 +335,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return ddlGenerator; } - public AdminAutofetch getAdminAutofetch() { - return adminAutofetch; - } - - public AutoFetchManager getAutoFetchManager() { - return autoFetchManager; + @Override + public AutoTune getAutoTune() { + return autoTuneService; } @Override @@ -388,41 +370,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. */ @@ -454,16 +401,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) @@ -1115,7 +1055,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.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) { // use deployment FetchType.LAZY/EAGER annotations // to define the 'default' select clause query.setDefaultSelectClause(); @@ -1123,13 +1063,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (query.selectAllForLazyLoadProperty()) { // we need to select all properties to ensure the lazy load property - // was included (was not included by default or via autofetch). + // was included (was not included by default or via autoTune). if (logger.isDebugEnabled()) { logger.debug("Using selectAllForLazyLoadProperty"); } } - // if determine cost and no origin for Autofetch + // if determine cost and no origin for AutoTune if (query.getParentNode() == null) { query.setOrigin(createCallStack()); } 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 92635429e..a3daf21ca 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -15,8 +15,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.DefaultChangeLogListener; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister; @@ -36,8 +36,6 @@ import com.avaje.ebeaninternal.server.query.DefaultOrmQueryEngine; import com.avaje.ebeaninternal.server.query.DefaultRelationalQueryEngine; import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditLogger; import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditPrepare; -import com.avaje.ebeaninternal.server.resource.ResourceManager; -import com.avaje.ebeaninternal.server.resource.ResourceManagerFactory; import com.avaje.ebeaninternal.server.text.json.DJsonContext; import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager; import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager; @@ -70,8 +68,6 @@ public class InternalConfiguration { private final DeployInherit deployInherit; - private final ResourceManager resourceManager; - private final DeployOrmXml deployOrmXml; private final TypeManager typeManager; @@ -122,8 +118,7 @@ public class InternalConfiguration { this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses); - this.resourceManager = ResourceManagerFactory.createResourceManager(serverConfig); - this.deployOrmXml = new DeployOrmXml(resourceManager.getResourceSource()); + this.deployOrmXml = new DeployOrmXml(); this.deployInherit = new DeployInherit(bootupClasses); this.deployCreateProperties = new DeployCreateProperties(typeManager); @@ -253,8 +248,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); } public RelationalQueryEngine createRelationalQueryEngine() { @@ -305,10 +300,6 @@ public class InternalConfiguration { return deployInherit; } - public ResourceManager getResourceManager() { - return resourceManager; - } - public DeployOrmXml getDeployOrmXml() { return deployOrmXml; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index af024e09a..e2fa6399c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -129,7 +129,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { */ private final String selectLastInsertedId; - private final boolean autoFetchTunable; + private final boolean autoTunable; /** * The concurrency mode for beans of this type. @@ -375,7 +375,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { this.baseTable = InternString.intern(deploy.getBaseTable()); this.baseTableAsOf = deploy.getBaseTableAsOf(); this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween(); - this.autoFetchTunable = EntityType.ORM.equals(entityType) && (beanFinder == null); + this.autoTunable = EntityType.ORM.equals(entityType) && (beanFinder == null); // helper object used to derive lists of properties DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy); @@ -1687,10 +1687,10 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { } /** - * Return true if queries for beans of this type are autoFetch tunable. + * Return true if queries for beans of this type are auto tunable. */ - public boolean isAutoFetchTunable() { - return autoFetchTunable; + public boolean isAutoTunable() { + return autoTunable; } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java index 833d1a5cc..fedc80baf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java @@ -1,18 +1,16 @@ package com.avaje.ebeaninternal.server.deploy; +import com.avaje.ebeaninternal.server.lib.util.Dnode; +import com.avaje.ebeaninternal.server.lib.util.DnodeReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import com.avaje.ebeaninternal.server.lib.resource.ResourceContent; -import com.avaje.ebeaninternal.server.lib.resource.ResourceSource; -import com.avaje.ebeaninternal.server.lib.util.Dnode; -import com.avaje.ebeaninternal.server.lib.util.DnodeReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Controls the creation and caching of BeanManager's, BeanDescriptors, * BeanTable etc for both beans and tables(MapBeans). @@ -27,15 +25,10 @@ public class DeployOrmXml { private final HashMap nativeQueryCache; private final ArrayList ormXmlList; - - private final ResourceSource resSource; - public DeployOrmXml(ResourceSource resSource) { - - this.resSource = resSource; + public DeployOrmXml() { this.nativeQueryCache = new HashMap(); this.ormXmlList = findAllOrmXml(); - initialiseNativeQueries(); } @@ -106,17 +99,7 @@ public class DeployOrmXml { private void readOrmXml(String ormXmlName, ArrayList ormXmlList) { try { - Dnode ormXml; - ResourceContent content = resSource.getContent(ormXmlName); - if (content != null) { - // servlet resource or file system... - ormXml = readOrmXml(content.getInputStream()); - - } else { - // try the classpath... - ormXml = readOrmXmlFromClasspath(ormXmlName); - } - + Dnode ormXml = readOrmXmlFromClasspath(ormXmlName); if (ormXml != null) { ormXml.setAttribute("ebean.filename", ormXmlName); ormXmlList.add(ormXml); 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/lib/resource/DirectoryFinder.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java deleted file mode 100644 index 0755ba6c8..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.avaje.ebeaninternal.server.lib.resource; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -/** - * Helper object used to find directories typically from the current working - * directory. - */ -public class DirectoryFinder { - - private static final Logger logger = LoggerFactory.getLogger(DirectoryFinder.class); - - /** - * Find a directory by search through subdirectories. - *

- * For example, used to find the WEB-INF directory starting from the current - * working directory. - *

- * - *
-	 * 
-	 * // search to a depth of 3 from the current working directory
-	 * // looking for a directory WEB-INF that contains the subdirectory
-	 * // data
-	 * 
-	 * File dir = DirectoryFinder.find(null, "WEB-INF/data", 3);
-	 * if (dir != null) {
-	 * 	//found the directory
-	 * }
-	 * 
- */ - public static File find(File startDir, String match, int maxDepth) { - - String matchSub = null; - int slashPos = match.indexOf('/'); - if (slashPos > -1) { - // match has sub directories - matchSub = match.substring(slashPos + 1); - match = match.substring(0, slashPos); - } - - // search for the directory - File found = find(startDir, match, matchSub, 0, maxDepth); - - if (found != null && matchSub != null) { - // match has sub directories - return new File(found, matchSub); - } - return found; - } - - private static File find(File dir, String match, String matchSub, int depth, int maxDepth) { - - if (dir == null) { - String curDir = System.getProperty("user.dir"); - dir = new File(curDir); - } - - if (dir.exists()) { - File[] list = dir.listFiles(); - if (list != null){ - for (int i = 0; i < list.length; i++) { - if (isMatch(list[i], match, matchSub)) { - return list[i]; - } - } - - // go through the directories again - // Aka *NOT* a depth first search - if (depth < maxDepth) { - for (int i = 0; i < list.length; i++) { - if (list[i].isDirectory()) { - File found = find(list[i], match, matchSub, depth + 1, maxDepth); - if (found != null) { - return found; - } - } - } - } - } - } - return null; - } - - private static boolean isMatch(File f, String match, String matchSub) { - if (f == null) { - return false; - } - if (!f.isDirectory()) { - return false; - } - if (!f.getName().equalsIgnoreCase(match)) { - return false; - } - if (matchSub == null) { - return true; - } - File sub = new File(f, matchSub); - if (logger.isTraceEnabled()){ - logger.trace("search; " + f.getPath()); - } - return sub.exists(); - - } -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java deleted file mode 100644 index b2f6cb07e..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.avaje.ebeaninternal.server.lib.resource; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -/** - * Content from a file system file. - */ -public class FileResourceContent implements ResourceContent { - - /** - * The underlying file. - */ - final File file; - - final String entryName; - - /** - * Create with a File and the entryName. - */ - public FileResourceContent(File file, String entryName) { - this.file = file; - this.entryName = entryName; - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("[").append(getName()); - sb.append("] size[").append(size()); - sb.append("] lastModified[").append(new Date(lastModified())); - sb.append("]"); - return sb.toString(); - } - - /** - * Returns the entry name which contains the path from the base directory. - *

- * This does not return the full path of the file, but the path relative to - * the FileIoSource directory. - *

- */ - public String getName() { - return entryName; - } - - /** - * Return the time the file was last modified. - */ - public long lastModified() { - return file.lastModified(); - } - - /** - * Return the size of the file. - */ - public long size() { - return file.length(); - } - - /** - * Return the input stream for this file. - */ - public InputStream getInputStream() throws IOException { - - return new FileInputStream(file); - } -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java deleted file mode 100644 index 496660c1e..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.avaje.ebeaninternal.server.lib.resource; - -import java.io.File; - -/** - * A file system directory represented as a FileSource. - */ -public class FileResourceSource implements ResourceSource { - - /** - * The directory name. - */ - final String directory; - - final String baseDir; - - /** - * Create the source based on a directory name. - */ - public FileResourceSource(String directory) { - this.directory = directory; - this.baseDir = directory + File.separator; - } - - /** - * Create the source based on a directory file. - */ - public FileResourceSource(File dir) { - this(dir.getPath()); - } - - - public String getRealPath() { - return directory; - } - - /** - * Search for the given file and return as IoContent. - */ - public ResourceContent getContent(String entry) { - - String fullPath = baseDir + entry; - - File f = new File(fullPath); - if (f.exists()) { - return new FileResourceContent(f, entry); - } - return null; - } -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java deleted file mode 100644 index 6aab953c7..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.avaje.ebeaninternal.server.lib.resource; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Represents content that can be read via the ResourceManager. - *

- * Typically either content from a File or a URL. - *

- */ -public interface ResourceContent { - - /** - * The name of the content. - */ - String getName(); - - /** - * The size of the content in bytes. - */ - long size(); - - /** - * The last modified timestamp of the content. - */ - long lastModified(); - - /** - * The content itself. - */ - InputStream getInputStream() throws IOException; - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java deleted file mode 100644 index 1be9338bb..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.avaje.ebeaninternal.server.lib.resource; - -/** - * A Source for ResourceManager. - *

- * Typically a File System Directory based source or a ServletContext URL - * resource based source (for Servlet WAR files). - *

- */ -public interface ResourceSource { - - /** - * Return the File System path of the root of the ResourceSource. - *

- * This will return null IF the ResourceSource is an unpacked WAR file. - *

- */ - String getRealPath(); - - /** - * Find the content with a given entry name. This will return null if no - * matching content was found. - */ - ResourceContent getContent(String entry); - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html deleted file mode 100644 index 9ca4bbdba..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - AvajeLib - - -Service for reading content from either the file system or servlet context. -

-A service used to read deployment content such as xml files, images etc -taking into account the environment (servlet war file or file system). -

- - \ 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 175d27ac6..1794bac80 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -61,7 +61,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 ae7924217..36805c2b6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -48,7 +48,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(); @@ -69,7 +69,7 @@ public class DLoadContext implements LoadContext { this.disableReadAudit = query.isDisableReadAudit(); 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) { @@ -199,8 +199,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 1100e7f1b..6bd24e234 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -73,7 +73,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 cc52ef6bb..1f1f07a7a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -13,7 +13,7 @@ import com.avaje.ebean.event.readaudit.ReadEvent; 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; @@ -161,13 +161,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; @@ -198,11 +198,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... @@ -549,8 +548,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); @@ -678,7 +677,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) { @@ -700,8 +699,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 45fc3cfc6..0085f56e0 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; @@ -27,7 +18,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; @@ -37,6 +28,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. */ @@ -58,7 +57,7 @@ public class DefaultOrmQuery implements SpiQuery { */ private transient TableJoin includeTableJoin; - private transient AutoFetchManager autoFetchManager; + private transient ProfilingListener profilingListener; private transient BeanDescriptor beanDescriptor; @@ -489,7 +488,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; @@ -624,19 +623,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/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java index b7a2885d8..0f4ab5f46 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -9,6 +9,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.persistence.PersistenceException; @@ -80,26 +81,35 @@ public class OrmQueryDetail implements Serializable { } /** - * Return true if equal in terms of autofetch (select and joins). + * Return true if equal in terms of autoTune (select and fetch). */ - public boolean isAutoFetchEqual(OrmQueryDetail otherDetail) { - return autofetchPlanHash() == otherDetail.autofetchPlanHash(); - } + public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) { - /** - * Calculate the hash for the query plan. - */ - private int autofetchPlanHash() { - - int hc = (baseProps == null ? 1 : baseProps.autofetchPlanHash()); - - if (fetchPaths != null) { - for (OrmQueryProperties p : fetchPaths.values()) { - hc = hc * 31 + p.autofetchPlanHash(); + if (!isSame(baseProps, otherDetail.baseProps)) { + return false; + } + if (fetchPaths == null) { + return otherDetail.fetchPaths == null; + } + if (fetchPaths.size() != otherDetail.fetchPaths.size()) { + return false; + } + Set> entries = fetchPaths.entrySet(); + for (Map.Entry entry : entries) { + OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false); + if (!isSame(entry.getValue(), chunk)) { + return false; } } - return hc; + return true; + } + + private boolean isSame(OrmQueryProperties p1, OrmQueryProperties p2) { + if (p1 == null) { + return p2 == null; + } + return p1.isSame(p2); } public String toString() { @@ -212,7 +222,7 @@ public class OrmQueryDetail implements Serializable { boolean tuned = false; OrmQueryProperties tunedRoot = tunedDetail.getChunk(null, false); - if (tunedRoot != null && tunedRoot.hasProperties()) { + if (tunedRoot != null) { tuned = true; baseProps.setTunedProperties(tunedRoot); diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java index f33572efb..9a5026693 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java @@ -27,10 +27,11 @@ public class OrmQueryDetailParser { this.parser = new SimpleTextParser(oql); } - public void parse() throws PersistenceException { + public OrmQueryDetail parse() throws PersistenceException { parser.nextWord(); processInitial(); + return detail; } protected void assign(DefaultOrmQuery query) { @@ -42,7 +43,9 @@ public class OrmQueryDetailParser { } private void processInitial() { - if (parser.isMatch("find")) { + if (parser.isMatch("select")) { + readSelect(); + } else if (parser.isMatch("find")) { OrmQueryProperties props = readFindFetch(); detail.setBase(props); } else { @@ -149,6 +152,19 @@ public class OrmQueryDetailParser { } } + private void readSelect() { + String path = null; + String props = parser.nextWord(); + if (props.startsWith("(")) { + props = props.substring(1, props.length() - 1); + OrmQueryProperties base = new OrmQueryProperties(path, props); + detail.setBase(base); + parser.nextWord(); + } else { + process(); + } + } + private OrmQueryProperties readFindFetch() { boolean readAlias = false; diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java index a045b4194..21f2e566b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -215,9 +215,13 @@ public class OrmQueryProperties implements Serializable { * Set the properties from a matching autofetch tuned properties. */ public void setTunedProperties(OrmQueryProperties tunedProperties) { - this.properties = tunedProperties.properties; - this.trimmedProperties = tunedProperties.trimmedProperties; - this.included = tunedProperties.included; + if (tunedProperties.hasProperties()) { + this.properties = tunedProperties.properties; + this.trimmedProperties = tunedProperties.trimmedProperties; + this.included = tunedProperties.included; + this.queryFetchBatch = Math.max(queryFetchBatch, tunedProperties.queryFetchBatch); + this.lazyFetchBatch = Math.max(lazyFetchBatch, tunedProperties.lazyFetchBatch); + } } /** @@ -262,6 +266,7 @@ public class OrmQueryProperties implements Serializable { copy.parentPath = parentPath; copy.path = path; copy.properties = properties; + copy.trimmedProperties = trimmedProperties; copy.cache = cache; copy.readOnly = readOnly; copy.queryFetchAll = queryFetchAll; @@ -573,4 +578,11 @@ public class OrmQueryProperties implements Serializable { return Collections.unmodifiableSet(set); } + + public boolean isSame(OrmQueryProperties p2) { + if (included == null) { + return p2.included == null; + } + return included.equals(p2.included); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java b/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java deleted file mode 100644 index 087e1d2cd..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.avaje.ebeaninternal.server.resource; - -import java.io.File; - -import com.avaje.ebeaninternal.server.lib.resource.ResourceSource; - -/** - * The ResourceManager implementation. - */ -public class ResourceManager { - - final ResourceSource resourceSource; - - final File autofetchDir; - - public ResourceManager(ResourceSource resourceSource, File autofetchDir) { - this.resourceSource = resourceSource; - this.autofetchDir = autofetchDir; - } - - public ResourceSource getResourceSource() { - return resourceSource; - } - - public File getAutofetchDirectory() { - return autofetchDir; - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java deleted file mode 100644 index a32fd5625..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.avaje.ebeaninternal.server.resource; - -import com.avaje.ebean.config.ServerConfig; -import com.avaje.ebeaninternal.server.lib.resource.DirectoryFinder; -import com.avaje.ebeaninternal.server.lib.resource.FileResourceSource; -import com.avaje.ebeaninternal.server.lib.resource.ResourceSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -/** - * Creates a ResourceManager for a server depending on the avaje.properties. - *

- * This can use URL based resource loading for web applications or file based - * otherwise. - *

- */ -public class ResourceManagerFactory { - - private static final Logger logger = LoggerFactory.getLogger(ResourceManagerFactory.class); - - /** - * Construct with the properties for a server. - */ - public ResourceManagerFactory() { - } - - /** - * Create the resource manager given the properties for this server. - */ - public static ResourceManager createResourceManager(ServerConfig serverConfig) { - - ResourceSource resourceSource = createResourceSource(serverConfig); - File autofetchDir = getAutofetchDir(serverConfig, resourceSource); - - return new ResourceManager(resourceSource, autofetchDir); - } - - - /** - * Return the directory that autofetch file goes into. - */ - protected static File getAutofetchDir(ServerConfig serverConfig, ResourceSource resourceSource) { - - String dir = null; - if (serverConfig.getAutofetchConfig() != null) { - dir = serverConfig.getAutofetchConfig().getLogDirectory(); - } - if (dir != null) { - return new File(dir); - } - - String realPath = resourceSource.getRealPath(); - if (realPath != null) { - return new File(realPath); - - } else { - throw new RuntimeException("No autofetch directory set?"); - } - } - - /** - * Return the resource loader for external sql files. - *

- * This can be url based (for webapps) or otherwise file based. - *

- */ - protected static ResourceSource createResourceSource(ServerConfig serverConfig) { - - // default for web application, override this for file system - String defaultDir = serverConfig.getResourceDirectory(); - - // use File System directory - return createFileSource(defaultDir); - } - - private static ResourceSource createFileSource(String fileDir) { - - if (fileDir != null) { - // explicitly stated so - File dir = new File(fileDir); - if (dir.exists()) { - logger.info("ResourceManager initialised: type[file] [" + fileDir + "]"); - return new FileResourceSource(fileDir); - } else { - String msg = "ResourceManager could not find directory [" + fileDir + "]"; - throw new IllegalArgumentException(msg); - } - } - - // try to guess the directory starting from the current working - // directory, and searching to a maximum depth of 3 subdirectories - File guessDir = DirectoryFinder.find(null, "WEB-INF", 3); - if (guessDir != null) { - // Typically this means we found the WEB-INF directory below the - // current working directory - logger.info("ResourceManager initialised: type[file] [" + guessDir.getPath() + "]"); - return new FileResourceSource(guessDir.getPath()); - } - - // default to the current working directory - File workingDir = new File("."); - return new FileResourceSource(workingDir); - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/resource/package.html b/src/main/java/com/avaje/ebeaninternal/server/resource/package.html deleted file mode 100644 index abed4e076..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/resource/package.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Resource Management - - -Resource Management - - - \ No newline at end of file diff --git a/src/main/resources/ebean-autotune-1.0.xsd b/src/main/resources/ebean-autotune-1.0.xsd new file mode 100644 index 000000000..fd4fc3ad2 --- /dev/null +++ b/src/main/resources/ebean-autotune-1.0.xsd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 6140611ee..22babcee4 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -15,7 +15,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; @@ -88,11 +87,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } - @Override - public AutoFetchManager getAutoFetchManager() { - return null; - } - @Override public ReadAuditLogger getReadAuditLogger() { return null; @@ -234,7 +228,7 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } @Override - public AdminAutofetch getAdminAutofetch() { + public AutoTune getAutoTune() { return null; } diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java new file mode 100644 index 000000000..0e877728c --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java @@ -0,0 +1,119 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebean.BaseTestCase; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNull; + + +public class OrmQueryDetailParserTest extends BaseTestCase { + + @Test + public void testParseBasic() throws Exception { + + + OrmQueryDetail other = new OrmQueryDetail(); + other.select("id,name"); + + OrmQueryProperties root = other.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).contains("id", "name"); + + OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name)"); + OrmQueryDetail detail = p.parse(); + + root = detail.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).contains("id", "name"); + } + + @Test + public void testParseEmptySelect() throws Exception { + + OrmQueryDetailParser p = new OrmQueryDetailParser("select fetch customer (email)"); + OrmQueryDetail detail = p.parse(); + + OrmQueryProperties root = detail.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).isNull(); + + OrmQueryProperties chunk = detail.getChunk("customer", false); + assertThat(chunk.getPath()).isEqualTo("customer"); + assertThat(chunk.getAllIncludedProperties()).contains("email"); + } + + @Test + public void testParseSelectFetch() throws Exception { + + OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (email)"); + OrmQueryDetail detail = p.parse(); + + OrmQueryProperties root = detail.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).contains("id", "name"); + + OrmQueryProperties chunk = detail.getChunk("customer", false); + assertThat(chunk.getPath()).isEqualTo("customer"); + assertThat(chunk.getAllIncludedProperties()).contains("email"); + } + + @Test + public void testParseSelectFetchMore() throws Exception { + + OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (email) fetch details.product (sku,description)"); + OrmQueryDetail detail = p.parse(); + + OrmQueryProperties root = detail.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).contains("id", "name"); + + OrmQueryProperties chunk = detail.getChunk("customer", false); + assertThat(chunk.getPath()).isEqualTo("customer"); + assertThat(chunk.getAllIncludedProperties()).contains("email"); + + chunk = detail.getChunk("details.product", false); + assertThat(chunk.getPath()).isEqualTo("details.product"); + assertThat(chunk.getAllIncludedProperties()).contains("sku","description"); + } + + @Test + public void testParseWithPlusQuery() throws Exception { + + OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (+query,id,name,email)"); + OrmQueryDetail detail = p.parse(); + + OrmQueryProperties root = detail.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).contains("id", "name"); + + OrmQueryProperties chunk = detail.getChunk("customer", false); + assertThat(chunk.getPath()).isEqualTo("customer"); + assertThat(chunk.getAllIncludedProperties()).contains("id", "name", "email"); + assertThat(chunk.isQueryFetch()).isTrue(); + + } + + @Test + public void testTuneApply() { + + OrmQueryDetailParser p = new OrmQueryDetailParser("select (status) fetch customer (email)"); + OrmQueryDetail detail = p.parse(); + + OrmQueryDetailParser p2 = new OrmQueryDetailParser("select (id,name) fetch customer (+query,id,name,email)"); + OrmQueryDetail tune = p2.parse(); + + + detail.tuneFetchProperties(tune); + + OrmQueryProperties root = detail.getChunk(null, false); + assertNull(root.getPath()); + assertThat(root.getAllIncludedProperties()).contains("id", "name"); + + OrmQueryProperties chunk = detail.getChunk("customer", false); + assertThat(chunk.getPath()).isEqualTo("customer"); + assertThat(chunk.getAllIncludedProperties()).contains("id", "name", "email"); + assertThat(chunk.isQueryFetch()).isTrue(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java new file mode 100644 index 000000000..3fa127b73 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -0,0 +1,21 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class OrmQueryDetailTest { + + + @Test + public void test_isAutoTuneEqual() { + + OrmQueryDetailParser parser1 = new OrmQueryDetailParser("select (id,name) fetch customer (name) fetch details (code)"); + OrmQueryDetail detail1 = parser1.parse(); + + OrmQueryDetailParser parser2 = new OrmQueryDetailParser("select (id,name) fetch details (code) fetch customer (name)"); + OrmQueryDetail detail2 = parser2.parse(); + + assertTrue(detail1.isAutoTuneEqual(detail2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/autofetch/AutofetchEmbeddedTest.java b/src/test/java/com/avaje/tests/autofetch/AutofetchEmbeddedTest.java index 2a201f256..c6de0cd29 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).getAutoTune().setProfiling(true); +// Ebean.getServer(null).getAutoTune().setQueryTuning(true); +// Ebean.getServer(null).getAutoTune().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).getAutoTune().collectProfiling()); +// } +// } } @Test diff --git a/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java b/src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java similarity index 87% rename from src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java rename to src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java index b66df26fb..fcd1990b7 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfoTest.java +++ b/src/test/java/com/avaje/tests/autofetch/TunedQueryInfoTest.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.tests.autofetch; import java.util.List; import java.util.Set; @@ -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; @@ -41,11 +41,11 @@ public class TunedQueryInfoTest extends BaseTestCase { OrmQueryDetail tunedDetail = new OrmQueryDetail(); tunedDetail.select(null); - TunedQueryInfo tunedInfo = new TunedQueryInfo(null, tunedDetail, 0); + TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail); Query query = server.find(Order.class).setId(1); - tunedInfo.autoFetchTune((SpiQuery)query); + tunedInfo.tuneQuery((SpiQuery) query); Order order = query.findUnique(); EntityBean eb = (EntityBean)order; @@ -69,11 +69,11 @@ public class TunedQueryInfoTest extends BaseTestCase { OrmQueryDetail tunedDetail = new OrmQueryDetail(); tunedDetail.select(""); - TunedQueryInfo tunedInfo = new TunedQueryInfo(null, tunedDetail, 0); + TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail); Query query = server.find(Order.class).setId(1); - tunedInfo.autoFetchTune((SpiQuery)query); + tunedInfo.tuneQuery((SpiQuery) query); Order order = query.findUnique(); EntityBean eb = (EntityBean)order; @@ -96,11 +96,11 @@ public class TunedQueryInfoTest extends BaseTestCase { OrmQueryDetail tunedDetail = new OrmQueryDetail(); tunedDetail.select("somethingThatDoesNotExist"); - TunedQueryInfo tunedInfo = new TunedQueryInfo(null, tunedDetail, 0); + TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail); Query query = server.find(Order.class).setId(1); - tunedInfo.autoFetchTune((SpiQuery)query); + tunedInfo.tuneQuery((SpiQuery) query); LoggedSqlCollector.start(); @@ -132,11 +132,11 @@ public class TunedQueryInfoTest extends BaseTestCase { OrmQueryDetail tunedDetail = new OrmQueryDetail(); tunedDetail.select("status, customer"); - TunedQueryInfo tunedInfo = new TunedQueryInfo(null, tunedDetail, 0); + TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail); Query query = server.find(Order.class).setId(1); - tunedInfo.autoFetchTune((SpiQuery)query); + tunedInfo.tuneQuery((SpiQuery) query); LoggedSqlCollector.start(); @@ -167,11 +167,11 @@ public class TunedQueryInfoTest extends BaseTestCase { OrmQueryDetail tunedDetail = new OrmQueryDetail(); tunedDetail.select("status"); - TunedQueryInfo tunedInfo = new TunedQueryInfo(null, tunedDetail, 0); + TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail); Query query = server.find(Order.class).setId(1); - tunedInfo.autoFetchTune((SpiQuery)query); + tunedInfo.tuneQuery((SpiQuery) query); LoggedSqlCollector.start(); diff --git a/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryWithNullFetchedBeanTest.java b/src/test/java/com/avaje/tests/autofetch/TunedQueryWithNullFetchedBeanTest.java similarity index 97% rename from src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryWithNullFetchedBeanTest.java rename to src/test/java/com/avaje/tests/autofetch/TunedQueryWithNullFetchedBeanTest.java index 2e304b56b..501614941 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryWithNullFetchedBeanTest.java +++ b/src/test/java/com/avaje/tests/autofetch/TunedQueryWithNullFetchedBeanTest.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.autofetch; +package com.avaje.tests.autofetch; import java.util.List; import java.util.Set; diff --git a/src/test/java/com/avaje/tests/basic/TestBatchLazy.java b/src/test/java/com/avaje/tests/basic/TestBatchLazy.java index 607d0c4cb..8a2e3ca37 100644 --- a/src/test/java/com/avaje/tests/basic/TestBatchLazy.java +++ b/src/test/java/com/avaje/tests/basic/TestBatchLazy.java @@ -1,44 +1,41 @@ -package com.avaje.tests.basic; - -import java.util.List; - -import org.junit.Test; - -import com.avaje.ebean.AdminAutofetch; -import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; -import com.avaje.ebean.Query; -import com.avaje.tests.model.basic.Customer; -import com.avaje.tests.model.basic.Order; -import com.avaje.tests.model.basic.OrderDetail; -import com.avaje.tests.model.basic.ResetBasicData; - -public class TestBatchLazy extends BaseTestCase { - - @Test - public void testMe() { - - ResetBasicData.reset(); - - Query query = Ebean.find(Order.class); - List list = query.findList(); - - - for (Order order : list) { - Customer customer = order.getCustomer(); - customer.getName(); - - List details = order.getDetails(); - for (OrderDetail orderDetail : details) { - orderDetail.getProduct().getSku(); - } - } - - AdminAutofetch adminAutofetch = Ebean.getServer(null).getAdminAutofetch(); - adminAutofetch.collectUsageViaGC(); - - } - - - -} +package com.avaje.tests.basic; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.OrderDetail; +import com.avaje.tests.model.basic.ResetBasicData; +import org.junit.Test; + +import java.util.List; + +public class TestBatchLazy extends BaseTestCase { + + @Test + public void testMe() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Order.class); + List list = query.findList(); + + + for (Order order : list) { + Customer customer = order.getCustomer(); + customer.getName(); + + List details = order.getDetails(); + for (OrderDetail orderDetail : details) { + orderDetail.getProduct().getSku(); + } + } + + Ebean.getDefaultServer().getAutoTune().collectProfiling(); + + } + + + +} diff --git a/src/test/java/com/avaje/tests/basic/lob/TestByteOnly.java b/src/test/java/com/avaje/tests/basic/lob/TestByteOnly.java index 758ac7e59..1ce5a64a3 100644 --- a/src/test/java/com/avaje/tests/basic/lob/TestByteOnly.java +++ b/src/test/java/com/avaje/tests/basic/lob/TestByteOnly.java @@ -24,8 +24,8 @@ public class TestByteOnly extends BaseTestCase { Ebean.save(e2); - // Ebean.getServer(null).getAdminAutofetch().collectUsageViaGC(); - // Ebean.getServer(null).getAdminAutofetch().updateTunedQueryInfo(); + // Ebean.getServer(null).getAutoTune().collectProfiling(); + // Ebean.getServer(null).getAutoTune().updateTunedQueryInfo(); System.out.println("done"); } diff --git a/src/test/java/com/avaje/tests/cache/TestL2CacheWithSharedBean.java b/src/test/java/com/avaje/tests/cache/TestL2CacheWithSharedBean.java index 381da8236..53d597056 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; @@ -28,11 +28,11 @@ public class TestL2CacheWithSharedBean extends BaseTestCase { OrmQueryDetail tunedDetail = new OrmQueryDetail(); tunedDetail.select("name"); - TunedQueryInfo tunedInfo = new TunedQueryInfo(null, tunedDetail, 0); + TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail); Query query = Ebean.find(FeatureDescription.class).setId(f1.getId()); - tunedInfo.autoFetchTune((SpiQuery) query); + tunedInfo.tuneQuery((SpiQuery) query); query.findUnique(); // PUT into cache diff --git a/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java b/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java index 0693038fe..d82fbfe6a 100644 --- a/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java +++ b/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java @@ -1,79 +1,78 @@ -package com.avaje.tests.query; - -import java.util.List; - -import org.junit.Test; - -import com.avaje.ebean.AdminAutofetch; -import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; -import com.avaje.ebean.Query; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.bean.ObjectGraphOrigin; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.tests.model.basic.Address; -import com.avaje.tests.model.basic.Customer; -import com.avaje.tests.model.basic.Order; -import com.avaje.tests.model.basic.ResetBasicData; - -public class TestAutofetchTuneWithJoin extends BaseTestCase { - - @Test - public void test() { - runQuery(); - collectUsage(); - } - - private void runQuery() { - - ResetBasicData.reset(); - - Query q = Ebean.find(Order.class).setAutofetch(true).fetch("customer") - .fetch("customer.contacts").where().lt("id", 3).query(); - - List list = q.findList(); - - for (int i = 0; i < list.size(); i++) { - Order order = list.get(i); - order.getOrderDate(); - order.getShipDate(); - // order.setShipDate(new Date(System.currentTimeMillis())); - Customer customer = order.getCustomer(); - customer.getName(); - Address shippingAddress = customer.getShippingAddress(); - if (shippingAddress != null) { - shippingAddress.getLine1(); - shippingAddress.getCity(); - } - // customer.getContacts() - } - - SpiQuery sq = (SpiQuery) q; - ObjectGraphNode parentNode = sq.getParentNode(); - ObjectGraphOrigin origin = parentNode.getOriginQueryPoint(); - - System.out.println("Origin:" + origin.getKey()); - - // MetaAutoFetchStatistic metaAutoFetchStatistic = - // ((DefaultOrmQuery)q).getMetaAutoFetchStatistic(); - // if (metaAutoFetchStatistic != null) { - // List nodeUsageStats = - // metaAutoFetchStatistic.getNodeUsageStats(); - // System.out.println(nodeUsageStats); - // List queryStats = metaAutoFetchStatistic.getQueryStats(); - // System.out.println(queryStats); - // } - - if (q.isAutofetchTuned()) { - System.out.println("TUNED..."); - } - } - - private static void collectUsage() { - - AdminAutofetch adminAutofetch = Ebean.getServer(null).getAdminAutofetch(); - adminAutofetch.collectUsageViaGC(); - - } - -} +package com.avaje.tests.query; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.tests.model.basic.Address; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.ResetBasicData; +import org.junit.Test; + +import java.util.List; + +public class TestAutofetchTuneWithJoin extends BaseTestCase { + + @Test + public void test() { + runQuery(); + collectUsage(); + } + + private void runQuery() { + + ResetBasicData.reset(); + + Query q = Ebean.find(Order.class) + .setAutofetch(true) + //.fetch("customer") + //.fetch("customer.contacts") + .where().lt("id", 3).query(); + + List list = q.findList(); + + for (int i = 0; i < list.size(); i++) { + Order order = list.get(i); + order.getOrderDate(); + order.getShipDate(); + // order.setShipDate(new Date(System.currentTimeMillis())); + Customer customer = order.getCustomer(); + customer.getName(); + Address shippingAddress = customer.getShippingAddress(); + if (shippingAddress != null) { + shippingAddress.getLine1(); + shippingAddress.getCity(); + } + // customer.getContacts() + } + + SpiQuery sq = (SpiQuery) q; + ObjectGraphNode parentNode = sq.getParentNode(); + ObjectGraphOrigin origin = parentNode.getOriginQueryPoint(); + + System.out.println("Origin:" + origin.getKey()); + + // MetaAutoFetchStatistic metaAutoFetchStatistic = + // ((DefaultOrmQuery)q).getMetaAutoFetchStatistic(); + // if (metaAutoFetchStatistic != null) { + // List nodeUsageStats = + // metaAutoFetchStatistic.getNodeUsageStats(); + // System.out.println(nodeUsageStats); + // List queryStats = metaAutoFetchStatistic.getQueryStats(); + // System.out.println(queryStats); + // } + + if (q.isAutofetchTuned()) { + System.out.println("TUNED..."); + } + } + + private static void collectUsage() { + + Ebean.getDefaultServer().getAutoTune().collectProfiling(); + } + +} diff --git a/src/test/java/com/avaje/tests/query/autotune/TestAutoTuneProfiling.java b/src/test/java/com/avaje/tests/query/autotune/TestAutoTuneProfiling.java new file mode 100644 index 000000000..2bcccb34a --- /dev/null +++ b/src/test/java/com/avaje/tests/query/autotune/TestAutoTuneProfiling.java @@ -0,0 +1,96 @@ +package com.avaje.tests.query.autotune; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.tests.model.basic.Address; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.OrderDetail; +import com.avaje.tests.model.basic.ResetBasicData; +import org.junit.Test; + +import java.util.List; + +public class TestAutoTuneProfiling extends BaseTestCase { + + @Test + public void test() { + + ResetBasicData.reset(); + + for (int i = 0; i < 1; i++) { + execute(); + } + + + collectUsage(); + } + + + private void execute() { + useOrderDate(); + useOrderDateCustomerName(); + useLots(); + useLotUntuned(); + } + + private Order findById(long id) { + return Ebean.find(Order.class) + .select("status, orderDate, shipDate") + .setId(id) + .findUnique(); + } + + private void useOrderDate() { + Order order = findById(3); + order.getStatus(); + order.getShipDate(); + } + + private void useOrderDateCustomerName() { + Order order = findById(3); + order.getOrderDate(); + order.getCustomer().getName(); + } + + private void useLots() { + + Order order = findById(3); + order.getOrderDate(); + order.getShipDate(); + // order.setShipDate(new Date(System.currentTimeMillis())); + Customer customer = order.getCustomer(); + customer.getName(); + Address shippingAddress = customer.getShippingAddress(); + if (shippingAddress != null) { + shippingAddress.getLine1(); + shippingAddress.getCity(); + } + } + + private void useLotUntuned() { + Order order = findById(3); + List details = order.getDetails(); + for (OrderDetail detail : details) { + detail.getProduct().getName(); + detail.getOrderQty(); + detail.getShipQty(); + detail.getUnitPrice(); + } + + Customer customer = order.getCustomer(); + customer.getName(); + Address billingAddress = customer.getBillingAddress(); + if (billingAddress != null) { + billingAddress.getCity(); + billingAddress.getLine1(); + billingAddress.getLine2(); + } + } + + private static void collectUsage() { + + Ebean.getDefaultServer().getAutoTune().collectProfiling(); + } + +} diff --git a/src/test/java/com/avaje/ebeaninternal/server/transaction/TestAutoCommitDataSource.java b/src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java similarity index 97% rename from src/test/java/com/avaje/ebeaninternal/server/transaction/TestAutoCommitDataSource.java rename to src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java index b24b37316..75ec8d753 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/transaction/TestAutoCommitDataSource.java +++ b/src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.transaction; +package com.avaje.tests.transaction; import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.EbeanServer; diff --git a/src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java b/src/test/java/com/avaje/tests/transaction/TestBatchPersistCascade.java similarity index 98% rename from src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java rename to src/test/java/com/avaje/tests/transaction/TestBatchPersistCascade.java index 9e0cc9608..db66fb72f 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java +++ b/src/test/java/com/avaje/tests/transaction/TestBatchPersistCascade.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.transaction; +package com.avaje.tests.transaction; import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties index 86562c599..12018186d 100644 --- a/src/test/resources/ebean.properties +++ b/src/test/resources/ebean.properties @@ -11,14 +11,8 @@ ebean.encryptKeyManager=com.avaje.tests.basic.encrypt.BasicEncyptKeyManager -ebean.autofetch.querytuning=false -ebean.autofetch.profiling=false -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.autotune.querytuning=true +ebean.autotune.profiling=true ebean.ddl.generate=true diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 6f9ee9b15..21dc15f99 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -72,6 +72,8 @@ + +