diff --git a/ebean-autotune-profiling.xml b/ebean-autotune-profiling.xml new file mode 100644 index 000000000..3911ea279 --- /dev/null +++ b/ebean-autotune-profiling.xml @@ -0,0 +1,32 @@ + + + + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:45) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useOrderDate(TestAutoTuneProfiling.java:49) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:35) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:26) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + + + + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:45) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useOrderDateCustomerName(TestAutoTuneProfiling.java:55) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:36) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:26) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + + diff --git a/ebean-autotune.xml b/ebean-autotune.xml new file mode 100644 index 000000000..73563dec3 --- /dev/null +++ b/ebean-autotune.xml @@ -0,0 +1,31 @@ + + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:45) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useLots(TestAutoTuneProfiling.java:62) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:37) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:26) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + com.avaje.tests.query.autotune.TestAutoTuneProfiling.findById(TestAutoTuneProfiling.java:45) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.useOrderDate(TestAutoTuneProfiling.java:49) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.execute(TestAutoTuneProfiling.java:35) + com.avaje.tests.query.autotune.TestAutoTuneProfiling.test(TestAutoTuneProfiling.java:26) + sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + + + + + + + + + + + + diff --git a/src/main/java/com/avaje/ebean/bean/CallStack.java b/src/main/java/com/avaje/ebean/bean/CallStack.java index 0f4b78f0c..f283ea591 100644 --- a/src/main/java/com/avaje/ebean/bean/CallStack.java +++ b/src/main/java/com/avaje/ebean/bean/CallStack.java @@ -87,6 +87,17 @@ public final class CallStack implements Serializable { return zeroHash + ":" + pathHash + ":" + callStack[0]; } + /** + * Return the call stack lines appended with the given newLine string. + */ + public String description(String newLine) { + StringBuilder sb = new StringBuilder(400); + for (int i = 0; i < callStack.length; i++) { + sb.append(callStack[i].toString()).append(newLine); + } + return sb.toString(); + } + public String getOriginKey(int queryHash) { return zeroHash + "." + enc(queryHash) + "." + pathHash; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java index e26b892f1..eb62059b9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoTuneCollection.java @@ -19,6 +19,10 @@ public class AutoTuneCollection { return entry; } + public List getEntries() { + return entries; + } + /** * Profiling entry at a given origin point. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Autotune.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Autotune.java new file mode 100644 index 000000000..4be03eb68 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Autotune.java @@ -0,0 +1,123 @@ + +package com.avaje.ebeaninternal.server.autofetch.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

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

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

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

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

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

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

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.server.autofetch.model + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link ProfileNew } + * + */ + public ProfileNew createProfileNew() { + return new ProfileNew(); + } + + /** + * Create an instance of {@link Origin } + * + */ + public Origin createOrigin() { + return new Origin(); + } + + /** + * Create an instance of {@link Autotune } + * + */ + public Autotune createAutotune() { + return new Autotune(); + } + + /** + * Create an instance of {@link ProfileDiff } + * + */ + public ProfileDiff createProfileDiff() { + return new ProfileDiff(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/model/Origin.java new file mode 100644 index 000000000..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