diff --git a/src/main/java/io/ebean/ProfileLocation.java b/src/main/java/io/ebean/ProfileLocation.java index c7015ec7c..548858549 100644 --- a/src/main/java/io/ebean/ProfileLocation.java +++ b/src/main/java/io/ebean/ProfileLocation.java @@ -34,4 +34,10 @@ public interface ProfileLocation { * Obtain the location description. */ String obtain(); + + /** + * Return a short version of the location description. + */ + String shortDescription(); + } diff --git a/src/main/java/io/ebean/meta/MetaBeanInfo.java b/src/main/java/io/ebean/meta/MetaBeanInfo.java deleted file mode 100644 index 757737712..000000000 --- a/src/main/java/io/ebean/meta/MetaBeanInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.ebean.meta; - -import java.util.List; - -public interface MetaBeanInfo { - - /** - * Collect the current query plan statistics return the non-empty statistics. - */ - List collectQueryPlanStatistics(boolean reset); - - /** - * Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions). - */ - List collectAllQueryPlanStatistics(boolean reset); - -} diff --git a/src/main/java/io/ebean/meta/MetaInfoManager.java b/src/main/java/io/ebean/meta/MetaInfoManager.java index c8e280483..d38b8e0c2 100644 --- a/src/main/java/io/ebean/meta/MetaInfoManager.java +++ b/src/main/java/io/ebean/meta/MetaInfoManager.java @@ -8,17 +8,7 @@ import java.util.List; public interface MetaInfoManager { /** - * Return the MetaBeanInfo for a bean type. - */ - MetaBeanInfo getMetaBeanInfo(Class beanClass); - - /** - * Return all the MetaBeanInfo. - */ - List getMetaBeanInfoList(); - - /** - * Collect and return the query plan statistics for all the beans. + * Collect and return the non-empty query plan statistics for all the beans. *

* Note that this excludes the query plan statistics where there has been no * executions (since the last collection with reset). diff --git a/src/main/java/io/ebean/meta/MetaQueryPlanStatistic.java b/src/main/java/io/ebean/meta/MetaQueryPlanStatistic.java index abe862b4e..fc01a0e7e 100644 --- a/src/main/java/io/ebean/meta/MetaQueryPlanStatistic.java +++ b/src/main/java/io/ebean/meta/MetaQueryPlanStatistic.java @@ -1,5 +1,7 @@ package io.ebean.meta; +import io.ebean.ProfileLocation; + import java.util.List; /** @@ -14,6 +16,11 @@ public interface MetaQueryPlanStatistic { */ Class getBeanType(); + /** + * Return the profile location. + */ + ProfileLocation getProfileLocation(); + /** * Return true if this query plan was tuned by AutoTune. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java index ea5ef91bd..426f450bb 100644 --- a/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -180,9 +180,14 @@ public interface SpiQuery extends Query, TxnProfileEventCodes { ProfileLocation getProfileLocation(); /** - * Check for a single "equal to" expression for the Id. + * Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id. */ - void checkIdEqualTo(); + boolean isFindById(); + + /** + * Return true if this is a "find all" query. Used to set a "find all" profile location if necessary. + */ + boolean isFindAll(); /** * Return true if AutoTune should be attempted on this query. diff --git a/src/main/java/io/ebeaninternal/server/core/CObjectGraphNodeStatistics.java b/src/main/java/io/ebeaninternal/server/core/CObjectGraphNodeStatistics.java index eb4aa2a16..754c0a990 100644 --- a/src/main/java/io/ebeaninternal/server/core/CObjectGraphNodeStatistics.java +++ b/src/main/java/io/ebeaninternal/server/core/CObjectGraphNodeStatistics.java @@ -21,10 +21,14 @@ public class CObjectGraphNodeStatistics { private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis()); - public CObjectGraphNodeStatistics(ObjectGraphNode node) { + CObjectGraphNodeStatistics(ObjectGraphNode node) { this.node = node; } + public boolean isEmpty() { + return count.sum() == 0; + } + public void add(long beanCount, long exeMicros) { count.increment(); totalTime.add(exeMicros); diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java b/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java index f1ceb982f..ee1f7a3e0 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java @@ -1,9 +1,10 @@ package io.ebeaninternal.server.core; -import io.ebean.meta.MetaBeanInfo; import io.ebean.meta.MetaInfoManager; import io.ebean.meta.MetaObjectGraphNodeStats; import io.ebean.meta.MetaQueryPlanStatistic; +import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.query.CQueryPlanStatsCollector; import java.util.ArrayList; import java.util.List; @@ -15,41 +16,27 @@ public class DefaultMetaInfoManager implements MetaInfoManager { private final DefaultServer server; - public DefaultMetaInfoManager(DefaultServer server) { + DefaultMetaInfoManager(DefaultServer server) { this.server = server; } - @Override - public MetaBeanInfo getMetaBeanInfo(Class beanClass) { - return server.getBeanDescriptor(beanClass); - } - - @Override - public List getMetaBeanInfoList() { - - return new ArrayList<>(server.getBeanDescriptors()); - } - @Override public List collectQueryPlanStatistics(boolean reset) { - List list = new ArrayList<>(); - for (MetaBeanInfo metaBeanInfo : getMetaBeanInfoList()) { - list.addAll(metaBeanInfo.collectQueryPlanStatistics(reset)); + CQueryPlanStatsCollector collector = new CQueryPlanStatsCollector(reset); + for (BeanDescriptor desc : server.getBeanDescriptors()) { + desc.collectQueryPlanStatistics(collector); } - return list; + return collector.getList(); } @Override public List collectNodeStatistics(boolean reset) { List list = new ArrayList<>(); - for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) { - MetaObjectGraphNodeStats nodeStats = nodeStatistics.get(reset); - if (nodeStats.getCount() > 0) { - // Only collection non-empty statistics - list.add(nodeStats); + if (!nodeStatistics.isEmpty()) { + list.add(nodeStatistics.get(reset)); } } return list; diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 30a722a45..8adbf8642 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1133,6 +1133,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { query.selectAllForLazyLoadProperty(); + ProfileLocation profileLocation = query.getProfileLocation(); + if (profileLocation != null) { + profileLocation.obtain(); + } // if determine cost and no origin for AutoTune if (query.getParentNode() == null) { query.setOrigin(createCallStack()); @@ -1213,6 +1217,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } SpiOrmQueryRequest request = createQueryRequest(spiQuery, t); + request.profileLocationById(); if (request.isUseDocStore()) { return docStore().find(request); } @@ -1234,9 +1239,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { public T findOne(Query query, Transaction transaction) { SpiQuery spiQuery = (SpiQuery) query; - spiQuery.checkIdEqualTo(); - Object id = spiQuery.getId(); - if (id != null) { + if (spiQuery.isFindById()) { // actually a find by Id query return findId(query, transaction); } @@ -1537,6 +1540,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private List findList(Query query, Transaction t, boolean findOne) { SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, t); + request.profileLocationAll(); request.resetBeanCacheAutoMode(findOne); Object result = request.getFromQueryCache(); if (result != null) { diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index d20453e8b..d6d4376f6 100644 --- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -96,6 +96,20 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe return queryEngine.translate(this, bindLog, sql, e); } + @Override + public void profileLocationById() { + if (query.getProfileLocation() == null) { + query.setProfileLocation(beanDescriptor.profileLocationById()); + } + } + + @Override + public void profileLocationAll() { + if (query.getProfileLocation() == null && query.isFindAll()) { + query.setProfileLocation(beanDescriptor.profileLocationAll()); + } + } + @Override public boolean isMultiValueIdSupported() { return beanDescriptor.isMultiValueIdSupported(); diff --git a/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java index 49f7080ce..66830935b 100644 --- a/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -155,4 +155,14 @@ public interface SpiOrmQueryRequest extends DocQueryRequest { * Return true if this query is expected to use the doc store. */ boolean isUseDocStore(); + + /** + * Set profile location for "find by id" if not set. + */ + void profileLocationById(); + + /** + * Set profile location for "find all" if not set. + */ + void profileLocationAll(); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index b979463d1..f911712ed 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy; import io.ebean.OrderBy; import io.ebean.PersistenceContextScope; +import io.ebean.ProfileLocation; import io.ebean.Query; import io.ebean.SqlUpdate; import io.ebean.Transaction; @@ -27,8 +28,6 @@ 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.MetaBeanInfo; -import io.ebean.meta.MetaQueryPlanStatistic; import io.ebean.plugin.BeanDocType; import io.ebean.plugin.BeanType; import io.ebean.plugin.ExpressionPath; @@ -63,7 +62,7 @@ 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.CQueryPlanStats.Snapshot; +import io.ebeaninternal.server.query.CQueryPlanStatsCollector; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.text.json.ReadJson; @@ -100,7 +99,7 @@ import java.util.concurrent.ConcurrentHashMap; /** * Describes Beans including their deployment information. */ -public class BeanDescriptor implements MetaBeanInfo, BeanType { +public class BeanDescriptor implements BeanType { private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class); @@ -119,6 +118,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final Map namedQuery; private final short profileBeanId; + private final ProfileLocation locationById; + private final ProfileLocation locationAll; private final boolean multiValueSupported; @@ -415,6 +416,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.name = InternString.intern(deploy.getName()); this.baseTableAlias = "t0"; this.fullName = InternString.intern(deploy.getFullName()); + this.locationById = ProfileLocation.create(fullName+".byId"); + this.locationAll = ProfileLocation.create(fullName+".all"); this.profileBeanId = deploy.getProfileId(); this.beanType = deploy.getBeanType(); this.rootBeanType = PersistenceContextUtil.root(beanType); @@ -537,6 +540,20 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } } + /** + * Return a location for "find by id". + */ + public ProfileLocation profileLocationById() { + return locationById; + } + + /** + * Return a location for "find all". + */ + public ProfileLocation profileLocationAll() { + return locationAll; + } + /** * Return the id used in profiling to identify the bean type. */ @@ -1528,25 +1545,12 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return new DeployUpdateParser(this).parse(ormUpdateStatement); } - @Override - public List collectQueryPlanStatistics(boolean reset) { - return collectQueryPlanStatisticsInternal(reset, false); - } - - @Override - public List collectAllQueryPlanStatistics(boolean reset) { - return collectQueryPlanStatisticsInternal(reset, false); - } - - public List collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) { - List list = new ArrayList<>(queryPlanCache.size()); + public void collectQueryPlanStatistics(CQueryPlanStatsCollector collector) { for (CQueryPlan queryPlan : queryPlanCache.values()) { - Snapshot snapshot = queryPlan.getSnapshot(reset); - if (collectAll || snapshot.getExecutionCount() > 0) { - list.add(snapshot); + if (!queryPlan.isEmptyStats()) { + collector.add(queryPlan.getSnapshot(collector.isReset())); } } - return list; } /** diff --git a/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java b/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java index 543f99c91..94ef41106 100644 --- a/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java +++ b/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java @@ -8,17 +8,35 @@ import io.ebean.ProfileLocation; class BasicProfileLocation implements ProfileLocation { private final String location; + private final String shortDescription; BasicProfileLocation(String location) { this.location = location; + this.shortDescription = shortDesc(location); } public String toString() { - return "location: " + location; + return shortDescription; } public String obtain() { return location; } + @Override + public String shortDescription() { + return shortDescription; + } + + private String shortDesc(String location) { + int lastPer = location.lastIndexOf('.'); + if (lastPer > -1) { + lastPer = location.lastIndexOf('.', lastPer-1); + if (lastPer > -1) { + return location.substring(lastPer+1); + } + } + return location; + } + } diff --git a/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java b/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java index b64129b86..0ab43d9f1 100644 --- a/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java +++ b/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java @@ -13,6 +13,8 @@ class DProfileLocation implements ProfileLocation { private String location; + private String shortDescription; + private final int lineNumber; DProfileLocation() { @@ -31,10 +33,16 @@ class DProfileLocation implements ProfileLocation { // atomic assignment so happy with this if (location == null) { location = create(); + shortDescription = shortDesc(location); } return location; } + @Override + public String shortDescription() { + return shortDescription; + } + private String create() { StackTraceElement[] trace = Thread.currentThread().getStackTrace(); for (int i = 3; i < trace.length; i++) { @@ -52,4 +60,20 @@ class DProfileLocation implements ProfileLocation { return traceLine.substring(0, traceLine.length() - 1) + ":" + lineNumber + ")"; } } + + private String shortDesc(String location) { + int pos = location.lastIndexOf('('); + if (pos == -1) { + pos = location.length(); + } + + pos = location.lastIndexOf('.', pos); + if (pos > -1) { + pos = location.lastIndexOf('.', pos - 1); + if (pos > -1) { + return location.substring(pos + 1); + } + } + return location; + } } diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 5c2b84fb5..24e793666 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -108,7 +108,7 @@ class CQueryBuilder { if (queryPlan != null) { // skip building the SqlTree and Sql string predicates.prepare(false); - return new CQueryUpdate(type, request, predicates, queryPlan.getSql()); + return new CQueryUpdate(type, request, predicates, queryPlan); } predicates.prepare(true); @@ -125,7 +125,7 @@ class CQueryBuilder { // cache the query plan queryPlan = new CQueryPlan(request, sql, sqlTree, false, false, predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); - return new CQueryUpdate(type, request, predicates, sql); + return new CQueryUpdate(type, request, predicates, queryPlan); } private String buildDeleteSql(OrmQueryRequest request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) { @@ -251,8 +251,7 @@ class CQueryBuilder { if (queryPlan != null) { // skip building the SqlTree and Sql string predicates.prepare(false); - String sql = queryPlan.getSql(); - return new CQueryRowCount(queryPlan, request, predicates, sql); + return new CQueryRowCount(queryPlan, request, predicates); } predicates.prepare(true); @@ -283,7 +282,7 @@ class CQueryBuilder { queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); - return new CQueryRowCount(queryPlan, request, predicates, sql); + return new CQueryRowCount(queryPlan, request, predicates); } /** diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index de0b5cc1c..96b95bb46 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -55,7 +55,6 @@ public class CQueryEngine { this.defaultFetchSizeFindEach = serverConfig.getJdbcFetchSizeFindEach(); this.defaultFetchSizeFindList = serverConfig.getJdbcFetchSizeFindList(); this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate(); - this.historySupport = new CQueryHistorySupport(dbPlatform.getHistorySupport(), asOfTableMapping, serverConfig.getAsOfSysPeriod()); this.queryBuilder = new CQueryBuilder(dbPlatform, binder, historySupport, new CQueryDraftSupport(draftTableMap)); } @@ -331,8 +330,6 @@ public class CQueryEngine { * deemed to be a be a paging query - check that the order by contains the id * property to ensure unique row ordering for predicable paging but only in * case, this is not a distinct query - * - * @param request */ private void prepareForPaging(OrmQueryRequest request) { SpiQuery query = request.getQuery(); diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java b/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java index 088fba985..af6420980 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebeaninternal.api.SpiProfileTransactionEvent; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.core.OrmQueryRequest; @@ -18,10 +19,12 @@ import java.util.List; /** * Base compiled query request for single attribute queries. */ -class CQueryFetchSingleAttribute { +class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent { private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class); + private final CQueryPlan queryPlan; + /** * The overall find request wrapper object. */ @@ -56,17 +59,19 @@ class CQueryFetchSingleAttribute { private final ScalarType scalarType; + private long profileOffset; + /** * Create the Sql select based on the request. */ - CQueryFetchSingleAttribute(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan plan) { + CQueryFetchSingleAttribute(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { this.request = request; + this.queryPlan = queryPlan; this.query = request.getQuery(); - this.sql = plan.getSql(); + this.sql = queryPlan.getSql(); this.desc = request.getBeanDescriptor(); this.predicates = predicates; - this.scalarType = plan.getSingleAttributeScalarType(); - + this.scalarType = queryPlan.getSingleAttributeScalarType(); query.setGeneratedSql(sql); } @@ -102,6 +107,9 @@ class CQueryFetchSingleAttribute { executionTimeMicros = (System.nanoTime() - startNano) / 1000L; request.slowQueryCheck(executionTimeMicros, rowCount); + queryPlan.executionTime(rowCount, executionTimeMicros, null); + getTransaction().profileEvent(this); + return result; } finally { @@ -109,6 +117,10 @@ class CQueryFetchSingleAttribute { } } + private SpiTransaction getTransaction() { + return request.getTransaction(); + } + /** * Return the bind log. */ @@ -125,7 +137,8 @@ class CQueryFetchSingleAttribute { private void prepareExecute() throws SQLException { - SpiTransaction t = request.getTransaction(); + SpiTransaction t = getTransaction(); + profileOffset = t.profileOffset(); Connection conn = t.getInternalConnection(); pstmt = conn.prepareStatement(sql); @@ -166,4 +179,10 @@ class CQueryFetchSingleAttribute { } } + @Override + public void profile() { + getTransaction() + .profileStream() + .addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId()); + } } diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index 1e2d0f91f..670098f5b 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebean.ProfileLocation; import io.ebean.bean.ObjectGraphNode; import io.ebean.config.dbplatform.SqlLimitResponse; import io.ebeaninternal.api.CQueryPlanKey; @@ -48,6 +49,8 @@ public class CQueryPlan { private final boolean autoTuned; + private final ProfileLocation profileLocation; + private final CQueryPlanKey planKey; private final boolean rawSql; @@ -87,6 +90,7 @@ 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(); this.sql = sqlRes.getSql(); @@ -107,6 +111,7 @@ public class CQueryPlan { this.server = request.getServer(); this.dataTimeZone = server.getDataTimeZone(); this.beanType = request.getBeanDescriptor().getBeanType(); + this.profileLocation = request.getQuery().getProfileLocation(); this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql); this.autoTuned = false; this.asOfTableCount = 0; @@ -134,6 +139,10 @@ public class CQueryPlan { return beanType; } + public ProfileLocation getProfileLocation() { + return profileLocation; + } + public DataReader createDataReader(ResultSet rset) { return new RsetDataReader(dataTimeZone, rset); } @@ -252,4 +261,11 @@ public class CQueryPlan { ScalarType getSingleAttributeScalarType() { return sqlTree.getRootNode().getSingleAttributeScalarType(); } + + /** + * Return true if there are no statistics collected since the last reset. + */ + public boolean isEmptyStats() { + return stats.isEmpty(); + } } diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java b/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java index 7f78383de..371801037 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebean.ProfileLocation; import io.ebean.bean.ObjectGraphNode; import io.ebean.meta.MetaQueryPlanOriginCount; import io.ebean.meta.MetaQueryPlanStatistic; @@ -43,6 +44,13 @@ public final class CQueryPlanStats { this.origins = !collectQueryOrigins ? null : new ConcurrentHashMap<>(); } + /** + * Return true if there are no statistics collected since the last reset. + */ + public boolean isEmpty() { + return count.sum() == 0; + } + /** * Add a query execution to the statistics. */ @@ -188,8 +196,9 @@ public final class CQueryPlanStats { @Override public String toString() { - return queryPlan + " count:" + count + " time:" + totalTime + " maxTime:" + maxTime + " beans:" + totalBeans - + " start:" + startTime + " lastQuery:" + lastQueryTime + " origins:" + origins; + ProfileLocation profileLocation = queryPlan.getProfileLocation(); + String loc = (profileLocation == null) ? "" : profileLocation.shortDescription(); + return "location:" + loc + " count:" + count + " time:" + totalTime + " maxTime:" + maxTime + " beans:" + totalBeans + " sql:" + getSql(); } @Override @@ -197,6 +206,11 @@ public final class CQueryPlanStats { return queryPlan.getBeanType(); } + @Override + public ProfileLocation getProfileLocation() { + return queryPlan.getProfileLocation(); + } + @Override public long getExecutionCount() { return count; diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPlanStatsCollector.java b/src/main/java/io/ebeaninternal/server/query/CQueryPlanStatsCollector.java new file mode 100644 index 000000000..cb458e3c9 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPlanStatsCollector.java @@ -0,0 +1,32 @@ +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 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 getList() { + return list; + } +} diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java b/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java index 82b1b0cd7..efa590883 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebeaninternal.api.SpiProfileTransactionEvent; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.core.OrmQueryRequest; @@ -14,7 +15,7 @@ import java.sql.SQLException; /** * Executes the select row count query. */ -class CQueryRowCount { +class CQueryRowCount implements SpiProfileTransactionEvent { private final CQueryPlan queryPlan; @@ -53,17 +54,19 @@ class CQueryRowCount { private int rowCount; + private long profileOffset; + /** * Create the Sql select based on the request. */ - CQueryRowCount(CQueryPlan queryPlan, OrmQueryRequest request, CQueryPredicates predicates, String sql) { + CQueryRowCount(CQueryPlan queryPlan, OrmQueryRequest request, CQueryPredicates predicates) { this.queryPlan = queryPlan; this.request = request; this.query = request.getQuery(); - this.sql = sql; - query.setGeneratedSql(sql); + this.sql = queryPlan.getSql(); this.desc = request.getBeanDescriptor(); this.predicates = predicates; + query.setGeneratedSql(sql); } /** @@ -102,7 +105,8 @@ class CQueryRowCount { long startNano = System.nanoTime(); try { - SpiTransaction t = request.getTransaction(); + SpiTransaction t = getTransaction(); + profileOffset = t.profileOffset(); Connection conn = t.getInternalConnection(); pstmt = conn.prepareStatement(sql); @@ -121,6 +125,7 @@ class CQueryRowCount { executionTimeMicros = (System.nanoTime() - startNano) / 1000L; queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode()); request.slowQueryCheck(executionTimeMicros, rowCount); + getTransaction().profileEvent(this); return rowCount; } finally { @@ -128,6 +133,10 @@ class CQueryRowCount { } } + private SpiTransaction getTransaction() { + return request.getTransaction(); + } + /** * Close the resources. */ @@ -138,4 +147,10 @@ class CQueryRowCount { pstmt = null; } + @Override + public void profile() { + getTransaction() + .profileStream() + .addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId()); + } } diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java b/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java index 9c8faef06..a64738f0a 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.ebeaninternal.api.SpiProfileTransactionEvent; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.core.OrmQueryRequest; @@ -12,7 +13,9 @@ import java.sql.SQLException; /** * Executes the delete query. */ -class CQueryUpdate { +class CQueryUpdate implements SpiProfileTransactionEvent { + + private final CQueryPlan queryPlan; private final OrmQueryRequest request; @@ -43,17 +46,20 @@ class CQueryUpdate { private int rowCount; + private long profileOffset; + /** * Create the Sql select based on the request. */ - CQueryUpdate(String type, OrmQueryRequest request, CQueryPredicates predicates, String sql) { + CQueryUpdate(String type, OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { this.type = type; this.request = request; + this.queryPlan = queryPlan; this.query = request.getQuery(); - this.sql = sql; - query.setGeneratedSql(sql); + this.sql = queryPlan.getSql(); this.desc = request.getBeanDescriptor(); this.predicates = predicates; + query.setGeneratedSql(sql); } /** @@ -90,7 +96,8 @@ class CQueryUpdate { long startNano = System.nanoTime(); try { - SpiTransaction t = request.getTransaction(); + SpiTransaction t = getTransaction(); + profileOffset = t.profileOffset(); Connection conn = t.getInternalConnection(); pstmt = conn.prepareStatement(sql); @@ -103,6 +110,8 @@ class CQueryUpdate { executionTimeMicros = (System.nanoTime() - startNano) / 1000L; request.slowQueryCheck(executionTimeMicros, rowCount); + queryPlan.executionTime(rowCount, executionTimeMicros, null); + getTransaction().profileEvent(this); return rowCount; } finally { @@ -110,6 +119,10 @@ class CQueryUpdate { } } + private SpiTransaction getTransaction() { + return request.getTransaction(); + } + /** * Close the resources. */ @@ -118,4 +131,10 @@ class CQueryUpdate { pstmt = null; } + @Override + public void profile() { + getTransaction() + .profileStream() + .addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId()); + } } diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 29c7409ae..7a85fe2d8 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -277,14 +277,20 @@ public class DefaultOrmQuery implements SpiQuery { return beanDescriptor; } + + public boolean isFindAll() { + return whereExpressions == null && nativeSql == null && rawSql == null; + } + @Override - public void checkIdEqualTo() { + public boolean isFindById() { if (id == null && whereExpressions != null) { id = whereExpressions.idEqualTo(beanDescriptor.getIdName()); if (id != null) { whereExpressions = null; } } + return id != null; } @Override @@ -715,6 +721,7 @@ public class DefaultOrmQuery implements SpiQuery { DefaultOrmQuery copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory); copy.m2mIncludeJoin = m2mIncludeJoin; copy.profilingListener = profilingListener; + copy.profileLocation = profileLocation; copy.rootTableAlias = rootTableAlias; copy.distinct = distinct; diff --git a/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java b/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java index 14c3b9b22..6a23e4bae 100644 --- a/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java +++ b/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java @@ -10,8 +10,24 @@ public class BasicProfileLocationTest { public void obtain() { DProfileLocation loc = new DProfileLocation(12); - String obtain = loc.obtain(); - assertThat(obtain).endsWith(":12)"); + assertThat(loc.obtain()).endsWith(":12)"); + assertThat(loc.shortDescription()).isEqualTo("NativeMethodAccessorImpl.invoke0(Native Method:12)"); + } + + @Test + public void basic_trimPackage() { + + BasicProfileLocation loc = new BasicProfileLocation("com.foo.Bar.all"); + assertThat(loc.obtain()).isEqualTo("com.foo.Bar.all"); + assertThat(loc.shortDescription()).isEqualTo("Bar.all"); + } + + @Test + public void basic_trimSinglePackage() { + + BasicProfileLocation loc = new BasicProfileLocation("foo.Bar.all"); + assertThat(loc.obtain()).isEqualTo("foo.Bar.all"); + assertThat(loc.shortDescription()).isEqualTo("Bar.all"); } } diff --git a/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index 28047d00b..0d3ee55c9 100644 --- a/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -30,7 +30,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { assertThat(q1.getWhereExpressions()).isNotNull(); assertThat(q1.getId()).isNull(); - q1.checkIdEqualTo(); + assertThat(q1.isFindById()).isTrue(); assertThat(q1.getId()).isEqualTo(42); assertThat(q1.getWhereExpressions()).isNull(); @@ -41,7 +41,16 @@ public class DefaultOrmQueryTest extends BaseTestCase { DefaultOrmQuery q1 = (DefaultOrmQuery) Ebean.find(Order.class).where().idEq(42).query(); assertThat(q1.getId()).isEqualTo(42); - q1.checkIdEqualTo(); + assertThat(q1.isFindById()).isTrue(); + assertThat(q1.getId()).isEqualTo(42); + } + + @Test + public void checkForId_when_setId_ok() { + + DefaultOrmQuery q1 = (DefaultOrmQuery) Ebean.find(Order.class).setId(42); + assertThat(q1.getId()).isEqualTo(42); + assertThat(q1.isFindById()).isTrue(); assertThat(q1.getId()).isEqualTo(42); } diff --git a/src/test/java/org/tests/model/basic/finder/CustomerFinder.java b/src/test/java/org/tests/model/basic/finder/CustomerFinder.java index 63bedcfff..ac75b1762 100644 --- a/src/test/java/org/tests/model/basic/finder/CustomerFinder.java +++ b/src/test/java/org/tests/model/basic/finder/CustomerFinder.java @@ -54,4 +54,19 @@ public class CustomerFinder extends Finder { .setParameter(1, name+"%") .findSingleAttributeList(); } + + /** + * Bulk update names (not a good example here). + */ + public int updateNames(String newName, long minId) { + return update() + .set("name", newName) + .setRaw("version = version + 1") + .where().gt("id", minId) + .update(); + } + + public int totalCount() { + return query().findCount(); + } } diff --git a/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/src/test/java/org/tests/query/finder/TestCustomerFinder.java index 3c1f17554..65cae26a9 100644 --- a/src/test/java/org/tests/query/finder/TestCustomerFinder.java +++ b/src/test/java/org/tests/query/finder/TestCustomerFinder.java @@ -3,6 +3,8 @@ package org.tests.query.finder; import io.ebean.BaseTestCase; import io.ebean.Ebean; import io.ebean.Transaction; +import io.ebean.meta.MetaInfoManager; +import io.ebean.meta.MetaQueryPlanStatistic; import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import org.tests.model.basic.Customer; @@ -127,4 +129,34 @@ public class TestCustomerFinder extends BaseTestCase { List names = Customer.find.namesStartingWith("F"); assertThat(names).isNotEmpty(); } + + @Test + public void test_finders_queryPlans() { + + ResetBasicData.reset(); + + MetaInfoManager metaInfoManager = Ebean.getDefaultServer().getMetaInfoManager(); + metaInfoManager.collectQueryPlanStatistics(true); + + List customers = Customer.find.all(); + assertThat(customers).isNotEmpty(); + + Customer customer = Customer.find.byId(1); + assertThat(customer).isNotNull(); + Customer.find.byId(2); + + Customer.find.namesStartingWith("F"); + Customer.find.byNameStatus("Rob", Customer.Status.ACTIVE); + Customer.find.totalCount(); + Customer.find.updateNames("Junk", 2000); + Customer.find.byId(3); + + List planStats = metaInfoManager.collectQueryPlanStatistics(true); + assertThat(planStats).hasSize(6); + + for (MetaQueryPlanStatistic planStat : planStats) { + System.out.println(planStat); + } + } + } diff --git a/src/test/resources/ebean.mf b/src/test/resources/ebean.mf index ea8892b6f..c18791604 100644 --- a/src/test/resources/ebean.mf +++ b/src/test/resources/ebean.mf @@ -1,4 +1,5 @@ agent-use-only: true +profile-location: true entity-packages: org,misc transactional-packages: org querybean-packages: none