mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1027 - ENH: Add a Slow query event listener ... to log or listen to queries that exceed some defined execution time
This commit is contained in:
@@ -205,4 +205,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
|
||||
*/
|
||||
DataTimeZone getDataTimeZone();
|
||||
|
||||
/**
|
||||
* Check for slow query event.
|
||||
*/
|
||||
void slowQueryCheck(long executionTimeMicros, int rowCount, SpiQuery<?> query);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ import io.ebean.SqlRow;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.TxCallable;
|
||||
import io.ebean.TxRunnable;
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.Update;
|
||||
import io.ebean.UpdateQuery;
|
||||
@@ -43,6 +41,8 @@ import io.ebean.config.TenantMode;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.dbmigration.DdlGenerator;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.config.SlowQueryEvent;
|
||||
import io.ebean.config.SlowQueryListener;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
@@ -205,6 +205,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final boolean collectQueryStatsByNode;
|
||||
|
||||
private final long slowQueryMicros;
|
||||
|
||||
private final SlowQueryListener slowQueryListener;
|
||||
|
||||
/**
|
||||
* Cache used to collect statistics based on ObjectGraphNode (used to highlight lazy loading origin points).
|
||||
*/
|
||||
@@ -230,6 +234,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
|
||||
this.defaultPersistenceContextScope = serverConfig.getPersistenceContextScope();
|
||||
this.currentTenantProvider = serverConfig.getCurrentTenantProvider();
|
||||
this.slowQueryMicros = config.getSlowQueryMicros();
|
||||
this.slowQueryListener = config.getSlowQueryListener();
|
||||
|
||||
this.beanDescriptorManager = config.getBeanDescriptorManager();
|
||||
beanDescriptorManager.setEbeanServer(this);
|
||||
@@ -2221,4 +2227,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void slowQueryCheck(long timeMicros, int rowCount, SpiQuery<?> query) {
|
||||
if (timeMicros > slowQueryMicros) {
|
||||
if (slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.config.SlowQueryEvent;
|
||||
import io.ebean.config.SlowQueryListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Default slow query listener implementation that logs a warning message.
|
||||
*/
|
||||
class DefaultSlowQueryListener implements SlowQueryListener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger("io.ebean.SlowQuery");
|
||||
|
||||
@Override
|
||||
public void process(SlowQueryEvent event) {
|
||||
|
||||
String firstStack = "";
|
||||
ObjectGraphNode node = event.getOriginNode();
|
||||
if (node != null) {
|
||||
firstStack = node.getOriginQueryPoint().getFirstStackElement();
|
||||
}
|
||||
log.warn("Slow query warning - millis:{} rows:{} caller[{}] sql[{}]", event.getTimeMillis(), event.getRowCount(), firstStack, event.getSql());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import io.ebean.config.ExternalTransactionManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbHistorySupport;
|
||||
import io.ebean.config.SlowQueryListener;
|
||||
import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
@@ -432,4 +433,30 @@ public class InternalConfiguration {
|
||||
public ServerCacheManager cache() {
|
||||
return new DefaultCacheAdapter(cacheManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slow query warning limit in micros.
|
||||
*/
|
||||
long getSlowQueryMicros() {
|
||||
long millis = serverConfig.getSlowQueryMillis();
|
||||
return (millis < 1) ? Long.MAX_VALUE : millis * 1000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SlowQueryListener with a default that logs a warning message.
|
||||
*/
|
||||
SlowQueryListener getSlowQueryListener() {
|
||||
long millis = serverConfig.getSlowQueryMillis();
|
||||
if (millis < 1) {
|
||||
return null;
|
||||
}
|
||||
SlowQueryListener listener = serverConfig.getSlowQueryListener();
|
||||
if (listener == null) {
|
||||
listener = serverConfig.service(SlowQueryListener.class);
|
||||
if (listener == null) {
|
||||
listener = new DefaultSlowQueryListener();
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,4 +597,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
public Object getTenantId() {
|
||||
return (transaction == null) ? null : transaction.getTenantId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for slow query event.
|
||||
*/
|
||||
public void slowQueryCheck(long executionTimeMicros, int rowCount) {
|
||||
ebeanServer.slowQueryCheck(executionTimeMicros, rowCount, query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An object that represents a SqlSelect statement.
|
||||
@@ -561,16 +560,28 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update execution stats and check for slow query.
|
||||
*/
|
||||
void updateExecutionStatistics() {
|
||||
try {
|
||||
long exeNano = System.nanoTime() - startNano;
|
||||
executionTimeMicros = TimeUnit.NANOSECONDS.toMicros(exeNano);
|
||||
updateStatistics();
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update execution stats but skip slow query check as expected large query.
|
||||
*/
|
||||
void updateExecutionStatisticsIterator() {
|
||||
updateStatistics();
|
||||
}
|
||||
|
||||
private void updateStatistics() {
|
||||
try {
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
if (autoTuneProfiling) {
|
||||
profilingListener.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros);
|
||||
}
|
||||
queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error updating execution statistics", e);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class CQueryFetchSingleAttribute {
|
||||
|
||||
private String bindLog;
|
||||
|
||||
private int executionTimeMicros;
|
||||
private long executionTimeMicros;
|
||||
|
||||
private int rowCount;
|
||||
|
||||
@@ -91,20 +91,17 @@ class CQueryFetchSingleAttribute {
|
||||
|
||||
long startNano = System.nanoTime();
|
||||
try {
|
||||
|
||||
prepareExecute();
|
||||
|
||||
List<Object> result = new ArrayList<>();
|
||||
|
||||
while (dataReader.next()) {
|
||||
result.add(scalarType.read(dataReader));
|
||||
dataReader.resetColumnPosition();
|
||||
rowCount++;
|
||||
}
|
||||
|
||||
long exeNano = System.nanoTime() - startNano;
|
||||
executionTimeMicros = (int) exeNano / 1000;
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
return result;
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -38,7 +38,7 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
cquery.updateExecutionStatistics();
|
||||
cquery.updateExecutionStatisticsIterator();
|
||||
cquery.close();
|
||||
request.endTransIfRequired();
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
cquery.updateExecutionStatistics();
|
||||
cquery.updateExecutionStatisticsIterator();
|
||||
cquery.close();
|
||||
request.endTransIfRequired();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class CQueryRowCount {
|
||||
|
||||
private String bindLog;
|
||||
|
||||
private int executionTimeMicros;
|
||||
private long executionTimeMicros;
|
||||
|
||||
private int rowCount;
|
||||
|
||||
@@ -99,7 +99,6 @@ class CQueryRowCount {
|
||||
|
||||
long startNano = System.nanoTime();
|
||||
try {
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
Connection conn = t.getInternalConnection();
|
||||
pstmt = conn.prepareStatement(sql);
|
||||
@@ -110,16 +109,14 @@ class CQueryRowCount {
|
||||
|
||||
bindLog = predicates.bind(pstmt, conn);
|
||||
rset = pstmt.executeQuery();
|
||||
|
||||
if (!rset.next()) {
|
||||
throw new PersistenceException("Expecting 1 row but got none?");
|
||||
}
|
||||
|
||||
rowCount = rset.getInt(1);
|
||||
|
||||
long exeNano = System.nanoTime() - startNano;
|
||||
executionTimeMicros = (int) exeNano / 1000;
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
return rowCount;
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -39,7 +39,7 @@ class CQueryUpdate {
|
||||
|
||||
private String bindLog;
|
||||
|
||||
private int executionTimeMicros;
|
||||
private long executionTimeMicros;
|
||||
|
||||
private int rowCount;
|
||||
|
||||
@@ -90,7 +90,6 @@ class CQueryUpdate {
|
||||
|
||||
long startNano = System.nanoTime();
|
||||
try {
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
Connection conn = t.getInternalConnection();
|
||||
pstmt = conn.prepareStatement(sql);
|
||||
@@ -102,9 +101,8 @@ class CQueryUpdate {
|
||||
bindLog = predicates.bind(pstmt, conn);
|
||||
rowCount = pstmt.executeUpdate();
|
||||
|
||||
long exeNano = System.nanoTime() - startNano;
|
||||
executionTimeMicros = (int) exeNano / 1000;
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
return rowCount;
|
||||
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user