diff --git a/config/alfresco/caches.properties b/config/alfresco/caches.properties index 6c8cfd7ed4..ca0fccf913 100644 --- a/config/alfresco/caches.properties +++ b/config/alfresco/caches.properties @@ -49,6 +49,7 @@ # merge-policy How Hazelcast recovers from split brain syndrome, e.g. hz.ADD_NEW_ENTRY # Please see http://hazelcast.org/docs/2.4/manual/html-single/#NetworkPartitioning cache.propertyValueCache.tx.maxItems=1000 +cache.propertyValueCache.tx.statsEnabled=true cache.propertyValueCache.maxItems=10000 cache.propertyValueCache.timeToLiveSeconds=300 cache.propertyValueCache.maxIdleSeconds=0 @@ -59,6 +60,7 @@ cache.propertyValueCache.eviction-percentage=25 cache.propertyValueCache.merge-policy=hz.ADD_NEW_ENTRY cache.propertyClassCache.tx.maxItems=1000 +cache.propertyClassCache.tx.statsEnabled=true cache.propertyClassCache.maxItems=10000 cache.propertyClassCache.timeToLiveSeconds=0 cache.propertyClassCache.maxIdleSeconds=0 @@ -69,6 +71,7 @@ cache.propertyClassCache.eviction-percentage=25 cache.propertyClassCache.merge-policy=hz.ADD_NEW_ENTRY cache.contentDataSharedCache.tx.maxItems=65000 +cache.contentDataSharedCache.tx.statsEnabled=true cache.contentDataSharedCache.maxItems=130000 cache.contentDataSharedCache.timeToLiveSeconds=0 cache.contentDataSharedCache.maxIdleSeconds=0 @@ -79,6 +82,7 @@ cache.contentDataSharedCache.eviction-percentage=25 cache.contentDataSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.contentUrlSharedCache.tx.maxItems=65000 +cache.contentUrlSharedCache.tx.statsEnabled=true cache.contentUrlSharedCache.maxItems=130000 cache.contentUrlSharedCache.timeToLiveSeconds=0 cache.contentUrlSharedCache.maxIdleSeconds=0 @@ -89,6 +93,7 @@ cache.contentUrlSharedCache.eviction-percentage=25 cache.contentUrlSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.contentUrlMasterKeySharedCache.tx.maxItems=50 +cache.contentUrlMasterKeySharedCache.tx.statsEnabled=true cache.contentUrlMasterKeySharedCache.maxItems=0 cache.contentUrlMasterKeySharedCache.timeToLiveSeconds=0 cache.contentUrlMasterKeySharedCache.maxIdleSeconds=0 @@ -102,6 +107,7 @@ cache.contentUrlMasterKeySharedCache.nearCache.maxIdleSeconds=0 cache.contentUrlMasterKeySharedCache.nearCache.timeToLiveSeconds=0 cache.contentUrlEncryptingMasterKeySharedCache.tx.maxItems=50 +cache.contentUrlEncryptingMasterKeySharedCache.tx.statsEnabled=true cache.contentUrlEncryptingMasterKeySharedCache.maxItems=0 cache.contentUrlEncryptingMasterKeySharedCache.timeToLiveSeconds=0 cache.contentUrlEncryptingMasterKeySharedCache.maxIdleSeconds=0 @@ -115,6 +121,7 @@ cache.contentUrlEncryptingMasterKeySharedCache.nearCache.maxIdleSeconds=0 cache.contentUrlEncryptingMasterKeySharedCache.nearCache.timeToLiveSeconds=0 cache.immutableEntitySharedCache.tx.maxItems=10000 +cache.immutableEntitySharedCache.tx.statsEnabled=true cache.immutableEntitySharedCache.maxItems=50000 cache.immutableEntitySharedCache.timeToLiveSeconds=0 cache.immutableEntitySharedCache.maxIdleSeconds=0 @@ -125,6 +132,7 @@ cache.immutableEntitySharedCache.eviction-percentage=25 cache.immutableEntitySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.node.rootNodesSharedCache.tx.maxItems=1000 +cache.node.rootNodesSharedCache.tx.statsEnabled=true cache.node.rootNodesSharedCache.maxItems=1000 cache.node.rootNodesSharedCache.timeToLiveSeconds=0 cache.node.rootNodesSharedCache.maxIdleSeconds=0 @@ -135,6 +143,7 @@ cache.node.rootNodesSharedCache.eviction-percentage=25 cache.node.rootNodesSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.node.allRootNodesSharedCache.tx.maxItems=500 +cache.node.allRootNodesSharedCache.tx.statsEnabled=true cache.node.allRootNodesSharedCache.maxItems=1000 cache.node.allRootNodesSharedCache.timeToLiveSeconds=0 cache.node.allRootNodesSharedCache.maxIdleSeconds=0 @@ -145,6 +154,7 @@ cache.node.allRootNodesSharedCache.eviction-percentage=25 cache.node.allRootNodesSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.node.nodesSharedCache.tx.maxItems=125000 +cache.node.nodesSharedCache.tx.statsEnabled=true cache.node.nodesSharedCache.maxItems=250000 cache.node.nodesSharedCache.timeToLiveSeconds=300 cache.node.nodesSharedCache.maxIdleSeconds=0 @@ -155,6 +165,7 @@ cache.node.nodesSharedCache.eviction-percentage=25 cache.node.nodesSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.node.aspectsSharedCache.tx.maxItems=65000 +cache.node.aspectsSharedCache.tx.statsEnabled=true cache.node.aspectsSharedCache.maxItems=130000 cache.node.aspectsSharedCache.timeToLiveSeconds=0 cache.node.aspectsSharedCache.maxIdleSeconds=0 @@ -165,6 +176,7 @@ cache.node.aspectsSharedCache.eviction-percentage=25 cache.node.aspectsSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.node.propertiesSharedCache.tx.maxItems=65000 +cache.node.propertiesSharedCache.tx.statsEnabled=true cache.node.propertiesSharedCache.maxItems=130000 cache.node.propertiesSharedCache.timeToLiveSeconds=0 cache.node.propertiesSharedCache.maxIdleSeconds=0 @@ -184,6 +196,7 @@ cache.node.parentAssocsSharedCache.eviction-percentage=25 cache.node.parentAssocsSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.node.childByNameSharedCache.tx.maxItems=65000 +cache.node.childByNameSharedCache.tx.statsEnabled=true cache.node.childByNameSharedCache.maxItems=130000 cache.node.childByNameSharedCache.timeToLiveSeconds=0 cache.node.childByNameSharedCache.maxIdleSeconds=0 @@ -194,6 +207,7 @@ cache.node.childByNameSharedCache.eviction-percentage=25 cache.node.childByNameSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.userToAuthoritySharedCache.tx.maxItems=100 +cache.userToAuthoritySharedCache.tx.statsEnabled=true cache.userToAuthoritySharedCache.maxItems=5000 cache.userToAuthoritySharedCache.timeToLiveSeconds=0 cache.userToAuthoritySharedCache.maxIdleSeconds=0 @@ -204,6 +218,7 @@ cache.userToAuthoritySharedCache.eviction-percentage=25 cache.userToAuthoritySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.authenticationSharedCache.tx.maxItems=100 +cache.authenticationSharedCache.tx.statsEnabled=true cache.authenticationSharedCache.maxItems=5000 cache.authenticationSharedCache.timeToLiveSeconds=0 cache.authenticationSharedCache.maxIdleSeconds=0 @@ -214,6 +229,7 @@ cache.authenticationSharedCache.eviction-percentage=25 cache.authenticationSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.authoritySharedCache.tx.maxItems=10000 +cache.authoritySharedCache.tx.statsEnabled=true cache.authoritySharedCache.maxItems=10000 cache.authoritySharedCache.timeToLiveSeconds=0 cache.authoritySharedCache.maxIdleSeconds=0 @@ -224,6 +240,7 @@ cache.authoritySharedCache.eviction-percentage=25 cache.authoritySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.authorityToChildAuthoritySharedCache.tx.maxItems=40000 +cache.authorityToChildAuthoritySharedCache.tx.statsEnabled=true cache.authorityToChildAuthoritySharedCache.maxItems=40000 cache.authorityToChildAuthoritySharedCache.timeToLiveSeconds=0 cache.authorityToChildAuthoritySharedCache.maxIdleSeconds=0 @@ -234,6 +251,7 @@ cache.authorityToChildAuthoritySharedCache.eviction-percentage=25 cache.authorityToChildAuthoritySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.zoneToAuthoritySharedCache.tx.maxItems=500 +cache.zoneToAuthoritySharedCache.tx.statsEnabled=true cache.zoneToAuthoritySharedCache.maxItems=500 cache.zoneToAuthoritySharedCache.timeToLiveSeconds=0 cache.zoneToAuthoritySharedCache.maxIdleSeconds=0 @@ -244,6 +262,7 @@ cache.zoneToAuthoritySharedCache.eviction-percentage=25 cache.zoneToAuthoritySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.permissionsAccessSharedCache.tx.maxItems=10000 +cache.permissionsAccessSharedCache.tx.statsEnabled=true cache.permissionsAccessSharedCache.maxItems=50000 cache.permissionsAccessSharedCache.timeToLiveSeconds=0 cache.permissionsAccessSharedCache.maxIdleSeconds=0 @@ -254,6 +273,7 @@ cache.permissionsAccessSharedCache.eviction-percentage=25 cache.permissionsAccessSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.readersSharedCache.tx.maxItems=10000 +cache.readersSharedCache.tx.statsEnabled=true cache.readersSharedCache.maxItems=10000 cache.readersSharedCache.timeToLiveSeconds=0 cache.readersSharedCache.maxIdleSeconds=0 @@ -264,6 +284,7 @@ cache.readersSharedCache.eviction-percentage=25 cache.readersSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.readersDeniedSharedCache.tx.maxItems=10000 +cache.readersDeniedSharedCache.tx.statsEnabled=true cache.readersDeniedSharedCache.maxItems=10000 cache.readersDeniedSharedCache.timeToLiveSeconds=0 cache.readersDeniedSharedCache.maxIdleSeconds=0 @@ -274,6 +295,7 @@ cache.readersDeniedSharedCache.eviction-percentage=25 cache.readersDeniedSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.nodeOwnerSharedCache.tx.maxItems=40000 +cache.nodeOwnerSharedCache.tx.statsEnabled=true cache.nodeOwnerSharedCache.maxItems=40000 cache.nodeOwnerSharedCache.timeToLiveSeconds=0 cache.nodeOwnerSharedCache.maxIdleSeconds=0 @@ -284,8 +306,10 @@ cache.nodeOwnerSharedCache.eviction-percentage=25 cache.nodeOwnerSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.nodeRulesSharedCache.tx.maxItems=2000 +cache.nodeRulesSharedCache.tx.statsEnabled=true cache.personSharedCache.tx.maxItems=1000 +cache.personSharedCache.tx.statsEnabled=true cache.personSharedCache.maxItems=1000 cache.personSharedCache.timeToLiveSeconds=0 cache.personSharedCache.maxIdleSeconds=0 @@ -305,8 +329,10 @@ cache.ticketsCache.eviction-percentage=25 cache.ticketsCache.merge-policy=hz.ADD_NEW_ENTRY cache.authorityEntitySharedCache.tx.maxItems=50000 +cache.authorityEntitySharedCache.tx.statsEnabled=true cache.webServicesQuerySessionSharedCache.tx.maxItems=50 +cache.webServicesQuerySessionSharedCache.tx.statsEnabled=true cache.webServicesQuerySessionSharedCache.maxItems=1000 cache.webServicesQuerySessionSharedCache.timeToLiveSeconds=0 cache.webServicesQuerySessionSharedCache.maxIdleSeconds=0 @@ -317,6 +343,7 @@ cache.webServicesQuerySessionSharedCache.eviction-percentage=25 cache.webServicesQuerySessionSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.aclSharedCache.tx.maxItems=20000 +cache.aclSharedCache.tx.statsEnabled=true cache.aclSharedCache.maxItems=50000 cache.aclSharedCache.timeToLiveSeconds=0 cache.aclSharedCache.maxIdleSeconds=0 @@ -327,6 +354,7 @@ cache.aclSharedCache.eviction-percentage=25 cache.aclSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.aclEntitySharedCache.tx.maxItems=50000 +cache.aclEntitySharedCache.tx.statsEnabled=true cache.aclEntitySharedCache.maxItems=50000 cache.aclEntitySharedCache.timeToLiveSeconds=0 cache.aclEntitySharedCache.maxIdleSeconds=0 @@ -337,6 +365,7 @@ cache.aclEntitySharedCache.eviction-percentage=25 cache.aclEntitySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.resourceBundleBaseNamesSharedCache.tx.maxItems=1000 +cache.resourceBundleBaseNamesSharedCache.tx.statsEnabled=true cache.resourceBundleBaseNamesSharedCache.maxItems=1000 cache.resourceBundleBaseNamesSharedCache.timeToLiveSeconds=0 cache.resourceBundleBaseNamesSharedCache.maxIdleSeconds=0 @@ -347,6 +376,7 @@ cache.resourceBundleBaseNamesSharedCache.eviction-percentage=25 cache.resourceBundleBaseNamesSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.loadedResourceBundlesSharedCache.tx.maxItems=1000 +cache.loadedResourceBundlesSharedCache.tx.statsEnabled=true cache.loadedResourceBundlesSharedCache.maxItems=1000 cache.loadedResourceBundlesSharedCache.timeToLiveSeconds=0 cache.loadedResourceBundlesSharedCache.maxIdleSeconds=0 @@ -357,6 +387,7 @@ cache.loadedResourceBundlesSharedCache.eviction-percentage=25 cache.loadedResourceBundlesSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.messagesSharedCache.tx.maxItems=1000 +cache.messagesSharedCache.tx.statsEnabled=true cache.messagesSharedCache.maxItems=1000 cache.messagesSharedCache.timeToLiveSeconds=0 cache.messagesSharedCache.maxIdleSeconds=0 @@ -376,6 +407,7 @@ cache.webScriptsRegistrySharedCache.eviction-percentage=25 cache.webScriptsRegistrySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.routingContentStoreSharedCache.tx.maxItems=10000 +cache.routingContentStoreSharedCache.tx.statsEnabled=true cache.routingContentStoreSharedCache.maxItems=10000 cache.routingContentStoreSharedCache.timeToLiveSeconds=0 cache.routingContentStoreSharedCache.maxIdleSeconds=0 @@ -395,6 +427,7 @@ cache.executingActionsCache.eviction-percentage=25 cache.executingActionsCache.merge-policy=hz.ADD_NEW_ENTRY cache.tagscopeSummarySharedCache.tx.maxItems=1000 +cache.tagscopeSummarySharedCache.tx.statsEnabled=true cache.tagscopeSummarySharedCache.maxItems=1000 cache.tagscopeSummarySharedCache.timeToLiveSeconds=0 cache.tagscopeSummarySharedCache.maxIdleSeconds=0 @@ -405,6 +438,7 @@ cache.tagscopeSummarySharedCache.eviction-percentage=25 cache.tagscopeSummarySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.imapMessageSharedCache.tx.maxItems=1000 +cache.imapMessageSharedCache.tx.statsEnabled=true cache.imapMessageSharedCache.maxItems=2000 cache.imapMessageSharedCache.timeToLiveSeconds=0 cache.imapMessageSharedCache.maxIdleSeconds=0 @@ -415,6 +449,7 @@ cache.imapMessageSharedCache.eviction-percentage=25 cache.imapMessageSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.tenantEntitySharedCache.tx.maxItems=1000 +cache.tenantEntitySharedCache.tx.statsEnabled=true cache.tenantEntitySharedCache.maxItems=1000 cache.tenantEntitySharedCache.timeToLiveSeconds=0 cache.tenantEntitySharedCache.maxIdleSeconds=0 @@ -425,6 +460,7 @@ cache.tenantEntitySharedCache.eviction-percentage=25 cache.tenantEntitySharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.immutableSingletonSharedCache.tx.maxItems=12000 +cache.immutableSingletonSharedCache.tx.statsEnabled=true cache.immutableSingletonSharedCache.maxItems=12000 cache.immutableSingletonSharedCache.timeToLiveSeconds=0 cache.immutableSingletonSharedCache.maxIdleSeconds=0 @@ -462,8 +498,10 @@ cache.globalConfigSharedCache.eviction-percentage=25 cache.globalConfigSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.permissionEntitySharedCache.tx.maxItems=50000 +cache.permissionEntitySharedCache.tx.statsEnabled=true cache.propertyUniqueContextSharedCache.tx.maxItems=10000 +cache.propertyUniqueContextSharedCache.tx.statsEnabled=true cache.propertyUniqueContextSharedCache.maxItems=10000 cache.propertyUniqueContextSharedCache.timeToLiveSeconds=0 cache.propertyUniqueContextSharedCache.maxIdleSeconds=0 @@ -474,6 +512,7 @@ cache.propertyUniqueContextSharedCache.eviction-percentage=25 cache.propertyUniqueContextSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.siteNodeRefSharedCache.tx.maxItems=5000 +cache.siteNodeRefSharedCache.tx.statsEnabled=true cache.siteNodeRefSharedCache.maxItems=5000 cache.siteNodeRefSharedCache.timeToLiveSeconds=0 cache.siteNodeRefSharedCache.maxIdleSeconds=0 @@ -484,6 +523,7 @@ cache.siteNodeRefSharedCache.eviction-percentage=25 cache.siteNodeRefSharedCache.merge-policy=hz.ADD_NEW_ENTRY cache.samlTrustEngineSharedCache.tx.maxItems=5000 +cache.samlTrustEngineSharedCache.tx.statsEnabled=true cache.samlTrustEngineSharedCache.maxItems=5000 cache.samlTrustEngineSharedCache.timeToLiveSeconds=0 cache.samlTrustEngineSharedCache.maxIdleSeconds=0 @@ -513,6 +553,7 @@ cache.publicapi.webScriptsRegistryCache.eviction-percentage=25 cache.publicapi.webScriptsRegistryCache.merge-policy=hz.ADD_NEW_ENTRY cache.deletePseudoFileCache.tx.maxItems=50000 +cache.deletePseudoFileCache.tx.statsEnabled=true cache.deletePseudoFileCache.maxItems=50000 cache.deletePseudoFileCache.timeToLiveSeconds=180 cache.deletePseudoFileCache.maxIdleSeconds=0 @@ -528,6 +569,7 @@ cache.deletePseudoFileCache.merge-policy=hz.ADD_NEW_ENTRY # RM Caveat cache # cache.caveatConfigCache.tx.maxItems=100 +cache.caveatConfigCache.tx.statsEnabled=true cache.caveatConfigCache.maxItems=5000 cache.caveatConfigCache.timeToLiveSeconds=0 cache.caveatConfigCache.maxIdleSeconds=0 @@ -541,6 +583,7 @@ cache.caveatConfigCache.merge-policy=hz.ADD_NEW_ENTRY #Solr Facets cache # cache.solrFacetNodeRefSharedCache.tx.maxItems=5000 +cache.solrFacetNodeRefSharedCache.tx.statsEnabled=true cache.solrFacetNodeRefSharedCache.maxItems=5000 cache.solrFacetNodeRefSharedCache.timeToLiveSeconds=0 cache.solrFacetNodeRefSharedCache.maxIdleSeconds=0 diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 6584b6301b..c762e21431 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -2,7 +2,10 @@ - + + + + @@ -16,6 +19,8 @@ + + @@ -31,6 +36,8 @@ + + @@ -46,6 +53,8 @@ + + @@ -61,6 +70,8 @@ + + @@ -74,6 +85,8 @@ + + @@ -87,6 +100,8 @@ + + @@ -100,6 +115,8 @@ + + @@ -115,6 +132,8 @@ + + @@ -131,6 +150,8 @@ + + @@ -146,6 +167,8 @@ + + @@ -162,6 +185,8 @@ + + @@ -177,6 +202,8 @@ + + @@ -192,6 +219,8 @@ + + @@ -208,6 +237,8 @@ + + @@ -223,6 +254,8 @@ + + @@ -239,6 +272,8 @@ + + @@ -252,6 +287,8 @@ org.alfresco.authenticationTransactionalCache + + @@ -268,6 +305,8 @@ + + @@ -284,6 +323,8 @@ + + @@ -300,6 +341,8 @@ + + @@ -316,6 +359,8 @@ + + @@ -331,6 +376,8 @@ + + @@ -346,6 +393,8 @@ + + @@ -361,6 +410,8 @@ + + @@ -376,6 +427,8 @@ + + @@ -391,6 +444,8 @@ + + @@ -407,6 +462,8 @@ + + @@ -422,6 +479,8 @@ + + @@ -437,6 +496,8 @@ + + @@ -453,6 +514,8 @@ + + @@ -469,6 +532,8 @@ + + @@ -485,6 +550,8 @@ + + @@ -500,6 +567,8 @@ + + @@ -515,6 +584,8 @@ + + @@ -530,6 +601,8 @@ + + @@ -546,6 +619,8 @@ + + @@ -561,6 +636,8 @@ + + @@ -575,6 +652,8 @@ + + @@ -589,6 +668,8 @@ + + @@ -603,6 +684,8 @@ + + diff --git a/pom.xml b/pom.xml index 006ffa8e43..3e09c1b29f 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,11 @@ commons-lang commons-lang + + org.apache.commons + commons-math3 + 3.3 + commons-pool commons-pool diff --git a/source/java/org/alfresco/repo/cache/CacheStatistics.java b/source/java/org/alfresco/repo/cache/CacheStatistics.java new file mode 100644 index 0000000000..109e57759b --- /dev/null +++ b/source/java/org/alfresco/repo/cache/CacheStatistics.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cache; + +import org.alfresco.repo.cache.TransactionStats.OpType; + +/** + * Centralised cache statistics service. Transactional caches participating + * in statistical collection will provide their data to this service using the + * {@link #add(String, TransactionStats)} method. The data is then aggregated + * so that, for example, the hit ratio for a particular cache may be retrieved. + * + * @since 5.0 + * @author Matt Ward + */ +public interface CacheStatistics +{ + /** + * Add new details to the system wide cache statistics. + */ + void add(String cacheName, TransactionStats stats); + + /** + * Get the number of occurrences of the given operation type, + * retrieve the number of cache hits that have happened to the cache. + * + * @param cacheName Name of the cache. + * @param opType Type of cache operation. + * @return long count + */ + long count(String cacheName, OpType opType); + + /** + * The mean time in nanoseconds for all operations of the given type. + * + * @param cacheName The cache name. + * @param opType Type of operation, e.g. cache hits. + * @return Time in nanos (double) or NaN if not available yet. + */ + double meanTime(String cacheName, OpType opType); + + /** + * The hit ratio for the given cache, where 1.0 is the maximum possible + * value and 0.0 represents a cache that has never successfully + * returned a previously cached value. + * + * @param cacheName The cache name. + * @return ratio (double) + */ + double hitMissRatio(String cacheName); +} diff --git a/source/java/org/alfresco/repo/cache/CacheStatisticsCreated.java b/source/java/org/alfresco/repo/cache/CacheStatisticsCreated.java new file mode 100644 index 0000000000..f6b9354c72 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/CacheStatisticsCreated.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cache; + +import org.springframework.context.ApplicationEvent; + +/** + * Signal that cache statistics have been registered for the given cache. + * + * @since 5.0 + * @author Matt Ward + */ +public class CacheStatisticsCreated extends ApplicationEvent +{ + private static final long serialVersionUID = 1L; + private final CacheStatistics cacheStats; + private final String cacheName; + + public CacheStatisticsCreated(CacheStatistics cacheStats, String cacheName) + { + super(cacheStats); + this.cacheStats = cacheStats; + this.cacheName = cacheName; + } + + public CacheStatistics getCacheStats() + { + return this.cacheStats; + } + + public String getCacheName() + { + return this.cacheName; + } +} diff --git a/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java b/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java new file mode 100644 index 0000000000..996ad03161 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cache; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.alfresco.repo.cache.TransactionStats.OpType; +import org.apache.commons.math3.stat.descriptive.SummaryStatistics; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Simple non-persistent implementation of {@link CacheStatistics}. Statistics + * are empty at repository startup. + * + * @since 5.0 + * @author Matt Ward + */ +public class InMemoryCacheStatistics implements CacheStatistics, ApplicationContextAware +{ + /** Read/Write locks by cache name */ + private final ConcurrentMap locks = new ConcurrentHashMap<>(); + private Map> cacheToStatsMap = new HashMap<>(); + private ApplicationContext applicationContext; + + + @Override + public long count(String cacheName, OpType opType) + { + ReadLock readLock = getReadLock(cacheName); + readLock.lock(); + try + { + Map cacheStats = cacheToStatsMap.get(cacheName); + if (cacheStats == null) + { + throw new NoStatsForCache(cacheName); + } + OperationStats opStats = cacheStats.get(opType); + return opStats.count; + } + finally + { + readLock.unlock(); + } + } + + @Override + public double meanTime(String cacheName, OpType opType) + { + ReadLock readLock = getReadLock(cacheName); + readLock.lock(); + try + { + Map cacheStats = cacheToStatsMap.get(cacheName); + if (cacheStats == null) + { + throw new NoStatsForCache(cacheName); + } + OperationStats opStats = cacheStats.get(opType); + return opStats.meanTime(); + } + finally + { + readLock.unlock(); + } + } + + @Override + public void add(String cacheName, TransactionStats txStats) + { + boolean registerCacheStats = false; + WriteLock writeLock = getWriteLock(cacheName); + writeLock.lock(); + try + { + // Are we adding new stats for a previously unseen cache? + registerCacheStats = !cacheToStatsMap.containsKey(cacheName); + if (registerCacheStats) + { + // There are no statistics yet for this cache. + cacheToStatsMap.put(cacheName, new HashMap()); + } + Map cacheStats = cacheToStatsMap.get(cacheName); + + for (OpType opType : OpType.values()) + { + SummaryStatistics txOpSummary = txStats.getTimings(opType); + long count = txOpSummary.getN(); + double totalTime = txOpSummary.getSum(); + + OperationStats oldStats = cacheStats.get(opType); + OperationStats newStats; + if (oldStats == null) + { + newStats = new OperationStats(totalTime, count); + } + else + { + newStats = new OperationStats(oldStats, totalTime, count); + } + cacheStats.put(opType, newStats); + } + } + finally + { + writeLock.unlock(); + } + + if (registerCacheStats) + { + // We've added stats for a previously unseen cache, raise an event + // so that an MBean for the cache may be registered, for example. + applicationContext.publishEvent(new CacheStatisticsCreated(this, cacheName)); + } + } + + @Override + public double hitMissRatio(String cacheName) + { + ReadLock readLock = getReadLock(cacheName); + readLock.lock(); + try + { + Map cacheStats = cacheToStatsMap.get(cacheName); + if (cacheStats == null) + { + throw new NoStatsForCache(cacheName); + } + long hits = cacheStats.get(OpType.GET_HIT).count; + long misses = cacheStats.get(OpType.GET_MISS).count; + return (double)hits / (hits+misses); + } + finally + { + readLock.unlock(); + } + } + + /** + * Represents a single cache operation type's statistics. + */ + private static final class OperationStats + { + /** Total time spent in operations of this type */ + private final double totalTime; + /** Count of how many instances of this operation occurred. */ + private final long count; + + public OperationStats(double totalTime, long count) + { + this.totalTime = totalTime; + this.count = count; + } + + public OperationStats(OperationStats source, double totalTime, long count) + { + if (Double.compare(source.totalTime, Double.NaN) == 0) + { + // No previous time to add new time to. + this.totalTime = totalTime; + } + else + { + this.totalTime = source.totalTime + totalTime; + } + this.count = source.count + count; + } + + public double meanTime() + { + return totalTime / count; + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + + /** + * Gets a {@link ReentrantReadWriteLock} for a specific cache, lazily + * creating the lock if necessary. Locks may be created per cache + * (rather than hashing to a smaller pool) since the number of + * caches is not too large. + * + * @param cacheName Cache name to obtain lock for. + * @return ReentrantReadWriteLock + */ + private ReentrantReadWriteLock getLock(String cacheName) + { + if (!locks.containsKey(cacheName)) + { + ReentrantReadWriteLock newLock = new ReentrantReadWriteLock(); + if (locks.putIfAbsent(cacheName, newLock) == null) + { + // Lock was successfully added to map. + return newLock; + }; + } + return locks.get(cacheName); + } + + private ReadLock getReadLock(String cacheName) + { + ReadLock readLock = getLock(cacheName).readLock(); + return readLock; + } + + private WriteLock getWriteLock(String cacheName) + { + WriteLock writeLock = getLock(cacheName).writeLock(); + return writeLock; + } +} diff --git a/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java b/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java new file mode 100644 index 0000000000..4941713922 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cache; + +import org.alfresco.repo.cache.TransactionStats.OpType; + +/** + * A do-nothing implementation of {@link CacheStatistics}. Used + * by the {@link TransactionalCache} if it is not explicitly + * injected with an implementation. + * + * @since 5.0 + * @author Matt Ward + */ +public class NoOpCacheStatistics implements CacheStatistics +{ + @Override + public void add(String cacheName, TransactionStats stats) + { + // Does nothing + } + + @Override + public long count(String cacheName, OpType opType) + { + return 0; + } + + @Override + public double meanTime(String cacheName, OpType opType) + { + return Double.NaN; + } + + @Override + public double hitMissRatio(String cacheName) + { + return Double.NaN; + } +} diff --git a/source/java/org/alfresco/repo/cache/NoStatsForCache.java b/source/java/org/alfresco/repo/cache/NoStatsForCache.java new file mode 100644 index 0000000000..cda22494d8 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/NoStatsForCache.java @@ -0,0 +1,26 @@ +package org.alfresco.repo.cache; + +/** + * Read operations on {@link CacheStatistics} throw this + * RuntimeException when no statistics exist for the + * specified cache. + * + * @since 5.0 + * @author Matt Ward + */ +public class NoStatsForCache extends RuntimeException +{ + private static final long serialVersionUID = 1L; + private final String cacheName; + + public NoStatsForCache(String cacheName) + { + super("No statistics have been calculated for cache ["+cacheName+"]"); + this.cacheName = cacheName; + } + + public String getCacheName() + { + return this.cacheName; + } +} diff --git a/source/java/org/alfresco/repo/cache/TransactionStats.java b/source/java/org/alfresco/repo/cache/TransactionStats.java new file mode 100644 index 0000000000..f74e258273 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/TransactionStats.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cache; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.math3.stat.descriptive.SummaryStatistics; + +/** + * Only to be used within a single transaction/thread. + * + * @since 5.0 + * @author Matt Ward + */ +public class TransactionStats +{ + private Map timings = new HashMap<>(); + + /** + * Cache operation type. + */ + public enum OpType + { + GET_HIT, + GET_MISS, + PUT, + REMOVE, + CLEAR + } + + public long getCount(OpType op) + { + SummaryStatistics stats = getTimings(op); + return stats.getN(); + } + + public SummaryStatistics getTimings(OpType op) + { + SummaryStatistics opTimings = timings.get(op); + if (opTimings == null) + { + opTimings = new SummaryStatistics(); + timings.put(op, opTimings); + } + return opTimings; + } + + public void record(long start, long end, OpType op) + { + if (end < start) + { + throw new IllegalArgumentException("End time [" + end + "] occurs before start time [" + start + "]."); + } + double timeTaken = end - start; + addTiming(op, timeTaken); + } + + private void addTiming(OpType op, double time) + { + SummaryStatistics opTimings = getTimings(op); + opTimings.addValue(time); + } +} diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index 65c02cea75..f22197535d 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.TransactionStats.OpType; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; @@ -92,7 +93,10 @@ public class TransactionalCache private int maxCacheSize = 500; /** a unique string identifying this instance when binding resources */ private String resourceKeyTxnData; - + /** A "null" CacheStatistics impl is used by default for backwards compatibility */ + private CacheStatistics cacheStats = new NoOpCacheStatistics(); + /** Enable collection of statistics? */ + private boolean cacheStatsEnabled = false; private boolean isTenantAware = true; // true if tenant-aware (default), false if system-wide /** @@ -212,6 +216,16 @@ public class TransactionalCache this.isTenantAware = isTenantAware; } + public void setCacheStats(CacheStatistics cacheStats) + { + this.cacheStats = cacheStats; + } + + public void setCacheStatsEnabled(boolean cacheStatsEnabled) + { + this.cacheStatsEnabled = cacheStatsEnabled; + } + /** * Ensures that all properties have been set */ @@ -248,6 +262,7 @@ public class TransactionalCache data.removedItemsCache = new HashSet(13); data.lockedItemsCache = new HashSet(13); data.isReadOnly = AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; + data.stats = new TransactionStats(); // ensure that we get the transaction callbacks as we have bound the unique // transactional caches to a common manager @@ -405,30 +420,55 @@ public class TransactionalCache return cacheKeys; } + /** + * @see #getSharedCacheValue(SimpleCache, Serializable, TransactionStats) + */ + public static VAL getSharedCacheValue(SimpleCache> sharedCache, KEY key) + { + return getSharedCacheValue(sharedCache, key); + } + /** * Fetches a value from the shared cache. If values were wrapped, * then they will be unwrapped before being returned. If code requires * direct access to the wrapper object as well, then this call should not * be used. + *

