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-langcommons-lang
+
+ org.apache.commons
+ commons-math3
+ 3.3
+ commons-poolcommons-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