diff --git a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
index 421e0c1511..d401b41adb 100644
--- a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
+++ b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
@@ -3,13 +3,24 @@
/**
* Request used to capture query plans.
*/
-public class QueryPlanRequest {
+public final class QueryPlanRequest {
private long since;
- private int maxCount;
+ private final int maxCount;
- private long maxTimeMillis;
+ private final long maxTimeMillis;
+
+ /**
+ * Create with the max number of plans and max capture time.
+ *
+ * @param maxCount The maximum number of plans to capture
+ * @param maxTimeMillis The maximum time after which we stop capturing more plans
+ */
+ public QueryPlanRequest(int maxCount, long maxTimeMillis) {
+ this.maxCount = maxCount;
+ this.maxTimeMillis = maxTimeMillis;
+ }
/**
* Return the epoch time in millis for minimum bind capture time.
@@ -39,16 +50,6 @@ public int maxCount() {
return maxCount;
}
- /**
- * Set the maximum number of plans to capture.
- *
- * Use this to limit how much query plan capturing is done as query
- * plan capture is actual database load.
- */
- public void maxCount(int maxCount) {
- this.maxCount = maxCount;
- }
-
/**
* Return the maximum amount of time we want to use to capture plans.
*
@@ -57,15 +58,4 @@ public void maxCount(int maxCount) {
public long maxTimeMillis() {
return maxTimeMillis;
}
-
- /**
- * Set the maximum amount of time we want to use to capture plans.
- *
- * Query plan collection will stop once this time is exceeded. We use
- * this to ensure the query plan capture does not use excessive amount
- * of time - put too much load on the database.
- */
- public void maxTimeMillis(long maxTimeMillis) {
- this.maxTimeMillis = maxTimeMillis;
- }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java
index 9c6a54952e..f692cece8e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java
@@ -8,6 +8,11 @@
final class NoopQueryPlanManager implements QueryPlanManager {
+ @Override
+ public void startPlanCapture() {
+ // do nothing
+ }
+
@Override
public void setDefaultThreshold(long thresholdMicros) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java
index da7ce2b430..a2478425ad 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java
@@ -12,6 +12,11 @@ public interface QueryPlanManager {
QueryPlanManager NOOP = new NoopQueryPlanManager();
+ /**
+ * Start background capture of query plans if enabled.
+ */
+ void startPlanCapture();
+
/**
* Update the global default threshold used when new query plans are created.
*/
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java
index b4a29697c3..928d10c6e3 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.meta.*;
+import io.ebeaninternal.api.QueryPlanManager;
import java.util.List;
import java.util.function.Function;
@@ -11,11 +12,13 @@
final class DefaultMetaInfoManager implements MetaInfoManager {
private final DefaultServer server;
+ private final QueryPlanManager queryPlanManager;
private final Function naming;
- DefaultMetaInfoManager(DefaultServer server, Function naming) {
+ DefaultMetaInfoManager(DefaultServer server, QueryPlanManager queryPlanManager) {
this.server = server;
- this.naming = naming;
+ this.naming = server.config().getMetricNaming();
+ this.queryPlanManager = queryPlanManager;
}
@Override
@@ -25,7 +28,7 @@ public List queryPlanInit(QueryPlanInit initRequest) {
@Override
public List queryPlanCollectNow(QueryPlanRequest request) {
- return server.queryPlanCollectNow(request);
+ return queryPlanManager.collect(request);
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index 0c5a8b45f3..8252a51a04 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -56,7 +56,6 @@
import java.time.Clock;
import java.util.*;
import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -105,7 +104,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final EncryptKeyManager encryptKeyManager;
private final SpiJsonContext jsonContext;
private final DocumentStore documentStore;
- private final MetaInfoManager metaInfoManager;
+ private final DefaultMetaInfoManager metaInfoManager;
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
private final PersistenceContextScope defaultPersistenceContextScope;
@@ -156,8 +155,8 @@ public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
- this.queryPlanManager = config.initQueryPlanManager(transactionManager);
- this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming());
+ this.queryPlanManager = config.initQueryPlanManager(this, transactionManager);
+ this.metaInfoManager = new DefaultMetaInfoManager(this, queryPlanManager);
this.serverPlugins = config.getPlugins();
this.ddlGenerator = config.initDdlGenerator(this);
this.scriptRunner = new DScriptRunner(this);
@@ -326,31 +325,7 @@ public void start() {
migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource());
}
- startQueryPlanCapture();
- }
-
- private void startQueryPlanCapture() {
- if (config.isQueryPlanCapture()) {
- long secs = config.getQueryPlanCapturePeriodSecs();
- if (secs > 10) {
- log.log(INFO, "capture query plan enabled, every {0}secs", secs);
- backgroundExecutor.scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
- }
- }
- }
-
- private void collectQueryPlans() {
- QueryPlanRequest request = new QueryPlanRequest();
- request.maxCount(config.getQueryPlanCaptureMaxCount());
- request.maxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
-
- // obtains query explain plans ...
- List plans = metaInfoManager.queryPlanCollectNow(request);
- QueryPlanListener listener = config.getQueryPlanListener();
- if (listener == null) {
- listener = DefaultQueryPlanListener.INSTANT;
- }
- listener.process(new QueryPlanCapture(this, plans));
+ queryPlanManager.startPlanCapture();
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
index 9e2dbbfdc6..14eca74723 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
@@ -575,12 +575,12 @@ private SpiCacheManager initCacheManager() {
return new DefaultServerCacheManager(builder);
}
- public QueryPlanManager initQueryPlanManager(TransactionManager transactionManager) {
+ public QueryPlanManager initQueryPlanManager(DefaultServer server, TransactionManager transactionManager) {
if (!config.isQueryPlanEnable()) {
return QueryPlanManager.NOOP;
}
long threshold = config.getQueryPlanThresholdMicros();
- return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics);
+ return new CQueryPlanManager(server, transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics);
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java
index b3a4091d41..3355d9aab8 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java
@@ -1,22 +1,27 @@
package io.ebeaninternal.server.query;
+import io.ebean.config.QueryPlanCapture;
+import io.ebean.config.QueryPlanListener;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.metric.TimedMetric;
import io.ebeaninternal.api.*;
+import io.ebeaninternal.server.core.DefaultServer;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.bind.capture.BindCapture;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.*;
import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.INFO;
import static java.util.Collections.emptyList;
public final class CQueryPlanManager implements QueryPlanManager {
+ private static final System.Logger log = CoreLog.internal;
private static final Object dummy = new Object();
private final ConcurrentHashMap plans = new ConcurrentHashMap<>();
@@ -24,14 +29,25 @@ public final class CQueryPlanManager implements QueryPlanManager {
private final QueryPlanLogger planLogger;
private final TimedMetric timeCollection;
private final TimedMetric timeBindCapture;
+ private final DefaultServer server;
+ private final QueryPlanListener listener;
+ private final int maxCount;
+ private final long maxTimeMillis;
private long defaultThreshold;
- public CQueryPlanManager(TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) {
+ public CQueryPlanManager(DefaultServer server, TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) {
+ this.server = server;
this.transactionManager = transactionManager;
this.defaultThreshold = defaultThreshold;
this.planLogger = planLogger;
this.timeCollection = extraMetrics.planCollect();
this.timeBindCapture = extraMetrics.bindCapture();
+
+ final var config = server.config();
+ this.maxCount = config.getQueryPlanCaptureMaxCount();
+ this.maxTimeMillis = config.getQueryPlanCaptureMaxTimeMillis();
+ final var planListener = config.getQueryPlanListener();
+ this.listener = (planListener != null) ? planListener : DefaultQueryPlanListener.INSTANT;
}
@Override
@@ -49,15 +65,28 @@ public void notifyBindCapture(CQueryBindCapture planBind, long startNanos) {
timeBindCapture.addSinceNanos(startNanos);
}
+ @Override
+ public void startPlanCapture() {
+ final var config = server.config();
+ if (config.isQueryPlanCapture()) {
+ long secs = config.getQueryPlanCapturePeriodSecs();
+ if (secs > 10) {
+ log.log(INFO, "capture query plan enabled, every {0}secs", secs);
+ server.backgroundExecutor().scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
+ }
+ }
+ }
+
+ private void collectQueryPlans() {
+ List plans = collect(new QueryPlanRequest(maxCount, maxTimeMillis));
+ listener.process(new QueryPlanCapture(server, plans));
+ }
+
@Override
public List collect(QueryPlanRequest request) {
if (plans.isEmpty()) {
return emptyList();
}
- return collectPlans(request);
- }
-
- private List collectPlans(QueryPlanRequest request) {
try (Connection connection = transactionManager.queryPlanConnection()) {
CQueryPlanRequest req = new CQueryPlanRequest(connection, request, plans.keySet().iterator());
while (req.hasNext()) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultQueryPlanListener.java
similarity index 96%
rename from ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java
rename to ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultQueryPlanListener.java
index f38b9c8fab..cabba02fac 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultQueryPlanListener.java
@@ -1,4 +1,4 @@
-package io.ebeaninternal.server.core;
+package io.ebeaninternal.server.query;
import io.avaje.applog.AppLog;
import io.ebean.config.QueryPlanCapture;
diff --git a/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java
index 823327e3f5..21e9e75c62 100644
--- a/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java
+++ b/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java
@@ -199,11 +199,8 @@ public void test_finders_queryPlans() {
}
// obtains db query plans ...
- QueryPlanRequest request = new QueryPlanRequest();
- // collect max 1000 plans (use something more like 10)
- request.maxCount(1_000);
- // don't collect any more plans if used 10 secs
- request.maxTimeMillis(10_000);
+ // collect max 10 plans, after 10 secs don't collect any more plans
+ var request = new QueryPlanRequest(10, 10_000);
List plans0 = server().metaInfo().queryPlanCollectNow(request);
assertThat(plans0).isNotEmpty();
@@ -214,7 +211,7 @@ public void test_finders_queryPlans() {
System.out.println(plan);
}
- //DB.getBackgroundExecutor().scheduleWithFixedDelay(...)
+ // DB.backgroundExecutor().scheduleWithFixedDelay(...)
}
@Test