You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2016/07/26 13:49:20 UTC

svn commit: r1754128 - in /jackrabbit/oak/trunk/oak-segment-tar/src: main/java/org/apache/jackrabbit/oak/segment/ main/java/org/apache/jackrabbit/oak/segment/file/ test/java/org/apache/jackrabbit/oak/segment/

Author: mduerig
Date: Tue Jul 26 13:49:20 2016
New Revision: 1754128

URL: http://svn.apache.org/viewvc?rev=1754128&view=rev
Log:
OAK-4277: Finalise de-duplication caches
Monitoring for string and template deduplication caches

Added:
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCacheStats.java
Modified:
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCache.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCache.java?rev=1754128&r1=1754127&r2=1754128&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCache.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCache.java Tue Jul 26 13:49:20 2016
@@ -26,9 +26,10 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 import com.google.common.base.Supplier;
+import com.google.common.cache.CacheStats;
 
 // FIXME OAK-4277: Finalise de-duplication caches
-// implement configuration, monitoring and management
+// implement configuration
 /**
  * Partial mapping of keys of type {@code T} to values of type {@link RecordId}. This is
  * typically used for de-duplicating values that have already been persisted and thus
@@ -36,17 +37,33 @@ import com.google.common.base.Supplier;
  * @param <T>
  */
 public abstract class RecordCache<T> {
+    private long hitCount;
+    private long missCount;
+    private long loadCount;
+    private long evictionCount;
 
     /**
      * Add a mapping from {@code key} to {@code value}. Any existing mapping is replaced.
      */
-    public abstract void put(T key, RecordId value);
+    public abstract void put(@Nonnull T key, @Nonnull RecordId value);
 
     /**
      * @return  The mapping for {@code key}, or {@code null} if none.
      */
     @CheckForNull
-    public abstract RecordId get(T key);
+    public abstract RecordId get(@Nonnull T key);
+
+    /**
+     * @return number of mappings
+     */
+    public abstract long size();
+
+    /**
+     * @return  access statistics for this cache
+     */
+    public CacheStats getStats() {
+        return new CacheStats(hitCount, missCount, loadCount, 0, 0, evictionCount);
+    }
 
     /**
      * Factory method for creating {@code RecordCache} instances. The returned
@@ -89,10 +106,18 @@ public abstract class RecordCache<T> {
         }
 
         @Override
-        public synchronized void put(T key, RecordId value) { }
+        public synchronized void put(@Nonnull T key, @Nonnull RecordId value) { }
 
         @Override
-        public synchronized RecordId get(T key) { return null; }
+        public synchronized RecordId get(@Nonnull T key) {
+            super.missCount++;
+            return null;
+        }
+
+        @Override
+        public long size() {
+            return 0;
+        }
     }
 
     private static class Default<T> extends RecordCache<T> {
@@ -111,19 +136,35 @@ public abstract class RecordCache<T> {
             records = new LinkedHashMap<T, RecordId>(size * 4 / 3, 0.75f, true) {
                 @Override
                 protected boolean removeEldestEntry(Map.Entry<T, RecordId> eldest) {
-                    return size() > size;
+                    boolean remove = super.size() > size;
+                    if (remove) {
+                        Default.super.evictionCount++;
+                    }
+                    return remove;
                 }
             };
         }
 
         @Override
-        public synchronized void put(T key, RecordId value) {
+        public synchronized void put(@Nonnull T key, @Nonnull RecordId value) {
+            super.loadCount++;
             records.put(key, value);
         }
 
         @Override
-        public synchronized RecordId get(T key) {
-            return records.get(key);
+        public synchronized RecordId get(@Nonnull T key) {
+            RecordId value = records.get(key);
+            if (value == null) {
+                super.missCount++;
+            } else {
+                super.hitCount++;
+            }
+            return value;
+        }
+
+        @Override
+        public synchronized long size() {
+            return records.size();
         }
     }
 }

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCacheStats.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCacheStats.java?rev=1754128&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCacheStats.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/RecordCacheStats.java Tue Jul 26 13:49:20 2016
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.segment;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.apache.jackrabbit.oak.commons.IOUtils.humanReadableByteCount;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheStats;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
+import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
+
+/**
+ * Statistics for {@link RecordCache}.
+ */
+public class RecordCacheStats extends AnnotatedStandardMBean implements CacheStatsMBean {
+
+    @Nonnull
+    private final String name;
+
+    @Nonnull
+    private final Supplier<CacheStats> stats;
+
+    @Nonnull
+    private final Supplier<Long> elementCount;
+
+    private CacheStats lastSnapshot;
+
+    public RecordCacheStats(
+            @Nonnull String name, @Nonnull Supplier<CacheStats> stats, @Nonnull Supplier<Long> elementCount) {
+        super(CacheStatsMBean.class);
+        this.name = checkNotNull(name);
+        this.stats = checkNotNull(stats);
+        this.elementCount = checkNotNull(elementCount);
+        this.lastSnapshot = stats.get();
+    }
+
+    private CacheStats stats() {
+        return stats.get().minus(lastSnapshot);
+    }
+
+    @Override
+    public synchronized void resetStats() {
+        lastSnapshot = stats.get();
+    }
+
+    @Nonnull
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public long getRequestCount() {
+        return stats().requestCount();
+    }
+
+    @Override
+    public long getHitCount() {
+        return stats().hitCount();
+    }
+
+    @Override
+    public double getHitRate() {
+        return stats().hitRate();
+    }
+
+    @Override
+    public long getMissCount() {
+        return stats().missCount();
+    }
+
+    @Override
+    public double getMissRate() {
+        return stats().missRate();
+    }
+
+    @Override
+    public long getLoadCount() {
+        return stats().loadCount();
+    }
+
+    @Override
+    public long getLoadSuccessCount() {
+        return stats().loadSuccessCount();
+    }
+
+    @Override
+    public long getLoadExceptionCount() {
+        return stats().loadExceptionCount();
+    }
+
+    @Override
+    public double getLoadExceptionRate() {
+        return stats().loadExceptionRate();
+    }
+
+    @Override
+    public long getTotalLoadTime() {
+        return stats().totalLoadTime();
+    }
+
+    @Override
+    public double getAverageLoadPenalty() {
+        return stats().averageLoadPenalty();
+    }
+
+    @Override
+    public long getEvictionCount() {
+        return stats().evictionCount();
+    }
+
+    @Override
+    public long getElementCount() {
+        return elementCount.get();
+    }
+
+    @Override
+    public long getMaxTotalWeight() {
+        return -1;
+    }
+
+    @Override
+    public long estimateCurrentWeight() {
+        return -1;
+    }
+
+    @Override
+    public String cacheInfoAsString() {
+        return Objects.toStringHelper("CacheStats(" + name + ")")
+            .add("hitCount", getHitCount())
+            .add("hitRate", format("%1.2f", getHitRate()))
+            .add("missCount", getMissCount())
+            .add("missRate", format("%1.2f", getMissRate()))
+            .add("requestCount", getRequestCount())
+            .add("loadCount", getLoadCount())
+            .add("loadSuccessCount", getLoadSuccessCount())
+            .add("loadExceptionCount", getLoadExceptionCount())
+            .add("totalLoadTime", format("%d s", NANOSECONDS.toSeconds(getTotalLoadTime())))
+            .add("averageLoadPenalty ", format("%1.2f ns", getAverageLoadPenalty()))
+            .add("evictionCount", getEvictionCount())
+            .add("elementCount", getElementCount())
+            .add("totalWeight", humanReadableByteCount(estimateCurrentWeight()))
+            .add("maxWeight", humanReadableByteCount(getMaxTotalWeight()))
+            .toString();
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java?rev=1754128&r1=1754127&r2=1754128&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java Tue Jul 26 13:49:20 2016
@@ -38,9 +38,11 @@ import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.felix.scr.annotations.Activate;
@@ -240,21 +242,11 @@ public class SegmentNodeStoreService ext
 
     private ServiceRegistration storeRegistration;
     private ServiceRegistration providerRegistration;
-    private Registration checkpointRegistration;
-    private Registration revisionGCRegistration;
-    private Registration blobGCRegistration;
-    private Registration gcOptionsRegistration;
-    private Registration segmentCacheMBean;
-    private Registration stringCacheMBean;
-    private Registration fsgcMonitorMBean;
-    private Registration fileStoreStatsMBean;
+
+    private final List<Registration> registrations = new ArrayList<>();
     private WhiteboardExecutor executor;
     private boolean customBlobStore;
 
-    private Registration discoveryLiteDescriptorRegistration;
-
-    private Registration clusterIdDescriptorRegistration;
-
     /**
      * Blob modified before this time duration would be considered for Blob GC
      */
@@ -367,42 +359,62 @@ public class SegmentNodeStoreService ext
 
         // Expose an MBean to provide information about the gc options
 
-        gcOptionsRegistration = registerMBean(
+        registrations.add(registerMBean(
                 whiteboard,
                 SegmentRevisionGC.class,
                 new SegmentRevisionGCMBean(gcOptions),
                 SegmentRevisionGC.TYPE,
                 "Segment node store gc options"
-        );
+        ));
 
         // Expose stats about the segment cache
 
-        CacheStats segmentCacheStats = store.getSegmentCacheStats();
-         segmentCacheMBean = registerMBean(
+        CacheStatsMBean segmentCacheStats = store.getSegmentCacheStats();
+        registrations.add(registerMBean(
                 whiteboard,
                 CacheStatsMBean.class,
                 segmentCacheStats,
                 CacheStats.TYPE,
                 segmentCacheStats.getName()
-        );
+        ));
 
         // Expose stats about the string and template caches
 
-        CacheStats stringCacheStats = store.getStringCacheStats();
-        stringCacheMBean = registerMBean(
+        CacheStatsMBean stringCacheStats = store.getStringCacheStats();
+        registrations.add(registerMBean(
                 whiteboard,
                 CacheStatsMBean.class,
                 stringCacheStats,CacheStats.TYPE,
                 stringCacheStats.getName()
-        );
+        ));
 
-        CacheStats templateCacheStats = store.getTemplateCacheStats();
-        stringCacheMBean = registerMBean(
+        CacheStatsMBean templateCacheStats = store.getTemplateCacheStats();
+        registrations.add(registerMBean(
                 whiteboard,
                 CacheStatsMBean.class,
                 templateCacheStats,CacheStats.TYPE,
                 templateCacheStats.getName()
-        );
+        ));
+
+        CacheStatsMBean stringDeduplicationCacheStats = store.getStringDeduplicationCacheStats();
+        if (stringDeduplicationCacheStats != null) {
+            registrations.add(registerMBean(
+            whiteboard,
+            CacheStatsMBean.class,
+            stringDeduplicationCacheStats,CacheStats.TYPE,
+            stringDeduplicationCacheStats.getName()
+            ));
+        }
+
+        CacheStatsMBean templateDeduplicationCacheStats = store.getTemplateDeduplicationCacheStats();
+        if (templateDeduplicationCacheStats != null) {
+            registrations.add(registerMBean(
+            whiteboard,
+            CacheStatsMBean.class,
+            templateDeduplicationCacheStats,CacheStats.TYPE,
+            templateDeduplicationCacheStats.getName()
+            ));
+        }
 
         // Listen for Executor services on the whiteboard
 
@@ -420,29 +432,29 @@ public class SegmentNodeStoreService ext
 
         };
 
-        revisionGCRegistration = registerMBean(
+        registrations.add(registerMBean(
                 whiteboard,
                 RevisionGCMBean.class,
                 new RevisionGC(triggerGarbageCollection, executor),
                 RevisionGCMBean.TYPE,
                 "Segment node store revision garbage collection"
-        );
+        ));
 
         // Expose statistics about the FileStore
 
-        fileStoreStatsMBean = registerMBean(
+        registrations.add(registerMBean(
                 whiteboard,
                 FileStoreStatsMBean.class,
                 store.getStats(),
                 FileStoreStatsMBean.TYPE,
                 "FileStore statistics"
-        );
+        ));
 
         // Register a monitor for the garbage collection of the FileStore
 
         FileStoreGCMonitor fsgcm = new FileStoreGCMonitor(Clock.SIMPLE);
 
-        fsgcMonitorMBean = new CompositeRegistration(
+        registrations.add((new CompositeRegistration(
                 whiteboard.register(GCMonitor.class, fsgcm, emptyMap()),
                 registerMBean(
                         whiteboard,
@@ -452,7 +464,7 @@ public class SegmentNodeStoreService ext
                         "File Store garbage collection monitor"
                 ),
                 scheduleWithFixedDelay(whiteboard, fsgcm, 1)
-        );
+        )));
 
         // Register a factory service to expose the FileStore
 
@@ -489,8 +501,8 @@ public class SegmentNodeStoreService ext
         observerTracker = new ObserverTracker(segmentNodeStore);
         observerTracker.start(context.getBundleContext());
 
-        checkpointRegistration = registerMBean(whiteboard, CheckpointMBean.class, new SegmentCheckpointMBean(segmentNodeStore),
-                CheckpointMBean.TYPE, "Segment node store checkpoint management");
+        registrations.add(registerMBean(whiteboard, CheckpointMBean.class, new SegmentCheckpointMBean(segmentNodeStore),
+                CheckpointMBean.TYPE, "Segment node store checkpoint management"));
 
         // ensure a clusterId is initialized
         // and expose it as 'oak.clusterid' repository descriptor
@@ -498,18 +510,18 @@ public class SegmentNodeStoreService ext
         clusterIdDesc.put(ClusterRepositoryInfo.OAK_CLUSTERID_REPOSITORY_DESCRIPTOR_KEY,
                 new SimpleValueFactory().createValue(
                         ClusterRepositoryInfo.getOrCreateId(segmentNodeStore)), true, false);
-        clusterIdDescriptorRegistration = whiteboard.register(
+        registrations.add(whiteboard.register(
                 Descriptors.class,
                 clusterIdDesc,
                 Collections.emptyMap()
-        );
+        ));
 
         // Register "discovery lite" descriptors
-        discoveryLiteDescriptorRegistration = whiteboard.register(
+        registrations.add(whiteboard.register(
                 Descriptors.class,
                 new SegmentDiscoveryLiteDescriptors(segmentNodeStore),
                 Collections.emptyMap()
-        );
+        ));
 
         // If a shared data store register the repo id in the data store
         String repoId = "";
@@ -532,13 +544,13 @@ public class SegmentNodeStoreService ext
                     repoId
             );
 
-            blobGCRegistration = registerMBean(
+            registrations.add(registerMBean(
                     whiteboard,
                     BlobGCMBean.class,
                     new BlobGC(gc, executor),
                     BlobGCMBean.TYPE,
                     "Segment node store blob garbage collection"
-            );
+            ));
         }
 
         log.info("SegmentNodeStore initialized");
