Author: mduerig
Date: Tue May  2 15:18:56 2017
New Revision: 1793528

URL: http://svn.apache.org/viewvc?rev=1793528&view=rev
Log:
OAK-5956: Improve cache statistics of the segment cache
Implement cache stats

Modified:
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
 Tue May  2 15:18:56 2017
@@ -21,43 +21,47 @@ package org.apache.jackrabbit.oak.segmen
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 import javax.annotation.Nonnull;
 
-import org.apache.jackrabbit.oak.cache.CacheStats;
-import org.apache.jackrabbit.oak.segment.CacheWeights.SegmentCacheWeigher;
-
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.RemovalListener;
 import com.google.common.cache.RemovalNotification;
 import com.google.common.cache.Weigher;
+import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
+import org.apache.jackrabbit.oak.segment.CacheWeights.SegmentCacheWeigher;
 
 /**
- * A cache for {@link Segment} instances by their {@link SegmentId}.
+ * A cache for {@link SegmentId#isDataSegmentId() data} {@link Segment} 
instances by their
+ * {@link SegmentId}. This cache ignores {@link SegmentId#isBulkSegmentId() 
bulk} segments.
  * <p>
  * Conceptually this cache serves as a 2nd level cache for segments. The 1st 
level cache is
  * implemented by memoising the segment in its id (see {@link 
SegmentId#segment}. Every time
  * an segment is evicted from this cache the memoised segment is discarded (see
- * {@link SegmentId#unloaded()}).
- * <p>
- * As a consequence this cache is actually only queried for segments it does 
not contain,
- * which are then loaded through the loader passed to {@link 
#getSegment(SegmentId, Callable)}.
- * This behaviour is eventually reflected in the cache statistics (see {@link 
#getCacheStats()}),
- * which always reports a {@link CacheStats#getHitRate()} () miss rate} of 1.
+ * {@link SegmentId#unloaded()}) and {@link SegmentId#onAccess}.
  */
 public class SegmentCache {
+    /** Default maximum weight of this cache in MB */
     public static final int DEFAULT_SEGMENT_CACHE_MB = 256;
 
+    /** Weigher to determine the current weight of all items in this cache */
     private final Weigher<SegmentId, Segment> weigher = new 
SegmentCacheWeigher();
 
+    /** Maximum weight of the items in this cache */
     private final long maximumWeight;
 
+    /** Cache of recently accessed segments */
+    @Nonnull
+    private final Cache<SegmentId, Segment> cache;
+
     /**
-     * Cache of recently accessed segments
+     * Statistics of this cache. Do to the special access patter (see class 
comment), we cannot
+     * rely on {@link Cache#stats()}.
      */
     @Nonnull
-    private final Cache<SegmentId, Segment> cache;
+    private final Stats stats = new Stats("Segment Cache");
 
     /**
      * Create a new segment cache of the given size.
@@ -67,25 +71,43 @@ public class SegmentCache {
         this.maximumWeight = cacheSizeMB * 1024 * 1024;
         this.cache = CacheBuilder.newBuilder()
                 .concurrencyLevel(16)
-                .recordStats()
                 .maximumWeight(maximumWeight)
                 .weigher(weigher)
-                .removalListener(new RemovalListener<SegmentId, Segment>() {
-                    @Override
-                    public void onRemoval(@Nonnull 
RemovalNotification<SegmentId, Segment> notification) {
-                        SegmentId id = notification.getKey();
-                        if (id != null) {
-                            id.unloaded();
-                        }
-                    }
-                }).build();
+                .removalListener(this::onRemove)
+                .build();
+    }
+
+    /**
+     * Create a new segment cache with the {@link #DEFAULT_SEGMENT_CACHE_MB 
default size}.
+     */
+    public SegmentCache() {
+        this(DEFAULT_SEGMENT_CACHE_MB);
+    }
+
+    /**
+     * Removal handler called whenever an item is evicted from the cache. 
Propagates
+     * to {@link SegmentId#unloaded()}.
+     */
+    private void onRemove(@Nonnull RemovalNotification<SegmentId, Segment> 
notification) {
+        SegmentId id = notification.getKey();
+        if (id != null) {
+            Segment segment = notification.getValue();
+            if (segment != null) {
+                stats.currentWeight.addAndGet(-weigher.weigh(id, segment));
+            }
+            stats.evictionCount.incrementAndGet();
+            id.unloaded();
+        }
     }
 
-    private void put(@Nonnull SegmentId id, Segment segment) {
+    /** Unconditionally put an item in the cache */
+    private Segment put(@Nonnull SegmentId id, @Nonnull Segment segment) {
         // Call loaded *before* putting the segment into the cache as the 
latter
         // might cause it to get evicted right away again.
         id.loaded(segment);
         cache.put(id, segment);
+        stats.currentWeight.addAndGet(weigher.weigh(id, segment));
+        return segment;
     }
 
     /**
@@ -98,30 +120,40 @@ public class SegmentCache {
     @Nonnull
     public Segment getSegment(@Nonnull final SegmentId id, @Nonnull final 
Callable<Segment> loader)
     throws ExecutionException {
+        // Load bulk segment directly without putting it in cache
         try {
-            Segment segment = loader.call();
             if (id.isBulkSegmentId()) {
-                return segment;
+                return loader.call();
             }
+        } catch (Exception e) {
+            throw new ExecutionException(e);
+        }
 
-            put(id, segment);
-            return segment;
+        // Load data segment and put it in the cache
+        try {
+            long t0 = System.nanoTime();
+            Segment segment = loader.call();
+            stats.loadSuccessCount.incrementAndGet();
+            stats.loadTime.addAndGet(System.nanoTime() - t0);
+            stats.missCount.incrementAndGet();
+
+            return put(id, segment);
         } catch (Exception e) {
+            stats.loadExceptionCount.incrementAndGet();
             throw new ExecutionException(e);
         }
     }
 
     /**
-     * Put a segment into the cache
+     * Put a segment into the cache. This method does nothing for
+     * {@link SegmentId#isBulkSegmentId() bulk} segments.
      * @param segment  the segment to cache
      */
     public void putSegment(@Nonnull Segment segment) {
-        SegmentId segmentId = segment.getSegmentId();
-        if (segmentId.isBulkSegmentId()) {
-            return;
+        SegmentId id = segment.getSegmentId();
+        if (!id.isBulkSegmentId()) {
+            put(id, segment);
         }
-
-        put(segmentId, segment);
     }
 
     /**
@@ -132,11 +164,75 @@ public class SegmentCache {
     }
 
     /**
-     * See the class comment regarding some peculiarities of this cache's 
statistics
-     * @return  statistics for this cache.
+     * @return  Statistics for this cache.
      */
     @Nonnull
-    public CacheStats getCacheStats() {
-        return new CacheStats(cache, "Segment Cache", weigher, maximumWeight);
+    public AbstractCacheStats getCacheStats() {
+        return stats;
+    }
+
+    /**
+     * Record a hit in this cache's underlying statistics.
+     * @see SegmentId#onAccess
+     */
+    public void recordHit() {
+        stats.hitCount.incrementAndGet();
+    }
+
+    /** We cannot rely on the statistics of the underlying Guava cache as all 
cache hits
+     * are taken by {@link SegmentId#getSegment()} and thus never seen by the 
cache.
+     */
+    private class Stats extends AbstractCacheStats {
+        @Nonnull
+        final AtomicLong currentWeight = new AtomicLong();
+
+        @Nonnull
+        final AtomicLong loadSuccessCount = new AtomicLong();
+
+        @Nonnull
+        final AtomicInteger loadExceptionCount = new AtomicInteger();
+
+        @Nonnull
+        final AtomicLong loadTime = new AtomicLong();
+
+        @Nonnull
+        final AtomicLong evictionCount = new AtomicLong();
+
+        @Nonnull
+        final AtomicLong hitCount = new AtomicLong();
+
+        @Nonnull
+        final AtomicLong missCount = new AtomicLong();
+
+        protected Stats(@Nonnull String name) {
+            super(name);
+        }
+
+        @Override
+        protected com.google.common.cache.CacheStats getCurrentStats() {
+            return new com.google.common.cache.CacheStats(
+                    hitCount.get(),
+                    missCount.get(),
+                    loadSuccessCount.get(),
+                    loadExceptionCount.get(),
+                    loadTime.get(),
+                    evictionCount.get());
+        }
+
+        @Override
+        public long getElementCount() {
+            return cache.size();
+        }
+
+        @Override
+        public long getMaxTotalWeight() {
+            return maximumWeight;
+        }
+
+        @Override
+        public long estimateCurrentWeight() {
+            return currentWeight.get();
+        }
     }
+
 }
\ No newline at end of file

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
 Tue May  2 15:18:56 2017
@@ -63,6 +63,9 @@ public class SegmentId implements Compar
 
     private final long creationTime;
 
+    /** Callback called whenever an underlying and locally memoised segment is 
accessed */
+    private final Runnable onAccess;
+
     /**
      * The gc generation of this segment or -1 if unknown.
      */
@@ -80,14 +83,32 @@ public class SegmentId implements Compar
      */
     private volatile Segment segment;
 
-    public SegmentId(@Nonnull SegmentStore store, long msb, long lsb) {
+    /**
+     * Create a new segment id with access tracking.
+     * @param store  store this is belongs to
+     * @param msb    most significant bits of this id
+     * @param lsb    least significant bits of this id
+     * @param onAccess  callback called whenever an underlying and locally 
memoised segment is accessed.
+     */
+    public SegmentId(@Nonnull SegmentStore store, long msb, long lsb, @Nonnull 
Runnable onAccess) {
         this.store = store;
         this.msb = msb;
         this.lsb = lsb;
+        this.onAccess = onAccess;
         this.creationTime = System.currentTimeMillis();
     }
 
     /**
+     * Create a new segment id without access tracking.
+     * @param store  store this is belongs to
+     * @param msb    most significant bits of this id
+     * @param lsb    least significant bits of this id
+     */
+    public SegmentId(@Nonnull SegmentStore store, long msb, long lsb) {
+        this(store, msb, lsb, () -> {});
+    }
+
+    /**
      * Checks whether this is a data segment identifier.
      *
      * @return {@code true} for a data segment, {@code false} otherwise
@@ -128,10 +149,11 @@ public class SegmentId implements Compar
                 segment = this.segment;
                 if (segment == null) {
                     log.debug("Loading segment {}", this);
-                    segment = store.readSegment(this);
+                    return store.readSegment(this);
                 }
             }
         }
+        onAccess.run();
         return segment;
     }
 

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
 Tue May  2 15:18:56 2017
@@ -19,8 +19,6 @@
 package org.apache.jackrabbit.oak.segment.file;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.Maps.newHashMap;
 
 import java.io.Closeable;
 import java.io.File;
@@ -28,12 +26,9 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
@@ -122,7 +117,7 @@ public abstract class AbstractFileStore
         this.tracker = new SegmentTracker(new SegmentIdFactory() {
             @Override @Nonnull
             public SegmentId newSegmentId(long msb, long lsb) {
-                return new SegmentId(AbstractFileStore.this, msb, lsb);
+                return new SegmentId(AbstractFileStore.this, msb, lsb, 
segmentCache::recordHit);
             }
         });
         this.blobStore = builder.getBlobStore();

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
 Tue May  2 15:18:56 2017
@@ -27,8 +27,9 @@ import java.util.AbstractMap.SimpleImmut
 import java.util.Map.Entry;
 import java.util.UUID;
 
+import com.google.common.base.Supplier;
 import org.apache.commons.lang.RandomStringUtils;
-import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
 import org.apache.jackrabbit.oak.commons.StringUtils;
 import org.apache.jackrabbit.oak.segment.CacheWeights.StringCacheWeigher;
 import org.apache.jackrabbit.oak.segment.file.PriorityCache;
@@ -39,8 +40,6 @@ import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Supplier;
-
 /**
  * Test/Utility class to measure size in memory for common segment-tar objects.
  * <p>
@@ -320,7 +319,7 @@ public class CacheWeightsTest {
                     Segment segment = randomSegment(bufferSize);
                     cache.putSegment(segment);
                 }
-                CacheStats stats = cache.getCacheStats();
+                AbstractCacheStats stats = cache.getCacheStats();
                 long elements = stats.getElementCount();
                 long weight = stats.estimateCurrentWeight();
                 return new SimpleImmutableEntry<Object, Long[]>(cache,

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
 Tue May  2 15:18:56 2017
@@ -18,60 +18,60 @@
  */
 package org.apache.jackrabbit.oak.segment;
 
+import static org.apache.jackrabbit.oak.segment.SegmentStore.EMPTY_STORE;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
 import org.junit.Test;
 
 public class SegmentCacheTest {
-    @Test
-    public void putTest() {
-        SegmentId id = new SegmentId(mock(SegmentStore.class), -1, -1);
-        Segment segment = mock(Segment.class);
-        when(segment.getSegmentId()).thenReturn(id);
-        SegmentCache cache = new SegmentCache(1);
+    private final SegmentCache cache = new SegmentCache();
 
-        cache.putSegment(segment);
-        assertEquals(segment, id.getSegment());
+    private final SegmentId id1 = new SegmentId(EMPTY_STORE, -1, -1, 
cache::recordHit);
+    private final Segment segment1 = mock(Segment.class);
+    private final SegmentId id2 = new SegmentId(EMPTY_STORE, -1, -2, 
cache::recordHit);
+    private final Segment segment2 = mock(Segment.class);
+
+    {
+        when(segment1.getSegmentId()).thenReturn(id1);
+        when(segment2.getSegmentId()).thenReturn(id2);
     }
 
     @Test
-    public void invalidateTests() {
-        Segment segment1 = mock(Segment.class);
-        Segment segment2 = mock(Segment.class);
-        SegmentStore store = mock(SegmentStore.class);
-        SegmentId id = new SegmentId(store, -1, -1);
-        when(segment1.getSegmentId()).thenReturn(id);
-        SegmentCache cache = new SegmentCache(1);
+    public void putTest() throws ExecutionException {
+        cache.putSegment(segment1);
 
+        // Segment should be cached with the segmentId and thus not trigger a 
call
+        // to the (empty) node store.
+        assertEquals(segment1, cache.getSegment(id1, id1::getSegment));
+    }
+
+    @Test
+    public void invalidateTests() throws ExecutionException {
         cache.putSegment(segment1);
-        assertEquals(segment1, id.getSegment());
+        assertEquals(segment1, cache.getSegment(id1, id1::getSegment));
 
         // Clearing the cache should cause an eviction call back for id
         cache.clear();
 
-        // Check that this was the case by loading a different segment
-        when(store.readSegment(id)).thenReturn(segment2);
-        assertEquals(segment2, id.getSegment());
+        // Check that segment1 was evicted and needs reloading through the 
node store
+        AtomicBoolean cached = new AtomicBoolean(true);
+        assertEquals(segment1, cache.getSegment(id1, () -> {
+            cached.set(false);
+            return segment1;
+        }));
+        assertFalse(cached.get());
     }
 
     @Test
     public void statsTest() throws Exception {
-        Callable<Segment> loader = new Callable<Segment>() {
-
-            @Override
-            public Segment call() throws Exception {
-                return mock(Segment.class);
-            }
-        };
-
-        SegmentCache cache = new SegmentCache(1);
-        CacheStats stats = cache.getCacheStats();
-        SegmentId id = new SegmentId(mock(SegmentStore.class), -1, -1);
+        AbstractCacheStats stats = cache.getCacheStats();
 
         // empty cache
         assertEquals(0, stats.getElementCount());
@@ -81,27 +81,27 @@ public class SegmentCacheTest {
         assertEquals(0, stats.getRequestCount());
 
         // load
-        cache.getSegment(id, loader);
+        cache.getSegment(id1, () -> segment1);
         assertEquals(1, stats.getElementCount());
-        assertEquals(0, stats.getLoadCount());
+        assertEquals(1, stats.getLoadCount());
         assertEquals(0, stats.getHitCount());
-        assertEquals(0, stats.getMissCount());
-        assertEquals(0, stats.getRequestCount());
+        assertEquals(1, stats.getMissCount());
+        assertEquals(1, stats.getRequestCount());
 
         // cache hit
-        cache.getSegment(id, loader);
+        assertEquals(segment1, id1.getSegment());
         assertEquals(1, stats.getElementCount());
-        assertEquals(0, stats.getLoadCount());
-        assertEquals(0, stats.getHitCount());
-        assertEquals(0, stats.getMissCount());
-        assertEquals(0, stats.getRequestCount());
+        assertEquals(1, stats.getLoadCount());
+        assertEquals(1, stats.getHitCount());
+        assertEquals(1, stats.getMissCount());
+        assertEquals(2, stats.getRequestCount());
 
         cache.clear();
         assertEquals(0, stats.getElementCount());
-        assertEquals(0, stats.getLoadCount());
-        assertEquals(0, stats.getHitCount());
-        assertEquals(0, stats.getMissCount());
-        assertEquals(0, stats.getRequestCount());
+        assertEquals(1, stats.getLoadCount());
+        assertEquals(1, stats.getHitCount());
+        assertEquals(1, stats.getMissCount());
+        assertEquals(2, stats.getRequestCount());
 
         stats.resetStats();
         assertEquals(0, stats.getElementCount());


Reply via email to