diff --git a/docs/_docs/monitoring-metrics/new-metrics.adoc b/docs/_docs/monitoring-metrics/new-metrics.adoc index 1b260d435e060..5f3aaeabc00c4 100644 --- a/docs/_docs/monitoring-metrics/new-metrics.adoc +++ b/docs/_docs/monitoring-metrics/new-metrics.adoc @@ -241,7 +241,7 @@ Register name: `io.statistics.cacheGroups.{group_name}` |=== -== Sorted Indexes +== Sorted Indexes I/O statistics Register name: `io.statistics.sortedIndexes.{cache_name}.{index_name}` @@ -257,8 +257,21 @@ Register name: `io.statistics.sortedIndexes.{cache_name}.{index_name}` |startTime| long| Statistics collection start time |=== +== Sorted Indexes operations -== Hash Indexes +Contains metrics about low-level operations (such as `Insert`, `Search`, etc.) on pages of sorted secondary indexes. + +Register name: `index.{schema_name}.{table_name}.{index_name}` + +[cols="2,1,3",opts="header"] +|=== +|Name | Type | Description +|{opType}Count| long| Count of {opType} operations on index. +|{opType}Time| long| Total duration (nanoseconds) of {opType} operations on index. +|=== + + +== Hash Indexes I/O statistics Register name: `io.statistics.hashIndexes.{cache_name}.{index_name}` diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index b0682d54a364f..32056ed6304a9 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -1436,6 +1436,12 @@ public final class IgniteSystemProperties { defaults = "" + IGNITE_BPLUS_TREE_LOCK_RETRIES_DEFAULT) public static final String IGNITE_BPLUS_TREE_LOCK_RETRIES = "IGNITE_BPLUS_TREE_LOCK_RETRIES"; + /** + * Disables secondary indexes B+Tree metrics. + */ + @SystemProperty(value = "Disables secondary indexes B+Tree metrics", defaults = "false") + public static final String IGNITE_BPLUS_TREE_DISABLE_METRICS = "IGNITE_BPLUS_TREE_DISABLE_METRICS"; + /** * Amount of memory reserved in the heap at node start, which can be dropped to increase the chances of success when * handling OutOfMemoryError. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java index d57c76d9cc08c..adab519a499af 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java @@ -54,11 +54,15 @@ import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.failure.FailureType.CRITICAL_ERROR; +import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName; /** * Sorted index implementation. */ public class InlineIndexImpl extends AbstractIndex implements InlineIndex { + /** */ + public static final String INDEX_METRIC_PREFIX = "index"; + /** Unique ID. */ private final UUID id = UUID.randomUUID(); @@ -553,6 +557,7 @@ private void destroy0(boolean softDel) throws IgniteCheckedException { } cctx.kernalContext().metric().remove(stats.metricRegistryName()); + cctx.kernalContext().metric().remove(metricName(INDEX_METRIC_PREFIX, def.idxName().fullName())); if (cctx.group().persistenceEnabled() || cctx.shared().kernalContext().state().clusterState().state() != INACTIVE) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java index 79e863c9dd4b5..5a7ba04481dcf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java @@ -43,6 +43,7 @@ import org.apache.ignite.internal.pagemem.PageIdUtils; import org.apache.ignite.internal.pagemem.PageMemory; import org.apache.ignite.internal.processors.cache.CacheGroupContext; +import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager; import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils; import org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter; @@ -53,7 +54,11 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO; import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIoResolver; import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList; +import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler; +import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandlerWrapper; import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataRow; +import org.apache.ignite.internal.processors.metric.MetricRegistry; +import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.internal.S; @@ -61,10 +66,13 @@ import org.apache.ignite.maintenance.MaintenanceTask; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_BPLUS_TREE_DISABLE_METRICS; +import static org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexImpl.INDEX_METRIC_PREFIX; import static org.apache.ignite.internal.cache.query.index.sorted.inline.types.NullableInlineIndexKeyType.CANT_BE_COMPARE; import static org.apache.ignite.internal.cache.query.index.sorted.inline.types.NullableInlineIndexKeyType.COMPARE_UNSUPPORTED; import static org.apache.ignite.internal.cache.query.index.sorted.maintenance.MaintenanceRebuildIndexUtils.mergeTasks; import static org.apache.ignite.internal.cache.query.index.sorted.maintenance.MaintenanceRebuildIndexUtils.toMaintenanceTask; +import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName; /** * BPlusTree where nodes stores inlined index keys. @@ -139,7 +147,8 @@ public InlineIndexTree( PageIdAllocator.FLAG_IDX, grpCtx.shared().kernalContext().failure(), grpCtx.shared().diagnostic().pageLockTracker(), - pageIoResolver + pageIoResolver, + wrapper(def) ); this.grpCtx = grpCtx; @@ -667,4 +676,69 @@ private int mvccCompare(IndexRow r1, IndexRow r2) { ", schemaName=" + idxName.schemaName() + ", tblName=" + idxName.tableName() + ", idxName=" + idxName.idxName() + ']'; } + + /** */ + private static PageHandlerWrapper wrapper(SortedIndexDefinition def) { + if (def == null || def.cacheInfo().cacheContext() == null) + return null; + + if (IgniteSystemProperties.getBoolean(IGNITE_BPLUS_TREE_DISABLE_METRICS)) + return null; + + return new PageHandlerWrapper() { + @Override public PageHandler wrap(BPlusTree tree, PageHandler hnd) { + GridCacheContext cctx = def.cacheInfo().cacheContext(); + + MetricRegistry mreg = cctx.shared().kernalContext().metric().registry( + metricName(INDEX_METRIC_PREFIX, def.idxName().fullName())); + + LongAdderMetric cnt = mreg.longAdderMetric(hnd.getClass().getSimpleName() + "Count", + "Count of " + hnd.getClass().getSimpleName() + " operations"); + LongAdderMetric time = mreg.longAdderMetric(hnd.getClass().getSimpleName() + "Time", + "Total time of " + hnd.getClass().getSimpleName() + " operations (nanoseconds)"); + + return new PageHandler() { + @Override public Result run( + int cacheId, + long pageId, + long page, + long pageAddr, + PageIO io, + Boolean walPlc, + Object arg, + int intArg, + IoStatisticsHolder statHolder + ) throws IgniteCheckedException { + if (!cctx.statisticsEnabled()) { + return ((PageHandler)hnd).run(cacheId, pageId, page, pageAddr, io, walPlc, + arg, intArg, statHolder); + } + + long ts = System.nanoTime(); + + try { + return ((PageHandler)hnd).run(cacheId, pageId, page, pageAddr, io, walPlc, + arg, intArg, statHolder); + } + finally { + cnt.increment(); + time.add(System.nanoTime() - ts); + } + } + + @Override public boolean releaseAfterWrite( + int cacheId, + long pageId, + long page, + long pageAddr, + Object arg, + int intArg + ) { + return ((PageHandler)hnd).releaseAfterWrite(cacheId, pageId, page, pageAddr, + arg, intArg); + } + }; + } + }; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java index e4b1bfdb2c2f1..055138e91729e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java @@ -987,7 +987,8 @@ protected BPlusTree( pageFlag, failureProcessor, pageLockTrackerManager, - DEFAULT_PAGE_IO_RESOLVER + DEFAULT_PAGE_IO_RESOLVER, + null ); setIos(innerIos, leafIos); @@ -1018,7 +1019,8 @@ protected BPlusTree( byte pageFlag, @Nullable FailureProcessor failureProcessor, PageLockTrackerManager pageLockTrackerManager, - PageIoResolver pageIoRslvr + PageIoResolver pageIoRslvr, + @Nullable PageHandlerWrapper hndWrapper ) { super(name, cacheGrpId, grpName, pageMem, wal, pageLockTrackerManager, pageIoRslvr, pageFlag); @@ -1034,31 +1036,33 @@ protected BPlusTree( this.failureProcessor = failureProcessor; // Initialize page handlers. - askNeighbor = (PageHandler)wrap(this, new AskNeighbor()); - search = (PageHandler)wrap(this, new Search()); - lockTailExact = (PageHandler)wrap(this, new LockTailExact()); - lockTail = (PageHandler)wrap(this, new LockTail()); - lockTailForward = (PageHandler)wrap(this, new LockTailForward()); - lockBackAndTail = (PageHandler)wrap(this, new LockBackAndTail()); - lockBackAndRmvFromLeaf = (PageHandler)wrap(this, new LockBackAndRmvFromLeaf()); - rmvFromLeaf = (PageHandler)wrap(this, new RemoveFromLeaf()); - insert = (PageHandler)wrap(this, new Insert()); - replace = (PageHandler)wrap(this, new Replace()); - rmvRangeFromLeaf = (PageHandler)wrap(this, new RemoveRangeFromLeaf()); + askNeighbor = wrap(hndWrapper, new AskNeighbor()); + search = wrap(hndWrapper, new Search()); + lockTailExact = wrap(hndWrapper, new LockTailExact()); + lockTail = wrap(hndWrapper, new LockTail()); + lockTailForward = wrap(hndWrapper, new LockTailForward()); + lockBackAndTail = wrap(hndWrapper, new LockBackAndTail()); + lockBackAndRmvFromLeaf = wrap(hndWrapper, new LockBackAndRmvFromLeaf()); + rmvFromLeaf = wrap(hndWrapper, new RemoveFromLeaf<>()); + insert = wrap(hndWrapper, new Insert()); + replace = wrap(hndWrapper, new Replace()); + rmvRangeFromLeaf = wrap(hndWrapper, new RemoveRangeFromLeaf()); } /** - * Returns a wrapper for page handler. By default, there is no wrapper. + * Returns a wrapped page handler. By default, there is no wrapper. * - * @param tree B-plus tree. + * @param hndWrapper Page handler wrapper for this tree. * @param hnd Page handler. - * @return Page handler wrapper. + * @return Wrapped page handler. */ - private PageHandler wrap(BPlusTree tree, PageHandler hnd) { - if (testHndWrapper == null) - return hnd; - else - return testHndWrapper.wrap(tree, hnd); + private PageHandler wrap(PageHandlerWrapper hndWrapper, PageHandler hnd) { + // Wrap handler using test wrapper. + if (testHndWrapper != null) + hnd = testHndWrapper.wrap(this, hnd); + + // Additionally wrap using tree page handler wrapper, if it's specified. + return (PageHandler)(hndWrapper == null ? hnd : hndWrapper.wrap(this, hnd)); } /** diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BPlusTreeMetricsTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BPlusTreeMetricsTest.java new file mode 100644 index 0000000000000..d977fba73bab8 --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BPlusTreeMetricsTest.java @@ -0,0 +1,228 @@ +/* + * 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.ignite.internal.processors.cache.index; + +import org.apache.ignite.IgniteCache; +import org.apache.ignite.IgniteSystemProperties; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.cache.query.annotations.QuerySqlField; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexImpl; +import org.apache.ignite.spi.metric.LongMetric; +import org.apache.ignite.spi.metric.ReadOnlyMetricRegistry; +import org.apache.ignite.testframework.junits.WithSystemProperty; +import org.junit.Test; + +/** + * Tests BPlusTree metrics. + */ +public class BPlusTreeMetricsTest extends AbstractIndexingCommonTest { + /** */ + private static final String INSERT_CNT = "InsertCount"; + + /** */ + private static final String REMOVE_CNT = "RemoveFromLeafCount"; + + /** */ + private static final String SEARCH_CNT = "SearchCount"; + + /** */ + private static final String INSERT_TIME = "InsertTime"; + + /** */ + private static final String REMOVE_TIME = "RemoveFromLeafTime"; + + /** */ + private static final String SEARCH_TIME = "SearchTime"; + + /** + * @return Default cache configuration. + */ + private CacheConfiguration cacheConfiguration(String cacheName) { + CacheConfiguration ccfg = new CacheConfiguration<>(cacheName); + + ccfg.setIndexedTypes(Integer.class, TestClass.class); + ccfg.setStatisticsEnabled(true); + + return ccfg; + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + stopAllGrids(); + + super.afterTest(); + } + + /** + * Test metrics exposed by index BPlusTree. + * + * @throws Exception If failed. + */ + @Test + public void testValues() throws Exception { + IgniteEx ignite = startGrid(0); + + IgniteCache cache = ignite.getOrCreateCache(cacheConfiguration(DEFAULT_CACHE_NAME)); + + ReadOnlyMetricRegistry intFieldReg = findRegistry(ignite, "intField"); + ReadOnlyMetricRegistry strFieldReg = findRegistry(ignite, "strField"); + + assertEquals(0, metric(intFieldReg, INSERT_CNT)); + assertEquals(0, metric(strFieldReg, INSERT_CNT)); + assertEquals(0, metric(intFieldReg, INSERT_TIME)); + assertEquals(0, metric(strFieldReg, INSERT_TIME)); + + for (int i = 0; i < 5000; i++) + cache.put(i, new TestClass(i)); + + // This metric includes insert to inner nodes, so value can exceed count of inserted keys. + assertTrue(metric(intFieldReg, INSERT_CNT) > 5000); + assertTrue(metric(strFieldReg, INSERT_CNT) > 5000); + assertTrue(metric(intFieldReg, INSERT_TIME) > 0); + assertTrue(metric(strFieldReg, INSERT_TIME) > 0); + + assertEquals(0, metric(intFieldReg, REMOVE_CNT)); + assertEquals(0, metric(strFieldReg, REMOVE_CNT)); + assertEquals(0, metric(intFieldReg, REMOVE_TIME)); + assertEquals(0, metric(strFieldReg, REMOVE_TIME)); + + for (int i = 0; i < 1000; i++) + cache.remove(i); + + assertEquals(1000, metric(intFieldReg, REMOVE_CNT)); + assertEquals(1000, metric(strFieldReg, REMOVE_CNT)); + assertTrue(metric(intFieldReg, REMOVE_TIME) > 0); + assertTrue(metric(strFieldReg, REMOVE_TIME) > 0); + + long searchOnIntCnt = metric(intFieldReg, SEARCH_CNT); + long searchOnStrCnt = metric(strFieldReg, SEARCH_CNT); + long searchOnIntTime = metric(intFieldReg, SEARCH_TIME); + long searchOnStrTime = metric(strFieldReg, SEARCH_TIME); + + cache.query(new SqlFieldsQuery("SELECT * FROM TESTCLASS WHERE INTFIELD = ?").setArgs(3000)).getAll(); + + assertTrue(metric(intFieldReg, SEARCH_CNT) > searchOnIntCnt); + assertEquals(searchOnStrCnt, metric(strFieldReg, SEARCH_CNT)); + assertTrue(metric(intFieldReg, SEARCH_TIME) > searchOnIntTime); + assertEquals(searchOnStrTime, metric(strFieldReg, SEARCH_TIME)); + } + + /** + * Test metrics when cache statistics is disabled. + * + * @throws Exception If failed. + */ + @Test + public void testDisableStatistics() throws Exception { + IgniteEx ignite = startGrid(0); + + IgniteCache cache = ignite.getOrCreateCache(cacheConfiguration(DEFAULT_CACHE_NAME)); + + ReadOnlyMetricRegistry intFieldReg = findRegistry(ignite, "intField"); + + cache.enableStatistics(false); + + cache.put(0, new TestClass(0)); + + assertEquals(0, metric(intFieldReg, INSERT_CNT)); + assertEquals(0, metric(intFieldReg, INSERT_TIME)); + + cache.query(new SqlFieldsQuery("SELECT * FROM TESTCLASS WHERE INTFIELD = ?").setArgs(0)).getAll(); + + assertEquals(0, metric(intFieldReg, SEARCH_CNT)); + assertEquals(0, metric(intFieldReg, SEARCH_TIME)); + + cache.remove(0); + + assertEquals(0, metric(intFieldReg, REMOVE_CNT)); + assertEquals(0, metric(intFieldReg, REMOVE_TIME)); + + cache.enableStatistics(true); + + cache.put(0, new TestClass(0)); + + assertTrue(metric(intFieldReg, INSERT_CNT) > 0); + assertTrue(metric(intFieldReg, INSERT_TIME) > 0); + + cache.query(new SqlFieldsQuery("SELECT * FROM TESTCLASS WHERE INTFIELD = ?").setArgs(0)).getAll(); + + assertTrue(metric(intFieldReg, SEARCH_CNT) > 0); + assertTrue(metric(intFieldReg, SEARCH_TIME) > 0); + + cache.remove(0); + + assertTrue(metric(intFieldReg, REMOVE_CNT) > 0); + assertTrue(metric(intFieldReg, REMOVE_TIME) > 0); + } + + /** + * Test disable metrics by system property. + * + * @throws Exception If failed. + */ + @Test + @WithSystemProperty(key = IgniteSystemProperties.IGNITE_BPLUS_TREE_DISABLE_METRICS, value = "true") + public void testDisableMetrics() throws Exception { + IgniteEx ignite = startGrid(0); + + ignite.getOrCreateCache(cacheConfiguration(DEFAULT_CACHE_NAME)); + + for (ReadOnlyMetricRegistry reg : ignite.context().metric()) + assertFalse(reg.name().startsWith(InlineIndexImpl.INDEX_METRIC_PREFIX)); + } + + /** */ + private ReadOnlyMetricRegistry findRegistry(IgniteEx ignite, String fieldName) { + for (ReadOnlyMetricRegistry reg : ignite.context().metric()) { + if (reg.name().startsWith(InlineIndexImpl.INDEX_METRIC_PREFIX) + && reg.name().toUpperCase().contains(fieldName.toUpperCase())) + return reg; + } + + throw new AssertionError("Not found metric registry for index on field " + fieldName); + } + + /** */ + private long metric(ReadOnlyMetricRegistry reg, String metric) { + LongMetric m = reg.findMetric(metric); + + if (m == null) + throw new AssertionError("Not found metric " + metric + " in registry " + reg.name()); + + return m.value(); + } + + /** */ + private static class TestClass { + /** */ + @QuerySqlField(index = true) + private final int intField; + + /** */ + @QuerySqlField(index = true) + private final String strField; + + /** */ + public TestClass(int val) { + intField = val; + strField = "str" + val; + } + } +} diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java index a7af46f773634..78bf075031462 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java +++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java @@ -72,6 +72,7 @@ import org.apache.ignite.internal.processors.cache.distributed.replicated.IgniteCacheReplicatedQueryP2PDisabledSelfTest; import org.apache.ignite.internal.processors.cache.distributed.replicated.IgniteCacheReplicatedQuerySelfTest; import org.apache.ignite.internal.processors.cache.index.ArrayIndexTest; +import org.apache.ignite.internal.processors.cache.index.BPlusTreeMetricsTest; import org.apache.ignite.internal.processors.cache.index.BasicIndexMultinodeTest; import org.apache.ignite.internal.processors.cache.index.BasicIndexTest; import org.apache.ignite.internal.processors.cache.index.ComplexPrimaryKeyUnwrapSelfTest; @@ -203,6 +204,7 @@ ArrayIndexTest.class, BasicIndexMultinodeTest.class, IndexMetricsTest.class, + BPlusTreeMetricsTest.class, // Misc tests. QueryEntityValidationSelfTest.class,