Add MetaInfoManager API, remove MetaQueryStatistic entity bean, refactor query execution stats collection

This commit is contained in:
Rob Bygrave
2014-01-19 22:49:18 +13:00
parent 05dcdf162b
commit 6bd8c2bd05
40 changed files with 1375 additions and 1093 deletions
@@ -11,6 +11,7 @@ import javax.persistence.OptimisticLockException;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
@@ -117,6 +118,13 @@ public interface EbeanServer {
*/
public ExpressionFactory getExpressionFactory();
/**
* Return the MetaInfoManager which is used to get meta data from the EbeanServer
* such as query execution statistics.
*/
public MetaInfoManager getMetaInfoManager();
/**
* Return the BeanState for a given entity bean.
* <p>
@@ -1,6 +1,7 @@
package com.avaje.ebean.bean;
import java.io.Serializable;
import java.util.Arrays;
/**
* Represent the call stack (stack trace elements).
@@ -34,6 +35,25 @@ public final class CallStack implements Serializable {
}
this.pathHash = enc(hc);
}
public int hashCode() {
int hc = 0;
for (int i = 0; i < callStack.length; i++) {
hc = 31 * hc + callStack[i].hashCode();
}
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CallStack)) {
return false;
}
CallStack e = (CallStack) obj;
return Arrays.equals(callStack, e.callStack);
}
/**
* Return the first element of the call stack.
@@ -1,6 +1,7 @@
package com.avaje.ebean.bean;
import java.io.Serializable;
import java.util.Objects;
/**
* Identifies a unique node of an object graph.
@@ -66,4 +67,23 @@ public final class ObjectGraphNode implements Serializable {
public String toString() {
return "origin:" + originQueryPoint + " " + ":" + path + ":" + path;
}
public int hashCode() {
int hc = 31 * originQueryPoint.hashCode();
hc = 31 * hc + Objects.hashCode(path);
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphNode)) {
return false;
}
ObjectGraphNode e = (ObjectGraphNode) obj;
return Objects.equals(e.path, path)
&& e.originQueryPoint.equals(originQueryPoint);
}
}
@@ -14,17 +14,20 @@ import java.io.Serializable;
*/
public final class ObjectGraphOrigin implements Serializable {
private static final long serialVersionUID = 410937765287968707L;
private static final long serialVersionUID = 410937765287968708L;
private final CallStack callStack;
private final String key;
private final String beanType;
private final int queryHash;
private final String key;
public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) {
this.callStack = callStack;
this.beanType = beanType;
this.queryHash = queryHash;
this.key = callStack.getOriginKey(queryHash);
}
@@ -58,4 +61,24 @@ public final class ObjectGraphOrigin implements Serializable {
return key + " " + beanType + " " + callStack.getFirstStackTraceElement();
}
public int hashCode() {
int hc = 31 * callStack.hashCode();
hc = 31 * hc + beanType.hashCode();
hc = 31 * hc + queryHash;
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphOrigin)) {
return false;
}
ObjectGraphOrigin e = (ObjectGraphOrigin) obj;
return e.queryHash == queryHash
&& e.beanType.equals(beanType)
&& e.callStack.equals(callStack);
}
}
@@ -101,13 +101,13 @@ public class MetaAutoFetchStatistic implements Serializable {
private final String path;
private final int exeCount;
private final long exeCount;
private final int totalBeanLoaded;
private final long totalBeanLoaded;
private final int totalMicros;
private final long totalMicros;
public QueryStats(String path, int exeCount, int totalBeanLoaded, int totalMicros) {
public QueryStats(String path, long exeCount, long totalBeanLoaded, long totalMicros) {
this.path = path;
this.exeCount = exeCount;
this.totalBeanLoaded = totalBeanLoaded;
@@ -125,21 +125,21 @@ public class MetaAutoFetchStatistic implements Serializable {
/**
* The number of queries executed.
*/
public int getExeCount() {
public long getExeCount() {
return exeCount;
}
/**
* The total number of beans loaded by the query.
*/
public int getTotalBeanLoaded() {
public long getTotalBeanLoaded() {
return totalBeanLoaded;
}
/**
* The total time in microseconds of the queries.
*/
public int getTotalMicros() {
public long getTotalMicros() {
return totalMicros;
}
@@ -0,0 +1,17 @@
package com.avaje.ebean.meta;
import java.util.List;
public interface MetaBeanInfo {
/**
* Collect the current query plan statistics return the non-empty statistics.
*/
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
/**
* Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions).
*/
public List<MetaBeanQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset);
}
@@ -0,0 +1,79 @@
package com.avaje.ebean.meta;
/**
* Query execution statistics Meta data.
*/
public interface MetaBeanQueryPlanStatistic {
/**
* Return the bean type this query plan is for.
*/
public Class<?> getBeanType();
/**
* Return true if this query plan was tuned by Autofetch.
*/
public boolean isAutofetchTuned();
/**
* Return the query plan hash.
*/
public int getQueryPlanHash();
/**
* Return the sql executed.
*/
public String getSql();
/**
* Return the total number of queries executed.
*/
public long getExecutionCount();
/**
* Return the total number of beans loaded by the queries.
* <p>
* This excludes background fetching.
* </p>
*/
public long getTotalLoadedBeans();
/**
* Return the total time taken by executions of this query.
*/
public long getTotalTimeMicros();
/**
* Return the max execution time for this query.
*/
public long getMaxTimeMicros();
/**
* Return the time collection started (or was last reset).
*/
public long getCollectionStart();
/**
* Return the time of the last query executed using this plan.
*/
public long getLastQueryTime();
/**
* Return the average query execution time in microseconds.
* <p>
* This excludes background fetching.
* </p>
*/
public long getAvgTimeMicros();
/**
* Return the average number of bean loaded per query.
* <p>
* This excludes background fetching.
* </p>
*/
public long getAvgLoadedBeans();
}
@@ -0,0 +1,26 @@
package com.avaje.ebean.meta;
import java.util.List;
public interface MetaInfoManager {
/**
* Return the MetaBeanInfo for a bean type.
*/
public MetaBeanInfo getMetaBeanInfo(Class<?> beanClass);
/**
* Return all the MetaBeanInfo.
*/
public List<MetaBeanInfo> getMetaBeanInfoList();
/**
* Collect and return the query plan statistics for all the beans.
* <p>
* Note that this excludes the query plan statistics where there has been no
* executions (since the last collection with reset).
* </p>
*/
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
}
@@ -1,172 +0,0 @@
package com.avaje.ebean.meta;
import java.io.Serializable;
import javax.persistence.Entity;
/**
* Query execution statistics Meta data.
*/
@Entity
public class MetaQueryStatistic implements Serializable {
private static final long serialVersionUID = -8746524372894472583L;
boolean autofetchTuned;
String beanType;
/**
* The original query plan hash (calculated prior to autofetch tuning).
*/
int origQueryPlanHash;
/**
* The final query plan hash (calculated after to autofetch tuning).
*/
int finalQueryPlanHash;
String sql;
int executionCount;
int totalLoadedBeans;
int totalTimeMicros;
long collectionStart;
long lastQueryTime;
int avgTimeMicros;
int avgLoadedBeans;
public MetaQueryStatistic() {
}
/**
* Create a MetaQueryStatistic.
*/
public MetaQueryStatistic(boolean autofetchTuned, String beanType, int plan, String sql,
int executionCount, int totalLoadedBeans, int totalTimeMicros, long collectionStart,
long lastQueryTime) {
this.autofetchTuned = autofetchTuned;
this.beanType = beanType;
this.finalQueryPlanHash = plan;
this.sql = sql;
this.executionCount = executionCount;
this.totalLoadedBeans = totalLoadedBeans;
this.totalTimeMicros = totalTimeMicros;
this.collectionStart = collectionStart;
this.lastQueryTime = lastQueryTime;
this.avgTimeMicros = executionCount == 0 ? 0 : totalTimeMicros / executionCount;
this.avgLoadedBeans = executionCount == 0 ? 0 : totalLoadedBeans / executionCount;
}
public String toString() {
return "type=" + beanType + " tuned:" + autofetchTuned + " origHash=" + origQueryPlanHash
+ " count=" + executionCount + " avgMicros=" + getAvgTimeMicros();
}
/**
* Return true if this query plan was built for Autofetch tuned queries.
*/
public boolean isAutofetchTuned() {
return autofetchTuned;
}
/**
* Return the original query plan hash (calculated prior to autofetch tuning).
* <p>
* This will return 0 if there is no autofetch profiling or tuning on this
* query.
* </p>
*/
public int getOrigQueryPlanHash() {
return origQueryPlanHash;
}
/**
* Return the queryPlanHash value. This is unique for a given query plan.
*/
public int getFinalQueryPlanHash() {
return finalQueryPlanHash;
}
/**
* Return the bean type.
*/
public String getBeanType() {
return beanType;
}
/**
* Return the sql executed.
*/
public String getSql() {
return sql;
}
/**
* Return the total number of queries executed.
*/
public int getExecutionCount() {
return executionCount;
}
/**
* Return the total number of beans loaded by the queries.
* <p>
* This excludes background fetching.
* </p>
*/
public int getTotalLoadedBeans() {
return totalLoadedBeans;
}
/**
* Return the number of times this query was executed.
*/
public int getTotalTimeMicros() {
return totalTimeMicros;
}
/**
* Return the time collection started.
*/
public long getCollectionStart() {
return collectionStart;
}
/**
* Return the time of the last query executed using this plan.
*/
public long getLastQueryTime() {
return lastQueryTime;
}
/**
* Return the average query execution time in microseconds.
* <p>
* This excludes background fetching.
* </p>
*/
public int getAvgTimeMicros() {
return avgTimeMicros;
}
/**
* Return the average number of bean loaded per query.
* <p>
* This excludes background fetching.
* </p>
*/
public int getAvgLoadedBeans() {
return avgLoadedBeans;
}
}
@@ -0,0 +1,4 @@
/**
* Meta data that can be retrieved for the EbeanServer.
*/
package com.avaje.ebean.meta;
@@ -1,17 +0,0 @@
<html>
<head>
<title>Entity Beans for getting "Meta" data from Ebean</title>
</head>
<body>
Entity Beans for getting "Meta" data from Ebean
<p>
You can query these entity beans to get "meta" data from Ebean.
This includes things like query execution statistics.
</p>
<pre class="code">
// fetch the meta data that controls autoFetch query tuning
Query<MetaAutoFetchTunedFetch> query = Ebean.createQuery(MetaAutoFetchTunedFetch.class);
List<MetaAutoFetchTunedFetch> list = query.findList();
</pre>
</body>
</html>
@@ -3,8 +3,7 @@ package com.avaje.ebeaninternal.api;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -14,22 +13,16 @@ import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
/**
* Parameters used for binding to a statement.
* <p>
* Used by FindByNativeSql and UpdateSql to support ordered and named
* parameters. Note that you can use either ordered OR named parameters.
* Supports ordered or named parameters.
* </p>
*/
public class BindParams implements Serializable {
private static final long serialVersionUID = 4541081933302086285L;
private ArrayList<Param> positionedParameters = new ArrayList<Param>();
private List<Param> positionedParameters = new ArrayList<Param>();
private HashMap<String, Param> namedParameters = new HashMap<String, Param>();
/**
* Need to create a hash when binding collection values (for in clauses).
*/
private int queryPlanHash = 1;
private Map<String, Param> namedParameters = new LinkedHashMap<String, Param>();
/**
* This is the sql. For named parameters this is the sql after the named
@@ -38,6 +31,39 @@ public class BindParams implements Serializable {
*/
private String preparedSql;
public BindParams() {
}
public int queryBindHash() {
int hc = namedParameters.hashCode();
for (int i = 0; i < positionedParameters.size(); i++) {
hc = hc * 31 + positionedParameters.get(i).hashCode();
}
return hc;
}
/**
* Return the hash that should be included with the query plan.
* <p>
* This is to handle binding collections to in clauses. The number of values
* in the collection effects the query (number of bind values) and so must be
* taken into account when calculating the query hash.
* </p>
*/
public int getQueryPlanHash() {
int hc = 31;
for (Param param : positionedParameters) {
hc = hc * 31 + param.queryBindCount();
}
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
hc = hc * 31 + entry.getKey().hashCode();
hc = hc * 31 + entry.getValue().queryBindCount();
}
return hc;
}
/**
* Return a deep copy of the BindParams.
*/
@@ -46,45 +72,12 @@ public class BindParams implements Serializable {
for (Param p : positionedParameters) {
copy.positionedParameters.add(p.copy());
}
Iterator<Entry<String, Param>> it = namedParameters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Param> entry = (Map.Entry<String, Param>) it.next();
copy.namedParameters.put(entry.getKey(), entry.getValue().copy());
for (Entry<String, Param> entry : namedParameters.entrySet()) {
copy.namedParameters.put(entry.getKey(), entry.getValue().copy());
}
return copy;
}
public int queryBindHash() {
int hc = namedParameters.hashCode();
for (int i = 0; i < positionedParameters.size(); i++) {
hc = hc * 31 + positionedParameters.get(i).hashCode();
}
return hc;
}
public int hashCode() {
int hc = getClass().hashCode();
hc = hc * 31 + namedParameters.hashCode();
for (int i = 0; i < positionedParameters.size(); i++) {
hc = hc * 31 + positionedParameters.get(i).hashCode();
}
hc = hc * 31 + (preparedSql == null ? 0 : preparedSql.hashCode());
return hc;
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
if (o instanceof BindParams) {
return hashCode() == o.hashCode();
}
return false;
}
/**
* Return true if there are no bind parameters.
*/
@@ -131,10 +124,8 @@ public class BindParams implements Serializable {
* Set an In Out parameter using position.
*/
public void setParameter(int position, Object value, int outType) {
addToQueryPlanHash(String.valueOf(position), value);
Param p = getParam(position);
Param p = getParam(position);
p.setInValue(value);
p.setOutType(outType);
}
@@ -144,10 +135,8 @@ public class BindParams implements Serializable {
* must use setNullParameter.
*/
public void setParameter(int position, Object value) {
addToQueryPlanHash(String.valueOf(position), value);
Param p = getParam(position);
Param p = getParam(position);
p.setInValue(value);
}
@@ -182,10 +171,8 @@ public class BindParams implements Serializable {
* Set a named In Out parameter.
*/
public void setParameter(String name, Object value, int outType) {
addToQueryPlanHash(name, value);
Param p = getParam(name);
Param p = getParam(name);
p.setInValue(value);
p.setOutType(outType);
}
@@ -203,48 +190,22 @@ public class BindParams implements Serializable {
*/
public Param setParameter(String name, Object value) {
addToQueryPlanHash(name, value);
Param p = getParam(name);
Param p = getParam(name);
p.setInValue(value);
return p;
}
/**
* For binding collections calculate a hash to be used for the query plan.
*/
private void addToQueryPlanHash(String name, Object value){
if (value != null){
if (value instanceof Collection<?>){
queryPlanHash = queryPlanHash * 31 + name.hashCode();
queryPlanHash = queryPlanHash * 31 + ((Collection<?>)value).size();
}
}
}
/**
* Return the hash that should be included with the query plan.
* <p>
* This is to handle binding collections to in clauses. The number
* of values in the collection effects the query (number of bind values)
* and so must be taken into account when calculating the query hash.
* </p>
*/
public int getQueryPlanHash() {
return queryPlanHash;
}
/**
* Set an encryption key as a bind value.
* <p>
* Needs special treatment as the value should not be included in a log.
* </p>
*/
public Param setEncryptionKey(String name, Object value) {
Param p = getParam(name);
p.setEncryptionKey(value);
return p;
}
/**
* Set an encryption key as a bind value.
* <p>
* Needs special treatment as the value should not be included in a log.
* </p>
*/
public Param setEncryptionKey(String name, Object value) {
Param p = getParam(name);
p.setEncryptionKey(value);
return p;
}
/**
* Register the named parameter as an Out parameter.
@@ -300,9 +261,9 @@ public class BindParams implements Serializable {
*/
public static final class OrderedList {
final List<Param> paramList;
private final List<Param> paramList;
final StringBuilder preparedSql;
private final StringBuilder preparedSql;
public OrderedList() {
this(new ArrayList<Param>());
@@ -373,6 +334,16 @@ public class BindParams implements Serializable {
public Param() {
}
public int queryBindCount() {
if (inValue == null) {
return 0;
}
if (inValue instanceof Collection<?>){
return ((Collection<?>)inValue).size();
}
return 1;
}
/**
* Create a deep copy of the Param.
*/
@@ -448,14 +419,14 @@ public class BindParams implements Serializable {
this.isInParam = true;
}
/**
* Set an encryption key (which can not be logged).
*/
public void setEncryptionKey(Object in) {
this.inValue = in;
this.isInParam = true;
this.encryptionKey = true;
}
/**
* Set an encryption key (which can not be logged).
*/
public void setEncryptionKey(Object in) {
this.inValue = in;
this.isInParam = true;
this.encryptionKey = true;
}
/**
* Specify that the In parameter is NULL and the specific type that it
@@ -506,12 +477,12 @@ public class BindParams implements Serializable {
this.textLocation = textLocation;
}
/**
* If true do not include this value in a transaction log.
*/
public boolean isEncryptionKey() {
return encryptionKey;
}
/**
* If true do not include this value in a transaction log.
*/
public boolean isEncryptionKey() {
return encryptionKey;
}
}
}
@@ -224,7 +224,7 @@ public interface AutoFetchManager extends NodeUsageListener {
* @param micros
* the query executing time in microseconds
*/
public void collectQueryInfo(ObjectGraphNode node, int beans, int micros);
public void collectQueryInfo(ObjectGraphNode node, long beans, long micros);
/**
@@ -61,6 +61,7 @@ public class AutoFetchManagerFactory {
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()+"]");
@@ -527,7 +527,7 @@ public class DefaultAutoFetchManager implements AutoFetchManager, Serializable {
* 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, int beans, int micros) {
public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) {
if (node != null){
ObjectGraphOrigin origin = node.getOriginQueryPoint();
@@ -127,7 +127,7 @@ public class Statistics implements Serializable {
}
public void collectQueryInfo(ObjectGraphNode node, int beansLoaded, int micros) {
public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) {
synchronized (monitor) {
String key = node.getPath();
@@ -13,11 +13,11 @@ public class StatisticsQuery implements Serializable {
private final String path;
private int exeCount;
private long exeCount;
private int totalBeanLoaded;
private long totalBeanLoaded;
private int totalMicros;
private long totalMicros;
public StatisticsQuery(String path){
this.path = path;
@@ -27,7 +27,7 @@ public class StatisticsQuery implements Serializable {
return new QueryStats(path, exeCount, totalBeanLoaded, totalMicros);
}
public void add(int beansLoaded, int micros) {
public void add(long beansLoaded, long micros) {
exeCount++;
totalBeanLoaded += beansLoaded;
totalMicros += micros;
@@ -1,79 +0,0 @@
package com.avaje.ebeaninternal.server.bean;
import java.util.Iterator;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.common.BeanList;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaAutoFetchStatistic;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.autofetch.Statistics;
/**
* Bean Finder for MetaAutoFetchStatistic.
* <p>
* This gets the meta data from the AutoFetchManager and creates a copy of that
* data to give back to the caller in the form of MetaAutoFetchStatistic beans.
* </p>
*/
public class BFAutoFetchStatisticFinder implements BeanFinder<MetaAutoFetchStatistic> {
public MetaAutoFetchStatistic find(BeanQueryRequest<MetaAutoFetchStatistic> request) {
SpiQuery<MetaAutoFetchStatistic> query = (SpiQuery<MetaAutoFetchStatistic>)request.getQuery();
try {
String queryPointKey = (String) query.getId();
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
Statistics stats = manager.getStatistics(queryPointKey);
if (stats != null) {
return stats.createPublicMeta();
} else {
return null;
}
} catch (Exception e) {
throw new PersistenceException(e);
}
}
/**
* Only returns Lists at this stage.
*/
public BeanCollection<MetaAutoFetchStatistic> findMany(BeanQueryRequest<MetaAutoFetchStatistic> request) {
SpiQuery.Type queryType = ((SpiQuery<?>)request.getQuery()).getType();
if (!queryType.equals(SpiQuery.Type.LIST)) {
throw new PersistenceException("Only findList() supported at this stage.");
}
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
BeanList<MetaAutoFetchStatistic> list = new BeanList<MetaAutoFetchStatistic>();
Iterator<Statistics> it = manager.iterateStatistics();
while (it.hasNext()) {
Statistics stats = it.next();
// create a copy for public use
list.add(stats.createPublicMeta());
}
String orderBy = request.getQuery().order().toStringFormat();
if (orderBy == null){
orderBy = "beanType";
}
server.sort(list, orderBy);
return list;
}
}
@@ -1,76 +0,0 @@
package com.avaje.ebeaninternal.server.bean;
import java.util.Iterator;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.common.BeanList;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaAutoFetchTunedQueryInfo;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.autofetch.TunedQueryInfo;
/**
* BeanFinder for MetaAutoFetchTunedFetch.
*/
public class BFAutoFetchTunedFetchFinder implements BeanFinder<MetaAutoFetchTunedQueryInfo> {
public MetaAutoFetchTunedQueryInfo find(BeanQueryRequest<MetaAutoFetchTunedQueryInfo> request) {
SpiQuery<?> query = (SpiQuery<?>)request.getQuery();
try {
String queryPointKey = (String)query.getId();
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
TunedQueryInfo tunedFetch = manager.getTunedQueryInfo(queryPointKey);
if (tunedFetch != null){
return tunedFetch.createPublicMeta();
} else {
return null;
}
} catch (Exception e){
throw new PersistenceException(e);
}
}
/**
* Only returns Lists at this stage.
*/
public BeanCollection<MetaAutoFetchTunedQueryInfo> findMany(BeanQueryRequest<MetaAutoFetchTunedQueryInfo> request) {
SpiQuery.Type queryType = ((SpiQuery<?>)request.getQuery()).getType();
if (!queryType.equals(SpiQuery.Type.LIST)){
throw new PersistenceException("Only findList() supported at this stage.");
}
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
BeanList<MetaAutoFetchTunedQueryInfo> list = new BeanList<MetaAutoFetchTunedQueryInfo>();
Iterator<TunedQueryInfo> it = manager.iterateTunedQueryInfo();
while (it.hasNext()) {
TunedQueryInfo tunedFetch = it.next();
// create a copy for public use
list.add(tunedFetch.createPublicMeta());
}
String orderBy = request.getQuery().order().toStringFormat();
if (orderBy == null){
orderBy = "beanType, origQueryPlanHash";
}
server.sort(list, orderBy);
return list;
}
}
@@ -1,69 +0,0 @@
package com.avaje.ebeaninternal.server.bean;
import java.util.Iterator;
import java.util.List;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.common.BeanList;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaQueryStatistic;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQueryPlan;
/**
* BeanFinder for MetaQueryStatistic.
*/
public class BFQueryStatisticFinder implements BeanFinder<MetaQueryStatistic> {
public MetaQueryStatistic find(BeanQueryRequest<MetaQueryStatistic> request) {
throw new RuntimeException("Not Supported yet");
}
/**
* Only returns Lists at this stage.
*/
public BeanCollection<MetaQueryStatistic> findMany(BeanQueryRequest<MetaQueryStatistic> request) {
SpiQuery.Type queryType = ((SpiQuery<?>)request.getQuery()).getType();
if (!queryType.equals(SpiQuery.Type.LIST)){
throw new PersistenceException("Only findList() supported at this stage.");
}
BeanList<MetaQueryStatistic> list = new BeanList<MetaQueryStatistic>();
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
build(list, server);
String orderBy = request.getQuery().order().toStringFormat();
if (orderBy == null){
orderBy = "beanType, origQueryPlanHash, autofetchTuned";
}
server.sort(list, orderBy);
return list;
}
private void build(List<MetaQueryStatistic> list, SpiEbeanServer server) {
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
desc.clearQueryStatistics();
build(list, desc);
}
}
private void build(List<MetaQueryStatistic> list, BeanDescriptor<?> desc) {
Iterator<CQueryPlan> it = desc.queryPlans();
while (it.hasNext()) {
CQueryPlan queryPlan = (CQueryPlan) it.next();
list.add(queryPlan.createMetaQueryStatistic(desc.getFullName()));
}
}
}
@@ -1,8 +0,0 @@
<html>
<head>
<title>BeanFinders, BeanControllers etc for "meta" beans</title>
</head>
<body>
BeanFinders, BeanControllers etc for "meta" beans
</body>
</html>
@@ -0,0 +1,44 @@
package com.avaje.ebeaninternal.server.core;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebean.meta.MetaInfoManager;
/**
* DefaultServer based implementation of MetaInfoManager.
*/
public class DefaultMetaInfoManager implements MetaInfoManager {
private final DefaultServer server;
public DefaultMetaInfoManager(DefaultServer server) {
this.server = server;
}
@Override
public MetaBeanInfo getMetaBeanInfo(Class<?> beanClass) {
return server.getBeanDescriptor(beanClass);
}
@Override
public List<MetaBeanInfo> getMetaBeanInfoList() {
return new ArrayList<MetaBeanInfo>(server.getBeanDescriptors());
}
@Override
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
List<MetaBeanQueryPlanStatistic> list = new ArrayList<MetaBeanQueryPlanStatistic>();
for (MetaBeanInfo metaBeanInfo : getMetaBeanInfoList()) {
list.addAll(metaBeanInfo.collectQueryPlanStatistics(reset));
}
return list;
}
}
@@ -57,6 +57,8 @@ import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebean.text.json.JsonElement;
@@ -168,6 +170,8 @@ public final class DefaultServer implements SpiEbeanServer {
private final JsonContext jsonContext;
private final MetaInfoManager metaInfoManager;
/**
* The MBean name used to register Ebean.
*/
@@ -208,6 +212,7 @@ public final class DefaultServer implements SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.metaInfoManager = new DefaultMetaInfoManager(this);
this.serverCacheManager = cache;
this.pstmtBatch = config.getPstmtBatch();
this.databasePlatform = config.getDatabasePlatform();
@@ -298,6 +303,11 @@ public final class DefaultServer implements SpiEbeanServer {
public DatabasePlatform getDatabasePlatform() {
return databasePlatform;
}
@Override
public MetaInfoManager getMetaInfoManager() {
return metaInfoManager;
}
public BackgroundExecutor getBackgroundExecutor() {
return backgroundExecutor;
@@ -1927,6 +1937,13 @@ public final class DefaultServer implements SpiEbeanServer {
return beanDescriptorManager.getBeanDescriptorList();
}
public List<MetaBeanInfo> getMetaBeanInfoList() {
List<MetaBeanInfo> list = new ArrayList<MetaBeanInfo>();
list.addAll(getBeanDescriptors());
return list;
}
public void register(BeanPersistController c) {
List<BeanDescriptor<?>> list = beanDescriptorManager.getBeanDescriptorList();
for (int i = 0; i < list.size(); i++) {
@@ -389,4 +389,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
}
}
public void flushPersistenceContextOnIterate() {
beanDescriptor.flushPersistenceContextOnIterate(persistenceContext);
}
}
@@ -15,6 +15,9 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.PersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.Query;
import com.avaje.ebean.Query.UseIndex;
import com.avaje.ebean.SqlUpdate;
@@ -33,6 +36,8 @@ import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriteBeanVisitor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
@@ -59,6 +64,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.persist.DmlUtil;
import com.avaje.ebeaninternal.server.query.CQueryPlan;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.reflect.BeanReflect;
@@ -72,13 +78,10 @@ import com.avaje.ebeaninternal.util.SortByClause;
import com.avaje.ebeaninternal.util.SortByClause.Property;
import com.avaje.ebeaninternal.util.SortByClauseParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> {
public class BeanDescriptor<T> implements MetaBeanInfo {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
@@ -1149,6 +1152,27 @@ public class BeanDescriptor<T> {
return new DeployUpdateParser(this).parse(ormUpdateStatement);
}
@Override
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
return collectQueryPlanStatisticsInternal(reset, false);
}
@Override
public List<MetaBeanQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset) {
return collectQueryPlanStatisticsInternal(reset, false);
}
public List<MetaBeanQueryPlanStatistic> collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) {
List<MetaBeanQueryPlanStatistic> list = new ArrayList<MetaBeanQueryPlanStatistic>(queryPlanCache.size());
for (CQueryPlan queryPlan : queryPlanCache.values()) {
Snapshot snapshot = queryPlan.getSnapshot(reset);
if (collectAll || snapshot.getExecutionCount() > 0) {
list.add(snapshot);
}
}
return list;
}
/**
* Reset the statistics on all the query plans.
*/
@@ -2445,5 +2469,13 @@ public class BeanDescriptor<T> {
return false;
}
public void flushPersistenceContextOnIterate(PersistenceContext persistenceContext) {
persistenceContext.clear(beanType);
for (int i = 0; i < propertiesMany.length; i++) {
persistenceContext.clear(propertiesMany[i].getBeanDescriptor().getBeanType());
}
}
}
@@ -126,6 +126,11 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
return context.serverName;
}
@Override
public String getFullPath() {
return context.fullPath;
}
@Override
public BeanDescriptor<?> getBeanDescriptor() {
return context.desc;
@@ -171,10 +176,6 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
context.desc.getEbeanServer().loadBean(req);
}
@Override
public String getFullPath() {
return context.fullPath;
}
}
}
@@ -6,6 +6,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.persistence.PersistenceException;
@@ -39,6 +40,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.DataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -204,8 +206,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
private final CQueryPlan queryPlan;
private long startNano;
private final Mode queryMode;
private final boolean autoFetchProfiling;
@@ -213,14 +213,17 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
private final ObjectGraphNode autoFetchParentNode;
private final AutoFetchManager autoFetchManager;
private final WeakReference<NodeUsageListener> autoFetchManagerRef;
private int executionTimeMicros;
private final Boolean readOnly;
private final SpiExpressionList<?> filterMany;
private long startNano;
private long executionTimeMicros;
/**
* Create the Sql select based on the request.
*/
@@ -520,7 +523,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
}
}
public int getQueryExecutionTimeMicros() {
public long getQueryExecutionTimeMicros() {
return executionTimeMicros;
}
@@ -638,7 +641,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
protected void updateExecutionStatistics() {
try {
long exeNano = System.nanoTime() - startNano;
executionTimeMicros = (int) exeNano / 1000;
executionTimeMicros = TimeUnit.NANOSECONDS.toMicros(exeNano);
if (autoFetchProfiling) {
autoFetchManager
@@ -674,7 +677,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
}
protected boolean hasNextBean(boolean inForeground) throws SQLException {
if (!readBeanInternal(inForeground)) {
return false;
@@ -115,7 +115,7 @@ public class CQueryBuilder implements Constants {
String sql = s.getSql();
// cache the query plan
queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryFetchIds(request, predicates, sql, backgroundExecutor);
@@ -171,7 +171,7 @@ public class CQueryBuilder implements Constants {
}
// cache the query plan
queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryRowCount(request, predicates, sql);
@@ -9,38 +9,37 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* QueryIterator that does not require a buffer for secondary queries.
*
* @author rbygrave
*/
class CQueryIteratorSimple<T> implements QueryIterator<T> {
private final CQuery<T> cquery;
private final OrmQueryRequest<T> request;
private final CQuery<T> cquery;
private final OrmQueryRequest<T> request;
CQueryIteratorSimple(CQuery<T> cquery, OrmQueryRequest<T> request){
this.cquery = cquery;
this.request = request;
}
public boolean hasNext() {
try {
return cquery.hasNextBean(true);
} catch (SQLException e){
throw cquery.createPersistenceException(e);
}
}
CQueryIteratorSimple(CQuery<T> cquery, OrmQueryRequest<T> request) {
this.cquery = cquery;
this.request = request;
}
public T next() {
return cquery.getLoadedBean();
public boolean hasNext() {
try {
request.flushPersistenceContextOnIterate();
return cquery.hasNextBean(true);
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
}
}
public void close() {
cquery.updateExecutionStatistics();
cquery.close();
request.endTransIfRequired();
}
public T next() {
return cquery.getLoadedBean();
}
public void remove() {
throw new PersistenceException("Remove not allowed");
}
public void close() {
cquery.updateExecutionStatistics();
cquery.close();
request.endTransIfRequired();
}
public void remove() {
throw new PersistenceException("Remove not allowed");
}
}
@@ -10,58 +10,58 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* A QueryIterator that uses a buffer to execute secondary queries periodically.
*
* @author rbygrave
*/
class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
private final CQuery<T> cquery;
private final int bufferSize;
private final OrmQueryRequest<T> request;
private final ArrayList<T> buffer;
private final CQuery<T> cquery;
private final int bufferSize;
private final OrmQueryRequest<T> request;
private final ArrayList<T> buffer;
private boolean moreToLoad = true;
private boolean moreToLoad = true;
CQueryIteratorWithBuffer(CQuery<T> cquery, OrmQueryRequest<T> request, int bufferSize) {
this.cquery = cquery;
this.request = request;
this.bufferSize = bufferSize;
this.buffer = new ArrayList<T>(bufferSize);
}
CQueryIteratorWithBuffer(CQuery<T> cquery, OrmQueryRequest<T> request, int bufferSize) {
this.cquery = cquery;
this.request = request;
this.bufferSize = bufferSize;
this.buffer = new ArrayList<T>(bufferSize);
}
public boolean hasNext() {
try {
if (buffer.isEmpty() && moreToLoad) {
// load buffer
int i = -1;
while (moreToLoad && ++i < bufferSize) {
if (cquery.hasNextBean(true)) {
buffer.add(cquery.getLoadedBean());
} else {
moreToLoad = false;
}
}
// execute secondary queries
request.executeSecondaryQueries(bufferSize);
}
return !buffer.isEmpty();
public boolean hasNext() {
try {
if (buffer.isEmpty() && moreToLoad) {
// load buffer
request.flushPersistenceContextOnIterate();
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
int i = -1;
while (moreToLoad && ++i < bufferSize) {
if (cquery.hasNextBean(true)) {
buffer.add(cquery.getLoadedBean());
} else {
moreToLoad = false;
}
}
}
// execute secondary queries
request.executeSecondaryQueries(bufferSize);
}
return !buffer.isEmpty();
public T next() {
return buffer.remove(0);
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
}
}
public void close() {
cquery.updateExecutionStatistics();
cquery.close();
request.endTransIfRequired();
}
public T next() {
return buffer.remove(0);
}
public void remove() {
throw new PersistenceException("Remove not allowed");
}
public void close() {
cquery.updateExecutionStatistics();
cquery.close();
request.endTransIfRequired();
}
public void remove() {
throw new PersistenceException("Remove not allowed");
}
}
@@ -4,9 +4,9 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import com.avaje.ebean.config.dbplatform.SqlLimitResponse;
import com.avaje.ebean.meta.MetaQueryStatistic;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.DataReader;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
@@ -51,7 +51,9 @@ public class CQueryPlan {
*/
private final BeanProperty[] encryptedProps;
private CQueryStats queryStats = new CQueryStats();
private final CQueryPlanStats stats;
private final Class<?> beanType;
/**
* Create a query plan based on a OrmQueryRequest.
@@ -59,6 +61,8 @@ public class CQueryPlan {
public CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree,
boolean rawSql, String logWhereSql, String luceneQueryDescription) {
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this);
this.hash = request.getQueryPlanHash();
this.autofetchTuned = request.getQuery().isAutofetchTuned();
if (sqlRes != null){
@@ -77,9 +81,11 @@ public class CQueryPlan {
/**
* Create a query plan for a raw sql query.
*/
public CQueryPlan(String sql, SqlTree sqlTree,
public CQueryPlan(Class<?> beanType, String sql, SqlTree sqlTree,
boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
this.beanType = beanType;
this.stats = new CQueryPlanStats(this);
this.hash = 0;
this.autofetchTuned = false;
this.sql = sql;
@@ -90,23 +96,27 @@ public class CQueryPlan {
this.encryptedProps = sqlTree.getEncryptedProps();
}
public boolean isLucene() {
return false;
public String toString() {
return beanType+" hash:"+hash;
}
public DataReader createDataReader(ResultSet rset){
return new RsetDataReader(rset);
}
public Class<?> getBeanType() {
return beanType;
}
public void bindEncryptedProperties(DataBind dataBind) throws SQLException {
if (encryptedProps != null){
for (int i = 0; i < encryptedProps.length; i++) {
String key = encryptedProps[i].getEncryptKey().getStringValue();
dataBind.setString(key);
}
}
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(rset);
}
public void bindEncryptedProperties(DataBind dataBind) throws SQLException {
if (encryptedProps != null) {
for (int i = 0; i < encryptedProps.length; i++) {
String key = encryptedProps[i].getEncryptKey().getStringValue();
dataBind.setString(key);
}
}
}
public boolean isAutofetchTuned() {
return autofetchTuned;
@@ -140,33 +150,33 @@ public class CQueryPlan {
* Reset the query statistics.
*/
public void resetStatistics() {
queryStats = new CQueryStats();
stats.reset();
}
/**
* Register an execution time against this query plan;
*/
public void executionTime(int loadedBeanCount, int timeMicros) {
// Atomic operation
queryStats = queryStats.add(loadedBeanCount, timeMicros);
public void executionTime(long loadedBeanCount, long timeMicros) {
stats.add(loadedBeanCount, timeMicros);
}
public Snapshot getSnapshot(boolean reset) {
return stats.getSnapshot(reset);
}
/**
* Return the current query statistics.
*/
public CQueryStats getQueryStats() {
return queryStats;
public CQueryPlanStats getQueryStats() {
return stats;
}
/**
* Return the time this query plan was last used.
*/
public long getLastQueryTime(){
return queryStats.getLastQueryTime();
return stats.getLastQueryTime();
}
public MetaQueryStatistic createMetaQueryStatistic(String beanName) {
return queryStats.createMetaQueryStatistic(beanName, this);
}
}
@@ -0,0 +1,154 @@
package com.avaje.ebeaninternal.server.query;
import java.util.concurrent.atomic.AtomicLong;
import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic;
import com.avaje.ebeaninternal.server.util.LongAdder;
/**
* Statistics for a specific query plan that can accumulate.
*/
public final class CQueryPlanStats {
private final CQueryPlan queryPlan;
private final LongAdder count = new LongAdder();
private final LongAdder totalTime = new LongAdder();
private final LongAdder totalBeans = new LongAdder();
private final AtomicLong maxTime = new AtomicLong();
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
private long lastQueryTime;
public CQueryPlanStats(CQueryPlan queryPlan) {
this.queryPlan = queryPlan;
}
public void add(long loadedBeanCount, long timeMicros) {
count.increment();
totalBeans.add(loadedBeanCount);
totalTime.add(timeMicros);
if (timeMicros > maxTime.get()) {
// effectively a high water mark
maxTime.set(timeMicros);
}
lastQueryTime = System.currentTimeMillis();
}
public void reset() {
count.reset();
totalBeans.reset();
totalTime.reset();
maxTime.set(0);
startTime.set(System.currentTimeMillis());
}
public long getLastQueryTime() {
return lastQueryTime;
}
public Snapshot getSnapshot(boolean reset) {
// not guaranteed to be consistent - time gaps between getting each value
if (reset) {
return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getAndSet(0), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime);
}
return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime);
}
/**
* A snapshot of the current statistics for a query plan.
*/
public static class Snapshot implements MetaBeanQueryPlanStatistic {
private final CQueryPlan queryPlan;
private final long count;
private final long totalTime;
private final long totalBeans;
private final long maxTime;
private final long startTime;
private final long lastQueryTime;
public Snapshot(CQueryPlan queryPlan, long count, long totalTime, long totalBeans, long maxTime, long startTime, long lastQueryTime) {
super();
this.queryPlan = queryPlan;
this.count = count;
this.totalTime = totalTime;
this.totalBeans = totalBeans;
this.maxTime = maxTime;
this.startTime = startTime;
this.lastQueryTime = lastQueryTime;
}
public String toString() {
return queryPlan+" count:"+count+" time:"+totalTime+" maxTime:"+maxTime+" beans:"+totalBeans+" start:"+startTime+" lastQuery:"+lastQueryTime;
}
@Override
public Class<?> getBeanType() {
return queryPlan.getBeanType();
}
@Override
public long getExecutionCount() {
return count;
}
@Override
public long getTotalTimeMicros() {
return totalTime;
}
@Override
public long getTotalLoadedBeans() {
return totalBeans;
}
@Override
public long getMaxTimeMicros() {
return maxTime;
}
@Override
public long getCollectionStart() {
return startTime;
}
@Override
public long getLastQueryTime() {
return lastQueryTime;
}
@Override
public boolean isAutofetchTuned() {
return queryPlan.isAutofetchTuned();
}
@Override
public int getQueryPlanHash() {
return queryPlan.getHash();
}
@Override
public String getSql() {
return queryPlan.getSql();
}
@Override
public long getAvgTimeMicros() {
return count < 1 ? 0 : totalTime / count;
}
@Override
public long getAvgLoadedBeans() {
return count < 1 ? 0 : totalBeans / count;
}
}
}
@@ -1,77 +0,0 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.meta.MetaQueryStatistic;
/**
* Statistics for query plan that can accumulate.
*/
public final class CQueryStats {
private final int count;
private final int totalLoadedBeanCount;
private final int totalTimeMicros;
private final long startCollecting;
private final long lastQueryTime;
public CQueryStats() {
count = 0;
totalLoadedBeanCount = 0;
totalTimeMicros = 0;
startCollecting = System.currentTimeMillis();
lastQueryTime = 0;
}
/**
* Accumulate/Increment the statistics based on the previous statistics.
*/
public CQueryStats(CQueryStats previous, int loadedBeanCount, int timeMicros) {
count = previous.count + 1;
totalLoadedBeanCount = previous.totalLoadedBeanCount + loadedBeanCount;
totalTimeMicros = previous.totalTimeMicros + timeMicros;
startCollecting = previous.startCollecting;
lastQueryTime = System.currentTimeMillis();
}
public CQueryStats add(int loadedBeanCount, int timeMicros) {
return new CQueryStats(this, loadedBeanCount, timeMicros);
}
public int getCount() {
return count;
}
public int getAverageTimeMicros() {
if (count == 0) {
return 0;
} else {
return totalTimeMicros / count;
}
}
public int getTotalLoadedBeanCount() {
return totalLoadedBeanCount;
}
public int getTotalTimeMicros() {
return totalTimeMicros;
}
public long getStartCollecting() {
return startCollecting;
}
public long getLastQueryTime() {
return lastQueryTime;
}
public MetaQueryStatistic createMetaQueryStatistic(String beanName, CQueryPlan qp) {
return new MetaQueryStatistic(qp.isAutofetchTuned(), beanName, qp.getHash(),
qp.getSql(), count, totalLoadedBeanCount, totalTimeMicros, startCollecting, lastQueryTime);
}
}
@@ -83,7 +83,7 @@ public class RawSqlSelectClauseBuilder {
SqlTree sqlTree = sqlSelect.getSqlTree();
CQueryPlan queryPlan = new CQueryPlan(sql, sqlTree, true, includeRowNumColumn, "");
CQueryPlan queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, true, includeRowNumColumn, "");
CQuery<T> compiledQuery = new CQuery<T>(request, predicates, queryPlan);
return compiledQuery;
@@ -112,6 +112,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private OrderBy<T> orderBy;
private String loadMode;
private String loadDescription;
private String generatedSql;
@@ -127,7 +128,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private String lazyLoadProperty;
private String lazyLoadManyPath;
private String lazyLoadManyPath;
/**
* Set to true if you want a DISTINCT query.
@@ -172,6 +173,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private boolean usageProfiling = true;
private boolean loadBeanCache;
private Boolean useBeanCache;
private Boolean useQueryCache;
@@ -2,9 +2,7 @@ package com.avaje.ebeaninternal.server.transaction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.avaje.ebean.bean.PersistenceContext;
@@ -17,8 +15,9 @@ import com.avaje.ebeaninternal.api.Monitor;
* id.
* </p>
* <p>
* PersistenceContext lives on a Transaction and as such is expected to only have
* a single thread accessing it at a time. This is not expected to be used concurrently.
* PersistenceContext lives on a Transaction and as such is expected to only
* have a single thread accessing it at a time. This is not expected to be used
* concurrently.
* </p>
* <p>
* Duplicate beans are ones having the same type and unique id value. These are
@@ -28,166 +27,172 @@ import com.avaje.ebeaninternal.api.Monitor;
*/
public final class DefaultPersistenceContext implements PersistenceContext {
/**
* Map used hold caches. One cache per bean type.
*/
private final HashMap<String,ClassContext> typeCache = new HashMap<String,ClassContext>();
/**
* Map used hold caches. One cache per bean type.
*/
private final HashMap<String, ClassContext> typeCache = new HashMap<String, ClassContext>();
private final Monitor monitor = new Monitor();
/**
* Create a new PersistanceContext.
*/
public DefaultPersistenceContext() {
}
private final Monitor monitor = new Monitor();
/**
* Set an object into the PersistanceContext.
*/
public void put(Object id, Object bean) {
synchronized (monitor) {
getClassContext(bean.getClass()).put(id, bean);
}
}
public Object putIfAbsent(Object id, Object bean){
synchronized (monitor) {
return getClassContext(bean.getClass()).putIfAbsent(id, bean);
}
}
/**
* Create a new PersistanceContext.
*/
public DefaultPersistenceContext() {
}
/**
* Return an object given its type and unique id.
*/
public Object get(Class<?> beanType, Object id) {
synchronized (monitor) {
return getClassContext(beanType).get(id);
}
/**
* Set an object into the PersistanceContext.
*/
public void put(Object id, Object bean) {
synchronized (monitor) {
getClassContext(bean.getClass()).put(id, bean);
}
public WithOption getWithOption(Class<?> beanType, Object id) {
synchronized (monitor) {
return getClassContext(beanType).getWithOption(id);
}
public Object putIfAbsent(Object id, Object bean) {
synchronized (monitor) {
return getClassContext(bean.getClass()).putIfAbsent(id, bean);
}
}
/**
* Return an object given its type and unique id.
*/
public Object get(Class<?> beanType, Object id) {
synchronized (monitor) {
return getClassContext(beanType).get(id);
}
}
public WithOption getWithOption(Class<?> beanType, Object id) {
synchronized (monitor) {
return getClassContext(beanType).getWithOption(id);
}
}
/**
* Return the number of beans of the given type in the persistence context.
*/
public int size(Class<?> beanType) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
return classMap == null ? 0 : classMap.size();
}
}
/**
* Clear the PersistenceContext.
*/
public void clear() {
synchronized (monitor) {
typeCache.clear();
}
}
public void clear(Class<?> beanType) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
if (classMap != null) {
classMap.clear();
}
}
}
/**
* Return the number of beans of the given type in the persistence context.
*/
public int size(Class<?> beanType) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
return classMap == null ? 0 : classMap.size();
}
}
/**
* Clear the PersistenceContext.
*/
public void clear() {
synchronized (monitor) {
typeCache.clear();
}
}
public void clear(Class<?> beanType) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
if (classMap != null) {
classMap.clear();
}
}
}
public void deleted(Class<?> beanType, Object id) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
if (classMap != null && id != null) {
classMap.deleted(id);
}
public void deleted(Class<?> beanType, Object id) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
if (classMap != null && id != null) {
classMap.deleted(id);
}
}
public void clear(Class<?> beanType, Object id) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
if (classMap != null && id != null) {
classMap.remove(id);
}
}
}
public void clear(Class<?> beanType, Object id) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(beanType.getName());
if (classMap != null && id != null) {
classMap.remove(id);
}
}
}
public String toString() {
synchronized (monitor) {
return typeCache.toString();
}
}
private ClassContext getClassContext(Class<?> beanType) {
String clsName = beanType.getName();
ClassContext classMap = typeCache.get(clsName);
if (classMap == null) {
classMap = new ClassContext();
typeCache.put(clsName, classMap);
}
return classMap;
}
private static class ClassContext {
private final Map<Object, Object> map = new HashMap<Object, Object>();
private Set<Object> deleteSet;
private ClassContext() {
}
public String toString() {
synchronized (monitor) {
StringBuilder sb = new StringBuilder();
Iterator<Entry<String, ClassContext>> it = typeCache.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, ClassContext> entry = it.next();
if (entry.getValue().size() > 0){
sb.append(entry.getKey()+":"+entry.getValue().size()+"; ");
}
}
return sb.toString();
}
}
private ClassContext getClassContext(Class<?> beanType) {
String clsName = beanType.getName();
ClassContext classMap = typeCache.get(clsName);
if (classMap == null) {
classMap = new ClassContext();
typeCache.put(clsName, classMap);
}
return classMap;
return "size:" + map.size();
}
private static class ClassContext {
private final WeakValueMap<Object, Object> map = new WeakValueMap<Object, Object>();
private Set<Object> deleteSet;
private WithOption getWithOption(Object id){
if (deleteSet != null && deleteSet.contains(id)) {
return WithOption.DELETED;
}
Object bean = map.get(id);
return (bean == null) ? null : new WithOption(bean);
}
private Object get(Object id){
return map.get(id);
}
private Object putIfAbsent(Object id, Object bean){
return map.putIfAbsent(id, bean);
}
private void put(Object id, Object b){
map.put(id, b);
}
private int size() {
return map.size();
}
private void clear(){
map.clear();
}
private Object remove(Object id){
return map.remove(id);
}
private void deleted(Object id){
if (deleteSet == null) {
deleteSet = new HashSet<Object>();
}
deleteSet.add(id);
map.remove(id);
private WithOption getWithOption(Object id) {
if (deleteSet != null && deleteSet.contains(id)) {
return WithOption.DELETED;
}
Object bean = map.get(id);
return (bean == null) ? null : new WithOption(bean);
}
private Object get(Object id) {
return map.get(id);
}
private Object putIfAbsent(Object id, Object bean) {
Object existingValue = map.get(id);
if (existingValue != null) {
// it is not absent
return existingValue;
}
// put the new value and return null indicating the put was successful
map.put(id, bean);
return null;
}
private void put(Object id, Object b) {
map.put(id, b);
}
private int size() {
return map.size();
}
private void clear() {
map.clear();
}
private Object remove(Object id) {
return map.remove(id);
}
private void deleted(Object id) {
if (deleteSet == null) {
deleteSet = new HashSet<Object>();
}
deleteSet.add(id);
map.remove(id);
}
}
}
@@ -1,133 +0,0 @@
package com.avaje.ebeaninternal.server.transaction;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
/**
* A Weak value map designed for use with DefaultPersistenceContext.
* <p>
* This provides the mechanism where entries in the persistence context will be
* automatically removed when they are not referenced externally.
* </p>
*
* @author mario, rbygrave
*/
public class WeakValueMap<K, V> {
protected final ReferenceQueue<V> refQueue = new ReferenceQueue<V>();
/**
* Backing map.
*/
private final Map<K, WeakReferenceWithKey<K, V>> backing;
/**
* Hold the key with the value for expunge purposes.
*/
private static class WeakReferenceWithKey<K, V> extends WeakReference<V> {
private final K key;
public WeakReferenceWithKey(K key, V referent, ReferenceQueue<? super V> q) {
super(referent, q);
this.key = key;
}
public K getKey() {
return key;
}
}
public WeakValueMap() {
this.backing = new HashMap<K, WeakReferenceWithKey<K, V>>();
}
private WeakReferenceWithKey<K, V> createReference(K key, V value) {
return new WeakReferenceWithKey<K, V>(key, value, refQueue);
}
@SuppressWarnings({ "rawtypes" })
private void expunge() {
Reference ref;
while ((ref = refQueue.poll()) != null) {
backing.remove(((WeakReferenceWithKey) ref).getKey());
}
}
/**
* Put the key value pair if there is not already a matching entry. If there
* is an existing entry then return that instead.
*/
public Object putIfAbsent(K key, V value) {
expunge();
Reference<V> ref = backing.get(key);
if (ref != null) {
V existingValue = ref.get();
if (existingValue != null) {
// it is not absent
return existingValue;
}
}
// put the new value and return null
// indicating the put was successful
backing.put(key, createReference(key, value));
return null;
}
public void put(K key, V value) {
expunge();
backing.put(key, createReference(key, value));
}
public V get(K key) {
expunge();
Reference<V> v = backing.get(key);
return v == null ? null : v.get();
}
public int size() {
expunge();
return backing.size();
}
public boolean isEmpty() {
expunge();
return backing.isEmpty();
}
public boolean containsKey(Object key) {
expunge();
return backing.containsKey(key);
}
public V remove(K key) {
expunge();
Reference<V> v = backing.remove(key);
return v == null ? null : v.get();
}
public void clear() {
expunge();
backing.clear();
expunge();
}
public String toString() {
expunge();
return backing.toString();
}
}
@@ -0,0 +1,201 @@
package com.avaje.ebeaninternal.server.util;
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
import java.util.concurrent.atomic.AtomicLong;
import java.io.Serializable;
/**
* One or more variables that together maintain an initially zero
* {@code long} sum. When updates (method {@link #add}) are contended
* across threads, the set of variables may grow dynamically to reduce
* contention. Method {@link #sum} (or, equivalently, {@link
* #longValue}) returns the current total combined across the
* variables maintaining the sum.
*
* <p>This class is usually preferable to {@link AtomicLong} when
* multiple threads update a common sum that is used for purposes such
* as collecting statistics, not for fine-grained synchronization
* control. Under low update contention, the two classes have similar
* characteristics. But under high contention, expected throughput of
* this class is significantly higher, at the expense of higher space
* consumption.
*
* <p>This class extends {@link Number}, but does <em>not</em> define
* methods such as {@code equals}, {@code hashCode} and {@code
* compareTo} because instances are expected to be mutated, and so are
* not useful as collection keys.
*
* <p><em>jsr166e note: This class is targeted to be placed in
* java.util.concurrent.atomic.</em>
*
* @since 1.8
* @author Doug Lea
*/
public class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
/**
* Version of plus for use in retryUpdate
*/
final long fn(long v, long x) { return v + x; }
/**
* Creates a new adder with initial sum of zero.
*/
public LongAdder() {
}
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as; long b, v; HashCode hc; Cell a; int n;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
int h = (hc = threadHashCode.get()).code;
if (as == null || (n = as.length) < 1 ||
(a = as[(n - 1) & h]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
retryUpdate(x, hc, uncontended);
}
}
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
/**
* Equivalent to {@code add(-1)}.
*/
public void decrement() {
add(-1L);
}
/**
* Returns the current sum. The returned value is <em>NOT</em> an
* atomic snapshot; invocation in the absence of concurrent
* updates returns an accurate result, but concurrent updates that
* occur while the sum is being calculated might not be
* incorporated.
*
* @return the sum
*/
public long sum() {
long sum = base;
Cell[] as = cells;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
sum += a.value;
}
}
return sum;
}
/**
* Resets variables maintaining the sum to zero. This method may
* be a useful alternative to creating a new adder, but is only
* effective if there are no concurrent updates. Because this
* method is intrinsically racy, it should only be used when it is
* known that no threads are concurrently updating.
*/
public void reset() {
internalReset(0L);
}
/**
* Equivalent in effect to {@link #sum} followed by {@link
* #reset}. This method may apply for example during quiescent
* points between multithreaded computations. If there are
* updates concurrent with this method, the returned value is
* <em>not</em> guaranteed to be the final value occurring before
* the reset.
*
* @return the sum
*/
public long sumThenReset() {
long sum = base;
Cell[] as = cells;
base = 0L;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
/**
* Returns the String representation of the {@link #sum}.
* @return the String representation of the {@link #sum}
*/
public String toString() {
return Long.toString(sum());
}
/**
* Equivalent to {@link #sum}.
*
* @return the sum
*/
public long longValue() {
return sum();
}
/**
* Returns the {@link #sum} as an {@code int} after a narrowing
* primitive conversion.
*/
public int intValue() {
return (int)sum();
}
/**
* Returns the {@link #sum} as a {@code float}
* after a widening primitive conversion.
*/
public float floatValue() {
return (float)sum();
}
/**
* Returns the {@link #sum} as a {@code double} after a widening
* primitive conversion.
*/
public double doubleValue() {
return (double)sum();
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeLong(sum());
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
busy = 0;
cells = null;
base = s.readLong();
}
}
@@ -0,0 +1,342 @@
package com.avaje.ebeaninternal.server.util;
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
import java.util.Random;
/**
* A package-local class holding common representation and mechanics
* for classes supporting dynamic striping on 64bit values. The class
* extends Number so that concrete subclasses must publicly do so.
*/
abstract class Striped64 extends Number {
/*
* This class maintains a lazily-initialized table of atomically
* updated variables, plus an extra "base" field. The table size
* is a power of two. Indexing uses masked per-thread hash codes.
* Nearly all declarations in this class are package-private,
* accessed directly by subclasses.
*
* Table entries are of class Cell; a variant of AtomicLong padded
* to reduce cache contention on most processors. Padding is
* overkill for most Atomics because they are usually irregularly
* scattered in memory and thus don't interfere much with each
* other. But Atomic objects residing in arrays will tend to be
* placed adjacent to each other, and so will most often share
* cache lines (with a huge negative performance impact) without
* this precaution.
*
* In part because Cells are relatively large, we avoid creating
* them until they are needed. When there is no contention, all
* updates are made to the base field. Upon first contention (a
* failed CAS on base update), the table is initialized to size 2.
* The table size is doubled upon further contention until
* reaching the nearest power of two greater than or equal to the
* number of CPUS. Table slots remain empty (null) until they are
* needed.
*
* A single spinlock ("busy") is used for initializing and
* resizing the table, as well as populating slots with new Cells.
* There is no need for a blocking lock; when the lock is not
* available, threads try other slots (or the base). During these
* retries, there is increased contention and reduced locality,
* which is still better than alternatives.
*
* Per-thread hash codes are initialized to random values.
* Contention and/or table collisions are indicated by failed
* CASes when performing an update operation (see method
* retryUpdate). Upon a collision, if the table size is less than
* the capacity, it is doubled in size unless some other thread
* holds the lock. If a hashed slot is empty, and lock is
* available, a new Cell is created. Otherwise, if the slot
* exists, a CAS is tried. Retries proceed by "double hashing",
* using a secondary hash (Marsaglia XorShift) to try to find a
* free slot.
*
* The table size is capped because, when there are more threads
* than CPUs, supposing that each thread were bound to a CPU,
* there would exist a perfect hash function mapping threads to
* slots that eliminates collisions. When we reach capacity, we
* search for this mapping by randomly varying the hash codes of
* colliding threads. Because search is random, and collisions
* only become known via CAS failures, convergence can be slow,
* and because threads are typically not bound to CPUS forever,
* may not occur at all. However, despite these limitations,
* observed contention rates are typically low in these cases.
*
* It is possible for a Cell to become unused when threads that
* once hashed to it terminate, as well as in the case where
* doubling the table causes no thread to hash to it under
* expanded mask. We do not try to detect or remove such cells,
* under the assumption that for long-running instances, observed
* contention levels will recur, so the cells will eventually be
* needed again; and for short-lived ones, it does not matter.
*/
/**
* Padded variant of AtomicLong supporting only raw accesses plus CAS.
* The value field is placed between pads, hoping that the JVM doesn't
* reorder them.
*
* JVM intrinsics note: It would be possible to use a release-only
* form of CAS here, if it were provided.
*/
static final class Cell {
volatile long p0, p1, p2, p3, p4, p5, p6;
volatile long value;
volatile long q0, q1, q2, q3, q4, q5, q6;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/**
* Holder for the thread-local hash code. The code is initially
* random, but may be set to a different value upon collisions.
*/
static final class HashCode {
static final Random rng = new Random();
int code;
HashCode() {
int h = rng.nextInt(); // Avoid zero to allow xorShift rehash
code = (h == 0) ? 1 : h;
}
}
/**
* The corresponding ThreadLocal class
*/
static final class ThreadHashCode extends ThreadLocal<HashCode> {
public HashCode initialValue() { return new HashCode(); }
}
/**
* Static per-thread hash codes. Shared across all instances to
* reduce ThreadLocal pollution and because adjustments due to
* collisions in one table are likely to be appropriate for
* others.
*/
static final ThreadHashCode threadHashCode = new ThreadHashCode();
/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
*/
transient volatile int busy;
/**
* Package-private default constructor
*/
Striped64() {
}
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val);
}
/**
* CASes the busy field from 0 to 1 to acquire lock.
*/
final boolean casBusy() {
return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1);
}
/**
* Computes the function of current and new value. Subclasses
* should open-code this update function for most uses, but the
* virtualized form is needed within retryUpdate.
*
* @param currentValue the current value (of either base or a cell)
* @param newValue the argument from a user update call
* @return result of the update function
*/
abstract long fn(long currentValue, long newValue);
/**
* Handles cases of updates involving initialization, resizing,
* creating new Cells, and/or contention. See above for
* explanation. This method suffers the usual non-modularity
* problems of optimistic retry code, relying on rechecked sets of
* reads.
*
* @param x the value
* @param hc the hash code holder
* @param wasUncontended false if CAS failed before call
*/
final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
int h = hc.code;
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (busy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (busy == 0 && casBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
busy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value, fn(v, x)))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (busy == 0 && casBusy()) {
try {
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
busy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h ^= h << 13; // Rehash
h ^= h >>> 17;
h ^= h << 5;
}
else if (busy == 0 && cells == as && casBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
busy = 0;
}
if (init)
break;
}
else if (casBase(v = base, fn(v, x)))
break; // Fall back on using base
}
hc.code = h; // Record index for next time
}
/**
* Sets base and all cells to the given value.
*/
final void internalReset(long initialValue) {
Cell[] as = cells;
base = initialValue;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
a.value = initialValue;
}
}
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long baseOffset;
private static final long busyOffset;
static {
try {
UNSAFE = getUnsafe();
Class<?> sk = Striped64.class;
baseOffset = UNSAFE.objectFieldOffset
(sk.getDeclaredField("base"));
busyOffset = UNSAFE.objectFieldOffset
(sk.getDeclaredField("busy"));
} catch (Exception e) {
throw new Error(e);
}
}
/**
* Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package.
* Replace with a simple call to Unsafe.getUnsafe when integrating
* into a jdk.
*
* @return a sun.misc.Unsafe
*/
private static sun.misc.Unsafe getUnsafe() {
try {
return sun.misc.Unsafe.getUnsafe();
} catch (SecurityException tryReflectionInstead) {}
try {
return java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {
public sun.misc.Unsafe run() throws Exception {
Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
for (java.lang.reflect.Field f : k.getDeclaredFields()) {
f.setAccessible(true);
Object x = f.get(null);
if (k.isInstance(x))
return k.cast(x);
}
throw new NoSuchFieldError("the Unsafe");
}});
} catch (java.security.PrivilegedActionException e) {
throw new RuntimeException("Could not initialize intrinsics",
e.getCause());
}
}
}