#2067 - Extract ebean-autotune into separate module

This commit is contained in:
rob bygrave committed 2020-10-08 23:34:56 +13:00
1 parent 7c36439daf
commit d113ddc525
48 files changed
+700 -424

No files matched your search

@@ -1,115 +0,0 @@
package io.ebeaninternal.server.autotune;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.ArrayList;
import java.util.List;
/**
* Profiling information collected.
*/
public class AutoTuneCollection {
final List<Entry> entries = new ArrayList<>();
public Entry add(ObjectGraphOrigin origin, OrmQueryDetail detail, String sourceQuery) {
Entry entry = new Entry(origin, detail, sourceQuery);
entries.add(entry);
return entry;
}
public List<Entry> getEntries() {
return entries;
}
/**
* Profiling entry at a given origin point.
*/
public static class Entry {
/**
* Profiling origin point.
*/
private final ObjectGraphOrigin origin;
/**
* The tuned query detail.
*/
private final OrmQueryDetail detail;
/**
* The original/existing query detail.
*/
private final String originalQuery;
/**
* Summary execution statistics for queries related to this origin point.
*/
private final List<EntryQuery> queries = new ArrayList<>();
public Entry(ObjectGraphOrigin origin, OrmQueryDetail detail, String originalQuery) {
this.origin = origin;
this.detail = detail;
this.originalQuery = originalQuery;
}
public void addQuery(EntryQuery entryQuery) {
queries.add(entryQuery);
}
public ObjectGraphOrigin getOrigin() {
return origin;
}
public OrmQueryDetail getDetail() {
return detail;
}
public String getOriginalQuery() {
return originalQuery;
}
public List<EntryQuery> getQueries() {
return queries;
}
}
/**
* Summary query execution statistics for the origin point.
*/
public static class EntryQuery {
final String path;
final long exeCount;
final long totalBeanLoaded;
final long totalMicros;
public EntryQuery(String path, long exeCount, long totalBeanLoaded, long totalMicros) {
this.path = path;
this.exeCount = exeCount;
this.totalBeanLoaded = totalBeanLoaded;
this.totalMicros = totalMicros;
}
/**
* Return the relative path with empty string for the origin query.
*/
public String getPath() {
return path;
}
public long getExeCount() {
return exeCount;
}
public long getTotalBeanLoaded() {
return totalBeanLoaded;
}
public long getTotalMicros() {
return totalMicros;
}
}
}
@@ -0,0 +1,9 @@
package io.ebeaninternal.server.autotune;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.api.SpiEbeanServer;
public interface AutoTuneServiceProvider {
AutoTuneService create(SpiEbeanServer server, DatabaseConfig config);
}
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.autotune;
import io.ebeaninternal.api.SpiQuery;
/**
* Noop service when AutoTuneService is not available.
*/
public class NoAutoTuneService implements AutoTuneService {
@Override
public void startup() {
// do nothing
}
@Override
public boolean tuneQuery(SpiQuery<?> query) {
return false;
}
@Override
public void collectProfiling() {
// do nothing
}
@Override
public void reportProfiling() {
// do nothing
}
@Override
public void shutdown() {
// do nothing
}
}
@@ -1,133 +0,0 @@
package io.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
/**
* Java class for anonymous complex type.
* <p>
* The following schema fragment specifies the expected content contained within this class.
* <p>
* <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;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileEmpty" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin",
"profileDiff",
"profileNew",
"profileEmpty"
})
@XmlRootElement(name = "autotune")
public class Autotune {
protected List<Origin> origin;
protected ProfileDiff profileDiff;
protected ProfileNew profileNew;
protected ProfileEmpty profileEmpty;
/**
* Gets the value of the origin property.
* <p>
* <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>
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
* <p>
* <p>
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*/
public List<Origin> 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;
}
/**
* Gets the value of the profileEmpty property.
*
* @return possible object is
* {@link ProfileEmpty }
*/
public ProfileEmpty getProfileEmpty() {
return profileEmpty;
}
/**
* Sets the value of the profileEmpty property.
*
* @param value allowed object is
* {@link ProfileEmpty }
*/
public void setProfileEmpty(ProfileEmpty value) {
this.profileEmpty = value;
}
}
@@ -1,64 +0,0 @@
package io.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the io.ebeaninternal.server.autotune.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: io.ebeaninternal.server.autotune.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 ProfileEmpty }
*/
public ProfileEmpty createProfileEmpty() {
return new ProfileEmpty();
}
/**
* Create an instance of {@link Autotune }
*/
public Autotune createAutotune() {
return new Autotune();
}
/**
* Create an instance of {@link ProfileDiff }
*/
public ProfileDiff createProfileDiff() {
return new ProfileDiff();
}
}
@@ -1,148 +0,0 @@
package io.ebeaninternal.server.autotune.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>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* &lt;/sequence>
* &lt;attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="original" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"callStack"
})
@XmlRootElement(name = "origin")
public class Origin {
protected String callStack;
@XmlAttribute(name = "key", required = true)
protected String key;
@XmlAttribute(name = "beanType")
protected String beanType;
@XmlAttribute(name = "detail")
protected String detail;
@XmlAttribute(name = "original")
protected String original;
/**
* Gets the value of the callStack property.
*
* @return possible object is
* {@link String }
*/
public String getCallStack() {
return callStack;
}
/**
* Sets the value of the callStack property.
*
* @param value allowed object is
* {@link String }
*/
public void setCallStack(String value) {
this.callStack = value;
}
/**
* Gets the value of the key property.
*
* @return possible object is
* {@link String }
*/
public String getKey() {
return key;
}
/**
* Sets the value of the key property.
*
* @param value allowed object is
* {@link String }
*/
public void setKey(String value) {
this.key = value;
}
/**
* Gets the value of the beanType property.
*
* @return possible object is
* {@link String }
*/
public String getBeanType() {
return beanType;
}
/**
* Sets the value of the beanType property.
*
* @param value allowed object is
* {@link String }
*/
public void setBeanType(String value) {
this.beanType = value;
}
/**
* Gets the value of the detail property.
*
* @return possible object is
* {@link String }
*/
public String getDetail() {
return detail;
}
/**
* Sets the value of the detail property.
*
* @param value allowed object is
* {@link String }
*/
public void setDetail(String value) {
this.detail = value;
}
/**
* Gets the value of the original property.
*
* @return possible object is
* {@link String }
*/
public String getOriginal() {
return original;
}
/**
* Sets the value of the original property.
*
* @param value allowed object is
* {@link String }
*/
public void setOriginal(String value) {
this.original = value;
}
}
@@ -1,64 +0,0 @@
package io.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <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>
* <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>
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
* <p>
* <p>
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<>();
}
return this.origin;
}
}
@@ -1,64 +0,0 @@
package io.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <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 = "profileEmpty")
public class ProfileEmpty {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
* <p>
* <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>
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
* <p>
* <p>
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<>();
}
return this.origin;
}
}
@@ -1,64 +0,0 @@
package io.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <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>
* <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>
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
* <p>
* <p>
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<>();
}
return this.origin;
}
}
@@ -1,2 +0,0 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/autotune", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package io.ebeaninternal.server.autotune.model;
@@ -1,61 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebeaninternal.server.autotune.model.Autotune;
import java.util.Collection;
/**
* Event where all tuned query information is collected.
* <p>
* This is for writing the "all" file on shutdown when using runtime tuning.
* </p>
*/
public class AutoTuneAllCollection {
final Autotune document = new Autotune();
final BaseQueryTuner queryTuner;
/**
* Construct to collect/report all tuned queries.
*/
public AutoTuneAllCollection(BaseQueryTuner queryTuner) {
this.queryTuner = queryTuner;
loadAllTuned();
}
/**
* Return the number of origin elements in the document.
*/
public int size() {
return document.getOrigin().size();
}
/**
* Return the Autotune document object.
*/
public Autotune getDocument() {
return document;
}
/**
* Write the document as an xml file.
*/
public void writeFile(String filePrefix, boolean withNow) {
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
writer.write(document, filePrefix, withNow);
}
/**
* Loads all the existing query tuning into the document.
*/
private void loadAllTuned() {
Collection<TunedQueryInfo> all = queryTuner.getAll();
for (TunedQueryInfo tuned : all) {
document.getOrigin().add(tuned.getOrigin());
}
}
}
@@ -1,162 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebeaninternal.server.autotune.AutoTuneCollection;
import io.ebeaninternal.server.autotune.model.Autotune;
import io.ebeaninternal.server.autotune.model.Origin;
import io.ebeaninternal.server.autotune.model.ProfileDiff;
import io.ebeaninternal.server.autotune.model.ProfileNew;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
/**
* Event where profiling information is collected and processed for differences
* relative to the current query tuning.
*/
public class AutoTuneDiffCollection {
final Autotune document = new Autotune();
final AutoTuneCollection profiling;
final BaseQueryTuner queryTuner;
final boolean updateTuning;
int newCount;
int diffCount;
/**
* Construct to collect/report the new/diff query tuning entries.
*/
public AutoTuneDiffCollection(AutoTuneCollection profiling, BaseQueryTuner queryTuner, boolean updateTuning) {
this.profiling = profiling;
this.queryTuner = queryTuner;
this.updateTuning = updateTuning;
}
/**
* Return true if there are no new or diff entries.
*/
public boolean isEmpty() {
return newCount == 0 && diffCount == 0;
}
/**
* Return the underlying Autotune document object.
*/
public Autotune getDocument() {
return document;
}
/**
* Return the number of diff entries.
*/
public int getDiffCount() {
return diffCount;
}
/**
* Return the number of new entries.
*/
public int getNewCount() {
return newCount;
}
/**
* Return the total new and diff entries.
*/
public int getChangeCount() {
return newCount + diffCount;
}
/**
* Write the underlying document as an xml file.
*/
public void writeFile(String filePrefix) {
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
writer.write(document, filePrefix, true);
}
/**
* Process checking profiling entries against existing query tuning.
*/
public void process() {
for (AutoTuneCollection.Entry entry : profiling.getEntries()) {
addToDocument(entry);
}
}
/**
* Check if the entry is new or diff and add as necessary.
*/
private void addToDocument(AutoTuneCollection.Entry entry) {
ObjectGraphOrigin point = entry.getOrigin();
OrmQueryDetail profileDetail = entry.getDetail();
// compare with the existing query tuning entry
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
if (tuneDetail == null) {
addToDocumentNewEntry(entry, point);
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
addToDocumentDiffEntry(entry, point, tuneDetail);
}
}
/**
* Add as a diff entry.
*/
private void addToDocumentDiffEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, OrmQueryDetail tuneDetail) {
diffCount++;
Origin origin = createOrigin(entry, point, tuneDetail.toString());
ProfileDiff diff = document.getProfileDiff();
if (diff == null) {
diff = new ProfileDiff();
document.setProfileDiff(diff);
}
diff.getOrigin().add(origin);
}
/**
* Add as a "new" entry.
*/
private void addToDocumentNewEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
newCount++;
ProfileNew profileNew = document.getProfileNew();
if (profileNew == null) {
profileNew = new ProfileNew();
document.setProfileNew(profileNew);
}
Origin origin = createOrigin(entry, point, entry.getOriginalQuery());
profileNew.getOrigin().add(origin);
}
/**
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
*/
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, String query) {
Origin origin = new Origin();
origin.setKey(point.getKey());
origin.setBeanType(point.getBeanType());
origin.setDetail(entry.getDetail().toString());
origin.setCallStack(point.getCallOrigin().getFullDescription());
origin.setOriginal(query);
if (updateTuning) {
queryTuner.put(origin);
}
return origin;
}
}
@@ -1,13 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.autotune.AutoTuneService;
public class AutoTuneServiceFactory {
public static AutoTuneService create(SpiEbeanServer server, DatabaseConfig config) {
return new DefaultAutoTuneService(server, config);
}
}
@@ -1,52 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebeaninternal.server.autotune.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 static Autotune read(File file) {
try {
return readFile(file);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
protected static Autotune readFile(File file) throws IOException {
if (!file.exists()) {
return new Autotune();
}
try (FileInputStream is = new FileInputStream(file)) {
return read(is);
}
}
/**
* Read and return a Profiling from an xml document.
*/
public static 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);
}
}
}
@@ -1,57 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebeaninternal.server.autotune.model.Autotune;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Simple writer for output of the AutoTune Profiling as an XML document.
*/
public class AutoTuneXmlWriter {
/**
* Return 'now' as a string to second precision.
*/
public static String now() {
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
return df.format(new Date());
}
/**
* Write the document as xml file with the given prefix.
*/
public void write(Autotune document, String fileName, boolean withNow) {
SortAutoTuneDocument.sort(document);
if (withNow) {
fileName += "-" + now() + ".xml";
}
// write the file with serverName and now suffix as we can output the profiling many times
write(document, new File(fileName));
}
/**
* 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,202 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.AutoTuneConfig;
import io.ebean.config.AutoTuneMode;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.autotune.model.Origin;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
*
*/
public class BaseQueryTuner {
private final boolean queryTuning;
private final boolean profiling;
private final AutoTuneMode mode;
/**
* Map of the tuned query details per profile query point.
*/
private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<>();
private final SpiEbeanServer server;
private final ProfilingListener profilingListener;
/**
* Flag set true when there is no profiling or query tuning.
*/
private final boolean skipAll;
BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) {
this.server = server;
this.profilingListener = profilingListener;
this.mode = config.getMode();
this.queryTuning = config.isQueryTuning();
this.profiling = config.isProfiling();
this.skipAll = !queryTuning && !profiling;
}
/**
* Return all the current tuned query entries.
*/
public Collection<TunedQueryInfo> getAll() {
return tunedQueryInfoMap.values();
}
/**
* Put a query tuning entry.
*/
public void put(Origin origin) {
tunedQueryInfoMap.put(origin.getKey(), new TunedQueryInfo(origin));
}
/**
* Load the tuned query information.
*/
public void load(String key, TunedQueryInfo queryInfo) {
tunedQueryInfoMap.put(key, queryInfo);
}
/**
* Return the detail currently used for tuning.
* This returns null if there is currently no matching tuning.
*/
public OrmQueryDetail get(String key) {
TunedQueryInfo info = tunedQueryInfoMap.get(key);
return (info == null) ? null : info.getTunedDetail();
}
/**
* Auto tune the query and enable profiling.
*/
boolean tuneQuery(SpiQuery<?> query) {
if (skipAll || !tunableQuery(query)) {
return false;
}
if (query.getProfilingListener() != null) {
// profiling secondary query
return false;
}
if (!useTuning(query)) {
if (profiling) {
profiling(query, server.createCallOrigin());
}
return false;
}
if (query.getParentNode() != null) {
// This is a +lazy/+query query with profiling on.
// We continue to collect the profiling information.
query.setProfilingListener(profilingListener);
return true;
}
// create a query point to identify the query
CallOrigin callOrigin = server.createCallOrigin();
ObjectGraphNode origin = query.setOrigin(callOrigin);
if (profiling) {
if (profilingListener.isProfileRequest(origin, query)) {
// collect more profiling based on profiling rate etc
query.setProfilingListener(profilingListener);
}
}
if (queryTuning) {
// get current "tuned fetch" for this query point
TunedQueryInfo tuneInfo = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey());
return tuneInfo != null && tuneInfo.tuneQuery(query);
}
return false;
}
/**
* Return false for row count, find ids, subQuery, delete and Versions queries.
* <p>
* These queries are not applicable for autoTune in that they don't have a select/fetch (fetch group).
* </p>
* <p>
* We also exclude queries that are explicitly set to load the L2 bean cache as we want full beans
* in that case.
* </p>
*/
private boolean tunableQuery(SpiQuery<?> query) {
SpiQuery.Type type = query.getType();
switch (type) {
case COUNT:
case ATTRIBUTE:
case ID_LIST:
case UPDATE:
case DELETE:
case SUBQUERY:
return false;
default:
// not using autoTune when explicitly loading the l2 bean cache
// or when using Versions query
return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
}
}
private void profiling(SpiQuery<?> query, CallOrigin call) {
// create a query point to identify the query
ObjectGraphNode origin = query.setOrigin(call);
if (profilingListener.isProfileRequest(origin, query)) {
// collect more profiling based on profiling rate etc
query.setProfilingListener(profilingListener);
}
}
/**
* Return true if we should try to tune this query.
*/
private boolean useTuning(SpiQuery<?> query) {
Boolean autoTune = query.isAutoTune();
if (autoTune != null) {
// explicitly set...
return autoTune;
} else {
// determine using implicit mode...
switch (mode) {
case DEFAULT_ON:
return true;
case DEFAULT_OFF:
return false;
case DEFAULT_ONIFEMPTY:
return query.isDetailEmpty();
default:
throw new PersistenceException("Invalid AutoTuneMode " + mode);
}
}
}
/**
* Return the keys as a set.
*/
public Set<String> keySet() {
return tunedQueryInfoMap.keySet();
}
}
@@ -1,288 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.config.AutoTuneConfig;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.autotune.AutoTuneCollection;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.autotune.model.Autotune;
import io.ebeaninternal.server.autotune.model.Origin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
/**
* Implementation of the AutoTuneService which is comprised of profiling and query tuning.
*/
public class DefaultAutoTuneService implements AutoTuneService {
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class);
private final SpiEbeanServer server;
private final long defaultGarbageCollectionWait;
private final boolean skipGarbageCollectionOnShutdown;
private final boolean skipProfileReportingOnShutdown;
private final BaseQueryTuner queryTuner;
private final ProfileManager profileManager;
private final boolean profiling;
private final boolean queryTuning;
private final String tuningFile;
private final String profilingFile;
private final String serverName;
private final int profilingUpdateFrequency;
private long runtimeChangeCount;
public DefaultAutoTuneService(SpiEbeanServer server, DatabaseConfig databaseConfig) {
AutoTuneConfig config = databaseConfig.getAutoTuneConfig();
this.server = server;
this.queryTuning = config.isQueryTuning();
this.profiling = config.isProfiling();
this.tuningFile = config.getQueryTuningFile();
this.profilingFile = config.getProfilingFile();
this.profilingUpdateFrequency = config.getProfilingUpdateFrequency();
this.serverName = server.getName();
this.profileManager = new ProfileManager(config, server);
this.queryTuner = new BaseQueryTuner(config, server, profileManager);
this.skipGarbageCollectionOnShutdown = config.isSkipGarbageCollectionOnShutdown();
this.skipProfileReportingOnShutdown = config.isSkipProfileReportingOnShutdown();
this.defaultGarbageCollectionWait = config.getGarbageCollectionWait();
}
/**
* Load the query tuning information from it's data store.
*/
@Override
public void startup() {
if (queryTuning) {
loadTuningFile();
if (isRuntimeTuningUpdates()) {
// periodically gather and update query tuning
server.getBackgroundExecutor().executePeriodically(new ProfilingUpdate(), profilingUpdateFrequency, TimeUnit.SECONDS);
}
}
}
/**
* Return true if the tuning should update periodically at runtime.
*/
private boolean isRuntimeTuningUpdates() {
return profilingUpdateFrequency > 0;
}
private class ProfilingUpdate implements Runnable {
@Override
public void run() {
runtimeTuningUpdate();
}
}
/**
* Load tuning information from an existing tuning file.
*/
private void loadTuningFile() {
File file = new File(tuningFile);
if (file.exists()) {
loadAutoTuneProfiling(AutoTuneXmlReader.read(file));
} else {
// look for autotune as a resource
try (InputStream stream = getClass().getResourceAsStream("/" + tuningFile)) {
if (stream != null) {
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
} else {
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
}
} catch (IOException e) {
throw new IllegalStateException("Error on auto close of " + tuningFile, e);
}
}
}
private void loadAutoTuneProfiling(Autotune profiling) {
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
for (Origin origin : profiling.getOrigin()) {
queryTuner.put(origin);
}
}
/**
* Collect profiling, check for new/diff to existing tuning and apply changes.
*/
private void runtimeTuningUpdate() {
synchronized (this) {
try {
long start = System.currentTimeMillis();
AutoTuneCollection profiling = profileManager.profilingCollection(false);
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, true);
event.process();
if (event.isEmpty()) {
long exeMillis = System.currentTimeMillis() - start;
logger.debug("No query tuning updates for server:{} executionMillis:{}", serverName, exeMillis);
} else {
// report the query tuning changes that have been made
runtimeChangeCount += event.getChangeCount();
event.writeFile(profilingFile + "-" + serverName + "-update");
long exeMillis = System.currentTimeMillis() - start;
logger.info("query tuning updates - new:{} diff:{} for server:{} executionMillis:{}", event.getNewCount(), event.getDiffCount(), serverName, exeMillis);
}
} catch (Throwable e) {
logger.error("Error collecting or applying automatic query tuning", e);
}
}
}
private void saveProfilingOnShutdown(boolean reset) {
synchronized (this) {
if (isRuntimeTuningUpdates()) {
runtimeTuningUpdate();
outputAllTuning();
} else {
AutoTuneCollection profiling = profileManager.profilingCollection(reset);
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, false);
event.process();
if (event.isEmpty()) {
logger.info("No new or diff entries for profiling server:{}", serverName);
} else {
event.writeFile(profilingFile + "-" + serverName);
logger.info("writing new:{} diff:{} profiling entries for server:{}", event.getNewCount(), event.getDiffCount(), serverName);
}
}
}
}
/**
* Output all the query tuning (the "all" file).
* <p>
* This is the originally loaded tuning plus any tuning changes picked up and applied at runtime.
* </p>
* <p>
* This "all" file can be used as the next "ebean-autotune.xml" file.
* </p>
*/
private void outputAllTuning() {
if (runtimeChangeCount == 0) {
logger.info("no runtime query tuning changes for server:{}", serverName);
} else {
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
int size = event.size();
File existingTuning = new File(tuningFile);
if (existingTuning.exists()) {
// rename the existing autotune.xml file (appending 'now')
if (!existingTuning.renameTo(new File(tuningFile + "." + AutoTuneXmlWriter.now()))) {
logger.warn("Failed to rename autotune file [{}]", tuningFile);
}
}
event.writeFile(tuningFile, false);
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
}
}
/**
* Shutdown the listener.
* <p>
* We should try to collect the usage statistics by calling a System.gc().
* This is necessary for use with short lived applications where garbage
* collection may not otherwise occur at all.
* </p>
*/
@Override
public void shutdown() {
if (profiling) {
if (!skipGarbageCollectionOnShutdown && !skipProfileReportingOnShutdown) {
// trigger GC to update profiling information on recently executed queries
collectProfiling(-1);
}
if (!skipProfileReportingOnShutdown) {
saveProfilingOnShutdown(false);
}
}
}
/**
* Output the profiling.
* <p>
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
* to the existing tuning.
* </p>
*/
@Override
public void reportProfiling() {
saveProfilingOnShutdown(false);
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
* Really only want to do this sparingly but useful just prior to shutdown
* for short run application where garbage collection may otherwise not
* occur at all.
* </p>
* <p>
* waitMillis will do a thread sleep to give the garbage collection a little
* time to do its thing assuming we are shutting down the VM.
* </p>
* <p>
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
* defaults to 100 milliseconds.
* </p>
*/
@Override
public void collectProfiling() {
collectProfiling(-1);
}
public void collectProfiling(long waitMillis) {
System.gc();
try {
if (waitMillis < 0) {
waitMillis = defaultGarbageCollectionWait;
}
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
// restore the interrupted status
Thread.currentThread().interrupt();
logger.warn("Error while sleeping after System.gc() request.", e);
}
}
/**
* Auto tune the query and enable profiling.
*/
@Override
public boolean tuneQuery(SpiQuery<?> query) {
return queryTuner.tuneQuery(query);
}
}
@@ -1,127 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebean.config.AutoTuneConfig;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.autotune.AutoTuneCollection;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the collection of object graph usage profiling.
*/
public class ProfileManager implements ProfilingListener {
private final boolean queryTuningAddVersion;
/**
* Converted from a 0-100 int to a double. Effectively a percentage rate at
* which to collect profiling information.
*/
private final double profilingRate;
private final int profilingBase;
/**
* Map of the usage and query statistics gathered.
*/
private final Map<String, ProfileOrigin> profileMap = new ConcurrentHashMap<>();
private final Object monitor = new Object();
private final SpiEbeanServer server;
public ProfileManager(AutoTuneConfig config, SpiEbeanServer server) {
this.server = server;
this.profilingRate = config.getProfilingRate();
this.profilingBase = config.getProfilingBase();
this.queryTuningAddVersion = config.isQueryTuningAddVersion();
}
@Override
public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query) {
ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey());
if (profileOrigin == null) {
profileMap.put(origin.getOriginQueryPoint().getKey(), createProfileOrigin(origin, query));
return true;
} else {
return profileOrigin.isProfile();
}
}
/**
* Create the profile origin noting the query detail currently being used.
* <p>
* For new profiling entries it is useful to compare the profiling against the current
* query detail that is specified in the code (as the query might already be manually optimised).
* </p>
*/
private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery<?> query) {
ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate);
// set the current query detail (fetch group) so that we can compare against profiling for new entries
profileOrigin.setOriginalQuery(query.getDetail().toString());
return profileOrigin;
}
/**
* Gather query execution statistics. This could either be the originating
* query in which case the parentNode will be null, or a lazy loading query
* resulting from traversal of the object graph.
*/
@Override
public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) {
if (node != null) {
ObjectGraphOrigin origin = node.getOriginQueryPoint();
if (origin != null) {
ProfileOrigin stats = getProfileOrigin(origin);
stats.collectQueryInfo(node, beans, micros);
}
}
}
/**
* Collect usage statistics from a node in the object graph.
* <p>
* This is sent to use from a EntityBeanIntercept when the finalise method
* is called on the bean.
* </p>
*/
@Override
public void collectNodeUsage(NodeUsageCollector usageCollector) {
ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint());
profileOrigin.collectUsageInfo(usageCollector);
}
private ProfileOrigin getProfileOrigin(ObjectGraphOrigin originQueryPoint) {
synchronized (monitor) {
return profileMap.computeIfAbsent(originQueryPoint.getKey(), k -> new ProfileOrigin(originQueryPoint, queryTuningAddVersion, profilingBase, profilingRate));
}
}
/**
* Collect all the profiling information.
*/
public AutoTuneCollection profilingCollection(boolean reset) {
AutoTuneCollection req = new AutoTuneCollection();
for (ProfileOrigin origin : profileMap.values()) {
BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
if (desc != null) {
origin.profilingCollection(desc, req, reset);
}
}
return req;
}
}
@@ -1,172 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebean.text.PathProperties;
import io.ebean.text.PathProperties.Props;
import io.ebeaninternal.server.autotune.AutoTuneCollection;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class ProfileOrigin {
private static final long RESET_COUNT = -1000000000L;
private final ObjectGraphOrigin origin;
private final boolean queryTuningAddVersion;
private final int profilingBase;
private final double profilingRate;
private final Map<String, ProfileOriginQuery> queryStatsMap = new ConcurrentHashMap<>();
private final Map<String, ProfileOriginNodeUsage> nodeUsageMap = new ConcurrentHashMap<>();
private final Object monitor = new Object();
private final AtomicLong requestCount = new AtomicLong();
private final AtomicLong profileCount = new AtomicLong();
private String originalQuery;
public ProfileOrigin(ObjectGraphOrigin origin, boolean queryTuningAddVersion, int profilingBase, double profilingRate) {
this.origin = origin;
this.queryTuningAddVersion = queryTuningAddVersion;
this.profilingBase = profilingBase;
this.profilingRate = profilingRate;
}
public String getOriginalQuery() {
return originalQuery;
}
public void setOriginalQuery(String originalQuery) {
this.originalQuery = originalQuery;
}
/**
* Return true if this query should be profiled based on a percentage rate.
*/
public boolean isProfile() {
long count = requestCount.incrementAndGet();
if (count < profilingBase) {
return true;
}
long hits = profileCount.get();
if (profilingRate > (double) hits / count) {
profileCount.incrementAndGet();
return true;
} else {
return false;
}
}
/**
* Collect profiling information with the option to reset the underlying profiling detail.
*/
public void profilingCollection(BeanDescriptor<?> rootDesc, AutoTuneCollection req, boolean reset) {
synchronized (monitor) {
if (nodeUsageMap.isEmpty()) {
return;
}
OrmQueryDetail detail = buildDetail(rootDesc);
AutoTuneCollection.Entry entry = req.add(origin, detail, originalQuery);
Collection<ProfileOriginQuery> values = queryStatsMap.values();
for (ProfileOriginQuery queryEntry : values) {
entry.addQuery(queryEntry.createEntryQuery(reset));
}
if (reset) {
nodeUsageMap.clear();
if (requestCount.get() > RESET_COUNT) {
requestCount.set(profilingBase);
profileCount.set(0);
}
}
}
}
OrmQueryDetail buildDetail(BeanDescriptor<?> rootDesc) {
PathProperties pathProps = new PathProperties();
for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) {
statsNode.buildTunedFetch(pathProps, rootDesc, queryTuningAddVersion);
}
OrmQueryDetail detail = new OrmQueryDetail();
for (Props props : pathProps.getPathProps()) {
if (!props.isEmpty()) {
detail.fetch(props.getPath(), props.getPropertiesAsString(), null);
}
}
detail.sortFetchPaths(rootDesc);
return detail;
}
/**
* Return the origin.
*/
public ObjectGraphOrigin getOrigin() {
return origin;
}
/**
* Collect query execution summary statistics.
* <p>
* This can give us a quick overview into bad lazy loading areas etc.
* </p>
*/
public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) {
String key = node.getPath();
if (key == null) {
key = "";
}
ProfileOriginQuery stats = queryStatsMap.get(key);
if (stats == null) {
// a race condition but we don't care
stats = new ProfileOriginQuery(key);
queryStatsMap.put(key, stats);
}
stats.add(beansLoaded, micros);
}
/**
* Collect the usage information for from a instance for this node.
*/
public void collectUsageInfo(NodeUsageCollector profile) {
if (!profile.isEmpty()) {
getNodeStats(profile.getNode().getPath()).collectUsageInfo(profile);
}
}
private ProfileOriginNodeUsage getNodeStats(String path) {
synchronized (monitor) {
// handle null paths as using ConcurrentHashMap
path = (path == null) ? "" : path;
ProfileOriginNodeUsage nodeStats = nodeUsageMap.get(path);
if (nodeStats == null) {
nodeStats = new ProfileOriginNodeUsage(path);
nodeUsageMap.put(path, nodeStats);
}
return nodeStats;
}
}
}
@@ -1,125 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Collects usages statistics for a given node in the object graph.
*/
public class ProfileOriginNodeUsage {
private static final Logger logger = LoggerFactory.getLogger(ProfileOriginNodeUsage.class);
private final Object monitor = new Object();
private final String path;
private int profileCount;
private int profileUsedCount;
private boolean modified;
private final Set<String> aggregateUsed = new LinkedHashSet<>();
public ProfileOriginNodeUsage(String path) {
// handle null paths as using ConcurrentHashMap
this.path = "".equals(path) ? null : path;
}
protected void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc, boolean addVersionProperty) {
synchronized (monitor) {
BeanDescriptor<?> desc = rootDesc;
if (path != null) {
ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
if (elGetValue == null) {
logger.warn("AutoTune: Can't find join for path[" + path + "] for " + rootDesc.getName());
return;
} else {
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>) {
desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
}
}
}
BeanProperty toOneIdProperty = null;
boolean addedToPath = false;
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.findPropertyFromPath(propName);
if (beanProp == null) {
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
} else {
if (beanProp.isId()) {
// remember and maybe add ToOne property to parent path
toOneIdProperty = beanProp;
} else if (beanProp instanceof BeanPropertyAssoc<?>) {
// intentionally skip
} else {
//noinspection StatementWithEmptyBody
if (beanProp.isLob() && !beanProp.isFetchEager()) {
// AutoTune will not include Lob's marked FetchLazy
// (which is the default for Lob's so typical).
} else {
addedToPath = true;
pathProps.addToPath(path, beanProp.getName());
}
}
}
}
if ((modified || addVersionProperty) && desc != null) {
BeanProperty versionProp = desc.getVersionProperty();
if (versionProp != null) {
addedToPath = true;
pathProps.addToPath(path, versionProp.getName());
}
}
if (toOneIdProperty != null && !addedToPath) {
// add ToOne property to parent path
ElPropertyValue assocOne = rootDesc.getElGetValue(path);
pathProps.addToPath(SplitName.parent(path), assocOne.getName());
}
}
}
/**
* Collect usage from a node.
*/
protected void collectUsageInfo(NodeUsageCollector profile) {
synchronized (monitor) {
Set<String> used = profile.getUsed();
profileCount++;
if (!used.isEmpty()) {
profileUsedCount++;
aggregateUsed.addAll(used);
}
if (profile.isModified()) {
modified = true;
}
}
}
@Override
public String toString() {
return "path[" + path + "] profileCount[" + profileCount + "] used[" + profileUsedCount + "] props" + aggregateUsed;
}
}
@@ -1,43 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebeaninternal.server.autotune.AutoTuneCollection;
import java.io.Serializable;
import java.util.concurrent.atomic.LongAdder;
/**
* Used to accumulate query execution statistics for paths relative to the origin query.
*/
public class ProfileOriginQuery implements Serializable {
private static final long serialVersionUID = -1133958958072778811L;
private final String path;
private final LongAdder exeCount = new LongAdder();
private final LongAdder totalBeanLoaded = new LongAdder();
private final LongAdder totalMicros = new LongAdder();
public ProfileOriginQuery(String path) {
this.path = path;
}
public void add(long beansLoaded, long micros) {
exeCount.increment();
totalBeanLoaded.add(beansLoaded);
totalMicros.add(micros);
}
public AutoTuneCollection.EntryQuery createEntryQuery(boolean reset) {
if (reset) {
return new AutoTuneCollection.EntryQuery(path, exeCount.sumThenReset(), totalBeanLoaded.sumThenReset(), totalMicros.sumThenReset());
} else {
return new AutoTuneCollection.EntryQuery(path, exeCount.sum(), totalBeanLoaded.sum(), totalMicros.sum());
}
}
}
@@ -1,70 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebeaninternal.server.autotune.model.Autotune;
import io.ebeaninternal.server.autotune.model.Origin;
import io.ebeaninternal.server.autotune.model.ProfileDiff;
import io.ebeaninternal.server.autotune.model.ProfileEmpty;
import io.ebeaninternal.server.autotune.model.ProfileNew;
import java.util.Comparator;
import java.util.List;
/**
* Sorts Autotune document by
*/
public class SortAutoTuneDocument {
/**
* Set the diff and new entries by bean type followed by key.
*/
public static void sort(Autotune document) {
ProfileDiff profileDiff = document.getProfileDiff();
if (profileDiff != null) {
profileDiff.getOrigin().sort(NAME_KEY_SORT);
}
ProfileNew profileNew = document.getProfileNew();
if (profileNew != null) {
profileNew.getOrigin().sort(NAME_KEY_SORT);
}
ProfileEmpty profileEmpty = document.getProfileEmpty();
if (profileEmpty != null) {
profileEmpty.getOrigin().sort(KEY_SORT);
}
List<Origin> origins = document.getOrigin();
if (!origins.isEmpty()) {
origins.sort(NAME_KEY_SORT);
}
}
private static final OriginNameKeySort NAME_KEY_SORT = new OriginNameKeySort();
private static final OriginKeySort KEY_SORT = new OriginKeySort();
/**
* Comparator sort by bean type then key.
*/
private static class OriginNameKeySort implements Comparator<Origin> {
@Override
public int compare(Origin o1, Origin o2) {
int comp = o1.getBeanType().compareTo(o2.getBeanType());
if (comp == 0) {
comp = o1.getKey().compareTo(o2.getKey());
}
return comp;
}
}
/**
* Comparator sort by bean type then key.
*/
private static class OriginKeySort implements Comparator<Origin> {
@Override
public int compare(Origin o1, Origin o2) {
return o1.getKey().compareTo(o2.getKey());
}
}
}
@@ -1,71 +0,0 @@
package io.ebeaninternal.server.autotune.service;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.autotune.model.Origin;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryDetailParser;
import java.io.Serializable;
/**
* Holds tuned query information. Is immutable so this represents the tuning at
* a given point in time.
*/
public class TunedQueryInfo implements Serializable {
private static final long serialVersionUID = 8661702592481810396L;
private final Origin origin;
private final OrmQueryDetail tunedDetail;
public TunedQueryInfo(Origin origin) {
this.origin = origin;
this.tunedDetail = new OrmQueryDetailParser(origin.getDetail()).parse();
}
/**
* Return the origin entry (includes call stack and bean type).
*/
public Origin getOrigin() {
return origin;
}
/**
* Return the tuned detail (for comparison with profiling information).
*/
public OrmQueryDetail getTunedDetail() {
return tunedDetail;
}
/**
* Tune the query by replacing its OrmQueryDetail with a tuned one.
*
* @return true if the query was tuned, otherwise false.
*/
public boolean tuneQuery(SpiQuery<?> query) {
if (tunedDetail == null) {
return false;
}
boolean tuned;
if (query.isDetailEmpty()) {
tuned = true;
// tune by 'replacement'
query.setDetail(tunedDetail.copy());
} else {
// tune by 'addition'
tuned = query.tuneFetchProperties(tunedDetail);
}
if (tuned) {
query.setAutoTuned(true);
}
return tuned;
}
@Override
public String toString() {
return tunedDetail.toString();
}
}
@@ -33,7 +33,8 @@ import io.ebeaninternal.api.SpiLoggerFactory;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.autotune.service.AutoTuneServiceFactory;
import io.ebeaninternal.server.autotune.AutoTuneServiceProvider;
import io.ebeaninternal.server.autotune.NoAutoTuneService;
import io.ebeaninternal.server.cache.CacheManagerOptions;
import io.ebeaninternal.server.cache.DefaultCacheAdapter;
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
@@ -342,7 +343,8 @@ public class InternalConfiguration {
}
AutoTuneService createAutoTuneService(SpiEbeanServer server) {
return AutoTuneServiceFactory.create(server, config);
final AutoTuneServiceProvider provider = config.service(AutoTuneServiceProvider.class);
return provider == null ? new NoAutoTuneService() : provider.create(server, config);
}
DtoQueryEngine createDtoQueryEngine() {
@@ -1,55 +0,0 @@
<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:element ref="profileEmpty" 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="original" 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:element name="profileEmpty">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="origin" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>