@@ -546,22 +558,7 @@ public class SegmentNodeStoreService ext
     }
 
     private void unregisterNodeStore() {
-        if (discoveryLiteDescriptorRegistration != null) {
-            discoveryLiteDescriptorRegistration.unregister();
-            discoveryLiteDescriptorRegistration = null;
-        }
-        if (clusterIdDescriptorRegistration != null) {
-            clusterIdDescriptorRegistration.unregister();
-            clusterIdDescriptorRegistration = null;
-        }
-        if (segmentCacheMBean != null) {
-            segmentCacheMBean.unregister();
-            segmentCacheMBean = null;
-        }
-        if (stringCacheMBean != null) {
-            stringCacheMBean.unregister();
-            stringCacheMBean = null;
-        }
+        new CompositeRegistration(registrations).unregister();
         if (providerRegistration != null) {
             providerRegistration.unregister();
             providerRegistration = null;
@@ -570,30 +567,6 @@ public class SegmentNodeStoreService ext
             storeRegistration.unregister();
             storeRegistration = null;
         }
-        if (checkpointRegistration != null) {
-            checkpointRegistration.unregister();
-            checkpointRegistration = null;
-        }
-        if (revisionGCRegistration != null) {
-            revisionGCRegistration.unregister();
-            revisionGCRegistration = null;
-        }
-        if (blobGCRegistration != null) {
-            blobGCRegistration.unregister();
-            blobGCRegistration = null;
-        }
-        if (gcOptionsRegistration != null) {
-            gcOptionsRegistration.unregister();
-            gcOptionsRegistration = null;
-        }
-        if (fsgcMonitorMBean != null) {
-            fsgcMonitorMBean.unregister();
-            fsgcMonitorMBean = null;
-        }
-        if (fileStoreStatsMBean != null) {
-            fileStoreStatsMBean.unregister();
-            fileStoreStatsMBean = null;
-        }
         if (executor != null) {
             executor.stop();
             executor = null;

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java?rev=1754128&r1=1754127&r2=1754128&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java Tue Jul 26 13:49:20 2016
@@ -64,6 +64,7 @@ import org.apache.commons.math3.stat.des
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
 import org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState;
 import org.apache.jackrabbit.oak.segment.WriteOperationHandler.WriteOperation;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
@@ -132,6 +133,22 @@ public class SegmentWriter {
     }
 
     /**
+     * @return  Statistics for the string deduplication cache or {@code null} if not available.
+     */
+    @CheckForNull
+    public CacheStatsMBean getStringCacheStats() {
+        return cacheManager.getStringCacheStats();
+    }
+
+    /**
+     * @return  Statistics for the template deduplication cache or {@code null} if not available.
+     */
+    @CheckForNull
+    public CacheStatsMBean getTemplateCacheStats() {
+        return cacheManager.getTemplateCacheStats();
+    }
+
+    /**
      * Write a map record.
      * @param base      base map relative to which the {@code changes} are applied ot
      *                  {@code null} for the empty map.

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java?rev=1754128&r1=1754127&r2=1754128&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java Tue Jul 26 13:49:20 2016
@@ -21,6 +21,7 @@ package org.apache.jackrabbit.oak.segmen
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Suppliers.memoize;
+import static com.google.common.collect.Iterators.transform;
 import static com.google.common.collect.Maps.newConcurrentMap;
 import static java.lang.Integer.getInteger;
 import static org.apache.jackrabbit.oak.segment.RecordCache.newRecordCache;
@@ -28,15 +29,20 @@ import static org.apache.jackrabbit.oak.
 import java.util.Iterator;
 import java.util.concurrent.ConcurrentMap;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
+import com.google.common.cache.CacheStats;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
 
 // FIXME OAK-4277: Finalise de-duplication caches
 // implement configuration, monitoring and management
 // add unit tests
-// document, nullability
+// document
 public abstract class WriterCacheManager {
     private static final int DEFAULT_STRING_CACHE_SIZE = getInteger(
             "oak.tar.stringsCacheSize", 15000);
@@ -53,6 +59,22 @@ public abstract class WriterCacheManager
     @Nonnull
     public abstract NodeCache getNodeCache(int generation);
 
+    /**
+     * @return  statistics for the string cache or {@code null} if not available.
+     */
+    @CheckForNull
+    public CacheStatsMBean getStringCacheStats() {
+        return null;
+    }
+
+    /**
+     * @return  statistics for the template cache or {@code null} if not available.
+     */
+    @CheckForNull
+    public CacheStatsMBean getTemplateCacheStats() {
+        return null;
+    }
+
     public static class Empty extends WriterCacheManager {
         public static final WriterCacheManager INSTANCE = new Empty();
 
@@ -83,27 +105,27 @@ public abstract class WriterCacheManager
          * Cache of recently stored string records, used to avoid storing duplicates
          * of frequently occurring data.
          */
-        private final Generation<RecordCache<String>> stringCaches;
+        private final Generations<RecordCache<String>> stringCaches;
 
         /**
          * Cache of recently stored template records, used to avoid storing
          * duplicates of frequently occurring data.
          */
-        private final Generation<RecordCache<Template>> templateCaches;
+        private final Generations<RecordCache<Template>> templateCaches;
 
         /**
          * Cache of recently stored nodes to avoid duplicating linked nodes (i.e. checkpoints)
          * during compaction.
          */
-        private final Generation<NodeCache> nodeCaches;
+        private final Generations<NodeCache> nodeCaches;
 
         public Default(
                 @Nonnull Supplier<RecordCache<String>> stringCacheFactory,
                 @Nonnull Supplier<RecordCache<Template>> templateCacheFactory,
                 @Nonnull Supplier<NodeCache> nodeCacheFactory) {
-            this.stringCaches = new Generation<>(stringCacheFactory);
-            this.templateCaches = new Generation<>(templateCacheFactory);
-            this.nodeCaches = new Generation<>(nodeCacheFactory);
+            this.stringCaches = new Generations<>(stringCacheFactory);
+            this.templateCaches = new Generations<>(templateCacheFactory);
+            this.nodeCaches = new Generations<>(nodeCacheFactory);
         }
 
         public Default() {
@@ -112,11 +134,11 @@ public abstract class WriterCacheManager
                  NodeCache.factory(1000000, 20));
         }
 
-        private static class Generation<T> {
+        private static class Generations<T> implements Iterable<T> {
             private final ConcurrentMap<Integer, Supplier<T>> generations = newConcurrentMap();
             private final Supplier<T> cacheFactory;
 
-            Generation(@Nonnull Supplier<T> cacheFactory) {
+            Generations(@Nonnull Supplier<T> cacheFactory) {
                 this.cacheFactory = checkNotNull(cacheFactory);
             }
 
@@ -128,6 +150,16 @@ public abstract class WriterCacheManager
                 return generations.get(generation).get();
             }
 
+            @Override
+            public Iterator<T> iterator() {
+                return transform(generations.values().iterator(), new Function<Supplier<T>, T>() {
+                    @Nullable @Override
+                    public T apply(Supplier<T> cacheFactory) {
+                        return cacheFactory.get();
+                    }
+                });
+            }
+
             void evictGenerations(@Nonnull Predicate<Integer> evict) {
                 Iterator<Integer> it = generations.keySet().iterator();
                 while (it.hasNext()) {
@@ -156,6 +188,48 @@ public abstract class WriterCacheManager
             return nodeCaches.getGeneration(generation);
         }
 
+        @CheckForNull
+        @Override
+        public CacheStatsMBean getStringCacheStats() {
+            return new RecordCacheStats("String deduplication cache stats",
+                    accumulateStats(stringCaches), accumulateSizes(stringCaches));
+        }
+
+        @CheckForNull
+        @Override
+        public CacheStatsMBean getTemplateCacheStats() {
+            return new RecordCacheStats("Template deduplication cache stats",
+                    accumulateStats(templateCaches), accumulateSizes(templateCaches));
+        }
+
+        @Nonnull
+        private static <T> Supplier<CacheStats> accumulateStats(final Iterable<RecordCache<T>> caches) {
+            return new Supplier<CacheStats>() {
+                @Override
+                public CacheStats get() {
+                    CacheStats stats = new CacheStats(0, 0, 0, 0, 0, 0);
+                    for (RecordCache<?> cache : caches) {
+                        stats = stats.plus(cache.getStats());
+                    }
+                    return stats;
+                }
+            };
+        }
+
+        @Nonnull
+        public static <T> Supplier<Long> accumulateSizes(final Generations<RecordCache<T>> caches) {
+            return new Supplier<Long>() {
+                @Override
+                public Long get() {
+                    long size = 0;
+                    for (RecordCache<?> cache : caches) {
+                        size += cache.size();
+                    }
+                    return size;
+                }
+            };
+        }
+
         protected final void evictCaches(Predicate<Integer> generations) {
             stringCaches.evictGenerations(generations);
             templateCaches.evictGenerations(generations);

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java?rev=1754128&r1=1754127&r2=1754128&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java Tue Jul 26 13:49:20 2016
@@ -73,7 +73,7 @@ import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Stopwatch;
 import com.google.common.base.Supplier;
-import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
 import org.apache.jackrabbit.oak.plugins.blob.ReferenceCollector;
 import org.apache.jackrabbit.oak.segment.BinaryReferenceConsumer;
 import org.apache.jackrabbit.oak.segment.CachingSegmentReader;
@@ -400,20 +400,30 @@ public class FileStore implements Segmen
     }
 
     @Nonnull
-    public CacheStats getSegmentCacheStats() {
+    public CacheStatsMBean getSegmentCacheStats() {
         return segmentCache.getCacheStats();
     }
 
     @Nonnull
-    public CacheStats getStringCacheStats() {
+    public CacheStatsMBean getStringCacheStats() {
         return segmentReader.getStringCacheStats();
     }
 
     @Nonnull
-    public CacheStats getTemplateCacheStats() {
+    public CacheStatsMBean getTemplateCacheStats() {
         return segmentReader.getTemplateCacheStats();
     }
 
+    @CheckForNull
+    public CacheStatsMBean getStringDeduplicationCacheStats() {
+        return segmentWriter.getStringCacheStats();
+    }
+
+    @CheckForNull
+    public CacheStatsMBean getTemplateDeduplicationCacheStats() {
+        return segmentWriter.getTemplateCacheStats();
+    }
+
     public void maybeCompact(boolean cleanup) throws IOException {
         gcListener.info("TarMK GC #{}: started", GC_COUNT.incrementAndGet());
 

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java?rev=1754128&r1=1754127&r2=1754128&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java Tue Jul 26 13:49:20 2016
@@ -34,6 +34,7 @@ import static java.util.concurrent.TimeU
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.defaultGCOptions;
 import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
 import static org.slf4j.helpers.MessageFormatter.arrayFormat;
 import static org.slf4j.helpers.MessageFormatter.format;
@@ -71,7 +72,7 @@ import com.google.common.util.concurrent
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
 import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
 import org.apache.jackrabbit.oak.plugins.commit.ConflictHook;
 import org.apache.jackrabbit.oak.plugins.commit.DefaultConflictHandler;
@@ -242,15 +243,23 @@ public class SegmentCompactionIT {
                 new ObjectName("IT:TYPE=Segment Revision GC")));
         registrations.add(registerMBean(fileStoreGCMonitor,
                 new ObjectName("IT:TYPE=GC Monitor")));
-        CacheStats segmentCacheStats = fileStore.getSegmentCacheStats();
+        CacheStatsMBean segmentCacheStats = fileStore.getSegmentCacheStats();
         registrations.add(registerMBean(segmentCacheStats,
                 new ObjectName("IT:TYPE=" + segmentCacheStats.getName())));
-        CacheStats stringCacheStats = fileStore.getStringCacheStats();
+        CacheStatsMBean stringCacheStats = fileStore.getStringCacheStats();
         registrations.add(registerMBean(stringCacheStats,
                 new ObjectName("IT:TYPE=" + stringCacheStats.getName())));
-        CacheStats templateCacheStats = fileStore.getTemplateCacheStats();
+        CacheStatsMBean templateCacheStats = fileStore.getTemplateCacheStats();
         registrations.add(registerMBean(templateCacheStats,
                 new ObjectName("IT:TYPE=" + templateCacheStats.getName())));
+        CacheStatsMBean stringDeduplicationCacheStats = fileStore.getStringDeduplicationCacheStats();
+        assertNotNull(stringDeduplicationCacheStats);
+        registrations.add(registerMBean(stringDeduplicationCacheStats,
+                new ObjectName("IT:TYPE=" + stringDeduplicationCacheStats.getName())));
+        CacheStatsMBean templateDeduplicationCacheStats = fileStore.getTemplateDeduplicationCacheStats();
+        assertNotNull(templateDeduplicationCacheStats);
+        registrations.add(registerMBean(templateDeduplicationCacheStats,
+                new ObjectName("IT:TYPE=" + templateDeduplicationCacheStats.getName())));
         mBeanRegistration = new CompositeRegistration(registrations);
     }