The following schema fragment specifies the expected content contained within this class.
+ *
+ *
origin;
+ protected ProfileDiff profileDiff;
+ protected ProfileNew profileNew;
+
+ /**
+ * Gets the value of the origin property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the origin property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getOrigin().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link Origin }
+ *
+ *
+ */
+ public List getOrigin() {
+ if (origin == null) {
+ origin = new ArrayList();
+ }
+ return this.origin;
+ }
+
+ /**
+ * Gets the value of the profileDiff property.
+ *
+ * @return
+ * possible object is
+ * {@link ProfileDiff }
+ *
+ */
+ public ProfileDiff getProfileDiff() {
+ return profileDiff;
+ }
+
+ /**
+ * Sets the value of the profileDiff property.
+ *
+ * @param value
+ * allowed object is
+ * {@link ProfileDiff }
+ *
+ */
+ public void setProfileDiff(ProfileDiff value) {
+ this.profileDiff = value;
+ }
+
+ /**
+ * Gets the value of the profileNew property.
+ *
+ * @return
+ * possible object is
+ * {@link ProfileNew }
+ *
+ */
+ public ProfileNew getProfileNew() {
+ return profileNew;
+ }
+
+ /**
+ * Sets the value of the profileNew property.
+ *
+ * @param value
+ * allowed object is
+ * {@link ProfileNew }
+ *
+ */
+ public void setProfileNew(ProfileNew value) {
+ this.profileNew = value;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ObjectFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ObjectFactory.java
new file mode 100644
index 000000000..a6fc83ab8
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ObjectFactory.java
@@ -0,0 +1,64 @@
+
+package com.avaje.ebeaninternal.server.autofetch.model;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.avaje.ebeaninternal.server.autofetch.model package.
+ * An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.server.autofetch.model
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link ProfileNew }
+ *
+ */
+ public ProfileNew createProfileNew() {
+ return new ProfileNew();
+ }
+
+ /**
+ * Create an instance of {@link Origin }
+ *
+ */
+ public Origin createOrigin() {
+ return new Origin();
+ }
+
+ /**
+ * Create an instance of {@link Autotune }
+ *
+ */
+ public Autotune createAutotune() {
+ return new Autotune();
+ }
+
+ /**
+ * Create an instance of {@link ProfileDiff }
+ *
+ */
+ public ProfileDiff createProfileDiff() {
+ return new ProfileDiff();
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java
new file mode 100644
index 000000000..86eebba46
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java
@@ -0,0 +1,171 @@
+
+package com.avaje.ebeaninternal.server.autofetch.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ *
Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ * </sequence>
+ * <attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="tuneDetail" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "callStack"
+})
+@XmlRootElement(name = "origin")
+public class Origin {
+
+ protected String callStack;
+ @XmlAttribute(name = "key", required = true)
+ protected String key;
+ @XmlAttribute(name = "beanType")
+ protected String beanType;
+ @XmlAttribute(name = "detail")
+ protected String detail;
+ @XmlAttribute(name = "tuneDetail")
+ protected String tuneDetail;
+
+ /**
+ * 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 tuneDetail property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getTuneDetail() {
+ return tuneDetail;
+ }
+
+ /**
+ * Sets the value of the tuneDetail property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setTuneDetail(String value) {
+ this.tuneDetail = value;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileDiff.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileDiff.java
new file mode 100644
index 000000000..a1bc1c91b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileDiff.java
@@ -0,0 +1,69 @@
+
+package com.avaje.ebeaninternal.server.autofetch.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "origin"
+})
+@XmlRootElement(name = "profileDiff")
+public class ProfileDiff {
+
+ protected List origin;
+
+ /**
+ * Gets the value of the origin property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the origin property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getOrigin().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link Origin }
+ *
+ *
+ */
+ public List getOrigin() {
+ if (origin == null) {
+ origin = new ArrayList();
+ }
+ return this.origin;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileNew.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileNew.java
new file mode 100644
index 000000000..0a058fb29
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/ProfileNew.java
@@ -0,0 +1,69 @@
+
+package com.avaje.ebeaninternal.server.autofetch.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "origin"
+})
+@XmlRootElement(name = "profileNew")
+public class ProfileNew {
+
+ protected List origin;
+
+ /**
+ * Gets the value of the origin property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the origin property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getOrigin().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link Origin }
+ *
+ *
+ */
+ public List getOrigin() {
+ if (origin == null) {
+ origin = new ArrayList();
+ }
+ return this.origin;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/package-info.java
new file mode 100644
index 000000000..7204153e4
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/package-info.java
@@ -0,0 +1,2 @@
+@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/autotune", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
+package com.avaje.ebeaninternal.server.autofetch.model;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlReader.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlReader.java
new file mode 100644
index 000000000..8ff62af16
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlReader.java
@@ -0,0 +1,57 @@
+package com.avaje.ebeaninternal.server.autofetch.service;
+
+
+import com.avaje.ebeaninternal.server.autofetch.model.Autotune;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Reads a profiling xml document.
+ */
+public class AutoTuneXmlReader {
+
+ /**
+ * Read and return a Profiling from an xml file.
+ */
+ public Autotune read(File file) {
+
+ try {
+ return readFile(file);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ protected Autotune readFile(File file) throws IOException {
+ if (!file.exists()) {
+ return new Autotune();
+ }
+ FileInputStream is = new FileInputStream(file);
+ try {
+ return read(is);
+ } finally {
+ is.close();
+ }
+ }
+
+ /**
+ * Read and return a Profiling from an xml document.
+ */
+ public Autotune read(InputStream is) {
+
+ try {
+ JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+ return (Autotune) unmarshaller.unmarshal(is);
+ } catch (JAXBException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlWriter.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlWriter.java
new file mode 100644
index 000000000..7770bafb1
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/AutoTuneXmlWriter.java
@@ -0,0 +1,32 @@
+package com.avaje.ebeaninternal.server.autofetch.service;
+
+
+import com.avaje.ebeaninternal.server.autofetch.model.Autotune;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import java.io.File;
+
+/**
+ * Simple writer for output of the AutoTune Profiling as an XML document.
+ */
+public class AutoTuneXmlWriter {
+
+ /**
+ * Write Profiling to a file as xml.
+ */
+ public void write(Autotune profiling, File file) {
+
+ try {
+ JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
+ Marshaller marshaller = jaxbContext.createMarshaller();
+ marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+ marshaller.marshal(profiling, file);
+
+ } catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java
index 9b4923276..5dc886cfe 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseAutoTuneService.java
@@ -1,14 +1,25 @@
package com.avaje.ebeaninternal.server.autofetch.service;
+import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.config.AutofetchConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
-import com.avaje.ebeaninternal.server.autofetch.AutoTuneService;
import 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.util.List;
+
/**
* Implementation of the AutoTuneService which is comprised of profiling and query tuning.
*/
@@ -40,12 +51,65 @@ public class BaseAutoTuneService implements AutoTuneService {
*/
public void startup() {
+ File file = new File("ebean-autotune.xml");
+ AutoTuneXmlReader reader = new AutoTuneXmlReader();
+ Autotune profiling = reader.read(file);
+ List originList = profiling.getOrigin();
+ for (Origin origin : originList) {
+ String key = origin.getKey();
+ String detail = origin.getDetail();
+ OrmQueryDetailParser parser = new OrmQueryDetailParser(detail);
+ OrmQueryDetail fetchDetail = parser.parse();
+ TunedQueryInfo tunedQueryInfo = new TunedQueryInfo(fetchDetail);
+ queryTuner.load(key, tunedQueryInfo);
+ }
}
private void saveProfiling() {
+ Autotune document = new Autotune();
+
AutoTuneCollection autoTuneCollection = profileManager.profilingCollection(false);
+ List entries = autoTuneCollection.getEntries();
+ for (AutoTuneCollection.Entry entry : entries) {
+ ObjectGraphOrigin point = entry.getOrigin();
+ OrmQueryDetail profileDetail = entry.getDetail();
+
+ OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
+ if (tuneDetail == null) {
+ ProfileNew profileNew = document.getProfileNew();
+ if (profileNew == null) {
+ profileNew = new ProfileNew();
+ document.setProfileNew(profileNew);
+ }
+ profileNew.getOrigin().add( createOrigin(entry, point));
+
+ } else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
+ Origin origin1 = createOrigin(entry, point);
+ origin1.setTuneDetail(tuneDetail.toString());
+ ProfileDiff diff = document.getProfileDiff();
+ if (diff == null) {
+ diff = new ProfileDiff();
+ document.setProfileDiff(diff);
+ }
+ diff.getOrigin().add(origin1);
+ }
+ }
+
+ File file = new File("ebean-autotune-profiling.xml");
+ AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
+ writer.write(document, file);
+ }
+
+ @NotNull
+ private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
+ Origin origin1 = new Origin();
+ origin1.setKey(point.getKey());
+ origin1.setBeanType(point.getBeanType());
+ origin1.setDetail(entry.getDetail().toString());
+ origin1.setCallStack(point.getCallStack().description("\n"));
+ return origin1;
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java
index 6ad1fe2c0..9536197d5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/BaseQueryTuner.java
@@ -7,6 +7,7 @@ import com.avaje.ebean.config.AutofetchMode;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autofetch.ProfilingListener;
+import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import javax.persistence.PersistenceException;
import java.util.Map;
@@ -48,6 +49,17 @@ public class BaseQueryTuner {
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();
+ }
+
+ boolean fullProfiling = true;
+
/**
* Auto tune the query and enable profiling.
*/
@@ -58,7 +70,11 @@ public class BaseQueryTuner {
}
if (!useAutoTune(query)) {
- // not using autoFetch for this query
+ // not tuning this query but maybe profiling
+ if (fullProfiling) {
+ CallStack stack = server.createCallStack();
+ profiling(query, stack);
+ }
return false;
}
@@ -88,6 +104,16 @@ public class BaseQueryTuner {
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)) {
+ // collect more profiling based on profiling rate etc
+ query.setProfilingListener(profilingListener);
+ }
+ }
+
/**
* Return true if we should try to tune this query.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java
index 84f758c0b..411f9756f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOrigin.java
@@ -16,6 +16,8 @@ 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;
@@ -89,10 +91,10 @@ public class ProfileOrigin {
}
private OrmQueryDetail buildDetail(BeanDescriptor> rootDesc) {
- PathProperties pathProps = new PathProperties();
+ PathProperties pathProps = new PathProperties();
for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) {
- statsNode.buildTunedFetch(pathProps, rootDesc);
+ statsNode.buildTunedFetch(pathProps, rootDesc, queryTuningAddVersion);
}
OrmQueryDetail detail = new OrmQueryDetail();
@@ -142,10 +144,10 @@ public class ProfileOrigin {
*/
public void collectUsageInfo(NodeUsageCollector profile) {
- if (!profile.isEmpty()) {
- ObjectGraphNode node = profile.getNode();
+ //logger.info("COLLECT USAGE {}", profile.toString());
- ProfileOriginNodeUsage nodeStats = getNodeStats(node.getPath());
+ if (!profile.isEmpty()) {
+ ProfileOriginNodeUsage nodeStats = getNodeStats(profile.getNode().getPath());
nodeStats.collectUsageInfo(profile);
}
}
@@ -153,9 +155,11 @@ public class ProfileOrigin {
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, queryTuningAddVersion);
+ nodeStats = new ProfileOriginNodeUsage(path);
nodeUsageMap.put(path, nodeStats);
}
return nodeStats;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java
index 632ac8097..ec8e9dc1a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/ProfileOriginNodeUsage.java
@@ -24,8 +24,6 @@ public class ProfileOriginNodeUsage {
private final String path;
- private final boolean queryTuningAddVersion;
-
private int profileCount;
private int profileUsedCount;
@@ -34,12 +32,12 @@ public class ProfileOriginNodeUsage {
private final Set aggregateUsed = new LinkedHashSet();
- public ProfileOriginNodeUsage(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) {
@@ -59,6 +57,7 @@ public class ProfileOriginNodeUsage {
}
for (String propName : aggregateUsed) {
+ //propName = "".equals(propName) ? null : propName;
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
if (beanProp == null) {
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
@@ -81,7 +80,7 @@ public class ProfileOriginNodeUsage {
}
}
- if ((modified || queryTuningAddVersion) && desc != null) {
+ if ((modified || addVersionProperty) && desc != null) {
BeanProperty versionProp = desc.getVersionProperty();
if (versionProp != null) {
pathProps.addToPath(path, versionProp.getName());
@@ -93,7 +92,7 @@ public class ProfileOriginNodeUsage {
/**
* Collect usage from a node.
*/
- public void collectUsageInfo(NodeUsageCollector profile) {
+ protected void collectUsageInfo(NodeUsageCollector profile) {
synchronized (monitor) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java
index b1e554cbd..e203d5f70 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/service/TunedQueryInfo.java
@@ -21,6 +21,18 @@ public class TunedQueryInfo implements Serializable {
this.tunedDetail = tunedDetail;
}
+ public TunedQueryInfo(OrmQueryDetail tunedDetail) {
+ this.origin = null;
+ 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.
*
@@ -47,7 +59,7 @@ public class TunedQueryInfo implements Serializable {
}
public String toString() {
- return origin.getBeanType() + " " + origin.getKey() + " " + tunedDetail;
+ return tunedDetail.toString();
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index 4b14fc42b..cdab0e94f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -236,6 +236,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private void configureServerPlugins() {
+ autoTuneService.startup();
+
for (SpiServerPlugin plugin : serverPlugins) {
plugin.configure(this);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java
index a65edfa16..954c5f4fe 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java
@@ -9,6 +9,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.persistence.PersistenceException;
@@ -83,25 +84,48 @@ public class OrmQueryDetail implements Serializable {
* Return true if equal in terms of autofetch (select and joins).
*/
public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) {
- return autofetchPlanHash() == otherDetail.autofetchPlanHash();
- }
- /**
- * 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;
+ }
+ Set> entries = fetchPaths.entrySet();
+ for (Map.Entry entry : entries) {
+ OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false);
+ if (!isSame(entry.getValue(), chunk)) {
+ return false;
}
}
- return hc;
+ return true;
+ //return autofetchPlanHash() == otherDetail.autofetchPlanHash();
}
+ private boolean isSame(OrmQueryProperties p1, OrmQueryProperties p2) {
+ if (p1 == null) {
+ return p2 == null;
+ }
+ return p1.isSame(p2);
+ }
+
+// /**
+// * 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();
+// }
+// }
+//
+// return hc;
+// }
+
public String toString() {
StringBuilder sb = new StringBuilder();
if (baseProps != null) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java
index f33572efb..9a5026693 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java
@@ -27,10 +27,11 @@ public class OrmQueryDetailParser {
this.parser = new SimpleTextParser(oql);
}
- public void parse() throws PersistenceException {
+ public OrmQueryDetail parse() throws PersistenceException {
parser.nextWord();
processInitial();
+ return detail;
}
protected void assign(DefaultOrmQuery> query) {
@@ -42,7 +43,9 @@ public class OrmQueryDetailParser {
}
private void processInitial() {
- if (parser.isMatch("find")) {
+ if (parser.isMatch("select")) {
+ readSelect();
+ } else if (parser.isMatch("find")) {
OrmQueryProperties props = readFindFetch();
detail.setBase(props);
} else {
@@ -149,6 +152,19 @@ public class OrmQueryDetailParser {
}
}
+ private void readSelect() {
+ String path = null;
+ String props = parser.nextWord();
+ if (props.startsWith("(")) {
+ props = props.substring(1, props.length() - 1);
+ OrmQueryProperties base = new OrmQueryProperties(path, props);
+ detail.setBase(base);
+ parser.nextWord();
+ } else {
+ process();
+ }
+ }
+
private OrmQueryProperties readFindFetch() {
boolean readAlias = false;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java
index a045b4194..23d270ce4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java
@@ -573,4 +573,11 @@ public class OrmQueryProperties implements Serializable {
return Collections.unmodifiableSet(set);
}
+
+ public boolean isSame(OrmQueryProperties p2) {
+ if (included == null) {
+ return p2.included == null;
+ }
+ return included.equals(p2.included);
+ }
}
diff --git a/src/main/resources/ebean-autotune-1.0.xsd b/src/main/resources/ebean-autotune-1.0.xsd
new file mode 100644
index 000000000..42a1ce081
--- /dev/null
+++ b/src/main/resources/ebean-autotune-1.0.xsd
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java
new file mode 100644
index 000000000..5ddd475b1
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParserTest.java
@@ -0,0 +1,80 @@
+package com.avaje.ebeaninternal.server.querydefn;
+
+import com.avaje.ebean.BaseTestCase;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertNull;
+
+
+public class OrmQueryDetailParserTest extends BaseTestCase {
+
+ @Test
+ public void testParseBasic() throws Exception {
+
+
+ OrmQueryDetail other = new OrmQueryDetail();
+ other.select("id,name");
+
+ OrmQueryProperties root = other.getChunk(null, false);
+ assertNull(root.getPath());
+ assertThat(root.getAllIncludedProperties()).contains("id", "name");
+
+ OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name)");
+ OrmQueryDetail detail = p.parse();
+
+ root = detail.getChunk(null, false);
+ assertNull(root.getPath());
+ assertThat(root.getAllIncludedProperties()).contains("id", "name");
+ }
+
+ @Test
+ public void testParseEmptySelect() throws Exception {
+
+ OrmQueryDetailParser p = new OrmQueryDetailParser("select fetch customer (email)");
+ OrmQueryDetail detail = p.parse();
+
+ OrmQueryProperties root = detail.getChunk(null, false);
+ assertNull(root.getPath());
+ assertThat(root.getAllIncludedProperties()).isNull();
+
+ OrmQueryProperties chunk = detail.getChunk("customer", false);
+ assertThat(chunk.getPath()).isEqualTo("customer");
+ assertThat(chunk.getAllIncludedProperties()).contains("email");
+ }
+
+ @Test
+ public void testParseSelectFetch() throws Exception {
+
+ OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (email)");
+ OrmQueryDetail detail = p.parse();
+
+ OrmQueryProperties root = detail.getChunk(null, false);
+ assertNull(root.getPath());
+ assertThat(root.getAllIncludedProperties()).contains("id", "name");
+
+ OrmQueryProperties chunk = detail.getChunk("customer", false);
+ assertThat(chunk.getPath()).isEqualTo("customer");
+ assertThat(chunk.getAllIncludedProperties()).contains("email");
+ }
+
+ @Test
+ public void testParseSelectFetchMore() throws Exception {
+
+ OrmQueryDetailParser p = new OrmQueryDetailParser("select (id,name) fetch customer (email) fetch details.product (sku,description)");
+ OrmQueryDetail detail = p.parse();
+
+ OrmQueryProperties root = detail.getChunk(null, false);
+ assertNull(root.getPath());
+ assertThat(root.getAllIncludedProperties()).contains("id", "name");
+
+ OrmQueryProperties chunk = detail.getChunk("customer", false);
+ assertThat(chunk.getPath()).isEqualTo("customer");
+ assertThat(chunk.getAllIncludedProperties()).contains("email");
+
+ chunk = detail.getChunk("details.product", false);
+ assertThat(chunk.getPath()).isEqualTo("details.product");
+ assertThat(chunk.getAllIncludedProperties()).contains("sku","description");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java
new file mode 100644
index 000000000..4a6a18ec6
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java
@@ -0,0 +1,21 @@
+package com.avaje.ebeaninternal.server.querydefn;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class OrmQueryDetailTest {
+
+
+ @Test
+ public void test_isAutoTuneEqual() {
+
+ OrmQueryDetailParser parser1 = new OrmQueryDetailParser("select order (id,name) fetch customer (name) fetch details (code)");
+ OrmQueryDetail detail1 = parser1.parse();
+
+ OrmQueryDetailParser parser2 = new OrmQueryDetailParser("select order (id,name) fetch details (code) fetch customer (name)");
+ OrmQueryDetail detail2 = parser2.parse();
+
+ assertTrue(detail1.isAutoTuneEqual(detail2));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java b/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java
index f9fcfe399..d5e2cbdf9 100644
--- a/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java
+++ b/src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java
@@ -30,8 +30,8 @@ public class TestAutofetchTuneWithJoin extends BaseTestCase {
Query q = Ebean.find(Order.class)
.setAutofetch(true)
- .fetch("customer")
- .fetch("customer.contacts")
+ //.fetch("customer")
+ //.fetch("customer.contacts")
.where().lt("id", 3).query();
List list = q.findList();
diff --git a/src/test/java/com/avaje/tests/query/autotune/TestAutoTuneProfiling.java b/src/test/java/com/avaje/tests/query/autotune/TestAutoTuneProfiling.java
new file mode 100644
index 000000000..e270607e2
--- /dev/null
+++ b/src/test/java/com/avaje/tests/query/autotune/TestAutoTuneProfiling.java
@@ -0,0 +1,82 @@
+package com.avaje.tests.query.autotune;
+
+import com.avaje.ebean.AdminAutofetch;
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Query;
+import com.avaje.ebean.bean.ObjectGraphNode;
+import com.avaje.ebean.bean.ObjectGraphOrigin;
+import com.avaje.ebeaninternal.api.SpiQuery;
+import com.avaje.tests.model.basic.Address;
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.Order;
+import com.avaje.tests.model.basic.ResetBasicData;
+import org.junit.Test;
+
+import java.util.List;
+
+public class TestAutoTuneProfiling extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+ ResetBasicData.reset();
+
+ for (int i = 0; i < 1; i++) {
+ execute();
+ }
+
+
+ collectUsage();
+ }
+
+
+ private void execute() {
+ useOrderDate();
+ useOrderDateCustomerName();
+ useLots();
+ }
+
+
+ private Order findById(long id) {
+ return Ebean.find(Order.class)
+ .setAutofetch(true)
+ .setId(id)
+ .findUnique();
+ }
+
+ private void useOrderDate() {
+ Order order = findById(3);
+ order.getStatus();
+ order.getShipDate();
+ }
+
+ private void useOrderDateCustomerName() {
+ Order order = findById(3);
+ order.getOrderDate();
+ order.getCustomer().getName();
+ }
+
+ private void useLots() {
+
+ Order order = findById(3);
+ order.getOrderDate();
+ order.getShipDate();
+ // order.setShipDate(new Date(System.currentTimeMillis()));
+ Customer customer = order.getCustomer();
+ customer.getName();
+ Address shippingAddress = customer.getShippingAddress();
+ if (shippingAddress != null) {
+ shippingAddress.getLine1();
+ shippingAddress.getCity();
+ }
+ }
+
+ private static void collectUsage() {
+
+ AdminAutofetch adminAutofetch = Ebean.getServer(null).getAdminAutofetch();
+ adminAutofetch.collectUsageViaGC();
+
+ }
+
+}
diff --git a/src/test/resources/autotune/test-profiling.xml b/src/test/resources/autotune/test-profiling.xml
new file mode 100644
index 000000000..7bd775db9
--- /dev/null
+++ b/src/test/resources/autotune/test-profiling.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file