WIP AutoTune refactor

This commit is contained in:
Robin Bygrave
2015-09-04 08:48:25 +12:00
parent 5c1de0a886
commit 5b8ce8eaa0
27 changed files with 1092 additions and 34 deletions
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<autotune xmlns="http://ebean-orm.github.io/xml/ns/autotune">
<!-- CHANGES (to existing tuning entries) -->
<profileDiff>
<origin key="B0dP9I.DWeHD4.DKE89p" beanType="com.avaje.tests.model.basic.Order" detail="select (status,shipDate) "
tuneDetail="select (shipDate) ">
<callStack>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)
</callStack>
</origin>
</profileDiff>
<!-- NEW ENTRIES (didn't exist in tuning entries) -->
<profileNew>
<origin key="B0dP9I.DWeHD4.DCmfLb" beanType="com.avaje.tests.model.basic.Order"
detail="select (orderDate) fetch customer (id,name) ">
<callStack>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)
</callStack>
</origin>
</profileNew>
</autotune>
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<autotune xmlns="http://ebean-orm.github.io/xml/ns/autotune">
<origin key="B0dP9I.DWeHD4.DigF0q" beanType="com.avaje.tests.model.basic.Order"
detail="select (orderDate,shipDate) fetch customer (id,name) fetch customer.shippingAddress (line1,city,id) ">
<callStack>
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)
</callStack>
</origin>
<origin key="B0dP9I.DWeHD4.DKE89p" beanType="com.avaje.tests.model.basic.Order" detail="select (shipDate) ">
<callStack>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)
</callStack>
</origin>
<!--<origin key="B0dP9I.DWeHD4.DCmfLb" beanType="com.avaje.tests.model.basic.Order"-->
<!--detail="select (orderDate) fetch customer (id,name) ">-->
<!--<callStack>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)-->
<!--</callStack>-->
<!--</origin>-->
</autotune>
@@ -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;
}
@@ -19,6 +19,10 @@ public class AutoTuneCollection {
return entry;
}
public List<Entry> getEntries() {
return entries;
}
/**
* Profiling entry at a given origin point.
*/
@@ -0,0 +1,123 @@
package com.avaje.ebeaninternal.server.autofetch.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileDiff" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileNew" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin",
"profileDiff",
"profileNew"
})
@XmlRootElement(name = "autotune")
public class Autotune {
protected List<Origin> origin;
protected ProfileDiff profileDiff;
protected ProfileNew profileNew;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
/**
* Gets the value of the profileDiff property.
*
* @return
* possible object is
* {@link ProfileDiff }
*
*/
public ProfileDiff getProfileDiff() {
return profileDiff;
}
/**
* Sets the value of the profileDiff property.
*
* @param value
* allowed object is
* {@link ProfileDiff }
*
*/
public void setProfileDiff(ProfileDiff value) {
this.profileDiff = value;
}
/**
* Gets the value of the profileNew property.
*
* @return
* possible object is
* {@link ProfileNew }
*
*/
public ProfileNew getProfileNew() {
return profileNew;
}
/**
* Sets the value of the profileNew property.
*
* @param value
* allowed object is
* {@link ProfileNew }
*
*/
public void setProfileNew(ProfileNew value) {
this.profileNew = value;
}
}
@@ -0,0 +1,64 @@
package com.avaje.ebeaninternal.server.autofetch.model;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.avaje.ebeaninternal.server.autofetch.model package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.server.autofetch.model
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link ProfileNew }
*
*/
public ProfileNew createProfileNew() {
return new ProfileNew();
}
/**
* Create an instance of {@link Origin }
*
*/
public Origin createOrigin() {
return new Origin();
}
/**
* Create an instance of {@link Autotune }
*
*/
public Autotune createAutotune() {
return new Autotune();
}
/**
* Create an instance of {@link ProfileDiff }
*
*/
public ProfileDiff createProfileDiff() {
return new ProfileDiff();
}
}
@@ -0,0 +1,171 @@
package com.avaje.ebeaninternal.server.autofetch.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* &lt;/sequence>
* &lt;attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="tuneDetail" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"callStack"
})
@XmlRootElement(name = "origin")
public class Origin {
protected String callStack;
@XmlAttribute(name = "key", required = true)
protected String key;
@XmlAttribute(name = "beanType")
protected String beanType;
@XmlAttribute(name = "detail")
protected String detail;
@XmlAttribute(name = "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;
}
}
@@ -0,0 +1,69 @@
package com.avaje.ebeaninternal.server.autofetch.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileDiff")
public class ProfileDiff {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -0,0 +1,69 @@
package com.avaje.ebeaninternal.server.autofetch.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileNew")
public class ProfileNew {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -0,0 +1,2 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/autotune", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.avaje.ebeaninternal.server.autofetch.model;
@@ -0,0 +1,57 @@
package com.avaje.ebeaninternal.server.autofetch.service;
import com.avaje.ebeaninternal.server.autofetch.model.Autotune;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Reads a profiling xml document.
*/
public class AutoTuneXmlReader {
/**
* Read and return a Profiling from an xml file.
*/
public Autotune read(File file) {
try {
return readFile(file);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
protected Autotune readFile(File file) throws IOException {
if (!file.exists()) {
return new Autotune();
}
FileInputStream is = new FileInputStream(file);
try {
return read(is);
} finally {
is.close();
}
}
/**
* Read and return a Profiling from an xml document.
*/
public Autotune read(InputStream is) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (Autotune) unmarshaller.unmarshal(is);
} catch (JAXBException e) {
throw new IllegalStateException(e);
}
}
}
@@ -0,0 +1,32 @@
package com.avaje.ebeaninternal.server.autofetch.service;
import com.avaje.ebeaninternal.server.autofetch.model.Autotune;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.File;
/**
* Simple writer for output of the AutoTune Profiling as an XML document.
*/
public class AutoTuneXmlWriter {
/**
* Write Profiling to a file as xml.
*/
public void write(Autotune profiling, File file) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(profiling, file);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
@@ -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<Origin> 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<AutoTuneCollection.Entry> 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;
}
/**
@@ -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.
*/
@@ -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;
@@ -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<String> aggregateUsed = new LinkedHashSet<String>();
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) {
@@ -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();
}
}
@@ -236,6 +236,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private void configureServerPlugins() {
autoTuneService.startup();
for (SpiServerPlugin plugin : serverPlugins) {
plugin.configure(this);
}
@@ -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<Map.Entry<String, OrmQueryProperties>> entries = fetchPaths.entrySet();
for (Map.Entry<String, OrmQueryProperties> entry : entries) {
OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false);
if (!isSame(entry.getValue(), chunk)) {
return false;
}
}
return hc;
return true;
//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) {
@@ -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;
@@ -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);
}
}
+46
View File
@@ -0,0 +1,46 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://ebean-orm.github.io/xml/ns/autotune"
targetNamespace="http://ebean-orm.github.io/xml/ns/autotune" elementFormDefault="qualified">
<!-- Root level type : profiling -->
<xsd:element name="autotune">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="origin" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="profileDiff" minOccurs="0" maxOccurs="1"/>
<xsd:element ref="profileNew" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="origin">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="callStack" type="xsd:string" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="key" type="xsd:string" use="required"/>
<xsd:attribute name="beanType" type="xsd:string"/>
<xsd:attribute name="detail" type="xsd:string"/>
<xsd:attribute name="tuneDetail" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="profileDiff">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="origin" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="profileNew">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="origin" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -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");
}
}
@@ -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));
}
}
@@ -30,8 +30,8 @@ public class TestAutofetchTuneWithJoin extends BaseTestCase {
Query<Order> q = Ebean.find(Order.class)
.setAutofetch(true)
.fetch("customer")
.fetch("customer.contacts")
//.fetch("customer")
//.fetch("customer.contacts")
.where().lt("id", 3).query();
List<Order> list = q.findList();
@@ -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();
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<profiling xmlns="http://ebean-orm.github.io/xml/ns/autotune" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ebean-orm.github.io/xml/ns/autotune http://ebean-orm.github.io/xml/ns/autotune-1.0.xsd">
<origin type="baseline">
</origin>
</profiling>