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; + } + +}