#1345 - Refactor io.ebean.meta.MetaInfoManager API (query execution metrics). Breaking change for people collecting query execution metrics.

This commit is contained in:
Rob Bygrave
2018-03-10 02:51:51 +13:00
parent eda6350d89
commit 85bfe3d03e
79 changed files with 928 additions and 466 deletions
@@ -545,6 +545,11 @@ public interface ExpressionList<T> {
*/
Query<T> setDisableReadAuditing();
/**
* Set a label on the query (to help identify query execution statistics).
*/
Query<T> setLabel(String label);
/**
* Add expressions to the having clause.
* <p>
+9
View File
@@ -1419,6 +1419,15 @@ public interface Query<T> {
*/
Query<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set a label on the query.
* <p>
* This label can be used to help identify query performance metrics but we can also use
* profile location enhancement on Finders so for some that would be a better option.
* </p>
*/
Query<T> setLabel(String label);
/**
* Set to true if this query should execute against the doc store.
* <p>
+5
View File
@@ -115,6 +115,11 @@ public interface SqlQuery extends Serializable {
*/
SqlQuery setTimeout(int secs);
/**
* Set a label that can be put on performance metrics that are collected.
*/
SqlQuery setLabel(String label);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
+5
View File
@@ -152,6 +152,11 @@ public interface Update<T> {
*/
Update<T> setNullParameter(String name, int jdbcType);
/**
* Set a label meaning performance metrics will be collected for the execution of this update.
*/
Update<T> setLabel(String label);
/**
* Return the sql that is actually executed.
*/
@@ -0,0 +1,43 @@
package io.ebean.meta;
/**
* An abstract MetricVisitor that handles the boolean flags - reset, collectTransactionMetrics and collectQueryMetrics.
*/
public abstract class AbstractMetricVisitor implements MetricVisitor {
private final boolean reset;
private final boolean collectTransactionMetrics;
private final boolean collectQueryMetrics;
public AbstractMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
this.reset = reset;
this.collectTransactionMetrics = collectTransactionMetrics;
this.collectQueryMetrics = collectQueryMetrics;
}
@Override
public boolean isReset() {
return reset;
}
@Override
public boolean isCollectTransactionMetrics() {
return collectTransactionMetrics;
}
@Override
public boolean isCollectQueryMetrics() {
return collectQueryMetrics;
}
@Override
public void visitStart() {
// do nothing by default
}
@Override
public void visitEnd() {
// do nothing by default
}
}
@@ -0,0 +1,64 @@
package io.ebean.meta;
import java.util.ArrayList;
import java.util.List;
/**
* A simple MetricVisitor that can collect the desired metrics into lists.
*/
public class BasicMetricVisitor extends AbstractMetricVisitor {
private final List<MetaTimedMetric> timed = new ArrayList<>();
private final List<MetaQueryMetric> dtoQuery = new ArrayList<>();
private final List<MetaOrmQueryMetric> ormQuery = new ArrayList<>();
/**
* Construct to reset and collect everything.
*/
public BasicMetricVisitor() {
super(true, true, true);
}
/**
* Construct specifying reset and what to collect.
*/
public BasicMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
super(reset, collectTransactionMetrics, collectQueryMetrics);
}
/**
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
*/
public List<MetaTimedMetric> getTimedMetrics() {
return timed;
}
/**
* Return the DTO query metrics.
*/
public List<MetaQueryMetric> getDtoQueryMetrics() {
return dtoQuery;
}
/**
* Return the ORM query metrics.
*/
public List<MetaOrmQueryMetric> getOrmQueryMetrics() {
return ormQuery;
}
@Override
public void visitTimed(MetaTimedMetric metric) {
timed.add(metric);
}
@Override
public void visitQuery(MetaQueryMetric metric) {
dtoQuery.add(metric);
}
@Override
public void visitOrmQuery(MetaOrmQueryMetric metric) {
ormQuery.add(metric);
}
}
@@ -8,23 +8,20 @@ import java.util.List;
public interface MetaInfoManager {
/**
* Collect and return the transaction execution metrics.
* Visit the metrics resetting and collecting/reporting as desired.
*/
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
void visitMetrics(MetricVisitor visitor);
/**
* Collect query plan statistics (new, will migrate ORM query stats over to this).
* Run a visit collecting all the metrics and returning BasicMetricVisitor
* which holds all the metrics in simple lists.
*/
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
BasicMetricVisitor visitBasic();
/**
* Collect and return the non-empty query plan statistics for all the beans.
* <p>
* Note that this excludes the query plan statistics where there has been no
* executions (since the last collection with reset).
* </p>
* Just reset all the metrics. Maybe only useful for testing purposes.
*/
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
void resetAllMetrics();
/**
* Collect and return the ObjectGraphNode statistics.
@@ -36,6 +33,6 @@ public interface MetaInfoManager {
*
* @param reset Set to true to reset the underlying statistics after collection.
*/
List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset);
List<MetaOrmQueryNode> collectNodeStatistics(boolean reset);
}
@@ -0,0 +1,42 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
* Query execution statistics for Orm queries.
*/
public interface MetaOrmQueryMetric extends MetaQueryMetric {
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
boolean isAutoTuned();
/**
* Return a string representation of the query plan hash.
*/
String getQueryPlanHash();
/**
* Return the time of the last query executed using this plan.
*/
long getLastQueryTime();
/**
* Return the 'origin' points and paths that resulted in the query being
* executed and the associated number of times the query was executed via that
* path.
* <p>
* This includes direct and lazy loading paths.
* </p>
*/
List<MetaOrmQueryOrigin> getOrigins();
}
@@ -8,10 +8,8 @@ import io.ebean.bean.ObjectGraphNode;
* These statistics can be used to identify origin queries that result in lots
* of lazy loading.
* </p>
*
* @see MetaInfoManager#collectNodeStatistics(boolean)
*/
public interface MetaObjectGraphNodeStats {
public interface MetaOrmQueryNode {
/**
* Return the ObjectGraphNode which has the origin point and relative path.
@@ -9,11 +9,8 @@ import io.ebean.bean.ObjectGraphNode;
* This basically points to the bit of original code and query that results in
* this query directly or via lazy loading.
* </p>
*
* @see MetaQueryPlanStatistic
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
*/
public interface MetaQueryPlanOriginCount {
public interface MetaOrmQueryOrigin {
/**
* The 'origin' and path which this query belongs to.
@@ -1,98 +0,0 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
* Query execution statistics Meta data.
*
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
*/
public interface MetaQueryPlanStatistic {
/**
* Return the bean type this query plan is for.
*/
Class<?> getBeanType();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
boolean isAutoTuned();
/**
* Return a string representation of the query plan hash.
*/
String getQueryPlanHash();
/**
* Return the sql executed.
*/
String getSql();
/**
* Return the total number of queries executed.
*/
long getExecutionCount();
/**
* Return the total number of beans loaded by the queries.
* <p>
* This excludes background fetching.
* </p>
*/
long getTotalLoadedBeans();
/**
* Return the total time taken by executions of this query.
*/
long getTotalTimeMicros();
/**
* Return the max execution time for this query.
*/
long getMaxTimeMicros();
/**
* Return the time collection started (or was last reset).
*/
long getCollectionStart();
/**
* Return the time of the last query executed using this plan.
*/
long getLastQueryTime();
/**
* Return the average query execution time in microseconds.
* <p>
* This excludes background fetching.
* </p>
*/
long getAvgTimeMicros();
/**
* Return the average number of bean loaded per query.
* <p>
* This excludes background fetching.
* </p>
*/
long getAvgLoadedBeans();
/**
* Return the 'origin' points and paths that resulted in the query being
* executed and the associated number of times the query was executed via that
* path.
* <p>
* This includes direct and lazy loading paths.
* </p>
*/
List<MetaQueryPlanOriginCount> getOrigins();
}
@@ -6,6 +6,11 @@ package io.ebean.meta;
*/
public interface MetaTimedMetric {
/**
* Return the metric type.
*/
MetricType getMetricType();
/**
* Return the metric name.
*/
@@ -0,0 +1,30 @@
package io.ebean.meta;
/**
* The type of Metric.
*/
public enum MetricType {
/**
* Transactions.
*/
TXN,
/**
* ORM queries.
*/
ORM,
/**
* DTO queries.
*/
DTO,
/**
* SQL queries with a label will have metrics collected.
* <p>
* SqlQuery and SqlUpdate without a label have no metrics collected.
*/
SQL
}
@@ -0,0 +1,48 @@
package io.ebean.meta;
/**
* Defines visitor to read and report the transaction and query metrics.
*/
public interface MetricVisitor {
/**
* Return true if the metrics should be reset.
*/
boolean isReset();
/**
* Return true if we should visit the transaction metrics.
*/
boolean isCollectTransactionMetrics();
/**
* Return true if we should visit the ORM and SQL query metrics.
*/
boolean isCollectQueryMetrics();
/**
* Visit has started.
*/
void visitStart();
/**
* Visit transaction metrics (and L2 cache metrics in future).
*/
void visitTimed(MetaTimedMetric metric);
/**
* Visit DTO and SQL query metrics.
*/
void visitQuery(MetaQueryMetric metric);
/**
* Visit ORM query metrics.
*/
void visitOrmQuery(MetaOrmQueryMetric metric);
/**
* Visit has completed.
*/
void visitEnd();
}
@@ -14,6 +14,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
@@ -257,4 +258,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Execute the underlying ORM query returning as a JDBC ResultSet to map to DTO beans.
*/
SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction);
/**
* Visit all the metrics (typically reporting them).
*/
void visitMetrics(MetricVisitor visitor);
}
@@ -179,6 +179,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
ProfileLocation getProfileLocation();
/**
* Return the label set on the query.
*/
String getLabel();
/**
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
*/
@@ -15,6 +15,11 @@ public interface SpiSqlBinding {
*/
String getQuery();
/**
* Return the label (to collect metrics on when set).
*/
String getLabel();
/**
* Return the first row to fetch.
*/
@@ -43,6 +43,11 @@ public interface SpiUpdate<T> extends Update<T> {
*/
Class<?> getBeanType();
/**
* Return the label (for metrics collection).
*/
String getLabel();
/**
* Return the type of this - insert, update or delete.
*/
@@ -1,5 +1,7 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetricType;
/**
* Factory to create timed metric counters.
*/
@@ -15,20 +17,16 @@ public interface MetricFactory {
/**
* Create a timed metric group.
*/
TimedMetricMap createTimedMetricMap(String name);
TimedMetricMap createTimedMetricMap(MetricType metricType, String name);
/**
* Create a Timed metric.
*/
TimedMetric createTimedMetric(String name);
TimedMetric createTimedMetric(MetricType metricType, String name);
/**
* Create a Timed metric.
*/
QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql);
QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, String sql);
/**
* Return a instance used to collect Query plan metrics.
*/
QueryPlanCollector createCollector(boolean reset);
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetricVisitor;
/**
* Internal Query plan metric holder.
*/
@@ -11,7 +13,7 @@ public interface QueryPlanMetric {
TimedMetric getMetric();
/**
* Collect the non-empty query plan metrics.
* Visit the underlying metric.
*/
void collect(QueryPlanCollector collector);
void visit(MetricVisitor visitor);
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
import java.util.List;
import io.ebean.meta.MetricVisitor;
/**
* Metric for timed events like transaction execution times.
@@ -25,12 +23,17 @@ public interface TimedMetric {
boolean isEmpty();
/**
* Collect the timed metric statistics.
* Reset the statistics.
*/
void reset();
/**
* Collect and return a snapshot of the metrics.
*/
TimedMetricStats collect(boolean reset);
/**
* Add non empty metrics to the result.
* Visit non empty metrics.
*/
void collect(boolean reset, List<MetaTimedMetric> result);
void visit(MetricVisitor visitor);
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
import java.util.List;
import io.ebean.meta.MetricVisitor;
/**
* A map of timed metrics keyed by a string.
@@ -15,7 +13,12 @@ public interface TimedMetricMap {
void add(String key, long exeMicros);
/**
* Add non empty metrics to the given result.
* Add an execution for the given key including row/bean count.
*/
void collect(boolean reset, List<MetaTimedMetric> result);
void add(String key, long exeMicros, int rows);
/**
* Visit the metric.
*/
void visit(MetricVisitor visitor);
}
@@ -24,7 +24,7 @@ import java.sql.SQLException;
*/
public abstract class AbstractSqlQueryRequest {
private final SpiSqlBinding query;
protected final SpiSqlBinding query;
protected final SpiEbeanServer server;
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaOrmQueryNode;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
@@ -35,7 +35,7 @@ public class CObjectGraphNodeStatistics {
totalBeans.add(beanCount);
}
public MetaObjectGraphNodeStats get(boolean reset) {
public MetaOrmQueryNode get(boolean reset) {
if (reset) {
return new Snapshot(node, startTime.getAndSet(System.currentTimeMillis()), count.sumThenReset(),
totalTime.sumThenReset(), totalBeans.sumThenReset());
@@ -44,7 +44,7 @@ public class CObjectGraphNodeStatistics {
}
}
private static class Snapshot implements MetaObjectGraphNodeStats {
private static class Snapshot implements MetaOrmQueryNode {
private final ObjectGraphNode node;
private final long startTime;
@@ -1,12 +1,13 @@
package io.ebeaninternal.server.core;
import io.ebean.meta.AbstractMetricVisitor;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaOrmQueryMetric;
import io.ebean.meta.MetaOrmQueryNode;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
import io.ebean.meta.MetricVisitor;
import java.util.ArrayList;
import java.util.List;
@@ -23,29 +24,26 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
}
@Override
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return server.collectTransactionStatistics(reset);
public void visitMetrics(MetricVisitor visitor) {
server.visitMetrics(visitor);
}
@Override
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return server.collectQueryStatistics(reset);
public BasicMetricVisitor visitBasic() {
BasicMetricVisitor basic = new BasicMetricVisitor();
visitMetrics(basic);
return basic;
}
@Override
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
CQueryPlanStatsCollector collector = new CQueryPlanStatsCollector(reset);
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
desc.collectQueryPlanStatistics(collector);
}
return collector.getList();
public void resetAllMetrics() {
server.visitMetrics(new ResetVisitor());
}
@Override
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset) {
public List<MetaOrmQueryNode> collectNodeStatistics(boolean reset) {
List<MetaObjectGraphNodeStats> list = new ArrayList<>();
List<MetaOrmQueryNode> list = new ArrayList<>();
for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) {
if (!nodeStatistics.isEmpty()) {
list.add(nodeStatistics.get(reset));
@@ -54,4 +52,29 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
return list;
}
/**
* Visitor that resets the statistics but doesn't collect them.
*/
private static class ResetVisitor extends AbstractMetricVisitor {
ResetVisitor() {
super(true, true, true);
}
@Override
public void visitTimed(MetaTimedMetric metric) {
// ignore
}
@Override
public void visitQuery(MetaQueryMetric metric) {
// ignore
}
@Override
public void visitOrmQuery(MetaOrmQueryMetric metric) {
// ignore
}
}
}
@@ -48,8 +48,7 @@ import io.ebean.event.BeanPersistController;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.Property;
@@ -2192,10 +2191,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return transactionManager.collectTransactionStatistics(reset);
}
@Override
public Set<Property> checkUniqueness(Object bean) {
return checkUniqueness(bean, null);
@@ -2237,8 +2232,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Returns a set of properties if saving the bean will violate the unique constraints
* (definded by given properties).
* Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties).
*/
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props,
Transaction transaction) {
@@ -2261,15 +2255,24 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (findCount(query, transaction) > 0) {
Set<Property> ret = new LinkedHashSet<>();
for (Property prop : props) {
ret.add(prop);
}
Collections.addAll(ret, props);
return ret;
}
return null;
}
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return dtoBeanManager.collectStats(reset);
@Override
public void visitMetrics(MetricVisitor visitor) {
visitor.visitStart();
if (visitor.isCollectTransactionMetrics()) {
transactionManager.visitMetrics(visitor);
}
if (visitor.isCollectQueryMetrics()) {
beanDescriptorManager.visitMetrics(visitor);
dtoBeanManager.visitMetrics(visitor);
relationalQueryEngine.visitMetrics(visitor);
persister.visitMetrics(visitor);
}
visitor.visitEnd();
}
}
@@ -22,6 +22,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
UPDATESQL(EVT_UPDATESQL),
CALLABLESQL(EVT_CALLABLESQL);
String profileEventId;
Type(String profileEventId) {
this.profileEventId = profileEventId;
}
@@ -36,12 +37,30 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
final PersistExecute persistExecute;
protected String label;
protected long startNanos;
PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
super(server, t);
this.persistExecute = persistExecute;
}
/**
* Used by CallableSqlRequest and UpdateSqlRequest.
*/
public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
super(server, t);
this.persistExecute = persistExecute;
PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute, String label) {
this(server, t, persistExecute);
this.label = label;
}
/**
* Effectively set start nanos if we are collecting metrics on a label.
*/
public void startBind(boolean batchThisRequest) {
if (!batchThisRequest && label != null) {
startNanos = System.nanoTime();
}
}
/**
@@ -31,10 +31,9 @@ public final class PersistRequestCallableSql extends PersistRequest {
/**
* Create.
*/
public PersistRequestCallableSql(SpiEbeanServer server,
CallableSql cs, SpiTransaction t, PersistExecute persistExecute) {
public PersistRequestCallableSql(SpiEbeanServer server, CallableSql cs, SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute);
super(server, t, persistExecute, cs.getLabel());
this.type = PersistRequest.Type.CALLABLESQL;
this.callableSql = (SpiCallableSql) cs;
}
@@ -88,7 +87,9 @@ public final class PersistRequestCallableSql extends PersistRequest {
*/
@Override
public void postExecute() {
if (startNanos > 0) {
persistExecute.collectSqlCall(label, startNanos, rowCount);
}
if (transaction.isLogSummary()) {
String m = "CallableSql label[" + callableSql.getLabel() + "]" + " rows[" + rowCount + "]" + " bind[" + bindLog + "]";
transaction.logSummary(m);
@@ -27,7 +27,7 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
public PersistRequestOrmUpdate(SpiEbeanServer server, BeanManager<?> mgr, SpiUpdate<?> ormUpdate,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute);
super(server, t, persistExecute, ormUpdate.getLabel());
this.beanDescriptor = mgr.getBeanDescriptor();
this.ormUpdate = ormUpdate;
}
@@ -86,7 +86,9 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
*/
@Override
public void postExecute() {
if (startNanos > 0) {
persistExecute.collectOrmUpdate(label, startNanos, rowCount);
}
OrmUpdateType ormUpdateType = ormUpdate.getOrmUpdateType();
String tableName = ormUpdate.getBaseTable();
@@ -33,7 +33,7 @@ public final class PersistRequestUpdateSql extends PersistRequest {
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate updateSql,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute);
super(server, t, persistExecute, updateSql.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = (SpiSqlUpdate) updateSql;
}
@@ -102,7 +102,9 @@ public final class PersistRequestUpdateSql extends PersistRequest {
*/
@Override
public void postExecute() {
if (startNanos > 0) {
persistExecute.collectSqlUpdate(label, startNanos, rowCount);
}
if (transaction.isLogSummary()) {
String m = description + " table[" + tableName + "] rows[" + rowCount + "] bind[" + bindLog + "]";
transaction.logSummary(m);
@@ -6,6 +6,7 @@ import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.Update;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import java.util.Collection;
import java.util.List;
@@ -78,4 +79,8 @@ public interface Persister {
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.SqlRow;
import io.ebean.meta.MetricVisitor;
import java.util.List;
import java.util.function.Consumer;
@@ -29,4 +30,13 @@ public interface RelationalQueryEngine {
*/
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
/**
* Collect SQL query execution statistics.
*/
void collect(String label, long exeMicros, int rows);
/**
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
}
@@ -46,7 +46,11 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
@Override
protected void requestComplete() {
String label = query.getLabel();
if (label != null) {
long exeMicros = (System.nanoTime() - startNano) / 1000L;
queryEngine.collect(label, exeMicros, rows);
}
}
public void findEach(Consumer<SqlRow> consumer) {
@@ -27,6 +27,7 @@ import io.ebean.event.changelog.ChangeType;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.BeanDocType;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
@@ -61,7 +62,6 @@ import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -1598,10 +1598,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
return new DeployUpdateParser(this).parse(ormUpdateStatement);
}
public void collectQueryPlanStatistics(CQueryPlanStatsCollector collector) {
/**
* Visit all the ORM query plan metrics (includes UpdateQuery with updates and deletes).
*/
public void visitMetrics(MetricVisitor visitor) {
for (CQueryPlan queryPlan : queryPlanCache.values()) {
if (!queryPlan.isEmptyStats()) {
collector.add(queryPlan.getSnapshot(collector.isReset()));
visitor.visitOrmQuery(queryPlan.getSnapshot(visitor.isReset()));
}
}
}
@@ -19,6 +19,7 @@ import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.BeanType;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ConcurrencyMode;
@@ -1632,6 +1633,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
target.setPrimaryKeyJoin(inverseJoin);
}
public void visitMetrics(MetricVisitor visitor) {
for (BeanDescriptor<?> desc : immutableDescriptorList) {
desc.visitMetrics(visitor);
}
}
/**
* Comparator to sort the BeanDescriptors by name.
*/
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebean.meta.MetricVisitor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -37,9 +37,9 @@ public class DtoBeanDescriptor<T> {
plans.put(planKey, plan);
}
public void collectStats(QueryPlanCollector collector) {
public void visit(MetricVisitor visitor) {
for (DtoQueryPlan plan : plans.values()) {
plan.collectStats(collector);
plan.visit(visitor);
}
}
}
@@ -1,11 +1,8 @@
package io.ebeaninternal.server.dto;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.server.type.TypeManager;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -41,13 +38,9 @@ public class DtoBeanManager {
}
}
public List<MetaQueryMetric> collectStats(boolean reset) {
QueryPlanCollector collector = MetricFactory.get().createCollector(reset);
public void visitMetrics(MetricVisitor visitor) {
for (DtoBeanDescriptor value : descriptorMap.values()) {
value.collectStats(collector);
value.visit(visitor);
}
return collector.complete();
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.dto;
import io.ebean.meta.MetricType;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanMetric;
@@ -44,6 +45,6 @@ public class DtoMappingRequest {
}
public QueryPlanMetric createMetric() {
return MetricFactory.get().createQueryPlanMetric(type, label, sql);
return MetricFactory.get().createQueryPlanMetric(MetricType.DTO, type, label, sql);
}
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
@@ -21,7 +21,7 @@ public interface DtoQueryPlan {
void collect(long exeMicros, int rows);
/**
* Collect the query plan statistics.
* Visit the metric (if not empty).
*/
void collectStats(QueryPlanCollector collector);
void visit(MetricVisitor visitor);
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
@@ -21,8 +21,7 @@ abstract class DtoQueryPlanBase implements DtoQueryPlan {
}
@Override
public void collectStats(QueryPlanCollector collector) {
planMetric.collect(collector);
public void visit(MetricVisitor visitor) {
planMetric.visit(visitor);
}
}
@@ -513,6 +513,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.setDisableReadAuditing();
}
@Override
public Query<T> setLabel(String label) {
return query.setLabel(label);
}
@Override
public ExpressionList<T> having() {
return query.having();
@@ -798,6 +798,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.setCountDistinct(orderBy);
}
@Override
public Query<T> setLabel(String label) {
return exprList.setLabel(label);
}
@Override
public ExpressionList<T> startsWith(String propertyName, String value) {
return exprList.startsWith(propertyName, value);
@@ -1,6 +1,10 @@
package io.ebeaninternal.server.persist;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.core.PersistRequestCallableSql;
import io.ebeaninternal.server.core.PersistRequestOrmUpdate;
import io.ebeaninternal.server.core.PersistRequestUpdateSql;
@@ -24,15 +28,48 @@ final class DefaultPersistExecute implements PersistExecute {
*/
private final int defaultBatchSize;
private final TimedMetricMap ormUpdateMetric;
private final TimedMetricMap sqlUpdateMetric;
private final TimedMetricMap sqlCallMetric;
/**
* Construct this DmlPersistExecute.
*/
DefaultPersistExecute(Binder binder, int defaultBatchSize) {
this.exeOrmUpdate = new ExeOrmUpdate(binder);
this.exeUpdateSql = new ExeUpdateSql(binder);
this.exeCallableSql = new ExeCallableSql(binder);
this.defaultBatchSize = defaultBatchSize;
this.ormUpdateMetric = MetricFactory.get().createTimedMetricMap(MetricType.SQL, "orm.update.");
this.sqlUpdateMetric = MetricFactory.get().createTimedMetricMap(MetricType.SQL, "sql.update.");
this.sqlCallMetric = MetricFactory.get().createTimedMetricMap(MetricType.SQL, "sql.call.");
}
@Override
public void visitMetrics(MetricVisitor visitor) {
ormUpdateMetric.visit(visitor);
sqlUpdateMetric.visit(visitor);
sqlCallMetric.visit(visitor);
}
@Override
public void collectOrmUpdate(String label, long startNanos, int rowCount) {
long exeMicros = (System.nanoTime() - startNanos) / 1000L;
ormUpdateMetric.add(label, exeMicros, rowCount);
}
@Override
public void collectSqlUpdate(String label, long startNanos, int rowCount) {
long exeMicros = (System.nanoTime() - startNanos) / 1000L;
sqlUpdateMetric.add(label, exeMicros, rowCount);
}
@Override
public void collectSqlCall(String label, long startNanos, int rowCount) {
long exeMicros = (System.nanoTime() - startNanos) / 1000L;
sqlCallMetric.add(label, exeMicros, rowCount);
}
@Override
@@ -10,6 +10,7 @@ import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanPersistController;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdate;
@@ -79,6 +80,11 @@ public final class DefaultPersister implements Persister {
this.persistExecute = new DefaultPersistExecute(binder, server.getServerConfig().getPersistBatchSize());
}
@Override
public void visitMetrics(MetricVisitor visitor) {
persistExecute.visitMetrics(visitor);
}
/**
* Execute the CallableSql.
*/
@@ -60,6 +60,7 @@ class ExeCallableSql {
private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException {
request.startBind(batchThisRequest);
SpiCallableSql callableSql = request.getCallableSql();
SpiTransaction t = request.getTransaction();
@@ -75,6 +75,7 @@ class ExeOrmUpdate {
private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException {
request.startBind(batchThisRequest);
SpiUpdate<?> ormUpdate = request.getOrmUpdate();
SpiTransaction t = request.getTransaction();
@@ -83,6 +83,7 @@ class ExeUpdateSql {
private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException {
request.startBind(batchThisRequest);
SpiSqlUpdate updateSql = request.getUpdateSql();
SpiTransaction t = request.getTransaction();
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.persist;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.PersistRequestCallableSql;
import io.ebeaninternal.server.core.PersistRequestOrmUpdate;
@@ -34,4 +35,23 @@ public interface PersistExecute {
*/
int executeSqlUpdate(PersistRequestUpdateSql request);
/**
* Collect execution metrics for sql update.
*/
void collectOrmUpdate(String label, long startNanos, int rowCount);
/**
* Collect execution metrics for sql update.
*/
void collectSqlUpdate(String label, long startNanos, int rowCount);
/**
* Collect execution metrics for sql callable.
*/
void collectSqlCall(String label, long startNanos, int rowCount);
/**
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
@@ -12,22 +12,18 @@ import io.ebeaninternal.metric.TimedMetricMap;
public class DMetricFactory implements MetricFactory {
@Override
public TimedMetricMap createTimedMetricMap(String name) {
return new DTimedMetricMap(name);
public TimedMetricMap createTimedMetricMap(MetricType metricType, String name) {
return new DTimedMetricMap(metricType, name);
}
@Override
public TimedMetric createTimedMetric(String name) {
return new DTimedMetric(name);
public TimedMetric createTimedMetric(MetricType metricType, String name) {
return new DTimedMetric(metricType, name);
}
@Override
public QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql) {
return new DQueryPlanMetric(new DQueryPlanMeta(type, label, sql), createTimedMetric(label));
public QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, String sql) {
return new DQueryPlanMetric(new DQueryPlanMeta(type, label, sql), new DTimedMetric(metricType, label));
}
@Override
public QueryPlanCollector createCollector(boolean reset) {
return new DQueryPlanCollector(reset);
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetricType;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
@@ -18,7 +19,7 @@ public class DProfileLocationFactory implements SpiProfileLocationFactory {
@Override
public ProfileLocation create(int lineNumber, String label) {
TimedMetric timedMetric = MetricFactory.get().createTimedMetric("txn.named." + label);
TimedMetric timedMetric = MetricFactory.get().createTimedMetric(MetricType.TXN, "txn.named." + label);
DTimedProfileLocation loc = new DTimedProfileLocation(lineNumber, label, timedMetric);
TimedProfileLocationRegistry.register(loc);
@@ -1,7 +1,8 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
@@ -9,18 +10,18 @@ import io.ebeaninternal.metric.TimedMetricStats;
class DQueryPlanMetric implements QueryPlanMetric {
private final DQueryPlanMeta meta;
private final TimedMetric metric;
private final DTimedMetric metric;
DQueryPlanMetric(DQueryPlanMeta meta, TimedMetric metric) {
DQueryPlanMetric(DQueryPlanMeta meta, DTimedMetric metric) {
this.meta = meta;
this.metric = metric;
}
@Override
public void collect(QueryPlanCollector collector) {
TimedMetricStats stats = metric.collect(collector.isReset());
public void visit(MetricVisitor visitor) {
TimedMetricStats stats = metric.collect(visitor.isReset());
if (stats != null) {
collector.add(new Stats(meta, stats));
visitor.visitQuery(new Stats(meta, stats));
}
}
@@ -44,6 +45,11 @@ class DQueryPlanMetric implements QueryPlanMetric {
return meta + " " + stats + " sql:" + getSql();
}
@Override
public MetricType getMetricType() {
return stats.getMetricType();
}
@Override
public Class<?> getType() {
return meta.getType();
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.TimedMetricStats;
/**
@@ -7,6 +8,8 @@ import io.ebeaninternal.metric.TimedMetricStats;
*/
class DTimeMetricStats implements TimedMetricStats {
private final MetricType metricType;
private final String name;
private String location;
@@ -21,7 +24,8 @@ class DTimeMetricStats implements TimedMetricStats {
private final long beanCount;
DTimeMetricStats(String name, long collectionStart, long count, long total, long max, long beanCount) {
DTimeMetricStats(MetricType metricType, String name, long collectionStart, long count, long total, long max, long beanCount) {
this.metricType = metricType;
this.name = name;
this.startTime = collectionStart;
this.count = count;
@@ -53,6 +57,11 @@ class DTimeMetricStats implements TimedMetricStats {
this.location = location;
}
@Override
public MetricType getMetricType() {
return metricType;
}
@Override
public String getName() {
return name;
@@ -1,9 +1,9 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetric;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
@@ -16,6 +16,8 @@ import java.util.concurrent.atomic.LongAdder;
*/
class DTimedMetric implements TimedMetric {
private final MetricType metricType;
private final String name;
private final LongAdder beanCount = new LongAdder();
@@ -28,7 +30,8 @@ class DTimedMetric implements TimedMetric {
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
DTimedMetric(String name) {
DTimedMetric(MetricType metricType, String name) {
this.metricType = metricType;
this.name = name;
}
@@ -54,11 +57,23 @@ class DTimedMetric implements TimedMetric {
return count.sum() == 0;
}
/**
* Reset all the internal counters and start time.
*/
@Override
public void collect(boolean reset, List<MetaTimedMetric> result) {
DTimeMetricStats metric = collect(reset);
public void reset() {
startTime.set(System.currentTimeMillis());
max.reset();
count.reset();
total.reset();
beanCount.reset();
}
@Override
public void visit(MetricVisitor visitor) {
DTimeMetricStats metric = collect(visitor.isReset());
if (metric != null) {
result.add(metric);
visitor.visitTimed(metric);
}
}
@@ -78,7 +93,7 @@ class DTimedMetric implements TimedMetric {
/**
* Return the current statistics resetting the internal values if reset is true.
*/
public DTimeMetricStats getStatistics(boolean reset) {
private DTimeMetricStats getStatistics(boolean reset) {
if (reset) {
// Note these values are not guaranteed to be consistent wrt each other
@@ -88,21 +103,11 @@ class DTimedMetric implements TimedMetric {
final long totalVal = total.sumThenReset();
final long countVal = count.sumThenReset();
final long startTimeVal = startTime.getAndSet(System.currentTimeMillis());
return new DTimeMetricStats(name, startTimeVal, countVal, totalVal, maxVal, beans);
return new DTimeMetricStats(metricType, name, startTimeVal, countVal, totalVal, maxVal, beans);
} else {
return new DTimeMetricStats(name, startTime.get(), count.sum(), total.sum(), max.get(), beanCount.sum());
return new DTimeMetricStats(metricType, name, startTime.get(), count.sum(), total.sum(), max.get(), beanCount.sum());
}
}
/**
* Reset all the internal counters and start time.
*/
public void reset() {
startTime.set(System.currentTimeMillis());
max.reset();
count.reset();
total.reset();
}
}
@@ -1,30 +1,38 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetricMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
class DTimedMetricMap implements TimedMetricMap {
private final MetricType metricType;
private final String name;
private final ConcurrentHashMap<String, DTimedMetric> map = new ConcurrentHashMap<>();
DTimedMetricMap(String name) {
DTimedMetricMap(MetricType metricType, String name) {
this.metricType = metricType;
this.name = name;
}
@Override
public void add(String key, long exeMicros) {
map.computeIfAbsent(key, (k)-> new DTimedMetric(name + key)).add(exeMicros);
map.computeIfAbsent(key, (k) -> new DTimedMetric(metricType, name + key)).add(exeMicros);
}
@Override
public void collect(boolean reset, List<MetaTimedMetric> list) {
public void add(String key, long exeMicros, int rows) {
map.computeIfAbsent(key, (k) -> new DTimedMetric(metricType, name + key)).add(exeMicros, rows);
}
@Override
public void visit(MetricVisitor visitor) {
for (DTimedMetric value : map.values()) {
value.collect(reset, list);
value.visit(visitor);
}
}
}
@@ -1,11 +1,9 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
import java.util.List;
/**
* Default profile location that uses stack trace.
*/
@@ -37,13 +35,11 @@ class DTimedProfileLocation extends DProfileLocation implements TimedProfileLoca
}
@Override
public void collect(boolean reset, List<MetaTimedMetric> list) {
TimedMetricStats collect = timedMetric.collect(reset);
public void visit(MetricVisitor visitor) {
TimedMetricStats collect = timedMetric.collect(visitor.isReset());
if (collect != null) {
collect.setLocation(obtain());
list.add(collect);
visitor.visitTimed(collect);
}
}
}
@@ -1,11 +1,9 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetric;
import java.util.List;
/**
* ProfileLocation that collects timing metrics.
*/
@@ -22,7 +20,7 @@ public interface TimedProfileLocation extends ProfileLocation {
TimedMetric getMetric();
/**
* Collect the metrics adding to the given list if the metrics are non empty.
* Visit the non empty metrics.
*/
void collect(boolean reset, List<MetaTimedMetric> list);
void visit(MetricVisitor visitor);
}
@@ -3,8 +3,12 @@ package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.dbplatform.SqlLimitResponse;
import io.ebean.meta.MetricType;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -51,6 +55,10 @@ public class CQueryPlan {
private final ProfileLocation profileLocation;
private final String location;
private final String label;
private final CQueryPlanKey planKey;
private final boolean rawSql;
@@ -90,9 +98,12 @@ public class CQueryPlan {
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
this.planKey = request.getQueryPlanKey();
this.profileLocation = request.getQuery().getProfileLocation();
this.autoTuned = request.getQuery().isAutoTuned();
this.asOfTableCount = request.getQuery().getAsOfTableCount();
SpiQuery<?> query = request.getQuery();
this.profileLocation = query.getProfileLocation();
this.label = query.getLabel();
this.location = location();
this.autoTuned = query.isAutoTuned();
this.asOfTableCount = query.getAsOfTableCount();
this.sql = sqlRes.getSql();
this.rowNumberIncluded = sqlRes.isIncludesRowNumberColumn();
this.sqlTree = sqlTree;
@@ -105,13 +116,15 @@ public class CQueryPlan {
/**
* Create a query plan for a raw sql query.
*/
CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTree sqlTree,
boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTree sqlTree, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
this.server = request.getServer();
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
this.profileLocation = request.getQuery().getProfileLocation();
SpiQuery<?> query = request.getQuery();
this.profileLocation = query.getProfileLocation();
this.label = query.getLabel();
this.location = location();
this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
this.autoTuned = false;
this.asOfTableCount = 0;
@@ -124,6 +137,9 @@ public class CQueryPlan {
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
}
private String location() {
return (profileLocation == null) ? "" : profileLocation.shortDescription();
}
private CQueryPlanKey buildPlanKey(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
@@ -143,6 +159,14 @@ public class CQueryPlan {
return profileLocation;
}
public String getLabel() {
return label;
}
public String getLocation() {
return location;
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(dataTimeZone, rset);
}
@@ -240,17 +264,13 @@ public class CQueryPlan {
}
}
/**
* Return a copy of the current query statistics.
*/
public Snapshot getSnapshot(boolean reset) {
return stats.getSnapshot(reset);
}
/**
* Return the current query statistics.
*/
public CQueryPlanStats getQueryStats() {
return stats;
}
/**
* Return the time this query plan was last used.
*/
@@ -268,4 +288,8 @@ public class CQueryPlan {
public boolean isEmptyStats() {
return stats.isEmpty();
}
public TimedMetric createTimedMetric() {
return MetricFactory.get().createTimedMetric(MetricType.ORM, label);
}
}
@@ -2,16 +2,17 @@ package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.meta.MetaQueryPlanOriginCount;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.meta.MetaOrmQueryMetric;
import io.ebean.meta.MetaOrmQueryOrigin;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
/**
@@ -21,15 +22,7 @@ public final class CQueryPlanStats {
private final CQueryPlan queryPlan;
private final LongAdder count = new LongAdder();
private final LongAdder totalTime = new LongAdder();
private final LongAdder totalBeans = new LongAdder();
private final LongAccumulator maxTime = new LongAccumulator(Math::max, Long.MIN_VALUE);
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
private final TimedMetric timedMetric;
private long lastQueryTime;
@@ -39,16 +32,16 @@ public final class CQueryPlanStats {
* Construct for a given query plan.
*/
CQueryPlanStats(CQueryPlan queryPlan, boolean collectQueryOrigins) {
this.queryPlan = queryPlan;
this.origins = !collectQueryOrigins ? null : new ConcurrentHashMap<>();
this.timedMetric = queryPlan.createTimedMetric();
}
/**
* Return true if there are no statistics collected since the last reset.
*/
public boolean isEmpty() {
return count.sum() == 0;
return timedMetric.isEmpty();
}
/**
@@ -56,10 +49,7 @@ public final class CQueryPlanStats {
*/
public void add(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
count.increment();
totalBeans.add(loadedBeanCount);
totalTime.add(timeMicros);
maxTime.accumulate(timeMicros);
timedMetric.add(timeMicros, loadedBeanCount);
// not safe but should be atomic
lastQueryTime = System.currentTimeMillis();
@@ -82,14 +72,7 @@ public final class CQueryPlanStats {
* Reset the internal statistics counters.
*/
public void reset() {
// Racey but near enough for our purposes as we don't want locks
count.reset();
totalBeans.reset();
totalTime.reset();
maxTime.reset();
startTime.set(System.currentTimeMillis());
timedMetric.reset();
if (origins != null) {
for (LongAdder counter : origins.values()) {
counter.reset();
@@ -109,25 +92,20 @@ public final class CQueryPlanStats {
*/
Snapshot getSnapshot(boolean reset) {
List<MetaQueryPlanOriginCount> origins = getOrigins(reset);
// not guaranteed to be consistent due to time gaps between getting each value out of LongAdders but can live with that
// relative to the cost of making sure count and totalTime etc are all guaranteed to be consistent
if (reset) {
return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getThenReset(), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime, origins);
}
return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime, origins);
TimedMetricStats collect = timedMetric.collect(reset);
List<MetaOrmQueryOrigin> origins = getOrigins(reset);
return new Snapshot(queryPlan, collect, lastQueryTime, origins);
}
/**
* Return the list/snapshot of the origins and their counter value.
*/
private List<MetaQueryPlanOriginCount> getOrigins(boolean reset) {
private List<MetaOrmQueryOrigin> getOrigins(boolean reset) {
if (origins == null) {
return Collections.emptyList();
}
List<MetaQueryPlanOriginCount> list = new ArrayList<>(origins.size());
List<MetaOrmQueryOrigin> list = new ArrayList<>(origins.size());
for (Entry<ObjectGraphNode, LongAdder> entry : origins.entrySet()) {
if (reset) {
@@ -142,7 +120,7 @@ public final class CQueryPlanStats {
/**
* Snapshot of the origin ObjectGraphNode and counter value.
*/
private static class OriginSnapshot implements MetaQueryPlanOriginCount {
private static class OriginSnapshot implements MetaOrmQueryOrigin {
private final ObjectGraphNode objectGraphNode;
private final long count;
@@ -170,70 +148,83 @@ public final class CQueryPlanStats {
/**
* A snapshot of the current statistics for a query plan.
*/
public static class Snapshot implements MetaQueryPlanStatistic {
static class Snapshot implements MetaOrmQueryMetric {
private final CQueryPlan queryPlan;
private final long count;
private final long totalTime;
private final long totalBeans;
private final long maxTime;
private final long startTime;
private final TimedMetricStats metrics;
private final long lastQueryTime;
private final List<MetaQueryPlanOriginCount> origins;
Snapshot(CQueryPlan queryPlan, long count, long totalTime, long totalBeans, long maxTime, long startTime, long lastQueryTime,
List<MetaQueryPlanOriginCount> origins) {
private final List<MetaOrmQueryOrigin> origins;
Snapshot(CQueryPlan queryPlan, TimedMetricStats metrics, long lastQueryTime, List<MetaOrmQueryOrigin> origins) {
this.queryPlan = queryPlan;
this.count = count;
this.totalTime = totalTime;
this.totalBeans = totalBeans;
this.maxTime = maxTime;
this.startTime = startTime;
this.metrics = metrics;
this.lastQueryTime = lastQueryTime;
this.origins = origins;
}
@Override
public String toString() {
ProfileLocation profileLocation = queryPlan.getProfileLocation();
String loc = (profileLocation == null) ? "" : profileLocation.shortDescription();
return "location:" + loc + " count:" + count + " time:" + totalTime + " maxTime:" + maxTime + " beans:" + totalBeans + " sql:" + getSql();
return "location:" + getLocation() + " metrics:" + metrics + " sql:" + getSql();
}
@Override
public Class<?> getBeanType() {
public MetricType getMetricType() {
return MetricType.ORM;
}
@Override
public Class<?> getType() {
return queryPlan.getBeanType();
}
@Override
public String getLabel() {
return queryPlan.getLabel();
}
@Override
public String getName() {
return queryPlan.getLabel();
}
@Override
public String getLocation() {
return queryPlan.getLocation();
}
@Override
public ProfileLocation getProfileLocation() {
return queryPlan.getProfileLocation();
}
@Override
public long getExecutionCount() {
return count;
public long getBeanCount() {
return metrics.getBeanCount();
}
@Override
public long getTotalTimeMicros() {
return totalTime;
public long getCount() {
return metrics.getCount();
}
@Override
public long getTotalLoadedBeans() {
return totalBeans;
public long getTotal() {
return metrics.getTotal();
}
@Override
public long getMaxTimeMicros() {
return maxTime;
public long getMax() {
return metrics.getMax();
}
@Override
public long getCollectionStart() {
return startTime;
public long getMean() {
return metrics.getMean();
}
@Override
public long getStartTime() {
return metrics.getStartTime();
}
@Override
@@ -257,17 +248,7 @@ public final class CQueryPlanStats {
}
@Override
public long getAvgTimeMicros() {
return count < 1 ? 0 : totalTime / count;
}
@Override
public long getAvgLoadedBeans() {
return count < 1 ? 0 : totalBeans / count;
}
@Override
public List<MetaQueryPlanOriginCount> getOrigins() {
public List<MetaOrmQueryOrigin> getOrigins() {
return origins;
}
@@ -1,32 +0,0 @@
package io.ebeaninternal.server.query;
import io.ebean.meta.MetaQueryPlanStatistic;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to collect query plan execution statistics.
*/
public class CQueryPlanStatsCollector {
private final boolean reset;
List<MetaQueryPlanStatistic> list = new ArrayList<>();
public CQueryPlanStatsCollector(boolean reset) {
this.reset = reset;
}
public boolean isReset() {
return reset;
}
public void add(MetaQueryPlanStatistic planStatistic) {
list.add(planStatistic);
}
public List<MetaQueryPlanStatistic> getList() {
return list;
}
}
@@ -1,7 +1,11 @@
package io.ebeaninternal.server.query;
import io.ebean.SqlRow;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.RelationalQueryEngine;
import io.ebeaninternal.server.core.RelationalQueryRequest;
@@ -25,10 +29,23 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
private final boolean binaryOptimizedUUID;
private final TimedMetricMap timedMetricMap;
public DefaultRelationalQueryEngine(Binder binder, String dbTrueValue, boolean binaryOptimizedUUID) {
this.binder = binder;
this.dbTrueValue = dbTrueValue == null ? "true" : dbTrueValue;
this.binaryOptimizedUUID = binaryOptimizedUUID;
this.timedMetricMap = MetricFactory.get().createTimedMetricMap(MetricType.SQL, "sql.query.");
}
@Override
public void collect(String label, long exeMicros, int rows) {
timedMetricMap.add(label, exeMicros, rows);
}
@Override
public void visitMetrics(MetricVisitor visitor) {
timedMetricMap.visit(visitor);
}
@Override
@@ -51,6 +51,7 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
this.server = server;
this.descriptor = descriptor;
this.ormQuery = ormQuery;
this.label = ormQuery.getLabel();
}
/**
@@ -65,7 +66,7 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
@Override
public String planKey() {
return sql+":first"+firstRow+":max"+maxRows;
return sql + ":first" + firstRow + ":max" + maxRows;
}
@Override
@@ -96,6 +96,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private Type type;
private String label;
private Mode mode = Mode.NORMAL;
private Object tenantId;
@@ -340,6 +342,17 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return this;
}
@Override
public String getLabel() {
return label;
}
@Override
public Query<T> setLabel(String label) {
this.label = label;
return this;
}
@Override
public boolean isAutoTunable() {
return nativeSql == null && beanDescriptor.isAutoTunable();
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.EbeanServer;
import io.ebean.Update;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiUpdate;
@@ -23,6 +24,8 @@ public final class DefaultOrmUpdate<T> implements SpiUpdate<T>, Serializable {
*/
private final String name;
private String label;
/**
* The parameters used to bind to the sql.
*/
@@ -137,6 +140,17 @@ public final class DefaultOrmUpdate<T> implements SpiUpdate<T>, Serializable {
return name;
}
@Override
public Update<T> setLabel(String label) {
this.label = label;
return this;
}
@Override
public String getLabel() {
return label;
}
@Override
public String getUpdateStatement() {
return updateStatement;
@@ -19,6 +19,8 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
private final transient EbeanServer server;
private String label;
private String query;
private int firstRow;
@@ -122,6 +124,17 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
return this;
}
@Override
public String getLabel() {
return label;
}
@Override
public DefaultRelationalQuery setLabel(String label) {
this.label = label;
return this;
}
@Override
public BindParams getBindParams() {
return bindParams;
@@ -11,7 +11,8 @@ import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiProfileHandler;
@@ -37,7 +38,6 @@ import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -170,9 +170,9 @@ public class TransactionManager implements SpiTransactionManager {
this.transactionFactory = TransactionFactoryBuilder.build(this, dataSourceSupplier, tenantProvider);
MetricFactory metricFactory = MetricFactory.get();
this.txnMain = metricFactory.createTimedMetric("txn.main");
this.txnReadOnly = metricFactory.createTimedMetric("txn.readonly");
this.txnNamed = metricFactory.createTimedMetricMap("txn.named.");
this.txnMain = metricFactory.createTimedMetric(MetricType.TXN, "txn.main");
this.txnReadOnly = metricFactory.createTimedMetric(MetricType.TXN, "txn.readonly");
this.txnNamed = metricFactory.createTimedMetricMap(MetricType.TXN, "txn.named.");
scopeManager.register(this);
}
@@ -528,21 +528,13 @@ public class TransactionManager implements SpiTransactionManager {
txnNamed.add(label, exeMicros);
}
/**
* Collect the transaction execution statistics since the last reset.
*/
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
List<MetaTimedMetric> list = new ArrayList<>();
txnMain.collect(reset, list);
txnReadOnly.collect(reset, list);
public void visitMetrics(MetricVisitor visitor) {
txnMain.visit(visitor);
txnReadOnly.visit(visitor);
txnNamed.visit(visitor);
for (TimedProfileLocation timedLocation : TimedProfileLocationRegistry.registered()) {
timedLocation.collect(reset, list);
timedLocation.visit(visitor);
}
txnNamed.collect(reset, list);
return list;
}
/**
@@ -562,7 +554,7 @@ public class TransactionManager implements SpiTransactionManager {
if (st instanceof ScopedTransaction) {
// can be null for Supports as that can start as a 'No Transaction' and then
// effectively be replaced by transactions inside the scope
((ScopedTransaction)st).complete(returnOrThrowable, opCode);
((ScopedTransaction) st).complete(returnOrThrowable, opCode);
}
}