diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/PersistentCacheStatsMBean.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/PersistentCacheStatsMBean.java new file mode 100644 index 0000000..9ce88c1 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/PersistentCacheStatsMBean.java @@ -0,0 +1,61 @@ +package org.apache.jackrabbit.oak.api.jmx; +/* + * 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. + */ + +import aQute.bnd.annotation.ProviderType; + +import javax.management.openmbean.CompositeData; + +@ProviderType +public interface PersistentCacheStatsMBean { + String TYPE = "PersistentCacheStats"; + + String getName(); + + long getRequestCount(); + + long getHitCount(); + + double getHitRate(); + + long getMissCount(); + + double getMissRate(); + + long getLoadCount(); + + long getLoadSuccessCount(); + + long getLoadExceptionCount(); + + double getLoadExceptionRate(); + + CompositeData getRequestRateHistory(); + + CompositeData getHitRateHistory(); + + CompositeData getLoadRateHistory(); + + CompositeData getLoadExceptionRateHistory(); + + CompositeData getHitPercentageHistory(); + + String cacheInfoAsString(); + +} + + diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java index b0f2b7b..2c85c00 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java @@ -15,7 +15,7 @@ * limitations under the License. */ -@Version("3.0.0") +@Version("3.1.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.api.jmx; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java index ba0d08c..047a89c 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java @@ -874,6 +874,10 @@ public class DocumentMK { return this; } + public StatisticsProvider getStatisticsProvider() { + return this.statisticsProvider; + } + @CheckForNull public BlobStoreStats getBlobStoreStats() { return blobStoreStats; @@ -990,7 +994,7 @@ public class DocumentMK { if (docNodeStore != null) { docNodeStore.setPersistentCache(p); } - cache = p.wrap(docNodeStore, docStore, cache, cacheType); + cache = p.wrap(docNodeStore, docStore, cache, cacheType, statisticsProvider); } return cache; } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java index 093d90f..2354f74 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java @@ -83,6 +83,7 @@ import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector; import org.apache.jackrabbit.oak.plugins.blob.ReferencedBlob; import org.apache.jackrabbit.oak.plugins.document.cache.CacheInvalidationStats; import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCacheStats; import org.apache.jackrabbit.oak.spi.blob.BlobStore; import org.apache.jackrabbit.oak.commons.json.JsopStream; import org.apache.jackrabbit.oak.commons.json.JsopWriter; @@ -302,6 +303,8 @@ public final class DocumentNodeStore private final Cache docChildrenCache; private final CacheStats docChildrenCacheStats; + private final Map persistentCacheStats; + /** * The change log to keep track of commits for diff operations. */ @@ -441,6 +444,19 @@ public final class DocumentNodeStore diffCache = builder.getDiffCache(); checkpoints = new Checkpoints(this); + // extract persistent cache stats + + PersistentCacheStats pcs; + persistentCacheStats = Maps.newHashMap(); + + for (Cache cache: new Cache[]{ nodeCache, nodeChildrenCache, docChildrenCache}) { + pcs = PersistentCache.getPersistentCacheStats(cache); + if (pcs != null) { + persistentCacheStats.put(pcs.getName(), pcs); + } + } + + // check if root node exists NodeDocument rootDoc = store.find(NODES, Utils.getIdFromPath("/")); if (rootDoc == null) { @@ -734,6 +750,11 @@ public final class DocumentNodeStore return diffCache.getStats(); } + @Nonnull + public Map getPersistentCacheStats() { + return this.persistentCacheStats; + } + void invalidateDocChildrenCache() { docChildrenCache.invalidateAll(); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java index e33f617..945361b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java @@ -56,6 +56,7 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean; import org.apache.jackrabbit.oak.api.jmx.CheckpointMBean; +import org.apache.jackrabbit.oak.api.jmx.PersistentCacheStatsMBean; import org.apache.jackrabbit.oak.cache.CacheStats; import org.apache.jackrabbit.oak.commons.PropertiesUtil; import org.apache.jackrabbit.oak.osgi.ObserverTracker; @@ -66,6 +67,7 @@ import org.apache.jackrabbit.oak.plugins.blob.BlobGarbageCollector; import org.apache.jackrabbit.oak.plugins.blob.BlobStoreStats; import org.apache.jackrabbit.oak.plugins.blob.SharedDataStore; import org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCacheStats; import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection; import org.apache.jackrabbit.oak.plugins.identifier.ClusterRepositoryInfo; import org.apache.jackrabbit.oak.spi.blob.BlobStore; @@ -641,6 +643,18 @@ public class DocumentNodeStoreService { ); } + // register persistent cache stats + Map persistenceCacheStats = store.getPersistentCacheStats(); + for (PersistentCacheStats pcs: persistenceCacheStats.values()) { + registrations.add( + registerMBean(whiteboard, + PersistentCacheStatsMBean.class, + pcs, + PersistentCacheStatsMBean.TYPE, + pcs.getName()) + ); + } + final long versionGcMaxAgeInSecs = toLong(prop(PROP_VER_GC_MAX_AGE), DEFAULT_VER_GC_MAX_AGE); final long blobGcMaxAgeInSecs = toLong(prop(PROP_BLOB_GC_MAX_AGE), DEFAULT_BLOB_GC_MAX_AGE); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java index 8903471..c12f03b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java @@ -27,6 +27,8 @@ import javax.annotation.Nullable; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache.GenerationCache; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.apache.jackrabbit.oak.stats.TimerStats.Context; import org.h2.mvstore.MVMap; import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.type.DataType; @@ -39,6 +41,7 @@ import com.google.common.collect.ImmutableMap; class NodeCache implements Cache, GenerationCache { private final PersistentCache cache; + private final PersistentCacheStats stats; private final Cache memCache; private final MultiGenerationMap map; private final CacheType type; @@ -49,7 +52,8 @@ class NodeCache implements Cache, GenerationCache { PersistentCache cache, Cache memCache, DocumentNodeStore docNodeStore, - DocumentStore docStore, CacheType type) { + DocumentStore docStore, CacheType type, + StatisticsProvider statisticsProvider) { this.cache = cache; this.memCache = memCache; this.type = type; @@ -57,6 +61,7 @@ class NodeCache implements Cache, GenerationCache { map = new MultiGenerationMap(); keyType = new KeyDataType(type); valueType = new ValueDataType(docNodeStore, docStore, type); + stats = new PersistentCacheStats(type, statisticsProvider); } @Override @@ -131,9 +136,16 @@ class NodeCache implements Cache, GenerationCache { if (value != null) { return value; } + value = readIfPresent((K) key); + + // persistent cache hit/miss if (value != null) { memCache.put((K) key, value); + stats.markHit(); + } + else { + stats.markMiss(); } return value; } @@ -142,12 +154,27 @@ class NodeCache implements Cache, GenerationCache { public V get(K key, Callable valueLoader) throws ExecutionException { + + // stats hit/miss counters covered by getIfPresent V value = getIfPresent(key); if (value != null) { return value; } + + // track entry load time + Context ctx = stats.startLoaderTimer(); + try { value = memCache.get(key, valueLoader); + } + catch (ExecutionException e) { + this.stats.markException(); + } + finally { + ctx.stop(); + } + write(key, value); + return value; } @@ -221,4 +248,8 @@ class NodeCache implements Cache, GenerationCache { writeWithoutBroadcast(key, value); } + public PersistentCacheStats getPersistentCacheStats() { + return stats; + } + } \ No newline at end of file diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java index e3a8ca9..1dbef7b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java @@ -32,6 +32,7 @@ import org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.InMe import org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.TCPBroadcaster; import org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.UDPBroadcaster; import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; import org.h2.mvstore.FileStore; import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap.Builder; @@ -359,6 +360,14 @@ public class PersistentCache implements Broadcaster.Listener { DocumentNodeStore docNodeStore, DocumentStore docStore, Cache base, CacheType type) { + return wrap(docNodeStore, docStore, base, type, StatisticsProvider.NOOP); + } + + public synchronized Cache wrap( + DocumentNodeStore docNodeStore, + DocumentStore docStore, + Cache base, CacheType type, + StatisticsProvider statisticsProvider) { boolean wrap; switch (type) { case NODE: @@ -385,7 +394,7 @@ public class PersistentCache implements Broadcaster.Listener { } if (wrap) { NodeCache c = new NodeCache(this, - base, docNodeStore, docStore, type); + base, docNodeStore, docStore, type, statisticsProvider); initGenerationCache(c); return c; } @@ -505,6 +514,15 @@ public class PersistentCache implements Broadcaster.Listener { buff.position(end); } + public static PersistentCacheStats getPersistentCacheStats(Cache cache) { + if (cache instanceof NodeCache) { + return ((NodeCache) cache).getPersistentCacheStats(); + } + else { + return null; + } + } + private void receiveMessage(ByteBuffer buff) { CacheType type = CacheType.VALUES[buff.get()]; GenerationCache cache = caches.get(type); @@ -523,7 +541,5 @@ public class PersistentCache implements Broadcaster.Listener { void receive(ByteBuffer buff); void removeGeneration(int oldReadGeneration); - } - } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCacheStats.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCacheStats.java new file mode 100644 index 0000000..392a8e1 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCacheStats.java @@ -0,0 +1,261 @@ +/* + * 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.plugins.document.persistentCache; + +import com.google.common.base.Objects; +import org.apache.jackrabbit.api.stats.TimeSeries; +import org.apache.jackrabbit.oak.api.jmx.PersistentCacheStatsMBean; +import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean; +import org.apache.jackrabbit.oak.stats.MeterStats; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.apache.jackrabbit.oak.stats.StatsOptions; +import org.apache.jackrabbit.oak.stats.TimerStats; +import org.apache.jackrabbit.stats.TimeSeriesStatsUtil; + +import javax.management.openmbean.CompositeData; + +/** + * Persistence Cache Statistics. + */ +public class PersistentCacheStats extends AnnotatedStandardMBean implements PersistentCacheStatsMBean { + + private final StatisticsProvider statisticsProvider; + + private final String cacheName; + + private final MeterStats hitMeter; + private final TimeSeries hitRateHistory; + + private final MeterStats requestMeter; + private final TimeSeries requestRateHistory; + + private final MeterStats loadExceptionMeter; + private final TimeSeries loadExceptionRateHistory; + + private final TimerStats loadTimer; + private final TimeSeries loadRateHistory; + + private final TimeSeries hitPercentageHistory; + + public PersistentCacheStats(CacheType cacheType, StatisticsProvider statisticsProvider) { + super(PersistentCacheStatsMBean.class); + + if (statisticsProvider == null) { + this.statisticsProvider = StatisticsProvider.NOOP; + } else { + this.statisticsProvider = statisticsProvider; + } + + // Configure cache name + this.cacheName = cacheType.name(); + + // Fetch stats and time series + String statName; + + statName = getStatName("hits", cacheName); + this.hitMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT); + this.hitRateHistory = getTimeSeries(statName); + + statName = getStatName("requests", cacheName); + this.requestMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT); + this.requestRateHistory = getTimeSeries(statName); + + statName = getStatName("loadTime", cacheName); + this.loadTimer = statisticsProvider.getTimer(statName, StatsOptions.DEFAULT); + this.loadRateHistory = getTimeSeries(statName); + + statName = getStatName("loadExceptions", cacheName); + this.loadExceptionMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT); + this.loadExceptionRateHistory = getTimeSeries(statName); + + this.hitPercentageHistory = new PercentageTimeSeries(hitRateHistory, requestRateHistory); + } + + public void markHit() { + this.hitMeter.mark(); + this.requestMeter.mark(); + } + + public void markMiss() { + this.requestMeter.mark(); + } + + public void markException() { + this.loadExceptionMeter.mark(); + } + + public TimerStats.Context startLoaderTimer() { + return this.loadTimer.time(); + } + + //~--------------------------------------< private helpers > + + private static String getStatName(String meter, String cacheName) { + return PersistentCacheStats.class.getCanonicalName() + "." + cacheName + "." + meter; + } + + private TimeSeries getTimeSeries(String name) { + return statisticsProvider.getStats().getTimeSeries(name, true); + } + + private static class PercentageTimeSeries implements TimeSeries { + + private TimeSeries hit, total; + + PercentageTimeSeries(TimeSeries hit, TimeSeries total) { + this.hit = hit; + this.total = total; + } + @Override + public long[] getValuePerSecond() { + return percentage(hit.getValuePerSecond(), total.getValuePerSecond()); + } + + @Override + public long[] getValuePerMinute() { + return percentage(hit.getValuePerMinute(), total.getValuePerMinute()); + } + + @Override + public long[] getValuePerHour() { + return percentage(hit.getValuePerHour(), total.getValuePerHour()); + } + + @Override + public long[] getValuePerWeek() { + return percentage(hit.getValuePerWeek(), total.getValuePerWeek()); + } + + @Override + public long getMissingValue() { + return 0; + } + + private long[] percentage(long[] a, long[] b) { + long[] result = new long[a.length]; + for (int i=0; i + + @Override + public String getName() { + return cacheName; + } + + @Override + public long getRequestCount() { + return requestMeter.getCount(); + } + + @Override + public long getHitCount() { + return hitMeter.getCount(); + } + + @Override + public double getHitRate() { + if (requestMeter.getCount() == 0) { + return 0; + } + return (double)hitMeter.getCount() / requestMeter.getCount(); + } + + @Override + public long getMissCount() { + return requestMeter.getCount() - hitMeter.getCount(); + } + + @Override + public double getMissRate() { + if (requestMeter.getCount() == 0) { + return 0; + } + return (double)getMissCount() / requestMeter.getCount(); + } + + @Override + public long getLoadCount() { + return loadTimer.getCount(); + } + + @Override + public long getLoadSuccessCount() { + return loadTimer.getCount() - loadExceptionMeter.getCount(); + } + + @Override + public long getLoadExceptionCount() { + return loadExceptionMeter.getCount(); + } + + @Override + public double getLoadExceptionRate() { + if (requestMeter.getCount() == 0) { + return 0; + } + return (double)loadExceptionMeter.getCount() / loadTimer.getCount(); + } + + @Override + public CompositeData getRequestRateHistory() { + return TimeSeriesStatsUtil.asCompositeData(requestRateHistory, "Persistent cache requests"); + } + + @Override + public CompositeData getHitRateHistory() { + return TimeSeriesStatsUtil.asCompositeData(hitRateHistory, "Persistent cache hits"); + } + + @Override + public CompositeData getLoadRateHistory() { + return TimeSeriesStatsUtil.asCompositeData(loadRateHistory, "Persistent cache loads/misses"); + } + + @Override + public CompositeData getLoadExceptionRateHistory() { + return TimeSeriesStatsUtil.asCompositeData(loadExceptionRateHistory, "Persistent cache load exceptions"); + } + + @Override + public CompositeData getHitPercentageHistory() { + return TimeSeriesStatsUtil.asCompositeData(hitPercentageHistory, "Persistent cache hit percentage"); + } + + @Override + public String cacheInfoAsString() { + return Objects.toStringHelper("PersistentCacheStats") + .add("requestCount", getRequestCount()) + .add("hitCount", getHitCount()) + .add("hitRate", String.format("%1.2f", getHitRate())) + .add("missCount", getMissCount()) + .add("missRate", String.format("%1.2f", getMissRate())) + .add("loadCount", getLoadCount()) + .add("loadSuccessCount", getLoadSuccessCount()) + .add("loadExceptionCount", getLoadExceptionCount()) + .toString(); + } +}