From 151537105dadaf6058dac482caa6e5544f78ee21 Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Sat, 31 Jan 2015 15:26:15 +0000 Subject: [PATCH] Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud) 93938: Merged BRANCHES/DEV/mward/post50_hbf_fixes to BRANCHES/DEV/HEAD-BUG-FIX: 93561: Cache stats: Fixed concurrency issue with 'get' count. Added method to retrieve stats snapshot. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@95014 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/repo/cache/CacheStatistics.java | 24 ++++++- .../repo/cache/InMemoryCacheStatistics.java | 68 ++++++++++-------- .../repo/cache/NoOpCacheStatistics.java | 15 ++++ .../alfresco/repo/cache/OperationStats.java | 70 +++++++++++++++++++ .../cache/InMemoryCacheStatisticsTest.java | 62 ++++++++++++++++ 5 files changed, 207 insertions(+), 32 deletions(-) create mode 100644 source/java/org/alfresco/repo/cache/OperationStats.java diff --git a/source/java/org/alfresco/repo/cache/CacheStatistics.java b/source/java/org/alfresco/repo/cache/CacheStatistics.java index 109e57759b..d4002322a1 100644 --- a/source/java/org/alfresco/repo/cache/CacheStatistics.java +++ b/source/java/org/alfresco/repo/cache/CacheStatistics.java @@ -18,6 +18,8 @@ */ package org.alfresco.repo.cache; +import java.util.Map; + import org.alfresco.repo.cache.TransactionStats.OpType; /** @@ -63,5 +65,25 @@ public interface CacheStatistics * @param cacheName The cache name. * @return ratio (double) */ - double hitMissRatio(String cacheName); + double hitMissRatio(String cacheName); + + /** + * Retrieve the total number of get operations invoked on the + * cache (i.e. sum of hits and misses). + * + * @param cacheName The cache name. + * @return Count of get operations. + */ + long numGets(String cacheName); + + /** + * Retrieve a map containing a snapshot of all of the raw stats + * (e.g. counts, mean operation times etc.). Since this is a snapshot + * it is unaffected by future modifications to the statistics - by + * using {@link CacheStatistics#add(String, TransactionStats)} for example. + * + * @param cacheName The cache name. + * @return Map of OpType to OperationStats + */ + Map allStats(String cacheName); } diff --git a/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java b/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java index 996ad03161..86ba3df726 100644 --- a/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java +++ b/source/java/org/alfresco/repo/cache/InMemoryCacheStatistics.java @@ -60,7 +60,7 @@ public class InMemoryCacheStatistics implements CacheStatistics, ApplicationCont throw new NoStatsForCache(cacheName); } OperationStats opStats = cacheStats.get(opType); - return opStats.count; + return opStats.getCount(); } finally { @@ -150,8 +150,8 @@ public class InMemoryCacheStatistics implements CacheStatistics, ApplicationCont { throw new NoStatsForCache(cacheName); } - long hits = cacheStats.get(OpType.GET_HIT).count; - long misses = cacheStats.get(OpType.GET_MISS).count; + long hits = cacheStats.get(OpType.GET_HIT).getCount(); + long misses = cacheStats.get(OpType.GET_MISS).getCount(); return (double)hits / (hits+misses); } finally @@ -159,40 +159,46 @@ public class InMemoryCacheStatistics implements CacheStatistics, ApplicationCont readLock.unlock(); } } - - /** - * Represents a single cache operation type's statistics. - */ - private static final class OperationStats + + @Override + public long numGets(String cacheName) { - /** 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) + ReadLock readLock = getReadLock(cacheName); + readLock.lock(); + try { - this.totalTime = totalTime; - this.count = count; - } - - public OperationStats(OperationStats source, double totalTime, long count) - { - if (Double.compare(source.totalTime, Double.NaN) == 0) + Map cacheStats = cacheToStatsMap.get(cacheName); + if (cacheStats == null) { - // No previous time to add new time to. - this.totalTime = totalTime; + throw new NoStatsForCache(cacheName); } - else - { - this.totalTime = source.totalTime + totalTime; - } - this.count = source.count + count; + long hits = cacheStats.get(OpType.GET_HIT).getCount(); + long misses = cacheStats.get(OpType.GET_MISS).getCount(); + return hits+misses; } - - public double meanTime() + finally { - return totalTime / count; + readLock.unlock(); + } + } + + @Override + public Map allStats(String cacheName) + { + ReadLock readLock = getReadLock(cacheName); + readLock.lock(); + try + { + Map cacheStats = cacheToStatsMap.get(cacheName); + if (cacheStats == null) + { + throw new NoStatsForCache(cacheName); + } + return new HashMap<>(cacheStats); + } + finally + { + readLock.unlock(); } } diff --git a/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java b/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java index 4941713922..1227fa00de 100644 --- a/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java +++ b/source/java/org/alfresco/repo/cache/NoOpCacheStatistics.java @@ -18,6 +18,9 @@ */ package org.alfresco.repo.cache; +import java.util.HashMap; +import java.util.Map; + import org.alfresco.repo.cache.TransactionStats.OpType; /** @@ -53,4 +56,16 @@ public class NoOpCacheStatistics implements CacheStatistics { return Double.NaN; } + + @Override + public long numGets(String cacheName) + { + return 0; + } + + @Override + public Map allStats(String cacheName) + { + return new HashMap<>(); + } } diff --git a/source/java/org/alfresco/repo/cache/OperationStats.java b/source/java/org/alfresco/repo/cache/OperationStats.java new file mode 100644 index 0000000000..2d22c57b2d --- /dev/null +++ b/source/java/org/alfresco/repo/cache/OperationStats.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2015 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; + + +/** + * Represents a single cache operation type's statistics. + * For example, the cummalative time spent performing a cache's remove operation + * and the number of times that the remove was performed. + *

+ * Instances are immutable. + */ +public 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; + } + + public double getTotalTime() + { + return this.totalTime; + } + + public long getCount() + { + return this.count; + } +} diff --git a/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java b/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java index 1913968f78..c73e3773cc 100644 --- a/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java +++ b/source/test-java/org/alfresco/repo/cache/InMemoryCacheStatisticsTest.java @@ -2,6 +2,8 @@ package org.alfresco.repo.cache; import static org.junit.Assert.*; +import java.util.Map; + import org.alfresco.repo.cache.TransactionStats.OpType; import org.junit.Before; import org.junit.Test; @@ -60,6 +62,24 @@ public class InMemoryCacheStatisticsTest { // Good. } + try + { + cacheStats.numGets("cache1"); + fail("NoStatsForCache should have been thrown."); + } + catch(NoStatsForCache e) + { + // Good. + } + try + { + cacheStats.allStats("cache1"); + fail("NoStatsForCache should have been thrown."); + } + catch(NoStatsForCache e) + { + // Good. + } } @Test @@ -141,5 +161,47 @@ public class InMemoryCacheStatisticsTest assertEquals(5, cacheStats.count("cache1", OpType.GET_HIT)); assertEquals(1, cacheStats.count("cache1", OpType.GET_MISS)); assertEquals(0.83, cacheStats.hitMissRatio("cache1"), 0.01d); + + // Check hit+miss count + assertEquals(6, cacheStats.numGets("cache1")); + + // Stats snapshot map + Map snapshot = cacheStats.allStats("cache1"); + assertEquals(5, snapshot.get(OpType.GET_HIT).getCount()); + assertEquals(1, snapshot.get(OpType.GET_MISS).getCount()); + } + + @Test + public void canRetrieveSnapshotOfAllStats() + { + TransactionStats txStats = new TransactionStats(); + txStats.record(0, 1000, OpType.GET_HIT); + + // Add first statistical datapoint. + cacheStats.add("cache1", txStats); + + // Cache stats should be visible + Map snapshot1 = cacheStats.allStats("cache1"); + assertEquals(1, snapshot1.get(OpType.GET_HIT).getCount()); + assertEquals(1000, snapshot1.get(OpType.GET_HIT).getTotalTime(), 0.0d); + // Map is fully populated + assertEquals(0, snapshot1.get(OpType.CLEAR).getCount()); + assertEquals(0, snapshot1.get(OpType.PUT).getCount()); + assertEquals(0, snapshot1.get(OpType.REMOVE).getCount()); + assertEquals(0, snapshot1.get(OpType.GET_MISS).getCount()); + + // Record further data + txStats = new TransactionStats(); + txStats.record(0, 2000, OpType.GET_HIT); + cacheStats.add("cache1", txStats); + + // Check new snapshot reflects update + Map snapshot2 = cacheStats.allStats("cache1"); + assertEquals(2, snapshot2.get(OpType.GET_HIT).getCount()); + assertEquals(3000, snapshot2.get(OpType.GET_HIT).getTotalTime(), 0.0d); + + // Check old snapshot is not affected + assertEquals(1, snapshot1.get(OpType.GET_HIT).getCount()); + assertEquals(1000, snapshot1.get(OpType.GET_HIT).getTotalTime(), 0.0d); } }