Merge AutoTune refactor with master

This commit is contained in:
Robin Bygrave
2015-09-10 10:27:24 +12:00
82 changed files with 2538 additions and 2910 deletions
@@ -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.
* <p>
* After this amount of profiling has been obtained profiling is collected at
* the Profiling Percentage rate.
* </p>
*/
void setProfilingBase(int profilingBase);
/**
* Return the minimum number of queries profiled before autoFetch will start
* automatically tuning the queries.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before autoFetch
* will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
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".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
*
* @return a summary of the updates that occurred
*/
String updateTunedQueryInfo();
/**
* Clear all the tuned query info.
* <p>
* Should only need do this for testing and playing around.
* </p>
*
* @return the amount of tuned query information cleared.
*/
int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
*
* @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();
}
@@ -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();
}
@@ -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
@@ -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;
}
@@ -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<String> used = new HashSet<String>();
private final Set<String> used = new LinkedHashSet<String>();
/**
* 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<String> getUsed() {
public Set<String> getUsed() {
return used;
}
@@ -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.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
*/
public boolean isQueryTuningAddVersion() {
return queryTuningAddVersion;
}
/**
* Set to true to force the version property to be always added by the query
* tuning.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
* <p>
* Generally this is not expected to be turned on.
* </p>
*/
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.
* <p>
* The profiling information is collected and then used to generate the tuned
* queries for autofetch.
* </p>
*/
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.
* <p>
* 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.
* </p>
*/
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);
}
}
@@ -11,7 +11,7 @@ import com.avaje.ebean.Query;
* query.
* </p>
*/
public enum AutofetchMode {
public enum AutoTuneMode {
/**
* Don't implicitly use Autofetch. Must explicitly turn it on.
@@ -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.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
*/
public boolean isQueryTuningAddVersion() {
return queryTuningAddVersion;
}
/**
* Set to true to force the version property to be always added by the query
* tuning.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
*/
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.
* <p>
* The profiling information is collected and then used to generate the tuned
* queries for autofetch.
* </p>
*/
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.
* <p>
* 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.
* </p>
*/
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.
* <p>
* Autofetch profiling information is collected as part of garbage collection.
* </p>
*/
public boolean isGarbageCollectionOnShutdown() {
return garbageCollectionOnShutdown;
}
/**
* Set to true if you want GC to trigger on shutdown.
* <p>
* This would be done if you want to try and collect Autofetch profiling information
* on shutdown.
* </p>
*/
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);
}
}
@@ -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();
@@ -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.
*/
@@ -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<T> extends Query<T> {
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.
@@ -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.
* <p>
* The profile information is periodically converted into "tuned query details" -
* which is used to automatically tune the queries that use autoFetch.
* </p>
* <p>
* 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.
* </p>
*/
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.
* <p>
* Should only need do this for testing and playing around.
* </p>
*/
int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
*/
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.
* <p>
* This should be a read only iteration.
* </p>
*/
Iterator<TunedQueryInfo> iterateTunedQueryInfo();
/**
* Iterate the node usage statistics.
* <p>
* This should be a read only iteration.
* </p>
*/
Iterator<Statistics> iterateStatistics();
/**
* Return true if profiling is enabled.
*/
boolean isProfiling();
/**
* Set to true to enable profiling.
* <p>
* 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.
* </p>
* <p>
* Due to this garbage collection delay, when turning off profiling while
* the application is running you should consider calling
* collectUsageViaGC() <em>BEFORE</em> setProfiling(false). This hints to
* the JVM to perform garbage collection, and hopefully collects the
* profiling information.
* </p>
*/
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).
* <p>
* 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.
* </p>
*/
int getProfilingBase();
/**
* Set a max number of queries to profile per query point.
* <p>
* This number should provide a level of confidence that no more profiling
* is required for this query point.
* </p>
*/
void setProfilingBase(int profilingMax);
/**
* Return the minimum number of queries profiled before autoFetch will start
* automatically tuning the queries.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before
* autoFetch will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
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".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
* <p>
* This returns a string summary of the updates that occurred.
* </p>
*/
String updateTunedQueryInfo();
/**
* Called when a query thinks it should be automatically tuned by autoFetch.
* <p>
* 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.
* </p>
* <p>
* This will also determine if the query should be profiled.
* </p>
*/
boolean tuneQuery(SpiQuery<?> query);
/**
* Collect query profiling information.
* <p>
* This is for the original query as well as any subsequent lazy loading
* queries that are required as the object graph is traversed.
* </p>
*
* @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();
}
@@ -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);
}
}
@@ -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<Entry> entries = new ArrayList<Entry>();
public Entry add(ObjectGraphOrigin origin, OrmQueryDetail detail, String sourceQuery) {
Entry entry = new Entry(origin, detail, sourceQuery);
entries.add(entry);
return entry;
}
public List<Entry> 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<EntryQuery> queries = new ArrayList<EntryQuery>();
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<EntryQuery> 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;
}
}
}
@@ -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.
* <p>
* The profile information is periodically converted into "tuned query details" -
* which is used to automatically tune the queries that use autoFetch.
* </p>
* <p>
* 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.
* </p>
*/
public interface AutoTuneService extends AutoTune {
/**
* Load the query tuning information.
*/
void startup();
/**
* Called when a query thinks it should be automatically tuned by autoFetch.
* <p>
* 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.
* </p>
* <p>
* This will also determine if the query should be profiled.
* </p>
*/
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();
}
@@ -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);
}
@@ -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<String, Statistics> statisticsMap = new ConcurrentHashMap<String, Statistics>();
/**
* Map of the tuned query details per profile query point.
*/
private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<String, TunedQueryInfo>();
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<TunedQueryInfo> iterateTunedQueryInfo() {
return tunedQueryInfoMap.values().iterator();
}
public Iterator<Statistics> 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.
* <p>
* 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.
* </p>
*/
public void shutdown() {
if (garbageCollectionOnShutdown) {
collectUsageViaGC(-1);
serialize();
}
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
* defaults to 100 milliseconds.
* </p>
*/
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.
* <p>
* This is sent to use from a EntityBeanIntercept when the finalise method
* is called on the bean.
* </p>
*/
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();
}
}
}
@@ -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.
* <p>
* 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.
* </p>
*/
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);
}
}
@@ -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);
}
@@ -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<String, StatisticsQuery> queryStatsMap = new LinkedHashMap<String, StatisticsQuery>();
private final Map<String, StatisticsNodeUsage> nodeUsageMap = new LinkedHashMap<String, StatisticsNodeUsage>();
@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.
* <p>
* This tells us how much profiling we have done for this query.
* For example, after 100 times we may stop collecting more profiling info.
* </p>
*/
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<Props> 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();
}
}
}
@@ -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 + "]";
}
}
@@ -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;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileDiff" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileNew" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin",
"profileDiff",
"profileNew"
})
@XmlRootElement(name = "autotune")
public class Autotune {
protected List<Origin> origin;
protected ProfileDiff profileDiff;
protected ProfileNew profileNew;
/**
* Gets the value of the origin property.
*
* <p>
* 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 <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
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;
}
}
@@ -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.
* <p>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();
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* &lt;/sequence>
* &lt;attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="original" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@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;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileDiff")
public class ProfileDiff {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* 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 <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileNew")
public class ProfileNew {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* 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 <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -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;
@@ -1,8 +0,0 @@
<html>
<head>
<title>AutoFetch Implementation</title>
</head>
<body>
AutoFetch Implementation
</body>
</html>
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<String, TunedQueryInfo>();
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);
}
}
}
}
@@ -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<AutoTuneCollection.Entry> 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<Origin> {
@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.
* <p>
* 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.
* </p>
*/
@Override
public void shutdown() {
if (profiling && !skipCollectionOnShutdown) {
collectProfiling(-1);
saveProfiling(false);
}
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
* defaults to 100 milliseconds.
* </p>
*/
@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);
}
}
@@ -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<String, ProfileOrigin> profileMap = new ConcurrentHashMap<String, ProfileOrigin>();
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.
* <p>
* This is sent to use from a EntityBeanIntercept when the finalise method
* is called on the bean.
* </p>
*/
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;
}
}
@@ -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<String, ProfileOriginQuery> queryStatsMap = new ConcurrentHashMap<String, ProfileOriginQuery>();
private final Map<String, ProfileOriginNodeUsage> nodeUsageMap = new ConcurrentHashMap<String, ProfileOriginNodeUsage>();
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<ProfileOriginQuery> 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<Props> 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.
* <p>
* This can give us a quick overview into bad lazy loading areas etc.
* </p>
*/
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;
}
}
}
@@ -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<String> aggregateUsed = new LinkedHashSet<String>();
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<String> used = profile.getUsed();
Set<String> used = profile.getUsed();
profileCount++;
if (!used.isEmpty()) {
@@ -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());
}
}
}
@@ -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();
}
}
@@ -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
@@ -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 <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> 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());
}
@@ -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;
}
@@ -129,7 +129,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
*/
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<T> implements MetaBeanInfo, SpiBeanType<T> {
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<T> implements MetaBeanInfo, SpiBeanType<T> {
}
/**
* 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;
}
/**
@@ -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<String, DNativeQuery> nativeQueryCache;
private final ArrayList<Dnode> ormXmlList;
private final ResourceSource resSource;
public DeployOrmXml(ResourceSource resSource) {
this.resSource = resSource;
public DeployOrmXml() {
this.nativeQueryCache = new HashMap<String, DNativeQuery>();
this.ormXmlList = findAllOrmXml();
initialiseNativeQueries();
}
@@ -106,17 +99,7 @@ public class DeployOrmXml {
private void readOrmXml(String ormXmlName, ArrayList<Dnode> 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);
@@ -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.
* <p>
* This is accessible via {@link EbeanServer#getAdminAutofetch()} or via JMX
* MBeans.
* </p>
*/
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();
}
}
@@ -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).
* <p>
* 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.
* </p>
*/
int getProfilingBase();
/**
* Set a max number of queries to profile per query point.
* <p>
* This number should provide a level of confidence that no more profiling
* is required for this query point.
* </p>
*/
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.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before
* autoFetch will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
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".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
*/
String updateTunedQueryInfo();
/**
* Clear all the tuned query info.
* <p>
* Should only need do this for testing and playing around.
* </p>
* @return the amount of tuned query information cleared.
*/
int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
* @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();
}
@@ -1 +0,0 @@
package com.avaje.ebeaninternal.server.jmx;
@@ -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.
* <p>
* For example, used to find the WEB-INF directory starting from the current
* working directory.
* </p>
*
* <pre class="code">
*
* // 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, &quot;WEB-INF/data&quot;, 3);
* if (dir != null) {
* //found the directory
* }
* </pre>
*/
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();
}
}
@@ -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.
* <p>
* This does not return the full path of the file, but the path relative to
* the FileIoSource directory.
* </p>
*/
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);
}
}
@@ -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;
}
}
@@ -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.
* <p>
* Typically either content from a File or a URL.
* </p>
*/
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;
}
@@ -1,26 +0,0 @@
package com.avaje.ebeaninternal.server.lib.resource;
/**
* A Source for ResourceManager.
* <p>
* Typically a File System Directory based source or a ServletContext URL
* resource based source (for Servlet WAR files).
* </p>
*/
public interface ResourceSource {
/**
* Return the File System path of the root of the ResourceSource.
* <p>
* This will return null IF the ResourceSource is an unpacked WAR file.
* </p>
*/
String getRealPath();
/**
* Find the content with a given entry name. This will return null if no
* matching content was found.
*/
ResourceContent getContent(String entry);
}
@@ -1,13 +0,0 @@
<html>
<head>
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=iso-8859-1">
<title>AvajeLib</title>
</head>
<body BGCOLOR="#ffffff">
Service for reading content from either the file system or servlet context.
<p>
A service used to read deployment content such as xml files, images etc
taking into account the environment (servlet war file or file system).
</p>
</body>
</html>
@@ -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);
}
}
@@ -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<String, ObjectGraphNode> nodePathMap = new HashMap<String, ObjectGraphNode>();
@@ -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) {
@@ -73,7 +73,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
queryProps.configureBeanQuery(query);
}
if (parent.isUseAutofetchManager()) {
if (parent.isUseAutoTune()) {
query.setAutofetch(true);
}
}
@@ -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<T> 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<NodeUsageListener> autoFetchManagerRef;
private final WeakReference<NodeUsageListener> profilingListenerRef;
private final Boolean readOnly;
@@ -198,11 +198,10 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
this.readOnly = request.isReadOnly();
this.autoFetchManager = query.getAutoFetchManager();
this.autoFetchProfiling = autoFetchManager != null;
this.objectGraphNode = query.getParentNode();
this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference<NodeUsageListener>(
autoFetchManager) : null;
this.profilingListener = query.getProfilingListener();
this.autoTuneProfiling = profilingListener != null;
this.profilingListenerRef = autoTuneProfiling ? new WeakReference<NodeUsageListener>(profilingListener) : null;
// set the generated sql back to the query
// so its available to the user...
@@ -549,8 +548,8 @@ public class CQuery<T> 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<T> 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<T> 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<String, String> currentPathMap) {
@@ -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<T> implements SpiQuery<T> {
*/
private transient TableJoin includeTableJoin;
private transient AutoFetchManager autoFetchManager;
private transient ProfilingListener profilingListener;
private transient BeanDescriptor<?> beanDescriptor;
@@ -489,7 +488,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
DefaultOrmQuery<T> copy = new DefaultOrmQuery<T>(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<T> implements SpiQuery<T> {
return this;
}
@Override
public DefaultOrmQuery<T> 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;
}
@@ -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<Map.Entry<String, OrmQueryProperties>> entries = fetchPaths.entrySet();
for (Map.Entry<String, OrmQueryProperties> 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);
@@ -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;
@@ -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);
}
}
@@ -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;
}
}
@@ -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.
* <p>
* This can use URL based resource loading for web applications or file based
* otherwise.
* </p>
*/
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.
* <p>
* This can be url based (for webapps) or otherwise file based.
* </p>
*/
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);
}
}
@@ -1,10 +0,0 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Resource Management</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Resource Management
</Body>
</HTML>