mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Refactor for query plan capture with initial threshold micros
This commit is contained in:
@@ -497,6 +497,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean collectQueryPlans;
|
||||
|
||||
/**
|
||||
* The default threshold in micros for collecting query plans.
|
||||
*/
|
||||
private long collectQueryPlanThresholdMicros = Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
*/
|
||||
@@ -2857,6 +2862,7 @@ public class ServerConfig {
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
|
||||
collectQueryPlanThresholdMicros = p.getLong("collectQueryPlanThresholdMicros", collectQueryPlanThresholdMicros);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
|
||||
@@ -3242,6 +3248,20 @@ public class ServerConfig {
|
||||
this.collectQueryPlans = collectQueryPlans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan collection threshold in microseconds.
|
||||
*/
|
||||
public long getCollectQueryPlanThresholdMicros() {
|
||||
return collectQueryPlanThresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query plan collection threshold in microseconds.
|
||||
*/
|
||||
public void setCollectQueryPlanThresholdMicros(long collectQueryPlanThresholdMicros) {
|
||||
this.collectQueryPlanThresholdMicros = collectQueryPlanThresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if metrics should be dumped when the server is shutdown.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
|
||||
/**
|
||||
* Extra metrics collected to measure internal behaviour.
|
||||
*/
|
||||
public class ExtraMetrics {
|
||||
|
||||
private final TimedMetric bindCapture;
|
||||
private final TimedMetric planCollect;
|
||||
|
||||
/**
|
||||
* Create the extra metrics.
|
||||
*/
|
||||
public ExtraMetrics() {
|
||||
final MetricFactory factory = MetricFactory.get();
|
||||
this.bindCapture = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.bindcapture");
|
||||
this.planCollect = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.collect");
|
||||
}
|
||||
|
||||
/**
|
||||
* Timed metric for bind capture used with query plan collection.
|
||||
*/
|
||||
public TimedMetric getBindCapture() {
|
||||
return bindCapture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timed metric for query plan collection.
|
||||
*/
|
||||
public TimedMetric getPlanCollect() {
|
||||
return planCollect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the metrics.
|
||||
*/
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
bindCapture.visit(visitor);
|
||||
planCollect.visit(visitor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
class NoopQueryBindCapture implements SpiQueryBindCapture {
|
||||
|
||||
@Override
|
||||
public boolean collectFor(long timeMicros) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBind(BindCapture bindCapture, long timeMicros, long startNanos) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectQueryPlan(QueryPlanRequest request) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
class NoopQueryPlanManager implements QueryPlanManager {
|
||||
|
||||
@Override
|
||||
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
|
||||
return SpiQueryBindCapture.NOOP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Manage query plan capture.
|
||||
*/
|
||||
public interface QueryPlanManager {
|
||||
|
||||
QueryPlanManager NOOP = new NoopQueryPlanManager();
|
||||
|
||||
/**
|
||||
* Create the bind capture for the given query plan.
|
||||
*/
|
||||
SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan);
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import io.ebean.TxScope;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.BeanLoader;
|
||||
import io.ebean.bean.CallOrigin;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
@@ -311,4 +310,9 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader,
|
||||
* Execute the sql update regardless of transaction batch mode.
|
||||
*/
|
||||
int executeNow(SpiSqlUpdate sqlUpdate);
|
||||
|
||||
/**
|
||||
* Create a query bind capture for the given query plan.
|
||||
*/
|
||||
SpiQueryBindCapture createQueryBindCapture(SpiQueryPlan queryPlan);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
/**
|
||||
* Capture query bind values and with those actual database query plans.
|
||||
*/
|
||||
public interface SpiQueryBindCapture {
|
||||
|
||||
/**
|
||||
* NOOP implementation.
|
||||
*/
|
||||
SpiQueryBindCapture NOOP = new NoopQueryBindCapture();
|
||||
|
||||
/**
|
||||
* Return true if the query just executed should be bind captured to collect
|
||||
* the query plan from (as the query time is large / interesting).
|
||||
*/
|
||||
boolean collectFor(long timeMicros);
|
||||
|
||||
/**
|
||||
* Set the bind capture and the related query execution time.
|
||||
*/
|
||||
void setBind(BindCapture bindCapture, long timeMicros, long startNanos);
|
||||
|
||||
/**
|
||||
* Collect the query execution plan usually executing the query to do so.
|
||||
*/
|
||||
void collectQueryPlan(QueryPlanRequest request);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
/**
|
||||
* The internal ORM "query plan".
|
||||
*/
|
||||
public interface SpiQueryPlan {
|
||||
|
||||
/**
|
||||
* The related entity bean type
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* The plan name.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* The hash for the query plan.
|
||||
*/
|
||||
String getHash();
|
||||
|
||||
/**
|
||||
* The SQL for the query plan.
|
||||
*/
|
||||
String getSql();
|
||||
|
||||
/**
|
||||
* The related profile location.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
}
|
||||
@@ -38,7 +38,6 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.CallOrigin;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.bean.PersistenceContext.WithOption;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
@@ -64,8 +63,10 @@ import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.LoadBeanRequest;
|
||||
import io.ebeaninternal.api.LoadManyRequest;
|
||||
import io.ebeaninternal.api.QueryPlanManager;
|
||||
import io.ebeaninternal.api.ScopedTransaction;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiDtoQuery;
|
||||
@@ -74,6 +75,8 @@ import io.ebeaninternal.api.SpiJsonContext;
|
||||
import io.ebeaninternal.api.SpiLogManager;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiQuery.Type;
|
||||
import io.ebeaninternal.api.SpiQueryBindCapture;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.api.SpiSqlQuery;
|
||||
import io.ebeaninternal.api.SpiSqlUpdate;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
@@ -138,7 +141,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@@ -162,6 +164,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final TransactionManager transactionManager;
|
||||
|
||||
private final QueryPlanManager queryPlanManager;
|
||||
|
||||
private final ExtraMetrics extraMetrics;
|
||||
|
||||
private final DataTimeZone dataTimeZone;
|
||||
|
||||
/**
|
||||
@@ -256,6 +262,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.serverCacheManager = cache;
|
||||
this.databasePlatform = config.getDatabasePlatform();
|
||||
this.backgroundExecutor = config.getBackgroundExecutor();
|
||||
this.queryPlanManager = config.getQueryPlanManager();
|
||||
this.extraMetrics = config.getExtraMetrics();
|
||||
|
||||
this.serverName = serverConfig.getName();
|
||||
this.lazyLoadBatchSize = serverConfig.getLazyLoadBatchSize();
|
||||
@@ -2385,7 +2393,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties).
|
||||
*/
|
||||
@@ -2430,6 +2437,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
relationalQueryEngine.visitMetrics(visitor);
|
||||
persister.visitMetrics(visitor);
|
||||
}
|
||||
extraMetrics.visitMetrics(visitor);
|
||||
visitor.visitEnd();
|
||||
}
|
||||
|
||||
@@ -2447,4 +2455,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
return request.getPlans();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiQueryBindCapture createQueryBindCapture(SpiQueryPlan plan) {
|
||||
return queryPlanManager.createBindCapture(plan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.QueryPlanManager;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiJsonContext;
|
||||
@@ -64,9 +66,15 @@ import io.ebeaninternal.server.persist.DefaultPersister;
|
||||
import io.ebeaninternal.server.persist.platform.MultiValueBind;
|
||||
import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
|
||||
import io.ebeaninternal.server.query.CQueryEngine;
|
||||
import io.ebeaninternal.server.query.CQueryPlanManager;
|
||||
import io.ebeaninternal.server.query.DefaultOrmQueryEngine;
|
||||
import io.ebeaninternal.server.query.DefaultRelationalQueryEngine;
|
||||
import io.ebeaninternal.server.query.DtoQueryEngine;
|
||||
import io.ebeaninternal.server.query.QueryPlanLogger;
|
||||
import io.ebeaninternal.server.query.QueryPlanLoggerExplain;
|
||||
import io.ebeaninternal.server.query.QueryPlanLoggerOracle;
|
||||
import io.ebeaninternal.server.query.QueryPlanLoggerPostgres;
|
||||
import io.ebeaninternal.server.query.QueryPlanLoggerSqlServer;
|
||||
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
|
||||
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
|
||||
import io.ebeaninternal.server.text.json.DJsonContext;
|
||||
@@ -164,8 +172,10 @@ public class InternalConfiguration {
|
||||
|
||||
private final SpiLogManager logManager;
|
||||
|
||||
private final ExtraMetrics extraMetrics = new ExtraMetrics();
|
||||
|
||||
InternalConfiguration(boolean online, ClusterManager clusterManager, SpiBackgroundExecutor backgroundExecutor,
|
||||
ServerConfig serverConfig, BootupClasses bootupClasses) {
|
||||
ServerConfig serverConfig, BootupClasses bootupClasses) {
|
||||
|
||||
this.online = online;
|
||||
this.serverConfig = serverConfig;
|
||||
@@ -242,6 +252,10 @@ public class InternalConfiguration {
|
||||
return clockService;
|
||||
}
|
||||
|
||||
public ExtraMetrics getExtraMetrics() {
|
||||
return extraMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a SpiServerPlugin and if so 'collect' it to give the complete list
|
||||
* later on the DefaultServer for late call to configure().
|
||||
@@ -433,8 +447,8 @@ public class InternalConfiguration {
|
||||
|
||||
TransactionManagerOptions options =
|
||||
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
|
||||
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
|
||||
tableModState, cacheNotify, clockService);
|
||||
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
|
||||
tableModState, cacheNotify, clockService);
|
||||
|
||||
if (serverConfig.isExplicitTransactionBeginMode()) {
|
||||
return new ExplicitTransactionManager(options);
|
||||
@@ -527,7 +541,7 @@ public class InternalConfiguration {
|
||||
}
|
||||
|
||||
private boolean isMySql(Platform platform) {
|
||||
return platform.base() == Platform.MYSQL;
|
||||
return platform.base() == Platform.MYSQL;
|
||||
}
|
||||
|
||||
public DataTimeZone getDataTimeZone() {
|
||||
@@ -636,4 +650,28 @@ public class InternalConfiguration {
|
||||
|
||||
return new DefaultServerCacheManager(builder);
|
||||
}
|
||||
|
||||
public QueryPlanManager getQueryPlanManager() {
|
||||
if (!serverConfig.isCollectQueryPlans()) {
|
||||
return QueryPlanManager.NOOP;
|
||||
}
|
||||
long threshold = serverConfig.getCollectQueryPlanThresholdMicros();
|
||||
return new CQueryPlanManager(threshold, queryPlanLogger(databasePlatform.getPlatform()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logger to log query plans for the given platform.
|
||||
*/
|
||||
QueryPlanLogger queryPlanLogger(Platform platform) {
|
||||
switch (platform.base()) {
|
||||
case POSTGRES:
|
||||
return new QueryPlanLoggerPostgres(extraMetrics);
|
||||
case SQLSERVER:
|
||||
return new QueryPlanLoggerSqlServer(extraMetrics);
|
||||
case ORACLE:
|
||||
return new QueryPlanLoggerOracle(extraMetrics);
|
||||
default:
|
||||
return new QueryPlanLoggerExplain(extraMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1714,19 +1714,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Trim query plans not used since the passed in epoch time.
|
||||
*/
|
||||
List<CQueryPlan> trimQueryPlans(long unusedSince) {
|
||||
|
||||
List<CQueryPlan> list = new ArrayList<>();
|
||||
|
||||
void trimQueryPlans(long unusedSince) {
|
||||
Iterator<CQueryPlan> it = queryPlanCache.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
CQueryPlan queryPlan = it.next();
|
||||
if (queryPlan.getLastQueryTime() < unusedSince) {
|
||||
it.remove();
|
||||
list.add(queryPlan);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -244,17 +244,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
* Run periodic trim of query plans.
|
||||
*/
|
||||
public void scheduleBackgroundTrim() {
|
||||
backgroundExecutor.executePeriodically(this::trimQueryPlans, 30L, TimeUnit.SECONDS);
|
||||
backgroundExecutor.executePeriodically(this::trimQueryPlans, 60L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void trimQueryPlans() {
|
||||
long lastUsed = System.currentTimeMillis() - (queryPlanTTLSeconds * 1000L);
|
||||
for (BeanDescriptor<?> descriptor : immutableDescriptorList) {
|
||||
if (!descriptor.isEmbedded()) {
|
||||
List<CQueryPlan> trimmedPlans = descriptor.trimQueryPlans(lastUsed);
|
||||
if (!trimmedPlans.isEmpty()) {
|
||||
logger.trace("trimmed {} query plans for type:{}", trimmedPlans.size(), descriptor.getName());
|
||||
}
|
||||
descriptor.trimQueryPlans(lastUsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebeaninternal.api.SpiQueryBindCapture;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
class CQueryBindCapture {
|
||||
class CQueryBindCapture implements SpiQueryBindCapture {
|
||||
|
||||
private static final double multiplier = 1.3d;
|
||||
|
||||
private final CQueryPlan cQueryPlan;
|
||||
private final SpiQueryPlan queryPlan;
|
||||
private final QueryPlanLogger planLogger;
|
||||
|
||||
private BindCapture bindCapture;
|
||||
@@ -17,16 +19,18 @@ class CQueryBindCapture {
|
||||
|
||||
private long lastBindCapture;
|
||||
|
||||
CQueryBindCapture(CQueryPlan cQueryPlan, QueryPlanLogger planLogger) {
|
||||
this.cQueryPlan = cQueryPlan;
|
||||
CQueryBindCapture(SpiQueryPlan queryPlan, QueryPlanLogger planLogger, long thresholdMicros) {
|
||||
this.queryPlan = queryPlan;
|
||||
this.planLogger = planLogger;
|
||||
this.thresholdMicros = thresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should capture the bind values for this query.
|
||||
*/
|
||||
boolean collectFor(long timeMicros) {
|
||||
return (bindCapture == null || timeMicros > thresholdMicros);
|
||||
@Override
|
||||
public boolean collectFor(long timeMicros) {
|
||||
return timeMicros > thresholdMicros && captureCount < 10;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,23 +38,25 @@ class CQueryBindCapture {
|
||||
*
|
||||
* @param bindCapture The bind values of the query
|
||||
* @param queryTimeMicros The query execution time
|
||||
* @param startNanos The nanos start of this bind capture
|
||||
*/
|
||||
void setBind(BindCapture bindCapture, long queryTimeMicros) {
|
||||
@Override
|
||||
public void setBind(BindCapture bindCapture, long queryTimeMicros, long startNanos) {
|
||||
synchronized (this) {
|
||||
this.thresholdMicros = Math.round(queryTimeMicros * multiplier);
|
||||
this.captureCount++;
|
||||
this.bindCapture = bindCapture;
|
||||
this.queryTimeMicros = queryTimeMicros;
|
||||
this.thresholdMicros = Math.round(queryTimeMicros * multiplier);
|
||||
captureCount++;
|
||||
lastBindCapture = System.currentTimeMillis();
|
||||
planLogger.addBindTimeSince(startNanos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collect the query plan using already captured bind values.
|
||||
*/
|
||||
void collectQueryPlan(QueryPlanRequest request) {
|
||||
|
||||
@Override
|
||||
public void collectQueryPlan(QueryPlanRequest request) {
|
||||
if (bindCapture == null || request.getSince() > lastBindCapture) {
|
||||
// no bind capture since the last capture
|
||||
return;
|
||||
@@ -58,9 +64,9 @@ class CQueryBindCapture {
|
||||
|
||||
final BindCapture last = this.bindCapture;
|
||||
|
||||
DQueryPlanOutput queryPlan = planLogger.logQueryPlan(request.getConnection(), cQueryPlan, last);
|
||||
DQueryPlanOutput queryPlan = planLogger.collectQueryPlan(request.getConnection(), this.queryPlan, last);
|
||||
if (queryPlan != null) {
|
||||
queryPlan.with(queryTimeMicros, captureCount, cQueryPlan.getHash());
|
||||
queryPlan.with(queryTimeMicros, captureCount, this.queryPlan.getHash());
|
||||
request.process(queryPlan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
@@ -10,6 +9,8 @@ import io.ebean.metric.TimedMetric;
|
||||
import io.ebeaninternal.api.CQueryPlanKey;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiQueryBindCapture;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
|
||||
@@ -47,7 +48,7 @@ import java.util.Set;
|
||||
* for performance tuning.
|
||||
* </p>
|
||||
*/
|
||||
public class CQueryPlan {
|
||||
public class CQueryPlan implements SpiQueryPlan {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CQueryPlan.class);
|
||||
|
||||
@@ -98,13 +99,12 @@ public class CQueryPlan {
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
private final CQueryBindCapture bindCapture;
|
||||
private final SpiQueryBindCapture bindCapture;
|
||||
|
||||
/**
|
||||
* Create a query plan based on a OrmQueryRequest.
|
||||
*/
|
||||
CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) {
|
||||
|
||||
this.server = request.getServer();
|
||||
this.dataTimeZone = server.getDataTimeZone();
|
||||
this.beanType = request.getBeanDescriptor().getBeanType();
|
||||
@@ -124,7 +124,7 @@ public class CQueryPlan {
|
||||
this.encryptedProps = sqlTree.getEncryptedProps();
|
||||
this.stats = new CQueryPlanStats(this);
|
||||
this.dependentTables = sqlTree.dependentTables();
|
||||
this.bindCapture = initBindCapture(server.getServerConfig(), query);
|
||||
this.bindCapture = initBindCapture(query);
|
||||
this.hash = md5Hash();
|
||||
}
|
||||
|
||||
@@ -132,7 +132,6 @@ public class CQueryPlan {
|
||||
* Create a query plan for a raw sql query.
|
||||
*/
|
||||
CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTree sqlTree, boolean rowNumberIncluded, String logWhereSql) {
|
||||
|
||||
this.server = request.getServer();
|
||||
this.dataTimeZone = server.getDataTimeZone();
|
||||
this.beanType = request.getBeanDescriptor().getBeanType();
|
||||
@@ -152,7 +151,7 @@ public class CQueryPlan {
|
||||
this.encryptedProps = sqlTree.getEncryptedProps();
|
||||
this.stats = new CQueryPlanStats(this);
|
||||
this.dependentTables = sqlTree.dependentTables();
|
||||
this.bindCapture = initBindCapture(server.getServerConfig(), query);
|
||||
this.bindCapture = initBindCapture(query);
|
||||
this.hash = md5Hash();
|
||||
}
|
||||
|
||||
@@ -166,12 +165,8 @@ public class CQueryPlan {
|
||||
return "orm." + beanType.getSimpleName() + "_" + label;
|
||||
}
|
||||
|
||||
private CQueryBindCapture initBindCapture(ServerConfig serverConfig, SpiQuery<?> query) {
|
||||
if (serverConfig.isCollectQueryPlans() && !query.getType().isUpdate()) {
|
||||
return new CQueryBindCapture(this, PlatformQueryPlan.getLogger(serverConfig.getDatabasePlatform().getPlatform()));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
private SpiQueryBindCapture initBindCapture(SpiQuery<?> query) {
|
||||
return query.getType().isUpdate() ? SpiQueryBindCapture.NOOP : server.createQueryBindCapture(this);
|
||||
}
|
||||
|
||||
private String location() {
|
||||
@@ -187,14 +182,27 @@ public class CQueryPlan {
|
||||
return beanType + " hash:" + planKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getBeanType() {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileLocation getProfileLocation() {
|
||||
return profileLocation;
|
||||
}
|
||||
@@ -203,8 +211,8 @@ public class CQueryPlan {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
@@ -242,10 +250,6 @@ public class CQueryPlan {
|
||||
return asOfTableCount;
|
||||
}
|
||||
|
||||
boolean isAutoTuned() {
|
||||
return autoTuned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a key used in audit logging to identify the query.
|
||||
*/
|
||||
@@ -277,14 +281,6 @@ public class CQueryPlan {
|
||||
}
|
||||
}
|
||||
|
||||
String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
SqlTree getSqlTree() {
|
||||
return sqlTree;
|
||||
}
|
||||
@@ -347,10 +343,10 @@ public class CQueryPlan {
|
||||
|
||||
void captureBindForQueryPlan(CQueryPredicates predicates, long executionTimeMicros) {
|
||||
try {
|
||||
long startNanos = System.nanoTime();
|
||||
DataBindCapture capture = bindCapture();
|
||||
predicates.bind(capture);
|
||||
bindCapture.setBind(capture.bindCapture(), executionTimeMicros);
|
||||
|
||||
bindCapture.setBind(capture.bindCapture(), executionTimeMicros, startNanos);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error capturing bind values", e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.api.QueryPlanManager;
|
||||
import io.ebeaninternal.api.SpiQueryBindCapture;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
|
||||
public class CQueryPlanManager implements QueryPlanManager {
|
||||
|
||||
private final long threshold;
|
||||
|
||||
private final QueryPlanLogger planLogger;
|
||||
|
||||
public CQueryPlanManager(long threshold, QueryPlanLogger planLogger) {
|
||||
this.threshold = threshold;
|
||||
this.planLogger = planLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
|
||||
return new CQueryBindCapture(queryPlan, planLogger, threshold);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
final class PlatformQueryPlan {
|
||||
|
||||
private static final QueryPlanLogger explainLogger = new QueryPlanLoggerExplain();
|
||||
|
||||
private static final QueryPlanLogger postgresLogger = new QueryPlanLoggerPostgres();
|
||||
|
||||
private static final QueryPlanLogger sqlServerLogger = new QueryPlanLoggerSqlServer();
|
||||
|
||||
private static final QueryPlanLogger oracleLogger = new QueryPlanLoggerOracle();
|
||||
|
||||
/**
|
||||
* Returns the logger to log query plans for the given platform.
|
||||
*/
|
||||
public static QueryPlanLogger getLogger(Platform platform) {
|
||||
|
||||
switch (platform) {
|
||||
case POSTGRES:
|
||||
return postgresLogger;
|
||||
|
||||
case SQLSERVER:
|
||||
case SQLSERVER16:
|
||||
case SQLSERVER17:
|
||||
return sqlServerLogger;
|
||||
|
||||
case ORACLE:
|
||||
return oracleLogger;
|
||||
|
||||
default:
|
||||
return explainLogger;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -12,9 +15,37 @@ public abstract class QueryPlanLogger {
|
||||
|
||||
static final Logger queryPlanLog = LoggerFactory.getLogger(QueryPlanLogger.class);
|
||||
|
||||
public abstract DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind);
|
||||
private final TimedMetric timeCollection;
|
||||
|
||||
DQueryPlanOutput readQueryPlan(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
private final TimedMetric timeBindCapture;
|
||||
|
||||
protected QueryPlanLogger(ExtraMetrics extraMetrics) {
|
||||
this.timeCollection = extraMetrics.getPlanCollect();
|
||||
this.timeBindCapture = extraMetrics.getBindCapture();
|
||||
}
|
||||
|
||||
abstract DQueryPlanOutput collect(Connection conn, SpiQueryPlan plan, BindCapture bind);
|
||||
|
||||
/**
|
||||
* Add timing for bind capture.
|
||||
*/
|
||||
public void addBindTimeSince(long startNanos) {
|
||||
timeBindCapture.addSinceNanos(startNanos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the DB query plan.
|
||||
*/
|
||||
public DQueryPlanOutput collectQueryPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
|
||||
long startNanos = System.nanoTime();
|
||||
try {
|
||||
return collect(conn, plan, bind);
|
||||
} finally {
|
||||
timeCollection.addSinceNanos(startNanos);
|
||||
}
|
||||
}
|
||||
|
||||
DQueryPlanOutput readQueryPlan(SpiQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
|
||||
sb.append(rset.getMetaData().getColumnLabel(i)).append("\t");
|
||||
@@ -25,11 +56,11 @@ public abstract class QueryPlanLogger {
|
||||
return createPlan(plan, bind.toString(), sb.toString());
|
||||
}
|
||||
|
||||
DQueryPlanOutput createPlan(CQueryPlan plan, String bind, String planString) {
|
||||
DQueryPlanOutput createPlan(SpiQueryPlan plan, String bind, String planString) {
|
||||
return new DQueryPlanOutput(plan.getBeanType(), plan.getName(), plan.getSql(), bind, planString, plan.getProfileLocation());
|
||||
}
|
||||
|
||||
DQueryPlanOutput readQueryPlanBasic(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
DQueryPlanOutput readQueryPlanBasic(SpiQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
readPlanData(sb, rset);
|
||||
return createPlan(plan, bind.toString(), sb.toString().trim());
|
||||
@@ -41,7 +72,7 @@ public abstract class QueryPlanLogger {
|
||||
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
|
||||
sb.append(rset.getString(i)).append("\t");
|
||||
}
|
||||
sb.setLength(sb.length()-1);
|
||||
sb.setLength(sb.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
/**
|
||||
* A QueryPlanLogger that prefixes "EXPLAIN " to the query. This works for Postgres, H2 and MySql.
|
||||
*/
|
||||
public class QueryPlanLoggerExplain extends QueryPlanLogger {
|
||||
|
||||
public QueryPlanLoggerExplain(ExtraMetrics extraMetrics) {
|
||||
super(extraMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
public DQueryPlanOutput collect(Connection conn, SpiQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN " + plan.getSql())) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
@@ -16,8 +18,12 @@ import java.sql.Statement;
|
||||
*/
|
||||
public class QueryPlanLoggerOracle extends QueryPlanLogger {
|
||||
|
||||
public QueryPlanLoggerOracle(ExtraMetrics extraMetrics) {
|
||||
super(extraMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
public DQueryPlanOutput collect(Connection conn, SpiQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN PLAN FOR " + plan.getSql())) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
@@ -12,8 +14,12 @@ import java.sql.SQLException;
|
||||
*/
|
||||
public class QueryPlanLoggerPostgres extends QueryPlanLogger {
|
||||
|
||||
public QueryPlanLoggerPostgres(ExtraMetrics extraMetrics) {
|
||||
super(extraMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
public DQueryPlanOutput collect(Connection conn, SpiQueryPlan plan, BindCapture bind) {
|
||||
|
||||
String explain = "explain analyze " + plan.getSql();
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement(explain)) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.api.ExtraMetrics;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
@@ -16,8 +18,12 @@ import java.sql.Statement;
|
||||
*/
|
||||
public class QueryPlanLoggerSqlServer extends QueryPlanLogger {
|
||||
|
||||
public QueryPlanLoggerSqlServer(ExtraMetrics extraMetrics) {
|
||||
super(extraMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
public DQueryPlanOutput collect(Connection conn, SpiQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("set statistics xml on");
|
||||
|
||||
Reference in New Issue
Block a user