HHH-12933 - Generate_statistics grows QueryStatistics ConcurrentHashMap indefinitely

This commit is contained in:
Vlad Mihalcea 2018-08-28 17:18:18 +03:00
parent 440a2ef490
commit b3c2c2fe47
8 changed files with 196 additions and 4 deletions

View File

@ -60,11 +60,10 @@ import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.stat.Statistics;
import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.tuple.entity.EntityTuplizerFactory; 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.ACQUIRE_CONNECTIONS;
import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS; import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS;
import static org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY; 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_BATCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_FETCH_SIZE; import static org.hibernate.cfg.AvailableSettings.STATEMENT_FETCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR; 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_DIRECT_REFERENCE_CACHE_ENTRIES;
import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS; import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS;
import static org.hibernate.cfg.AvailableSettings.USE_IDENTIFIER_ROLLBACK; import static org.hibernate.cfg.AvailableSettings.USE_IDENTIFIER_ROLLBACK;
@ -243,6 +243,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private boolean inClauseParameterPaddingEnabled; private boolean inClauseParameterPaddingEnabled;
private boolean nativeExceptionHandling51Compliance; private boolean nativeExceptionHandling51Compliance;
private int queryStatisticsMaxSize;
@SuppressWarnings({"WeakerAccess", "deprecation"}) @SuppressWarnings({"WeakerAccess", "deprecation"})
public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) {
@ -509,6 +510,13 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
configurationSettings, configurationSettings,
false false
); );
this.queryStatisticsMaxSize = ConfigurationHelper.getInt(
QUERY_STATISTICS_MAX_SIZE,
configurationSettings,
Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE
);
if ( context.isJpaBootstrap() && nativeExceptionHandling51Compliance ) { if ( context.isJpaBootstrap() && nativeExceptionHandling51Compliance ) {
log.nativeExceptionHandling51ComplianceJpaBootstrapping(); log.nativeExceptionHandling51ComplianceJpaBootstrapping();
this.nativeExceptionHandling51Compliance = false; this.nativeExceptionHandling51Compliance = false;
@ -1031,6 +1039,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return nativeExceptionHandling51Compliance; return nativeExceptionHandling51Compliance;
} }
@Override
public int getQueryStatisticsMaxSize() {
return queryStatisticsMaxSize;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In-flight mutation access // In-flight mutation access

View File

@ -432,4 +432,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
public boolean nativeExceptionHandling51Compliance() { public boolean nativeExceptionHandling51Compliance() {
return delegate.nativeExceptionHandling51Compliance(); return delegate.nativeExceptionHandling51Compliance();
} }
@Override
public int getQueryStatisticsMaxSize() {
return delegate.getQueryStatisticsMaxSize();
}
} }

View File

@ -35,6 +35,7 @@ import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.stat.Statistics;
import org.hibernate.tuple.entity.EntityTuplizerFactory; import org.hibernate.tuple.entity.EntityTuplizerFactory;
/** /**
@ -290,4 +291,8 @@ public interface SessionFactoryOptions {
default boolean nativeExceptionHandling51Compliance() { default boolean nativeExceptionHandling51Compliance() {
return false; return false;
} }
default int getQueryStatisticsMaxSize() {
return Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE;
}
} }

View File

@ -1951,4 +1951,14 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/ */
String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding"; 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.
* </p>
* 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";
} }

View File

@ -15,6 +15,8 @@ package org.hibernate.stat;
*/ */
public interface Statistics { public interface Statistics {
int DEFAULT_QUERY_STATISTICS_MAX_SIZE = 5000;
/** /**
* Are statistics enabled * Are statistics enabled
*/ */

View File

@ -17,9 +17,11 @@ import org.hibernate.cache.spi.Region;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.Service; import org.hibernate.service.Service;
import org.hibernate.stat.Statistics;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
import static org.hibernate.internal.CoreLogging.messageLogger; import static org.hibernate.internal.CoreLogging.messageLogger;
@ -92,7 +94,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service {
/** /**
* Keyed by query string * Keyed by query string
*/ */
private final ConcurrentMap<String, QueryStatisticsImpl> queryStatsMap = new ConcurrentHashMap(); private final BoundedConcurrentHashMap<String, QueryStatisticsImpl> queryStatsMap;
/** /**
* Keyed by region name * Keyed by region name
@ -108,8 +110,15 @@ public class StatisticsImpl implements StatisticsImplementor, Service {
} }
public StatisticsImpl(SessionFactoryImplementor sessionFactory) { public StatisticsImpl(SessionFactoryImplementor sessionFactory) {
clear();
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
this.queryStatsMap = new BoundedConcurrentHashMap(
sessionFactory != null ?
sessionFactory.getSessionFactoryOptions().getQueryStatisticsMaxSize() :
Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE,
20,
BoundedConcurrentHashMap.Eviction.LRU
);
clear();
} }
/** /**

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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() );
} );
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}