Refactor for query plan capture with initial threshold micros

This commit is contained in:
rob bygrave
2019-12-23 10:28:08 +13:00
parent 71295077a5
commit 6fcac27dfe
25 changed files with 388 additions and 114 deletions
@@ -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");