+ * If a TransactionStats instance is passed in, then cache access stats + * are tracked, otherwise - if null is passed in then stats are not tracked. * * @param key the key * @return Returns the value or null */ @SuppressWarnings("unchecked") - public static VAL getSharedCacheValue(SimpleCache> sharedCache, KEY key) + public static VAL getSharedCacheValue(SimpleCache> sharedCache, KEY key, TransactionStats stats) { + final long startNanos = stats != null ? System.nanoTime() : 0; Object possibleWrapper = sharedCache.get(key); + final long endNanos = stats != null ? System.nanoTime() : 0; if (possibleWrapper == null) { + if (stats != null) + { + stats.record(startNanos, endNanos, OpType.GET_MISS); + } return null; } else if (possibleWrapper instanceof ValueHolder) { + if (stats != null) + { + stats.record(startNanos, endNanos, OpType.GET_HIT); + } ValueHolder wrapper = (ValueHolder) possibleWrapper; return wrapper.getValue(); } else { + if (stats != null) + { + stats.record(startNanos, endNanos, OpType.GET_MISS); + } throw new IllegalStateException("All entries for TransactionalCache must be put using TransactionalCache.putSharedCacheValue."); } } @@ -442,10 +482,16 @@ public class TransactionalCache * * @since 4.2.3 */ - public static void putSharedCacheValue(SimpleCache> sharedCache, KEY key, VAL value) + public static void putSharedCacheValue(SimpleCache> sharedCache, KEY key, VAL value, TransactionStats stats) { ValueHolder wrapper = new ValueHolder(value); + final long startNanos = System.nanoTime(); // TODO: enabled? sharedCache.put(key, wrapper); + final long endNanos = System.nanoTime(); + if (stats != null) + { + stats.record(startNanos, endNanos, OpType.PUT); + } } /** @@ -582,7 +628,16 @@ public class TransactionalCache { // There is no in-txn entry for the key // Use the value direct from the shared cache - V value = TransactionalCache.getSharedCacheValue(sharedCache, key); + V value = null; + if (cacheStatsEnabled) + { + value = TransactionalCache.getSharedCacheValue(sharedCache, key, txnData.stats); + } + else + { + // No stats tracking, pass in null TransactionStats + value = TransactionalCache.getSharedCacheValue(sharedCache, key, null); + } bucket = new ReadCacheBucket(value); txnData.updatedItemsCache.put(key, bucket); return value; @@ -592,7 +647,7 @@ public class TransactionalCache // no value found - must we ignore the shared cache? if (!ignoreSharedCache) { - V value = TransactionalCache.getSharedCacheValue(sharedCache, key); + V value = TransactionalCache.getSharedCacheValue(sharedCache, key, null); // go to the shared cache if (isDebugEnabled) { @@ -629,7 +684,7 @@ public class TransactionalCache if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction { // no transaction - TransactionalCache.putSharedCacheValue(sharedCache, key, value); + TransactionalCache.putSharedCacheValue(sharedCache, key, value, null); // done if (isDebugEnabled) { @@ -881,7 +936,14 @@ public class TransactionalCache if (txnData.isClearOn) { // clear shared cache + final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0; sharedCache.clear(); + final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0; + if (cacheStatsEnabled) + { + TransactionStats stats = txnData.stats; + stats.record(startNanos, endNanos, OpType.CLEAR); + } if (isDebugEnabled) { logger.debug("Clear notification recieved in commit - clearing shared cache"); @@ -892,7 +954,11 @@ public class TransactionalCache // transfer any removed items for (Serializable key : txnData.removedItemsCache) { + final long startNanos = System.nanoTime(); sharedCache.remove(key); + final long endNanos = System.nanoTime(); + TransactionStats stats = txnData.stats; + stats.record(startNanos, endNanos, OpType.REMOVE); } if (isDebugEnabled) { @@ -942,7 +1008,14 @@ public class TransactionalCache if (txnData.isClearOn) { // clear shared cache + final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0; sharedCache.clear(); + final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0; + if (cacheStatsEnabled) + { + TransactionStats stats = txnData.stats; + stats.record(startNanos, endNanos, OpType.CLEAR); + } if (isDebugEnabled) { logger.debug("Clear notification recieved in commit - clearing shared cache"); @@ -953,7 +1026,11 @@ public class TransactionalCache // transfer any removed items for (Serializable key : txnData.removedItemsCache) { + final long startNanos = System.nanoTime(); sharedCache.remove(key); + final long endNanos = System.nanoTime(); + TransactionStats stats = txnData.stats; + stats.record(startNanos, endNanos, OpType.REMOVE); } if (isDebugEnabled) { @@ -971,7 +1048,7 @@ public class TransactionalCache { bucket.doPostCommit( sharedCache, - key, this.isMutable, this.allowEqualsChecks, txnData.isReadOnly); + key, this.isMutable, this.allowEqualsChecks, txnData.isReadOnly, txnData.stats); } catch (Exception e) { @@ -1000,6 +1077,11 @@ public class TransactionalCache finally { removeCaches(txnData); + // Aggregate this transaction's stats with centralised cache stats. + if (cacheStatsEnabled) + { + cacheStats.add(name, txnData.stats); + } } } @@ -1016,7 +1098,14 @@ public class TransactionalCache if (txnData.isClearOn) { // clear shared cache + final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0; sharedCache.clear(); + final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0; + if (cacheStatsEnabled) + { + TransactionStats stats = txnData.stats; + stats.record(startNanos, endNanos, OpType.CLEAR); + } if (isDebugEnabled) { logger.debug("Clear notification recieved in rollback - clearing shared cache"); @@ -1027,7 +1116,11 @@ public class TransactionalCache // transfer any removed items for (Serializable key : txnData.removedItemsCache) { + final long startNanos = System.nanoTime(); sharedCache.remove(key); + final long endNanos = System.nanoTime(); + TransactionStats stats = txnData.stats; + stats.record(startNanos, endNanos, OpType.REMOVE); } if (isDebugEnabled) { @@ -1042,6 +1135,11 @@ public class TransactionalCache finally { removeCaches(txnData); + // Aggregate this transaction's stats with centralised cache stats. + if (cacheStatsEnabled) + { + cacheStats.add(name, txnData.stats); + } } } @@ -1087,7 +1185,7 @@ public class TransactionalCache public void doPostCommit( SimpleCache> sharedCache, Serializable key, - boolean mutable, boolean allowEqualsCheck, boolean readOnly); + boolean mutable, boolean allowEqualsCheck, boolean readOnly, TransactionStats stats); } /** @@ -1117,13 +1215,13 @@ public class TransactionalCache public void doPostCommit( SimpleCache> sharedCache, Serializable key, - boolean mutable, boolean allowEqualsCheck, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly, TransactionStats stats) { ValueHolder sharedObjValueHolder = sharedCache.get(key); if (sharedObjValueHolder == null) { // Nothing has changed, write it through - TransactionalCache.putSharedCacheValue(sharedCache, key, value); + TransactionalCache.putSharedCacheValue(sharedCache, key, value, stats); } else if (!mutable) { @@ -1174,7 +1272,7 @@ public class TransactionalCache public void doPostCommit( SimpleCache> sharedCache, Serializable key, - boolean mutable, boolean allowEqualsCheck, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly, TransactionStats stats) { ValueHolder sharedObjValueHolder = sharedCache.get(key); if (sharedObjValueHolder == null) @@ -1183,7 +1281,7 @@ public class TransactionalCache if (!mutable) { // We can assume that our value is correct because it's immutable - TransactionalCache.putSharedCacheValue(sharedCache, key, value); + TransactionalCache.putSharedCacheValue(sharedCache, key, value, stats); } else { @@ -1204,7 +1302,7 @@ public class TransactionalCache { // The value in the cache did not change from what we observed before. // Update the value. - TransactionalCache.putSharedCacheValue(sharedCache, key, value); + TransactionalCache.putSharedCacheValue(sharedCache, key, value, stats); } else { @@ -1241,7 +1339,7 @@ public class TransactionalCache public void doPostCommit( SimpleCache> sharedCache, Serializable key, - boolean mutable, boolean allowEqualsCheck, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly, TransactionStats stats) { } } @@ -1257,6 +1355,7 @@ public class TransactionalCache private boolean isClosed; private boolean isReadOnly; private boolean noSharedCacheRead; + private TransactionStats stats; } /** diff --git a/source/test-java/org/alfresco/repo/cache/CacheTest.java b/source/test-java/org/alfresco/repo/cache/CacheTest.java index b776a4c54d..c58e088fd5 100644 --- a/source/test-java/org/alfresco/repo/cache/CacheTest.java +++ b/source/test-java/org/alfresco/repo/cache/CacheTest.java @@ -26,6 +26,7 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.repo.cache.TransactionStats.OpType; import org.alfresco.repo.cache.TransactionalCache.ValueHolder; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; @@ -56,7 +57,10 @@ public class CacheTest extends TestCase private ServiceRegistry serviceRegistry; private SimpleCache objectCache; private SimpleCache> backingCache; + private SimpleCache> backingCacheNoStats; private TransactionalCache transactionalCache; + private TransactionalCache transactionalCacheNoStats; + private CacheStatistics cacheStats; @SuppressWarnings("unchecked") @Override @@ -70,14 +74,20 @@ public class CacheTest extends TestCase serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); objectCache = (SimpleCache) ctx.getBean("objectCache"); backingCache = (SimpleCache>) ctx.getBean("backingCache"); + backingCacheNoStats = (SimpleCache>) ctx.getBean("backingCacheNoStats"); transactionalCache = (TransactionalCache) ctx.getBean("transactionalCache"); - + transactionalCacheNoStats = (TransactionalCache) ctx.getBean("transactionalCacheNoStats"); + cacheStats = (CacheStatistics) ctx.getBean("cacheStatistics"); // Make sure that the backing cache is empty backingCache.clear(); + backingCacheNoStats.clear(); // Make the cache mutable (default) transactionalCache.setMutable(true); transactionalCache.setAllowEqualsChecks(false); + + transactionalCacheNoStats.setMutable(true); + transactionalCacheNoStats.setAllowEqualsChecks(false); } @Override @@ -87,14 +97,18 @@ public class CacheTest extends TestCase objectCache = null; backingCache = null; transactionalCache = null; + backingCacheNoStats = null; + transactionalCacheNoStats = null; } public void testSetUp() throws Exception { assertNotNull(serviceRegistry); assertNotNull(backingCache); + assertNotNull(backingCacheNoStats); assertNotNull(objectCache); assertNotNull(transactionalCache); + assertNotNull(transactionalCacheNoStats); } public void testObjectCache() throws Exception @@ -122,18 +136,18 @@ public class CacheTest extends TestCase // no transaction - do a put transactionalCache.put(key, value); // check that the value appears in the backing cache, backingCache - assertEquals("Backing cache not used for put when no transaction present", value, TransactionalCache.getSharedCacheValue(backingCache, key)); + assertEquals("Backing cache not used for put when no transaction present", value, TransactionalCache.getSharedCacheValue(backingCache, key, null)); // remove the value from the backing cache and check that it is removed from the transaction cache backingCache.remove(key); assertNull("Backing cache not used for removed when no transaction present", transactionalCache.get(key)); // add value into backing cache - TransactionalCache.putSharedCacheValue(backingCache, key, value); + TransactionalCache.putSharedCacheValue(backingCache, key, value, null); // remove it from the transactional cache transactionalCache.remove(key); // check that it is gone from the backing cache - assertNull("Non-transactional remove didn't go to backing cache", TransactionalCache.getSharedCacheValue(backingCache, key)); + assertNull("Non-transactional remove didn't go to backing cache", TransactionalCache.getSharedCacheValue(backingCache, key, null)); } private static final String NEW_GLOBAL_ONE = "new_global_one"; @@ -148,7 +162,7 @@ public class CacheTest extends TestCase RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); // Add items to the global cache - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_ONE, NEW_GLOBAL_ONE, null); RetryingTransactionCallback callback = new RetryingTransactionCallback() { @@ -190,12 +204,13 @@ public class CacheTest extends TestCase public void testTransactionalCacheWithSingleTxn() throws Throwable { // add item to global cache - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_TWO, NEW_GLOBAL_TWO); - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_THREE, NEW_GLOBAL_THREE); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_ONE, NEW_GLOBAL_ONE, null); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_TWO, NEW_GLOBAL_TWO, null); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_THREE, NEW_GLOBAL_THREE, null); TransactionService transactionService = serviceRegistry.getTransactionService(); UserTransaction txn = transactionService.getUserTransaction(); + try { // begin a transaction @@ -210,7 +225,7 @@ public class CacheTest extends TestCase // read 2 from the cache assertEquals("Item not read from backing cache", NEW_GLOBAL_TWO, transactionalCache.get(NEW_GLOBAL_TWO)); // Change the backing cache - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_TWO, NEW_GLOBAL_TWO + "-updated"); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_TWO, NEW_GLOBAL_TWO + "-updated", null); // Ensure read-committed assertEquals("Read-committed not preserved", NEW_GLOBAL_TWO, transactionalCache.get(NEW_GLOBAL_TWO)); @@ -249,8 +264,8 @@ public class CacheTest extends TestCase // check that backing cache was updated with the in-transaction changes assertFalse("Item was not removed from backing cache", backingCache.contains(NEW_GLOBAL_ONE)); - assertNull("Item could still be fetched from backing cache", TransactionalCache.getSharedCacheValue(backingCache, NEW_GLOBAL_ONE)); - assertEquals("Item not updated in backing cache", "XXX", TransactionalCache.getSharedCacheValue(backingCache, UPDATE_TXN_THREE)); + assertNull("Item could still be fetched from backing cache", TransactionalCache.getSharedCacheValue(backingCache, NEW_GLOBAL_ONE, null)); + assertEquals("Item not updated in backing cache", "XXX", TransactionalCache.getSharedCacheValue(backingCache, UPDATE_TXN_THREE, null)); // Check that the transactional cache serves get requests assertEquals("Transactional cache must serve post-commit get requests", "XXX", @@ -337,9 +352,9 @@ public class CacheTest extends TestCase public void testTransactionalCacheDisableSharedCaches() throws Throwable { // add item to global cache - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_TWO, NEW_GLOBAL_TWO); - TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_THREE, NEW_GLOBAL_THREE); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_ONE, NEW_GLOBAL_ONE, null); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_TWO, NEW_GLOBAL_TWO, null); + TransactionalCache.putSharedCacheValue(backingCache, NEW_GLOBAL_THREE, NEW_GLOBAL_THREE, null); TransactionService transactionService = serviceRegistry.getTransactionService(); UserTransaction txn = transactionService.getUserTransaction(); @@ -519,7 +534,7 @@ public class CacheTest extends TestCase txn.begin(); - TransactionalCache.putSharedCacheValue(backingCache, "A", null); + TransactionalCache.putSharedCacheValue(backingCache, "A", null, null); transactionalCache.put("A", "AAA"); try @@ -596,7 +611,7 @@ public class CacheTest extends TestCase { try { txn.rollback(); } catch (Throwable ee) {} } - Object actualValue = TransactionalCache.getSharedCacheValue(backingCache, key); + Object actualValue = TransactionalCache.getSharedCacheValue(backingCache, key, null); assertEquals("Backing cache value was not correct", expectedValue, actualValue); assertEquals("Backing cache contains(key): ", mustContainKey, backingCache.contains(key)); @@ -612,8 +627,8 @@ public class CacheTest extends TestCase public void testValueLockingInTxn() throws Exception { // add item to global cache - TransactionalCache.putSharedCacheValue(backingCache, DEFINITIVE_TWO, "initial_two"); - TransactionalCache.putSharedCacheValue(backingCache, DEFINITIVE_THREE, "initial_three"); + TransactionalCache.putSharedCacheValue(backingCache, DEFINITIVE_TWO, "initial_two", null); + TransactionalCache.putSharedCacheValue(backingCache, DEFINITIVE_THREE, "initial_three", null); TransactionService transactionService = serviceRegistry.getTransactionService(); UserTransaction txn = transactionService.getUserTransaction(); @@ -670,9 +685,9 @@ public class CacheTest extends TestCase txn.commit(); // Check post-commit values - assertEquals("Definitive change not written through.", DEFINITIVE_ONE, TransactionalCache.getSharedCacheValue(backingCache, DEFINITIVE_ONE)); - assertEquals("Definitive change not written through.", DEFINITIVE_TWO, TransactionalCache.getSharedCacheValue(backingCache, DEFINITIVE_TWO)); - assertEquals("Definitive change not written through.", null, TransactionalCache.getSharedCacheValue(backingCache, DEFINITIVE_THREE)); + assertEquals("Definitive change not written through.", DEFINITIVE_ONE, TransactionalCache.getSharedCacheValue(backingCache, DEFINITIVE_ONE, null)); + assertEquals("Definitive change not written through.", DEFINITIVE_TWO, TransactionalCache.getSharedCacheValue(backingCache, DEFINITIVE_TWO, null)); + assertEquals("Definitive change not written through.", null, TransactionalCache.getSharedCacheValue(backingCache, DEFINITIVE_THREE, null)); } finally { @@ -698,7 +713,7 @@ public class CacheTest extends TestCase public Object execute() throws Throwable { transactionalCache.put(COMMON_KEY, VALUE_ONE_A); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B, null); return null; } }; @@ -732,7 +747,7 @@ public class CacheTest extends TestCase public Object execute() throws Throwable { transactionalCache.put(COMMON_KEY, VALUE_ONE_A); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); return null; } }; @@ -766,7 +781,7 @@ public class CacheTest extends TestCase public Object execute() throws Throwable { transactionalCache.put(COMMON_KEY, VALUE_ONE_A); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, null); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, null, null); return null; } }; @@ -834,9 +849,9 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.put(COMMON_KEY, VALUE_ONE_B); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_TWO_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_TWO_A, null); return null; } }; @@ -870,9 +885,9 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.put(COMMON_KEY, VALUE_ONE_B); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, null); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, null, null); return null; } }; @@ -906,9 +921,9 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.put(COMMON_KEY, null); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B, null); return null; } }; @@ -942,9 +957,9 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.put(COMMON_KEY, null); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, null); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, null, null); return null; } }; @@ -978,7 +993,7 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.put(COMMON_KEY, VALUE_ONE_B); backingCache.remove(COMMON_KEY); return null; @@ -1014,7 +1029,7 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.put(COMMON_KEY, VALUE_ONE_B); backingCache.clear(); return null; @@ -1052,7 +1067,7 @@ public class CacheTest extends TestCase { backingCache.remove(COMMON_KEY); transactionalCache.remove(COMMON_KEY); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B, null); return null; } }; @@ -1088,7 +1103,7 @@ public class CacheTest extends TestCase { backingCache.remove(COMMON_KEY); transactionalCache.put(COMMON_KEY, VALUE_ONE_A); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B, null); return null; } }; @@ -1122,9 +1137,9 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.remove(COMMON_KEY); - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_B, null); return null; } }; @@ -1158,7 +1173,7 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.remove(COMMON_KEY); backingCache.remove(COMMON_KEY); return null; @@ -1194,7 +1209,7 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A); + TransactionalCache.putSharedCacheValue(backingCache, COMMON_KEY, VALUE_ONE_A, null); transactionalCache.remove(COMMON_KEY); backingCache.clear(); return null; @@ -1216,4 +1231,280 @@ public class CacheTest extends TestCase executeAndCheck(callback, false, COMMON_KEY, null, false); // Immutable: Nothing to do executeAndCheck(callback, true, COMMON_KEY, null, false); // Immutable: Nothing to do } + + public void testTransactionalCacheStatsOnCommit() throws Throwable + { + // add item to global cache + TransactionalCache.putSharedCacheValue(backingCache, "stats-test1", "v", null); + TransactionalCache.putSharedCacheValue(backingCache, "stats-test2", "v", null); + TransactionalCache.putSharedCacheValue(backingCache, "stats-test3", "v", null); + + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + + final long hitsAtStart = cacheStats.count("transactionalCache", OpType.GET_HIT); + final long missesAtStart = cacheStats.count("transactionalCache", OpType.GET_MISS); + final long putsAtStart = cacheStats.count("transactionalCache", OpType.PUT); + final long removesAtStart = cacheStats.count("transactionalCache", OpType.REMOVE); + final long clearsAtStart = cacheStats.count("transactionalCache", OpType.CLEAR); + + try + { + // begin a transaction + txn.begin(); + + // Perform some puts + transactionalCache.put("stats-test4", "v"); + transactionalCache.put("stats-test5", "v"); + transactionalCache.put("stats-test6", "v"); + transactionalCache.put("stats-test7", "v"); + transactionalCache.put("stats-test8", "v"); + + // Perform some gets... + // hits + transactionalCache.get("stats-test3"); + transactionalCache.get("stats-test2"); + transactionalCache.get("stats-test1"); + // repeated hits won't touch the shared cache + transactionalCache.get("stats-test2"); + transactionalCache.get("stats-test1"); + // misses - not yet committed + transactionalCache.get("stats-miss1"); + transactionalCache.get("stats-miss2"); + transactionalCache.get("stats-miss3"); + transactionalCache.get("stats-miss4"); + // repeated misses won't touch the shared cache + transactionalCache.get("stats-miss2"); + transactionalCache.get("stats-miss3"); + + // Perform some removals + transactionalCache.remove("stats-test1"); + transactionalCache.remove("stats-test2"); + transactionalCache.remove("stats-test3"); + transactionalCache.remove("stats-test9"); + transactionalCache.remove("stats-test10"); + transactionalCache.remove("stats-test11"); + transactionalCache.remove("stats-test12"); + transactionalCache.remove("stats-test13"); + + // Check nothing has changed yet - changes not written through until commit or rollback + assertEquals(hitsAtStart, cacheStats.count("transactionalCache", OpType.GET_HIT)); + assertEquals(missesAtStart, cacheStats.count("transactionalCache", OpType.GET_MISS)); + assertEquals(putsAtStart, cacheStats.count("transactionalCache", OpType.PUT)); + assertEquals(removesAtStart, cacheStats.count("transactionalCache", OpType.REMOVE)); + assertEquals(clearsAtStart, cacheStats.count("transactionalCache", OpType.CLEAR)); + + // commit the transaction + txn.commit(); + + // TODO: remove is called twice for each remove (in beforeCommit and afterCommit) - check this is correct. + assertEquals(removesAtStart+16, cacheStats.count("transactionalCache", OpType.REMOVE)); + assertEquals(hitsAtStart+3, cacheStats.count("transactionalCache", OpType.GET_HIT)); + assertEquals(missesAtStart+4, cacheStats.count("transactionalCache", OpType.GET_MISS)); + assertEquals(putsAtStart+5, cacheStats.count("transactionalCache", OpType.PUT)); + // Performing a clear would affect the other stats, so a separate test is required. + assertEquals(clearsAtStart+0, cacheStats.count("transactionalCache", OpType.CLEAR)); + } + catch (Throwable e) + { + if (txn.getStatus() == Status.STATUS_ACTIVE) + { + txn.rollback(); + } + throw e; + } + } + + public void testTransactionalCacheStatsDisabled() throws Throwable + { + // add item to global cache + TransactionalCache.putSharedCacheValue(backingCacheNoStats, "stats-test1", "v", null); + TransactionalCache.putSharedCacheValue(backingCacheNoStats, "stats-test2", "v", null); + TransactionalCache.putSharedCacheValue(backingCacheNoStats, "stats-test3", "v", null); + + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + + for (OpType opType : OpType.values()) + { + try + { + cacheStats.count("transactionalCacheNoStats", opType); + fail("Expected NoStatsForCache error."); + } + catch(NoStatsForCache e) + { + // Good + } + } + + try + { + // begin a transaction + txn.begin(); + + // Perform some puts + transactionalCacheNoStats.put("stats-test4", "v"); + transactionalCache.put("stats-test5", "v"); + transactionalCache.put("stats-test6", "v"); + transactionalCache.put("stats-test7", "v"); + transactionalCache.put("stats-test8", "v"); + + // Perform some gets... + // hits + transactionalCache.get("stats-test3"); + transactionalCache.get("stats-test2"); + transactionalCache.get("stats-test1"); + // repeated hits won't touch the shared cache + transactionalCache.get("stats-test2"); + transactionalCache.get("stats-test1"); + // misses - not yet committed + transactionalCache.get("stats-miss1"); + transactionalCache.get("stats-miss2"); + transactionalCache.get("stats-miss3"); + transactionalCache.get("stats-miss4"); + // repeated misses won't touch the shared cache + transactionalCache.get("stats-miss2"); + transactionalCache.get("stats-miss3"); + + // Perform some removals + transactionalCache.remove("stats-test1"); + transactionalCache.remove("stats-test2"); + transactionalCache.remove("stats-test3"); + transactionalCache.remove("stats-test9"); + transactionalCache.remove("stats-test10"); + transactionalCache.remove("stats-test11"); + transactionalCache.remove("stats-test12"); + transactionalCache.remove("stats-test13"); + + // Check nothing has changed - changes not written through until commit or rollback + for (OpType opType : OpType.values()) + { + try + { + cacheStats.count("transactionalCacheNoStats", opType); + fail("Expected NoStatsForCache error."); + } + catch(NoStatsForCache e) + { + // Good + } + } + + // commit the transaction + txn.commit(); + + // Post-commit, nothing should have changed. + for (OpType opType : OpType.values()) + { + try + { + cacheStats.count("transactionalCacheNoStats", opType); + fail("Expected NoStatsForCache error."); + } + catch(NoStatsForCache e) + { + // Good + } + } + } + catch (Throwable e) + { + if (txn.getStatus() == Status.STATUS_ACTIVE) + { + txn.rollback(); + } + throw e; + } + } + + + public void testTransactionalCacheStatsForClears() throws Throwable + { + // add item to global cache + TransactionalCache.putSharedCacheValue(backingCache, "stats-test1", "v", null); + TransactionalCache.putSharedCacheValue(backingCache, "stats-test2", "v", null); + TransactionalCache.putSharedCacheValue(backingCache, "stats-test3", "v", null); + + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + + final long hitsAtStart = cacheStats.count("transactionalCache", OpType.GET_HIT); + final long missesAtStart = cacheStats.count("transactionalCache", OpType.GET_MISS); + final long putsAtStart = cacheStats.count("transactionalCache", OpType.PUT); + final long removesAtStart = cacheStats.count("transactionalCache", OpType.REMOVE); + final long clearsAtStart = cacheStats.count("transactionalCache", OpType.CLEAR); + + try + { + // begin a transaction + txn.begin(); + + // Perform some puts + transactionalCache.put("stats-test4", "v"); + transactionalCache.put("stats-test5", "v"); + transactionalCache.put("stats-test6", "v"); + transactionalCache.put("stats-test7", "v"); + transactionalCache.put("stats-test8", "v"); + + // Perform some gets... + // hits + transactionalCache.get("stats-test3"); + transactionalCache.get("stats-test2"); + transactionalCache.get("stats-test1"); + // repeated hits won't touch the shared cache + transactionalCache.get("stats-test2"); + transactionalCache.get("stats-test1"); + // misses - not yet committed + transactionalCache.get("stats-miss1"); + transactionalCache.get("stats-miss2"); + transactionalCache.get("stats-miss3"); + transactionalCache.get("stats-miss4"); + // repeated misses won't touch the shared cache + transactionalCache.get("stats-miss2"); + transactionalCache.get("stats-miss3"); + + // Perform some removals + transactionalCache.remove("stats-test1"); + transactionalCache.remove("stats-test2"); + transactionalCache.remove("stats-test3"); + transactionalCache.remove("stats-test9"); + transactionalCache.remove("stats-test10"); + transactionalCache.remove("stats-test11"); + transactionalCache.remove("stats-test12"); + transactionalCache.remove("stats-test13"); + + // Perform some clears + transactionalCache.clear(); + transactionalCache.clear(); + + // Check nothing has changed yet - changes not written through until commit or rollback + assertEquals(hitsAtStart, cacheStats.count("transactionalCache", OpType.GET_HIT)); + assertEquals(missesAtStart, cacheStats.count("transactionalCache", OpType.GET_MISS)); + assertEquals(putsAtStart, cacheStats.count("transactionalCache", OpType.PUT)); + assertEquals(removesAtStart, cacheStats.count("transactionalCache", OpType.REMOVE)); + assertEquals(clearsAtStart, cacheStats.count("transactionalCache", OpType.CLEAR)); + + // commit the transaction + txn.commit(); + + assertEquals(clearsAtStart+2, cacheStats.count("transactionalCache", OpType.CLEAR)); + // There are no removes or puts propagated to the shared cache, as a result of the clears. + assertEquals(removesAtStart+0, cacheStats.count("transactionalCache", OpType.REMOVE)); + assertEquals(putsAtStart+0, cacheStats.count("transactionalCache", OpType.PUT)); + assertEquals(hitsAtStart+3, cacheStats.count("transactionalCache", OpType.GET_HIT)); + assertEquals(missesAtStart+4, cacheStats.count("transactionalCache", OpType.GET_MISS)); + } + catch (Throwable e) + { + if (txn.getStatus() == Status.STATUS_ACTIVE) + { + txn.rollback(); + } + throw e; + } + } } diff --git a/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java b/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java new file mode 100644 index 0000000000..1913968f78 --- /dev/null +++ b/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java @@ -0,0 +1,145 @@ +package org.alfresco.repo.cache; + +import static org.junit.Assert.*; + +import org.alfresco.repo.cache.TransactionStats.OpType; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; + +/** + * Tests for the {@link InMemoryCacheStatistics} class. + * + * @since 5.0 + * @author Matt Ward + */ +@RunWith(MockitoJUnitRunner.class) +public class InMemoryCacheStatisticsTest +{ + InMemoryCacheStatistics cacheStats; + @Mock ApplicationContext appCtx; + + @Before + public void setUp() throws Exception + { + cacheStats = new InMemoryCacheStatistics(); + cacheStats.setApplicationContext(appCtx); + } + + @Test + public void readOperationsThrowNoCacheStatsException() + { + try + { + cacheStats.count("cache1", OpType.GET_HIT); + fail("NoStatsForCache should have been thrown."); + } + catch(NoStatsForCache e) + { + // Good. + } + try + { + cacheStats.hitMissRatio("cache1"); + fail("NoStatsForCache should have been thrown."); + } + catch(NoStatsForCache e) + { + // Good. + } + try + { + cacheStats.meanTime("cache1", OpType.GET_HIT); + fail("NoStatsForCache should have been thrown."); + } + catch(NoStatsForCache e) + { + // Good. + } + } + + @Test + public void canAccumulateStatisticsPerCache() + { + TransactionStats txStats = new TransactionStats(); + txStats.record(0, 1000, OpType.GET_HIT); + txStats.record(0, 2000, OpType.GET_HIT); + txStats.record(0, 3000, OpType.GET_HIT); + + // Add first statistical datapoint. + cacheStats.add("cache1", txStats); + + Mockito.verify(appCtx).publishEvent(Mockito.any(CacheStatisticsCreated.class)); + + // Cache stats should be visible + assertEquals(3, cacheStats.count("cache1", OpType.GET_HIT)); + assertEquals(2000, cacheStats.meanTime("cache1", OpType.GET_HIT), 0.0d); + + // A new transaction + txStats = new TransactionStats(); + txStats.record(0, 4000, OpType.GET_HIT); + txStats.record(0, 5000, OpType.GET_HIT); + + // Central cache stats should not yet include new transaction's stats. + assertEquals(3, cacheStats.count("cache1", OpType.GET_HIT)); + + cacheStats.add("cache1", txStats); + + // New TX's stats should now be visible + assertEquals(5, cacheStats.count("cache1", OpType.GET_HIT)); + assertEquals(3000, cacheStats.meanTime("cache1", OpType.GET_HIT), 0.0d); + + // A different cache + try + { + cacheStats.count("cache2", OpType.GET_HIT); + fail("Expected NoStatsForCache error."); + } + catch(NoStatsForCache e) + { + // Good + } + txStats = new TransactionStats(); + txStats.record(0, 4000, OpType.GET_HIT); + txStats.record(8000, 9000, OpType.GET_HIT); + cacheStats.add("cache2", txStats); + assertEquals(2, cacheStats.count("cache2", OpType.GET_HIT)); + assertEquals(2500, cacheStats.meanTime("cache2", OpType.GET_HIT), 0.0d); + // cache2 should NOT affect cache1 + assertEquals(5, cacheStats.count("cache1", OpType.GET_HIT)); + assertEquals(3000, cacheStats.meanTime("cache1", OpType.GET_HIT), 0.0d); + + // Some non-hit statistics + txStats = new TransactionStats(); + txStats.record(0, 810, OpType.GET_MISS); + txStats.record(1000, 1820, OpType.PUT); + txStats.record(3000, 3830, OpType.REMOVE); + txStats.record(4000, 4840, OpType.CLEAR); + cacheStats.add("cache1", txStats); + // Hits haven't changed + assertEquals(5, cacheStats.count("cache1", OpType.GET_HIT)); + assertEquals(3000, cacheStats.meanTime("cache1", OpType.GET_HIT), 0.0d); + + // Other stats have changed + assertEquals(1, cacheStats.count("cache1", OpType.GET_MISS)); + assertEquals(810, cacheStats.meanTime("cache1", OpType.GET_MISS), 0.01d); + + assertEquals(1, cacheStats.count("cache1", OpType.PUT)); + assertEquals(820, cacheStats.meanTime("cache1", OpType.PUT), 0.01d); + + assertEquals(1, cacheStats.count("cache1", OpType.REMOVE)); + assertEquals(830, cacheStats.meanTime("cache1", OpType.REMOVE), 0.01d); + + assertEquals(1, cacheStats.count("cache1", OpType.CLEAR)); + assertEquals(840, cacheStats.meanTime("cache1", OpType.CLEAR), 0.01d); + + // Check hit ratio + assertEquals(5, cacheStats.count("cache1", OpType.GET_HIT)); + assertEquals(1, cacheStats.count("cache1", OpType.GET_MISS)); + assertEquals(0.83, cacheStats.hitMissRatio("cache1"), 0.01d); + } +} diff --git a/source/test-java/org/alfresco/repo/cache/TransactionStatsTest.java b/source/test-java/org/alfresco/repo/cache/TransactionStatsTest.java new file mode 100644 index 0000000000..0cbaac15d1 --- /dev/null +++ b/source/test-java/org/alfresco/repo/cache/TransactionStatsTest.java @@ -0,0 +1,77 @@ +package org.alfresco.repo.cache; + +import static org.junit.Assert.*; + +import org.alfresco.repo.cache.TransactionStats.OpType; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for the {@link TransactionStats} class. + * + * @since 5.0 + * @author Matt Ward + */ +public class TransactionStatsTest +{ + + @Before + public void setUp() throws Exception + { + } + + @Test + public void canGetCacheOperationCountsWhenNoOpsTakenPlaceYet() + { + TransactionStats stats = new TransactionStats(); + + // No data has been added yet + assertTrue(OpType.values().length > 0); + for (OpType op : OpType.values()) + { + assertEquals(0, stats.getCount(op)); + } + } + + @Test + public void canRecordSomeOpsAndGetTheirValues() + { + TransactionStats stats = new TransactionStats(); + + // Some hits + stats.record(0, 1000, OpType.GET_HIT); + stats.record(2000, 4000, OpType.GET_HIT); + stats.record(3000, 4000, OpType.GET_HIT); + // Some misses + stats.record(0, 1000, OpType.GET_MISS); + stats.record(0, 2000, OpType.GET_MISS); + stats.record(0, 3000, OpType.GET_MISS); + stats.record(8000, 9000, OpType.GET_MISS); + stats.record(0, 2000, OpType.GET_MISS); + stats.record(0, 3000, OpType.GET_MISS); + // Some puts + stats.record(1500, 2500, OpType.PUT); + stats.record(100, 2100, OpType.PUT); + // Some removes + stats.record(0, 1000, OpType.REMOVE); + // Some clears + stats.record(0, 1000, OpType.CLEAR); + stats.record(0, 2000, OpType.CLEAR); + stats.record(0, 2000, OpType.CLEAR); + stats.record(0, 2000, OpType.CLEAR); + + // Counts + assertEquals(3, stats.getCount(OpType.GET_HIT)); + assertEquals(6, stats.getCount(OpType.GET_MISS)); + assertEquals(2, stats.getCount(OpType.PUT)); + assertEquals(1, stats.getCount(OpType.REMOVE)); + assertEquals(4, stats.getCount(OpType.CLEAR)); + + // Mean operation times + assertEquals(1333.33, stats.getTimings(OpType.GET_HIT).getMean(), 0.01d); + assertEquals(2000, stats.getTimings(OpType.GET_MISS).getMean(), 0.01d); + assertEquals(1500, stats.getTimings(OpType.PUT).getMean(), 0.01d); + assertEquals(1000, stats.getTimings(OpType.REMOVE).getMean(), 0.01d); + assertEquals(1750, stats.getTimings(OpType.CLEAR).getMean(), 0.01d); + } +} diff --git a/source/test-resources/cache-test/cache-test-context.xml b/source/test-resources/cache-test/cache-test-context.xml index a369106fa7..2117cddee7 100644 --- a/source/test-resources/cache-test/cache-test-context.xml +++ b/source/test-resources/cache-test/cache-test-context.xml @@ -8,10 +8,25 @@ + + transactionalCache 200000 + + + + + + + + + + transactionalCacheNoStats + 200000 + + \ No newline at end of file