From b3c2c2fe47ef5cf35c5ae1bf64e89efddbc716dc Mon Sep 17 00:00:00 2001
From: Vlad Mihalcea
Date: Tue, 28 Aug 2018 17:18:18 +0300
Subject: [PATCH] HHH-12933 - Generate_statistics grows QueryStatistics
ConcurrentHashMap indefinitely
---
.../SessionFactoryOptionsBuilder.java | 17 +++-
...stractDelegatingSessionFactoryOptions.java | 5 ++
.../boot/spi/SessionFactoryOptions.java | 5 ++
.../org/hibernate/cfg/AvailableSettings.java | 10 +++
.../java/org/hibernate/stat/Statistics.java | 2 +
.../stat/internal/StatisticsImpl.java | 13 ++-
.../stats/ExplicitQueryStatsMaxSizeTest.java | 65 +++++++++++++++
.../test/stats/QueryStatsMaxSizeTest.java | 83 +++++++++++++++++++
8 files changed, 196 insertions(+), 4 deletions(-)
create mode 100644 hibernate-core/src/test/java/org/hibernate/test/stats/ExplicitQueryStatsMaxSizeTest.java
create mode 100644 hibernate-core/src/test/java/org/hibernate/test/stats/QueryStatsMaxSizeTest.java
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
index a8b393a626..a142ab5b7a 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
@@ -60,11 +60,10 @@ import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
import org.hibernate.service.spi.ServiceRegistryImplementor;
+import org.hibernate.stat.Statistics;
import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.tuple.entity.EntityTuplizerFactory;
-import org.jboss.logging.Logger;
-
import static org.hibernate.cfg.AvailableSettings.ACQUIRE_CONNECTIONS;
import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS;
import static org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY;
@@ -113,6 +112,7 @@ import static org.hibernate.cfg.AvailableSettings.SESSION_SCOPED_INTERCEPTOR;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_BATCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_FETCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR;
+import static org.hibernate.cfg.AvailableSettings.QUERY_STATISTICS_MAX_SIZE;
import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES;
import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS;
import static org.hibernate.cfg.AvailableSettings.USE_IDENTIFIER_ROLLBACK;
@@ -243,6 +243,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private boolean inClauseParameterPaddingEnabled;
private boolean nativeExceptionHandling51Compliance;
+ private int queryStatisticsMaxSize;
@SuppressWarnings({"WeakerAccess", "deprecation"})
public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) {
@@ -509,6 +510,13 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
configurationSettings,
false
);
+
+ this.queryStatisticsMaxSize = ConfigurationHelper.getInt(
+ QUERY_STATISTICS_MAX_SIZE,
+ configurationSettings,
+ Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE
+ );
+
if ( context.isJpaBootstrap() && nativeExceptionHandling51Compliance ) {
log.nativeExceptionHandling51ComplianceJpaBootstrapping();
this.nativeExceptionHandling51Compliance = false;
@@ -1031,6 +1039,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return nativeExceptionHandling51Compliance;
}
+ @Override
+ public int getQueryStatisticsMaxSize() {
+ return queryStatisticsMaxSize;
+ }
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In-flight mutation access
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java
index 44b8c5f24c..480d4b1ffc 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java
@@ -432,4 +432,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
public boolean nativeExceptionHandling51Compliance() {
return delegate.nativeExceptionHandling51Compliance();
}
+
+ @Override
+ public int getQueryStatisticsMaxSize() {
+ return delegate.getQueryStatisticsMaxSize();
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java
index 08c0d66bed..a8e0695f47 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java
@@ -35,6 +35,7 @@ import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
+import org.hibernate.stat.Statistics;
import org.hibernate.tuple.entity.EntityTuplizerFactory;
/**
@@ -290,4 +291,8 @@ public interface SessionFactoryOptions {
default boolean nativeExceptionHandling51Compliance() {
return false;
}
+
+ default int getQueryStatisticsMaxSize() {
+ return Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE;
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java
index 4d2712bccf..0516ef1832 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java
@@ -1951,4 +1951,14 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/
String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding";
+ /**
+ * This setting controls the number of {@link org.hibernate.stat.QueryStatistics} entries
+ * that will be stored by the Hibernate {@link org.hibernate.stat.Statistics} object.
+ *
+ * The default value is given by the {@link org.hibernate.stat.Statistics#DEFAULT_QUERY_STATISTICS_MAX_SIZE} constant value.
+ *
+ * @since 5.4
+ */
+ String QUERY_STATISTICS_MAX_SIZE = "hibernate.statistics.query_max_size";
+
}
diff --git a/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java b/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java
index 226dd590e8..e20aa3f110 100755
--- a/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java
+++ b/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java
@@ -15,6 +15,8 @@ package org.hibernate.stat;
*/
public interface Statistics {
+ int DEFAULT_QUERY_STATISTICS_MAX_SIZE = 5000;
+
/**
* Are statistics enabled
*/
diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java
index 04043a2d71..6fecd5d15b 100644
--- a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java
@@ -17,9 +17,11 @@ import org.hibernate.cache.spi.Region;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
+import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.Service;
+import org.hibernate.stat.Statistics;
import org.hibernate.stat.spi.StatisticsImplementor;
import static org.hibernate.internal.CoreLogging.messageLogger;
@@ -92,7 +94,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service {
/**
* Keyed by query string
*/
- private final ConcurrentMap queryStatsMap = new ConcurrentHashMap();
+ private final BoundedConcurrentHashMap queryStatsMap;
/**
* Keyed by region name
@@ -108,8 +110,15 @@ public class StatisticsImpl implements StatisticsImplementor, Service {
}
public StatisticsImpl(SessionFactoryImplementor sessionFactory) {
- clear();
this.sessionFactory = sessionFactory;
+ this.queryStatsMap = new BoundedConcurrentHashMap(
+ sessionFactory != null ?
+ sessionFactory.getSessionFactoryOptions().getQueryStatisticsMaxSize() :
+ Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE,
+ 20,
+ BoundedConcurrentHashMap.Eviction.LRU
+ );
+ clear();
}
/**
diff --git a/hibernate-core/src/test/java/org/hibernate/test/stats/ExplicitQueryStatsMaxSizeTest.java b/hibernate-core/src/test/java/org/hibernate/test/stats/ExplicitQueryStatsMaxSizeTest.java
new file mode 100644
index 0000000000..20bc99d3db
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/stats/ExplicitQueryStatsMaxSizeTest.java
@@ -0,0 +1,65 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or .
+ */
+package org.hibernate.test.stats;
+
+import java.util.Map;
+import javax.persistence.EntityManagerFactory;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.stat.spi.StatisticsImplementor;
+
+import org.junit.Test;
+
+import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class ExplicitQueryStatsMaxSizeTest extends QueryStatsMaxSizeTest {
+
+ public static final int QUERY_STATISTICS_MAX_SIZE = 100;
+
+ @Override
+ protected void addConfigOptions(Map options) {
+ super.addConfigOptions( options );
+ options.put( AvailableSettings.QUERY_STATISTICS_MAX_SIZE, QUERY_STATISTICS_MAX_SIZE );
+ }
+
+ @Override
+ protected int expectedQueryStatisticsMaxSize() {
+ return QUERY_STATISTICS_MAX_SIZE;
+ }
+
+ @Test
+ public void testMaxSize() {
+ doInJPA( this::entityManagerFactory, entityManager -> {
+ EntityManagerFactory entityManagerFactory = entityManager.getEntityManagerFactory();
+ SessionFactory sessionFactory = entityManagerFactory.unwrap( SessionFactory.class );
+
+ assertEquals(
+ expectedQueryStatisticsMaxSize(),
+ sessionFactory.getSessionFactoryOptions().getQueryStatisticsMaxSize()
+ );
+
+ StatisticsImplementor statistics = (StatisticsImplementor) sessionFactory.getStatistics();
+
+ for ( int i = 0; i < 10; i++ ) {
+ statistics.queryExecuted( String.valueOf( i ), 100, i * 1000 );
+ }
+
+ assertEquals( 1000, statistics.getQueryStatistics( "1" ).getExecutionTotalTime() );
+
+ for ( int i = 100; i < 300; i++ ) {
+ statistics.queryExecuted( String.valueOf( i ), 100, i * 1000 );
+ }
+
+ assertEquals( 0, statistics.getQueryStatistics( "1" ).getExecutionTotalTime() );
+ } );
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/stats/QueryStatsMaxSizeTest.java b/hibernate-core/src/test/java/org/hibernate/test/stats/QueryStatsMaxSizeTest.java
new file mode 100644
index 0000000000..533d69fc2c
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/stats/QueryStatsMaxSizeTest.java
@@ -0,0 +1,83 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or .
+ */
+package org.hibernate.test.stats;
+
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Map;
+
+import javax.persistence.Entity;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Id;
+
+import org.hibernate.Hibernate;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.annotations.NaturalId;
+import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.internal.util.ReflectHelper;
+import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
+import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.stat.SessionStatistics;
+import org.hibernate.stat.Statistics;
+import org.hibernate.stat.internal.StatisticsImpl;
+
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+import org.junit.Test;
+
+import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class QueryStatsMaxSizeTest extends BaseEntityManagerFunctionalTestCase {
+
+ @Override
+ protected Class>[] getAnnotatedClasses() {
+ return new Class>[] {
+ Employee.class,
+ };
+ }
+
+ @Override
+ protected void addConfigOptions(Map options) {
+ options.put( AvailableSettings.GENERATE_STATISTICS, "true" );
+ }
+
+ @Test
+ public void test() {
+ doInJPA( this::entityManagerFactory, entityManager -> {
+ EntityManagerFactory entityManagerFactory = entityManager.getEntityManagerFactory();
+ SessionFactory sessionFactory = entityManagerFactory.unwrap( SessionFactory.class );
+
+ assertEquals(
+ expectedQueryStatisticsMaxSize(),
+ sessionFactory.getSessionFactoryOptions().getQueryStatisticsMaxSize()
+ );
+ } );
+ }
+
+ protected int expectedQueryStatisticsMaxSize() {
+ return Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE;
+ }
+
+ @Entity(name = "Employee")
+ public static class Employee {
+
+ @Id
+ private Long id;
+
+ @NaturalId
+ private String username;
+
+ private String password;
+ }
+
+}