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 a94cc8a..71fc7cc 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 @@ -25,6 +25,7 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.sql.DataSource; @@ -512,6 +513,7 @@ public class DocumentMK { private String persistentCacheURI = DEFAULT_PERSISTENT_CACHE_URI; private PersistentCache persistentCache; private LeaseFailureHandler leaseFailureHandler; + private DocumentStoreStats documentStoreStats; public Builder() { } @@ -851,6 +853,16 @@ public class DocumentMK { return this; } + public Builder setDocumentStoreStats(DocumentStoreStats stats) { + this.documentStoreStats = stats; + return this; + } + + @CheckForNull + public DocumentStoreStats getDocumentStoreStats() { + return this.documentStoreStats; + } + public Builder clock(Clock clock) { this.clock = clock; return this; 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 3169381..541fd79 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 @@ -77,6 +77,7 @@ import org.apache.jackrabbit.oak.spi.whiteboard.Registration; import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor; import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; @@ -254,6 +255,13 @@ public class DocumentNodeStoreService { ) private volatile DataSource blobDataSource; + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, + policy = ReferencePolicy.DYNAMIC, + referenceInterface = StatisticsProvider.class + ) + private volatile StatisticsProvider statisticsProvider; + private DocumentStoreStats docstoreStats = new DocumentStoreStats(); + private DocumentMK mk; private ObserverTracker observerTracker; private ComponentContext context; @@ -365,6 +373,7 @@ public class DocumentNodeStoreService { diffCachePercentage). setCacheSegmentCount(cacheSegmentCount). setCacheStackMoveDistance(cacheStackMoveDistance). + setDocumentStoreStats(this.docstoreStats). setLeaseCheck(true /* OAK-2739: enabled by default */). setLeaseFailureHandler(new LeaseFailureHandler() { @@ -539,6 +548,19 @@ public class DocumentNodeStoreService { unregisterNodeStore(); } + @SuppressWarnings("UnusedDeclaration") + protected void bindStatisticsProvider(StatisticsProvider provider) throws IOException { + log.info("Initializing DocumentStore with StatisticsProvider [{}]", provider); + this.docstoreStats.setStatisticsProvider(provider); + } + + @SuppressWarnings("UnusedDeclaration") + protected void unbindStatisticsProvider(StatisticsProvider provider) throws IOException { + log.info("Unbinding Statistics provider [{}]", provider); + this.docstoreStats.setStatisticsProvider(null); + } + + private void unregisterNodeStore() { deactivationTimestamp = System.currentTimeMillis(); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreStats.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreStats.java new file mode 100644 index 0000000..a771c9a --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreStats.java @@ -0,0 +1,86 @@ +/* + * 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; + +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.apache.jackrabbit.oak.stats.TimerStats; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; + +/** + * Document Store statistics helper class. + */ +public class DocumentStoreStats { + + private Map timers; + + /** + * All available DocumentStore metrics. + */ + public enum Metric { + FIND("find"), + QUERY("query"), + REMOVE("delete"), + CREATE("create"), + UPDATE("update"), + CREATE_OR_UPDATE("createOrUpdate"), + FIND_AND_MODIFY("findAndModify"); + + private String name; + + Metric(String metricName) { + this.name = metricName; + } + + public String getFullName() { + return DocumentStoreStats.class.getCanonicalName() + "." + name; + } + }; + + public DocumentStoreStats() { + this.timers = fetchTimers(StatisticsProvider.NOOP); + } + + public synchronized void setStatisticsProvider(StatisticsProvider provider) { + this.timers = fetchTimers(provider); + } + + @Nonnull + public TimerStats getTimer(Metric metric) { + return this.timers.get(metric); + } + + @Nonnull + private Map fetchTimers(StatisticsProvider provider) { + if (provider == null) { + provider = StatisticsProvider.NOOP; + } + + Map timerMap = new HashMap(); + String name; + TimerStats timer; + + for (Metric m: Metric.values()) { + name = m.getFullName(); + timer = provider.getTimer(name); + timerMap.put(m, timer); + } + return timerMap; + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java index 098dce9..7703c73 100755 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java @@ -74,6 +74,8 @@ import org.apache.jackrabbit.oak.plugins.document.cache.NodeDocumentCache; import org.apache.jackrabbit.oak.plugins.document.locks.NodeDocumentLocks; import org.apache.jackrabbit.oak.plugins.document.locks.StripedNodeDocumentLocks; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore; +import org.apache.jackrabbit.oak.plugins.document.DocumentStoreStats; +import org.apache.jackrabbit.oak.stats.TimerStats; import org.apache.jackrabbit.oak.util.OakVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -193,6 +195,8 @@ import com.google.common.hash.PrimitiveSink; */ public class RDBDocumentStore implements DocumentStore { + private DocumentStoreStats stats = new DocumentStoreStats(); + /** * Creates a {@linkplain RDBDocumentStore} instance using the provided * {@link DataSource}, {@link DocumentMK.Builder}, and {@link RDBOptions}. @@ -221,6 +225,7 @@ public class RDBDocumentStore implements DocumentStore { @Override public T find(final Collection collection, final String id, int maxCacheAge) { + // Profile only readDocumentUncached return readDocumentCached(collection, id, maxCacheAge); } @@ -234,12 +239,19 @@ public class RDBDocumentStore implements DocumentStore { @Override public List query(Collection collection, String fromKey, String toKey, String indexedProperty, long startValue, int limit) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.QUERY); + TimerStats.Context ctx = timer.time(); + try { List conditions = Collections.emptyList(); if (indexedProperty != null) { conditions = Collections.singletonList(new QueryCondition(indexedProperty, ">=", startValue)); } return internalQuery(collection, fromKey, toKey, EMPTY_KEY_PATTERN, conditions, limit); } + finally { + ctx.stop(); + } + } @Nonnull protected List query(Collection collection, String fromKey, String toKey, @@ -249,49 +261,99 @@ public class RDBDocumentStore implements DocumentStore { @Override public void remove(Collection collection, String id) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.REMOVE); + TimerStats.Context ctx = timer.time(); + try { delete(collection, id); invalidateCache(collection, id, true); } + finally { + ctx.stop(); + } + } @Override public void remove(Collection collection, List ids) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.REMOVE); + TimerStats.Context ctx = timer.time(); + try { for (String id : ids) { invalidateCache(collection, id, true); } delete(collection, ids); } + finally { + ctx.stop(); + } + } @Override public int remove(Collection collection, Map> toRemove) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.REMOVE); + TimerStats.Context ctx = timer.time(); + try { int num = delete(collection, toRemove); for (String id : toRemove.keySet()) { invalidateCache(collection, id, true); } return num; } + finally { + ctx.stop(); + } + } @Override public boolean create(Collection collection, List updateOps) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.CREATE); + TimerStats.Context ctx = timer.time(); + try { return internalCreate(collection, updateOps); } + finally { + ctx.stop(); + } + } @Override public void update(Collection collection, List keys, UpdateOp updateOp) { UpdateUtils.assertUnconditional(updateOp); + + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.UPDATE); + TimerStats.Context ctx = timer.time(); + try { internalUpdate(collection, keys, updateOp); } + finally { + ctx.stop(); + } + } @Override public T createOrUpdate(Collection collection, UpdateOp update) { UpdateUtils.assertUnconditional(update); + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.CREATE_OR_UPDATE); + TimerStats.Context ctx = timer.time(); + try { return internalCreateOrUpdate(collection, update, true, false); } + finally { + ctx.stop(); + } + } @Override public T findAndUpdate(Collection collection, UpdateOp update) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.FIND_AND_MODIFY); + TimerStats.Context ctx = timer.time(); + try { return internalCreateOrUpdate(collection, update, false, true); } + finally { + ctx.stop(); + } + } @Override public CacheInvalidationStats invalidateCache() { @@ -528,6 +590,11 @@ public class RDBDocumentStore implements DocumentStore { private void initialize(DataSource ds, DocumentMK.Builder builder, RDBOptions options) throws Exception { + this.stats = builder.getDocumentStoreStats(); + if (this.stats == null) { + this.stats = new DocumentStoreStats(); + } + this.tableMeta.put(Collection.NODES, new RDBTableMetaData(createTableName(options.getTablePrefix(), TABLEMAP.get(Collection.NODES)))); this.tableMeta.put(Collection.CLUSTER_NODES, @@ -1272,6 +1339,22 @@ public class RDBDocumentStore implements DocumentStore { @CheckForNull private T readDocumentUncached(Collection collection, String id, NodeDocument cachedDoc) { + TimerStats timer = this.stats.getTimer(DocumentStoreStats.Metric.FIND); + TimerStats.Context ctx = timer.time(); + try { + return readDocumentUncachedImpl(collection, id, cachedDoc); + } + finally { + ctx.stop(); + } + } + + /** + * The real read document action. This method should not be used. Use the readDocumentUncached wrapper that also + * profiles the method call. + */ + @CheckForNull + private T readDocumentUncachedImpl(Collection collection, String id, NodeDocument cachedDoc) { Connection connection = null; RDBTableMetaData tmd = getTable(collection); try { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/metric/StatisticsProviderFactory.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/metric/StatisticsProviderFactory.java index 102d532..b55cfc8 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/metric/StatisticsProviderFactory.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/metric/StatisticsProviderFactory.java @@ -66,6 +66,7 @@ public class StatisticsProviderFactory { "com.codahale.metrics.MetricRegistry"; @Property(value = TYPE_AUTO, options = { + @PropertyOption(name = TYPE_AUTO, value = TYPE_AUTO), @PropertyOption(name = TYPE_DEFAULT, value = TYPE_DEFAULT), @PropertyOption(name = TYPE_METRIC, value = TYPE_METRIC), @PropertyOption(name = TYPE_NONE, value = TYPE_NONE)}) diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreStatsTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreStatsTest.java new file mode 100644 index 0000000..ffa981b --- /dev/null +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreStatsTest.java @@ -0,0 +1,76 @@ +/* + * 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; + +import org.apache.jackrabbit.oak.stats.DefaultStatisticsProvider; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.junit.Test; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static org.junit.Assert.assertNotNull; + +public class DocumentStoreStatsTest { + + @Test + public void basicSetup() throws Exception { + DocumentStoreStats stats = new DocumentStoreStats(); + for(DocumentStoreStats.Metric m: DocumentStoreStats.Metric.values()) { + assertNotNull(stats.getTimer(m)); + } + } + + @Test + public void metricNames() throws Exception { + for (DocumentStoreStats.Metric m: DocumentStoreStats.Metric.values()) { + assertNotNull(m.getFullName()); + } + } + + @Test + public void switchStatisticsProvider() throws Exception { + DocumentStoreStats stats = new DocumentStoreStats(); + + // default NOOP StatisticsProvider is used + for(DocumentStoreStats.Metric m: DocumentStoreStats.Metric.values()) { + assertNotNull(stats.getTimer(m)); + } + + // use DefaultStatisticsProvider + StatisticsProvider provider = createDefaultStatisticsProvider(); + + stats.setStatisticsProvider(provider); + for(DocumentStoreStats.Metric m: DocumentStoreStats.Metric.values()) { + assertNotNull(stats.getTimer(m)); + } + + // switch back to default + StatisticsProvider noopProvider = StatisticsProvider.NOOP; + stats.setStatisticsProvider(noopProvider); + for(DocumentStoreStats.Metric m: DocumentStoreStats.Metric.values()) { + assertNotNull(stats.getTimer(m)); + } + } + + private StatisticsProvider createDefaultStatisticsProvider() { + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + DefaultStatisticsProvider defaultStatisticsProvider = new DefaultStatisticsProvider(executorService); + + return defaultStatisticsProvider; + } +}