From ac88e1f651b29cc3e1e5c45c99616fe63080c386 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 19 Feb 2014 20:56:14 +0100 Subject: [PATCH] implement cache statistics --- .../src/main/java/sonia/scm/cache/Cache.java | 13 +- .../java/sonia/scm/cache/CacheStatistics.java | 209 ++++++++++++++++++ .../sonia/scm/cache/CacheStatisticsTest.java | 73 ++++++ .../main/java/sonia/scm/cache/GuavaCache.java | 34 ++- .../src/main/resources/config/gcache.xml | 2 +- .../java/sonia/scm/cache/CacheTestBase.java | 25 +++ 6 files changed, 351 insertions(+), 5 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/cache/CacheStatistics.java create mode 100644 scm-core/src/test/java/sonia/scm/cache/CacheStatisticsTest.java diff --git a/scm-core/src/main/java/sonia/scm/cache/Cache.java b/scm-core/src/main/java/sonia/scm/cache/Cache.java index 1a5b380695..fde3e5d343 100644 --- a/scm-core/src/main/java/sonia/scm/cache/Cache.java +++ b/scm-core/src/main/java/sonia/scm/cache/Cache.java @@ -102,7 +102,7 @@ public interface Cache * Returns the number of entries in the cache. * * @return number of entries in the cache - * + * * @since 2.0.0 */ public int size(); @@ -118,4 +118,15 @@ public interface Cache * @return The cached element with the specified key or null */ public V get(K key); + + /** + * Returns performance statistics of the cache or null if the cache does not + * support statistics. The returned statistic is a snapshot of the current + * performance. + * + * @return performance statistics or null + * + * @since 2.0.0 + */ + public CacheStatistics getStatistics(); } diff --git a/scm-core/src/main/java/sonia/scm/cache/CacheStatistics.java b/scm-core/src/main/java/sonia/scm/cache/CacheStatistics.java new file mode 100644 index 0000000000..067e623e6d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/cache/CacheStatistics.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.cache; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + +/** + * Statistics about the performance of a {@link Cache}. + * Instances of this class are immutable. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public final class CacheStatistics +{ + + /** + * Constructs a new performance statistic for a {@link Cache}. + * + * + * @param name name of the cache + * @param hitCount hit count + * @param missCount miss count + */ + public CacheStatistics(String name, long hitCount, long missCount) + { + this.name = name; + this.hitCount = hitCount; + this.missCount = missCount; + } + + //~--- methods -------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final CacheStatistics other = (CacheStatistics) obj; + + return Objects.equal(name, other.name) + && Objects.equal(hitCount, other.hitCount) + && Objects.equal(missCount, other.missCount); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + return Objects.hashCode(name, hitCount, missCount); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("name", name) + .add("hitCount", hitCount) + .add("missCount", missCount) + .toString(); + //J+ + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns number of times requested elements were found in the cache. + * + * + * @return number of cache hits + */ + public long getHitCount() + { + return hitCount; + } + + /** + * Returns the ratio of cache requests which were hits. + * + * + * @return ratio of cache hits + */ + public double getHitRate() + { + return ratio(hitCount); + } + + /** + * Returns number of times a requested element was not found in the cache. + * + * + * @return number of cache misses + */ + public long getMissCount() + { + return missCount; + } + + /** + * Returns the ratio of cache requests which were misses. + * + * + * @return ratio of cache misses + */ + public double getMissRate() + { + return ratio(missCount); + } + + /** + * Returns name of the cache. + * + * + * @return name of the cache + */ + public String getName() + { + return name; + } + + /** + * Returns the total number of requests, this includes hits and misses. + * + * + * @return numer of requests + */ + public long getRequestCount() + { + return hitCount + missCount; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Calculates the ratio of a counter. + * + * + * @param counter counter + * + * @return rate of counter + */ + private double ratio(long counter) + { + long requestCount = getRequestCount(); + + return (requestCount == 0) + ? 1.0 + : (double) counter / requestCount; + } + + //~--- fields --------------------------------------------------------------- + + /** hit count */ + private final long hitCount; + + /** miss count */ + private final long missCount; + + /** name of cache */ + private final String name; +} diff --git a/scm-core/src/test/java/sonia/scm/cache/CacheStatisticsTest.java b/scm-core/src/test/java/sonia/scm/cache/CacheStatisticsTest.java new file mode 100644 index 0000000000..322f4353b3 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/cache/CacheStatisticsTest.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.cache; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class CacheStatisticsTest +{ + + /** + * Method description + * + */ + @Test + public void testCounters() + { + CacheStatistics stats = new CacheStatistics("", 12, 3); + + assertEquals(12, stats.getHitCount()); + assertEquals(3, stats.getMissCount()); + assertEquals(15, stats.getRequestCount()); + } + + /** + * Method description + * + */ + @Test + public void testRates() + { + CacheStatistics stats = new CacheStatistics("", 12, 3); + + assertEquals(0.8d, stats.getHitRate(), 0.0); + assertEquals(0.2d, stats.getMissRate(), 0.0); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java index be735a7ba4..6380719322 100644 --- a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java @@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; /** * @@ -111,6 +112,10 @@ public class GuavaCache { this.copyStrategy = copyStrategy; } + else + { + this.copyStrategy = CopyStrategy.NONE; + } } //~--- methods -------------------------------------------------------------- @@ -266,19 +271,42 @@ public class GuavaCache if (value != null) { value = copyStrategy.copyOnRead(value); + hitCount.incrementAndGet(); + } + else + { + missCount.incrementAndGet(); } return value; } + /** + * Method description + * + * + * @return + */ + @Override + public CacheStatistics getStatistics() + { + return new CacheStatistics(name, hitCount.get(), missCount.get()); + } + //~--- fields --------------------------------------------------------------- /** Field description */ - private com.google.common.cache.Cache cache; + private final com.google.common.cache.Cache cache; /** Field description */ - private CopyStrategy copyStrategy = CopyStrategy.NONE; + private final CopyStrategy copyStrategy; /** Field description */ - private String name; + private final AtomicLong hitCount = new AtomicLong(); + + /** Field description */ + private final AtomicLong missCount = new AtomicLong(); + + /** Field description */ + private final String name; } diff --git a/scm-webapp/src/main/resources/config/gcache.xml b/scm-webapp/src/main/resources/config/gcache.xml index ab6b8fb1ee..eb59f36446 100644 --- a/scm-webapp/src/main/resources/config/gcache.xml +++ b/scm-webapp/src/main/resources/config/gcache.xml @@ -93,7 +93,7 @@ expireAfterWrite="5400" /> - +